임베디드/기반지식2015. 4. 22. 14:35


make


파일관리유틸리티로 각파일간의 종속관계를 파악하고 Makefile에 기술된대로 컴파일명령이나 쉘명령을 순차적으로 실행해주는것으로 빌드를 자동화하여 개발자의 수고를 덜어준다.

프로그램의 종속구조를 파악하고 관리가 용이해진다.

all : diary

diary : main.o calendar.o memo.o

gcc -W -Wall -o diary memo.o calendar.o main.o

main.o : main.c

gcc -W -Wall -c -o main.o main.c

calendar.o : calendar.c

gcc -W -Wall -c -o calendar.o calendar.c

memo.o : memo.c

gcc -W -Wall -c -o memo.o memo.c


첫번째 타겟 의 dependency인 diary로 이동 diary: 는 main, calendar, memo 디펜던시가 있는데 main.o calendar.o main.o가 생성이 되었는지 확인하고 안되어 있으면 해당 타겟을 먼저 실행한다.

해당 타겟이 모두 준비되면 gcc -W -Wall -o diary memo.o calendar.o main.o 가 실행되어 diary라는 실행파일을 만들어낸다.


빌드 및 실행하면..

leeyosam$ make

gcc -W -Wall -c -o main.o main.c

gcc -W -Wall -c -o calendar.o calendar.c

gcc -W -Wall -c -o memo.o memo.c

gcc -W -Wall -o diary memo.o calendar.o main.o

leeyosam$ ./diary 

function memo.

function calendar.


위 상태에서 calendar.c파일을 수정하면 이 파일만 다시 빌드 한 후 링크한다.

leeyosam$ make

gcc -W -Wall -c -o calendar.o calendar.c

gcc -W -Wall -o diary memo.o calendar.o main.o


Makefile기본 구조 및 규칙


CC    = gcc

target1 : dependency1 dependency2

command1

command2

1) 명령의 시작은 반드시 탭으로 시작된다.

2) 비어 있는 행은 무시된다.

3) #을 만나면 개행을 만날때까지 무시한다.

4) 행이 길어지면 \ 문자로 다음행에 이어서 기술이 가능하다.

diary : memo.o calendar.o\

main.o

==> diary : memo.o calendar.o main.o

5) ;는 명령행을 나눌때 사용한다.

target1 : dependency1 dependency2; command1

6) 종속항목이 없는 타겟도 가능하다

clean :

rm -rf *.0 target1 target2


매크로 규칙


1) 매크로명    = 매크로내용 형식

2) #은 주석의 시작

3) 여러행 기술시 \ 사용

4) 매크로 참조시에는 소괄호나 중괄호사용 가능하고 $를 앞에 붙이는데 쉘의 환경변수를 사용할때 중괄호를 사용하므로 해깔리지 않도록 소괄호사용이 권장된다.

NAME    = string

$(NAME)

5) 정의되지 않은 매크로는 빈문자(null)로 치환된다.

6) 중복정의하게 되면 마지막 값이 사용된다.

7) 매크로 정의시 이전에 정의된 매크로참조가 가능하다.

8) =, :=, +=, ?= 등 대입이 가능하다.

=  는  매크로정의 순서와 상관없이 매크로들을 모두 반영한다.

:= 는 매크로정의 순서 그대로 반영한다.

+= 는 기존 매크로에 추가한다.

?= 는 정의되지 않았으면 새로 반영하고 정의되어 있으면 반영하지 않는다.

A    = $(B) BB

B    = $(C) CC

C    = D

==> 이 경우 A는 D CC BB 가 된다.

a    := $(b) bb

b    := $(c) cc

c    := d

==> 이 경우 a 는 bb가 된다. 순차적으로 반영이 되므로 a를 정의할때의 bb가 b의 값이 새로 정의되더라도 a에 반영되지 않는다.


내부적으로 정의되어 있는 매크로의 사용


make -p명령으로 내부 매크로리스트를 확인할 수 있다. 미리 정의된 매크로들을 추가로 정의할 필요가 없다. 

많이 사용하는 매크로들은 다음 명령어로 확인 가능하다

make -p | grep ^[[:alpha:]]*[[:space:]]*=[[:space:]]

매크로 이름

설명

기본값

AR

아카이브 관리 프로그램

ar

AS

어셈블러

as

CC

C 컴파일러

cc

CXX

C++ 컴파일러

g++

CO

RCS checkout 프로그램

co

CPP

C 전처리기

cc E

FC

포트란 컴파일러

f77

GET

SCCS 관련 프로그램

get

LD

링크

ld

LEX

스캐너 코드 생성기

lex

PC

파스칼 컴파일러

pc

YACC

파서 코드 생성기

yacc

MAKEINFO

Texinfo 파일을 Info 파일로 변환

makeinfo

TEX

TeX 문서로부터 TeX DVI 생성

tex

TEXI2DVI

Texinfo 파일을 dvi 파일로 변환 프로그램

texi2dvi

WEAVE

Web TeX로 변환

weave

CWEAVE

C Web TeX로 변환

cweave

TANGLE

Web을 파스칼로 변환

tangle

CTANGLE

C Web C로 변환

ctangle

RM

파일을 지우는 명령

rm f

ARFLAGS

ar 플래그

rv

ASFLAGS

어셈블러 플래그

 

CFLAGS

C 컴파일러 플래그

 

CXXFLAGS

C++ 컴파일러 플래그

 

COFLAGS

RCS co 플래그

 

CPPFLAGS

C 전처리기 플래그

 

FFLAGS

포트란 컴파일러 플래그

 

GFLAGS

SCCS get 플래그

 

LDFLAGS

링크 플래그

 

LFLAGS

Lex 플래그

 

PFLAGS

파스칼 컴파일러 플래그

 

YFLAGS

Yacc 플래그

 


make -p로 확인불가능한 추가 내부 매크로

$?

현재의 타겟보다 최근에 변경된 종속 항목 리스트

(확장자 규칙에서 사용 불가)

$^

현재 타겟의 종속 항목 리스트

(확장자 규칙에서 사용 불가)

$@

현재 타겟의 이름

$<

현재 타겟보다 최근에 변경된 종속 항목 리스트

(확장자 규칙에서만 사용 가능)

$*

현재 타겟보다 최근에 변경된 현재 종속 항목의 이름(확장자 제외)

(확장자 규칙에서만 사용 가능)

$%

현재의 타겟이 라이브러리 모듈일 때 .o 파일에 대응되는 이름

${@F}현재 타겟의 파일 부분(target)

${<F}: 현재 디펜던시항목의 파일 부분(test.c)

${@D}: 현재 타겟의 디렉토리 부분(/temp)

${<D}: 현재 디펜던시항목의 디렉토리 부분(/usr/local/c)


$@와 $^는 다음과 같이 사용될 수 있다.

target : dependency1.c dependency2.c

             gcc -o target dependency1.c dependency2.c

==> 

target : dependency1.c dependency2.c

             gcc -o $@ $^


${@F}와 ${<F}, 그리고 ${@D}와 ${<D}는 다음 타겟과 디펜던시가 있을 경우

 /temp/target : /usr/local/c/test.c

==>

${@F} : target

${<F} : test.c

${@D} : /temp

${<D} : /usr/local/c


확장자 규칙의 사용


make -p로 출력되는 내용 중에 %.o : %c 가 있다.

%o : %c

$(COMPILE.c) $(OUTPUT_OPTION) $<

이는 *.o타겟이고 확장자가 c인 c 코드파일을 만나면 c -o $@ $<를 수행하라는것이다.

다음 Makefile은 맨처음 이 문서에서 작성한 Makefile과 동일한 동작을 수행한다.

OBJECTS    = memo.o calendar.o main.o

all : diary

diary : $(OBJECTS)

$(CC) -o $@ $^

OBJECTS에 정의된 dependency들을 처리하는데 타겟이 *.o이고 확장자가 *.c인것들을 만나게 된다. 이 때 make는 다음 순서로 처리하게 된다.

1) Makefile에서  diary를 생성하기 위해 make는 dependency들을 살펴보고 각 각을 타겟으로 설정한다.

2) diary는 memo.o에 의존하고 있고 memo.o는 만들어져 있지 않으면 memo.o를 만들어야 하는게 룰이 정의되어 있지 않다. 

3) 따라서 make는 내부 확장자 규칙 .o를 참조하여 다음 기준으로 현재 디렉토리에서 memo.o를 생성할 파일을 찾는다

- 확장자를 제외하고 memo.0와 같은 이름이 있어야 한다. memo.c가 해당된다.

- 중요확장자를 가지고 있어야 한다. .c, .cc, .cpp, .s, .S, ... 등

- 내부확장자 규칙에 따라 memo.o를 만드는데 사용할 수 있어야 한다. make -p로 확인해본바 %.o : %.c가 있기 떄문에 가능하다.

4) 내부정의 확장자 규칙에 따라 memo.o를 생성한다. cc -c -o memo.o memo.c 가 수행된다.


내부확장자 규칙을 변경하고자 하는 경우에는 .SUFFIXES 타겟으로 가능하다. 다음 Makefile예는 -DDEBUG 옵션을 추가하는 예제이다.

OBJECTS    = memo.o calendar.o main.o

.SUFFIXES : .o .c

$(CC) -DDEBUG -c -o $@ $<

all : diary

diary : $(OBJECTS)

$(CC) -o $@ $^



명령어 사용 규칙

echo :
@echo "echo test!"
위 명령어를 실행하면 새로운 쉘이 뜨고 echo명령이 실행된다고 함. 그런데 우분투와 맥에서 안그러던데???. 내가 참조한 책이 좀 옜날 얘기를 하는것인지 모르겠음.
무튼 주의할것은...
어떤 명령이 성공했을때만 다음 작업을 해야 하는 경우가 있을 수 있다.
del :
cd ./backup
rm -rf*
위 명령의 cd ./backup이 실패한다면 현재 폴더의 모든 내용이 삭제될것이다. 아마 머리를 쥐어잡고 울부짖게 되겠지...
이걸 피하기 위해서 아래와 같이 한다.
del :
cd ./backup && rm -rf*
&&은 명령이 성공하였는지를 추가로 검사한다.
명령의 성공이라함은 c의 main함수가 반환하는 수가 0인경우를 말한다.
c로 만든 프로그램에서 0반환은 성공을 의미하며 0이 아닌 수를 반환하는것은 어떤 에러가 났음을 의미한다.
리눅스에서 바로전에 호출한 명령이 반환한 값을 확인하고 싶으면 $?를 입력해보면 된다.
del :
cd ./backup; rm -rf* 
위 와 같이 하면 하나의 쉘에서 연달아 명령을 수행할 수 있다고 한다. 하지만 이것도 우분투와 맥에서 ; 없이도 하나의 쉘에서 잘 동작한다.

모든 명령에 대해 에코기능을 끄려면 .SILENT : 타겟을 정의한다. 이 타겟에 등록한 디펜던시들의 명령어들은 모두 에코기능이 꺼지게 된다.

make는 명령어가 에러를 발생시키면 중단시킨다. 그런데 에러가 나도 중단시키기 않고 빌드를 계속 진행하고자 한다면 - 를 명령어 앞에 넣으면 된다.
cat :
-cat dummy.txt

Makefile전체에서 명령어 오류를 무시하고자 한다면 .IGNORE : 타겟을 사용한다. 여기에 적시한 디펜던시들은 명령어가 오류가 나도 무시하고 진행된다.


재귀적 make사용

소스가 많아지면 여러 폴더로 소스를 나누어서 빌드하면 관리가 아주 유용하다.
재귀적 make말고 VPATH를 이용하는 방법도 있으나 파일명에 종속되기 때문에 재귀적 make가 더 효율적이도 더 많이 사용된다.

아래와 같이 소스파일이 분포 시켰다.

leeyosam$ tree

.

├── Makefile

├── calendar

│   ├── Makefile

│   └── calendar.c

├── include

│   └── diary.h

├── main

│   ├── Makefile

│   └── main.c

└── memo

    ├── Makefile

    └── memo.c


여기서 calendar, main,  memo폴더 내의 Makefile은 모두 아래와 같이 정의된다.

OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))
CFLAGS = -I../include

all : $(OBJECTS)
cp -f $^ ../
clean :
rm -rf *.o

$(patsubst %.c, %.o, $(wildcard *.c)) 구문은 함수에서 다시 설명하겠지만 해석하자면 확장자가 c인파일을 찾아서 o로 바꾸는 동작을 수행한다.

그리고 최상위 위치의 Makefile은 아래와 같다.

DIRS = memo calendar main
OBJECTS = memo.o calendar.o main.o
TARGET = diary

all : objs
$(CC) -o $(TARGET) $(OBJECTS)

objs :
@for dir in $(DIRS); do \
make -C $$dir || exit $?; \
done
clean :
@for dir in $(DIRS); do \
make -C $$dir clean; \
done
-rm -rf *.o $(TARGET)

@for구문을 사용해서 단순화하였는데 DIRS에 기술한 폴더들을 루프를 돌면서 make를 수행하고 exit $? 이전 경로로 돌아가고 있다.
위와 같이 구성하면 소스폴더가 생길때마다 폴더를 추가하는것으로 끝나게 된다.

서브 Makefile에 대한 매크로 전달

최상위 Makefile에서 정의한 매크로를 서브 Makefile에서도 사용하고자 하는 경우가 있을것이다. 이 때는 export키워드를 사용한다.

export CC    = gcc

조건부 수행

C의 if else에 해당하는 구문
ifeq ($(CC),gcc)        # 반대는 ifneq
@echo "GNU GCC compiler.."
else
@echo "$(CC) compiler"
endif


함수의 사용


make는 Makefile에서 사용할 수 있는 여러 함수들을 제공한다.

$(shell 쉘명령어) : 쉘명령어를 수행하고 결과를 리턴한다.

SRCS = $(shell ls *.c)

echo :

@echo $(SRCS)

$(subst 찾을문자열, 변경할문자열, 목표문자열)  : 목표문자열에서 찾을문자열을 찾아서 변경할문자열로 변경한다.

STR    = $(subst like, LIKE, I like you)

==>  I LIKE you 를 찍게 된다.

$(patsubst 패턴, 변경문자열, 목표문자열) : subst와 다른점은 패턴을 발견하면 변경문자열로 변경한다. %기호가 사용될 수 있다.

STR    = $(patsubst %.c, %.o, memo.c main.c ABCD)

==> memo.o main.o ABCD

$(매크로명: 패턴=치환할문자열) : 매크로에서 패턴을 발견하면 변경한다.

MACRO    = memo.c main.c ABCD

STR       = $(MACRO:%.c=%.o)

$(sort 문자열) : 문자열의 인자들을 정렬하며 중복된것은 하나로 인식한다.

MACRO    = bbb aaa ccc aaa ddd

STR    = $(sort $(MACRO))

==> aaa bbb ccc ddd

$(strip 문자열) : 인자로 오는 문자열의 앞뒤의 공백을 제거하고 문자열내의 공백은 한칸으로 줄인다. 가령 'I love you.      ' 같은 문자열은 'I love you.'로 변경된다.

$(filter 패턴, 문자열) : 패턴과 일치하는 문자열만 걸러준다. 소스와 헤더파일이 섞여 있는 경우 소스와 헤더파일을 각각 읽어올때 유용하다.

FILES    = memo.c head.h mein.c asm.S diary.h

SRCS    = $(filter %c %S, $(FILES))

HEADERS    = $(filter %h, $(FILES))

$(filter-out 패턴, 문자열) : filter 의 반대

$(findstring 찾을 문자열, 대상문자열)

$(words 문자열) : 문자열내에서 사용된 단어들의 개수를 리턴한다.

$(word [n], 문자열) : 문자열내의 n번째 문자열을 리턴한다.

$(wordlist 시작번호, 끝번호, 문자열) : 문자열의 시작번호와 끝번호사이의 문자열을 리턴한다.

$(firstword 문자열) : 문자열에서 첫번째 단어를 리턴한다.

$(join 문자열, 문자열) : 앞뒤의 문자열을 합친다.

$(dir 문자열) : 문자열에서 디렉토리부분만 리턴한다.

PATHS    = /bin/ls /sbin/ifconfig memo/memo.c Makefile

echo : 

@echo $(dir $(PATHS))

==> /bin/ /sbin/ memo/ ./  

$(notdir 문자열) : 문자열에서 디렉토리부분만 제외하고 리턴한다.

==> ls i fconfig memo.c Makefile

$(suffix 문자열) : 문자열에서 확장자만 리턴한다.

FILES    = src/memo.c asm/asm.S for.f ls /etc/resolv.conf

echo :

@echo $(suffix $(FILES))

==> .c .S .f .conf

$(basename 문자열) : 문자열에서 점과 확장자를 제외한 순수한 파일명만 리턴한다.

==> src/memo asm/asm for ls /etc/resolv

$(addsuffix 접미사, 문자열) : 문자열에 접미사를 추가한다.

$(addprefix 접두사, 문자열) : 문자열에 접두사를 추가한다.

$(wildcard 패턴) : wildcard 패턴결과를 리턴한다.

$(foreach 변수명, 대입문자열, 확장문자열) : 변수명에 대입문자열을 단어별로 대입하여 넣고 그 변수를 ㅎ ㅘㄱ장문자열에서 사용한다.

SRCS    = $(foreach str, a b c, $(str).c)

echo :

@echo $(SRCS)

==> a.c b.c c.c

주로 다음과 같이 반복확장시 유용하다.

SRCS    = $(foreach dir, . memo main calendar, $(wildcard $(dir)/*.c))

echo :

@echo $(SRCS)

Posted by 삼스

댓글을 달아 주세요

  1. 재미있는 글 매우 잘 배우고 가여

    2020.11.24 00:30 [ ADDR : EDIT/ DEL : REPLY ]