12

Assume I have a build-target foo:

foo:foo.c
    $(CC) $(CFLAGS) $(ARGS) -c foo.c -o foo

Now, ARGS is something that I pass on the command line:

$ make ARGS:=-DX=1 foo

So, I need to bypass make's cleverness, because the foo target does not only depend on which files have changed, but also on the value of ARGS.

Is there something in make to do this? My hack (see answer) doesn't seem to be the most elegant but it works. Anything better?

bitmask
  • 32,434
  • 14
  • 99
  • 159
  • The only "better" solution I know of is to track extra files for this information which are then used as additional prereqs for the output target (a number of make generators, etc. use this trick). – Etan Reisner Oct 01 '14 at 16:12
  • I wouldn't do this. If `foo` depends on the value of `$(ARGS)`, it should be remade when the value of `$ARGS)` is different than the last time. But you don't know what that value was. I think it's better to make the value part of the target. – reinierpost Oct 01 '14 at 17:03
  • @reinierpost yes you do know "what that value was" :) take a look at my answer :) – Mark Galeck Oct 01 '14 at 18:35

4 Answers4

8

Here is a general solution to your specific problem.

You want to be able to depend on a variable as a prerequisite. That is, you can make it a prerequisite to any target in your makefile, and when the value of the variable changes, you rebuild those targets.

Here is a function that does that, you use this function to declare a variable to be dependable, and then you can use it as a prerequisite.

Note that if the variable is not used on the command line, it will still mean that variable still has a value, namely, the empty string.

define DEPENDABLE_VAR

.PHONY: phony
$1: phony
    @if [[ `cat $1 2>&1` != '$($1)' ]]; then \
        echo -n $($1) > $1 ; \
    fi

endef

#declare ARGS to be dependable
$(eval $(call DEPENDABLE_VAR,ARGS))


foo:foo.c ARGS
    $(CC) $(CFLAGS) $(ARGS) -c foo.c -o foo

In fact, we could omit the need for "declaration", and just write a similar function that will make all variables dependable by default. But I don't like that. I prefer that the users that modify makefiles I write, declare their intentions explicitly. It is good for them :)

Mark Galeck
  • 6,155
  • 1
  • 28
  • 55
  • 1
    Whoa, this looks like deep make voodoo (I like). I'll check it out after my vacation. Thanks. – bitmask Oct 01 '14 at 20:57
  • @bitmask nah, fear not, it is a very simple hack, in fact, it is along the lines of what Etan is talking about. It is just hiding all the details from the user. It's just creating the file named `ARGS` and storing the most recent value of that variable in that file (possibly empty). Nothing to it. – Mark Galeck Oct 01 '14 at 21:38
  • As Jens points out, this approach is not without drawbacks. The chief drawback, is that if you alternate between different values of the variable, you have to rebuild stuff each time. So if you do that, it is "slow". The reason why my "users" like it though, is because, it is very easy for them to set up dependability on any variable they want, it is as flexible as with files. So they save time not having to setup separate directories for various versions of intermediate files etc, which is error prone and tedious. – Mark Galeck Oct 02 '14 at 10:48
  • See "Note on Marks' excellent answer" below for a simplified version just for ARGS. – Berwyn Oct 12 '22 at 23:08
  • @Mark, I would would have thought that `.PHONY: $1` would have done the job without using `phony` at all. But it doesn't. Can you help me understand why? – Berwyn Oct 12 '22 at 23:19
1

I don't understand how the other solutions are supposed to work. If the ARGS target is .PHONY or depends on a .PHONY, then it will always be run, right?

Here is my solution using the $(file) function in newer versions of gmake:

.PHONY: FORCE

define DEPENDABLE_VAR
$(1):
        echo -n $($(1)) > $(1)
ifneq ("$(file <$(1))","$($(1))")
$(1): FORCE
endif

endef

#declare ARGS to be dependable
$(eval $(call DEPENDABLE_VAR,ARGS))

foo: foo.c ARGS
        touch foo

And the result:

~/stuff/makevars> make foo ARGS=1
echo -n 1 > ARGS
touch foo
~/stuff/makevars> make foo ARGS=1
make: 'foo' is up to date.
~/stuff/makevars> make foo ARGS=2
echo -n 2 > ARGS
touch foo
~/stuff/makevars> make foo ARGS=2
make: 'foo' is up to date.
deemer
  • 1,142
  • 8
  • 10
0

My solution was to create a dummy phony target:

.PHONY:dummy
dummy:
    @:

and have foo depend on dummy if ARGS is nonempty:

foo:foo.c $(patsubst %,dummy,$(ARGS))
bitmask
  • 32,434
  • 14
  • 99
  • 159
  • 1
    I would have used `$(and $(ARGS),dummy)` there instead of the `patsubst` but this is certainly a simple solution. Oh, also possibly a forced target instead of a `.PHONY` one since real targets depending on `.PHONY` targets is generally not a good idea. Also your phony target doesn't need an empty recipe line like that `dummy: ;` will also work and should (I believe) execute one less shell. – Etan Reisner Oct 01 '14 at 16:14
  • @EtanReisner: Thanks for your suggestions. I learnt new make stuff :) – bitmask Oct 01 '14 at 16:30
0

Note on Mark's excellent answer

The bare necessities of Mark's answer are actually very simple. It really boils down to just:

.PHONY: phony
ARGS: phony
    @if [[ `cat ARGS 2>&1` != '$(ARGS)' ]]; then echo -n $(ARGS) >ARGS; fi

The rest of his code is just to let you reproduce the recipe for other variable names without repeating yourself. Useful in practice, but the above version will help you see what's going on more easily.

In fact, my answer can even be made general (like Mark's) for any variable name, but in a less complicated way as follows:

.PHONY: phony
.ARG~%: phony
    @if [[ `cat .ARG~$* 2>&1` != '$($*)' ]]; then echo -n $($*) >.ARG~$*; fi

Then you simply add .ARG~MYVARNAME as a dependency to any target to make that target depend on variable MYVARNAME.

Note that the dot in front of .ARG~% simply causes it to create a dependency-tracking file that is 'hidden' in linux.

So in your case, you would do:

foo: foo.c .ARG~ARGS
    $(CC) $(CFLAGS) $(ARGS) -c foo.c -o foo
Berwyn
  • 151
  • 7