Regla GNU Makefile que genera algunos objetivos a partir de un solo archivo fuente

106

Estoy intentando hacer lo siguiente. Hay un programa, llámelo foo-bin, que toma un solo archivo de entrada y genera dos archivos de salida. Una regla tonta de Makefile para esto sería:

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

Sin embargo, esto no indica makede ninguna manera que ambos objetivos se generarán simultáneamente. Eso está bien cuando se ejecuta makeen serie, pero es probable que cause problemas si uno lo intenta make -j16o algo igualmente loco.

La pregunta es si existe una forma de escribir una regla Makefile adecuada para tal caso. Claramente, generaría un DAG, pero de alguna manera el manual de GNU make no especifica cómo se podría manejar este caso.

Ejecutar el mismo código dos veces y generar solo un resultado está fuera de discusión, porque el cálculo lleva tiempo (piense: horas). La salida de un solo archivo también sería bastante difícil, porque con frecuencia se usa como entrada para GNUPLOT que no sabe cómo manejar solo una fracción de un archivo de datos.

hace aurio
fuente
1
Automake tiene documentación sobre esto: gnu.org/software/automake/manual/html_node/…
Frank

Respuestas:

12

Lo resolvería de la siguiente manera:

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

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

En este caso, la marca paralela 'serializará' creando ayb, pero dado que la creación de b no hace nada, no lleva tiempo.

Peter Tillemans
fuente
16
De hecho, hay un problema con esto. Si file-b.outse creó como se indica y luego se eliminó misteriosamente, makeno podrá volver a crearlo porque file-a.outseguirá presente. ¿Alguna idea?
makesaurus
Si elimina misteriosamente file-a.out, la solución anterior funciona bien. Esto sugiere un truco: cuando solo existe uno de aob, entonces las dependencias deben ordenarse de manera que el archivo faltante aparezca como resultado de input.in. Unos pocos $(widcard...)s, s, $(filter...)s, $(filter-out...)etc., deberían funcionar. Ugh.
bobbogo
3
PS foo-binobviamente creará uno de los archivos primero (usando una resolución de microsegundos), por lo que debe asegurarse de tener el orden de dependencia correcto cuando ambos archivos existan.
bobbogo
2
@bobbogo eso, o agregar touch file-a.outcomo un segundo comando después de la foo-bininvocación.
Connor Harris
Aquí hay una posible solución para esto: agregue touch file-b.outcomo segundo comando en la primera regla y duplique los comandos en la segunda regla. Si falta input.inalgún archivo o es más antiguo , el comando se ejecutará una sola vez y regenerará ambos archivos con file-b.outmás reciente que file-a.out.
chqrlie
128

El truco consiste en utilizar una regla de patrón con varios objetivos. En ese caso, make asumirá que ambos destinos se crean mediante una única invocación del comando.

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

Esta diferencia en la interpretación entre las reglas de patrón y las reglas normales no tiene exactamente sentido, pero es útil para casos como este y está documentada en el manual.

Este truco se puede utilizar para cualquier número de archivos de salida siempre que sus nombres tengan alguna subcadena común para %que coincida. (En este caso, la subcadena común es ".")

perro lento
fuente
3
En realidad, esto no es correcto. Su regla especifica que los archivos que coincidan con estos patrones deben crearse a partir de input.in usando el comando especificado, pero en ninguna parte dice que se hagan simultáneamente. Si realmente lo ejecuta en paralelo, makeejecutará el mismo comando dos veces simultáneamente.
makesaurus
47
Inténtelo antes de decir que no funciona ;-) El manual de GNU make dice: "Las reglas de patrón pueden tener más de un objetivo. A diferencia de las reglas normales, esto no actúa como muchas reglas diferentes con los mismos prerrequisitos y comandos. Si una regla de patrón tiene múltiples objetivos, asegúrese de que los comandos de la regla son responsables de crear todos los objetivos. Los comandos se ejecutan solo una vez para crear todos los objetivos ". gnu.org/software/make/manual/make.html#Pattern-Intro
slowdog
4
Pero, ¿y si tengo entradas completamente diferentes ?, digamos foo.in y bar.src? ¿Las reglas de patrones admiten la coincidencia de patrones vacíos?
grwlf
2
@dcow: Los archivos de salida solo necesitan compartir una subcadena (incluso un solo carácter, como ".", es suficiente) no necesariamente un prefijo. Si no hay una subcadena común, puede usar un objetivo intermedio como lo describen richard y deemer. @grwlf: Oye, eso significa que "foo.in" y "bar.src" se pueden hacer foo%in bar%src: input.in:-)
slowdog
1
Si los archivos de salida respectivos se guardan en variables, la receta se puede escribir como: $(subst .,%,$(varA) $(varB)): input.in(probado con GNU make 4.1).
stefanct
55

Make no tiene una forma intuitiva de hacer esto, pero hay dos soluciones decentes.

Primero, si los objetivos involucrados tienen una raíz común, puede usar una regla de prefijo (con GNU make). Es decir, si quisiera corregir la siguiente regla:

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

Podrías escribirlo de esta manera:

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

(Usando la variable de regla de patrón $ *, que representa la parte coincidente del patrón)

Si desea ser portable a implementaciones Make que no sean GNU o si sus archivos no pueden ser nombrados para que coincidan con una regla de patrón, hay otra forma:

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

Esto le dice a make que input.in.intermediate no existirá antes de que se ejecute make, por lo que su ausencia (o su marca de tiempo) no hará que foo-bin se ejecute de manera falsa. Y ya sea que file-a.out o file-b.out o ambos estén desactualizados (en relación con input.in), foo-bin solo se ejecutará una vez. Puede usar .SECONDARY en lugar de .INTERMEDIATE, que indicará a make NOT que elimine un nombre de archivo hipotético input.in.intermediate. Este método también es seguro para compilaciones paralelas.

El punto y coma de la primera línea es importante. Crea una receta vacía para esa regla, para que Make sepa que realmente actualizaremos file-a.out y file-b.out (gracias @siulkiulki y otros que señalaron esto)

deemer
fuente
7
Bien, parece que el enfoque .INTERMEDIATE funciona bien. Consulte también el artículo de Automake que describe el problema (pero intentan resolverlo de forma portátil).
grwlf
3
Esta es la mejor respuesta que vi a esto. También podrían ser de interés los otros objetivos especiales: gnu.org/software/make/manual/html_node/Special-Targets.html
Arne Babenhauserheide
2
@deemer Esto no me funciona en OSX. (GNU Make 3.81)
Jorge Bucaran
2
He estado probando esto y he notado problemas sutiles con las compilaciones paralelas: las dependencias no siempre se propagan correctamente en el objetivo intermedio (aunque todo está bien con las -j1compilaciones). Además, si el archivo MAKE se interrumpe y deja el input.in.intermediatearchivo alrededor, se vuelve realmente confuso.
David dado el
3
Esta respuesta tiene un error. Puedes tener construcciones paralelas funcionando perfectamente. Solo tiene que cambiar file-a.out file-b.out: input.in.intermediatea file-a.out file-b.out: input.in.intermediate ;Ver stackoverflow.com/questions/37873522/… para obtener más detalles.
siulkilulki
10

Esto se basa en la segunda respuesta de @ deemer, que no se basa en reglas de patrón, y soluciona un problema que estaba experimentando con los usos anidados de la solución 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

Habría agregado esto como un comentario a la respuesta de @ deemer, pero no puedo porque acabo de crear esta cuenta y no tengo ninguna reputación.

Explicación: La receta vacía es necesaria para permitir que Make haga la contabilidad adecuada para marcar file-a.outy file-b.outcomo reconstruido. Si tiene otro objetivo intermedio del que depende file-a.out, Make elegirá no construir el intermedio externo, afirmando:

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

Después de GNU Make 4.3 (19 de enero de 2020), puede utilizar "objetivos explícitos agrupados". Reemplazar :con &:.

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

El archivo NEWS de GNU Make dice:

Nueva función: objetivos explícitos agrupados

Las reglas de patrón siempre han tenido la capacidad de generar múltiples objetivos con una sola invocación de la receta. Ahora es posible declarar que una regla explícita genera múltiples objetivos con una sola invocación. Para usar esto, reemplace el token ":" con "&:" en la regla. Para detectar esta característica, busque 'grupo-objetivo' en la variable especial .FEATURES. Implementación aportada por Kaz Kylheku <[email protected]>

kakkun61
fuente
2

Así es como lo hago. Primero, siempre separo los requisitos previos de las recetas. Entonces, en este caso, un nuevo objetivo para hacer la receta.

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 primera vez:
1. Intentamos construir file-a.out, pero dummy-a-and-b.out necesita hacerlo primero, así que ejecute la receta dummy-a-and-b.out.
2. Intentamos construir file-b.out, dummy-a-and-b.out está actualizado.

La segunda vez y las siguientes:
1. Intentamos construir file-a.out: revisamos los prerrequisitos, los prerrequisitos normales están actualizados, los prerrequisitos secundarios faltan, así que se ignoran.
2. Intentamos construir file-b.out: revise los prerrequisitos, los prerrequisitos normales están actualizados, los prerrequisitos secundarios faltan, así que se ignoran.

ctrl-alt-delor
fuente
1
Esto está descompuesto. Si elimina, file-b.outmake no tiene forma de volver a crearlo.
bobbogo
agregó todo: file-a.out file-b.out como primera regla. Como si file-b.out fuera eliminado y make se ejecutara sin destino, en la línea de comando, no lo reconstruiría.
ctrl-alt-delor
@bobbogo Corregido: vea el comentario anterior.
ctrl-alt-delor
0

Como una extensión de la respuesta de @ deemer, la he generalizado en una función.

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

Descompostura:

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

Quite cualquier espacio en blanco inicial / final del primer argumento

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

Cree un destino variable con un valor único para utilizarlo como destino intermedio. Para este caso, el valor será .inter.file-a.out_file-b.out.

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

Cree una receta vacía para las salidas con el objetivo único como dependencia.

$(eval .INTERMEDIATE: $(target) ) 

Declare el objetivo único como intermedio.

$(target)

Termine con una referencia al objetivo único para que esta función se pueda usar directamente en una receta.

También tenga en cuenta que el uso de eval aquí se debe a que eval se expande a nada, por lo que la expansión completa de la función es solo el objetivo único.

Debe dar crédito a las Reglas Atómicas en GNU Make, de las cuales se inspira esta función.

Lewis R
fuente
0

Para evitar la ejecución múltiple en paralelo de una regla con múltiples salidas con make -jN, utilice .NOTPARALLEL: outputs. En tu 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
guilloptero
fuente