SW 설계/make

makefile 기초: 소개 및 실행 과정

yztech 2021. 10. 19. 03:17
반응형

makefile은 어떤 모듈이 수정되어 다시 컴파일되어야 하는지 자동으로 결정해서, 필요한 파일들만 재 컴파일해서 하나의 프로그램으로 생성하므로 재컴파일에 따른 시간 낭비를 최소화할 수 있다. makefile은 makepp에게 프로그램을 컴파일하는 방법을 알려주는 명령어들의 집합이다. makepp는 표준 유닉스 make를 위해 작성된 makefile들을 읽을 수 있다. 소스 파일들을 사용하여 하나의 실행 프로그램으로 컴파일하는 과정은 복잡하고, 컴파일에 많은 시간이 소요된다. 개발자가 하나의 프로그램을 만들기 위해 각 소스 파일들을 컴파일하는 명령을 직접 입력하기엔 입력할 인자들이 너무 많다. 물론 shell script를 사용하여 프로그램을 컴파일할 수 도 있지만, 사용되는 소스 중 단 하나만 수정되어도 모든 파일들을 다시 컴파일하게 되므로 비효율적이고 시간 낭비다. 또한, 사람이 다시 컴파일할 소스파일을 일일이 컴파일러에게 알려주는 것도 많은 오류나 시행착오를 유발하기 쉽다. 이는 사람의 뇌가 정교한 일을 반복해서 실수 없이 수행하도록 설계되어 있지 않기 때문이다. makefile은 어떤 모듈이 수정되어 다시 컴파일되어야 하는지 자동으로 결정해서, 필요한 파일들만 재 컴파일해서 하나의 프로그램으로 생성하므로 재컴파일에 따른 시간 낭비를 최소화할 수 있다.

보통 입력 파일은 소스 코드이고, 출력 파일은 실행파일인 경우가 대부분이지만, mapepp를 사용할 때는 이러한 규칙은 필요 없다. mapepp는 파일의 수정 여부에 기인하여 명령을 선택적으로 수행하므로, 사용자가 원하는 임의의 작업들에도 사용할 수 있다. 예를 들면, 입력이 자주 변하는 데이터 분석 툴을 만들려고 할 때도, 바뀐 입력에 대해서만 결과를 업데이트하도록 구현할 수 도 있다.

간단한 makefile

만들려는 프로그램이 하나의 소스 코드만 포함하고 있다면, makefile을 사용할 필요가 없다. 사용자는 이미 언제 그 모듈을 컴파일해야하는지 알고 있기 때문이다. 하지만, 적어도 두 개 이상의 소스 코드들을 포함하고 있다면, 분명히 makefile을 사용하여 자동으로 프로그램을 컴파일하고 싶어질 것이다.

이제 두 개의 소스 코드들 main.cfunction.c 를 포함하는 C++ 프로그램을 만든다고 하자. main.cfunction.c 는 여러 헤더 파일들을 포함하고 있다. 직접 프로그램을 컴파일한다고 하면, 아래와 같은 명령어들을 직접 입력하여 프로그램을 컴파일하게 될 것이다.

c++ -c function.c -o function.o
c++ -c main.c -o main.o
c++ main.o function.o -o program

(1-2)는 컴파일 명령으로 각 소스 파일들 function.cmain.c 을 각각 컴파일하여 object 파일들 function.omain.o 를 생성한다.

(3)에서는 만들어진 두 개의 object 파일들 function.omain.o 를 link하여 하나의 실행파일 program을 생성하게 된다.

이 명령들은 makefile 내에서 각각 별도의 규칙 (rule)을 가져야 한다. makefile에서 규칙이란 하나 이상의 입력 파일들을 사용하여 하나 이상의 출력 파일들을 생성하는 것을 의미한다. makeapp은 어떤 규칙이 다시 수행되어야 하는지 결정하게 되는데, 이는 그 규칙이 최종 수행된 이후로 입력 파일이 수정되었는지 확인해서 결정한다. 기본적인 규칙은 아래와 같다.

output filenames: input filenames
    actions

(1)에서 출력 파일의 이름들을 space로 구분해서 적은 후, 콜론 (:)이 들어가고, 사용될 입력 파일들의 이름이 space로 구분되어 들어간다. 이 출력 파일들을 **targets**이라고 부르고. 출력 파일들을 생성할 때, 이 규칙을 다시 실행할지를 결정하는 입력 파일들을 dependencies라고 부른다. 따라서, dependencies에 변화가 있다면, 해당 target은 다시 컴파일된다.

(2)에서 action은 실제 수행할 명령어들이고, 보통 tab으로 들여 쓰기를 한다. actions들이 여러 개 있다면, 적힌 순서대로 실행되고, 하나라도 실패하면, 나머지는 더 이상 수행되지 않는다. 수행할 명령어들의 끝은 들여 쓰기 여부로 확인한다.

makefile은 작성 시에는 순서가 상관없지만, 보통 최종 프로그램을 만들기 위해 link 하는 규칙을 먼저 두고, 컴파일하는 규칙을 다음에 둔다. 위에서 사용한 컴파일 명령들을 makefile로 만들면 다음과 같이 구현할 수 있다.

# Link
program: main.o function.o
    c++ main.o function.o -o program

# Compilation
main.o: main.c
    c++ -c main.c -o main.o

function.o: function.c
    c++ -c function.c -o function.o

 

이 makefile을 사용하게 되면, makepp은 makefile의 첫 번째 target인 program을 빌드하려고 할 것이다.

makepp가 target인 program을 빌드하려고 할 때, action인 link명령을 수행하기에 앞서, dependencies인 main.o와 function.o를 빌드해야 한다는 것을 알게 된다. 따라서, makepp는 main.o와 function.o를 어떻게 빌드하는지, 다른 규칙들을 찾아보게 된다.

makepp는 main.o를 빌드하기 위해서, makepp은 두 번째 규칙을 사용해야 한다는 것을 알게 된다. main.o를 만들기 위해 action인 컴파일 명령어를 수행하기에 앞서, dependencies인 main.c를 빌드해야 한다는 것도 알게 된다. makepp는 main.c를 어떻게 빌드하는지, 다른 규칙을 을 찾아보게 된다.

하지만, main.c를 빌드하는 규칙은 없다는 것을 알게 되고, 이미 main.c는 파일로 존재하는 것도 알게 된다.

이때 makepp는 main.c가 main.o를 최종 빌드한 시간 이후에 수정되었는지 검사한다. 기본적으로 두 파일의 시간을 확인하고, 별도의 폴더에 그 정보를 저장한다. 만약, 다음 조건일 때 두 번째 규칙의 action을 실행하게 된다.

  • main.o가 없을 때
  • main.o는 존재하지만, makepp가 main.o를 언제 컴파일했는지 여부를 모를 때
  • main.o를 최종 빌드 후, dependencies인 main.c의 날짜가 수정되었을 때
  • main.o를 최종 빌드 후, action이 변경되었을 때
  • 다른 architecture에서 컴파일되었을 때

main.o는 main.c에만 depend하지 않을 수 있고, 여러 헤더 파일들을 포함할 수 도 있다. 또한, include 된 헤더 파일 중 하나가 변경되고, main.c는 변경되지 않더라도, main.o는 다시 빌드해야 할 것이다. 이런 경우 다음과 같이 규칙을 만들 수도 있다.

main.o: main.c function.h main.h test.h
    c++ -c main.c -o main.o 

하지만, makefile에는 헤더 파일들을 포함할 필요가 없다. makepp는 include 된 헤더 파일들의 수정 여부를 자동으로 확인한다.

makefile에 컴파일할 때마다 소스 파일들의 #include 문을 찾고, 컴파일러 옵션의 -l을 찾아서, 헤더 파일들의 위치를 찾고, 자동으로 dependencies list에 포함시킨다. 따라서, 헤더 파일이 수정되면, 해당 헤더 파일을 include 한 소스파일들도 다시 컴파일된다.

일단, makepp가 main.o 가 최신인지 확인한 후에는, function.o 가 다시 빌드되어야 하는지 확인하기, 세 번째 규칙을 찾게 된다. 세 번째 규칙의 적용도 main.o의 두 번째 규칙과 동일하게 진행된다.

main.ofunction.o 가 제대로 빌드되었다고 확인되면, 첫 번째 규칙을 사용하여 link 명령을 수행하고, program을 빌드한다. main.ofunction.o의 시간과 program의 시간일 확인해서 dependencies가 최신인 경우 program을 다시 빌드하게 된다.

반응형

'SW 설계 > make' 카테고리의 다른 글

makefile 기초: 변수, 패턴 규칙, 자동 변수  (0) 2021.10.19
makefile 사용시 유용한 팁들!  (0) 2021.10.12