Como posso criar um Makefile para projetos C com subdiretórios SRC, OBJ e BIN?

94

Há alguns meses, criei o seguinte genérico Makefilepara tarefas 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!"

Basicamente, isso compilará todos os arquivos .ce .hpara gerar .oarquivos e o executável, projectnametodos na mesma pasta.

Agora, eu gostaria de forçar um pouco isso. Como posso escrever um Makefile para compilar um projeto C com a seguinte estrutura de diretório?

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

Em outras palavras, gostaria de ter um Makefile que compila fontes C de e, ./src/em ./obj/seguida, vincula tudo para criar o executável em ./bin/.

Tentei ler diferentes Makefiles, mas simplesmente não consigo fazê-los funcionar para a estrutura do projeto acima; em vez disso, o projeto falha em compilar com todos os tipos de erros. Claro, eu poderia usar o IDE completo (Monodevelop, Anjuta, etc.), mas honestamente prefiro ficar com o gEdit e o bom e velho terminal.

Existe um guru que pode me dar uma solução de trabalho ou informações claras sobre como isso pode ser feito? Obrigado!

** ATUALIZAÇÃO (v4) **

A solução final :

# ------------------------------------------------
# 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
fonte
Qual é a questão específica aqui?
Oliver Charlesworth
Não tenho certeza se entendi o que você quer fazer.
Tom
Atualizado o Makefile. Estou chegando perto, mas tenho problemas com as variáveis ​​automáticas, pelo que parece
Yanick Rochon
Acabei de encontrar uma solução. Se alguém quiser encontrar algo melhor, o Makefile ainda pode ser melhorado.
Yanick Rochon
2
@YanickRochon Não tive a intenção de criticar seu inglês. Mas para os alvos PHONY fazerem sentido, você definitivamente não pode escrever BANANA;) gnu.org/software/make/manual/html_node/Phony-Targets.html
joni

Respostas:

34

Primeiro, sua $(OBJECTS)regra é problemática, porque:

  1. é meio indiscriminado, tornando todas as fontes pré-requisitos de cada objeto,
  2. muitas vezes usa a fonte errada (como você descobriu com file1.oe file2.o)
  3. ele tenta construir executáveis ​​em vez de parar nos objetos, e
  4. o nome do destino ( foo.o) não é o que a regra realmente produzirá ( obj/foo.o).

Eu sugiro o seguinte:

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

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

A $(TARGET)regra tem o mesmo problema de que o nome do destino não descreve realmente o que a regra constrói. Por esse motivo, se você digitar makevárias vezes, o Make reconstruirá o destino a cada vez, embora não haja motivo para isso. Uma pequena mudança corrige isso:

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

Depois que tudo estiver em ordem, você pode considerar um tratamento de dependência mais sofisticado; se você modificar um dos arquivos de cabeçalho, este makefile não saberá quais objetos / executáveis ​​devem ser reconstruídos. Mas isso pode esperar outro dia.

EDIT:
Desculpe, omiti parte da $(OBJECTS)regra acima; Eu corrigi isso. (Eu gostaria de poder usar "strike" dentro de uma amostra de código.)

Beta
fonte
com suas mudanças sugeridas, eu recebo: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: Você tem várias mainfunções? Talvez um dentro file1.ce um dentro main.c? Nesse caso, você não poderá vincular esses objetos; pode haver apenas um mainem um executável.
Beta de
Não, eu não. Tudo funciona bem com a última versão que postei na pergunta. Quando eu mudo meu Makefile para o que você sugere (e eu entendo os benefícios do que você está dizendo), é o que eu ganho. Acabei de colar, file1.cmas dá a mesma mensagem para todos os arquivos do projeto. E main.cé o único com função principal ... e main.cimporta file1.he file2.h(não há relação entre file1.ce file2.c), mas duvido que o problema venha daí.
Yanick Rochon
@Yanick Rochon: Cometi um erro ao colar a primeira linha da minha $(OBJECTS)regra; Eu editei. Com a linha incorreta, recebi um erro, mas não o que você obteve ...
Beta de
6

Você pode adicionar o -Isinalizador aos sinalizadores do compilador (CFLAGS) para indicar onde o compilador deve procurar os arquivos de origem e o sinalizador -o para indicar onde o binário deve ser deixado:

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

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

Para soltar os arquivos de objeto no objdiretório, use a -oopção ao compilar. Além disso, observe as variáveis ​​automáticas$@ e .$<

Por exemplo, considere este simples Makefile

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

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

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

Atualizar>

Olhando para o seu makefile, percebo que você está usando a -oflag. Boa. Continue usando, mas adicione uma variável de diretório de destino para indicar onde o arquivo de saída deve ser gravado.

Tom
fonte
Você poderia ser mais específico? Você quer dizer adicionar -l ...ao CFLAGSe ... já existe o -oargumento do vinculador ( LINKER)
Yanick Rochon
Sim, os CFLAGS, e sim, continue usando -o, basta adicionar a variável TARGETPATH.
Tom
Obrigado, eu fiz as modificações, mas parece que ainda estou faltando algumas coisas (veja a atualização sobre a questão)
Yanick Rochon
apenas make, de onde fica o Makefile
Yanick Rochon
Você não consegue ler o comando sendo executado? por exemplo gcc -c yadayada. Tenho certeza de que há uma variável que não contém o que você espera
Tom
-1

Eu parei de escrever makefiles estes dias, se sua intenção é aprender, vá em frente, senão você tem um bom gerador de makefile que vem com o eclipse CDT. Se você quiser alguma capacidade de manutenção / suporte a vários projetos em sua árvore de construção, dê uma olhada no seguinte -

https://github.com/dmoulding/boilermake Eu achei isso muito bom ..!

Kamath
fonte
3
Baseado em opinião. Não responde à pergunta de OP. Presume o ambiente Eclipse.
Nathaniel Johnson