[makefile] 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.

This question is related to makefile gnu-make

The answer is


Like this:

target:
    $(shell cd ....); \
    # ... commands execution in this directory
    # ... no need to go back (using "cd -" or so)
    # ... next target will be automatically in prev dir

Good luck!


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)

Starting from GNU make 3.82 (July 2010), you can use the .ONESHELL special target to run all recipe lines 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: # Only applies to all target
all:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded

another_rule:
    cd ~/some_dir
    pwd # Oops, prints ~

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

To change dir

foo: 
    $(MAKE) -C mydir

multi:
    $(MAKE) -C / -C my-custom-dir   ## Equivalent to /my-custom-dir

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
  • 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 calling set -e are possible, but such workarounds would have to be implemented for every recipe in the makefile.