template - ¿Cómo hacer un Makefile simple de C++?
makefile linux (7)
Estamos obligados a usar un Makefile para juntar todo para nuestro proyecto, pero nuestro profesor nunca nos mostró cómo hacerlo.
Solo tengo UN archivo, a3driver.cpp
. El controlador importa una clase desde una ubicación "/user/cse232/Examples/example32.sequence.cpp"
.
Eso es todo, todo lo demás está contenido con el .cpp
.
¿Cómo hago para crear un Makefile simple que cree un ejecutable llamado a3a.exe
?
¿Por qué a todos les gusta listar los archivos fuente? Un simple comando de búsqueda puede encargarse de eso fácilmente.
Aquí hay un ejemplo de un sencillo Makefile de C ++. Simplemente colóquelo en un directorio que contenga archivos .C
y luego escriba ...
appname := myapp
CXX := clang++
CXXFLAGS := -std=c++11
srcfiles := $(shell find . -name "*.C")
objects := $(patsubst %.C, %.o, $(srcfiles))
all: $(appname)
$(appname): $(objects)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)
depend: .depend
.depend: $(srcfiles)
rm -f ./.depend
$(CXX) $(CXXFLAGS) -MM $^>>./.depend;
clean:
rm -f $(objects)
dist-clean: clean
rm -f *~ .depend
include .depend
Copiado de una publicación de la wiki que escribí para estudiantes de física.
Como esto es para unix, los ejecutables no tienen extensiones.
Una cosa a tener en cuenta es que root-config
es una utilidad que proporciona la compilación correcta y las banderas de enlace; y las bibliotecas adecuadas para construir aplicaciones contra root. Eso es solo un detalle relacionado con la audiencia original de este documento.
Hazme bebé
o nunca olvidas la primera vez que te hiciste
Una discusión introductoria de make y cómo escribir un makefile simple
¿Qué es hacer? ¿Y por qué debería importarme?
La herramienta llamada make es un administrador de dependencias de compilación. Es decir, se ocupa de saber qué comandos deben ejecutarse en qué orden tomar el proyecto de software de una colección de archivos de origen, archivos de objetos, bibliotecas, encabezados, etc. etc .-- algunos de los cuales pueden haber cambiado recientemente --- y convertirlos en una versión correcta y actualizada del programa.
En realidad, también puedes usar make para otras cosas, pero no voy a hablar de eso.
Un makefile trivial
Supongamos que tiene un directorio que contiene: tool
tool.cc
tool.o
support.cc
support.hh
y support.o
que dependen de la root
y se supone que deben compilarse en un programa llamado tool
, y suponga que ha estado hackeando en los archivos de origen (lo que significa que la tool
existente ya no está actualizada) y desea compilar el programa.
Para hacer esto usted mismo podría
1) compruebe si support.cc
o support.hh
es más nuevo que support.o
, y si es así ejecute un comando como
g++ -g -c -pthread -I/sw/include/root support.cc
2) compruebe si support.hh
o tool.cc
son más nuevos que tool.o
, y si es así ejecute un comando como
g++ -g -c -pthread -I/sw/include/root tool.cc
3) verifique si tool.o
es más nuevo que tool
, y si es así ejecute un comando como
g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint /
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices /
-Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl
¡Uf! ¡Qué lío! Hay mucho que recordar y varias posibilidades de cometer errores. (Por cierto: los detalles de las líneas de comando que se muestran aquí dependen de nuestro entorno de software. Estos funcionan en mi computadora).
Por supuesto, puedes ejecutar los tres comandos cada vez. Eso funcionaría, pero no se adapta bien a una pieza sustancial de software (como DOGS, que tarda más de 15 minutos en compilarse desde cero en mi MacBook).
En su lugar, podría escribir un archivo llamado makefile
como este:
tool: tool.o support.o
g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint /
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices /
-Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl
tool.o: tool.cc support.hh
g++ -g -c -pthread -I/sw/include/root tool.cc
support.o: support.hh support.cc
g++ -g -c -pthread -I/sw/include/root support.cc
y solo escribe make
en la línea de comando. que realizará los tres pasos que se muestran arriba automáticamente.
Las líneas sin sangría aquí tienen la forma "destino: dependencias" y le dicen a make que los comandos asociados (líneas con sangría) se ejecuten si alguna de las dependencias es más nueva que el destino. Es decir, las líneas de dependencia describen la lógica de lo que se debe reconstruir para adaptarse a los cambios en varios archivos. Si support.cc
cambia, eso significa que support.o
debe reconstruirse, pero tool.o
puede dejarse solo. Cuando la tool
cambios support.o
debe ser reconstruida.
Los comandos asociados con cada línea de dependencia se activan con una pestaña (ver a continuación) deben modificar el objetivo (o al menos tocarlo para actualizar la hora de modificación).
Variables, reglas incorporadas y otras golosinas
En este punto, nuestro makefile es simplemente recordar el trabajo que se necesita hacer, pero aún tuvimos que descubrir y escribir todos y cada uno de los comandos necesarios en su totalidad. No tiene que ser así: make es un lenguaje poderoso con variables, funciones de manipulación de texto y una gran cantidad de reglas incorporadas que pueden hacernos esto mucho más fácil.
Hacer variables
La sintaxis para acceder a una variable make es $(VAR)
.
La sintaxis para asignar a una variable de VAR = A text value of some kind
es: VAR = A text value of some kind
(o VAR := A different text value but ignore this for the moment
).
Puede usar variables en reglas como esta versión mejorada de nuestro makefile:
CPPFLAGS=-g -pthread -I/sw/include/root
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint /
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz /
-Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root /
-lm -ldl
tool: tool.o support.o
g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)
tool.o: tool.cc support.hh
g++ $(CPPFLAGS) -c tool.cc
support.o: support.hh support.cc
g++ $(CPPFLAGS) -c support.cc
que es un poco más legible, pero aún requiere mucha escritura
Hacer funciones
GNU make admite una variedad de funciones para acceder a la información del sistema de archivos u otros comandos en el sistema. En este caso, estamos interesados en $(shell ...)
que se expande a la salida de los argumentos, y $(subst opat,npat,text)
que reemplaza todas las instancias de opat
con npat
en el texto.
Aprovechando esto nos da:
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
tool: $(OBJS)
g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)
tool.o: tool.cc support.hh
g++ $(CPPFLAGS) -c tool.cc
support.o: support.hh support.cc
g++ $(CPPFLAGS) -c support.cc
que es más fácil de escribir y mucho más legible.
Darse cuenta de
- Todavía estamos indicando explícitamente las dependencias para cada archivo objeto y el ejecutable final
- Hemos tenido que escribir explícitamente la regla de compilación para ambos archivos de origen
Reglas implícitas y de patrón
Por lo general, esperaríamos que todos los archivos fuente de c ++ se traten de la misma manera, y proporcionamos tres maneras de establecer esto
- reglas de sufijo (consideradas obsoletas en GNU make, pero guardadas para compatibilidad con versiones anteriores)
- reglas implícitas
- reglas de patrones
Las reglas implícitas están incorporadas, y algunas se discutirán a continuación. Las reglas de patrón se especifican en una forma como
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
lo que significa que los archivos objeto se generan a partir de archivos fuente c ejecutando el comando mostrado, donde la variable "automática" $<
expande al nombre de la primera dependencia.
Reglas incorporadas
Make tiene una gran cantidad de reglas incorporadas que significan que muy a menudo, un proyecto puede compilarse con un archivo make muy simple, de hecho.
La regla de creación de GNU incorporada para los archivos fuente c es la que se muestra arriba. De manera similar, creamos archivos de objetos a partir de archivos fuente de c ++ con una regla como $(CXX) -c $(CPPFLAGS) $(CFLAGS)
Los archivos de un solo objeto se vinculan utilizando $(LD) $(LDFLAGS) no $(LOADLIBES) $(LDLIBS)
, pero esto no funcionará en nuestro caso, porque queremos vincular varios archivos de objetos.
Variables utilizadas por las reglas incorporadas
Las reglas incorporadas utilizan un conjunto de variables estándar que le permiten especificar información del entorno local (como dónde encontrar los archivos de inclusión ROOT) sin volver a escribir todas las reglas. Los que más nos interesarán son:
-
CC
- el compilador de c para usar -
CXX
- el compilador de c ++ para usar -
LD
- el enlazador a usar -
CFLAGS
- bandera de compilación para archivos fuente c -
CXXFLAGS
- banderas de compilación para archivos fuente de c ++ -
CPPFLAGS
: indicadores para el preprocesador c (normalmente incluyen las rutas de los archivos y los símbolos definidos en la línea de comandos), utilizados por c y c ++ -
LDFLAGS
- banderas del enlazador -
LDLIBS
- bibliotecas para enlazar
Un makefile básico
Al aprovechar las reglas incorporadas, podemos simplificar nuestro makefile para:
CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
tool.o: tool.cc support.hh
support.o: support.hh support.cc
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) tool
También hemos agregado varios objetivos estándar que realizan acciones especiales (como limpiar el directorio de origen).
Tenga en cuenta que cuando se invoca make sin un argumento, utiliza el primer destino que se encuentra en el archivo (en este caso, todos), pero también puede asignar un nombre al objetivo para obtener lo que hace que make clean
elimine los archivos de objeto en este caso.
Todavía tenemos todas las dependencias codificadas.
Algunas mejoras misteriosas
CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
depend: .depend
.depend: $(SRCS)
$(RM) ./.depend
$(CXX) $(CPPFLAGS) -MM $^>>./.depend;
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) *~ .depend
include .depend
Darse cuenta de
- ¡Ya no hay líneas de dependencia para los archivos de origen!?!
- Hay alguna magia extraña relacionada con .dependir y depender.
- Si
.depend
entoncesls -A
verá un archivo llamado.depend
que contiene elementos que parecen hacer líneas de dependencia
Otra lectura
- GNU hace manual
- Recursive Make Considered Harmful en una forma común de escribir makefiles que no es óptima, y cómo evitarlo.
Conoce errores y notas históricas
El idioma de entrada para make es sensible al espacio en blanco. En particular, las líneas de acción que siguen a las dependencias deben comenzar con una pestaña . Pero una serie de espacios puede tener el mismo aspecto (y, de hecho, hay editores que convertirán tabuladores en espacios de manera silenciosa o viceversa), lo que da como resultado un archivo make que se ve bien y aún no funciona. Esto se identificó como un error desde el principio, pero (según la historia ) no se solucionó porque ya había 10 usuarios.
Siempre pensé que esto era más fácil de aprender con un ejemplo detallado, así que así es como pienso en makefiles. Para cada sección tiene una línea que no tiene sangría y muestra el nombre de la sección seguido de las dependencias. Las dependencias pueden ser otras secciones (que se ejecutarán antes de la sección actual) o archivos (que, si se actualizan, harán que la sección actual vuelva a ejecutarse la próxima vez que ejecute make
).
Aquí hay un ejemplo rápido (tenga en cuenta que estoy usando 4 espacios donde debería usar una pestaña, no me permite usar pestañas):
a3driver: a3driver.o
g++ -o a3driver a3driver.o
a3driver.o: a3driver.cpp
g++ -c a3driver.cpp
Cuando escribes make
, elegirá la primera sección (a3driver). a3driver depende de a3driver.o, por lo que irá a esa sección. a3driver.o depende de a3driver.cpp, por lo que solo se ejecutará si a3driver.cpp ha cambiado desde la última vez que se ejecutó. Suponiendo que se haya ejecutado (o nunca se haya ejecutado), compilará a3driver.cpp en un archivo .o, luego regresará a a3driver y compilará el archivo ejecutable final.
Como solo hay un archivo, incluso podría reducirse a:
a3driver: a3driver.cpp
g++ -o a3driver a3driver.cpp
La razón por la que mostré el primer ejemplo es que muestra el poder de los makefiles. Si necesita compilar otro archivo, puede agregar otra sección. Aquí hay un ejemplo con un secondFile.cpp (que se carga en un encabezado llamado secondFile.h):
a3driver: a3driver.o secondFile.o
g++ -o a3driver a3driver.o secondFile.o
a3driver.o: a3driver.cpp
g++ -c a3driver.cpp
secondFile.o: secondFile.cpp secondFile.h
g++ -c secondFile.cpp
De esta manera, si cambia algo en secondFile.cpp o secondFile.h y recompila, solo recompilará secondFile.cpp (no a3driver.cpp). O alternativamente, si cambia algo en a3driver.cpp, no volverá a compilar secondFile.cpp.
Déjame saber si tienes alguna pregunta al respecto.
También es tradicional incluir una sección llamada "todos" y una sección llamada "limpiar". "todos" generalmente construirá todos los ejecutables, y "limpiar" eliminará "artefactos de compilación" como los archivos .o y los ejecutables:
all: a3driver ;
clean:
# -f so this will succeed even if the files don''t exist
rm -f a3driver a3driver.o
EDIT: no me di cuenta de que estás en Windows. Creo que la única diferencia es cambiar el -o a3driver
a -o a3driver.exe
.
Su archivo make tendrá una o dos reglas de dependencia dependiendo de si compila y enlaza con un solo comando, o con un comando para la compilación y otro para el enlace.
La dependencia es un árbol de reglas que se ven así:
main_target : source1 source2 etc
command to build main_target from sources
source1 : dependents for source1
command to build source1
Debe haber una línea en blanco después de los comandos para un objetivo, y no debe haber una línea en blanco antes de los comandos. El primer objetivo en el archivo make es el objetivo general, otros objetivos se crean solo si el primer objetivo depende de ellos.
Así que tu makefile lucirá algo como esto.
a3a.exe : a3driver.obj
link /out:a3a.exe a3driver.obj
a3driver.obj : a3driver.cpp
cc a3driver.cpp
Usé la respuesta de friedmud. Miré esto por un tiempo, y parece ser una buena manera de comenzar. Esta solución también tiene un método bien definido para agregar marcas de compilación. Respondí nuevamente porque hice cambios para que funcionara en mi entorno, Ubuntu y g ++. Más ejemplos de trabajo son el mejor maestro, a veces.
appname := myapp
CXX := g++
CXXFLAGS := -Wall -g
srcfiles := $(shell find . -maxdepth 1 -name "*.cpp")
objects := $(patsubst %.cpp, %.o, $(srcfiles))
all: $(appname)
$(appname): $(objects)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)
depend: .depend
.depend: $(srcfiles)
rm -f ./.depend
$(CXX) $(CXXFLAGS) -MM $^>>./.depend;
clean:
rm -f $(objects)
dist-clean: clean
rm -f *~ .depend
include .depend
Los makefiles parecen ser muy complejos. Estaba usando uno, pero estaba generando un error relacionado con no enlazar en las bibliotecas g ++. Esta configuración solucionó ese problema.
Vieja pregunta, lo sé, pero para la posteridad. Tenías dos opciones.
Opción 1: makefile más simple = NO MAKEFILE.
Cambie el nombre de "a3driver.cpp" por "a3a.cpp", luego escriba en la línea de comandos:
nmake a3a.exe
Y eso es. Si está utilizando gnu-make, use "make" o "gmake" o lo que sea.
Opción 2: un makefile de 2 líneas.
a3a.exe: a3driver.obj
link /out:a3a.exe a3driver.obj
Voilà.
Yo sugiero:
tool: tool.o file1.o file2.o
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
o
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o
La última sugerencia es ligeramente mejor ya que reutiliza las reglas implícitas de GNU. Sin embargo, para que funcione, un archivo de origen debe tener el mismo nombre que el ejecutable final (es decir, tool.c
y tool
).
Aviso, no es necesario declarar fuentes. Los archivos de objetos intermedios se generan usando una regla implícita. En consecuencia, este Makefile
funciona para C y C ++ (y también para Fortran, etc ...).
También tenga en cuenta, de forma predeterminada, que Makefile use $(CC)
como enlazador. $(CC)
no funciona para vincular objetos C ++. Modificamos LINK.o
solo por eso. Si desea compilar el código C, no tiene que forzar el valor LINK.o
Claro, también puede agregar sus banderas de compilación con la variable CFLAGS
y agregar sus bibliotecas en LDLIBS
. Por ejemplo:
CFLAGS = -Wall
LDLIBS = -lm
Una nota al margen: si tiene que usar bibliotecas externas, sugiero usar pkg-config para configurar correctamente CFLAGS
y LDLIBS
:
CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)
El lector atento nota que este Makefile
no se reconstruye correctamente si se cambia un encabezado. Añade estas líneas para solucionar el problema:
override CPPFLAGS += -MMD
include $(wildcard *.d)
-MMD
permite crear archivos .d que contienen fragmentos de Makefile sobre dependencias de encabezados. La segunda línea solo los usa.
Por supuesto, un Makefile bien escrito también debe incluir reglas clean
y distclean
:
clean:
$(RM) *.o *.d
distclean: clean
$(RM) tool
Tenga en cuenta que $(RM)
es equivalente a rm -f
pero es una buena práctica no llamar a rm
directamente.
all
regla también es apreciada. Para que funcione, debe ser la primera regla de su archivo:
all: tool
También puede agregar la regla de install
:
PREFIX = /usr/local
install:
install -m 755 tool $(DESTDIR)$(PREFIX)/bin
DESTDIR
está vacío por defecto. El usuario puede configurarlo para instalar su programa en un sistema alternativo (obligatorio para el proceso de compilación cruzada). Los mantenedores de paquetes para distribución múltiple también pueden cambiar PREFIX
para instalar su paquete en /usr
.
Una última palabra, no coloque los archivos de origen en los subdirectorios. Si realmente desea hacerlo, mantenga este Makefile
en el directorio raíz y use las rutas completas para identificar sus archivos (es decir, subdir/file.o
).
Así que para resumir, tu Makefile completo debería verse como:
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
PREFIX = /usr/local
override CPPFLAGS += -MMD
include $(wildcard *.d)
all: tool
tool: tool.o file1.o file2.o
clean:
$(RM) *.o *.d
distclean: clean
$(RM) tool
install:
install -m 755 tool $(DESTDIR)$(PREFIX)/bin