307

In my GNUmakefile, I would like to have a rule that uses a temporary directory. For example:

out.tar: TMP := $(shell mktemp -d)
        echo hi $(TMP)/hi.txt
        tar -C $(TMP) cf $@ .
        rm -rf $(TMP)

As written, the above rule creates the temporary directory at the time that the rule is parsed. This means that, even I don't make out.tar all the time, many temporary directories get created. I would like to avoid my /tmp being littered with unused temporary directories.

Is there a way to cause the variable to only be defined when the rule is fired, as opposed to whenever it is defined?

My main thought is to dump the mktemp and tar into a shell script but that seems somewhat unsightly.

4 Answers 4

481

In your example, the TMP variable is set (and the temporary directory created) whenever the rules for out.tar are evaluated. In order to create the directory only when out.tar is actually fired, you need to move the directory creation down into the steps:

out.tar : 
    $(eval TMP := $(shell mktemp -d))
    @echo hi $(TMP)/hi.txt
    tar -C $(TMP) cf $@ .
    rm -rf $(TMP)

The eval function evaluates a string as if it had been typed into the makefile manually. In this case, it sets the TMP variable to the result of the shell function call.

edit (in response to comments):

To create a unique variable, you could do the following:

out.tar : 
    $(eval $@_TMP := $(shell mktemp -d))
    @echo hi $($@_TMP)/hi.txt
    tar -C $($@_TMP) cf $@ .
    rm -rf $($@_TMP)

This would prepend the name of the target (out.tar, in this case) to the variable, producing a variable with the name out.tar_TMP. Hopefully, that is enough to prevent conflicts.

9
  • 61
    Be careful with this solution! $(eval $@_TMP := $(shell mktemp -d)) will happen when the Makefile is first evaluated not in order of the rule procedures. In other words, $(eval ...) happens sooner than you think. While that might be okay for this example, this method will cause problems for some sequential operations. Commented Jun 11, 2015 at 2:53
  • two (off the subject) details: I believe a > sign is missing in the echo line @echo hi > $(TMP)/hi.txt, or is there a system where that sign is not needed?. And, my tar command did not accept cf options without the leading dash -cf, but I have seen versions which do accept this.
    – boclodoa
    Commented Jul 24, 2015 at 17:42
  • 3
    @JamesThomasMoon1979 do you know how to overcome those limitations, e.g. eval some variables that should be a result of steps executed earlier in the rule? Commented Jun 5, 2017 at 13:36
  • 2
    Answering my own question: the workaround for me was creating files during 1st rule execution, and trying to eval them in the first step of 2nd rule. Commented Jun 5, 2017 at 13:59
  • 2
    @JamesThomasMoon1979, if you run make in this Makefile, it doesn't seem to execute when the Makefile is first evaluated. Saying it differently, the folder isn't created with make, only with make TEST as expected. gist.github.com/jsign/c7313f359daa99c02bafb222becacad3 Commented Jul 29, 2019 at 1:06
121

A relatively easy way of doing this is to write the entire sequence as a shell script.

out.tar:
   set -e ;\
   TMP=$$(mktemp -d) ;\
   echo hi $$TMP/hi.txt ;\
   tar -C $$TMP cf $@ . ;\
   rm -rf $$TMP ;\

I have consolidated some related tips here: Multi-line bash commands in makefile

4
  • 6
    This is definitely the simplest and therefore best answer (avoiding @ and eval and doing the same job). Note that in your output you see $TMP (e.g. tar -C $TMP ...) although the value is correcty passed to the command. Commented May 8, 2015 at 9:21
  • This is how it's normally done; I hope people see this answer because in practice nobody goes through all the effort of the accepted one.
    – smheidrich
    Commented Mar 8, 2017 at 14:21
  • 1
    What if you are using shell specific commands ? In that case shell commands shall be in same shell language than the one calling make, right ? I had seen some makefile with shebang.
    – ptitpion
    Commented Apr 25, 2018 at 13:34
  • 2
    @ptitpion: You may also want SHELL := /bin/bash in your makefile to enable BASH-specific features. Commented Apr 25, 2018 at 16:18
45

Another possibility is to use separate lines to set up Make variables when a rule fires.

For example, here is a makefile with two rules. If a rule fires, it creates a temp dir and sets TMP to the temp dir name.

PHONY = ruleA ruleB display

all: ruleA

ruleA: TMP = $(shell mktemp -d testruleA_XXXX)
ruleA: display

ruleB: TMP = $(shell mktemp -d testruleB_XXXX)
ruleB: display

display:
    echo ${TMP}

Running the code produces the expected result:

$ ls
Makefile
$ make ruleB
echo testruleB_Y4Ow
testruleB_Y4Ow
$ ls
Makefile  testruleB_Y4Ow
4
  • Just as a reminder, GNU make has a syntax for order-only prerequisites which may be necessary to complement this approach.
    – anol
    Commented Jun 5, 2014 at 19:24
  • 21
    Be careful with this solution! ruleA: TMP = $(shell mktemp -d testruleA_XXXX) and ruleB: TMP = $(shell mktemp -d testruleB_XXXX) will happen when the Makefile is first evaluated. In other words, ruleA: TMP = $(shell ... happens sooner than you think. While this might work for this particular case, this method will cause problems for some sequential operations. Commented Jun 11, 2015 at 2:55
  • 1
    That's not my experience - rule-local variables are expanded only when the particular rule is requested (either explicitly or as dependcy)
    – Zaar Hai
    Commented Oct 23, 2020 at 9:54
  • 1
    This should still work if you do ruleA: private TMP = .... See target-specific variables. Commented Jan 22, 2021 at 0:23
10

I dislike "Don't" answers, but... don't.

make's variables are global and are supposed to be evaluated during makefile's "parsing" stage, not during execution stage.

In this case, as long as the variable local to a single target, follow @nobar's answer and make it a shell variable.

Target-specific variables, too, are considered harmful by other make implementations: kati, Mozilla pymake. Because of them, a target can be built differently depending on if it's built standalone, or as a dependency of a parent target with a target-specific variable. And you won't know which way it was, because you don't know what is already built.

2
  • 1
    local variables would be useful even if they are, well, truly local, i.e. they are not visible downwards to the rules that get executed under us. Commented Aug 6, 2020 at 11:59
  • 1
    @AttilaLendvai at the time of writing the answer, I didn't know you can make target-specific variable private. This surely makes them more safe, but I would still discourage their usage. Commented Jan 22, 2021 at 0:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.