491

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.

Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
Davit Siradeghyan
  • 6,053
  • 6
  • 24
  • 29
  • 5
    It's unclear what you want to do, but, in my experience with `make`, I never wanted to change the directory like this. Maybe you should try another approach to your solution? – P Shved Nov 24 '09 at 18:17
  • 3
    It's a common newbie mistake to believe your directory is important. For most things it isn't; `cd dir; cmd file` can nearly always be more usefully expressed as `cmd dir/file`. – tripleee Nov 25 '13 at 11:14
  • 92
    It's a common newbie mistake to believe your present working directory is inconsequential. Many programs, especially shell scripts, are written with a specific value of `.` in mind. It's true that most tools are designed in such a way that you don't need to change your pwd for it. But this isn't always true, and I don't think it's a good idea to call it a "mistake" to believe your directory may be important. – Evelyn Kokemoor Mar 28 '16 at 20:07
  • 3
    Tip: if your cd command says "No such file or directory", even when the (relative) directory _does_ exist, check that your CDPATH environment variable is either empty or includes ".". Make executes commands with "sh", which will only find a relative path via CDPATH if it is set. This contrasts with bash, which will try . before consulting CDPATH. – Denis Howe Sep 13 '19 at 11:00
  • 5
    To add to what @tripleee said (twelve years ago, yeesh), there _are_ instances where the current directory is important. On MacOS, for instance, the `zip` command will include the entire given search path in the structure of the compressed archive, which may be undesirable. – MikeTheCoder Oct 07 '20 at 15:59

8 Answers8

832

It is actually executing the command, changing the directory to some_directory, however, this is performed in a sub-process shell, and affects neither make nor the shell you're working from.

If you're looking to perform more tasks within some_directory, you need to add a semi-colon and append the other commands as well. Note that you cannot use new lines as they are interpreted by make as the end of the rule, so any new lines you use for clarity need to be escaped by a backslash.

For example:

all:
        cd some_dir; echo "I'm in some_dir"; \
          gcc -Wall -o myTest myTest.c

Note also that the semicolon is necessary between every command even though you add a backslash and a newline. This is due to the fact that the entire string is parsed as a single line by the shell. As noted in the comments, you should use '&&' to join commands, which means they only get executed if the preceding command was successful.

all:
        cd some_dir && echo "I'm in some_dir" && \
          gcc -Wall -o myTest myTest.c

This is especially crucial when doing destructive work, such as clean-up, as you'll otherwise destroy the wrong stuff, should the cd fail for whatever reason.

A common usage, though, is to call make in the subdirectory, which you might want to look into. There's a command-line option for this, so you don't have to call cd yourself, so your rule would look like this

all:
        $(MAKE) -C some_dir all

which will change into some_dir and execute the Makefile in that directory, with the target "all". As a best practice, use $(MAKE) instead of calling make directly, as it'll take care to call the right make instance (if you, for example, use a special make version for your build environment), as well as provide slightly different behavior when running using certain switches, such as -t.

For the record, make always echos the command it executes (unless explicitly suppressed), even if it has no output, which is what you're seeing.

falstro
  • 34,597
  • 9
  • 72
  • 86
  • 10
    Well, not *always*. To suppress the echo, just put @ at the beginning of the line. – Beta Nov 24 '09 at 15:54
  • 4
    @Beta: well yes, and a dash prefix ignores the error status as well. Maybe I got a little carried away, I wanted to point out the fact that make does echos the command, regardless of what kind of command it is. And in this case, it's a command with no output, which makes the echoing seem even stranger to someone not familiar with make. – falstro Nov 24 '09 at 16:42
  • 65
    Two nits: 1. The commands should really be joined by `&&`, because with `;` if the directory doesn’t exist and the `cd` fails, the shell will keep running the rest of the commands in the current directory, which can cause things like mysterious “file not found” messages for compiles, infinite loops when invoking make, or disaster for rules like `clean:: cd dir; rm -rf *`. 2. When invoking sub-makes, call `$(MAKE)` instead of `make` so that [options will be passed on correctly](http://www.gnu.org/software/make/manual/make.html#MAKE-Variable). – andrewdotn Nov 12 '12 at 18:38
  • @roe, how would you write `clean` rule? Another rule like `make -C some_dir clean`? – perreal Jan 23 '13 at 02:08
  • 2
    @perreal, I usually define a pattern rule like so: `%-recursive:` with the body: `@T="$@";$(MAKE) -C some_dir $${T%-*}` (I usually have a for-loop too, to loop over a list of subdirs, the `$${T%-*}` is a bash expansion which removes the `-recursive` part of the target name) and then define explicit short-hand (and .PHONY) targets for each, like `all: all-recursive`, `check: check-recursive`, `clean: clean-recursive`. – falstro Jan 29 '13 at 09:05
  • Make doesn't always echo, if you put @ in front of it it does not. – Christian Stewart Aug 16 '13 at 21:47
  • 1
    @ChristianStewart, true, as was mentioned in comment 2 and 3. – falstro Aug 20 '13 at 09:43
  • @andrewdotn I just ran into the `&&` vs `;` issue myself. – hookenz Jan 15 '23 at 21:29
144

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.

Chnossos
  • 9,971
  • 4
  • 28
  • 40
  • 8
    Just note that `pwd` itself works, as well as `\`pwd\`` (with backticks), but `$(shell pwd)` and `$(PWD)` will still return the directory before doing the `cd` command, so you cannot use them directly. – anol Apr 14 '16 at 11:55
  • 3
    Yes because variable and function expansion are done before executing the commands by make, whereas `pwd` and `\`pwd\`` is executed by the shell itself. – Chnossos Apr 14 '16 at 11:57
  • 2
    Not everybody works on legacy makefiles, and even then this answer is about *knowing* this possibility exists. – Chnossos May 20 '17 at 14:52
  • 1
    This can be an annoying/dangerous option because only the last command for a target can cause a failure (any earlier command failures will be ignored), and probably nobody working with you will be expecting that. – tobii Aug 13 '18 at 22:08
  • 1
    Set `SHELLFLAGS` to `-e -c` and the shell will exit at the first command that fails. – Timothy Baldwin Aug 09 '19 at 23:48
  • does `.ONESHELL` on one target or all following also? – kyb Nov 28 '19 at 10:57
  • @kyb Read the documentation excerpt again : _"[...] and provide it with the entire recipe, [...]"_ – Chnossos Nov 29 '19 at 15:06
  • 1
    This does not seem to do anything for me. – Caleb Stanford Oct 24 '21 at 05:33
  • 2
    Not working for me on macOS. – xilopaint Apr 09 '22 at 14:01
  • macOS seems notoriously behind with its tools. Install [MacPorts](https://www.macports.org/) or [Brew](https://brew.sh/) for an updated `make` command. – Jens Sep 17 '22 at 19:52
20

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
JoeS
  • 381
  • 2
  • 4
  • 57
    "Cute"? More like enough rope to shoot yourself in the foot. – tripleee Nov 25 '13 at 11:12
  • 1
    Is this current directory then set for the commands just in that rule, or for all subsequently executed rules? Also, will some variation of this work under Windows? – user117529 May 29 '14 at 00:57
  • 10
    This of course breaks for parallel execution (`-jn`), which is the whole point of _make_ really. – bobbogo Jun 26 '14 at 20:12
  • 6
    This is a bad hack. If you ever have to resort to such thing, you are not using Makefiles for what they are for. – gatopeich Nov 07 '14 at 11:58
  • 4
    I won't disagree that its a bad hack that's for sure. But does demonstrate some of the wicked things you can do. – JoeS Dec 19 '14 at 19:54
9

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)
Nadir SOUALEM
  • 3,451
  • 2
  • 23
  • 30
  • 10
    Why the `$(shell ...)` when `cd $(BIN); ls` or `cd $(BIN) && ls` (as @andrewdotn pointed out) would be enough. – vapace Nov 26 '13 at 15:05
6

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 calling set -e are possible, but such workarounds would have to be implemented for every recipe in the makefile.
Jasha
  • 5,507
  • 2
  • 33
  • 44
  • 1
    As noted [above](https://stackoverflow.com/questions/1789594/how-do-i-write-the-cd-command-in-a-makefile#30590240) you can use `.SHELLFLAGS` in conjunction with `.ONESHELL` to set the `-e` option. – Jens Sep 17 '22 at 19:56
2

To change dir

foo: 
    $(MAKE) -C mydir

multi:
    $(MAKE) -C / -C my-custom-dir   ## Equivalent to /my-custom-dir
jackotonye
  • 3,537
  • 23
  • 31
0

PYTHON = python3

test: cd src/mainscripts; ${PYTHON} -m pytest

#to keep make file in root directory and run test from source root above #worked for me.

jamila
  • 39
  • 3
-8

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!

  • 1
    No, this is wrong. Specifically, the `$(shell cd ....)` is executed when the Makefile is initially parsed, not when this particular recipe is run. – tripleee Apr 07 '18 at 18:06
  • @triplee Not quite — the `$(shell)` is expanded only when _make_ decides to build _target_. If _make_ never needs the recipe, it won't expand it. – bobbogo Jan 15 '19 at 14:39