Reputation: 39141
How to integrate an external, black-box build system like go build
into Make?
I don't think this question is specific to Go, but I will use it as an example. The go build system tracks the relation between inputs and outputs (= the dependencies) internally, and avoids rebuilding the output if no inputs have changed.
I have a Makefile which contains targets based on shell scripts and targets based on invoking go build
, for example:
my-exe:
go build <some-url>
intermediate: my-exe
<expensive shell script>
test: intermediate test-data
<some test>
Requirements:
Let the make goal be test
.
test-data
, I don't want to run the intermediate
target.Options considered:
It is possible to list the go source files as dependencies of my-exe
. However, my go source folder contains files for multiple targets, and I would have to somehow list the right files/folders in my Makefile. I could also overshoot and list all go source files as dependencies in the Makefile.
If I turn my-exe
into a phony target, then requirement 2. is fulfilled but 1. is broken.
Upvotes: 0
Views: 383
Reputation: 100956
Renaud's solution will work. But as in my comment above, I think all you need to do is take advantage of the FORCE target trick described here: https://www.gnu.org/software/make/manual/html_node/Force-Targets.html
Change your makefile to this:
FORCE:
my-exe: FORCE
go build <some-url>
intermediate: my-exe
<expensive shell script>
test: intermediate test-data
<some test>
By adding a prerequisite that can never be satisfied to my-exe
you will force it to always be built. Assuming that the go build ...
command doesn't actually update the my-exe
target if nothing changed and does update it when something changed, this makefile will work the way you want.
Upvotes: 2
Reputation: 39141
If we turn the external build step into a phony target, it will always be built. However, any target which depends on a phony target is also always built. GNU make documentation:
A phony target should not be a prerequisite of a real target file; if it is, its recipe will be run every time make goes to update that file.
There are two ways to use this:
.PHONY: always-rebuild
my-exe: always-rebuild
go build -o my-exe <some-url> # creates my-exe
intermediate: my-exe
<expensive shell script>
test: intermediate test-data
<some test>
(GNU) make evaluates the timestamp of my-exe
after the target my-exe
has run. If the target did not change the timestamp, then the succeeding steps (intermediate, test) are not run.
.PHONY: external_my-exe
external_my-exe:
go build -o my-exe <some-url> # creates my-exe
my-exe: external_my-exe
# do nothing!
true
intermediate: my-exe
<expensive shell script>
test: intermediate test-data
<some test>
external_my-exe
is always built (if it occurs in the dependency tree of the make goal).my-exe
is always built because it depends on a phony target.intermediate
depends on an actual file (my-exe
), so it is only run if the file's timestamp changed.Upvotes: 1
Reputation: 29280
Let's abstract all this a bit with 2 targets: always
and expensive
. We want to always build always
because it is cheap and it relies on a external build system. But we want to build expensive
only if building always
changed something. The solution consists in declaring always
as phony, such that it is always remade, but not declaring it as a prerequisite of expensive
, else it will always be remade too. We thus need a third target, relay
that is not phony, that is also cheap and that will really change only if always
did something.
In the following the GO
make variable is used to emulate the external build system. If it is set to a non-empty string always
changes, else it stays the same.
expensive: relay
@echo "$@"
@touch "$@"
relay: always
@echo "$@"
@if ! [ -f "$@" ] || [ "$<" -nt "$@" ]; then touch "$@"; fi
.PHONY: always
always:
@echo "$@"
@if ! [ -f "$@" ] || [ -n "$(GO)" ]; then touch "$@"; fi
And that's it:
$ make
always
relay
expensive
$ make
always
relay
$ make GO=1
always
relay
expensive
Upvotes: 1