More experimentation with obfuscation. Wondering why it is slow.
[mgear/mgear.git] / src / mage.mk
CommitLineData
00804b7c
MM
1# Mage by Matt McCutchen
2
3# Text utilities
4empty :=
5bs := \$(empty)
6define nl
7
8
9endef
10# Shell-quote: a'b => 'a'\''b'
11sq = '$(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!*
14mqas = $(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).
17streq = $(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.
23fmt-make-assignment = $1:=$$(empty)$(call mqas,$2)
24
25mg-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.
34define mg-check-file
35$(if $(mg-checked-$1),,$(eval
36cmd-$1 :=
37errors-$1 :=
38exitcode-$1 :=
39-include $1.g
40mg-checked-$1 := done
41))
42endef
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)@ .
49define mg-translate-cmd
50$(subst $$@,$$(mg@),$(subst $$<,$$(mg<),$(subst $$^,$$(mg^),$(subst $$+,$$(mg+),$1))))
51endef
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
69MG-FORCE-TARGET-DNE:
70MG-FORCE-CMD-CHANGED:
71MG-FORCE-REPLAY:
72.PHONY: MG-FORCE-TARGET-DNE MG-FORCE-CMD-CHANGED MG-FORCE-REPLAY
73define 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)
109endef
110
111# Just add additional prerequisites.
112define mg-prereq
113$(eval
114$1.g: $2
115)
116endef
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.
127define 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 )
142endef
143
144# We don't want anything we defined to become the default goal.
145.DEFAULT_GOAL := $(mg-orig-default-goal)