在makefile中,如何获取从一个绝对路径到另一个绝对路径的相对路径?

时间:2010-07-27 07:25:37

标签: regex shell makefile path

举例说明我的问题:

顶级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

如何在尽可能便携的同时做到这一点?

7 个答案:

答案 0 :(得分:6)

做你想做的事并不容易。可能有可能在makefile中使用大量的$(if但不可移植(仅限gmake)并且很麻烦。

恕我直言,你正试图解决你自己创造的问题。为什么不从顶级Makefile发送正确的includedir值作为相对路径?它可以很容易地完成如下:

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

然后您可以在子makefile中使用$(includedir)。它已被定义为相对。

答案 1 :(得分:5)

Python是可移植的!所以,我建议你在submakefile中使用这个简单的例子

使用current_dirdestination_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)"