목차
- 소개
- Hello World
- target
- 변수 사용하기
- pattern 을 이용한 여러 파일 일괄 처리
- 부가 설명
1. 소개
만약 ant 를 이용한 java 개발 경험이 있다면 target 과 의존 target 에 대한 이해가 있을 것이다.
make 도 마찬가지로 target 이 존재하고 의존 target 개념이 존재한다.
간단한 Makefile 부터 시작해서 변수를 활용한 Makefile 작성 방법을 설명하겠다.
2. Hello World
all:
echo "Hello World"
Makefile 이라는 이름으로 저장하고 아래와 같이 실행
주의) all: 다음 줄 시작은 반드시 tab 을 입력해야 함
$ make
echo "Hello World"
Hello World
또는 아래와 같이 target 이름을 명령 인자로 넘겨줌
$ make all
echo "Hello World"
Hello World
존재하지 않는 target 을 입력하면 에러 출력
$ make man
make: *** No rule to make target `man'. Stop.
3. target
target 설명
target 은 make 가 수행하는 하나의 작업 단위를 의미함
기본적으로 make 는 target 을 파일명으로 간주함
예)
$ ls -l
합계 1
-rwxr-xr-x 1 root Domain Users 23 9월 11 11:26 Makefile
$ cat Makefile
all: hello.txt
cat $<
$ make
make: *** No rule to make target 'hello.txt', needed by 'all'. 멈춤.
$ echo hello world > hello.txt
$ ls -l
합계 2
-rw-r--r-- 1 root Domain Users 12 9월 11 11:30 hello.txt
-rwxr-xr-x 1 root Domain Users 23 9월 11 11:26 Makefile
$ make
cat hello.txt
hello world
위 예에서 hello.txt 는 Makefile 에 target 으로 작성하지 않았지만 hello.txt 파일이 존재하는지 확인하고 all 을 수행한다.
target 문법
<target>: <prerequisite target> <another prerequisite target> ...
<tab><system command>
<tab><another system command>
...
<another target>: ...
...
예)
$ cat Makefile
all: prepare build
prepare:
echo "prepare"
touch .prepare
build:
echo "build"
touch product
clean:
echo "clean"
rm .prepare product
$ make
echo "prepare"
prepare
touch .prepare
echo "build"
build
touch product
$ ls -l
total 12
4 drwxrwxr-x 2 root root 4096 Sep 4 16:08 ./
4 drwxrwxr-x 4 root root 4096 Sep 4 16:07 ../
0 -rw-rw-r-- 1 root root 0 Sep 4 16:08 .prepare
4 -rw-rw-r-- 1 root root 141 Sep 4 16:08 Makefile
0 -rw-rw-r-- 1 root root 0 Sep 4 16:08 product
설명:
prepare 와 build 는 단일 수행 target 이고 all 은 prepare 와 build 가 선행되어야 하는 target 이다.
위 에제를 통해 아래와 같은 특징을 알 수 있음
- 여러줄의 명령 입력 가능함 (단, tab 으로 시작만 하면됨)
- all 은 기본 target 이기 때문에 make 명령에 target 을 지정하지 않으면 all 을 실행함
- make 프로그램은 기본적으로 작업 위치의 Makefile 이라는 이름의 파일을 찾아 실행함
- make 프로그램은 Makefile 파일을 한줄씩 읽으며 바로바로 실행해 가는게 아니라 파일 전체를 먼저 파악하고 실행한다.
- prepare 와 build 는 all 보다 하단에 정의 되었는데 실행에 문제 없었다.
- make 프로그램이 Makefile 을 찾아 실행한다.
- make 에 target 을 입력하지 않았기 때문에 기본 target 인 all 을 수행한다.
- all target 이 prepare 와 build target 을 사전에 실행하길 원하기 때문에 순서대로 prepare 와 build 를 수행한다.
- all target 에 command 가 있다면 수행하고 종료한다.
컴파일에 적용해 보기
$ cat Makefile
all:
gcc -o hello main.c
$ cat main.c
#include <stdio.h>
int main(int argc, char * args[]) {
printf("Hello World\n");
return 0;
}
$ make
gcc -o hello main.c
$ ./hello
Hello World
위에서는 all target 에 컴파일 명령을 넣어 줌으로써 script 를 작성한 수준과 다르지 않다.
하지만 컴파일 과정은 위와 같이 단순한 경우도 있겠지만 컴파일과 링크 과정을 나눌 수 있고 라이브러리 별로 include 와 link 옵션 등을 구분지어야 할 경우도 있다.
아래에서 설명할 make 에서 지원하는 변수 기능과 dependency target 기능을 활용하여 컴파일 과정을 분리하는 등의 일을 할 수 있다.
4. 변수 사용하기
예)
$ cat Makefile
CC=gcc
TARGET=hello
SRC=main.c
all: $(TARGET)
$(TARGET):
$(CC) -o $@ $(SRC)
clean:
rm $(TARGET)
$ cat main.c
#include <unistd.h>
#include <stdio.h>
int main(int argc, char * args[]) {
printf("Hello World\n");
return 0;
}
$ make
gcc -o hello main.c
$ ./hello
Hello World
변수 선언
name=value
= 또는 :=, ?=, += 으로 변수값을 지정할 수 있다.
참고: http://stackoverflow.com/questions/448910/makefile-variable-assignment
변수 사용
$(name)
환경 변수
make 실행시 기본으로 환경 변수들이 make 변수로 선언되어 있다.
$ cat Makefile
all:
echo $(PWD)
echo $(PATH)
$ echo $PWD
/root/practice/makefile/variable_env
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
$ make
echo /root/practice/makefile/variable_env
/root/practice/makefile/variable_env
echo /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
PWD 는 shell 에서 사용하는 환경 변수지만 make 가 실행되면서 자동으로 환경 변수들을 make 변수로 선언했기 때문에 $(PWD) 를 이용해 변수를 사용할 수 있다.
assign 문
assign 에 사용할 수 있는 기호는 아래와 같다.
- = : 값을 무조건 선언 (실행시 최종 값이 정해짐)
- := : 값을 무조건 선언 (선언시 최종 값이 정해짐)
- ?= : 변수가 선언되어 있지 않을 때만 선언
- += : 변수에 값을 추가
$ cat Makefile
A=a
B:=b
C?=c
D+=d
all:
echo A : $(A)
echo B : $(B)
echo C : $(C)
echo D : $(D)
$ make
echo A : a
A : a
echo B : b
B : b
echo C : c
C : c
echo D : d
D : d
위에서는 A 와 B, C, D 모두 Makefile 안에서 적용한 값이 출력되지만 아래와 같이 변수를 미리 선언한 경우 차이점을 확인할 수 있다.
$ export A=global_a
$ export B=global_b
$ export C=global_c
$ export D=global_d
$ make
echo A : a
A : a
echo B : b
B : b
echo C : global_c
C : global_c
echo D : global_d d
D : global_d d
= 와 := 의 차이점
예)
$ cat Makefile
A = a
CWD1=$(A)
CWD2:=$(A)
A += b
all:
echo $(CWD1)
echo $(CWD2)
$ make
echo a b
a b
echo a
a
= 로 지정한 CWD1 은 A += b 가 반영된 a b 를 출력하고 := 로 지정한 CWD2 은 선언시 정해진 A 의 값인 a 를 출력한다.
5. pattern 을 이용한 여러 파일 일괄 처리
프로젝트가 커지면 소스 파일의 개수가 많아 질 수 있다.
보통 .c 또는 .cpp 등의 확장자를 갖고 같은 작업을 통해 빌드 결과물을 얻는다.
이런 경우 pattern 키워드를 활용하여 여러 반복작업들을 표현할 수 있다.
$ ls -l
total 16
-rw-rw-r-- 1 root root 158 Sep 5 09:48 Makefile
-rw-rw-r-- 1 root root 102 Sep 5 09:50 hello.c
-rw-rw-r-- 1 root root 70 Sep 5 09:49 hello.h
-rw-rw-r-- 1 root root 121 Sep 5 09:49 main.c
$ cat Makefile
CC=gcc
TARGET=hello
OBJS=main.o hello.o
all: $(TARGET)
$(TARGET) : $(OBJS)
$(CC) -o $@ $(OBJS)
%.o : %.c
$(CC) -c -o $@ $<
clean:
rm -rf *.o $(TARGET)
$ cat main.c
#include <unistd.h>
#include <stdio.h>
#include "hello.h"
int main(int argc, char * args[]) {
hello();
return 0;
}
$ cat hello.h
#ifndef __HELLO_H__
#define __HELLO_H__
extern void hello();
#endif
$ cat hello.c
#include <unistd.h>
#include <stdio.h>
#include "hello.h"
void hello() {
printf("Hello World\n");
}
$ make
gcc -c -o main.o main.c
gcc -c -o hello.o hello.c
gcc -o hello main.o hello.o
$ ls -l
total 32
-rw-rw-r-- 1 root root 158 Sep 5 09:48 Makefile
-rwxrwxr-x 1 root root 7204 Sep 5 09:51 hello
-rw-rw-r-- 1 root root 102 Sep 5 09:50 hello.c
-rw-rw-r-- 1 root root 70 Sep 5 09:49 hello.h
-rw-rw-r-- 1 root root 1020 Sep 5 09:51 hello.o
-rw-rw-r-- 1 root root 121 Sep 5 09:49 main.c
-rw-rw-r-- 1 root root 936 Sep 5 09:51 main.o
$ ./hello
Hello World
$ make clean
rm -rf *.o hello
$ ls -l
total 16
-rw-rw-r-- 1 root root 158 Sep 5 09:48 Makefile
-rw-rw-r-- 1 root root 102 Sep 5 09:50 hello.c
-rw-rw-r-- 1 root root 70 Sep 5 09:49 hello.h
-rw-rw-r-- 1 root root 121 Sep 5 09:49 main.c
수행 과정 설명
- make 명령에 target 을 지정하지 않았으므로 all target 을 실행한다.
- TARGET 변수 값을 hello 로 지정했고 $(TARGET) 으로 target 을 하나 정의하였으므로 hello target 을 실행한다.
- hello target 은 $(OBJS) 가 먼저 수행되어 하므로 OBJS 에 지정한 main.o 와 hello.o 가 차례로 수행된다.
- main.o 와 hello.o 둘다 이름이 .o 로 끝난다는 공통점이 있고 %.o 의 조건에 맞기 때문에 %.o target 이 순차적으로 실행된다.
- %.o 는 main.o 로 바뀌고 %.c 는 main.c 로 바뀌며 main.c target 은 작성하지 않지만 make 는 기본적으로 target 을 파일명으로 인식하기 때문에 해당 파일이 존재하는지 확인하고 존재한다면 main.o 를 수행한다.
- gcc 의 -c 옵션은 링크 과정 제외하고 컴파일만 하라는 옵션이며 $@ 은 main.o 로 치환되고 $< 은 main.c 로 치환된다.
- hello.o 도 6번 과정을 거친다.
gcc -c -o main.o main.c
gcc -c -o hello.o hello.c - $(OBJS) 의 과정을 모두 마쳤으므로 $(TARGET) 을 수행한다.
gcc -o hello main.o hello.o - all 에서는 명령을 지정하지 않았으므로 작업을 종료한다.
패턴 설명
%.o : %.c
- make 실행시 % 기호가 .o 파일의 이름 부분으로 치환된다.
- 위 예제에서는 OBJS=main.o hello.o 라고 지정했기 때문에 main.o 와 hello.o 로 치환된다.
- %.c 는 .o 파일의 이름을 따라 main.c 와 hello.c 로 치환된다.
- main.o 와 hello.o 두개를 지정했기 때문에 두번의 실행을 거친다.
gcc -c -o main.o main.c
gcc -c -o hello.o hello.c
$@ 와 $<
- 참고: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html#Automatic-Variables
- 복잡한 규칙이 있지만 $@ 은 %.o 가 치환된 이름 (main.o 또는 hello.o) $< 은 %.c 가 치환된 이름 (main.c 또는 hello.c) 을 가져오라는 의미로 기억하자. (아래와 같이 기억하고 있음)
- $@ 은 at 기호를 사용함으로 목적지 %.o
- $< 은 < 기호가 입력해 사용되는 기호와 같으므로 입력해 해당하는 %.c
팁) .c 와 .cpp 파일이 같이 포함되어 있다면
$ ls -l
total 16
-rw-rw-r-- 1 root root 195 Sep 5 10:12 Makefile
-rw-rw-r-- 1 root root 102 Sep 5 10:10 hello.c
-rw-rw-r-- 1 root root 70 Sep 5 10:10 hello.h
-rw-rw-r-- 1 root root 121 Sep 5 10:09 main.cpp
$ cat Makefile
CXX=g++
OBJS=main.o hello.o
TARGET=hello
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) -o $@ $(OBJS)
%.o: %.c
$(CXX) -c -o $@ $<
%.o: %.cpp
$(CXX) -c -o $@ $<
clean:
rm -rf $(OBJS) $(TARGET)
$ cat main.cpp
#include <iostream>
#include "hello.h"
using namespace std;
int main(int argc, char * args[]) {
hello();
return 0;
}
$ cat hello.h
#ifndef __HELLO_H__
#define __HELLO_H__
extern void hello();
#endif
$ cat hello.c
#include <unistd.h>
#include <stdio.h>
#include "hello.h"
void hello() {
printf("Hello World\n");
}
$ make
g++ -c -o main.o main.cpp
g++ -c -o hello.o hello.c
g++ -o hello main.o hello.o
$ ./hello
Hello World
6. 부가 설명
.PHONY target 사용
참고: http://www.gnu.org/software/make/manual/make.html#Phony-Targets
make 의 특징 중에 하나는 컴파일 시간을 단축하기 위해 재작업이 필요없다고 판단한 경우 해당 작업을 skip 한다.
예)
$ ls -l
합계 1
-rwxr-xr-x 1 tjjang Domain Users 26 9월 11 14:27 Makefile
$ cat Makefile
all:
echo "Hello World!"
$ make
echo "Hello World!"
Hello World!
$ touch all
$ ls -l
합계 1
-rw-r--r-- 1 root Domain Users 0 9월 11 14:28 all
-rwxr-xr-x 1 root Domain Users 26 9월 11 14:27 Makefile
$ make
make: 'all' is up to date.
all 파일을 생성한 이후 make 프로그램이 all target 이 이미 최신 상태로 갱신되어 있다고 판단하고 all 을 수행하지 않고 있다.
하지만 all 은 항상 수행되어야 하며 파일명과 무관하기 때문에 make 프로그램에게 해당 target 은 항상 실행해야 한다고 명시해 줘야 한다.
이 때 .PHONY target 을 지정한다.
예)
$ cat Makefile
.PHONY: all
all:
echo "Hello World!"
$ ls -l
합계 1
-rw-r--r-- 1 rootDomain Users 0 9월 11 14:28 all
-rwxr-xr-x 1 root Domain Users 41 9월 11 14:31 Makefile
$ make
echo "Hello World!"
Hello World!
.PHONY 지정 후 all 파일이 존재하여도 all 을 수행한다.
굿굿 잘보고 감~
답글삭제굿굿 잘보고 감~
답글삭제