Makefiles com arquivos de origem em diferentes diretórios

135

Eu tenho um projeto onde a estrutura de diretórios é assim:

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

Como devo escrever um makefile que estaria na parte / src (ou onde quer que realmente) pudesse complementar / vincular os arquivos de origem c / c ++ na parte? / Src?

Posso fazer algo como -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Se isso funcionar, existe uma maneira mais fácil de fazer isso. Já vi projetos em que há um makefile em cada parte correspondente? pastas [neste post eu usei o ponto de interrogação como na sintaxe do bash]

devin
fonte
1
No manual original do gnu ( gnu.org/software/make/manual/html_node/Phony-Targets.html ), em Phony Targets, há uma amostra recursive invocation, que pode ser bastante elegante.
22414 Frank Nocke
Qual ferramenta você usou para criar esse gráfico de texto?
Khalid Hussain

Respostas:

114

A forma tradicional é ter um Makefileem cada um dos subdiretórios ( part1, part2, etc.) que lhe permite construí-los de forma independente. Além disso, tenha um Makefileno diretório raiz do projeto que cria tudo. A "raiz" Makefileseria algo como o seguinte:

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

Como cada linha em um destino make é executada em seu próprio shell, não há necessidade de se preocupar em fazer o backup da árvore de diretórios ou de outros diretórios.

Sugiro dar uma olhada na seção 5.7 do GNU make manual ; é muito útil.

Akhan
fonte
26
Isso deve ser +$(MAKE) -C part1etc. Isso permite que o controle de tarefas do Make trabalhe nos subdiretórios.
ephemient
26
Essa é uma abordagem clássica e amplamente usada, mas é sub-ideal de várias maneiras que pioram à medida que o projeto cresce. Dave Hinton tem o ponteiro a seguir.
dmckee --- gatinho ex-moderador
89

Se você tiver código em um subdiretório dependente do código em outro subdiretório, provavelmente será melhor ter um único makefile no nível superior.

Consulte Consideração sobre a recursividade considerada prejudicial para obter a justificativa completa, mas basicamente você quer ter todas as informações necessárias para decidir se um arquivo precisa ou não ser reconstruído, e isso não acontecerá se você contar apenas um terço dos seu projecto.

O link acima parece não estar acessível. O mesmo documento é acessível aqui:

dave4420
fonte
3
Obrigado, não estava ciente disso. É muito útil conhecer o "caminho certo" de fazer as coisas, em vez de maneiras que "simplesmente funcionam" ou são aceitas como padrão.
tjklemz
3
A marca recursiva era considerada prejudicial, quando realmente não funcionava bem. Atualmente, não é considerado prejudicial, é como as ferramentas automáticas / automáticas gerenciam projetos maiores.
Edwin Buck #
36

A opção VPATH pode ser útil, o que indica para que diretórios procurar o código-fonte. Você ainda precisaria de uma opção -I para cada caminho de inclusão. Um exemplo:

CXXFLAGS=-Ipart1/inc -Ipart2/inc -Ipart3/inc
VPATH=part1/src:part2/src:part3/src

OutputExecutable: part1api.o part2api.o part3api.o

Isso localizará automaticamente os arquivos partXapi.cpp correspondentes em qualquer um dos diretórios especificados pelo VPATH e os compilará. No entanto, isso é mais útil quando o diretório src é dividido em subdiretórios. Pelo que você descreve, como outros já disseram, provavelmente você está melhor com um makefile para cada parte, especialmente se cada parte puder ficar sozinha.

CodeGoat
fonte
2
Não acredito que esta resposta simples e perfeita não tenha mais votos. É um +1 de mim.
Nicholas Hamilton
2
Eu tinha alguns arquivos de origem comuns em um diretório superior para vários projetos diferentes em subpastas, VPATH=..funcionou para mim!
EkriirkE
23

Você pode adicionar regras ao Makefile raiz para compilar os arquivos cpp necessários em outros diretórios. O exemplo do Makefile abaixo deve ser um bom começo para você chegar onde deseja estar.

CC = g ++
TARGET = cppTest
OTHERDIR = .. / .. / someotherpath / in / project / src

SOURCE = cppTest.cpp
FONTE = $ (OTHERDIR) /file.cpp

## Definição de fontes finais
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
INCLUIR = -I. $ (OTHERDIR) /../ inc
## end inclui mais

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

## Corrija o destino da dependência como ../.dep em relação ao diretório src
DEPENDS = $ (junte $ (adduffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Regra padrão executada
tudo: $ (TARGET)
        @verdade

## Regra limpa
limpar \ limpo:
        @ -rm -f $ (TARGET) $ (OBJ) $ (DEPENDE)


## Regra para definir o destino real
$ (TARGET): $ (OBJ)
        @echo "============="
        @echo "Vinculando o destino $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Link finalizado -

Regra de compilação genérica
% .o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilando $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Regras para arquivos de objeto de arquivos cpp
## O arquivo de objeto para cada arquivo é colocado no diretório obj
## um nível acima do diretório de origem real.
../obj/%.o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilando $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Regra para "outro diretório" Você precisará de um por dir "outro"
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Compilando $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Faça regras de dependência
../.dep/%.d:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Criando arquivo de dependências para $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Regra de dependência para o diretório "other"
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Criando arquivo de dependências para $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## Inclua os arquivos de dependência
-include $ (DEPENDS)

RC.
fonte
7
Sei que isso já é bastante antigo, mas SOURCE e INCLUDE são substituídas por outra tarefa uma linha depois?
Skelliam # 16/15
@ skelliam sim, ele faz.
Nbroyan
1
Essa abordagem viola o princípio DRY. É uma má idéia duplicar o código para cada "outro diretório"
Jesus H
20

Se as fontes estão espalhadas em muitas pastas e faz sentido ter Makefiles individuais, como sugerido anteriormente, a composição recursiva é uma boa abordagem, mas para projetos menores, acho mais fácil listar todos os arquivos de origem no Makefile com seu caminho relativo para o Makefile assim:

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

Posso então definir VPATHdesta maneira:

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

Então eu construo os objetos:

COMMON_OBJS := $(patsubst %.cpp, $(ObjDir)/%$(ARCH)$(DEBUG).o, $(notdir $(COMMON_SRC)))

Agora a regra é simples:

# the "common" object files
$(ObjDir)/%$(ARCH)$(DEBUG).o : %.cpp Makefile
    @echo creating $@ ...
    $(CXX) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<

E construir a saída é ainda mais fácil:

# This will make the cbsdk shared library
$(BinDir)/$(OUTPUTBIN): $(COMMON_OBJS)
    @echo building output ...
    $(CXX) -o $(BinDir)/$(OUTPUTBIN) $(COMMON_OBJS) $(LFLAGS)

Pode-se até tornar a VPATHgeração automatizada por:

VPATH := $(dir $(COMMON_SRC))

Ou usando o fato de sortremover duplicatas (embora não deva importar):

VPATH := $(sort  $(dir $(COMMON_SRC)))
traçado
fonte
isso funciona muito bem para projetos menores, nos quais você apenas deseja adicionar algumas bibliotecas personalizadas e colocá-las em uma pasta separada. Muito obrigado
zitroneneis
6

Eu acho que é melhor ressaltar que usar Make (recursivo ou não) é algo que geralmente você deseja evitar, porque, comparado às ferramentas atuais, é difícil aprender, manter e dimensionar.

É uma ferramenta maravilhosa, mas seu uso direto deve ser considerado obsoleto em mais de 2010.

A menos, claro, que você esteja trabalhando em um ambiente especial, ou seja, com um projeto herdado, etc.

Use um IDE, CMake ou, se você for mais experiente, o Autotools .

(editado devido a votos negativos, digite Honza por apontar)

IlDan
fonte
1
Seria bom ter os votos negativos explicados. Eu também costumava fazer meus Makefiles.
IlDan
2
Estou votando pela sugestão "não faça isso" - o KDevelop tem uma interface gráfica para configurar o Make e outros sistemas de compilação, e enquanto eu escrevo Makefiles, o KDevelop é o que eu dou aos meus colegas de trabalho - mas eu não pense que o link Autotools ajuda. Para entender o Autotools, você precisa entender m4, libtool, automake, autoconf, shell, make e basicamente toda a pilha.
ephemient
7
Os votos negativos são provavelmente porque você está respondendo sua própria pergunta. A questão não era se deveria ou não usar Makefiles. Era sobre como escrevê-los.
Honza
8
seria bom se você pudesse, em poucas frases, explicar como as ferramentas modernas tornam a vida mais fácil do que escrever um Makefile. Eles fornecem uma abstração de nível superior? ou o que?
Abhishek Anand
1
xterm, vi, bash, makefile == governam o mundo, cmake é para pessoas que não podem hackear código.
precisa saber é o seguinte
2

A publicação de RC foi SUPER útil. Eu nunca pensei em usar a função $ (dir $ @), mas ela fez exatamente o que eu precisava.

No parentDir, existem vários diretórios com arquivos de origem: dirA, dirB, dirC. Vários arquivos dependem dos arquivos de objeto em outros diretórios, então eu queria poder criar um arquivo de dentro de um diretório e fazê-lo fazer essa dependência chamando o makefile associado a essa dependência.

Essencialmente, criei um Makefile no parentDir que tinha (entre muitas outras coisas) uma regra genérica semelhante à do RC:

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

Cada subdiretório incluía esse makefile de nível superior para herdar esta regra genérica. No Makefile de cada subdiretório, escrevi uma regra personalizada para cada arquivo, para poder acompanhar tudo o que cada arquivo individual dependia.

Sempre que precisei criar um arquivo, usei (essencialmente) essa regra para fazer recursivamente qualquer / todas as dependências. Perfeito!

OBSERVAÇÃO: existe um utilitário chamado "makepp" que parece executar essa tarefa ainda mais intuitivamente, mas por uma questão de portabilidade e não dependendo de outra ferramenta, eu escolhi fazê-lo dessa maneira.

Espero que isto ajude!

jvriesem
fonte
2

Uso Recursivo da Marca

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

Isso permite makedividir em tarefas e usar vários núcleos

EhevuTov
fonte
2
Como isso é melhor do que fazer algo assim make -j4?
Devin
1
@ Kevin, a meu ver, não é melhor , apenas permite make usar o controle do trabalho. Ao executar um processo separado de make, ele não está sujeito ao controle do trabalho.
Michael Pankov 01/03/2013
1

Eu estava procurando por algo assim e, depois de algumas tentativas e quedas, crio meu próprio makefile, sei que essa não é a "maneira idiomática", mas é um começo para entender o make e isso funciona para mim, talvez você possa tentar em seu projeto.

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!

Eu sei que é simples e para algumas pessoas minhas bandeiras estão erradas, mas como eu disse, este é o meu primeiro Makefile a compilar meu projeto em vários diretórios e vincular todos juntos para criar minha lixeira.

Estou aceitando sugestões: D

Matheus Vinícius de Andrade
fonte
-1

Sugiro usar autotools:

//## Coloque os arquivos de objeto gerados (.o) no mesmo diretório que os arquivos de origem, para evitar colisões quando a criação não recursiva for usada.

AUTOMAKE_OPTIONS = subdir-objects

apenas incluí-lo Makefile.amcom outras coisas bastante simples.

Aqui está o tutorial .

user2763812
fonte