'How do I write the 'cd' command in a makefile?
For example, I have something like this in my makefile:
all:
cd some_directory
But when I typed make
I saw only 'cd some_directory', like in the echo
command.
Solution 1:[1]
Starting from GNU make 3.82 (July 2010), you can use the .ONESHELL
special target to run all recipes in a single instantiation of the shell (bold emphasis mine):
- New special target:
.ONESHELL
instructs make to invoke a single instance of the shell and provide it with the entire recipe, regardless of how many lines it contains.
.ONESHELL: # Applies to every targets in the file!
all:
cd ~/some_dir
pwd # Prints ~/some_dir if cd succeeded
another_rule:
cd ~/some_dir
pwd # Prints ~/some_dir if cd succeeded
Note that this will be equivalent to manually running
$(SHELL) $(.SHELLFLAGS) "cd ~/some_dir; pwd"
# Which gets replaced to this, most of the time:
/bin/sh -c "cd ~/some_dir; pwd"
Commands are not linked with &&
so if you want to stop at the first one that fails, you should also add the -e
flag to your .SHELLFLAGS
:
.SHELLFLAGS += -e
Also the -o pipefail
flag might be of interest:
If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.
Solution 2:[2]
Here's a cute trick to deal with directories and make. Instead of using multiline strings, or "cd ;" on each command, define a simple chdir function as so:
CHDIR_SHELL := $(SHELL)
define chdir
$(eval _D=$(firstword $(1) $(@D)))
$(info $(MAKE): cd $(_D)) $(eval SHELL = cd $(_D); $(CHDIR_SHELL))
endef
Then all you have to do is call it in your rule as so:
all:
$(call chdir,some_dir)
echo "I'm now always in some_dir"
gcc -Wall -o myTest myTest.c
You can even do the following:
some_dir/myTest:
$(call chdir)
echo "I'm now always in some_dir"
gcc -Wall -o myTest myTest.c
Solution 3:[3]
What do you want it to do once it gets there? Each command is executed in a subshell, so the subshell changes directory, but the end result is that the next command is still in the current directory.
With GNU make, you can do something like:
BIN=/bin
foo:
$(shell cd $(BIN); ls)
Solution 4:[4]
To change dir
foo:
$(MAKE) -C mydir
multi:
$(MAKE) -C / -C my-custom-dir ## Equivalent to /my-custom-dir
Solution 5:[5]
Here is the pattern I've used:
.PHONY: test_py_utils
PY_UTILS_DIR = py_utils
test_py_utils:
cd $(PY_UTILS_DIR) && black .
cd $(PY_UTILS_DIR) && isort .
cd $(PY_UTILS_DIR) && mypy .
cd $(PY_UTILS_DIR) && pytest -sl .
cd $(PY_UTILS_DIR) && flake8 .
My motivations for this pattern are:
- The above solution is simple and readable (albeit verbose)
- I read the classic paper "Recursive Make Considered Harmful", which discouraged me from using
$(MAKE) -C some_dir all
- I didn't want to use just one line of code (punctuated by semicolons or
&&
) because it is less readable, and I fear that I will make a typo when editing the make recipe. - I didn't want to use the
.ONESHELL
special target because:- that is a global option that affects all recipes in the makefile
- using
.ONESHELL
causes all lines of the recipe to be executed even if one of the earlier lines has failed with a nonzero exit status. Workarounds like callingset -e
are possible, but such workarounds would have to be implemented for every recipe in the makefile.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | JoeS |
Solution 3 | Nadir SOUALEM |
Solution 4 | jackotonye |
Solution 5 |