Crie diretórios usando make file

101

Eu sou um novato em makefiles e quero criar diretórios usando makefile. Meu diretório de projeto é assim

+--Project  
   +--output  
   +--source  
     +Testfile.cpp  
   +Makefile  

Quero colocar todos os objetos e a saída na respectiva pasta de saída. Quero criar uma estrutura de pastas que seja assim após a compilação.

+--Project
   +--output
     +--debug (or release)
       +--objs
         +Testfile.o
       +Testfile (my executable file)
   +--source
     +Testfile.cpp
   +Makefile

Tentei com várias opções, mas não consegui. Por favor me ajude a fazer diretórios usando make file. Estou postando meu Makefile para sua consideração.

#---------------------------------------------------------------------
# Input dirs, names, files
#---------------------------------------------------------------------
OUTPUT_ROOT := output/

TITLE_NAME := TestProj 

ifdef DEBUG 
    TITLE_NAME += _DEBUG
else
ifdef RELEASE
    TITLE_NAME += _RELEASE
endif
endif


# Include all the source files here with the directory tree
SOURCES := \
        source/TestFile.cpp \

#---------------------------------------------------------------------
# configs
#---------------------------------------------------------------------
ifdef DEBUG
OUT_DIR     := $(OUTPUT_ROOT)debug
CC_FLAGS    := -c -Wall
else
ifdef RELEASE
OUT_DIR     := $(OUTPUT_ROOT)release
CC_FLAGS    := -c -Wall
else
$(error no build type defined)
endif
endif

# Put objects in the output directory.
OUT_O_DIR   := $(OUT_DIR)/objs

#---------------------------------------------------------------------
# settings
#---------------------------------------------------------------------
OBJS = $(SOURCES:.cpp=.o)
DIRS = $(subst /,/,$(sort $(dir $(OBJS))))
DIR_TARGET = $(OUT_DIR)

OUTPUT_TARGET = $(OUT_DIR)/$(TITLE_NAME)

CC_FLAGS +=   

LCF_FLAGS := 

LD_FLAGS := 

#---------------------------------------------------------------------
# executables
#---------------------------------------------------------------------
MD := mkdir
RM := rm
CC := g++

#---------------------------------------------------------------------
# rules
#---------------------------------------------------------------------
.PHONY: all clean title 

all: title 

clean:
    $(RM) -rf $(OUT_DIR)

$(DIR_TARGET):
    $(MD) -p $(DIRS)

.cpp.o: 
    @$(CC) -c $< -o $@

$(OBJS): $(OUT_O_DIR)/%.o: %.cpp
    @$(CC) -c $< -o $@

title: $(DIR_TARGET) $(OBJS)

Desde já, obrigado. Por favor, me guie se eu também cometi algum erro.

Jabez
fonte

Respostas:

88

Isso bastaria - assumindo um ambiente semelhante ao Unix.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

${OUT_DIR}:
        ${MKDIR_P} ${OUT_DIR}

Isso teria que ser executado no diretório de nível superior - ou a definição de $ {OUT_DIR} teria que ser correta em relação a onde é executado. Claro, se você seguir os decretos do artigo de Peter Miller " Recursive Make Considered Harmful ", então você executará make no diretório de nível superior de qualquer maneira.

Estou brincando com isso (RMCH) no momento. Precisou de um pouco de adaptação ao pacote de software que estou usando como campo de teste. A suíte tem uma dúzia de programas separados construídos com código-fonte espalhado por 15 diretórios, alguns deles compartilhados. Mas com um pouco de cuidado, isso pode ser feito. OTOH, pode não ser apropriado para um novato.


Conforme observado nos comentários, listar o comando 'mkdir' como ação para 'diretórios' está errado. Como também observado nos comentários, existem outras maneiras de corrigir o erro 'não sei como fazer saída / depuração' resultante. Uma é remover a dependência da linha 'diretórios'. Isso funciona porque 'mkdir -p' não gera erros se todos os diretórios que ele é solicitado a criar já existirem. O outro é o mecanismo mostrado, que só tentará criar o diretório se ele não existir. A versão 'corrigida' é o que eu tinha em mente ontem à noite - mas ambas as técnicas funcionam (e ambas têm problemas se a saída / depuração existir, mas for um arquivo em vez de um diretório).

Jonathan Leffler
fonte
Obrigado Jonathan. Quando tentei, recebi um erro "make: *** Nenhuma regra para criar output/debug', needed by diretórios de destino . Pare." Mas não vou me preocupar com isso agora. seguirá as regras básicas. :). Obrigado por orientar. E estou executando "make" apenas no diretório de nível superior.
Jabez
Apenas apague os diretórios $ {OUT_DIR} atrás :, então ele deve funcionar.
Doc Brown
Implementar isso requer que você capture todos os casos possíveis em que use os diretórios da linha de comando. Além disso, você não pode fazer qualquer acúmulo de geração de arquivo de regras dependente directories, sem causar-lhes para sempre reconstruir ..
mtalexan
@mtalexan Você forneceu alguns comentários explicando o que há de errado com algumas dessas respostas, mas não propôs uma resposta alternativa. Ansioso para ouvir sua solução para este problema.
Samuel,
@Samuel apontei os problemas sem dar uma solução porque estava procurando a mesma coisa e nunca encontrei uma solução. Acabei lidando apenas com a queda de uma solução menos do que ideal.
mtalexan
135

Na minha opinião, os diretórios não devem ser considerados alvos de seu makefile, seja no sentido técnico ou de design. Você deve criar arquivos e, se a criação de um arquivo precisar de um novo diretório, crie silenciosamente o diretório dentro da regra para o arquivo relevante.

Se você está almejando um arquivo normal ou "padronizado", apenas use makea variável interna de $(@D), que significa "o diretório em que o destino atual reside" (cmp. Com $@para o destino). Por exemplo,

$(OUT_O_DIR)/%.o: %.cpp
        @mkdir -p $(@D)
        @$(CC) -c $< -o $@

title: $(OBJS)

Então, você estará efetivamente fazendo o mesmo: criar diretórios para todos $(OBJS), mas de uma maneira menos complicada.

A mesma política (arquivos são destinos, diretórios nunca) é usada em vários aplicativos. Por exemplo, o gitsistema de controle de revisão não armazena diretórios.


Nota: Se você for usá-lo, pode ser útil introduzir uma variável de conveniência e utilizar makeas regras de expansão de.

dir_guard=@mkdir -p $(@D)

$(OUT_O_DIR)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -c $< -o $@

$(OUT_O_DIR_DEBUG)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -g -c $< -o $@

title: $(OBJS)
P Shved
fonte
12
Embora vincular o requisito de diretório a arquivos seja uma opção melhor na minha opinião, sua solução também tem a desvantagem significativa de que o processo mkdir será chamado pelo arquivo make para cada arquivo que for reconstruído, a maioria dos quais não precisará fazer o diretório novamente. Quando adaptado a sistemas de compilação não Linux como o Windows, ele na verdade causa uma saída de erro desbloqueável, já que não há nenhum -p equivalente ao comando mkdir, e mais importante, uma quantidade gigantesca de sobrecarga, já que a invocação do shell não é minimamente invasiva.
mtalexan
1
Em vez de chamar diretamente mkdir, fiz o seguinte para evitar tentar criar o diretório se ele já existir: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady
26

Ou KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Isso criará todos os diretórios depois que o Makefile for analisado.

Nick Zalutskiy
fonte
3
Gosto dessa abordagem porque não preciso desordenar cada destino com comandos para manipular os diretórios.
Ken Williams
1
Eu só precisava criar um diretório se ele não existir. Essa resposta se encaixa perfeitamente no meu problema.
Jeff Pal
Além disso, evita que os timestamps modificados de cada diretório acionem uma etapa de construção desnecessária. Esta deve ser a resposta
Assimilater
6
Isso é melhor: $(info $(shell mkdir -p $(DIRS)))sem o $(info ...), a saída do mkdircomando será colada no Makefile , levando a erros de sintaxe na melhor das hipóteses. A $(info ...)chamada garante que a) os erros (se houver) sejam visíveis para o usuário eb) que a chamada de função se expanda a nada.
cmaster - restabelecer monica
10

makedentro e fora dele, trata os destinos de diretório da mesma forma que os destinos de arquivo. Portanto, é fácil escrever regras como esta:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

O único problema com isso é que a data e hora dos diretórios depende do que é feito com os arquivos dentro deles. Para as regras acima, isso leva ao seguinte resultado:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget

Isso definitivamente não é o que você quer. Sempre que você toca no arquivo, também toca no diretório. E como o arquivo depende do diretório, o arquivo consequentemente parece estar desatualizado, forçando-o a ser reconstruído.

No entanto, você pode facilmente interromper esse loop dizendo ao make para ignorar o carimbo de data / hora do diretório . Isso é feito declarando o diretório como um pré-requisito somente para pedido:

# The pipe symbol tells make that the following prerequisites are order-only
#                           |
#                           v
outDir/someTarget: Makefile | outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Isso produz corretamente:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
make: 'outDir/someTarget' is up to date.

TL; DR:

Escreva uma regra para criar o diretório:

$(OUT_DIR):
    mkdir -p $(OUT_DIR)

E fazer com que os alvos para o material interno dependam do diretório apenas por ordem:

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)
cmaster - reintegrar monica
fonte
Em qual OS / FS quebrado você viu touchmodificar algum dado estatístico no diretório pai? Isso não faz sentido para mim. O mtime de um dir depende apenas dos nomes de arquivo que ele contém. Não consegui reproduzir seu problema.
Johan Boulé
@ JohanBoulé Debian.
cmaster - restabelecer monica
E você preencheu um bug para tal comportamento quebrado?
Johan Boulé
6

Todas as soluções, incluindo a aceita, apresentam alguns problemas, conforme declarado em seus respectivos comentários. A resposta aceita por @ jonathan-leffler já é muito boa, mas não leva em consideração que os pré-requisitos não devem ser necessariamente criados em ordem (durante, make -jpor exemplo). No entanto, simplesmente mover o directoriespré-requisito de allpara programprovoca reconstruções em cada AFAICT executado. A solução a seguir não tem esse problema e AFAICS funciona conforme o planejado.

MKDIR_P := mkdir -p
OUT_DIR := build

.PHONY: directories all clean

all: $(OUT_DIR)/program

directories: $(OUT_DIR)

$(OUT_DIR):
    ${MKDIR_P} $(OUT_DIR)

$(OUT_DIR)/program: | directories
    touch $(OUT_DIR)/program

clean:
    rm -rf $(OUT_DIR)
stefanct
fonte
4

Acabei de apresentar uma solução bastante razoável que permite definir os arquivos a serem construídos e fazer com que os diretórios sejam criados automaticamente. Primeiro, defina uma variável ALL_TARGET_FILESque contém o nome do arquivo de cada arquivo que seu makefile será construído. Em seguida, use o seguinte código:

define depend_on_dir
$(1): | $(dir $(1))

ifndef $(dir $(1))_DIRECTORY_RULE_IS_DEFINED
$(dir $(1)):
    mkdir -p $$@

$(dir $(1))_DIRECTORY_RULE_IS_DEFINED := 1
endif
endef

$(foreach file,$(ALL_TARGET_FILES),$(eval $(call depend_on_dir,$(file))))

É assim que funciona. Eu defino uma função depend_on_dirque pega um nome de arquivo e gera uma regra que faz o arquivo depender do diretório que o contém e, em seguida, defino uma regra para criar esse diretório, se necessário. Então eu uso foreachpara callesta função em cada nome de arquivo e evalo resultado.

Observe que você precisará de uma versão do GNU make que suporte eval, que eu acho que é a versão 3.81 e superior.

Ryan C. Thompson
fonte
Criar uma variável que contém "o nome do arquivo de cada arquivo que seu makefile irá construir" é uma espécie de requisito oneroso - gosto de definir meus destinos de nível superior, as coisas das quais eles dependem, e assim por diante. Ter uma lista simples de todos os arquivos de destino vai contra a natureza hierárquica da especificação makefile e pode não ser (facilmente) possível quando os arquivos de destino dependem de cálculos em tempo de execução.
Ken Williams
3

já que você é um novato, diria que não tente fazer isso ainda. é definitivamente possível, mas complicará desnecessariamente o seu Makefile. siga as formas simples até ficar mais confortável com o make.

Dito isso, uma maneira de construir em um diretório diferente do diretório de origem é VPATH ; eu prefiro regras padrão

apenas alguem
fonte
3

A independência do sistema operacional é crítica para mim, portanto, mkdir -pnão é uma opção. Criei esta série de funções que usam evalpara criar destinos de diretório com o pré-requisito no diretório pai. Isso tem a vantagem de make -j 2funcionar sem problemas, pois as dependências são determinadas corretamente.

# convenience function for getting parent directory, will eventually return ./
#     $(call get_parent_dir,somewhere/on/earth/) -> somewhere/on/
get_parent_dir=$(dir $(patsubst %/,%,$1))

# function to create directory targets.
# All directories have order-only-prerequisites on their parent directories
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types
TARGET_DIRS:=
define make_dirs_recursively
TARGET_DIRS+=$1
$1: | $(if $(subst ./,,$(call get_parent_dir,$1)),$(call get_parent_dir,$1))
    mkdir $1
endef

# function to recursively get all directories 
#     $(call get_all_dirs,things/and/places/) -> things/ things/and/ things/and/places/
#     $(call get_all_dirs,things/and/places) -> things/ things/and/
get_all_dirs=$(if $(subst ./,,$(dir $1)),$(call get_all_dirs,$(call get_parent_dir,$1)) $1)

# function to turn all targets into directories
#     $(call get_all_target_dirs,obj/a.o obj/three/b.o) -> obj/ obj/three/
get_all_target_dirs=$(sort $(foreach target,$1,$(call get_all_dirs,$(dir $(target)))))

# create target dirs
create_dirs=$(foreach dirname,$(call get_all_target_dirs,$1),$(eval $(call make_dirs_recursively,$(dirname))))

TARGETS := w/h/a/t/e/v/e/r/things.dat w/h/a/t/things.dat

all: $(TARGETS)

# this must be placed after your .DEFAULT_GOAL, or you can manually state what it is
# https://www.gnu.org/software/make/manual/html_node/Special-Variables.html
$(call create_dirs,$(TARGETS))

# $(TARGET_DIRS) needs to be an order-only-prerequisite
w/h/a/t/e/v/e/r/things.dat: w/h/a/t/things.dat | $(TARGET_DIRS)
    echo whatever happens > $@

w/h/a/t/things.dat: | $(TARGET_DIRS)
    echo whatever happens > $@

Por exemplo, a execução do acima criará:

$ make
mkdir w/
mkdir w/h/
mkdir w/h/a/
mkdir w/h/a/t/
mkdir w/h/a/t/e/
mkdir w/h/a/t/e/v/
mkdir w/h/a/t/e/v/e/
mkdir w/h/a/t/e/v/e/r/
echo whatever happens > w/h/a/t/things.dat
echo whatever happens > w/h/a/t/e/v/e/r/things.dat
SimplyKnownAsG
fonte