В Makefile, как получить относительный путь от абсолютного пути к другому?

пример для иллюстрации моего вопроса:

make-файл топ-уровня

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

Makefile для 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

как это можно сделать, пытаясь быть максимально портативным?

6 ответов


делать то, что вы хотите, нелегко. Это может быть возможно, используя много комбинированных $(if в makefile, но не портативный (только gmake) и громоздкий.

ИМХО, вы пытаетесь решить проблему, которую вы создаете сами. Почему бы вам не отправить правильное значение includedir как относительный путь из файла Makefile верхнего уровня? Это можно сделать очень легко следующим образом:

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

затем вы можете использовать $(includedir) в суб-makefiles. Он уже определен как родственник.


оболочка имеет эту функцию, используя realpath (1) и --относительно флаг, чтобы вы могли просто вызвать оболочку.

вот пример:

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

вы даже можете обработать целый список файлов с одним вызовом realpath (1) так как он знает, как для обработки имен файлов.

вот пример:

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

Python-это портативный! Итак, я бы предложил вам этот простой пример в вашем submakefile

С current_dir и destination_dir пути os.path.relpath() делает всю работу за вас, так что вам не придется заново изобретать колесо.

submakefile.МК

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

Дидье является лучшим, но может дать вам некоторые идеи:

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"

отсортированный!

(никто не сказал, что это должно быть красиво...)


вот решение, которое только использует функции GNU make. Даже если это рекурсивно, это должно быть более эффективно, чем вызов внешней программы. Идея довольно прямолинейна: относительный путь будет равен нулю или больше .. чтобы перейти к наиболее распространенному предку, затем суффикс, чтобы спуститься во 2-й каталог. Трудная часть-найти самый длинный общий префикс в обоих путях.

# 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). Поэтому я использую эту функцию в значительной степени для генерации канонических путей.


Робастное падени-в разрешении с чисто делает:

$(subst ,, ) := $(subst ,, )

define join
$(1)$(2)
endef

define dirname
$(patsubst %/,%,$(dir $(patsubst %/,%,$(1))))
endef

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

define prefix
$(call prefix_1,$(1),$(2),$(1))
endef

define relpath_1
$(patsubst /%,%,$(call join,$(subst $( ),/,$(patsubst %,..,$(subst /,$( ),
$(patsubst $(3)%,%,$(abspath $(2)))))),
$(patsubst $(3)%,%,$(abspath $(1)))))
endef

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