¿Cómo escribo el comando 'cd' en un archivo MAKE?

Resuelto Davit Siradeghyan asked hace 14 años • 8 respuestas

Por ejemplo, tengo algo como esto en mi archivo MAKE:

all:
     cd some_directory

Pero cuando escribí makesolo vi 'cd some_directory', como en el echocomando.

Davit Siradeghyan avatar Nov 24 '09 18:11 Davit Siradeghyan
Aceptado

En realidad, está ejecutando el comando, cambiando el directorio a some_directory; sin embargo, esto se realiza en un shell de subproceso y no afecta ni a make ni al shell desde el que está trabajando.

Si desea realizar más tareas dentro de some_directory, debe agregar un punto y coma y agregar también los otros comandos . Tenga en cuenta que no puede utilizar líneas nuevas, ya que make las interpreta como el final de la regla, por lo que cualquier línea nueva que utilice para mayor claridad debe ir acompañada de una barra invertida como escape.

Por ejemplo:

all:
        cd some_dir; echo "I'm in some_dir"; \
          gcc -Wall -o myTest myTest.c

Tenga en cuenta también que el punto y coma es necesario entre cada comando aunque agregue una barra invertida y una nueva línea. Esto se debe al hecho de que el shell analiza toda la cadena como una sola línea. Como se indicó en los comentarios, debe usar '&&' para unir comandos, lo que significa que solo se ejecutarán si el comando anterior fue exitoso.

all:
        cd some_dir && echo "I'm in some_dir" && \
          gcc -Wall -o myTest myTest.c

Esto es especialmente crucial cuando se realiza un trabajo destructivo, como la limpieza, ya que de lo contrario destruirá las cosas equivocadas, en caso de que falle cdpor cualquier motivo.

Sin embargo, un uso común es llamar a make en el subdirectorio, que quizás quieras investigar. Hay una opción de línea de comando para esto, por lo que no es necesario que te llames cda ti mismo, por lo que tu regla se vería así

all:
        $(MAKE) -C some_dir all

que cambiará some_diry ejecutará Makefileen ese directorio, con el objetivo "todos". Como práctica recomendada, use $(MAKE)en lugar de llamar makedirectamente, ya que se encargará de llamar a la instancia de make correcta (si, por ejemplo, usa una versión de make especial para su entorno de compilación), además de proporcionar un comportamiento ligeramente diferente al ejecutar usando ciertos interruptores, como -t.

Para que conste, make siempre repite el comando que ejecuta (a menos que se suprima explícitamente), incluso si no tiene salida, que es lo que estás viendo.

falstro avatar Nov 24 '2009 11:11 falstro

A partir de GNU make 3.82 (julio de 2010), puede usar el .ONESHELLobjetivo especial para ejecutar todas las recetas en una única instancia del shell (el énfasis en negrita es mío):

  • Nuevo objetivo especial: .ONESHELLindica a make que invoque una sola instancia del shell y le proporcione la receta completa , independientemente de cuántas líneas contenga.
.ONESHELL: # Applies to every targets in the file!

all:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded

another_rule:
    cd ~/some_dir
    pwd # Prints ~/some_dir if cd succeeded

Tenga en cuenta que esto será equivalente a ejecutar manualmente

$(SHELL) $(.SHELLFLAGS) "cd ~/some_dir; pwd"
# Which gets replaced to this, most of the time:
/bin/sh -c "cd ~/some_dir; pwd"

Los comandos no están vinculados &&, por lo que si desea detenerse en el primero que falla, también debe agregar la -ebandera a su .SHELLFLAGS:

.SHELLFLAGS += -e

También -o pipefailte puede interesar la bandera:

Si se establece, el valor de retorno de una canalización es el valor del último comando (el más a la derecha) que sale con un estado distinto de cero, o cero si todos los comandos de la canalización salen correctamente. Esta opción está deshabilitada de forma predeterminada.

Chnossos avatar Jun 02 '2015 07:06 Chnossos

Aquí tienes un lindo truco para lidiar con directorios y crear. En lugar de utilizar cadenas de varias líneas o "cd;" En cada comando, defina una función chdir simple como sigue:

CHDIR_SHELL := $(SHELL)
define chdir
   $(eval _D=$(firstword $(1) $(@D)))
   $(info $(MAKE): cd $(_D)) $(eval SHELL = cd $(_D); $(CHDIR_SHELL))
endef

Entonces todo lo que tienes que hacer es llamarlo en tu regla así:

all:
          $(call chdir,some_dir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c

Incluso puedes hacer lo siguiente:

some_dir/myTest:
          $(call chdir)
          echo "I'm now always in some_dir"
          gcc -Wall -o myTest myTest.c
JoeS avatar Aug 10 '2011 22:08 JoeS

¿Qué quieres que haga una vez que llegue allí? Cada comando se ejecuta en un subshell, por lo que el subshell cambia de directorio, pero el resultado final es que el siguiente comando todavía está en el directorio actual.

Con GNU make, puedes hacer algo como:

BIN=/bin
foo:
    $(shell cd $(BIN); ls)
Nadir SOUALEM avatar Nov 24 '2009 11:11 Nadir SOUALEM

Aquí está el patrón que he usado:

.PHONY: test_py_utils
PY_UTILS_DIR = py_utils
test_py_utils:
    cd $(PY_UTILS_DIR) && black .
    cd $(PY_UTILS_DIR) && isort .
    cd $(PY_UTILS_DIR) && mypy .
    cd $(PY_UTILS_DIR) && pytest -sl .
    cd $(PY_UTILS_DIR) && flake8 .

Mis motivaciones para este patrón son:

  • La solución anterior es simple y legible (aunque detallada)
  • Leí el artículo clásico "Recursive Make Considered Harmful" , que me disuadió de usar$(MAKE) -C some_dir all
  • No quería usar solo una línea de código (puntuada con punto y coma o &&) porque es menos legible y temo cometer un error tipográfico al editar la receta.
  • No quería usar el .ONESHELLobjetivo especial porque:
    • esa es una opción global que afecta a todas las recetas en el archivo MAKE
    • El uso .ONESHELLhace que se ejecuten todas las líneas de la receta incluso si una de las líneas anteriores ha fallado con un estado de salida distinto de cero. Son posibles soluciones como llamar set -e, pero dichas soluciones deberían implementarse para cada receta en el archivo MAKE.
Jasha avatar Feb 21 '2021 04:02 Jasha