In the makefile, how to get the relative path from one absolute path to another?

An example to illustrate my question:

Top level makefile

rootdir = $(realpath .) export includedir = $(rootdir)/include default: @$(MAKE) --directory=$(rootdir)/src/libs/libfoo 

Makefile for src / libfoo

 currentdir = $(realpath .) includedir = $(function or magic to make a relative path from $(currentdir) to $(includedir), which in this example would be ../../../include) 

Another example:

 current dir = /home/username/projects/app/trunk/src/libs/libfoo/ destination = /home/username/projects/app/build/libfoo/ relative = ../../../../build/libfoo 

How can this be done in an attempt to be as portable as possible?

+7
regex shell path makefile
source share
6 answers

Doing what you want does not look easy. It may be possible to use a large amount of $(if in the makefile, but not portable (gmake only) and bulky.

IMHO, you are trying to solve a problem that you create yourself. Why don't you send the correct includedir value as a relative path from the top-level Makefile? This can be done very easily as follows:

 rootdir = $(realpath .) default: @$(MAKE) --directory=$(rootdir)/src/libs/libfoo includedir=../../../include 

Then you can use $(includedir) in sub-make files. It is already defined as relative.

+6
source share

Python is portable! So, I would suggest you this simple example in your swap file

The paths current_dir and destination_dir os.path.relpath() do the job for you, so you don’t need to reinvent the wheel.

submakefile.mk

 current_dir=$(CURDIR) makefile_target: (echo "import os"; echo "print os.path.relpath('$(destination_dir)', '$(current_dir)')" )| python 
+5
source share

The shell has this function using realpath (1) and the - relative-to flag so you can just call the shell.

Here is an example:

  RELATIVE_FILE1_FILE2: = $ (shell realpath --relative-to $ (FILE1) $ (FILE2)) 

You can even process the entire file list with a single call to realpath (1) , because it knows how to handle many file names.

Here is an example:

  RELATIVES: = $ (shell realpath --relative-to $ (RELATIVE) $ (FILES)) 
+5
source share

Didier's answer is the best, but the following may give you some ideas:

 includedir=/a/b/c/d currentdir=/a/b/e/f/g up=; while ! expr $includedir : $currentdir >/dev/null; do up=../$up; currentdir=`dirname $currentdir`; done; relative=$up`expr $includedir : $currentdir'/*\(.*\)'` echo "up=$up currentdir=$currentdir, relative=$relative" 

Sorted!

(no one said it should be beautiful ...)

+2
source share

Here's a solution that only uses GNU make functions. Although it is recursive, it should be more efficient than calling an external program. The idea is pretty straightforward: the relative path will be zero or more .. to go to the most common ancestor, and then the suffix to go to the second directory. The tough part is finding the longest common prefix in both ways.

 # DOES not work if path has spaces OneDirectoryUp=$(patsubst %/$(lastword $(subst /, ,$(1))),%,$(1)) # FindParentDir2(dir0, dir1, prefix) returns prefix if dir0 and dir1 # start with prefix, otherwise returns # FindParentDir2(dir0, dir1, OneDirectoryUp(prefix)) FindParentDir2= $(if $(or $(patsubst $(3)/%,,$(1)), $(patsubst $(3)/%,,$(2)) ), $(call FindParentDir2,$(1),$(2),$(call OneDirectoryUp,$(3))), $(3) ) FindParentDir=$(call FindParentDir2,$(1),$(2),$(1)) # how to make a variable with a space, courtesy of John Graham-Cumming # http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html space:= space+= # dir1 relative to dir2 (dir1 and dir2 must be absolute paths) RelativePath=$(subst $(space), , $(patsubst %, ../, $(subst /, , $(patsubst $(call FindParentDir,$(1),$(2))/%, %, $(2) ) ) ) ) $(patsubst $(call FindParentDir,$(1),$(2))/%, %, $(1) ) # example of how to use (will give ..) $(call RelativePath,/home/yale,/home/yale/workspace) 

I recently translated a large set of recursive makefiles into an entire project, as it is well known that recursive make is bad because it does not expose the entire dependency graph ( http://aegis.sourceforge.net/auug97.pdf ). All source codes and library paths are defined relative to the current file directory. Instead of defining a fixed number of general assembly rules%, I create a set of rules for each pair (source directory, output directory), which avoids the ambiguity of using vpath. When creating assembly rules, I need a canonical path for each source code directory. Although an absolute path can be used, it is usually too long and less portable (I accidentally used Cygwin GNU, where absolute paths are prefixed with / cygdrive and are not recognized by Windows programs). Therefore, I use this function to generate canonical paths.

+2
source share

Reliable paste solution with pure Make:

 override define \s := $() $() endef ifndef $(\s) override $(\s) := else $(error Defined special variable '$(\s)': reserved for internal use) endif override define dirname $(patsubst %/,%,$(dir $(patsubst %/,%,$1))) endef override define prefix_1 $(if $(or $\ $(patsubst $(abspath $3)%,,$(abspath $1)),$\ $(patsubst $(abspath $3)%,,$(abspath $2))),$\ $(strip $(call prefix_1,$1,$2,$(call dirname,$3))),$\ $(strip $(abspath $3))) endef override define prefix $(call prefix_1,$1,$2,$1) endef override define relpath_1 $(patsubst /%,%,$(subst $(\s),/,$(patsubst %,..,$(subst /,$(\s),$\ $(patsubst $3%,%,$(abspath $2)))))$\ $(patsubst $3%,%,$(abspath $1))) endef override define relpath $(call relpath_1,$1,$2,$(call prefix,$1,$2)) endef 

Test cases:

 $(info $(call prefix,/home/user,/home/user)) $(info $(call prefix,/home/user,/home/user/)) $(info $(call prefix,/home/user/,/home/user)) $(info $(call prefix,/home/user/,/home/user/)) $(info $(call relpath,/home/user,/home/user)) $(info $(call relpath,/home/user,/home/user/)) $(info $(call relpath,/home/user/,/home/user)) $(info $(call relpath,/home/user/,/home/user/)) $(info ----------------------------------------------------------------------) $(info $(call prefix,/home/user,/home/user/.local/share)) $(info $(call prefix,/home/user,/home/user/.local/share/)) $(info $(call prefix,/home/user/,/home/user/.local/share)) $(info $(call prefix,/home/user/,/home/user/.local/share/)) $(info $(call relpath,/home/user,/home/user/.local/share)) $(info $(call relpath,/home/user,/home/user/.local/share/)) $(info $(call relpath,/home/user/,/home/user/.local/share)) $(info $(call relpath,/home/user/,/home/user/.local/share/)) $(info ----------------------------------------------------------------------) $(info $(call prefix,/home/user/.config,/home/user/.local/share)) $(info $(call prefix,/home/user/.config,/home/user/.local/share/)) $(info $(call prefix,/home/user/.config/,/home/user/.local/share)) $(info $(call prefix,/home/user/.config/,/home/user/.local/share/)) $(info $(call relpath,/home/user/.config,/home/user/.local/share)) $(info $(call relpath,/home/user/.config,/home/user/.local/share/)) $(info $(call relpath,/home/user/.config/,/home/user/.local/share)) $(info $(call relpath,/home/user/.config/,/home/user/.local/share/)) $(info ----------------------------------------------------------------------) $(info $(call prefix,/home/user/.local/share,/home/user)) $(info $(call prefix,/home/user/.local/share,/home/user/)) $(info $(call prefix,/home/user/.local/share/,/home/user)) $(info $(call prefix,/home/user/.local/share/,/home/user/)) $(info $(call relpath,/home/user/.local/share,/home/user)) $(info $(call relpath,/home/user/.local/share,/home/user/)) $(info $(call relpath,/home/user/.local/share/,/home/user)) $(info $(call relpath,/home/user/.local/share/,/home/user/)) $(info ----------------------------------------------------------------------) $(info $(call prefix,/home/user/.local/share,/home/user/.config)) $(info $(call prefix,/home/user/.local/share,/home/user/.config/)) $(info $(call prefix,/home/user/.local/share/,/home/user/.config)) $(info $(call prefix,/home/user/.local/share/,/home/user/.config/)) $(info $(call relpath,/home/user/.local/share,/home/user/.config)) $(info $(call relpath,/home/user/.local/share,/home/user/.config/)) $(info $(call relpath,/home/user/.local/share/,/home/user/.config)) $(info $(call relpath,/home/user/.local/share/,/home/user/.config/)) $(info ----------------------------------------------------------------------) $(info $(call prefix,/root,/home/user)) $(info $(call prefix,/root,/home/user/)) $(info $(call prefix,/root/,/home/user)) $(info $(call prefix,/root/,/home/user/)) $(info $(call relpath,/root,/home/user)) $(info $(call relpath,/root,/home/user/)) $(info $(call relpath,/root/,/home/user)) $(info $(call relpath,/root/,/home/user/)) 

Expected results:

 /home/user /home/user /home/user /home/user ---------------------------------------------------------------------- /home/user /home/user /home/user /home/user ../.. ../.. ../.. ../.. ---------------------------------------------------------------------- /home/user /home/user /home/user /home/user ../../.config ../../.config ../../.config ../../.config ---------------------------------------------------------------------- /home/user /home/user /home/user /home/user .local/share .local/share .local/share .local/share ---------------------------------------------------------------------- /home/user /home/user /home/user /home/user ../.local/share ../.local/share ../.local/share ../.local/share ---------------------------------------------------------------------- ../../root ../../root ../../root ../../root 
+1
source share

All Articles