dyp
dyp

Reputation: 39141

Integrating an external build system with (GNU) make

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.

  1. When I touch the test-data, I don't want to run the intermediate target.
  2. When I touch the go source code, I want to run all steps.

Options considered:

Upvotes: 0

Views: 383

Answers (3)

MadScientist
MadScientist

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

dyp
dyp

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:


  1. Make the external build step depend on a phony target:
.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.


  1. Introduce a dummy target to remove the "phony" property:
.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

Renaud Pacalet
Renaud Pacalet

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

Related Questions