Here's what I have so far related to the Mage build tool.
[mgear/mgear.git] / src / mage.mk
1 # Mage by Matt McCutchen
2
3 # Text utilities
4 empty :=
5 bs := \$(empty)
6 define nl
7
8
9 endef
10 # Shell-quote: a'b => 'a'\''b'
11 sq = '$(subst ','\'',$1)'
12 # Make-quote: a$\nb => a$$$(nl)b
13 # This is enough to assign the value, *but not to use it as an argument!*
14 mqas = $(subst $(nl),$$(nl),$(subst $$,$$$$,$1))
15 # Return nonempty if the strings are equal, empty otherwise.
16 # If the strings have only nice characters, you can do $(filter x$1,x$2).
17 streq = $(findstring x$1,$(findstring x$2,x$1))
18
19 # $(call fmt-make-assignment,foo,bar)
20 # Output a make assignment that stores bar in variable foo.
21 # The result is like `foo:=bar' but handles leading spaces, $, and
22 # newlines appearing in bar safely.
23 fmt-make-assignment = $1:=$$(empty)$(call mqas,$2)
24
25 mg-orig-default-goal := $(.DEFAULT_GOAL)
26
27 # bar.g file format:
28 # cmd-bar:=cat foo >bar.tmp
29 # errors-bar:=$(empty)yikes!
30 # exitcode-bar:=0
31
32 # $(call mg-check-file,foo.o)
33 # Make sure foo.o's genfile, if any, has been loaded.
34 define mg-check-file
35 $(if $(mg-checked-$1),,$(eval 
36 cmd-$1 :=
37 errors-$1 :=
38 exitcode-$1 :=
39 -include $1.g
40 mg-checked-$1 := done
41 ))
42 endef
43
44 # $(call mg-translate-cmd,touch $$@)
45 # Currently translates $@, $<, $^, $+ to Mage-ized variables.
46 # $* needs no translation.  We won't support $%, $?, $|.
47 # There might be false matches, e.g., $$@ => $$(out) ;
48 # to prevent that, do $$$(empty)@ .
49 define mg-translate-cmd
50 $(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))
51 endef
52
53 # OOOH!!! .SECONDEXPANSION does let us watch the implicit rule search as it
54 # happens.
55 .SECONDEXPANSION:
56
57 # $(call mg-rule,target,prerequisite,cmd)
58 # Defines a rule.
59 # If cmd uses $@, quote if necessary so this function sees $@, etc.
60
61 # When we build bar from foo using a Mage rule:
62 # Division of work:
63 # - "bar.g: foo" does the computation.
64 # - "bar: bar.g" replays errors and exit code.
65 # Cases:
66 # - bar is managed and up to date => replay
67 # - bar doesn't exist or is managed and out of date => compute
68 # - bar is unmanaged => warn about override 
69 MG-FORCE-TARGET-DNE:
70 MG-FORCE-CMD-CHANGED:
71 MG-FORCE-REPLAY:
72 .PHONY: MG-FORCE-TARGET-DNE MG-FORCE-CMD-CHANGED MG-FORCE-REPLAY
73 define mg-rule
74 $(eval 
75
76 # Define some target-specific variables.
77 # It might look like we could use $*, but $1 most likely isn't %.
78 $1.g: target = $$(@:.g=)
79 $1.g: cmd = $(call mg-translate-cmd,$3)
80 $1.g: mg@ = $$(target).tmp
81 $1.g: mg^ = $$(filter-out MG-% $$(abspath $$(target)),$$^)
82 $1.g: mg+ = $$(filter-out MG-% $$(abspath $$(target)),$$+)
83 $1.g: mg< = $$(firstword $$(mg^))
84
85 # Load the target's genfile if necessary.
86 $1.g: $$$$(call mg-check-file,$$$$(target))
87
88 # If the target doesn't exist, we must run the rule; we'll generate it.
89 # If the target is unmanaged, we must run the rule; we'll see that the
90 # obfuscated target is in $? and complain.
91 $1.g: $$$$(if $$$$(wildcard $$$$(target)),$$$$(abspath $$$$(target)),$$$$(if $$(exitcode-$$$$(target)),,MG-FORCE-TARGET-DNE))
92
93 # If the command changed, we must regenerate.
94 $1.g: $$$$(if $$$$(call streq,$$$$(cmd),$$$$(cmd-$$$$(target))),,MG-FORCE-CMD-CHANGED)
95
96 $1.g: $2
97         $(value mg-commands)
98
99 # Replay the errors and the exit code (if any of either).
100 # We don't have to worry about .DELETE_ON_ERROR deleting the target because
101 # it is only touched if we *successfully* regenerate it.
102 # Recheck the file in case it was regenerated.
103 # HMMM Maybe errors should go to stderr???
104 $1: $1.g MG-FORCE-REPLAY
105         $$(call mg-check-file,$$@)
106         $$(if $$(errors-$$@)$$(exitcode-$$@)$$(built-$$@),$$(info $$(cmd-$$@)$$(if $$(built-$$@),, [replay]))$$(if $$(errors-$$@),$$(info $$(errors-$$@)),),)
107         $$(if $$(exitcode-$$@),@exit $$(exitcode-$$@),)
108 )
109 endef
110
111 # Just add additional prerequisites.
112 define mg-prereq
113 $(eval 
114 $1.g: $2
115 )
116 endef
117
118 # If an override:
119 #     1. Complain.
120 #     2. Clear out the errors and exit code so we don't replay them.
121 # Otherwise:
122 #     1. Store the command.
123 #     2. Run the command, storing errors.
124 #     3. Store exit code.
125 #     4. Update the real files as applicable.
126 #     5. Read the new genfile.
127 define mg-commands
128         $(if $(filter $(abspath $(target)),$?),\
129                 $(info mage: warning: Manually created/modified file at $(target) overrides rule.)\
130                 $(eval cmd-$(target):=)$(eval errors-$(target):=)$(eval exitcode-$(target):=)\
131         ,\
132         @$(eval mg-checked-$(target) :=)$(eval built-$(target) := yes)\
133         exec 3>$@.tmp &&\
134         echo $(call sq,$(call fmt-make-assignment,cmd-$(target),$(cmd))) >&3 &&\
135         set -o pipefail &&\
136         { { ($(cmd)) && { [ -r $(target).tmp ] || { echo 'mage: error: Command for $(target) succeeded without creating it!'; false; }; }; } 2>&1\
137         | sed -re '1s/^/errors-$(target):=$$(empty)/; 1!s/^/errors-$(target)+=$$(nl)/' >&3; xc=$$?; } &&\
138         { [ $$xc == 0 ] || echo "exitcode-$(target):=$$xc" >&3; } &&\
139         if [ $$xc != 0 ] || cmp -s $(target) $(target).tmp; then mv -f $@.tmp $@ && rm -f $(target).tmp;\
140         else rm -f $(target) && mv -f $@.tmp $@ && mv -f $(target).tmp $(target); fi\
141         )
142 endef
143
144 # We don't want anything we defined to become the default goal.
145 .DEFAULT_GOAL := $(mg-orig-default-goal)