¿Cómo escribo el comando 'cd' en un archivo MAKE?
Por ejemplo, tengo algo como esto en mi archivo MAKE:
all:
cd some_directory
Pero cuando escribí make
solo vi 'cd some_directory', como en el echo
comando.
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 cd
por 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 cd
a ti mismo, por lo que tu regla se vería así
all:
$(MAKE) -C some_dir all
que cambiará some_dir
y ejecutará Makefile
en ese directorio, con el objetivo "todos". Como práctica recomendada, use $(MAKE)
en lugar de llamar make
directamente, 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.
A partir de GNU make 3.82 (julio de 2010), puede usar el .ONESHELL
objetivo especial para ejecutar todas las recetas en una única instancia del shell (el énfasis en negrita es mío):
- Nuevo objetivo especial:
.ONESHELL
indica 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 -e
bandera a su .SHELLFLAGS
:
.SHELLFLAGS += -e
También -o pipefail
te 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.
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
¿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)
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
.ONESHELL
objetivo especial porque:- esa es una opción global que afecta a todas las recetas en el archivo MAKE
- El uso
.ONESHELL
hace 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 llamarset -e
, pero dichas soluciones deberían implementarse para cada receta en el archivo MAKE.