47650b323d69ad75ce97274e53f22ff8f40290fc
[mgear/mgear.git] / src / mage.mk
1 # Mage Build Tool by Matt McCutchen
2 # http://www.kepreon.com/~matt/mage/
3
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
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
29 # OBFUSCATION
30
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         
37
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)
50 # Make sure foo.o's genfile, if any, has been loaded.
51 define gload
52 $(if $($1@gloaded),,$(eval 
53 $1@cmd:=
54 $1@warnings:=
55 $1@deps:=
56 -include $1.g
57 $1@gloaded:=1
58 ))
59 endef
60
61 # $(call mg-translate-cmd,touch $$@)
62 # Translates $@, $<, $^, $+, $* to Mage-ized variables if necessary.
63 # We won't support $%, $?, $|.
64 # There might be false matches, e.g., $$@ => $$(mg@) ;
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
78 # When we generate bar from foo using a Mage rule:
79 # Division of work:
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.
83 # Cases:
84 # - bar is managed and up to date => replay
85 # - bar doesn't exist or is managed and out of date => generate
86 # - bar is unmanaged => warn about override 
87 MG-FORCE:
88 MG-FORCE-TARGET-DNE:
89 .PHONY: MG-FORCE MG-FORCE-TARGET-DNE
90 define mg-define-rule
91 $(eval 
92
93 # Define some target-specific variables.
94 # It might look like we could use $*, but $1 most likely isn't %.
95 $1.g: target = $$(@:.g=)
96 $1.g: cmd = $(call mg-translate-cmd,$3)
97 $1.g: mg@ = $$(target).tmp
98 $1.g: mg^ = $$(filter-out MG-% /./%,$$^)
99 $1.g: mg+ = $$(filter-out MG-% /./%,$$+)
100 $1.g: mg< = $$(firstword $$(mg^))
101
102 # Rule for the genfile.  Evidently all the prerequisites we want second-expanded
103 # have to go on the same rule.
104 $1.g: $2 $$$$(mg-scout-target) MG-FORCE
105         $$(mg-generate)
106
107 # If the file was regenerated, load the new genfile.
108 # If not, replay any warnings.
109 ## We don't have to worry about .DELETE_ON_ERROR deleting the target because
110 ## it is only touched if we *successfully* regenerate it.
111 # - No longer applies because we don't remember errors.
112 # HMMM Maybe errors should go to stderr???
113 $1: MG-FORCE | $1.g
114         $$(call gload,$$@)
115         $$(if $$($$@@warnings),$$(if $$($$@@built),,$$(info $$($$@@cmd) # warning replay)$$(info $$($$@@warnings))),)
116 )
117 endef
118
119 # If the target is unmanaged, we must run the rule; we'll see that the
120 # obfuscated target is in $? and complain.
121 mg-scout-target=$(if $(wildcard $(target)),/./proc/self/cwd/$(target),)
122
123 # If the command changed, we must regenerate.
124 mg-check-cmd=$(if $(call streq,$(cmd),$($(target)@cmd)),,x)
125
126 # Just add additional prerequisites.
127 # I don't think this works for implicit rules.
128 define mg-define-prereq
129 $(eval 
130 $1.g: $2
131 )
132 endef
133
134 # Procedure to generate bar.  Remember, $@ is bar.g.
135 # If an override:
136 #     1. Complain.  (Should we stop "`bar' is up to date" using @:; ?)
137 #     2. Clear out the warnings so we don't replay them.
138 # Otherwise, check prereqs, command, and nonexistence to decide whether bar
139 # needs to be regenerated.  If so:
140 #     1. Set a flag so Mage knows to reread the genfile when make runs target
141 #        "bar".
142 #     2. Echo the command being run.
143 #     On error, skip to 8:
144 #        3. Open the new genfile bar.g.tmp for writing.
145 #        4. Store the command in bar.g.tmp.
146 #        5. Run the command to bar.tmp, storing warnings in bar.g.tmp.
147 #        6. If bar.tmp differs from bar:
148 #           a. Clear and touch bar.g (so bar doesn't become an override if we're
149 #              killed between steps 6b and 7).
150 #           b. Move in the new bar.
151 #        7. Now that bar is known to be up to date, move in the new bar.g.
152 #     8. Defensively remove both temporary files and exit with the exit code
153 #        from before the removal. (trap EXIT)
154 define mg-generate
155         $(call gload,$(target))\
156         $(if $(filter /./proc/self/cwd/$(target),$?),\
157                 $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\
158                 $(eval $(target)@cmd:=)$(eval $(target)@warnings:=)$(eval $(target)@deps:=)\
159         ,\
160         $(if $?$(if $(wildcard $(target)),,x)$(mg-check-cmd),\
161         $(eval $(target)@gloaded:=)$(eval $(target)@built:=1)$(info $(cmd))\
162         @trap 'rm -f $(target).tmp $@.tmp' EXIT &&\
163         exec 3>$@.tmp && $(mg-assign-cmd) >&3 &&\
164         set -o pipefail && { $(mg-run-cmd) | tee /dev/fd/4 | $(mg-wrap-warnings) >&3; } 4>&1 &&\
165         $(mg-maybe-move-target) && mv -f $@.tmp $@\
166         ))
167 endef
168
169 # Pieces of mg-generate that I factored out to make mg-generate more readable.
170 mg-assign-cmd=echo $(call sq,$(call fmt-make-assignment,$(target)@cmd,$(cmd)))
171 mg-run-cmd={ ($(cmd)) 2>&1 && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; }
172 mg-wrap-warnings=sed -re '1s/^/$(target)@warnings:=$$(empty)/; 1!s/^/$(target)@warnings+=$$(nl)/'
173 mg-maybe-move-target={ cmp -s $(target) $(target).tmp || echo >$@ && mv -f $(target).tmp $(target); }
174
175 # END
176
177 # We don't want anything we defined to become the default goal.
178 .DEFAULT_GOAL := $(mg-orig-default-goal)