makefilegnu-makeguile

How to change current directory in GNU Make


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".


Solution

  • 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):

    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.