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

7 minutos de lectura

avatar de usuario
Yanick Rochón

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

# ------------------------------------------------
# Generic Makefile
#
# Author: yanick.rochon@gmail.com
# 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!"

Esto básicamente compilará cada .c y .h archivo para generar .o archivos y el ejecutable projectname todo en 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 compile fuentes C de ./src/ dentro ./obj/ y luego vincular 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 la buena 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: yanick.rochon@gmail.com
# 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!"

  • ¿Cuál es la pregunta específica aquí?

    –Oliver Charlesworth

    10 de agosto de 2011 a las 0:44

  • No estoy seguro de entender lo que quieres hacer.

    – Tomás

    10 de agosto de 2011 a las 0:44

  • Actualizado el Makefile. Me estoy acercando, pero tengo problemas con las variables automáticas, por lo que parece de todos modos

    – Yanick Rochón

    10 de agosto de 2011 a las 3:06

  • Acabo de encontrar una solución. Si a alguien le interesa encontrar algo mejor, el Makefile aún se puede mejorar.

    – Yanick Rochón

    10 de agosto de 2011 a las 4:05

  • @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

    10 de marzo de 2016 a las 5:54

avatar de usuario
Beta

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

  1. es un poco indiscriminado, haciendo todos fuentes requisitos previos de cada objeto,
  2. a menudo usa la fuente equivocada (como descubriste con file1.o y file2.o)
  3. intenta crear ejecutables en lugar de detenerse en los objetos, y
  4. el nombre del objetivo (foo.o) no es lo que la regla realmente producirá (obj/foo.o).

Sugiero lo siguiente:

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

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

los $(TARGET) rule tiene el mismo problema de que el nombre de destino en realidad no describe lo que crea la regla. Por esa razón, si escribe make varias veces, Make reconstruirá el objetivo cada vez, aunque no haya 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, podría 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 a otro día.

EDITAR:

Lo siento, omití parte del $(OBJECTS) regla anterior; lo he corregido (Desearía poder usar “strike” dentro de una muestra de código).

  • 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 Rochón

    10 de agosto de 2011 a las 5:18


  • @Yanick Rochon: ¿Tienes múltiples main funciones? tal vez uno en file1.c y uno en main.c? Si es así, no podrá vincular estos objetos; Sólo puede haber uno main en un ejecutable.

    – Beta

    10 de agosto de 2011 a las 5:49


  • 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 dices), eso es lo que obtengo. acabo de pegar file1.c pero da el mismo mensaje a todos los archivos del proyecto. Y main.c es el solamente uno con una función principal… y main.c importaciones file1.h y file2.h (no hay relación entre file1.c y file2.c), pero dudo que el problema venga de ahí.

    – Yanick Rochón

    10 de agosto de 2011 a las 6:25


  • @Yanick Rochon: Me equivoqué al pegar la primera línea de mi $(OBJECTS) regla; lo he editado Con la linea mala me salio un error, pero no el que te salio a ti…

    – Beta

    10 de agosto de 2011 a las 12:48

avatar de usuario
Tomás

Puedes agregar el -I bandera a las banderas del compilador (CFLAGS) para indicar dónde debe buscar el compilador los archivos fuente, y la bandera -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 obj directorio, utilice el -o opción al compilar. Además, mira el $@ y $< variables automáticas.

Por ejemplo, considere este simple 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 el -o bandera. Bueno. Continúe usándolo, pero agregue una variable de directorio de destino para indicar dónde debe escribirse el archivo de salida.

  • ¿Podría ser más específico? ¿Quieres decir agregar -l ... al CFLAGS y… ya está el -o argumento al enlazador (LINKER)

    – Yanick Rochón

    10 de agosto de 2011 a las 1:03


  • Sí, los CFLAGS, y sí, sigue usando -o, solo agrega la variable TARGETPATH.

    – Tomás

    10 de agosto de 2011 a las 1:07

  • Gracias, hice las modificaciones, pero parece que todavía me faltan algunas cosas (vea la actualización sobre la pregunta)

    – Yanick Rochón

    10 de agosto de 2011 a las 1:16

  • sólo makedesde donde se encuentra el Makefile

    – Yanick Rochón

    10 de agosto de 2011 a las 1:25

  • ¿No puedes leer el comando que se está ejecutando? por ejemplo gcc -c yadayada. Bastante seguro de que hay una variable que no contiene lo que esperas

    – Tomás

    10 de agosto de 2011 a las 1:41

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 algo de mantenimiento / compatibilidad con múltiples proyectos en su árbol de compilación, eche un vistazo a lo siguiente:

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

  • Basado en opiniones. No responde la pregunta de OP. Supone un entorno Eclipse.

    –Nathaniel Johnson

    2 de diciembre de 2017 a las 14:27

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad