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]



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.


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:


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.


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)


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)))

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)


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!


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


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


-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 .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.