이하의 글을 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