12

I want to separate the directory with sources from the directory with targets. And it seems that changing the current working directory from Makefile should be the simplest solution.

Explicit path to targets is not sufficient because of the following drawbacks:

  1. Redundant code in Makefile since every reference to target should be prefixed with variable.
  2. More complex command line to build particular intermediate target (worse for debugging).

See also Pauls's rule #3:

Life is simplest if the targets are built in the current working directory.

Regarding VPATH — I also agree that requiring developers "to change to the target directory before running make is a pain".

ruvim
  • 7,151
  • 2
  • 27
  • 36

5 Answers5

10

Building targets in a separate directory is a commonplace make practice that GNU make conveniently supports without changing directory or invoking auxiliary tools. Here is a routine illustration:

Makefile

srcs := main.c foo.c
blddir := bld
objs := $(addprefix $(blddir)/,$(srcs:.c=.o))
exe := $(blddir)/prog

.PHONY: all clean

all: $(exe)

$(blddir):
    mkdir -p $@

$(blddir)/%.o: %.c | $(blddir)
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

$(exe) : $(objs)
    $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)

clean:
    rm -fr $(blddir)

which runs like:

$ make
mkdir -p bld
cc   -c -o bld/main.o main.c
cc   -c -o bld/foo.o foo.c
cc -o bld/prog bld/main.o bld/foo.o

Cribs:-

There can be powerful reasons to make make change its working directory but merely putting build products in a separate directory isn't one.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • Yes, this method "every reference to every target is prefixed with the pathname" and its drawbacks are [described](http://make.mad-scientist.net/papers/multi-architecture-builds/#explicitpath) in the mentioned paper — I have updated the answer. – ruvim May 26 '16 at 21:10
  • 1
    This is the second result when i search "How to change current directory in Make". In your answer you indicate that someone might have legitimate reasons to change the current directory. Can you add a little snippet at the end for people who come in to this question and might want to change the current directory for reasons other than those specified in the question? – Steve Cox Sep 07 '17 at 21:51
9

In the GNU Make program I am using built for mingw64 (windows),

GNU Make 4.2.1 Built for x86_64-w64-mingw32

I am able to use this target with this command,

debug:
    cd $(PREFIX) && $(GDB) kiwigb.exe

The results are the directory change is temporary, but all works.

user2262111
  • 563
  • 1
  • 6
  • 16
  • It is not a solution for the question. You don't eliminate explicit path to the targets. – ruvim Dec 04 '19 at 12:15
  • 1
    I'm not sure what you mean by explicit path to targets. I think it is a solution to the question because this allows you to cd and run a command. You said, > And it seems that changing the current working directory from Makefile should be the simplest solution. – user2262111 Dec 10 '19 at 08:24
  • Well, it seems "[targets](https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html)" and "path to target" are out of your knowledge, but via `cd` you change the current directory in a sub-shell, not in Make (see subject). Therefore, it is not a solution. – ruvim Dec 16 '19 at 10:48
5

Known methods overview

The excellent research of various methods how to separate the source and target directories was made by Paul D. Smith in "Multi-Architecture Builds" paper. The following methods are described (with their drawbacks):

  • Source copy
  • Explicit path (reference to every target is prefixed with the pathname)
  • VPATH (invoke build from the target directory)
  • Advanced VPATH (auto recursive invocation)

Yet another method

However I found the simpler solution — with smaller boilerplate and without recursive invocation of make. In case of GNU Make with Guile support we can just use Guile chdir function to change the current working directory from Makefile. Also we can create directory via mkdir before that.

data ?= ./data/

# Create $(data) directory if it is not exist (just for example)
$(guile (if (not (access? "$(data)" F_OK)) (mkdir "$(data)") ))

# Set the new correct value of CURDIR (before changing directory)
CURDIR := $(abspath $(data))

# Change the current directory to $(data)
$(guile (chdir "$(data)"))

# Another way of updating CURDIR
#  — via sub-shell call after changing directory
# CURDIR := $(shell pwd)


# Don't try to recreate Makefile file
# that is disappeared now from the current directory
Makefile : ;

$(info     CURDIR = $(CURDIR)     )
$(info        PWD = $(shell pwd)  )

Final boilerplate to change the current directory

The assumptions: data variable is available in the context and the parent of $(data) directory is accessible, the path can be relative.

srcdir := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
ifeq (,$(filter guile,$(.FEATURES)))
  $(warning Guile is required to change the current directory.)
  $(error Your Make version $(MAKE_VERSION) is not built with support for Guile)
endif
$(MAKEFILE_LIST): ;
$(guile (if (not (file-exists? "$(data)")) (mkdir "$(data)") ))
ORIGCURDIR  := $(CURDIR)
CURDIR      := $(realpath $(data))
$(guile (chdir "$(data)"))
ifneq ($(CURDIR),$(realpath .))
  $(error Cannot change the current directory)
endif
$(warning CURDIR is changed to "$(data)")

Remember that relative path in include directive is calculated from the current directory by default, hence it depends on the location — is it used before this boilerplate or after.

NB: $(data) should not be used in the rules; $(srcdir) can be used to specify a file relative to this Makefile file location.

Found issues

This method was tested in GNU Make 4.0 and 4.2.1

One minor issue was observed. abspath function works incorrectly after changing the current directory — it continues resolving relative paths according to the old CURDIR; realpath works correctly.

Also this method may have other yet unknown drawbacks.

ruvim
  • 7,151
  • 2
  • 27
  • 36
  • 2
    The main drawback is it requires you to have a version of GNU make built with Guile support, which wasn't present until GNU make 4.0 and is still optional so not all builds might have it. – MadScientist May 27 '16 at 03:59
  • To be more correct, Make with Guile support is **prerequisite** to use this method. So compare to the drawbacks of other methods it is rather limitation ;) – ruvim May 27 '16 at 12:28
  • See also: [How to check whether your Make version supports Guile](https://stackoverflow.com/questions/54670790/how-to-check-whether-gnu-make-supports-guile) – ruvim Feb 13 '19 at 15:01
4

The correct way is to open a man page:

man make

and search for -C by inputting /-C and pressing Enter once. You will find something like this:

   -C dir, --directory=dir
        Change  to  directory dir before reading the makefiles or doing
        anything else.  If multiple -C options are specified,  each  is
        interpreted relative to the previous one: -C / -C etc is equiv‐
        alent to -C /etc.  This is typically used with recursive  invo‐
        cations of make.

So you can use it:

make -C <desired directory> ...
71GA
  • 1,132
  • 6
  • 36
  • 69
  • 2
    Please refer to the original [problem description](http://make.mad-scientist.net/papers/multi-architecture-builds/) by Paul D. Smith. Pointing the target directory via `-C` command line option is annoying on the same extent as changing the directory by `cd` or `pushd` before `make`. And you still have to place the source Makefile into the target directory. – ruvim May 04 '21 at 13:57
0
BUILD_DIR:=build

SRC_P1:=hello.c bye.c
HDR_P1:=hello.h
OBJ_P1:=$(addprefix $(BUILD_DIR)/,$(SRC_P1:.c=.o))
P1:=$(BUILD_DIR)/hello

CFLAGS=-I.
LDFLAGS=-L.

$(P1): $(BUILD_DIR) $(OBJ_P1) 
  $(LINK.c) $(OUTPUT_OPTION) $(OBJ_P1)

$(OBJ_P1): $(HDR_P1)

$(BUILD_DIR)/%.o: %.c
  $(COMPILE.c) $(OUTPUT_OPTION) $<

$(BUILD_DIR):
  mkdir $@

.PHONY: clean
clean:
  rm -fr $(BUILD_DIR)

It is very similar to an answer above with the difference that it is possible define CFLAGS and LDFLAGS with no need to explicitly include them in the recipe parts.
The rule $(BUILD_DIR)/%.o: %.c is very interesting because it only applies to the set of objects defined, not to every .c file in the current directory.

  • Yes, this answer is too similar to [another answer](https://stackoverflow.com/questions/37467969/how-to-change-current-directory-in-gnu-make/#answer-37469528) — no reason to publish it. `CFLAGS` and `LDFLAGS` have nothing to do with the original problem, it's a C-compiler specific [feature](https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html) of GNU Make. – ruvim Sep 12 '22 at 08:57
  • I added that in regard to decrease redundant code and command complexity, which are issues laid in the question I think. – Jorge Opaso Pazos Sep 14 '22 at 20:57