web-dev-qa-db-fra.com

GNU Règle Makefile générant quelques cibles à partir d'un seul fichier source

J'essaie de faire ce qui suit. Il existe un programme, appelé foo-bin, qui prend un seul fichier d'entrée et génère deux fichiers de sortie. Une règle Makefile stupide pour cela serait:

file-a.out file-b.out: input.in
    foo-bin input.in file-a.out file-b.out

Cependant, cela ne dit en aucun cas à make que les deux cibles seront générées simultanément. C’est très bien lorsqu’on exécute make en série, mais cela posera probablement des problèmes si l’on essaye make -j16 ou quelque chose d’équivalent.

La question est de savoir s'il existe un moyen d'écrire une règle Makefile appropriée pour un tel cas? Clairement, cela générerait un DAG, mais le manuel GNU make ne précise pas comment cette affaire pourrait être traitée. 

Exécuter le même code deux fois et générer un seul résultat est hors de question, car le calcul prend du temps (pensez: heures). La sortie d'un seul fichier serait également assez difficile, car il est souvent utilisé comme une entrée pour GNUPLOT qui ne sait pas gérer seulement une fraction d'un fichier de données.

90
makesaurus

Je le résoudrais comme suit:

file-a.out: input.in
    foo-bin input.in file-a.out file-b.out   

file-b.out: file-a.out
    #do nothing
    noop

Dans ce cas, make parallèle 'sérialisera' en créant a et b mais comme créer b ne fait rien, cela ne prend pas de temps.

9
Peter Tillemans

L'astuce consiste à utiliser une règle de modèle avec plusieurs cibles. Dans ce cas, make supposera que les deux cibles sont créées par un seul appel de la commande.

 all: fichier-a.out fichier-b.out 
 fichier-a% out fichier-b% out: input.in 
 foo-bin input.in fichier-a $ * out fichier-b $ * out 

Cette différence d'interprétation entre les règles de modèle et les règles normales n'a pas vraiment de sens, mais elle est utile dans de tels cas, et cela est documenté dans le manuel.

Cette astuce peut être utilisée pour n'importe quel nombre de fichiers de sortie, à condition que leurs noms aient une sous-chaîne commune à laquelle le % doit correspondre. (Dans ce cas, la sous-chaîne commune est ".")

120
slowdog

Make n’a aucun moyen intuitif de le faire, mais il existe deux solutions de contournement décentes.

Premièrement, si les cibles impliquées ont une racine commune, vous pouvez utiliser une règle de préfixe (avec GNU make). Autrement dit, si vous voulez corriger la règle suivante:

object.out1 object.out2: object.input
    foo-bin object.input object.out1 object.out2

Vous pourriez l'écrire de cette façon:

%.out1 %.out2: %.input
    foo-bin $*.input $*.out1 $*.out2

(Utilisation de la variable de règle de modèle $ *, qui correspond à la partie correspondante du modèle)

Si vous souhaitez être portable vers des implémentations non-GNU Make ou si vos fichiers ne peuvent pas être nommés pour correspondre à une règle de modèle, il existe un autre moyen:

file-a.out file-b.out: input.in.intermediate ;

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

Cela indique à make que input.in.intermediate n'existera pas avant l'exécution de make, son absence (ou son horodatage) ne provoquera donc pas un fonctionnement erroné de foo-bin. Et que fichier-a.out ou fichier-b.out ou les deux soient obsolètes (par rapport à input.in), foo-bin ne sera exécuté qu'une fois. Vous pouvez utiliser .SECONDARY au lieu de .INTERMEDIATE, qui indiquera à NOT de ne pas supprimer un nom de fichier hypothétique input.in.intermediate. Cette méthode est également sûre pour les constructions en parallèle.

Le point-virgule sur la première ligne est important. Cela crée une recette vide pour cette règle, afin que Make sache que nous allons vraiment mettre à jour fichier-a.out et fichier-b.out (merci à @siulkiulki et aux autres personnes qui l'ont signalé)

49
deemer

Ceci est basé sur la deuxième réponse de @ deemer, qui ne repose pas sur des règles de modèle, et résout un problème que je rencontrais avec l'utilisation imbriquée de la solution de contournement.

file-a.out file-b.out: input.in.intermediate
    @# Empty recipe to propagate "newness" from the intermediate to final targets

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

J'aurais ajouté ceci en tant que commentaire à la réponse de @ deemer, mais je ne peux pas car je viens de créer ce compte et je n'ai aucune réputation.

Explication: La recette vide est nécessaire afin de permettre à Make d'effectuer la comptabilité appropriée pour marquer file-a.out et file-b.out comme ayant été reconstruit. Si vous avez encore une autre cible intermédiaire qui dépend de file-a.out, alors Make choisira de ne pas construire l’intermédiaire externe, en prétendant que:

No recipe for 'file-a.out' and no prerequisites actually changed.
No need to remake target 'file-a.out'.
6
Richard Xia

C'est comme ça que je le fais. D'abord, je sépare toujours les pré-requis des recettes. Puis dans ce cas une nouvelle cible pour faire la recette. 

all: file-a.out file-b.out #first rule

file-a.out file-b.out: input.in

file-a.out file-b.out: dummy-a-and-b.out

.SECONDARY:dummy-a-and-b.out
dummy-a-and-b.out:
    echo creating: file-a.out file-b.out
    touch file-a.out file-b.out

La première fois:
1. Nous essayons de construire file-a.out, mais dummy-a-and-b.out doit d’abord être exécuté.
2. Nous essayons de construire file-b.out, dummy-a-and-b.out est à jour.

La seconde fois et les suivantes:
1. Nous essayons de construire file-a.out: make regarde les prérequis, les prérequis normaux sont à jour, les prérequis secondaires sont manquants et donc ignorés.
2. Nous essayons de construire file-b.out: make regarde les prérequis, les prérequis normaux sont à jour, les prérequis secondaires sont manquants et donc ignorés.

3
ctrl-alt-delor

Dans le prolongement de la réponse de @ deemer, je l'ai généralisée en une fonction.

sp :=
sp +=
inter = .inter.$(subst $(sp),_,$(subst /,_,$1))

ATOMIC=\
    $(eval s1=$(strip $1)) \
    $(eval target=$(call inter,$(s1))) \
    $(eval $(s1): $(target) ;) \
    $(eval .INTERMEDIATE: $(target) ) \
    $(target)

$(call ATOMIC, file-a.out file-b.out): input.in
    foo-bin input.in file-a.out file-b.out

Panne:

$(eval s1=$(strip $1))

Supprimez tous les espaces de début/fin du premier argument

$(eval target=$(call inter,$(s1)))

Créez une cible de variable avec une valeur unique à utiliser comme cible intermédiaire. Dans ce cas, la valeur sera .inter.file-a.out_file-b.out.

$(eval $(s1): $(target) ;)

Créez une recette vide pour les sorties avec la cible unique comme dépendance.

$(eval .INTERMEDIATE: $(target) ) 

Déclarez la cible unique en tant qu'intermédiaire.

$(target)

Terminez avec une référence à la cible unique afin que cette fonction puisse être utilisée directement dans une recette.

Notez également que l'utilisation de eval ici est due au fait que eval ne se développe pas, de sorte que l'extension complète de la fonction est simplement la cible unique.

Doit donner crédit aux règles atomiques dans GNU Make dont cette fonction est inspirée. 

0
Lewis R