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

发布于 2024-09-11 09:09:05 字数 687 浏览 4 评论 0原文

一个例子来说明我的问题:

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)

另一个例子:

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

如何做到这一点,同时尝试尽可能可移植?

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, while trying to be as portable as possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

回忆凄美了谁 2024-09-18 09:09:05

您可以使用shell函数,并使用realpath(1)(它是coreutils的一部分)和--relative-标记。

下面是一个示例:

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

您甚至可以通过一次调用 realpath(1) 来处理整个文件列表,因为它知道如何处理许多文件名。

这是一个例子:

RELATIVES:=$(shell realpath --relative-to $(RELATIVE) $(FILES))

You can use the shell function, and use realpath(1) (which is part of coreutils) and the --relative-to flag.

Here is an example:

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

You can even process a whole list of files with one invocation of realpath(1) since it knows how to process many file names.

Here is an example:

RELATIVES:=$(shell realpath --relative-to $(RELATIVE) $(FILES))
迟到的我 2024-09-18 09:09:05

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

恕我直言,您正在尝试解决您自己创建的问题。为什么不呢您将 includedir 的正确值作为顶级 Makefile 的相对路径发送吗?这可以很容易地完成,如下所示:

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

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

Doing what you want does not look easy. It may be possible using a lot of combined $(if in the makefile but not portable (gmake only) and cumbersome.

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

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

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

心房敞 2024-09-18 09:09:05

Python 是可移植的!因此,我建议您在子makefile中使用这个简单的示例,

使用 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

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

With current_dir and destination_dir paths os.path.relpath() does the job for you so you do not have to re-invent the wheel.

submakefile.mk

current_dir=$(CURDIR)
makefile_target:
    (echo "import os"; echo "print(os.path.relpath('$(destination_dir)', '$(current_dir)'))" )| python
滥情稳全场 2024-09-18 09:09:05

具有纯 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

Robust drop-in 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
唱一曲作罢 2024-09-18 09:09:05

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"

排序!

(没有人说它必须很漂亮......)

Didier's answer is the best one, but the following might 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 had to be pretty...)

掩于岁月 2024-09-18 09:09:05

这是一个使用 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 很糟糕,因为它没有公开整个依赖关系图 (

Here's a solution that only uses GNU make functions. Even though it's recursive, it ought to be more efficient than calling an external program. The idea is pretty straight forward: the relative path will be zero or more .. to go up to the most common ancestor, then a suffix to go down to the 2nd directory. The hard part is finding the longest common prefix in both paths.

# 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 a whole project make as it's well known that recursive make is bad due to not exposing the entire dependence graph (http://aegis.sourceforge.net/auug97.pdf). All source code and library paths are defined relative to the current makefile directory. Instead of defining a fixed number of generic % build rules, I create a set of rules for every (source code directory, output directory) pair, which avoids the ambiguity of using vpath. When creating the build rules, I need a canonical path for each source code directory. Although the absolute path can be used, it's usually too long and less portable (I happened to be using Cygwin GNU make where absolute paths have a /cygdrive prefix and aren't recognized by Windows programs). Therefore, I use this function heavily for generating canonical paths.

末骤雨初歇 2024-09-18 09:09:05

Perl 是可移植的!下面是使用核心 Perl 模块 File::Spec(或 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)"

Perl is portable! Here's a solution using the abs2rel function from the core Perl module File::Spec (resp. File::Spec::Functions):

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)"
风筝在阴天搁浅。 2024-09-18 09:09:05

这是我基于诺曼·格雷的想法的解决方案:

define relative
CURDIR=$(1); UP=; while ! expr $(2) : ${CURDIR} > /dev/null; do UP=../${UP}; CURDIR=$(dirname ${CURDIR}); done; echo ${UP}$(expr $(2) : ${CURDIR}'/*\(.*\)')
endef

includedir1=/a/b/c/d
includedir2=/a/b/e/f/g/h/j
currentdir=/a/b/e/f/g

relative1:=$(shell $(call relative,$(currentdir),$(includedir1)))

makefile_target:
    @echo "Relative1: $(relative1)"
    @echo "Relative2: $(shell $(call relative,$(currentdir),$(includedir2)))"

This is my solution based on the Norman Gray's idea:

define relative
CURDIR=$(1); UP=; while ! expr $(2) : ${CURDIR} > /dev/null; do UP=../${UP}; CURDIR=$(dirname ${CURDIR}); done; echo ${UP}$(expr $(2) : ${CURDIR}'/*\(.*\)')
endef

includedir1=/a/b/c/d
includedir2=/a/b/e/f/g/h/j
currentdir=/a/b/e/f/g

relative1:=$(shell $(call relative,$(currentdir),$(includedir1)))

makefile_target:
    @echo "Relative1: $(relative1)"
    @echo "Relative2: $(shell $(call relative,$(currentdir),$(includedir2)))"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文