2009-07-16 25 views
108

tengo un proyecto en el que la estructura de directorios es la siguiente:Makefile con los archivos de origen en diferentes directorios

      $projectroot 
           | 
       +---------------+----------------+ 
       |    |    | 
      part1/   part2/   part3/ 
       |    |    | 
     +------+-----+  +---+----+  +---+-----+ 
     |  |  |  |  |  |   | 
    data/ src/ inc/ src/  inc/ src/  inc/ 

¿Cómo debo escribir un makefile que sería en parte/src (o donde sea realmente) que podrían comple/enlace en los archivos fuente c/C++ en parte?/src?

¿Puedo hacer algo como -I $ projectroot/part1/src -I $ projectroot/part1/inc -I $ projectroot/part2/src ...

Si eso funcionaría, ¿hay una manera más fácil forma de hacerlo ¿He visto proyectos en los que hay un archivo MAKE en cada una de las partes correspondientes? carpetas. [En este post he usado el signo de interrogación como en la sintaxis de Bash]

+0

http://stackoverflow.com/questions/7123431/building-multiple-executables-with-similar-rules/7321954#7321954 –

+1

En el manual de gnu original (https://www.gnu.org/software/make /manual/html_node/Phony-Targets.html) bajo Phony Targets hay una muestra en 'invocación recursiva', que puede ser bastante elegante. –

+0

¿Qué herramienta usaste para crear ese gráfico de texto? –

Respuesta

85

La forma tradicional es tener un Makefile en cada uno de los subdirectorios (part1, part2, etc.) que le permite construir de forma independiente. Además, tenga un Makefile en el directorio raíz del proyecto que crea todo. La "raíz" Makefile sería algo como lo siguiente:

all: 
    +$(MAKE) -C part1 
    +$(MAKE) -C part2 
    +$(MAKE) -C part3 

Dado que cada línea en un objetivo maquillaje se ejecuta en su propia concha, no hay necesidad de preocuparse por atravesar una copia de seguridad del árbol de directorios o para otros directorios .

Sugiero echar un vistazo al GNU make manual section 5.7; es de mucha ayuda.

+0

+1 para el manual. Excelente recurso. –

+25

Esto debería ser '+ $ (MAKE) -C parte1', etc. Esto permite que el control de trabajo de Make funcione en los subdirectorios. – ephemient

+21

Este es un enfoque clásico y se usa ampliamente, pero no es óptimo de varias maneras que empeora a medida que crece el proyecto. Dave Hinton tiene el puntero a seguir. – dmckee

78

Si tiene código en un subdirectorio que depende del código en otro subdirectorio, probablemente sea mejor con un solo archivo MAKE en el nivel superior.

Consulte la explicación completa en el artículo Recursive Make Considered Harmful, pero básicamente debe hacer para tener la información completa que necesita para decidir si un archivo necesita o no ser reconstruido, y no tendrá eso si solo le informa un tercio de tu proyecto

El enlace anterior parece no ser alcanzable. En el mismo documento se puede llegar aquí:

+3

Gracias, no estaba enterado de esto. Es muy útil conocer la "forma correcta" de hacer las cosas en lugar de las que "simplemente funcionan" o se aceptan como estándar. – tjklemz

+0

La marca recursiva se consideraba dañina, cuando realmente no funcionaba bien. No se considera dañino en estos días, de hecho, es la forma en que autotools/automake gestionan proyectos más grandes. –

8

Creo que es mejor que señalar que el uso de make (recursivo o no) es algo que por lo general es posible que desee evitar, porque en comparación con las herramientas de hoy en día, es difícil de aprender, mantener y escalar.

Es una herramienta maravillosa, pero su uso directo se debe considerar obsoleto en 2010+.

A menos que, por supuesto, que está trabajando en un entorno especial, es decir con un proyecto heredado etc.

uso de un IDE, CMake o, si usted es dura sin corazón, el Autotools.

(editado debida a downvotes, ty Honza por señalar)

+1

Sería bueno que se explicaran los votos abajo.Solía ​​hacer mis Makefiles yo también. – IlDan

+2

Estoy votando por la sugerencia de "no hacer eso": KDevelop tiene una interfaz muy gráfica para configurar Make y otros sistemas de compilación, y mientras yo escribo Makefiles, KDevelop es lo que le doy a mis compañeros de trabajo, pero No creo que el enlace Autotools ayude. Para comprender Autotools, necesita comprender m4, libtool, automake, autoconf, shell, make y básicamente toda la pila. – ephemient

+3

Los votos bajos son probablemente porque está respondiendo su propia pregunta. La pregunta no era si uno debería usar Makefiles o no. Se trataba de cómo escribirlos. – Honza

19

La opción VPATH puede ser útil, que le dice a make qué directorios mirar para buscar el código fuente. Sin embargo, todavía necesitarías una opción -I para cada ruta de inclusión.Un ejemplo:

CXXFLAGS=-Ipart1/inc -Ipart2/inc -Ipart3/inc 
VPATH=part1/src:part2/src:part3/src 

OutputExecutable: part1api.o part2api.o part3api.o 

Este encontrará automáticamente los archivos partXapi.cpp coincidentes en cualquiera de los directorios especificados VPath y compilarlos. Sin embargo, esto es más útil cuando su directorio src está dividido en subdirectorios. Por lo que describes, como han dicho otros, probablemente estés mejor con un archivo MAKE para cada parte, especialmente si cada parte puede ser independiente.

+2

No puedo creer que esta simple respuesta perfecta no tenga más votos. Es un +1 de mi parte. –

+0

Tenía algunos archivos fuente comunes en un directorio superior para varios proyectos dispares en subcarpetas, '¡VPATH = ..' funcionó para mí! – EkriirkE

22

Puede agregar reglas a su raíz Makefile para compilar los archivos cpp necesarios en otros directorios. El siguiente ejemplo de Makefile debería ser un buen comienzo para llevarte a donde quieres estar.

 
CC=g++ 
TARGET=cppTest 
OTHERDIR=../../someotherpath/in/project/src 

SOURCE = cppTest.cpp 
SOURCE = $(OTHERDIR)/file.cpp 

## End sources definition 
INCLUDE = -I./ $(AN_INCLUDE_DIR) 
INCLUDE = -I.$(OTHERDIR)/../inc 
## end more includes 

VPATH=$(OTHERDIR) 
OBJ=$(join $(addsuffix ../obj/, $(dir $(SOURCE))), $(notdir $(SOURCE:.cpp=.o))) 

## Fix dependency destination to be ../.dep relative to the src dir 
DEPENDS=$(join $(addsuffix ../.dep/, $(dir $(SOURCE))), $(notdir $(SOURCE:.cpp=.d))) 

## Default rule executed 
all: $(TARGET) 
     @true 

## Clean Rule 
clean: 
     @-rm -f $(TARGET) $(OBJ) $(DEPENDS) 


## Rule for making the actual target 
$(TARGET): $(OBJ) 
     @echo "=============" 
     @echo "Linking the target [email protected]" 
     @echo "=============" 
     @$(CC) $(CFLAGS) -o [email protected] $^ $(LIBS) 
     @echo -- Link finished -- 

## Generic compilation rule 
%.o : %.cpp 
     @mkdir -p $(dir [email protected]) 
     @echo "=============" 
     @echo "Compiling $<" 
     @$(CC) $(CFLAGS) -c $< -o [email protected] 


## Rules for object files from cpp files 
## Object file for each file is put in obj directory 
## one level up from the actual source directory. 
../obj/%.o : %.cpp 
     @mkdir -p $(dir [email protected]) 
     @echo "=============" 
     @echo "Compiling $<" 
     @$(CC) $(CFLAGS) -c $< -o [email protected] 

# Rule for "other directory" You will need one per "other" dir 
$(OTHERDIR)/../obj/%.o : %.cpp 
     @mkdir -p $(dir [email protected]) 
     @echo "=============" 
     @echo "Compiling $<" 
     @$(CC) $(CFLAGS) -c $< -o [email protected] 

## Make dependancy rules 
../.dep/%.d: %.cpp 
     @mkdir -p $(dir [email protected]) 
     @echo "=============" 
     @echo Building dependencies file for $*.o 
     @$(SHELL) -ec '$(CC) -M $(CFLAGS) $< | sed "s^$*.o^../obj/$*.o^" > [email protected]' 

## Dependency rule for "other" directory 
$(OTHERDIR)/../.dep/%.d: %.cpp 
     @mkdir -p $(dir [email protected]) 
     @echo "=============" 
     @echo Building dependencies file for $*.o 
     @$(SHELL) -ec '$(CC) -M $(CFLAGS) $< | sed "s^$*.o^$(OTHERDIR)/../obj/$*.o^" > [email protected]' 

## Include the dependency files 
-include $(DEPENDS) 

+7

Sé que esto ya es bastante viejo pero, ¿no están ambos SOURCE e INCLUDE sobrescritos por otra asignación una línea más adelante? – skelliam

+0

@skelliam sí, lo hace. – nabroyan

+0

Este enfoque viola el principio DRY. Es una mala idea duplicar el código para cada "otro directorio" –

2

La publicación de RC fue SUPER útil. Nunca pensé en usar la función $ (dir $ @), pero hizo exactamente lo que necesitaba.

En parentDir, tiene un grupo de directorios con archivos fuente en ellos: dirA, dirB, dirC. Varios archivos dependen de los archivos objeto en otros directorios, así que quería poder hacer un archivo desde un directorio y hacer que esa dependencia llame al archivo MAKE asociado con esa dependencia.

Esencialmente, hice un Makefile en parentDir que tenía (entre muchas otras cosas) una regla genérica similar a RC:

%.o : %.cpp 
     @mkdir -p $(dir [email protected]) 
     @echo "=============" 
     @echo "Compiling $<" 
     @$(CC) $(CFLAGS) -c $< -o [email protected] 

Cada subdirectorio incluyó este makefile de nivel superior con el fin de heredar esta regla genérica En el Makefile de cada subdirectorio, escribí una regla personalizada para cada archivo para poder realizar un seguimiento de todo lo que dependía de cada archivo individual.

Cuando necesité hacer un archivo, utilicé (esencialmente) esta regla para hacer recursivamente cualquier/todas las dependencias. ¡Perfecto!

NOTA: hay una utilidad llamada "makepp" que parece hacer esta misma tarea de forma aún más intuitiva, pero por el bien de la portabilidad y no dependiendo de otra herramienta, elegí hacerlo de esta manera.

Espero que esto ayude!

2

Recursive Use of Make

all: 
    +$(MAKE) -C part1 
    +$(MAKE) -C part2 
    +$(MAKE) -C part3 

Esto permite make a dividirse en puestos de trabajo y el uso de múltiples núcleos

+2

¿Cómo es esto mejor que hacer algo como 'make -j4'? – devin

+1

@devin como yo lo veo, no es * mejor *, simplemente * permite * 'hacer' para usar el control de trabajo en absoluto. Cuando se ejecuta un proceso separado de make, no está sujeto al control del trabajo. –

15

Si las fuentes están repartidas en muchas carpetas, y que tiene sentido tener Makefile individuales a continuación, como se sugirió antes, recursive make es un buen enfoque, pero para proyectos más pequeños me resulta más fácil enumerar todos los archivos fuente en el Makefile con su ruta relativa al Makefile de esta manera:

# common sources 
COMMON_SRC := ./main.cpp \ 
       ../src1/somefile.cpp \ 
       ../src1/somefile2.cpp \ 
       ../src2/somefile3.cpp \ 

que puede entonces fijar VPATH esta manera:

VPATH := ../src1:../src2 

Entonces construir los objetos:

COMMON_OBJS := $(patsubst %.cpp, $(ObjDir)/%$(ARCH)$(DEBUG).o, $(notdir $(COMMON_SRC))) 

Ahora, la regla es simple:

# the "common" object files 
$(ObjDir)/%$(ARCH)$(DEBUG).o : %.cpp Makefile 
    @echo creating [email protected] ... 
    $(CXX) $(CFLAGS) $(EXTRA_CFLAGS) -c -o [email protected] $< 

y la construcción la salida es aún más fácil:

# This will make the cbsdk shared library 
$(BinDir)/$(OUTPUTBIN): $(COMMON_OBJS) 
    @echo building output ... 
    $(CXX) -o $(BinDir)/$(OUTPUTBIN) $(COMMON_OBJS) $(LFLAGS) 

Uno puede incluso hacer que la generación VPATH automatizado por:

VPATH := $(dir $(COMMON_SRC)) 

O usando el hecho de que sort elimina duplicados (aunque no debe importar):

VPATH := $(sort $(dir $(COMMON_SRC))) 
+0

esto funciona muy bien para proyectos más pequeños donde solo desea agregar algunas bibliotecas personalizadas y tenerlas en una carpeta separada. Muchas gracias – zitroneneis

-1

Sugiero use autotools:

//## Coloque los archivos de objetos generados (.o) en el mismo directorio que sus archivos de origen, para evitar colisiones cuando se utiliza la marca no recursiva.

AUTOMAKE_OPTIONS = subdir-objects 

simplemente incluyéndolo en Makefile.am con las otras cosas bastante simple.

Aquí está el tutorial.

Cuestiones relacionadas