entre - Regla de GNU Makefile que genera unos pocos objetivos desde un único archivo fuente
makefile wikipedia (5)
Estoy intentando hacer lo siguiente. Hay un programa, llámalo foo-bin
, que toma un solo archivo de entrada y genera dos archivos de salida. Una regla muda 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 significa make
de ninguna manera que ambos objetivos se generarán simultáneamente. Eso está bien cuando se ejecuta make
en serie, pero probablemente cause problemas si uno intenta make -j16
o algo igualmente loco.
La pregunta es si existe una forma de escribir una regla adecuada de Makefile para tal caso. Claramente, generaría un DAG, pero de alguna manera el manual de fabricación de GNU 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 (pensar: horas). La generación de solo un archivo también sería bastante difícil, ya que con frecuencia se usa como entrada para GNUPLOT, que no sabe cómo manejar solo una fracción de un archivo de datos.
Así es como lo hago. Primero, siempre separo pre-requesits 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 crear file-a.out, pero dummy-a-and-b.out necesita hacerlo primero, así que make ejecuta la receta dummy-a-and-b.out.
2. Intentamos crear file-b.out, dummy-a-and-b.out está actualizado.
El segundo y el tiempo posterior:
1. Intentamos crear file-a.out: ver los requisitos previos, los requisitos previos normales están actualizados, faltan los requisitos previos secundarios, por lo que se ignoran.
2. Intentamos crear file-b.out: ver los requisitos previos, los requisitos previos normales están actualizados, faltan los requisitos previos secundarios, por lo que se ignoran.
El truco es usar una regla de patrón con múltiples objetivos. En ese caso, make supondrá que ambos objetivos se crean con una sola invocación 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
Esta diferencia en la interpretación entre reglas de patrones y reglas normales no tiene exactamente sentido, pero es útil para casos como este, y está documentada en el manual.
Este truco se puede usar para cualquier número de archivos de salida, siempre y cuando sus nombres tengan una subcadena común para que el %
coincida. (En este caso, la subcadena común es ".")
Esto se basa en la segunda respuesta de @ deemer, que no se basa en reglas de patrones, 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
Hubiera 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 que file-a.out
y file-b.out
han sido reconstruidos. Si tiene otro objetivo intermedio que depende de file-a.out
, entonces Make decidirá no construir el intermedio externo, alegando:
No recipe for ''file-a.out'' and no prerequisites actually changed.
No need to remake target ''file-a.out''.
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 se ''serializará'' creando a y b, pero como crear b no hace nada, no lleva tiempo.
Make no tiene una forma intuitiva de hacerlo, pero hay dos soluciones decentes.
En primer lugar, si los objetivos involucrados tienen una raíz común, puede usar una regla de prefijo (con la marca GNU). Es decir, si desea 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 portátil para implementaciones que no sean de GNU Make o si sus archivos no se pueden nombrar para que coincidan con una regla de patrón, existe 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 make se ejecute, por lo que su ausencia (o su sello de tiempo) no hará que foo-bin se ejecute de forma espuria. Y si 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 le indicará a NOT que elimine un nombre de archivo hipotético input.in.intermediate. Este método también es seguro para construcciones paralelas.