В make-файле, как получить относительный путь от одного абсолютного пути к другому? - PullRequest
8 голосов
/ 27 июля 2010

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

Makefile верхнего уровня

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 ]

7 голосов
/ 27 июля 2010

Делать то, что вы хотите, нелегко. Это может быть возможно с использованием большого количества комбинированных $(if в make-файле, но не переносимых (только gmake) и громоздких.

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

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

Затем вы можете использовать $(includedir) в подфайлах. Он уже определен как относительный.

5 голосов
/ 08 августа 2016

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

Вотпример:

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

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

Вот пример:

RELATIVES:=$(shell realpath --relative-to $(RELATIVE) $(FILES))
5 голосов
/ 27 июля 2010

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

с current_dir и destination_dir paths os.path.relpath(), который сделает вашу работу за вас, поэтому вам не придется заново изобретать колесо.

submakefile.mk

current_dir=$(CURDIR)
makefile_target:
    (echo "import os"; echo "print os.path.relpath('$(destination_dir)', '$(current_dir)')" )| python
3 голосов
/ 02 февраля 2016

Вот решение, которое только использует 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)

Недавно я перевел большой набор рекурсивных make-файлов в проект всего проекта, поскольку хорошо известно, что рекурсивное make является плохим из-за того, что не отображается весь граф зависимостей (http://aegis.sourceforge.net/auug97.pdf). Все исходные коды и пути к библиотекам определены относительно в текущий каталог makefile. Вместо определения фиксированного числа общих правил сборки%, я создаю набор правил для каждой пары (каталог исходного кода, каталог вывода), что позволяет избежать неоднозначности использования vpath. При создании правил сборки Мне нужен канонический путь для каждой директории исходного кода. Хотя абсолютный путь можно использовать, он обычно слишком длинный и менее переносимый (мне случалось использовать Cygwin GNU make, где абсолютные пути имеют префикс / cygdrive и не распознаются Windows программы). Поэтому я активно использую эту функцию для генерации канонических путей.

2 голосов
/ 27 июля 2010

Ответ Дидье является лучшим, но следующее может дать вам несколько идей:

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"

Рассортировано!

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

1 голос
/ 19 сентября 2018

Надежное встроенное решение с чистой маркой:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...