Regola GNU Makefile che genera alcuni target da un unico file sorgente


106

Sto tentando di fare quanto segue. Esiste un programma, chiamatelo foo-bin, che accetta un singolo file di input e genera due file di output. Una stupida regola di Makefile per questo sarebbe:

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

Tuttavia, questo non dice makein alcun modo che entrambi i target verranno generati contemporaneamente. Va bene quando si esegue makein serie, ma probabilmente causerà problemi se si prova make -j16o qualcosa di altrettanto pazzo.

La domanda è se esiste un modo per scrivere una regola Makefile adeguata per un caso del genere? Chiaramente, genererebbe un DAG, ma in qualche modo il manuale di GNU make non specifica come potrebbe essere gestito questo caso.

Eseguire lo stesso codice due volte e generare un solo risultato è fuori questione, perché il calcolo richiede tempo (pensa: ore). Anche l'output di un solo file sarebbe piuttosto difficile, perché spesso viene utilizzato come input per GNUPLOT che non sa come gestire solo una frazione di un file di dati.


1
Automake ha la documentazione su questo: gnu.org/software/automake/manual/html_node/…
Frank

Risposte:


12

Lo risolverei come segue:

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 questo caso parallel make 'serializzerà' la creazione di aeb ma poiché la creazione di b non fa nulla, non ci vuole tempo.


16
In realtà, c'è un problema con questo. Se è file-b.outstato creato come dichiarato e poi misteriosamente cancellato, non makesarà possibile ricrearlo perché file-a.outsarà comunque presente. qualche idea?
makeaurus

Se elimini misteriosamente, file-a.outla soluzione di cui sopra funziona bene. Questo suggerisce un trucco: quando esiste solo uno di a o b, le dipendenze dovrebbero essere ordinate in modo tale che il file mancante appaia come output di input.in. Alcuni $(widcard...)s, $(filter...)s, $(filter-out...)ecc. Dovrebbero fare il trucco. Ugh.
bobbogo

3
PS foo-binovviamente creerà prima uno dei file (usando una risoluzione di microsecondi), quindi devi assicurarti di avere il corretto ordine delle dipendenze quando esistono entrambi i file.
bobbogo

2
@bobbogo o quello, o aggiungi touch file-a.outcome secondo comando dopo l' foo-bininvocazione.
Connor Harris

Ecco una potenziale soluzione per questo: aggiungi touch file-b.outcome secondo comando nella prima regola e duplica i comandi nella seconda regola. Se uno dei file manca o è più vecchio di input.in, il comando verrà eseguito una sola volta e rigenererà entrambi i file con un file file-b.outpiù recente di file-a.out.
chqrlie

128

Il trucco sta nell'usare una regola del modello con più bersagli. In tal caso make presumerà che entrambi i target siano creati da una singola invocazione del comando.

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

Questa differenza nell'interpretazione tra le regole dei pattern e le regole normali non ha esattamente senso, ma è utile in casi come questo ed è documentata nel manuale.

Questo trucco può essere utilizzato per qualsiasi numero di file di output purché i loro nomi abbiano una sottostringa comune per la %corrispondenza. (In questo caso la sottostringa comune è ".")


3
Questo in realtà non è corretto. La tua regola specifica che i file che corrispondono a questi modelli devono essere creati da input.in usando il comando specificato, ma da nessuna parte si dice che siano creati simultaneamente. Se lo esegui effettivamente in parallelo, makeeseguirà lo stesso comando due volte contemporaneamente.
makeaurus

47
Per favore provalo prima di dire che non funziona ;-) Il manuale make di GNU dice: "Le regole dei pattern possono avere più di un obiettivo. A differenza delle regole normali, questo non agisce come molte regole diverse con gli stessi prerequisiti e comandi. Se una regola del modello ha più obiettivi, assicurati che i comandi della regola siano responsabili della creazione di tutti i bersagli. I comandi vengono eseguiti una sola volta per creare tutti i bersagli. " gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog

4
Ma cosa succede se ho input completamente diversi ?, Dì foo.in e bar.src? Le regole del modello supportano la corrispondenza del modello vuoto?
grwlf

2
@dcow: i file di output devono condividere solo una sottostringa (anche un singolo carattere, come ".", è sufficiente) non necessariamente un prefisso. Se non esiste una sottostringa comune, puoi utilizzare un obiettivo intermedio come descritto da richard e deemer. @grwlf: Ehi, questo significa che "foo.in" e "bar.src" possono essere eseguiti: foo%in bar%src: input.in:-)
slowdog

1
Se i rispettivi file di output sono conservati in variabili, la ricetta può essere scritta come: $(subst .,%,$(varA) $(varB)): input.in(testato con GNU make 4.1).
stefanct

55

Make non ha alcun modo intuitivo per farlo, ma ci sono due soluzioni alternative decenti.

Primo, se i target coinvolti hanno una radice comune, puoi usare una regola di prefisso (con GNU make). Cioè, se vuoi correggere la seguente regola:

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

Puoi scriverlo in questo modo:

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

(Utilizzando la variabile pattern-rule $ *, che sta per la parte corrispondente del pattern)

Se vuoi essere portabile su implementazioni non GNU Make o se i tuoi file non possono essere denominati per corrispondere a una regola di pattern, c'è un altro modo:

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

Questo dice a make che input.in.intermediate non esisterà prima che make venga eseguito, quindi la sua assenza (o il suo timestamp) non causerà l'esecuzione spuria di foo-bin. E se file-a.out o file-b.out o entrambi non sono aggiornati (rispetto a input.in), foo-bin verrà eseguito solo una volta. Puoi usare .SECONDARY invece di .INTERMEDIATE, che istruirà make NOT a cancellare un ipotetico nome di file input.in.intermediate. Questo metodo è sicuro anche per build parallele.

Il punto e virgola sulla prima riga è importante. Crea una ricetta vuota per quella regola, in modo che Make sappia che aggiorneremo davvero file-a.out e file-b.out (grazie @siulkiulki e altri che lo hanno sottolineato)


7
Bello, sembra che l'approccio .INTERMEDIATE funzioni bene. Vedi anche l' articolo di Automake che descrive il problema (ma cercano di risolverlo in modo portatile).
grwlf

3
Questa è la migliore risposta che ho visto a questo. Interessanti anche gli altri obiettivi speciali: gnu.org/software/make/manual/html_node/Special-Targets.html
Arne Babenhauserheide

2
@deemer Questo non funziona per me su OSX. (GNU Make 3.81)
Jorge Bucaran

2
Ho provato questo e ho notato problemi sottili con le build parallele: le dipendenze non si propagano sempre correttamente attraverso l'obiettivo intermedio (anche se tutto va bene con le -j1build). Inoltre, se il makefile viene interrotto e lascia il input.in.intermediatefile in giro, diventa davvero confuso.
David dato il

3
Questa risposta contiene un errore. Puoi avere build parallele che funzionano perfettamente. Devi solo passare file-a.out file-b.out: input.in.intermediatea file-a.out file-b.out: input.in.intermediate ;Vedere stackoverflow.com/questions/37873522/… per maggiori dettagli.
siulkilulki

10

Questo si basa sulla seconda risposta di @ deemer che non si basa sulle regole del modello e risolve un problema che stavo riscontrando con gli usi nidificati della soluzione alternativa.

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

Avrei aggiunto questo come commento alla risposta di @ deemer, ma non posso perché ho appena creato questo account e non ho alcuna reputazione.

Spiegazione: La ricetta vuota è necessaria per consentire a Make di fare la corretta contabilità per segnare file-a.oute file-b.outcome ricostruita. Se hai ancora un altro target intermedio da cui dipendefile-a.out , Make sceglierà di non costruire l'intermedio esterno, affermando:

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

9

Dopo GNU Make 4.3 (19 gennaio 2020) è possibile utilizzare "obiettivi espliciti raggruppati". Sostituisci :con &:.

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

Il file NEWS di GNU Make dice:

Nuova funzionalità: obiettivi espliciti raggruppati

Le regole del modello hanno sempre avuto la capacità di generare più bersagli con una singola invocazione della ricetta. È ora possibile dichiarare che una regola esplicita genera più destinazioni con una singola chiamata. Per utilizzarlo, sostituire il token ":" con "&:" nella regola. Per rilevare questa funzionalità, cerca "grouped-target" nella variabile speciale .FEATURES. Implementazione contribuito da Kaz Kylheku <kaz@kylheku.com>


2

Ecco come lo faccio. Per prima cosa separo sempre i pre-requisiti dalle ricette. Quindi in questo caso un nuovo target per fare la ricetta.

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 prima volta:
1. Proviamo a compilare file-a.out, ma prima è necessario che dummy-a-and-b.out esegua la ricetta dummy-a-and-b.out.
2. Proviamo a creare file-b.out, dummy-a-and-b.out è aggiornato.

La seconda volta e le successive:
1. Proviamo a compilare file-a.out: make esamina i prerequisiti, i normali prerequisiti sono aggiornati, i prerequisiti secondari mancano quindi ignorati.
2. Cerchiamo di creare file-b.out: fare uno sguardo ai prerequisiti, i normali prerequisiti sono aggiornati, i prerequisiti secondari mancano quindi ignorati.


1
Questo è rotto. Se elimini file-b.outmake non ha modo di ricrearlo.
bobbogo

aggiunto tutto: file-a.out file-b.out come prima regola. Come se file-b.out fosse stato rimosso e make fosse stato eseguito senza destinazione, sulla riga di comando, non lo avrebbe ricostruito.
ctrl-alt-delor

@bobbogo Risolto: vedi commento sopra.
ctrl-alt-delor

0

Come estensione alla risposta di @ deemer, l'ho generalizzata in una funzione.

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

Abbattersi:

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

Elimina qualsiasi spazio bianco iniziale / finale dal primo argomento

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

Crea un obiettivo variabile su un valore univoco da utilizzare come obiettivo intermedio. In questo caso, il valore sarà .inter.file-a.out_file-b.out.

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

Crea una ricetta vuota per le uscite con l'obiettivo univoco come dipendenza.

$(eval .INTERMEDIATE: $(target) ) 

Dichiara l'obiettivo univoco come intermedio.

$(target)

Termina con un riferimento all'obiettivo univoco in modo che questa funzione possa essere utilizzata direttamente in una ricetta.

Si noti inoltre che l'uso di eval qui è perché eval si espande a nulla, quindi la piena espansione della funzione è solo l'obiettivo unico.

Deve dare credito alle regole atomiche in GNU Make da cui si ispira questa funzione.


0

Per impedire l'esecuzione multipla parallela di una regola con più output con make -jN, utilizzare .NOTPARALLEL: outputs. Nel tuo caso :

.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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.