2010-07-27 27 views
6

Un exemple pour illustrer ma question:Dans un makefile, comment obtenir le chemin relatif d'un chemin absolu à un autre?

Niveau supérieur makefile

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

Makefile pour src/libfoo

currentdir = $(realpath .) 
includedir = $(function or magic to make a relative path 
       from $(currentdir) to $(includedir), 
       which in this example would be ../../../include) 

Un autre exemple:

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

Comment cela peut-il être fait , tout en essayant d'être aussi portable que possible?

Répondre

6

Faire ce que vous voulez n'a pas l'air facile. Il peut être possible d'utiliser beaucoup de $(if combiné dans le fichier makefile mais pas portable (gmake seulement) et encombrant.

À mon humble avis, vous essayez de résoudre un problème que vous créez vous-même. Pourquoi ne pas envoyer la valeur correcte de includedir en tant que chemin relatif à partir du Makefile de niveau supérieur? Il peut se faire très facilement comme suit:

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

Ensuite, vous pouvez utiliser $(includedir) dans les sous-makefiles. Il est déjà défini comme relatif.

+0

Cette méta-réponse est le meilleur - éviter le problème! Notez aussi que '--directory' n'est pas portable:' cd $ (rootdir)/src/libs/libfoo; $ (MAKE) ... 'serait plus fiable. –

+1

@Norman Gray: qu'est-ce qui ne va pas avec '$ (MAKE) -C ...'? – Beta

+0

Je ne pense pas que j'irais aussi loin que _wrong_, mais toutes les commandes 'make' ne le supportent pas (et ce n'est pas dans [posix] (http://www.opengroup.org/onlinepubs/009695399/utilities/make .html), par exemple), donc si ce makefile est pour la distribution, alors cela pourrait créer des problèmes. En tout cas, j'ai tendance à penser que cd foo; $ (MAKE) ... 'exprime l'intention plus clairement. –

4

Python est portable! Donc, je vous suggère cet exemple simple dans votre submakefile

Avec os.path.relpath() fait le travail pour vous et les chemins current_dirdestination_dir de sorte que vous n'avez pas à réinventer la roue.

submakefile.mk

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

Premier: excellente solution! Je vais devoir me rappeler d'utiliser la géniale bibliothèque OS de python ... Deuxièmement: vous avez changé les arguments de os.path.relpath(). Il devrait être: os.path.relpath ('$ (destination_dir)', '$ (current_dir)') – jvriesem

+0

J'ai écrit un script python qui prend 1-2 paramètres et imprime le résultat. Appelez-le ensuite par $ (shell python relpath.py $ (destination_dir)) '. Astuce incroyable! –

2

La réponse de Didier est le meilleur, mais ce qui suit pourrait vous donner quelques idées:

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" 

Classé!

(personne ne dit qu'il fallait être assez ...)

1

est ici une solution qui ne utilise GNU make fonctions. Même s'il est récursif, il devrait être plus efficace que d'appeler un programme externe. L'idée est assez simple: le chemin relatif sera zéro ou plus ... pour remonter à l'ancêtre le plus commun, puis un suffixe pour descendre au 2ème répertoire. La partie difficile est de trouver le plus long préfixe commun dans les deux chemins.

# 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) 

J'ai récemment traduit un grand nombre de makefiles récursifs dans un projet tout faire comme il est bien connu que faire récursive est mauvais en raison de ne pas exposer l'ensemble de graphe de dépendance (http://aegis.sourceforge.net/auug97.pdf). Tous les chemins de code source et de bibliothèque sont définis par rapport au répertoire makefile actuel. Au lieu de définir un nombre fixe de règles de génération% génériques, je crée un ensemble de règles pour chaque paire (répertoire de code source, répertoire de sortie), ce qui évite l'ambiguïté d'utiliser vpath. Lors de la création des règles de construction, j'ai besoin d'un chemin canonique pour chaque répertoire de code source. Bien que le chemin absolu puisse être utilisé, il est généralement trop long et moins portable (j'utilisais Cygwin GNU make où les chemins absolus ont un préfixe/cygdrive et ne sont pas reconnus par les programmes Windows).Par conséquent, j'utilise cette fonction fortement pour générer des chemins canoniques.

4

La coque a cette fonction à l'aide realpath (1) et le --relative à drapeau de sorte que vous pouvez simplement invoquer le shell.

Voici un exemple:

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

Vous pouvez même traiter une liste de fichiers avec une invocation de realpath (1) car il sait comment traiter beaucoup de noms de fichiers.

est un exemple:

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

realpath n'existe pas dans mon shell. (BSD) Je ne pense pas qu'il existe sous cygwin, non plus. – dbn

+1

realpath (1) fait partie de GNU coreutils donc c'est la norme sur Linux. –

Questions connexes