make 파일을 사용하여 디렉토리 생성


101

저는 makefiles를 처음 접했고 makefile을 사용하여 디렉토리를 만들고 싶습니다. 내 프로젝트 디렉토리는 다음과 같습니다.

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

모든 개체와 출력을 각 출력 폴더에 넣고 싶습니다. 컴파일 후 다음과 같은 폴더 구조를 만들고 싶습니다.

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

몇 가지 옵션을 시도했지만 성공하지 못했습니다. make 파일을 사용하여 디렉토리를 만들도록 도와주세요. 나는 당신의 고려를 위해 내 Makefile을 게시하고 있습니다.

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

미리 감사드립니다. 저도 실수를했다면 안내 해주세요.

답변:


88

이것은 유닉스와 같은 환경을 가정하여 그렇게 할 것입니다.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

이것은 최상위 디렉토리에서 실행되어야합니다. 그렇지 않으면 $ {OUT_DIR}의 정의가 실행되는 위치에 상대적으로 정확해야합니다. 물론 Peter Miller의 " Recursive Make Thoughmful "문서 의 칙령을 따른다면 어쨌든 최상위 디렉토리에서 make를 실행하게 될 것입니다.

나는 지금이 (RMCH)를 가지고 놀고있다. 테스트 그라운드로 사용중인 소프트웨어 제품군에 약간의 적응이 필요했습니다. 이 제품군에는 15 개의 디렉토리에 분산 된 소스로 구축 된 12 개의 개별 프로그램이 있으며, 일부는 공유됩니다. 그러나 약간의주의를 기울이면 가능합니다. OTOH, 초보자에게는 적합하지 않을 수 있습니다.


주석에서 언급했듯이 '디렉토리'에 대한 작업으로 'mkdir'명령을 나열하는 것은 잘못되었습니다. 주석에서 언급했듯이 '출력 / 디버그를 만드는 방법을 모릅니다'오류를 수정하는 다른 방법이 있습니다. 하나는 '디렉토리'줄에 대한 종속성을 제거하는 것입니다. 이것은 'mkdir -p'가 생성하도록 요청 된 모든 디렉토리가 이미 존재하는 경우 오류를 생성하지 않기 때문에 작동합니다. 다른 하나는 표시된 메커니즘으로, 디렉토리가없는 경우에만 생성을 시도합니다. '수정 된'버전은 내가 어젯밤 염두에 두었던 것입니다.하지만 두 기술 모두 작동합니다 (출력 / 디버그가 존재하지만 디렉토리가 아닌 파일 인 경우 둘 다 문제가 있습니다).


조나단 감사합니다. 내가 시도했을 때 "make : *** No rule to make target output/debug', needed by directory '. Stop." 오류가 발생했습니다 . 그러나 나는 지금 그것에 대해 걱정하지 않을 것입니다. 기본 규칙을 고수합니다. :). 안내해 주셔서 감사합니다. 그리고 나는 최상위 디렉토리에서만 "make"를 실행하고 있습니다.
Jabez

디렉토리 뒤에있는 $ {OUT_DIR}를 삭제하면 작동합니다.
Doc Brown

이를 구현하려면 명령 줄에서 디렉토리를 사용하는 모든 가능한 경우를 포착해야합니다. 또한 파일을 생성하는 빌드 규칙 directories을 항상 다시 빌드하지 않고 종속되도록 만들 수 없습니다 .
mtalexan

@mtalexan이 답변 중 일부의 문제점을 설명하는 몇 가지 의견을 제공했지만 대체 답변을 제안하지 않았습니다. 이 문제에 대한 해결책을 듣고 싶습니다.
사무엘

@Samuel 나는 같은 것을 찾고 있었지만 해결책을 찾지 못했기 때문에 해결책을 제공하지 않고 문제를 지적했습니다. 나는 이상적이지 않은 해결책에서 빠져 나가는 문제를 해결했습니다.
mtalexan

135

제 생각에, 디렉토리는 기술적 또는 디자인 적 측면에서 Makefile의 대상으로 간주되어서는 안됩니다. 당신은 만들어야합니다 파일 및 파일 생성은 새로운 디렉토리를 필요로하는 경우 다음 조용히 관련 파일에 대한 규칙 내에서 디렉토리를 생성합니다.

일반 또는 "패턴 화 된"파일을 대상으로하는 경우 make's 내부 변수 $(@D)를 사용하십시오. 이는 "현재 대상이 상주하는 디렉토리"(대상의 경우 cmp. $@)를 의미합니다. 예를 들면

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

title: $(OBJS)

그런 다음 효과적으로 동일한 작업을 수행합니다. 모두를위한 디렉토리를 생성 $(OBJS)하지만 덜 복잡한 방식으로 수행합니다.

다양한 애플리케이션에서 동일한 정책 (파일은 대상, 디렉토리는 절대로 사용되지 않음)이 사용됩니다. 예를 들어 git개정 제어 시스템은 디렉토리를 저장하지 않습니다.


참고 : 사용하려는 경우 편의 변수를 도입하고 make의 확장 규칙을 활용하는 것이 유용 할 수 있습니다 .

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)

12
디렉토리 요구 사항을 파일에 연결하는 것이 더 나은 옵션이지만 솔루션에는 다시 빌드되는 모든 단일 파일에 대해 make 파일이 mkdir 프로세스를 호출한다는 중요한 단점이 있습니다. 다시 디렉토리. Windows와 같은 비 Linux 빌드 시스템에 적용 할 때 실제로 mkdir 명령에 해당하는 -p가 없기 때문에 차단할 수없는 오류 출력이 발생하고 쉘 호출이 최소한으로 침습적이지 않기 때문에 더 중요한 양의 오버 헤드가 발생합니다.
mtalexan

1
mkdir을 직접 호출하는 대신 디렉토리가 이미있는 경우 디렉토리를 만들지 않도록 다음을 수행했습니다. $ (shell [! -d $ (@ D)] && mkdir -p $ (@ D))
Brady

26

또는 KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Makefile이 구문 분석 된 후 모든 디렉토리가 생성됩니다.


3
디렉토리를 처리하기 위해 각 대상을 명령으로 어지럽 힐 필요가 없기 때문에이 접근 방식을 좋아합니다.
Ken Williams

1
존재하지 않는 경우 디렉터리를 만들어야했습니다. 이 답변은 내 문제에 적합합니다.
Jeff Pal

뿐만 아니라 이는 각 디렉토리의 수정 된 타임 스탬프가 불필요한 빌드 단계를 트리거하는 것을 방지합니다. 이것이 답이되어야합니다
Assimilater

6
이 방법이 더 좋습니다.이 $(info $(shell mkdir -p $(DIRS)))없으면 명령 $(info ...)의 출력이 Makefile에 붙여 넣어 기껏해야 구문 오류가 발생합니다. 이 호출은 a) 오류 (있는 경우)가 사용자에게 표시되고 b) 함수 호출이 확장되지 않도록합니다. mkdir$(info ...)
cmaster - 분석 재개 모니카

10

makein 및 off 자체는 파일 대상과 동일하게 디렉토리 대상을 처리합니다. 따라서 다음과 같은 규칙을 작성하는 것은 쉽습니다.

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

유일한 문제는 디렉토리 타임 스탬프가 내부 파일에 수행되는 작업에 따라 달라진다는 것입니다. 위의 규칙의 경우 다음과 같은 결과가 나타납니다.

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

이것은 확실히 당신이 원하는 것이 아닙니다. 파일을 터치 할 때마다 디렉토리도 터치합니다. 그리고 파일이 디렉토리에 의존하기 때문에 파일이 오래되어 다시 빌드되어야합니다.

그러나 make에게 디렉토리의 타임 스탬프를 무시하도록 지시 하여이 루프를 쉽게 끊을 수 있습니다 . 이는 디렉토리를 주문 전용 전제 조건으로 선언하여 수행됩니다.

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

outDir:
    mkdir -p outDir

이것은 올바르게 다음을 산출합니다.

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

TL; DR :

디렉터리를 만드는 규칙을 작성합니다.

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

그리고 내부의 물건에 대한 대상은 디렉토리 주문에만 의존합니다.

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)

어떤 깨진 OS / FS touch에서 상위 디렉토리의 통계 데이터를 수정 했습니까? 그것은 나에게 의미가 없습니다. dir의 mtime은 포함 된 파일 이름에만 의존합니다. 문제를 재현 할 수 없습니다.
Johan Boulé 2019

@ JohanBoulé 데비안.
cmaster - 분석 재개 모니카

그리고 그러한 깨진 행동에 대해 버그를 채웠습니까?
Johan Boulé 2019

6

수락 된 솔루션을 포함한 모든 솔루션에는 각각의 의견에 명시된 몇 가지 문제가 있습니다. 조나단 - 레플러 @에 의해 허용 대답은 이미 꽤 좋은 그러나 전제 조건이 반드시 (중 순서에 건설되지 않도록 효력을지지 않습니다 make -j예를 들어). 그러나 단순히 directories전제 조건을에서 all로 이동하면 programAFAICT가 실행될 때마다 재 구축 이 유발됩니다. 다음 솔루션에는 해당 문제가 없으며 AFAICS가 의도 한대로 작동합니다.

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)

4

빌드 할 파일을 정의하고 디렉토리를 자동으로 생성 할 수있는 상당히 합리적인 솔루션을 찾았습니다. 먼저 ALL_TARGET_FILESmakefile이 빌드 될 모든 파일의 파일 이름을 보유 하는 변수 를 정의 하십시오. 그런 다음 다음 코드를 사용하십시오.

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

작동 방식은 다음과 같습니다. depend_on_dir파일 이름을 가져 와서 파일이 포함 된 디렉토리에 종속되는 규칙을 생성 하는 함수 를 정의한 다음 필요한 경우 해당 디렉토리를 생성하는 규칙을 정의합니다. 그런 다음 각 파일 이름과 결과 에이 기능을 사용 foreach합니다 .calleval

를 지원하는 GNU make 버전이 필요합니다 eval.이 버전은 3.81 이상이라고 생각합니다.


"메이크 파일이 빌드 할 모든 파일의 파일 이름"을 포함하는 변수를 만드는 것은 일종의 부담스러운 요구 사항입니다. 저는 최상위 대상을 정의하고 그 대상이 의존하는 항목을 정의하는 것을 좋아합니다. 모든 대상 파일의 단순 목록을 갖는 것은 makefile 사양의 계층 적 특성에 위배되며 대상 파일이 런타임 계산에 의존하는 경우 (쉽게) 불가능할 수 있습니다.
Ken Williams

3

당신이 초보자라는 것을 감안할 때, 나는 이것을 아직 시도하지 말라고 말하고 싶습니다. 확실히 가능하지만 Makefile을 불필요하게 복잡하게 만듭니다. make에 더 익숙해 질 때까지 간단한 방법을 고수하십시오.

즉, 소스 디렉토리와 다른 디렉토리에 빌드하는 한 가지 방법은 VPATH입니다 . 나는 패턴 규칙을 선호한다


3

OS 독립성은 나에게 중요하므로 선택 사항 mkdir -p이 아닙니다. eval부모 디렉터리에 대한 전제 조건으로 디렉터리 대상을 만드는 데 사용 하는 일련의 함수를 만들었습니다 . make -j 2종속성이 올바르게 결정되기 때문에 문제없이 작동 하는 이점이 있습니다 .

# 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 > $@

예를 들어, 위를 실행하면 다음이 생성됩니다.

$ 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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.