Crea directorios usando make file

101

Soy muy nuevo en makefiles y quiero crear directorios usando makefile. Mi directorio de proyectos es así

+--Project  
   +--output  
   +--source  
     +Testfile.cpp  
   +Makefile  

Quiero poner todos los objetos y la salida en la carpeta de salida respectiva. Quiero crear una estructura de carpetas que sería así después de compilar.

+--Project
   +--output
     +--debug (or release)
       +--objs
         +Testfile.o
       +Testfile (my executable file)
   +--source
     +Testfile.cpp
   +Makefile

Intenté con varias opciones, pero no pude. Ayúdame a crear directorios usando make file. Estoy publicando mi Makefile para su consideración.

#---------------------------------------------------------------------
# Input dirs, names, files
#---------------------------------------------------------------------
OUTPUT_ROOT := output/

TITLE_NAME := TestProj 

ifdef DEBUG 
    TITLE_NAME += _DEBUG
else
ifdef RELEASE
    TITLE_NAME += _RELEASE
endif
endif


# Include all the source files here with the directory tree
SOURCES := \
        source/TestFile.cpp \

#---------------------------------------------------------------------
# configs
#---------------------------------------------------------------------
ifdef DEBUG
OUT_DIR     := $(OUTPUT_ROOT)debug
CC_FLAGS    := -c -Wall
else
ifdef RELEASE
OUT_DIR     := $(OUTPUT_ROOT)release
CC_FLAGS    := -c -Wall
else
$(error no build type defined)
endif
endif

# Put objects in the output directory.
OUT_O_DIR   := $(OUT_DIR)/objs

#---------------------------------------------------------------------
# settings
#---------------------------------------------------------------------
OBJS = $(SOURCES:.cpp=.o)
DIRS = $(subst /,/,$(sort $(dir $(OBJS))))
DIR_TARGET = $(OUT_DIR)

OUTPUT_TARGET = $(OUT_DIR)/$(TITLE_NAME)

CC_FLAGS +=   

LCF_FLAGS := 

LD_FLAGS := 

#---------------------------------------------------------------------
# executables
#---------------------------------------------------------------------
MD := mkdir
RM := rm
CC := g++

#---------------------------------------------------------------------
# rules
#---------------------------------------------------------------------
.PHONY: all clean title 

all: title 

clean:
    $(RM) -rf $(OUT_DIR)

$(DIR_TARGET):
    $(MD) -p $(DIRS)

.cpp.o: 
    @$(CC) -c $< -o $@

$(OBJS): $(OUT_O_DIR)/%.o: %.cpp
    @$(CC) -c $< -o $@

title: $(DIR_TARGET) $(OBJS)

Gracias por adelantado. Por favor, guíeme si también cometí algún error.

Jabes
fuente

Respuestas:

88

Esto lo haría, asumiendo un entorno similar a Unix.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

${OUT_DIR}:
        ${MKDIR_P} ${OUT_DIR}

Esto debería ejecutarse en el directorio de nivel superior, o la definición de $ {OUT_DIR} debería ser correcta en relación con el lugar donde se ejecuta. Por supuesto, si sigue los edictos del documento " Recursive Make Considered Harmful " de Peter Miller, de todos modos estará ejecutando make en el directorio de nivel superior.

Estoy jugando con esto (RMCH) en este momento. Necesitaba un poco de adaptación al paquete de software que estoy usando como campo de prueba. La suite tiene una docena de programas separados creados con fuentes distribuidas en 15 directorios, algunos de ellos compartidos. Pero con un poco de cuidado, se puede hacer. OTOH, puede que no sea apropiado para un novato.


Como se señaló en los comentarios, enumerar el comando 'mkdir' como la acción para 'directorios' es incorrecto. Como también se señaló en los comentarios, hay otras formas de corregir el error 'no sé cómo hacer salida / depuración' que resulta. Uno es eliminar la dependencia en la línea 'directorios'. Esto funciona porque 'mkdir -p' no genera errores si ya existen todos los directorios que se le pide que cree. El otro es el mecanismo que se muestra, que solo intentará crear el directorio si no existe. La versión 'modificada' es lo que tenía en mente anoche, pero ambas técnicas funcionan (y ambas tienen problemas si existe salida / depuración pero es un archivo en lugar de un directorio).

Jonathan Leffler
fuente
Gracias Jonathan. cuando lo intenté, recibí un error "make: *** No hay regla para crear output/debug', needed by directorios de destino ". Detener ". Pero no me voy a preocupar por eso ahora. se apegará a las reglas básicas. :). Gracias por guiarnos. Y estoy ejecutando "make" solo desde el directorio de nivel superior.
Jabez
Simplemente elimine $ {OUT_DIR} detrás de los directorios:, entonces debería funcionar.
Doc Brown
Sin embargo, implementar esto requiere que detecte todos los casos posibles en los que use los directorios desde la línea de comandos. Por otra parte, no se puede hacer cualquier acumulación de generación de archivo de reglas depende directoriessin causar a reconstruir siempre ..
mtalexan
@mtalexan Ha proporcionado un par de comentarios que explican qué hay de malo en algunas de estas respuestas, pero no ha propuesto una respuesta alternativa. Ansioso por escuchar su solución para este problema.
Samuel
@Samuel Señalé los problemas sin dar una solución porque buscaba lo mismo y nunca encontré una solución. Terminé lidiando con la caída de una solución menos que ideal.
mtalexan
135

En mi opinión, los directorios no deberían considerarse objetivos de su archivo MAKE, ni en sentido técnico ni de diseño. Debe crear archivos y si la creación de un archivo necesita un nuevo directorio, cree silenciosamente el directorio dentro de la regla para el archivo relevante.

Si está apuntando a un archivo normal o "con patrón", simplemente use makela variable interna $(@D), que significa "el directorio en el que reside el objetivo actual" (cmp. With $@para el objetivo). Por ejemplo,

$(OUT_O_DIR)/%.o: %.cpp
        @mkdir -p $(@D)
        @$(CC) -c $< -o $@

title: $(OBJS)

Entonces, estás haciendo efectivamente lo mismo: crea directorios para todos $(OBJS), pero lo harás de una manera menos complicada.

La misma política (los archivos son destinos, los directorios nunca lo son) se usa en varias aplicaciones. Por ejemplo, el gitsistema de control de revisiones no almacena directorios.


Nota: Si lo va a utilizar, puede resultar útil introducir una variable de conveniencia y utilizar makelas reglas de expansión.

dir_guard=@mkdir -p $(@D)

$(OUT_O_DIR)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -c $< -o $@

$(OUT_O_DIR_DEBUG)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -g -c $< -o $@

title: $(OBJS)
P Shved
fuente
12
Si bien vincular el requisito del directorio a los archivos es una mejor opción en mi opinión, su solución también tiene el inconveniente significativo de que el archivo make llamará al proceso mkdir para cada archivo que se reconstruya, la mayoría de los cuales no necesitarán hacer el directorio de nuevo. Cuando se adapta a sistemas de compilación que no son de Linux como Windows, en realidad causa tanto una salida de error imbloqueable ya que no hay -p equivalente al comando mkdir, y lo que es más importante, una cantidad gigantesca de sobrecarga ya que la invocación del shell no es mínimamente invasiva.
mtalexan
1
En lugar de llamar directamente a mkdir, hice lo siguiente para evitar intentar crear el directorio si ya existe: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady
26

O, BESO.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Esto creará todos los directorios después de analizar el Makefile.

Nick Zalutskiy
fuente
3
Me gusta este enfoque porque no tengo que abarrotar cada objetivo con comandos para manejar los directorios.
Ken Williams
1
Solo necesitaba crear un directorio si no existe. Esta respuesta encaja perfectamente con mi problema.
Jeff Pal
No solo eso, esto evita que las marcas de tiempo modificadas de cada directorio activen un paso de compilación innecesario. Esta debería ser la respuesta
Assimilater
6
Esto es mejor: $(info $(shell mkdir -p $(DIRS)))sin el $(info ...), la salida del mkdircomando se pegará en el Makefile , lo que provocará errores de sintaxis en el mejor de los casos. La $(info ...)llamada asegura que a) los errores (si los hay) sean visibles para el usuario, yb) que la llamada a la función se expanda a la nada.
cmaster - reinstalar a monica
10

makedentro y fuera de sí mismo, maneja los destinos de directorio de la misma manera que los destinos de archivo. Entonces, es fácil escribir reglas como esta:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

El único problema con eso es que la marca de tiempo de los directorios depende de lo que se haga con los archivos internos. Para las reglas anteriores, esto conduce al siguiente resultado:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget

Definitivamente, esto no es lo que quieres. Siempre que toca el archivo, también toca el directorio. Y dado que el archivo depende del directorio, el archivo parece estar desactualizado, lo que obliga a reconstruirlo.

Sin embargo, puede romper fácilmente este ciclo diciéndole a make que ignore la marca de tiempo del directorio . Esto se hace declarando el directorio como un prerrequisito de solo pedido:

# The pipe symbol tells make that the following prerequisites are order-only
#                           |
#                           v
outDir/someTarget: Makefile | outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Esto produce correctamente:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
make: 'outDir/someTarget' is up to date.

TL; DR:

Escribe una regla para crear el directorio:

$(OUT_DIR):
    mkdir -p $(OUT_DIR)

Y hacer que los objetivos de las cosas internas dependan solo del orden del directorio:

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)
cmaster - reinstalar a monica
fuente
¿En qué OS / FS averiado vio touchmodificar algún dato estadístico en el directorio principal? Eso no tiene sentido para mí. El mtime de un directorio solo depende de los nombres de archivo que contiene. No pude reproducir tu problema.
Johan Boulé
@ JohanBoulé Debian.
cmaster - reinstalar a monica
¿Y llenó un error por un comportamiento tan roto?
Johan Boulé
6

Todas las soluciones, incluida la aceptada, tienen algunos problemas, como se indica en sus respectivos comentarios. La respuesta aceptada por @ jonathan-leffler ya es bastante buena, pero no tiene en cuenta que los requisitos previos no necesariamente deben construirse en orden (durante, make -jpor ejemplo). Sin embargo, simplemente mover el directoriesrequisito previo de alla programprovoca reconstrucciones en cada ejecución AFAICT. La siguiente solución no tiene ese problema y AFAICS funciona según lo previsto.

MKDIR_P := mkdir -p
OUT_DIR := build

.PHONY: directories all clean

all: $(OUT_DIR)/program

directories: $(OUT_DIR)

$(OUT_DIR):
    ${MKDIR_P} $(OUT_DIR)

$(OUT_DIR)/program: | directories
    touch $(OUT_DIR)/program

clean:
    rm -rf $(OUT_DIR)
Stefanct
fuente
4

Acabo de encontrar una solución bastante razonable que le permite definir los archivos para compilar y crear directorios automáticamente. Primero, defina una variable ALL_TARGET_FILESque contenga el nombre de archivo de cada archivo que se construirá su archivo MAKE. Luego usa el siguiente código:

define depend_on_dir
$(1): | $(dir $(1))

ifndef $(dir $(1))_DIRECTORY_RULE_IS_DEFINED
$(dir $(1)):
    mkdir -p $$@

$(dir $(1))_DIRECTORY_RULE_IS_DEFINED := 1
endif
endef

$(foreach file,$(ALL_TARGET_FILES),$(eval $(call depend_on_dir,$(file))))

Así es como funciona. Defino una función depend_on_dirque toma un nombre de archivo y genera una regla que hace que el archivo dependa del directorio que lo contiene y luego define una regla para crear ese directorio si es necesario. Luego uso foreachde callesta función en cada nombre de archivo y evalel resultado.

Tenga en cuenta que necesitará una versión de GNU make compatible eval, que creo que es la versión 3.81 y superior.

Ryan C. Thompson
fuente
Crear una variable que contenga "el nombre de archivo de cada archivo que construirá su archivo MAKE" es una especie de requisito oneroso: me gusta definir mis objetivos de nivel superior, luego las cosas de las que dependen, y así sucesivamente. Tener una lista plana de todos los archivos de destino va en contra de la naturaleza jerárquica de la especificación del archivo MAKE y puede no ser (fácilmente) posible cuando los archivos de destino dependen de los cálculos en tiempo de ejecución.
Ken Williams
3

dado que eres un novato, diría que no intentes hacer esto todavía. definitivamente es posible, pero complicará innecesariamente su Makefile. apégate a las formas simples hasta que te sientas más cómodo con make.

Dicho esto, una forma de construir en un directorio diferente del directorio de origen es VPATH ; prefiero reglas de patrones

solo alguien
fuente
3

La independencia del sistema operativo es fundamental para mí, así mkdir -pque no es una opción. Creé esta serie de funciones que se utilizan evalpara crear destinos de directorio con el requisito previo en el directorio principal. Esto tiene el beneficio de que make -j 2funcionará sin problemas ya que las dependencias están determinadas correctamente.

# convenience function for getting parent directory, will eventually return ./
#     $(call get_parent_dir,somewhere/on/earth/) -> somewhere/on/
get_parent_dir=$(dir $(patsubst %/,%,$1))

# function to create directory targets.
# All directories have order-only-prerequisites on their parent directories
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types
TARGET_DIRS:=
define make_dirs_recursively
TARGET_DIRS+=$1
$1: | $(if $(subst ./,,$(call get_parent_dir,$1)),$(call get_parent_dir,$1))
    mkdir $1
endef

# function to recursively get all directories 
#     $(call get_all_dirs,things/and/places/) -> things/ things/and/ things/and/places/
#     $(call get_all_dirs,things/and/places) -> things/ things/and/
get_all_dirs=$(if $(subst ./,,$(dir $1)),$(call get_all_dirs,$(call get_parent_dir,$1)) $1)

# function to turn all targets into directories
#     $(call get_all_target_dirs,obj/a.o obj/three/b.o) -> obj/ obj/three/
get_all_target_dirs=$(sort $(foreach target,$1,$(call get_all_dirs,$(dir $(target)))))

# create target dirs
create_dirs=$(foreach dirname,$(call get_all_target_dirs,$1),$(eval $(call make_dirs_recursively,$(dirname))))

TARGETS := w/h/a/t/e/v/e/r/things.dat w/h/a/t/things.dat

all: $(TARGETS)

# this must be placed after your .DEFAULT_GOAL, or you can manually state what it is
# https://www.gnu.org/software/make/manual/html_node/Special-Variables.html
$(call create_dirs,$(TARGETS))

# $(TARGET_DIRS) needs to be an order-only-prerequisite
w/h/a/t/e/v/e/r/things.dat: w/h/a/t/things.dat | $(TARGET_DIRS)
    echo whatever happens > $@

w/h/a/t/things.dat: | $(TARGET_DIRS)
    echo whatever happens > $@

Por ejemplo, ejecutar lo anterior creará:

$ make
mkdir w/
mkdir w/h/
mkdir w/h/a/
mkdir w/h/a/t/
mkdir w/h/a/t/e/
mkdir w/h/a/t/e/v/
mkdir w/h/a/t/e/v/e/
mkdir w/h/a/t/e/v/e/r/
echo whatever happens > w/h/a/t/things.dat
echo whatever happens > w/h/a/t/e/v/e/r/things.dat
SimplyKnownAsG
fuente