makefile Tutorials(1)

예전 공군 작전소프트웨어 개발팀에서 근무할 때 규모가 큰 프로그램을 gnu make를 사용했던 기억이 있습니다. 당시 억소리가 저절로 나는 금액을 지불하고 이스라엘 방산업체 ELTA사에서 유지보수 교육을 와 주었는데, 마지막으로 gnu make로 컴파일을 했었습니다. 대부분 군 부대에서는 윈도우를 사용하는지라 gcc를 만질 일이 없었습니다. 그래서 ‘아 저거 좀 공부해봐야겠다’라고 생각했던게 벌써 몇 년이 지났네요.

마침 이번 학기 수강중인 데이터구조 수업에서 make를 사용합니다. 이번 기회에 한번 정리해볼까 합니다.

hello make!

이제까지는 리눅스에서 간단한 프로그램을 작성할 때 다음과 같이 명령어 하나면 되었습니다. 리눅스에서 C++을 사용할 일은 간단한 알고리즘 문제 풀이 정도에서나 사용했었으니 아래와 같이 한 줄이면 정상적으로 컴파일이 되었었습니다.

$ g++ -o hello_world *.cpp *.h -std=c++17

어차피 소스 파일이야 일반적으로 폴더에 다 몰아두고 코드를 작성하니 저 명령어 하나면 충분했습니다.

그러나 저런건 간단한 컴파일일때나 가능한 이야기이지, 예를 들어 ELTA사에서 작성한 프로그램 같은 경우는 정말 규모가 어마어마 합니다. 단 한 줄만 고쳐도 처음부터 끝까지 모든 파일을 컴파일해야 하니 시간도 오래 걸릴 뿐더러 효율도 좋지 않습니다. 이때 변경된 곳만 따로 컴파일해서 링킹 해주는 툴이 make입니다.

컴파일 과정

컴파일 과정은 아래와 같습니다.

figure

출처: https://www.oreilly.com/library/view/programming-embedded-systems/0596009836/ch04.html

컴파일 과정은 간단하게 짚고 넘어갑시다.

  • hello_world.cpp, hello_world.h
  • hello_world2.cpp, hello_world2.h
  • main.cpp

의 파일이 있다고 합시다. 이때 각 소스를 재료로 해서 hello_world.o, hello_world2.o, main.o Object 파일을 만듭니다. 이후 이들을 한 데 묶는 링크 과정을 통해서 실행 파일 app.out을 생성합니다. 이때 각 함수에서 의존성이 존재하게 됩니다.

단계적으로 빌드

노가다 식으로 빌드해봅시다.

$ g++ -c -o main.o main.cpp
$ g++ -c -o hello_world.o hello_world.cpp
$ g++ -c -o hello_world2.o hello_world2.cpp

우선 위의 명령어 중 -c 옵션은 링크를 하지 않고 컴파일만 하겠다는 의미입니다. 이 옵션을 생략하면 main 함수를 찾을 수 없다는 오류가 출력됩니다.

그리고, 다음 명령으로 Object파일들을 한데 묶는 링크 과정을 수행합니다. 명령은 g++지만 내부적으로는 링커(ld)를 실행해서 실행 파일을 생성합니다.

$ g++ -o app.out main.o hello_world.o hello_world2.o

여기까지가 g++을 이용한 기본적인 빌드 과정입니다. 만일 소스 수정이 이루어진다면 위의 과정을 처음부터 다시 시작해주어야 될겁니다. 물론, 이를 하나의 명령어로 수행해야 한다면 위에서 언급한 바와 같이

$ g++ -o app.out main.cpp hello_world.cpp hello_world2.cpp hello_world.h hello_world2.h

라고 작성하면 됩니다. 그래도 솔직히 너무 깁니다. 아니 그러면 쉘 스크립트를 작성해서 스크립트 하나면 실행시키면 되지 않느냐 라고 할 수 있지만, 처음 부분에서 언급한 make의 강력한 기능, 즉 Incremental Build를 사용할 수 없습니다.

Incremental Build란 반복적인 빌드 과정에서 변경된 소스코드에 의존성이 있는 대상들만 추려서 다시 빌드하는 기능입니다. 예를 들어 hello_world.cpp 만을 수정했을 때 hello_world.o 파일만 다시 만들고 링킹과정을 수행합니다.

Incremental Build는 특히 예를들어 군 장비의 주요 시스템을 유지보수해야 하는 경우라면 빛을 발합니다. 대규모 프로젝트에서는 파일의 개수와 복잡도가 어마어마하기 때문에 make는 거의 필수입니다.

make는 makefile에서 대상(Target)별로 의존성을 명시하면 자동으로 Incremental Build를 수행하기 때문에 효율적입니다. 그래고 자주 사용되는 규칙은 내부적으로 자동처리하므로 쉘 스크립트보다 무척 편리합니다.

기본 내용은 make 공식 문서(링크)를 참조하여 작성하였습니다.