¿Cómo puedo crear un Makefile para proyectos C con subdirectorios SRC, OBJ y BIN?

95

Hace unos meses, se me ocurrió el siguiente genérico Makefilepara tareas escolares:

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# Date  : 2010-11-05
#
# Changelog :
#   0.01 - first version
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc -std=c99 -c
# compiling flags here
CFLAGS   = -Wall -I.

LINKER   = gcc -o
# linking flags here
LFLAGS   = -Wall

SOURCES  := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS  := $(SOURCES:.c=*.o)
rm       = rm -f

$(TARGET): obj
    @$(LINKER) $(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

obj: $(SOURCES) $(INCLUDES)
    @$(CC) $(CFLAGS) $(SOURCES)
    @echo "Compilation complete!"

clean:
    @$(rm) $(TARGET) $(OBJECTS)
    @echo "Cleanup complete!"

Básicamente, esto se compilará todas .cy .harchivo para generar .olos archivos y el ejecutable projectnameen la misma carpeta.

Ahora, me gustaría impulsar esto un poco. ¿Cómo puedo escribir un Makefile para compilar un proyecto C con la siguiente estructura de directorios?

 ./
 ./Makefile
 ./src/*.c;*.h
 ./obj/*.o
 ./bin/<executable>

En otras palabras, me gustaría tener un Makefile que las fuentes compila C desde ./src/dentro ./obj/y luego enlace todo para crear el ejecutable en ./bin/.

He intentado leer diferentes Makefiles, pero simplemente no puedo hacer que funcionen para la estructura del proyecto anterior; en cambio, el proyecto no se compila con todo tipo de errores. Claro, podría usar IDE completo (Monodevelop, Anjuta, etc.), pero honestamente prefiero quedarme con gEdit y el viejo terminal.

¿Hay algún gurú que pueda darme una solución funcional o información clara sobre cómo se puede hacer esto? ¡Gracias!

** ACTUALIZAR (v4) **

La solución definitiva :

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# Date  : 2011-08-10
#
# Changelog :
#   2010-11-05 - first version
#   2011-08-10 - added structure : sources, objects, binaries
#                thanks to http://stackoverflow.com/users/128940/beta
#   2017-04-24 - changed order of linker params
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc
# compiling flags here
CFLAGS   = -std=c99 -Wall -I.

LINKER   = gcc
# linking flags here
LFLAGS   = -Wall -I. -lm

# change these to proper directories where each file should be
SRCDIR   = src
OBJDIR   = obj
BINDIR   = bin

SOURCES  := $(wildcard $(SRCDIR)/*.c)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
rm       = rm -f


$(BINDIR)/$(TARGET): $(OBJECTS)
    @$(LINKER) $(OBJECTS) $(LFLAGS) -o $@
    @echo "Linking complete!"

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    @$(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

.PHONY: clean
clean:
    @$(rm) $(OBJECTS)
    @echo "Cleanup complete!"

.PHONY: remove
remove: clean
    @$(rm) $(BINDIR)/$(TARGET)
    @echo "Executable removed!"
Yanick Rochon
fuente
¿Cuál es la pregunta específica aquí?
Oliver Charlesworth
No estoy seguro de entender lo que quiere hacer.
Tom
Actualizado el Makefile. Me estoy acercando, pero tengo problemas con las variables automáticas, así que parece que de todos modos
Yanick Rochon
Acabo de encontrar una solución. Si alguien quiere encontrar algo mejor, el Makefile aún se puede mejorar.
Yanick Rochon
2
@YanickRochon No quise criticar tus habilidades en inglés. Pero para que los objetivos PHONY tengan algún sentido, definitivamente no puedes escribir BANANA;) gnu.org/software/make/manual/html_node/Phony-Targets.html
joni

Respuestas:

34

Primero, su $(OBJECTS)regla es problemática porque:

  1. es algo indiscriminado, lo que hace que todas las fuentes sean requisitos previos de cada objeto,
  2. a menudo usa la fuente incorrecta (como descubrió con file1.oy file2.o)
  3. intenta construir ejecutables en lugar de detenerse en objetos, y
  4. el nombre del objetivo ( foo.o) no es lo que realmente producirá la regla ( obj/foo.o).

Sugiero lo siguiente:

OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

La $(TARGET)regla tiene el mismo problema de que el nombre de destino no describe realmente lo que crea la regla. Por esa razón, si escribe makevarias veces, Make reconstruirá el objetivo cada vez, aunque no haya ninguna razón para hacerlo. Un pequeño cambio corrige que:

$(BINDIR)/$(TARGET): $(OBJECTS)
    $(LINKER) $@ $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Una vez que todo esté en orden, puede considerar un manejo de dependencias más sofisticado; si modifica uno de los archivos de encabezado, este archivo MAKE no sabrá qué objetos / ejecutables deben reconstruirse. Pero eso puede esperar otro día.

EDITAR:
Lo siento, omití parte de la $(OBJECTS)regla anterior; Lo he corregido. (Me gustaría poder usar "strike" dentro de una muestra de código).

Beta
fuente
con los cambios sugeridos, obtengo:obj/file1.o: In function 'main': \n main.c:(.text+0x0): multiple definition of 'main' \n obj/main.o:main.c:(.text+0x0): first defined here
Yanick Rochon
@Yanick Rochon: ¿Tiene varias mainfunciones? ¿Quizás uno dentro file1.cy otro dentro main.c? Si es así, no podrá vincular estos objetos; solo puede haber uno mainen un ejecutable.
Beta
No, yo no. Todo funciona bien con la última versión que publiqué en la pregunta. Cuando cambio mi Makefile a lo que sugieres (y entiendo los beneficios de lo que estás diciendo) eso es lo que obtengo. Acabo de pegar, file1.cpero da el mismo mensaje a todos los archivos del proyecto. Y main.ces el único con una función principal ... e main.cimporta file1.hy file2.h(no hay relación entre file1.cy file2.c), pero dudo que el problema venga de ahí.
Yanick Rochon
@Yanick Rochon: Cometí un error al pegar la primera línea de mi $(OBJECTS)regla; Lo he editado. Con la línea incorrecta recibí un error, pero no el que obtuviste ...
Beta
6

Puede agregar el -Iindicador a los indicadores del compilador (CFLAGS) para indicar dónde debe buscar el compilador los archivos fuente, y el indicador -o para indicar dónde debe dejarse el binario:

CFLAGS   = -Wall -I./src
TARGETPATH = ./bin

$(TARGET): obj
    @$(LINKER) $(TARGETPATH)/$(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Para colocar los archivos de objeto en el objdirectorio, use la -oopción al compilar. Además, observe las variables automáticas$@ y .$<

Por ejemplo, considere este sencillo Makefile

CFLAGS= -g -Wall -O3                                                            
OBJDIR= ./obj

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o )
all:$(OBJS)

%.o: %.c 
   $(CC) $(CFLAGS) -c $< -o $(OBJDIR)/$@

Actualizar>

Al mirar su archivo MAKE, me doy cuenta de que está usando la -obandera. Bueno. Continúe usándolo, pero agregue una variable de directorio de destino para indicar dónde se debe escribir el archivo de salida.

Tom
fuente
¿Podrías ser más específico? ¿Te refieres a agregar -l ...al CFLAGSy ... ya existe el -oargumento para el vinculador ( LINKER)
Yanick Rochon
Sí, los CFLAGS, y sí, siga usando -o, solo agregue la variable TARGETPATH.
Tom
Gracias, hice las modificaciones, pero parece que todavía me faltan algunas cosas (ver la actualización de la pregunta)
Yanick Rochon
solo make, desde donde se encuentra el Makefile
Yanick Rochon
¿No puedes leer el comando que se está ejecutando? por ejemplo gcc -c yadayada. Estoy bastante seguro de que hay una variable que no contiene lo que espera
Tom
-1

He dejado de escribir archivos MAKE en estos días, si su intención es aprender, adelante, de lo contrario, tiene un buen generador de archivos MAKE que viene con eclipse CDT. Si desea algún soporte de mantenimiento / múltiples proyectos en su árbol de compilación, eche un vistazo a lo siguiente:

https://github.com/dmoulding/boilermake ¡Encontré esto bastante bueno ...!

Kamath
fuente
3
Basado en opiniones. No responde a la pregunta de OP. Asume el entorno Eclipse.
Nathaniel Johnson