SW 설계/make

makefile 사용시 유용한 팁들!

yztech 2021. 10. 12. 13:43
반응형

GNU make는 코드를 컴파일하기 위해 만들어진 유틸리티지만, 현재는 많은 파일들과 디렉토리들에서 변경사항을 적용하기 위한 용도로도 많이 사용된다. 따라서, 현재는 개발자 뿐만 아니라, 시스템 관리자에게도 매우 유용한 툴이다.
make를 사용하려면 설정 파일이 필요한데, 이 설정 파일이름이 보통 makefile이다.
아무 인자없이 make를 실행하면, GNU make는 현재 작업 디렉토리에서 설정파일 GNUmakefile, Makefile, makefile을 찾는다.
만약 설정 파일에 다른 이름을 사용하고자 한다면 make -f filename을 사용해야 한다.

본 자료는 makefile을 사용할 때 혼동되거나, 자주 사용되는 구문들을 정리해보았다.

오늘 포함할 내용들은 아래와 같다.

  • 변수 선언: =, :=, ?= 차이
  • 자동 변수 (Automatic variables)
  • phony target
  • MAKEFILE_LIST
  • Built-in functions
  • 파일 합치기

변수선언: :=, =, ?= 차이

구분 설명
:= TEMP := $(CC)/temp # CC가 없으므로 TEMP=/temp 가 된다.
CC := tcc
TEMP := $(CC)/temp # CC가 있으므로 TEMP=tcc/temp 가 된다
= TEMP = $(CC)/temp # CC가 어디에 있든, TEMP=tcc/temp 가 된다.
CC = tcc
TEMP = $(CC)/temp # CC가 어디에 있든, TEMP=tcc/temp 가 된다.
?= CC ?= tcc # CC가 선언되지 않은 경우에만 CC=tcc 가 된다. #ifdef와 동일하다.

 

자동 변수 (Automatic variables)

구분 설명
$@ 현재의 목표 파일이다.
$* 확장자가 없는 현재의 목표 파일이다.
예를 들어, 목표 파일명이 `foo.c`라면, `$*`는 `foo`가 된다. 
$% 목표 파일이 archive member일 때, 목표 파일명이다.
예를 들어, 목표 파일명이 `foo.a(bar.o)`라면, `$@`은  `foo.a`이고, `$%`는 `bar.o`이다.
$< 첫번째 prerequiste의 이름이다.
예를 들어, `all: library.cpp main.cpp` 라면, 
`$@`은 `all`이고,
`$<`은 `library.cpp`이며,
`$^`은 `library.cpp main.cpp`이다.
$^ 모든 prerequiste의 이름들이다.
$? 현재의 목표 파일보다, 최근에 갱신된 prerequiste의 이름들이다. 

$*: 확장자가 없는 현재의 목표 파일 (target) 이다.

$@: 현재의 목표 파일이다.

$<: 현재의 목표 파일보다 더 최근에 갱신된 파일 이름이다.

$?: 현재의 목표 파일보다 더 최근에 갱신된 파일 이름이다.

 

phony target

`phony` 타겟은 실제 파일 이름을 나타내는 target이 아니다.

이것은 `make` 로 명확한 요청을 하기 위한 규칙 (recipe) 을 만들기 위해 사용된다. 크게 두가지 목적으로 사용되는데,

하나는, 동일한 이름의 파일로 인한 충돌을 피하는 것이고, 둘째는, make 성능을 향상하는 것이다.

실제 타겟 파일을 생성하지 않는 규칙(recipe)을 만든다고 하면, 그 규칙은 make시에 매번 해당 내용을 실행한다. 예를 들면, 아래 예에서 `rm` 명령은 `clean` 이라는 파일을 생성하지 않기 때문에, `make clean` 을 수행할때마다 `rm` 은 매번 실행된다.

 clean:
     rm *.o temp

 

하지만, 여기서 문제는 디렉토리에 위 타겟과 동일한 `clean` 이름의 파일이 존재하면, 타겟 `clean` 은 제대로 동작하지 않는다.

`clean` 파일은 선행조건이 없기 때문에, 항상 최신으로 간주될 것이고, `clean` 규칙은 실행되지 않는다.

이러한 문제를 해결하기 위해서, 다음과 같이 명백하게 `.PHONY` 지시자를 사용하여 특별한 target의 전제조건을 만들어 준다.

 .PHONY: clean
 clean:
     rm *.o temp

 

위와 같이 구현하면, `make clean`은 `clean` 이라는 파일이나 디렉토리의 존재여부와 상관없이 명령이 실행된다.

Recursive use

phony 타겟은 `make` 의 재귀적 호출과 함께 사용될 수 있다.

`makefile` 이 빌드해야할 많은 하위 디렉토리가 있는 경우, 재귀적 호출을 사용하여 하뒤 디렉토리를 순회하는 규칙을 정의하게 된다.

 .PHONY: clean
 clean:
     rm *.o temp

 

이러한 규칙은 몇가지 문제점이 있다.

우선 하위 디렉토리에서 발생한 에러는 규칙에서 무시되어 리포트되지 않는다. 따라서, 어느 한 디렉토리에서 에러가 발생해도, 멈추지 않고 마지막까지 빌드가 계속 진행된다.

이것을 해결하는 한가지 방법은 쉘 명령에 에러를 리포트하고 exit하도록 추가할 수 있다. 하지만, keep going 옵션인 `make -k` 를 사용하여 수행한 경우에도 exit하게 되는 단점이 있다.

이러한 방법을 사용할 때 더 큰 문제는 make의 장점인 병렬 build를 사용할 수 없다는 것이다.

이때, 하위 디렉토리들을 phony target으로 선언하여 해결할 수 있다. 단, 해당 하위 디렉토리들이 반드시 존재해야 빌드가 된다.

 SUBDIRS = foo bar dol
 
 .PHONY: subdirs $(SUBDIRS)
 
 subdirs: $(SUBDIRS)
 
 $(SUBDIRS):
     $(MAKE) -C $@

 foo: dol

여기서는 `dol` 하위 디렉토리가 컴파일될 때까지 `foo` 하위 디렉토리는 빌드되지 않도록 선언하였다. 이러한 종류의 관계성은 빌드를 병렬화할 때 특히 중요하다.

성능 향상

phony target에 대해서는 암시적 규칙 검색 (implicit rule search) 이 적용되지 않는다. 따라서, 암시적 규칙 검색을 건너뜀으로써, 실제 파일이 존재 여부를 신경쓰지 않아도 되므로, 성능 향상에 도움이 된다.

주의 사항

phony target은 실제 target의 전제 조건 (prerequisite) 이 되지 않아야 한다. 만일 실제 target의 전제조건이 된다면, 해당 명령은 make가 실제 타겟 파일을 업데이트할 때마다 매번 수행된다. phony target 이 실제 target의 전제 조건이 되지 않는다면, phony target은 명백하게 target으로 지정되는 경우에만 수행될 것이다.

Summary

phony target을 실행파일을 만들지 않는 target이다.

 

 

MAKEFILE_LIST

GNU make는 특별한 몇 개의 변수들을 지원한다.

`make` 가 많은 `makefile` 들을 읽을 수 있다. make 가 읽는 파일들은 `MAKEFILES` 변수에서 얻는 것, 커맨드 라인에서 얻는 것, 기본적인 파일들 혹은 `include` 지시자를 사용한 파일들이 포함될 수 있고, 이들 파일의 이름들은 `MAKEFILES_LIST` 변수에 자동으로 저장되고, 새로 추가될 때마다 우측에 확장된다.

아래예와 같이 `makefile` 을 구성하고, `makefile test` 을 실행하면, 아래와 같이 출력된다.

(1) 에서 `MAKEFILE_LIST``Makefile` 이 되어, `name1` 에 설정되고,

(3) `inlcude` 가 사용되면서, `MAKEFILE_LIST` 는 `Makefile inc.mk` 이 확장된 후,

(5) 이 값이 `name2` 에 설정되었다.

따라서, (8)출력된 값은 `Makefile inc.mk` 가 되고,

(9) `name1``Makefile`,

(10) `name2` 는 `Makefile inc,mk` 가 출력되었다.

 

name1 := $(lastword $(MAKEFILE_LIST))		# (1)

include inc.mk								# (3)

name2 := $(lastword $(MAKEFILE_LIST))		# (5)

test:
	@echo $(MAKEFILE_LIST)					# (8)
	@echo name1 = $(name1)					# (9)
	@echo name2 = $(name2)					# (10)
Makefile inc.mk
Makefile
inc.mk

 

Built-in functions

lastword: $(lastword names): names 변수의 마지막 워드를 추출한다.

abspath: $(abspath names): names 변수에 있는 각 파일이름에 대해, 절대 경로를 추가한다.

dir: $(dir names): names변수에 있는 각 파일이름의 경로를 추출한다.

patsubst: $(patsubst pattern, replacement, text): text에서 공백으로 분리된 word들에서 pattern을 replacement로 교체한다. 와일드 카드(%)가 pattern에 사용되면, 이것은 임의의 문자열이 될 수 있고, replacement에 있는 %를 대체한다.

아래와 같이 makefile을 구성한 후 실행하면, 아래와 같이 출력된다.

test_dir1 := $(abspath $(lastword $(MAKEFILE_LIST)))
test_dir2 := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
test_dir3 := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))

all:
	@echo $(test_dir1)
	@echo $(test_dir2)
	@echo $(test_dir)

 

MAKEFILE_LIST 변수에는 Makefile 문자열만 들어있어서, $(lastword $(MAKEFILE_LIST))은 Makefile이다,

(1) test_dir1은 위 결과에 abspath인 /hdd/test/ 가 추가되어, hdd/test/Makefile이 되었고,

(2) test_dir2은 test_dir1에서 경로만 추출하여 /hdd/test/이 되었고,

(3) test_dir3는 %/ 을 % 으로 교체한다. /hdd/test/이 %/에 해당하므로, 이것을 %에 해당하는 /hdd/test로 교체한다.

/hdd/test/Makefile
/hdd/test/
/hdd/test

 

아래와 같이 makefile을 구성한다. test_dirs는 2개의 문자열만 포함한다.

test_dirs := test1 test2
include $(patsubst %,$(src)/%/Make.tests, $(test_dirs ))

 

이는 다음과 같다.

include $(src)/test1/Make.tests $(src)/test2/Make.tests

 

파일 합치기

아래 코드는 여러개의 파일들을 `cat` 명령을 사용하여 하나의 파일로 합치는 코드이다.

`$ALL_CSRCS` 에는 4개의 파일명인 `test1.c` , `test2.c` , `test3.c`, `test4.c` 이 들어간다.

타겟 `$(COMBINED_C)` 을 생성할 때, cat 명령을 사용하여 모든 `$ALL_CSRCS` 들을 현재 목표파일 `$@` 에 붙여넣는다.

ALL_CSRCS = \
	test1.c \
	test2.c \
	test3.c \
	test4.c
    
$(COMBINED_C): $(ALL_CSRCS)
	mkdir -p $(dir $(COMBINED_C))
	cat $(ALL_CSRCS) > $@
반응형

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

makefile 기초: 변수, 패턴 규칙, 자동 변수  (0) 2021.10.19
makefile 기초: 소개 및 실행 과정  (0) 2021.10.19