0

I have a build target for clean/verbose, but it's not propagating properly:

clean:
    $(eval export CLEAN_BUILD:=--no-cache)

verbose:
    $(eval export DOCKER_VERBOSE:=BUILDKIT_PROGRESS=plain)

docker.stop.rebuild:
    make -j 2 docker.build docker.stop

docker.build:
    $(DOCKER_VERBOSE) docker compose build $(CLEAN_BUILD) $1

My goal is to be able to say make clean verbose docker.stop.rebuild and have the final commands run differently than they would if I had run make docker.stop.rebuild without the other targets.

It seems like those variables are not sticking. Sometimes it works as I expected, and other times it works for part of the docker build and then stops working. I've tried a bunch of permutations (= vs :=, using shell, using && to try and force it to pass on the rebuild goal, etc.)

So I tried this, based on this thread: Export environment variables from makefile to shell

clean: CLEAN_BUILD:=--no-cache

verbose: DOCKER_VERBOSE:=BUILDKIT_PROGRESS=plain

But when I do that, I get this error: Makefile:81: *** recipe commences before first target. Stop.

Any idea why? I've tried with GNU Make 4.3 and 4.4.1 and both have this behavior.

user3534080
  • 1,201
  • 1
  • 14
  • 21
  • 1
    I don't understand what you're trying to do: these makefile rules make no sense. They set a variable, then they stop and make exits so those variable settings are lost. Maybe if you explained what you expected the makefile to do we could help. The error you get is explained in the manual: https://www.gnu.org/software/make/manual/html_node/Error-Messages.html It means your makefile is malformed but since you haven't shown us the entire makefile, we can't help with that. – MadScientist May 06 '23 at 21:52
  • The makefile language is a *rule specification* language, not a programming language. Appropriately so, because `make` is a rules engine, not a programming language interpreter. If you want to write programs then use something more appropriate, such as shell script. Possibly you want a wrapper script to set options for your `docker` commands. Possibly you want *just* a script, leaving `make` out of it. – John Bollinger May 07 '23 at 15:33
  • There are other tasks. My goal is to be able to do `make clean verbose other.task.not.listed` and have the clean/verbose tasks cause different behaviors for the last one. I'll update my question, but neither of you answered my question about the error – user3534080 May 08 '23 at 17:04
  • 1
    Your approach is inherently flawed. `make this that` says to `make this`, then `make that`. The targets are not related to or aware of each other; specifying a target is different from specifying an option for the whole build. What you are attempting would typically be solved with something like `make VERBOSE=true clean other-target` (or `make VERBOSE=true CLEAN=true other-target` if `clean` is also not really a target ... but you probably should have a target named `clean`, with the usual semantics of removing intermediate and nonessential files from the build directory). – tripleee May 08 '23 at 17:36
  • @tripleee does that mean the only way to force Makefile do this is to have a wrapper script? (My clean does have additional stuff, but it wasn't directly relevant to my question) – user3534080 May 08 '23 at 18:40
  • 1
    No, you don't need a wrapper script, you just need to adapt to these conventions. Though if you want functionality which differs from that of `make` you have to write it yourself, or find a tool which is better aligned with your mental model. – tripleee May 08 '23 at 18:55

2 Answers2

0

Remember that every action takes place in a separate shell. Thus the shell in which you set CLEAN_BUILD (in line 2) and the shell in which you set DOCKER_VERBOSE (line 5) have nothing to do with each other, or with the shell in the final line.

If you were to do something like

docker.stop.rebuild:
    CLEAN_BUILD=xxx \
      DOCKER_VERBOSE=xxx \
      make -j 2 docker.build docker.stop

(using the \ to escape the newline, so that this is a single-line action as far as Make is concerned), then you might get the effect you're looking for.

Norman Gray
  • 11,978
  • 2
  • 33
  • 56
  • that won't do what I want, because that will force you to use a clean/verbose build even when you don't want to. Also doesn't answer my last question about the error. I updated my question a bit. – user3534080 May 08 '23 at 17:05
0

I did figure out the answer to my question about the error; I needed to do the following (note that this didn't solve my problem, see below for the solution):

clean: export CLEAN_BUILD=--no-cache
clean:
    # real build task here

verbose: export DOCKER_VERBOSE=BUILDKIT_PROGRESS=plain
verbose:
    @true

docker.stop.rebuild:
    make -j 2 docker.build docker.stop

docker.build:
    $(DOCKER_VERBOSE) docker compose build $(CLEAN_BUILD) $1

But, this didn't actually result in what I wanted to happen. Likely due to reasons that people posted above in comments. This post is how I figured the above out: How to set child process' environment variable in Makefile

(I also tried using .ONESHELL, but that also didn't seem to accomplish what I wanted)

The ACTUAL solution that seems to work looks like this:

clean:
    $(eval export CLEAN_BUILD:=--no-cache)
    # real clean task here

verbose:
    $(eval export DOCKER_VERBOSE=BUILDKIT_PROGRESS=plain)

docker.stop.rebuild:
    make -j 2 docker.build docker.stop

docker.build:
    $(DOCKER_VERBOSE) docker compose build $(CLEAN_BUILD) $1
user3534080
  • 1,201
  • 1
  • 14
  • 21