Commit | Line | Data |
---|---|---|
ac64c802 MM |
1 | # Mage Build Tool by Matt McCutchen |
2 | # http://www.kepreon.com/~matt/mage/ | |
00804b7c | 3 | |
ac64c802 MM |
4 | # Remember the original default goal so we can restore it at the end of mage.mk. |
5 | mg-orig-default-goal := $(.DEFAULT_GOAL) | |
6 | ||
7 | # TEXT UTILITIES | |
00804b7c MM |
8 | empty := |
9 | bs := \$(empty) | |
10 | define nl | |
11 | ||
12 | ||
13 | endef | |
14 | # Shell-quote: a'b => 'a'\''b' | |
15 | sq = '$(subst ','\'',$1)' | |
16 | # Make-quote: a$\nb => a$$$(nl)b | |
17 | # This is enough to assign the value, *but not to use it as an argument!* | |
18 | mqas = $(subst $(nl),$$(nl),$(subst $$,$$$$,$1)) | |
19 | # Return nonempty if the strings are equal, empty otherwise. | |
20 | # If the strings have only nice characters, you can do $(filter x$1,x$2). | |
21 | streq = $(findstring x$1,$(findstring x$2,x$1)) | |
22 | ||
23 | # $(call fmt-make-assignment,foo,bar) | |
24 | # Output a make assignment that stores bar in variable foo. | |
25 | # The result is like `foo:=bar' but handles leading spaces, $, and | |
26 | # newlines appearing in bar safely. | |
27 | fmt-make-assignment = $1:=$$(empty)$(call mqas,$2) | |
28 | ||
ac64c802 | 29 | # OBFUSCATION |
00804b7c | 30 | |
ac64c802 MM |
31 | # Coming soon. |
32 | ||
33 | # High-priority implicit rule to ensure that we don't try to actually update | |
34 | # obfuscated targets. TEMP | |
35 | /./proc/self/cwd/%: | |
36 | ||
00804b7c | 37 | |
ac64c802 MM |
38 | # MAIN BUILD LOGIC |
39 | ||
40 | # bar.g file format: | |
41 | # bar@cmd:=cat foo >bar.tmp | |
42 | # bar@warnings:=$(empty)yikes! | |
43 | # And if dependency-logging: | |
44 | # List of filename@revision, revision is x for exists and empty for doesn't | |
45 | # exist. Later perhaps x will be the mtime. | |
46 | # bar@deps:=included@x oops@ | |
47 | # If bar is overridden, we clear all three variables. | |
48 | ||
49 | # $(call gload,foo.o) | |
00804b7c | 50 | # Make sure foo.o's genfile, if any, has been loaded. |
ac64c802 MM |
51 | define gload |
52 | $(if $($1@gloaded),,$(eval | |
53 | $1@cmd:= | |
54 | $1@warnings:= | |
55 | $1@deps:= | |
00804b7c | 56 | -include $1.g |
ac64c802 | 57 | $1@gloaded:=1 |
00804b7c MM |
58 | )) |
59 | endef | |
60 | ||
61 | # $(call mg-translate-cmd,touch $$@) | |
ac64c802 MM |
62 | # Translates $@, $<, $^, $+, $* to Mage-ized variables if necessary. |
63 | # We won't support $%, $?, $|. | |
64 | # There might be false matches, e.g., $$@ => $$(mg@) ; | |
00804b7c MM |
65 | # to prevent that, do $$$(empty)@ . |
66 | define mg-translate-cmd | |
67 | $(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1)))) | |
68 | endef | |
69 | ||
70 | # OOOH!!! .SECONDEXPANSION does let us watch the implicit rule search as it | |
71 | # happens. | |
72 | .SECONDEXPANSION: | |
73 | ||
74 | # $(call mg-rule,target,prerequisite,cmd) | |
75 | # Defines a rule. | |
76 | # If cmd uses $@, quote if necessary so this function sees $@, etc. | |
77 | # | |
ac64c802 | 78 | # When we generate bar from foo using a Mage rule: |
00804b7c | 79 | # Division of work: |
ac64c802 MM |
80 | # - "bar.g: foo" generates bar if necessary. |
81 | # - "bar: bar.g" loads new genfile and replays warnings if bar.g was | |
82 | # already up to date. | |
00804b7c MM |
83 | # Cases: |
84 | # - bar is managed and up to date => replay | |
ac64c802 | 85 | # - bar doesn't exist or is managed and out of date => generate |
00804b7c | 86 | # - bar is unmanaged => warn about override |
ac64c802 | 87 | MG-FORCE: |
ce8fee85 | 88 | .PHONY: MG-FORCE |
ac64c802 | 89 | define mg-define-rule |
00804b7c MM |
90 | $(eval |
91 | ||
92 | # Define some target-specific variables. | |
93 | # It might look like we could use $*, but $1 most likely isn't %. | |
94 | $1.g: target = $$(@:.g=) | |
95 | $1.g: cmd = $(call mg-translate-cmd,$3) | |
96 | $1.g: mg@ = $$(target).tmp | |
ac64c802 MM |
97 | $1.g: mg^ = $$(filter-out MG-% /./%,$$^) |
98 | $1.g: mg+ = $$(filter-out MG-% /./%,$$+) | |
00804b7c MM |
99 | $1.g: mg< = $$(firstword $$(mg^)) |
100 | ||
ac64c802 MM |
101 | # Rule for the genfile. Evidently all the prerequisites we want second-expanded |
102 | # have to go on the same rule. | |
103 | $1.g: $2 $$$$(mg-scout-target) MG-FORCE | |
104 | $$(mg-generate) | |
105 | ||
106 | # If the file was regenerated, load the new genfile. | |
107 | # If not, replay any warnings. | |
108 | ## We don't have to worry about .DELETE_ON_ERROR deleting the target because | |
109 | ## it is only touched if we *successfully* regenerate it. | |
110 | # - No longer applies because we don't remember errors. | |
111 | # HMMM Maybe errors should go to stderr??? | |
112 | $1: MG-FORCE | $1.g | |
113 | $$(call gload,$$@) | |
114 | $$(if $$($$@@warnings),$$(if $$($$@@built),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),) | |
115 | ) | |
116 | endef | |
00804b7c | 117 | |
00804b7c MM |
118 | # If the target is unmanaged, we must run the rule; we'll see that the |
119 | # obfuscated target is in $? and complain. | |
ac64c802 | 120 | mg-scout-target=$(if $(wildcard $(target)),/./proc/self/cwd/$(target),) |
00804b7c MM |
121 | |
122 | # If the command changed, we must regenerate. | |
ac64c802 | 123 | mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x) |
00804b7c MM |
124 | |
125 | # Just add additional prerequisites. | |
ac64c802 MM |
126 | # I don't think this works for implicit rules. |
127 | define mg-define-prereq | |
00804b7c MM |
128 | $(eval |
129 | $1.g: $2 | |
130 | ) | |
131 | endef | |
132 | ||
ac64c802 | 133 | # Procedure to generate bar. Remember, $@ is bar.g. |
00804b7c | 134 | # If an override: |
ac64c802 MM |
135 | # 1. Complain. (Should we stop "`bar' is up to date" using @:; ?) |
136 | # 2. Clear out the warnings so we don't replay them. | |
137 | # Otherwise, check prereqs, command, and nonexistence to decide whether bar | |
138 | # needs to be regenerated. If so: | |
139 | # 1. Set a flag so Mage knows to reread the genfile when make runs target | |
140 | # "bar". | |
141 | # 2. Echo the command being run. | |
142 | # On error, skip to 8: | |
143 | # 3. Open the new genfile bar.g.tmp for writing. | |
144 | # 4. Store the command in bar.g.tmp. | |
145 | # 5. Run the command to bar.tmp, storing warnings in bar.g.tmp. | |
146 | # 6. If bar.tmp differs from bar: | |
147 | # a. Clear and touch bar.g (so bar doesn't become an override if we're | |
148 | # killed between steps 6b and 7). | |
149 | # b. Move in the new bar. | |
150 | # 7. Now that bar is known to be up to date, move in the new bar.g. | |
151 | # 8. Defensively remove both temporary files and exit with the exit code | |
152 | # from before the removal. (trap EXIT) | |
153 | define mg-generate | |
154 | $(call gload,$(target))\ | |
155 | $(if $(filter /./proc/self/cwd/$(target),$?),\ | |
00804b7c | 156 | $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\ |
ac64c802 | 157 | $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\ |
00804b7c | 158 | ,\ |
ac64c802 MM |
159 | $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\ |
160 | $(eval $(target)@gloaded:=)$(eval $(target)@built:=1)$(info $(cmd))\ | |
161 | @trap 'rm -f $(target).tmp $@.tmp' EXIT &&\ | |
162 | exec 3>$@.tmp && $(mg-assign-cmd) >&3 &&\ | |
163 | set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\ | |
164 | $(mg-maybe-move-target) && mv -f $@.tmp $@\ | |
165 | )) | |
00804b7c MM |
166 | endef |
167 | ||
ac64c802 MM |
168 | # Pieces of mg-generate that I factored out to make mg-generate more readable. |
169 | mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$(target)@cmd,$(cmd))) | |
170 | mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; } | |
171 | mg-wrap-warnings=sed -re '1s/^/$(target)@warnings:=$$(empty)/; 1!s/^/$(target)@warnings+=$$(nl)/' | |
172 | mg-maybe-move-target={ cmp -s $(target) $(target).tmp || echo >$@ && mv -f $(target).tmp $(target); } | |
173 | ||
174 | # END | |
175 | ||
00804b7c | 176 | # We don't want anything we defined to become the default goal. |
ac64c802 | 177 | .DEFAULT_GOAL := $(mg-orig-default-goal) |