이하의 글을 https://makefiletutorial.com/ 의 글을 번역하고 갈무리하여 작성한 글입니다. 자세한 사항은 원문을 참조하길 바랍니다.
makefile 이 왜 필요한가? Makefile 은 큰 규모의 프로그램에서 어떤 부분이 다시 컴파일되어야 하는지 찾는데 도움을 준다. 단순 프로그램 컴파일을 넘어서 파일의 변경사항에 종속적인 일련의 작업들을 수행할 때에도 이용될 수 있다.
가령 가계부를 작성한다고 했을 때, 특정 월의 총 수입은 그 달의 지출과 수입을 결산한 결과일 것이다. 만일 특정 월의 누락된 수입이 존재하여 해당 내역을 추가하게 된다면 해당 월의 총 수입 또한 같이 변경되어야 한다. 년간 총 수입도 계산해야 한다면 월간 총 수입이 변경되었으므로 그 또한 변경이 이뤄져야 할 것이다.
다양한 용도로 사용이 가능하지만 일반적으로 Makefile 의 핵심은 C/C++ 빌드 시스템으로써 파일들의 변경사항(각 파일들의 수정시간)을 기반으로 하여 필요에 따라 컴파일을 수행하는 것이다.
Makefile 은 rule 의 집합으로 구성된다. rule 은 일반적으로 다음의 형태를 가진다:
targets: preprequisites
command
command
command
...
targets 는 공백으로 나뉘어진 파일의 이름들이다. 보통은 rule 당 하나씩 존재한다.commands 는 target 을 make 하기 위한 일련의 과정이다. 이들은 공백이 아닌 탭(tab) 으로 시작한다.prerequisites 또한 파일의 이름이며 공백으로 구분된다. 위 파일들은 target 을 make 하기 위한 commands 를 실행하기 전에 존재해야 한다. 전술한 가계부 예시로 makefile 을 작성하려 한다. 실제 문법은 아래와 다르지만 이해를 돕기 위해 일부 명령은 의사코드로 작성해보았다:
total: yearly
total = 0
for entry in yearly
total += entry
yearly: monthly
yearly = 0
for entry in monthly
yearly += entry
monthly: daily
monthly = 0
for entry in daily
monthly += entry
최종 수입에 해당하는 target 인 total 을 계산하기 위해선 연간 총 수입(yearly) 을 모두 더해야 한다는 rule 이 필요할 것이다. 연간 총 수입에 해당하는 yearly 역시 월간 총 수입인 monthly 를 prerequisites (전제, 필요조건) 으로 가진다. monthly 또한 daily 가 모여서 완성된 결과일 것이다.
만일 2022/08/24.txt 라는 이름의 일간 가계 내역의 변경(츄파츕스 구매 내역을 누락함)이 발생한다면 그 상위의 2022/08.txt 라는 이름의 월간 가계 내역, 2022.txt 라는 연간 가계 내역, 그리고 최종 결산 내역인 results.txt 또한 변경해야 한다.
make 는 이러한 상황에서 각 파일들의 최종 수정일만을 비교하여 수 많은 파일들 중 2022/08/24.txt 파일과 연관된 2022/08.txt, 2022.txt, 그리고 results.txt 만 수정하게 된다.
Makefile 작성하기 실제 makefile 을 작성하고 또 실행시키기 위해선 make 프로그램을 설치해야 한다. 설치 방법은 운영체제, 그리고 배포판마다 다르지만 보통 본인이 사용하는 배포판의 패키지 매니저에게 make 설치를 요청하면 자동으로 다운로드 받아 설치해준다.
makefile 작성하고 실행하기hello:
echo "hello, world!"
make 프로그램을 설치했다면 위 예제를 복사한 뒤 makefile 이라는 이름의 파일을 생성하여 붙여넣은 뒤, 같은 경로에서 make hello 를 입력하면 이하와 같은 결과를 확인할 수 있다:

위 예제를 조금 뜯어보면 다음과 같다:
makefile 은 오직 하나의 target 에 대한 하나의 rule 만을 가진다.target 인 hello 는 하나의 command 를 가진다.target 은 prerequisites 를 필요로 하지 않는다. hello 라는 이름의 파일만 동일 경로에 존재하지 않는다면 make hello 를 입력할 때마다 반복적으로 echo 명령을 수행한다. 만일 hello 라는 이름의 파일이 존재한다면 이미 target 에 해당하는 파일이 존재하므로 command 를 실행하지 않는다.
makefile 분석하기output: main.o
cc main.o -o output # Runs third
main.o: main.c
cc -c main.c -o main.o # Runs second
main.c:
echo "int main(void) { return 0; }" > main.c
다시 위 내용의 makefile 을 생성한 뒤 해당 파일만을 남기고 make 명령을 입력하면 자동으로 빌드가 진행되고 최종적으로 output 파일이 생성되는 것을 확인할 수 있다.
make 의 동작을 순서대로 설명하자면 이하와 같다:
make 명령으로 어떠한 target 도 지정하지 않았기 때문에 첫 번째 target 인 output 이 default 로 설정된다.output 은 prerequisite 으로 main.o 를 가지지만 main.o 가 없기 때문에 main.o 를 완성하기 위해 main.o 를 target 으로 하는 rule 을 찾는다.main.o 도 main.c 가 없다면 2번과 동일한 과정을 거친다. 그러나 main.c 가 이미 존재한다면 main.o 보다 새로운(최종 수정일) 경우에만 컴파일이 수행된다.main.c 는 prerequisite 을 가지지 않으므로 곧바로 command 를 실행한다. 이는 main.c 를 생성하게 된다.main.c 가 생성되었으므로 main.o 를 target 으로 가지는 rule 을 수행할 수 있게 되었다. main.c 를 컴파일하여 main.o 를 생성하는 command 를 수행한다.output 역시 main.o 가 생성되었으므로 이를 통해 command 를 수행하고 output 이라는 이름의 파일을 생성하게 된다.
만일 main.c 를 삭제하게 된다면 target 생성을 위해 위 세 rule 을 모두 실행하게 된다. 혹은 touch main.o 명령을 입력하여 main.o 의 최종 수정일을 변경하게 되면 output 만 다시 실행된다.
make clean clean 은 종종 다른 target 의 output 을 제거하기 위한 target 으로 사용된다. 그러나 이는 make 에 있어 특별한 키워드는 아니다. 필요에 따라 빌드 부산물 등을 제거하기 위해 make clean 을 이용할 수 있다:
some_file:
touch some_file
clean:
rm -f some_file
Variables Variable 은 오직 문자열(string)으로만 존재한다. 변수에 값을 대입하기 위해 := 혹은 = 을 사용할 수 있다. 이들의 차이점은 이후에 설명한다.
files := file1 file2
some_file: $(files)
echo "Look at this variable: " $(files)
touch some_file
file1:
touch file1
file2:
touch file2
clean:
rm -f file1 file2 some_file
makefile 내에서 작은 따옴표와 큰 따옴표는 어떠한 의미도 가지지 않는다. 따라서 file1 과 file2 도 변수에 대입되는 단순한 문자열에 불과하다. 따라서 이하의 예제는 동일한 결과를 반복 출력한다:
a := one two
b := 'one two'
all:
printf '$a'
printf $b
변수의 참조를 위해 ${} 와 $() 두 가지 형태 모두 사용이 가능하다:
x := dude
all:
echo $(x)
echo ${x}
# Bad practice, but works
echo $x