举例说明我的问题:
顶级makefile
rootdir = $(realpath .)
export includedir = $(rootdir)/include
default:
@$(MAKE) --directory=$(rootdir)/src/libs/libfoo
src / libfoo的Makefile
currentdir = $(realpath .)
includedir = $(function or magic to make a relative path
from $(currentdir) to $(includedir),
which in this example would be ../../../include)
另一个例子:
current dir = /home/username/projects/app/trunk/src/libs/libfoo/
destination = /home/username/projects/app/build/libfoo/
relative = ../../../../build/libfoo
如何在尽可能便携的同时做到这一点?
答案 0 :(得分:6)
做你想做的事并不容易。可能有可能在makefile中使用大量的$(if
但不可移植(仅限gmake)并且很麻烦。
includedir
值作为相对路径?它可以很容易地完成如下:
rootdir = $(realpath .)
default:
@$(MAKE) --directory=$(rootdir)/src/libs/libfoo includedir=../../../include
然后您可以在子makefile中使用$(includedir)
。它已被定义为相对。
答案 1 :(得分:5)
Python是可移植的!所以,我建议你在submakefile中使用这个简单的例子
使用current_dir
和destination_dir
路径os.path.relpath()
为您完成工作,因此您无需重新发明轮子。
<强> submakefile.mk 强>
current_dir=$(CURDIR)
makefile_target:
(echo "import os"; echo "print os.path.relpath('$(destination_dir)', '$(current_dir)')" )| python
答案 2 :(得分:5)
shell使用 realpath(1)和 - relative-to 标志具有此功能,因此您只需调用shell。
以下是一个例子:
RELATIVE_FILE1_FILE2:=$(shell realpath --relative-to $(FILE1) $(FILE2))
您甚至可以通过一次调用 realpath(1)来处理整个文件列表,因为它知道如何处理多个文件名。
以下是一个例子:
RELATIVES:=$(shell realpath --relative-to $(RELATIVE) $(FILES))
答案 3 :(得分:3)
这是仅使用GNU make函数的解决方案。即使它是递归的,它应该比调用外部程序更有效。这个想法非常简单:相对路径将为零或更多..前往最常见的祖先,然后是后缀到第二个目录。困难的部分是在两条路径中找到最长的公共前缀。
# 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)
我最近将一大组递归makefile转换为整个项目make,因为众所周知,递归make是不好的,因为没有暴露整个依赖图(http://aegis.sourceforge.net/auug97.pdf)。所有源代码和库路径都是相对于当前makefile目录定义的。我没有定义固定数量的通用%构建规则,而是为每个(源代码目录,输出目录)对创建一组规则,这避免了使用vpath的模糊性。在创建构建规则时,我需要每个源代码目录的规范路径。虽然可以使用绝对路径,但它通常太长而且不太便携(我碰巧使用Cygwin GNU make,绝对路径有/ cygdrive前缀,Windows程序无法识别)。因此,我大量使用此函数来生成规范路径。
答案 4 :(得分:2)
Didier的答案是最好的,但以下内容可能会给你一些想法:
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"
排序!
(没有人说它必须漂亮......)
答案 5 :(得分:1)
具有纯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
测试用例:
$(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/))
预期结果:
/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
答案 6 :(得分:0)
Perl是便携式的!这是一个使用Perl核心模块File :: Spec(resp。File :: Spec :: Functions)中的abs2rel函数的解决方案:
current_dir= /home/username/projects/app/trunk/src/libs/libfoo/
destination= /home/username/projects/app/build/libfoo/
relative= $(shell perl -MFile::Spec::Functions=abs2rel -E 'say abs2rel(shift, shift)' $(destination) $(current_dir))
makefile_target:
@echo "Use now $(relative)"