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 |