Makefile, 헤더 종속성


97

규칙이있는 메이크 파일이 있다고 가정 해 보겠습니다.

%.o: %.c
 gcc -Wall -Iinclude ...

헤더 파일이 변경 될 때마다 * .o가 다시 작성되기를 원합니다. 종속성 목록을 작성하는 대신 헤더 파일이 /include변경 될 때마다 dir의 모든 개체를 다시 빌드해야합니다.

나는 이것을 수용하기 위해 규칙을 변경하는 좋은 방법을 생각할 수 없다. 나는 제안에 개방적이다. 헤더 목록을 하드 코딩 할 필요가없는 경우 보너스 포인트


아래에 내 대답을 적어 관련 목록을 살펴본 결과 stackoverflow.com/questions/297514/… 중복 된 것으로 보입니다. Chris Dodd의 대답은 다른 명명 규칙을 사용하지만 내 것과 동일합니다.
dmckee --- ex-moderator kitten

답변:


116

GNU 컴파일러를 사용하는 경우 컴파일러가 종속성 목록을 조합 할 수 있습니다. Makefile 조각 :

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ -MF  ./.depend;

include .depend

또는

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ > ./.depend;

include .depend

여기서는 SRCS소스 파일의 전체 목록을 가리키는 변수입니다.

도구도 makedepend있지만 나는 그것을만큼 좋아하지 않았습니다.gcc -MM


2
이 트릭이 마음에 들지만 depend소스 파일이 변경된 경우에만 어떻게 실행할 수 있습니까? 매번 실행되는 것 같습니다 ...
chase

2
@chase : 음, 객체 파일에 대한 종속성을 잘못 만들었습니다. 분명히 소스에 있어야하고 두 대상에 대한 종속성 순서도 잘못되었습니다. 그것이 내가 기억에서 타이핑하는 것에 대한 것입니다. 지금 시도하십시오.
dmckee --- 전 중재자 새끼 고양이

4
다른 디렉토리에 있음을 보여주기 위해 모든 파일 앞에 접두사를 추가하는 방법 build/file.o입니까?
RiaD

SRCS를 OBJECTS로 변경했습니다. 여기서 OBJECTS는 내 * .o 파일 목록입니다. 이것은 의존성이 매번 실행되는 것을 막는 것처럼 보였고 헤더 파일의 변경 사항 만 포착했습니다. 이것은 이전 댓글과 반대되는 것 같습니다.
BigBrownBear00

2
세미콜론이 필요한 이유는 무엇입니까? 그것없이 또는 -MF ./.depend가 마지막 인수가 아닌 상태에서 시도하면 $ (SRCS)에 마지막 파일의 종속성 만 저장됩니다.
humodz

72

대부분의 답변은 놀랍도록 복잡하거나 오류가 있습니다. 그러나 간단하고 강력한 예제는 [ codereview ] 다른 곳에 게시되었습니다 . 확실히 gnu 전처리 기가 제공하는 옵션은 약간 혼란 스럽습니다. 그러나 빌드 대상에서 모든 디렉토리를 제거하는 -MM것은 문서화되어 있으며 버그 [ gpp ]가 아닙니다 .

기본적으로 CPP는 기본 입력 파일의 이름을 사용하고 모든 디렉토리 구성 요소 및 '.c'와 같은 파일 접미사를 삭제 하고 플랫폼의 일반적인 개체 접미사를 추가합니다.

(다소 더 새로운) -MMD옵션은 아마도 당신이 원하는 것입니다. 완전성을 위해 여러 src 디렉토리를 지원하고 일부 주석으로 디렉토리를 빌드하는 메이크 파일의 예입니다. 빌드 디렉토리가없는 간단한 버전은 [ codereview ]를 참조하십시오 .

CXX = clang++
CXX_FLAGS = -Wfatal-errors -Wall -Wextra -Wpedantic -Wconversion -Wshadow

# Final binary
BIN = mybin
# Put all auto generated stuff to this build dir.
BUILD_DIR = ./build

# List of all .cpp source files.
CPP = main.cpp $(wildcard dir1/*.cpp) $(wildcard dir2/*.cpp)

# All .o files go to build dir.
OBJ = $(CPP:%.cpp=$(BUILD_DIR)/%.o)
# Gcc/Clang will create these .d files containing dependencies.
DEP = $(OBJ:%.o=%.d)

# Default target named after the binary.
$(BIN) : $(BUILD_DIR)/$(BIN)

# Actual target of the binary - depends on all .o files.
$(BUILD_DIR)/$(BIN) : $(OBJ)
    # Create build directories - same structure as sources.
    mkdir -p $(@D)
    # Just link all the object files.
    $(CXX) $(CXX_FLAGS) $^ -o $@

# Include all .d files
-include $(DEP)

# Build target for every single object file.
# The potential dependency on header files is covered
# by calling `-include $(DEP)`.
$(BUILD_DIR)/%.o : %.cpp
    mkdir -p $(@D)
    # The -MMD flags additionaly creates a .d file with
    # the same name as the .o file.
    $(CXX) $(CXX_FLAGS) -MMD -c $< -o $@

.PHONY : clean
clean :
    # This should remove all generated files.
    -rm $(BUILD_DIR)/$(BIN) $(OBJ) $(DEP)

이 방법은 단일 대상에 대해 여러 종속성 라인이있는 경우 종속성이 단순히 결합되기 때문에 작동합니다. 예 :

a.o: a.h
a.o: a.c
    ./cmd

다음과 같습니다.

a.o: a.c a.h
    ./cmd

언급했듯이 : Makefile 단일 대상에 대한 여러 종속성 줄?


1
이 솔루션이 마음에 듭니다. make Depend 명령을 입력하고 싶지 않습니다. 유용합니다!
로버트

1
OBJ 변수 값의 맞춤법 오류가 있습니다 다음 CPP읽어야합니다CPPS
ctrucza

1
이것이 제가 선호하는 대답입니다. 당신을 위해 +1. 이것은이 페이지에서 의미가 있고 재 컴파일이 필요한 모든 상황을 다룹니다 (불필요한 컴파일을 피하지만 충분 함).
Joost

1
기본적으로 hpp와 cpp가 모두 동일한 디렉토리에 있더라도 헤더를 찾지 못했습니다.
villasv

1
에 소스 파일 ( a.cpp, b.cpp)이있는 ./src/경우 대체가 이루어지지 $(OBJ)=./build/src/a.o ./build/src/b.o않습니까?
galois

26

내가 여기에 게시했듯이 gcc는 종속성을 생성하고 동시에 컴파일 할 수 있습니다.

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MF $(patsubst %.o,%.d,$@) -o $@ $<

'-MF'매개 변수는 종속성을 저장할 파일을 지정합니다.

'-include'시작 부분의 대시는 Make가 .d 파일이 존재하지 않을 때 (예 : 첫 번째 컴파일시) 계속하도록 지시합니다.

-o 옵션과 관련하여 gcc에 버그가있는 것 같습니다. 객체 파일 이름을 obj / _file__c.o로 설정하면 생성 된 파일 .d에는 여전히 obj / _file__c.o가 아닌 .o 파일 이 포함 됩니다 .


4
이것을 시도하면 모든 .o 파일이 빈 파일로 생성됩니다. 빌드 하위 폴더에 내 개체가 있으므로 $ OBJECTS에는 build / main.o build / smbus.o build / etc ...가 포함되어 있으며 명백한 버그로 설명한대로 .d 파일을 확실히 생성하지만 확실히 .o 파일을 전혀 빌드하지 않지만 -MM 및 -MF를 제거하면 빌드됩니다.
bobpaul 2012

1
-MT를 사용하면 각 종속성 목록의 대상을 업데이트하는 답변의 마지막 줄에있는 메모가 해결됩니다.
Godric Seer 2013

3
@bobpaul man gcc은 "사전 처리 후 중지"하는을 -MM암시 하기 때문 -E입니다. 당신이 필요로 -MMD하는 대신 : stackoverflow.com/a/30142139/895245을
치로 틸리郝海东冠状病六四事件法轮功

23

다음과 같은 것은 어떻습니까?

includes = $(wildcard include/*.h)

%.o: %.c ${includes}
    gcc -Wall -Iinclude ...

와일드 카드를 직접 사용할 수도 있지만 여러 곳에서 필요한 경우가 많습니다.

이것은 모든 개체 파일이 모든 헤더 파일에 의존한다고 가정하기 때문에 소규모 프로젝트에서만 잘 작동합니다.


감사합니다. 저는 이것을 필요보다 훨씬 더 복잡하게 만들었습니다
Mike

15
이것은 작동하지만 문제는 모든 개체 파일이 다시 컴파일되고 작은 변경이 이루어질 때마다 즉, 100 개의 소스 / 헤더 파일이 있고 하나만 약간만 변경하면 100 개 모두 다시 컴파일된다는 것입니다. .
Nicholas Hamilton

1
헤더 파일이 변경 될 때마다 모든 파일을 다시 빌드하기 때문에 이것이 매우 비효율적 인 방법이라고 답변을 업데이트해야합니다. 다른 답변이 훨씬 낫습니다.
xaxxon

2
이것은 매우 나쁜 해결책입니다. 물론 소규모 프로젝트에서 작동하지만 프로덕션 규모의 팀과 빌드에 대해 이것은 끔찍한 컴파일 시간으로 이어지고 make clean all매번 실행되는 것과 동일 합니다.
줄리앙 Guertault

내 테스트에서 이것은 전혀 작동하지 않습니다. gcc라인은 전혀 실행되지 않지만, 내장 규칙 ( %o: %.c규칙) 대신 실행됩니다.
Penghe Geng

4

위의 Martin의 솔루션은 훌륭하게 작동하지만 하위 디렉토리에있는 .o 파일을 처리하지 않습니다. Godric은 -MT 플래그가 해당 문제를 처리하지만 동시에 .o 파일이 올바르게 작성되는 것을 방지한다고 지적합니다. 다음은 이러한 문제를 모두 처리합니다.

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MT $@ -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) -o $@ $<

3

이것은 작업을 잘 수행하고 지정된 하위 디렉토리도 처리합니다.

    $(CC) $(CFLAGS) -MD -o $@ $<

gcc 4.8.3으로 테스트했습니다.


3

다음은 두 줄짜리입니다.

CPPFLAGS = -MMD
-include $(OBJS:.c=.d)

에 모든 개체 파일 목록이있는 한 기본 make 레시피에서 작동합니다 OBJS.


1

저는이 솔루션을 선호합니다. Michael Williamson이 받아 들인 답변보다 소스 + 인라인 파일, 소스 + 헤더, 마지막으로 소스 만 변경됩니다. 여기서 장점은 몇 가지만 변경하면 전체 라이브러리가 다시 컴파일되지 않는다는 것입니다. 두 개의 파일이있는 프로젝트에 대한 큰 고려 사항은 아닙니다. 소스가 10 개 또는 100 개이면 차이를 알 수 있습니다.

COMMAND= gcc -Wall -Iinclude ...

%.o: %.cpp %.inl
    $(COMMAND)

%.o: %.cpp %.hpp
    $(COMMAND)

%.o: %.cpp
    $(COMMAND)

2
이는 헤더 파일에 해당 구현 파일 이외의 다른 cpp 파일을 재 컴파일해야하는 것이없는 경우에만 작동합니다.
matec

0

다음은 나를 위해 작동합니다.

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<

0

* .d 파일을 다른 폴더에 출력 할 수있는 Sophie의 답변 의 약간 수정 된 버전입니다 (종속 파일을 생성하는 흥미로운 부분 만 붙여 넣을 것입니다).

$(OBJDIR)/%.o: %.cpp
# Generate dependency file
    mkdir -p $(@D:$(OBJDIR)%=$(DEPDIR)%)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM -MT $@ $< -MF $(@:$(OBJDIR)/%.o=$(DEPDIR)/%.d)
# Generate object file
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@

매개 변수는

-MT $@

생성 된 * .d 파일의 대상 (즉, 개체 파일 이름)에 파일 이름뿐만 아니라 * .o 파일에 대한 전체 경로가 포함되도록하는 데 사용됩니다.

-MMD를 사용하는 경우이 매개 변수가 필요하지 않은 이유를 모르겠어요 조합 (소피에로 -c와 버전 ). 이 조합에서는 * .o 파일의 전체 경로를 * .d 파일에 쓰는 것처럼 보입니다. 이 조합이 없으면 -MMD는 디렉토리 구성 요소없이 순수 파일 이름 만 * .d 파일에 씁니다. 아마도 누군가 -MMD가 -c와 결합 될 때 전체 경로를 쓰는 이유를 알고있을 것입니다. g ++ 매뉴얼 페이지에서 힌트를 찾지 못했습니다.

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