Makefiles con archivos fuente en diferentes directorios

135

Tengo un proyecto donde la estructura de directorios es así:

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

¿Cómo debo escribir un archivo MAKE que estaría en parte / src (o donde sea realmente) que podría completar / enlazar en parte los archivos fuente de c / c ++? / Src?

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

Si eso funcionara, ¿hay una manera más fácil de hacerlo? ¿He visto proyectos en los que hay un archivo MAKE en cada parte correspondiente? carpetas [en esta publicación usé el signo de interrogación como en la sintaxis bash]

devin
fuente
1
En el manual original de gnu ( gnu.org/software/make/manual/html_node/Phony-Targets.html ) en Phony Targets hay una muestra recursive invocation, que podría ser bastante elegante.
Frank Nocke
¿Qué herramienta usaste para crear ese gráfico de texto?
Khalid Hussain

Respuestas:

114

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 Makefiledirectorio raíz del proyecto que construya todo. La "raíz" Makefilese vería similar a la siguiente:

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

Dado que cada línea en un destino de creación se ejecuta en su propio shell, no hay necesidad de preocuparse por atravesar el árbol de directorios u otros directorios.

Sugiero echar un vistazo a la sección 5.7 de GNU make manual ; es de mucha ayuda.

akhan
fuente
26
Esto debería ser +$(MAKE) -C part1etc. Esto permite que el control de trabajo de Make funcione en los subdirectorios.
ephemient
26
Este es un enfoque clásico y se usa ampliamente, pero es subóptimo en varias formas que empeora a medida que el proyecto crece. Dave Hinton tiene el puntero a seguir.
dmckee --- ex-gatito moderador
89

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

Vea Recursive Make Considered Damful para la justificación completa, pero básicamente desea que tenga la información completa que necesita para decidir si un archivo necesita ser reconstruido o no, y no tendrá eso si solo le cuenta un tercio de tu proyecto.

El enlace de arriba parece no ser accesible. Aquí se puede acceder al mismo documento:

dave4420
fuente
3
Gracias, no estaba al tanto de esto. Es muy útil conocer la "forma correcta" de hacer las cosas en lugar de las formas que "simplemente funcionan" o se aceptan como estándar.
tjklemz
3
La marca recursiva se consideraba dañina, cuando realmente no funcionaba bien. Actualmente no se considera dañino, de hecho, es cómo las herramientas automáticas / automake manejan proyectos más grandes.
Edwin Buck
36

La opción VPATH puede ser útil, que le dice a qué directorios buscar el código fuente. Sin embargo, aún necesitaría 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

Esto buscará automáticamente los archivos partXapi.cpp coincidentes en cualquiera de los directorios especificados por VPATH y los compilará. Sin embargo, esto es más útil cuando su directorio src se divide en subdirectorios. Por lo que describe, como han dicho otros, probablemente sea mejor con un archivo MAKE para cada parte, especialmente si cada parte puede estar sola.

CodeGoat
fuente
2
No puedo creer que esta respuesta simple y perfecta no tenga más votos. Es un +1 de mi parte.
Nicholas Hamilton
2
Tenía algunos archivos fuente comunes en un directorio superior para varios proyectos dispares en subcarpetas, ¡ VPATH=..funcionó para mí!
EkriirkE
23

Puede agregar reglas a su Makefile raíz 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

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

## Definición de fuentes finales
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
INCLUYE = -I. $ (OTHERDIR) /../ inc
## finaliza más incluye

VPATH = $ (OTHERDIR)
OBJ = $ (unirse a $ (adduffix ../obj/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .o))) 

## Se ha corregido el destino de dependencia para que sea ../.dep en relación con el directorio src
DEPENDS = $ (join $ (adduffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Regla predeterminada ejecutada
todos: $ (OBJETIVO)
        @cierto

## Regla limpia
limpiar:
        @ -rm -f $ (OBJETIVO) $ (OBJ) $ (DEPENDE)


## Regla para hacer el objetivo real
$ (OBJETIVO): $ (OBJ)
        @echo "============="
        @echo "Vinculación del objetivo $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Enlace terminado -

## Regla de compilación genérica
% .o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilando $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Reglas para archivos de objetos de archivos cpp
## El archivo de objeto para cada archivo se coloca en el directorio obj
## un nivel por encima del directorio de origen real.
../obj/%.o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilando $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Regla para "otro directorio" Necesitará uno por "otro" directorio
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilando $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Hacer reglas de dependencia
../.dep/%.d:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Construyendo archivo de dependencias por $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Regla de dependencia para el directorio "otro"
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Construyendo archivo de dependencias por $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## Incluye los archivos de dependencia
-incluye $ (DEPENDE)

RC.
fuente
77
Sé que esto es bastante antiguo por ahora, pero, ¿no son ambas FUENTE e INCLUYE sobrescritas por otra tarea una línea más tarde?
skelliam
@skelliam sí, lo hace.
nabroyan
1
Este enfoque viola el principio DRY. Es una mala idea duplicar el código para cada "otro directorio"
Jesus H
20

Si las fuentes se distribuyen en muchas carpetas, y tiene sentido tener Makefiles individuales, como se sugirió anteriormente, make recursivo 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 así:

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

Entonces puedo configurar de VPATHesta manera:

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

Luego construyo 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 $@ ...
    $(CXX) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<

Y construir 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)

Incluso se puede VPATHautomatizar la generación mediante:

VPATH := $(dir $(COMMON_SRC))

O usando el hecho de que sortelimina duplicados (aunque no debería importar):

VPATH := $(sort  $(dir $(COMMON_SRC)))
con guiones
fuente
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
6

Creo que es mejor señalar que usar Make (recursivo o no) es algo que generalmente querrás evitar, porque en comparación con las herramientas actuales, es difícil de aprender, mantener y escalar.

Es una herramienta maravillosa, pero su uso directo debería considerarse obsoleto en 2010+.

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

Use un IDE, CMake o, si tiene un núcleo duro, los Autotools .

(editado debido a votos negativos, ty Honza por señalar)

IlDan
fuente
1
Sería bueno tener explicados los votos negativos. Yo también solía hacer mis Makefiles.
IlDan el
2
Estoy votando por la sugerencia de "no hagas eso": KDevelop tiene una interfaz muy gráfica para configurar Make y otros sistemas de compilación, y mientras escribo Makefiles, KDevelop es lo que les doy a mis compañeros de trabajo, pero yo No creo que el enlace Autotools ayude. Para comprender las Autotools, debe comprender m4, libtool, automake, autoconf, shell, make y, básicamente, toda la pila.
efímero
77
Los votos negativos probablemente se deben a que estás respondiendo tu propia pregunta. La pregunta no era si uno debería usar Makefiles o no. Se trataba de cómo escribirlos.
Honza
8
Sería bueno si pudiera, en pocas oraciones, explicar cómo las herramientas modernas hacen la vida más fácil que escribir un Makefile. ¿Proporcionan una abstracción de nivel superior? ¿o que?
Abhishek Anand
1
xterm, vi, bash, makefile == gobiernan el mundo, cmake es para personas que no pueden hackear código.
μολὼν.λαβέ
2

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

En parentDir, tenga un montón de directorios con archivos fuente en ellos: dirA, dirB, dirC. Varios archivos dependen de los archivos de objetos en otros directorios, por lo que quería poder crear un archivo desde dentro de 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 las RC:

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

Cada subdirectorio incluía este archivo MAKE de nivel superior para heredar esta regla genérica. En el Makefile de cada subdirectorio, escribí una regla personalizada para cada archivo para poder hacer un seguimiento de todo de lo que dependía cada archivo individual.

Cada vez que necesitaba hacer un archivo, usaba (esencialmente) esta regla para hacer recursivamente cualquiera / todas las dependencias. ¡Perfecto!

NOTA: hay una utilidad llamada "makepp" que parece realizar esta tarea aún más intuitivamente, pero en aras de la portabilidad y no dependiendo de otra herramienta, elegí hacerlo de esta manera.

¡Espero que esto ayude!

jvriesem
fuente
2

Uso recursivo de Make

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

Esto permite makedividirse en trabajos y usar múltiples núcleos

EhevuTov
fuente
2
¿Cómo es esto mejor que hacer algo así make -j4?
devin
1
@devin tal como lo veo, no es mejor , solo permite make usar el control del trabajo. Cuando se ejecuta un proceso de creación por separado, no está sujeto al control del trabajo.
Michael Pankov
1

Estaba buscando algo como esto y después de algunos intentos y caídas creo mi propio archivo MAKE, sé que esa no es la "forma idiomática", pero es un comienzo para entender hacer y esto funciona para mí, tal vez podrías intentarlo en tu proyecto.

PROJ_NAME=mono

CPP_FILES=$(shell find . -name "*.cpp")

S_OBJ=$(patsubst %.cpp, %.o, $(CPP_FILES))

CXXFLAGS=-c \
         -g \
        -Wall

all: $(PROJ_NAME)
    @echo Running application
    @echo
    @./$(PROJ_NAME)

$(PROJ_NAME): $(S_OBJ)
    @echo Linking objects...
    @g++ -o $@ $^

%.o: %.cpp %.h
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS) -o $@

main.o: main.cpp
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS)

clean:
    @echo Removing secondary things
    @rm -r -f objects $(S_OBJ) $(PROJ_NAME)
    @echo Done!

Sé que eso es simple y para algunas personas mis indicadores están equivocados, pero como dije, este es mi primer Makefile para compilar mi proyecto en múltiples directorios y vincularlos para crear mi bin.

Estoy aceptando sugerencias: D

Matheus Vinícius de Andrade
fuente
-1

Sugiero usar autotools:

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

AUTOMAKE_OPTIONS = subdir-objects

solo incluyéndolo Makefile.amcon otras cosas bastante simples.

Aquí está el tutorial .

usuario2763812
fuente