GNU-Makefile-Regel, die einige Ziele aus einer einzelnen Quelldatei generiert


104

Ich versuche Folgendes zu tun. Es gibt ein Programm, nennen Sie es foo-bin, das eine einzelne Eingabedatei aufnimmt und zwei Ausgabedateien generiert. Eine dumme Makefile-Regel dafür wäre:

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

Dies bedeutet jedoch makein keiner Weise, dass beide Ziele gleichzeitig generiert werden. Das ist in Ordnung, wenn es makein Serie läuft , wird aber wahrscheinlich Probleme verursachen, wenn man es versucht make -j16oder etwas ähnlich Verrücktes.

Die Frage ist, ob es eine Möglichkeit gibt, eine richtige Makefile-Regel für einen solchen Fall zu schreiben. Natürlich würde es eine DAG erzeugen, aber irgendwie gibt das GNU make-Handbuch nicht an, wie dieser Fall behandelt werden könnte.

Es kommt nicht in Frage, denselben Code zweimal auszuführen und nur ein Ergebnis zu generieren, da die Berechnung Zeit in Anspruch nimmt (denken Sie an Stunden). Die Ausgabe nur einer Datei wäre ebenfalls ziemlich schwierig, da sie häufig als Eingabe für GNUPLOT verwendet wird, das nicht weiß, wie nur ein Bruchteil einer Datendatei zu verarbeiten ist.


1
Automake hat Dokumentation dazu: gnu.org/software/automake/manual/html_node/…
Frank

Antworten:


12

Ich würde es wie folgt lösen:

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

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

In diesem Fall "serialisiert" parallel make das Erstellen von a und b, aber da das Erstellen von b nichts bewirkt, dauert es keine Zeit.


16
Eigentlich gibt es ein Problem damit. Wenn file-b.outes wie angegeben erstellt und dann auf mysteriöse Weise gelöscht wurde, makekann es nicht neu erstellt werden, da file-a.outes weiterhin vorhanden ist. Irgendwelche Gedanken?
Makingaurus

Wenn Sie auf mysteriöse Weise löschen, file-a.outfunktioniert die obige Lösung gut. Dies deutet auf einen Hack hin: Wenn nur eines von a oder b vorhanden ist, sollten die Abhängigkeiten so angeordnet werden, dass die fehlende Datei als Ausgabe von angezeigt wird input.in. Ein paar $(widcard...)s, $(filter...)s, $(filter-out...)s usw. sollten den Trick machen. Pfui.
Bobbogo

3
PS erstellt foo-binoffensichtlich zuerst eine der Dateien (mit einer Auflösung von Mikrosekunden). Sie müssen also sicherstellen, dass Sie die richtige Abhängigkeitsreihenfolge haben, wenn beide Dateien vorhanden sind.
Bobbogo

2
@bobbogo entweder das oder touch file-a.outals zweiten Befehl nach dem foo-binAufruf hinzufügen .
Connor Harris

Hier ist eine mögliche Lösung dafür: Fügen Sie touch file-b.outin der ersten Regel einen zweiten Befehl hinzu und duplizieren Sie die Befehle in der zweiten Regel. Wenn eine Datei fehlt oder älter als ist input.in, wird der Befehl nur einmal ausgeführt und beide Dateien werden mit file-b.outneueren Dateien neu generiert file-a.out.
Chqrlie

127

Der Trick besteht darin, eine Musterregel mit mehreren Zielen zu verwenden. In diesem Fall geht make davon aus, dass beide Ziele durch einen einzigen Aufruf des Befehls erstellt werden.

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

Dieser Unterschied in der Interpretation zwischen Musterregeln und normalen Regeln ist nicht gerade sinnvoll, aber in solchen Fällen nützlich und im Handbuch dokumentiert.

Dieser Trick kann für eine beliebige Anzahl von Ausgabedateien verwendet werden, sofern deren Namen eine gemeinsame Teilzeichenfolge haben %, damit sie übereinstimmen. (In diesem Fall ist der übliche Teilstring ".")


2
Das ist eigentlich nicht richtig. Ihre Regel gibt an, dass Dateien, die diesen Mustern entsprechen, aus input.in mit dem angegebenen Befehl erstellt werden sollen, aber nirgends heißt es, dass sie gleichzeitig erstellt werden. Wenn Sie es tatsächlich parallel makeausführen , wird derselbe Befehl zweimal gleichzeitig ausgeführt.
Makingaurus

46
Bitte probieren Sie es aus, bevor Sie sagen, dass es nicht funktioniert Eine Musterregel hat mehrere Ziele. Machen Sie sich bewusst, dass die Befehle der Regel für die Erstellung aller Ziele verantwortlich sind. Die Befehle werden nur einmal ausgeführt, um alle Ziele zu erstellen. " gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog

4
Aber was ist, wenn ich völlig unterschiedliche Eingänge habe? Sagen wir foo.in und bar.src? Unterstützt Musterregeln die Übereinstimmung mit leeren Mustern?
Grwlf

2
@dcow: Die Ausgabedateien müssen nur eine Teilzeichenfolge gemeinsam nutzen (auch ein einzelnes Zeichen wie "." reicht aus), nicht unbedingt ein Präfix. Wenn es keinen gemeinsamen Teilstring gibt, können Sie ein Zwischenziel verwenden, wie von Richard und Deemer beschrieben. @grwlf: Hey, das bedeutet "foo.in" und "bar.src" getan werden kann: foo%in bar%src: input.in:-)
slowdog

1
Wenn die jeweiligen Ausgabedateien in Variablen gespeichert sind, kann das Rezept wie folgt geschrieben werden: $(subst .,%,$(varA) $(varB)): input.in(getestet mit GNU make 4.1).
stefanct

55

Make hat keine intuitive Möglichkeit, dies zu tun, aber es gibt zwei anständige Problemumgehungen.

Wenn die beteiligten Ziele einen gemeinsamen Stamm haben, können Sie zunächst eine Präfixregel verwenden (mit GNU make). Das heißt, wenn Sie die folgende Regel korrigieren möchten:

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

Sie könnten es so schreiben:

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

(Verwenden der Musterregelvariablen $ *, die für den übereinstimmenden Teil des Musters steht)

Wenn Sie für Nicht-GNU-Implementierungen portierbar sein möchten oder wenn Ihre Dateien nicht so benannt werden können, dass sie einer Musterregel entsprechen, gibt es einen anderen Weg:

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

Dies teilt make mit, dass input.in.intermediate nicht vorhanden ist, bevor make ausgeführt wird, sodass das Fehlen (oder der Zeitstempel) nicht dazu führt, dass foo-bin falsch ausgeführt wird. Und ob entweder file-a.out oder file-b.out oder beide veraltet sind (relativ zu input.in), foo-bin wird nur einmal ausgeführt. Sie können .SECONDARY anstelle von .INTERMEDIATE verwenden, wodurch make angewiesen wird, einen hypothetischen Dateinamen input.in.intermediate NICHT zu löschen. Diese Methode ist auch für parallele Make-Builds sicher.

Das Semikolon in der ersten Zeile ist wichtig. Es wird ein leeres Rezept für diese Regel erstellt, sodass Make weiß, dass wir file-a.out und file-b.out wirklich aktualisieren werden (danke @siulkiulki und anderen, die darauf hingewiesen haben).


7
Schön, sieht aus wie .INTERMEDIATE Ansatz funktioniert gut. Siehe auch Automake-Artikel, in dem das Problem beschrieben wird (sie versuchen jedoch, es auf tragbare Weise zu lösen).
Grwlf

3
Dies ist die beste Antwort, die ich darauf gesehen habe. Interessant könnten auch die anderen Sonderziele sein
Arne Babenhauserheide

2
@deemer Das funktioniert bei mir unter OSX nicht. (GNU Make
3.81

2
Ich habe dies versucht und dabei subtile Probleme mit parallelen Builds festgestellt. Abhängigkeiten breiten sich nicht immer ordnungsgemäß über das Zwischenziel aus (obwohl mit -j1Builds alles in Ordnung ist ). Wenn das Makefile unterbrochen wird und die input.in.intermediateDatei in der Nähe bleibt, wird es sehr verwirrt.
David Given

3
Diese Antwort hat einen Fehler. Sie können parallele Builds haben, die perfekt funktionieren. Sie müssen nur ändern sehen stackoverflow.com/questions/37873522/... für weitere Details. file-a.out file-b.out: input.in.intermediatefile-a.out file-b.out: input.in.intermediate ;
Siulkilulki

10

Dies basiert auf der zweiten Antwort von @ deemer, die nicht auf Musterregeln beruht, und behebt ein Problem, das bei verschachtelten Verwendungen der Problemumgehung aufgetreten ist.

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

Ich hätte dies als Kommentar zu @ deemers Antwort hinzugefügt, aber ich kann nicht, weil ich gerade dieses Konto erstellt habe und keinen Ruf habe.

Erläuterung: Das leere Rezept wird benötigt, damit Make die ordnungsgemäße Buchhaltung durchführen kann, um zu markieren file-a.outund file-b.outals neu erstellt zu werden. Wenn Sie noch ein anderes Zwischenziel haben, das davon abhängt file-a.out, wird Make entscheiden, das äußere Zwischenziel nicht zu erstellen, und behauptet:

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

8

Nach GNU Make 4.3 (19. Januar 2020) können Sie "gruppierte explizite Ziele" verwenden. Ersetzen :durch &:.

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

Die NEWS-Datei des GNU Make lautet:

Neue Funktion: Gruppierte explizite Ziele

Musterregeln hatten immer die Möglichkeit, mehrere Ziele mit einem einzigen Aufruf des Rezepts zu generieren. Es ist jetzt möglich zu deklarieren, dass eine explizite Regel mehrere Ziele mit einem einzigen Aufruf generiert. Ersetzen Sie dazu das Token ":" in der Regel durch "&:". Um diese Funktion zu erkennen, suchen Sie in der Spezialvariablen .FEATURES nach 'Gruppiertes Ziel'. Implementierung von Kaz Kylheku <kaz@kylheku.com>


3

So mache ich es. Zuerst trenne ich immer die Voraussetzungen von den Rezepten. Dann in diesem Fall ein neues Ziel, um das Rezept zu machen.

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

Das erste Mal:
1. Wir versuchen, file-a.out zu erstellen, aber Dummy-a-and-b.out muss zuerst ausgeführt werden, damit das Dummy-a-and-b.out-Rezept ausgeführt wird.
2. Wir versuchen, file-b.out zu erstellen, Dummy-a-and-b.out ist auf dem neuesten Stand.

Das zweite und nachfolgende Mal:
1. Wir versuchen, file-a.out zu erstellen: Schauen Sie sich die Voraussetzungen an, normale Voraussetzungen sind aktuell, sekundäre Voraussetzungen fehlen und werden ignoriert.
2. Wir versuchen, file-b.out zu erstellen: Schauen Sie sich die Voraussetzungen an, normale Voraussetzungen sind aktuell, sekundäre Voraussetzungen fehlen und werden ignoriert.


1
Das ist kaputt. Wenn Sie file-b.outmake löschen , können Sie es nicht neu erstellen.
Bobbogo

fügte alles hinzu: file-a.out file-b.out als erste Regel. Als ob file-b.out entfernt und make ohne Ziel in der Befehlszeile ausgeführt würde, würde es nicht neu erstellt.
Strg-Alt-Delor

@ Bobbogo Behoben: siehe obigen Kommentar.
Strg-Alt-Delor

0

Als Erweiterung der Antwort von @ deemer habe ich sie zu einer Funktion verallgemeinert.

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

Nervenzusammenbruch:

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

Entfernen Sie alle führenden / nachfolgenden Leerzeichen aus dem ersten Argument

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

Erstellen Sie ein variables Ziel mit einem eindeutigen Wert, der als Zwischenziel verwendet werden soll. In diesem Fall lautet der Wert .inter.file-a.out_file-b.out.

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

Erstellen Sie ein leeres Rezept für die Ausgaben mit dem eindeutigen Ziel als Abhängigkeit.

$(eval .INTERMEDIATE: $(target) ) 

Deklarieren Sie das eindeutige Ziel als Zwischenprodukt.

$(target)

Beenden Sie mit einem Verweis auf das eindeutige Ziel, damit diese Funktion direkt in einem Rezept verwendet werden kann.

Beachten Sie auch, dass eval hier verwendet wird, weil eval zu nichts erweitert wird, sodass die vollständige Erweiterung der Funktion nur das eindeutige Ziel ist.

Muss Kredit geben Atomic Regeln in GNU Make , die diese Funktion von inspiriert.


0

make -jNVerwenden Sie, um die parallele Mehrfachausführung einer Regel mit mehreren Ausgaben mit zu verhindern .NOTPARALLEL: outputs. In deinem Fall :

.NOTPARALLEL: file-a.out file-b.out

file-a.out file-b.out: input.in
    foo-bin input.in file-a.out file-b.out
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.