2021년 목표설정

이미지
기본적으로 작년에 달성하지 못한 것들을 하려고 생각중인데..코로나가 언제까지 이어질지, 한국이나 북해도는 갈 수 있을지..자격증은 응시 가능할지..여러가지가 불확실하다. 2021년은 무엇보다 정신적인 부분과 경제적인 부분에 중점을 두고 조금 더 치열하게 지내보고 싶다. 일본나이로도 30대 마지막 해, 이제 불혹에 접어드는 나이..복잡하지만 심플하게. 육체적목표 : 트라이에슬론 스탠다드 도전하기 정신적 : 자격증2개 도전 + 자체개발 서비스 론칭 가족적 : 가정의 평화를 유지하기 경제적 : 외식과 유흥비를 줄이고 부수입을 늘려서 결과적으로 저축하기 사회적 : 목표세미나를 포함해서 민단과 개인인맥의 활성화와 교류를 촉진하기

Makefile 강좌 요약

출처 블로그>hyona02님의 블로그 | 꿈지기

* Makefile 강좌 요약
>makefile을 사용하기 전에
makefile에 파일이름을 추가할 때 각각의 파일마다 main이 있으면 실행이 안된다.
main은 파일들 중 하나에만 있어야 한다. 모든 파일이 하나의 main을 사용해야 한다.
(main파일은 main.o를 링크해서 사용하고, 다른 함수들은 makefile에 넣어 컴파일 경우도 있다.) 
>makefile 이란
쉽게 말하면 다음과 같은 경우에 make를 쓰면 유리합니다.
1. 입력 파일이 바뀌면 자동적으로 결과 파일이 바뀌기를 원할 때, 기왕이면 좀 지능적으로 일이 수행되기를 바랄 때 말입니다.
2. 위의 LaTeX 파일처럼 자동적으로 프로그램이 수행이 되기를 바랄 때... (배치(batch)의 개념이죠)
=> make는 위의 두 가지 개념을 모두 포함하고 있다고 봅니다. 보통 리눅스 프로그램에서는 make all을 입력하면 자세한 내막은 모르지만 자기가 알아서 모든 일을 다하죠... 그 다음으로 make install만 입력하면 되구요... 히...
GNU make는 보통 GNUmakefile, Makefile, makefile 중에서 하나가 있으면 그 파일을 읽게 된다. 하지만 일반적으로 Makefile을 추천하게 되는데, 그 이유는 우선 GNUmakefile은 기존의 make에서 인식을 못한다는 단점이 있고, makefile은 보통 소스 파일에 묻혀서 잘 안보이게 되기 때문이다.
>구조
Makefile은 기본적으로 아래와 같이 목표(target), 의존 관계(dependency), 명령(command)의 세개로 이루어진 기본적인 규칙(rule)들이 계속적으로 나열되어 있다고 봐도 무방하다. make가 지능적으로 파일을 갱신하는 것도 모두 이 간단한 규칙에 의하기 때문이다.

--------------------------------------------------------------------------------
target ... : dependency ...
                command
                ...
                ...

--------------------------------------------------------------------------------
target(목표) : 목적파일(object file) 명령이 수행되어 나온 결과 파일 지정.
command(명령) : dependency가 바뀌어도 변하지 않고, 일반적으로 쉘에서 쓸 수 있는 모든 명령어들을 사용가능
dependency(의존관계) : 컴파일할 파일들, 내용이 바뀔 수 있다.
=> 참고: 참고로 목표 부분에는 결과 파일만 올 수 있는 것이 아니고, 보통 make clean 에서와 같이 간단한 레이블(label) 기능을 제공하기도 한다.
=> 명령 부분은 꼭 TAB 글자로 시작해야 한다. 그냥 빈칸 등을 사용하면 make 실행 중에 에러가 난다. 명심하세요. make가 명령어인지 아닌지를 TAB 가지고 구별하기 때문이죠.
>예제
+-----------------------------------------------
| % gcc -c main.c
| % gcc -c read.c
| % gcc -c write.c
| % gcc -o test main.o read.o write.o
+----------------------------------------------
+-----------------------------------------------------------
| test : main.o read.o write.o
|                  gcc -o test main.o read.o write.o
|
| main.o : io.h main.c
|                 gcc -c main.c
| read.o : io.h read.c
|                 gcc -c read.c
| write.o: io.h write.c
|                   gcc -c write.c
+-----------------------------------------------------------
>매크로 사용
간단한 매크로 기능을 사용해 보자. main.o read.o write.o라는 것을 OBJECTS 라는 매크로로 바꾸는 것이 아래의 예제 2에 나와 있다.
Makefile예제 2

--------------------------------------------------------------------------------
OBJECTS = main.o read.o write.o
test : $(OBJECTS)
                gcc -o test $(OBJECTS)
main.o : io.h main.c
                gcc -c main.c
read.o : io.h read.c
                gcc -c read.c
write.o: io.h write.c
                gcc -c write.c
clean:
 rm -f $(OBJECT)
--------------------------------------------------------------------------------

위에서 보다시피 매크로는 그냥 프로그램 짤 때와 같이 사용해서 값을 대입한다. 대신 사용할 때는 반드시 $(..) 안에 넣어서 사용한다. 매크로 치환을 위한 특수한 방법이 아닐까... 히... 매크로의 사용법은 위와 같이 간단하므로 다양하게 정의해서 사용할 수 있다. 매크로에 대한 자세한 설명은 다음 장에서 언급하기로 한다.
clean => 이것은 레이블... 레이블은 이렇게 사용할 수 있다.
        (clean이라는 파일은 없다. 그러나 [make clean] 이라고 치면 OBJECT의 파일들을 모두 삭제해준다.)
>매크로란?
매크로의 정의는 프로그램을 작성할 때 변수를 지정하는 것처럼 하면 된다. 그리고, 매크로를 사용하기 위해서는 $(..)을 이용하면 된다. 아래는 매크로의 간단한 예제이다.
=> 참고: 매크로의 사용에서 ${..}, $(..), $..를 모두 사용할 수 있습니다. 그러나 대부분의 책에서는 $(..) 을 사용하라고 권하는군요.
Makefile예제 4

--------------------------------------------------------------------------------
OBJS = main.o read.o write.o
test : $(OBJS) <- (1)
gcc -o test $(OBJS)
                ..........

--------------------------------------------------------------------------------
>미리 정의된 매크로
'make -p' 라고 입력해 보면 make에서 미리 세팅되어 있던 모든 값들(매크로, 환경 변수(environment) 등등)이 엄청 스크롤 된다.
--------------------------------------------------------------------------------
ASFLAGS = <- as 명령어의 옵션 세팅
AS = as
CFLAGS = <- gcc 의 옵션 세팅
CC = cc (= gcc)
CPPFLAGS = <- g++ 의 옵션
CXX = g++
LDLFAGS = <- ld 의 옵션 세팅
LD = ld
LFLAGS = <- lex 의 옵션 세팅
LEX = lex
YFLAGS = <- yacc 의 옵션 세팅
YACC = yacc
MAKE_COMMAND = make

--------------------------------------------------------------------------------

=> 참고: 직접 make -p를 해서 한번 확인해 보세요. 과연 make는 내부적으로 어떤 변수들을 사용하고 있는지 알아봅시다. 매크로는 관습적으로 대문자로 작성되니까 이점에 유의해서 보세요. make는 쉘상에서 정의한 환경 변수값들을 그대로 이용한다는 것을 알고 계시기 바랍니다.
--------------------------------------------------------------------------------
OBJECTS = main.o read.o write.o
SRCS = main.c read.c write.c <- 없어도 됨
CC = gcc <- gcc 로 세팅
CFLAGS = -g -c <- gcc 의 옵션에 -g 추가
TARGET = test <- 결과 파일을 test 라고 지정
$(TARGET) : $(OBJECTS)
$(CC) -o $(TARGET) $(OBJECTS)
clean :
                rm -rf $(OBJECTS) $(TARGET) core
main.o : io.h main.c <- (1)
read.o : io.h read.c
write.o: io.h write.c

--------------------------------------------------------------------------------
그런데 여기서 한가지 이상한 점을 발견하게 될 것이다. .c 파일을 .o 파일로 바꾸는 부분이 없는데 어떻게 컴파일이 되었을까? 빼먹고 타이핑 못한 것은 아닐까 하고... 절대 아님!
앞에서 CFLAGS 같은 매크로는 make 파일의 내부에서 이용된다고 하였다. 그렇다면 make는 과연 어디에서 이용을 할까? 바로 컴파일하는 곳에서 이용을 하는 것이다. 따라서 우리는 CFLAGS를 셋팅해 주기만 하면 make가 알아서 컴파일을 수행하는 것이다. (얼마나 편리합니까!)
=> 참고: 확장자 규칙에서 다시 한번 자세히 설명을 하겠습니다.
(1) 에 해당하는 부분은 어떤 파일이 어디에 의존하고 있는지를 표시해 주기 위해서 꼭 필요하다. .c 파일을 컴파일하는 부분은 일괄적인 루틴으로 작성할 수 있기 때문에 이들 파일간의 의존 관계(dependency)를 따로 표시해 주어야 한다.

>확장자 규칙
여기서 한가지 매크로가 등장하게 된다. .SUFFIXES 라고 하는 매크로인데 우리가 make 파일에게 주의 깊게 처리할 파일들의 확장자를 등록해 준다고 이해하면 될 것이다.
.SUFFIXES = .c .o

--------------------------------------------------------------------------------
.SUFFIXES = .c .o
OBJECTS = main.o read.o write.o
SRCS = main.c read.c write.c
CC = gcc
CFLAGS = -g -c
TARGET = test
$(TARGET) : $(OBJECTS)
                $(CC) -o $(TARGET) $(OBJECTS)
clean :
                rm -rf $(OBJECTS) $(TARGET) core
main.o : io.h main.c
read.o : io.h read.c
write.o: io.h write.c

--------------------------------------------------------------------------------
확장자 규칙에 의해서 make는 파일들간의 확장자를 자동으로 인식해서 필요한 작업을 수행한다. 즉 아래의 루틴이 자동적으로 동작하게 된다.

.c.o :
$(CC) $(CFLAGS) -c $< -o $@
make 내부에서 기본적으로 서비스를 제공해 주는 확장자들의 리스트를 열거해 보면 아래와 같다. 각 확장자에 따른 자세한 설명은 생략한다.
.out .a .ln .o .c .cc .C .p .f .F .r .y .l .s .S .mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch .web .sh .elc .el
Makefile내부에서 .SUFFIXES 매크로의 값을 세팅해 주면 내부적으로 정의된 확장자의 연산이 동작을 하게 된다. 따라서 확장자 규칙은 make가 어느 확장자를 가진 파일들을 처리할 것인가를 정해 주는 것이라고 생각할 수 있다.
그러나 이것은 필자만의 생각일지 몰라도 make에서 자동적으로 확장자를 알아서 해주는 것이 좋긴 하지만, 필자는 일부러 위의 .c.o 에 해당되는 부분을 그냥 정의해서 쓰길 더 좋아한다. 이것은 지금까지의 습관상 그렇지만 왠지 우리가 정의하는 것이 더 자유롭게(flexible) 사용할 수 있을 것 같기 때문이다. 그리고 이런 기능은 우리가 작성을 해봐야 make의 메카니즘을 더 잘 이해할 수 있다고 생각한다.

--------------------------------------------------------------------------------
.SUFFIXES = .c .o
OBJECTS = main.o read.o write.o
SRCS = main.c read.c write.c
CC = gcc
CFLAGS = -g -c
INC = -I/home/raxis/include <- include 패스 추가
TARGET = test
$(TARGET) : $(OBJECTS)
                $(CC) -o $(TARGET) $(OBJECTS)
.c.o : <- 우리가 확장자 규칙을 구현
                $(CC) $(INC) $(CFLAGS) $<
clean :
                rm -rf $(OBJECTS) $(TARGET) core
main.o : io.h main.c
read.o : io.h read.c
write.o : io.h write.c
----------------------------------------------------------
+--SUCCESS---------------------------------------------------------------------
| % make
| gcc -I/home/raxis/include -g -c main.c
| gcc -I/home/raxis/include -g -c read.c
| gcc -I/home/raxis/include -g -c write.c
| gcc -o test main.o read.o write.o <- OK
+-----------------------------------------------------------------------------
> 내부 매크로
--------------------------------------------------------------------------------
$* <- 확장자가 없는 현재의 목표 파일(Target)
$@ <- 현재의 목표 파일(Target)
$< <- 현재의 목표 파일(Target)보다 더 최근에 갱신된 파일 이름
$? <- 현재의 목표 파일(Target)보다 더 최근에 갱신된 파일이름

--------------------------------------------------------------------------------
=> 참고: 책에서는 $< 와 $?를 약간 구분하고 있지만 거의 같다고 봐도 무방할 것입니다.

Makefile의 이해를 돕기 위해서 Makefile을 하나 더 작성해 보기로 한다. make.tex 파일을 make.dvi로 만든 다음 이것을 다시 make.ps로 만드는 것이다. 보통의 순서라면 아래와 같다.

% latex make.tex <- make.dvi 가 만들어진다.
% dvips make.dvi -o <- make.ps 가 만들어진다.

보통의 가장 간단한 Makefile을 작성해 보면 아래와 같다.
Makefile예제 11

--------------------------------------------------------------------------------
make.ps : make.dvi
                dvips make.dvi -o
make.dvi : make.tex
                latex make.tex

--------------------------------------------------------------------------------

위와 같은 일을 하는 Makefile을 다르게 한번 작성해 보자. 매크로를 어느정도 사용해 보기로 하며, 확장자 규칙을 한번 적용해 보기로 한다.
Makefile예제 12

--------------------------------------------------------------------------------
.SUFFIXES = .tex .dvi
TEX = latex <- TEX 매크로를 재정의
PSFILE = make.ps
DVIFILE = make.dvi
$(PSFILE) : $(DVIFILE)
                dvips $(DVIFILE) -o
make.ps : make.dvi
make.dvi : make.tex

--------------------------------------------------------------------------------

예제 12 에서는 .tex 와 .dvi 를 처리하는 루틴이 자동적으로 동작을 하게 된다. Makefile 을 한번 동작시켜 보자.

% make
latex make.tex
....
dvips make.dvi -o <- OK
>확장자 규칙의 이용 II
어떤 파일들이 이미 규칙으로 정해져 있는지 한번 살펴보기로 한다. 아래에 열거된 파일들은 특별히 따로 정의하지 않은 상태에서 바로 이용할 수 있는 것들이다.(GNU Make 매뉴얼에 바탕을 두고 작성되었다.)

C 컴파일 (XX.c -> XX.o)
C++ 컴파일 (XX.cc 또는 XX.C -> XX.o)
Pascal 컴파일 (XX.p -> XX.o)
Fortran 컴파일 (XX.f 또는 XX.F -> XX.o)
Modula-2 컴파일 (XX.def -> XX.sym)
(XX.mod -> XX.o)
assembly 컴파일 (XX.s -> XX.o)
assembly 전처리 (XX.S -> XX.s)
single object file 의 링크 (XX.o -> XX)
Yacc 컴파일(?) (XX.y -> XX.o)
Lex 컴파일(?) (XX.l -> XX.o)
lint 라이브러리 생성 (XX.c -> XX.ln)
tex 파일 처리 (XX.tex -> XX.dvi)
texinfo 파일처리 (XX.texinfo 또는 XX.texi -> XX.dvi)
RCS 파일 처리 (RCS/XX,v -> XX)
SCCS 파일처리 (SCCS/XX.n -> XX)
위에 정의된 파일만이 make에서 처리할 수 있는 것은 아니다. 그 밖의 파일에 대해서는 사용자가 직접 정의해 주면 얼마든지 make를 사용할 수 있다.
그럼 이젠 위와 같은 파일들을 처리하기 위한 명령어는 어떤 매크로로 정의되어 있는지 알아보자. 이미 말했듯이 아래에 열거된 매크로는 재정의 가능하다. 가령 TEX = tex 이지만 대부분 TEX = latex로 재정의 되어야 할 것이다.

AR = ar (Archive maintaining program)
AS = as (Assembler)
CC = cc (= gcc , C compiler)
CXX = g++ (C++ compiler)
CO = co (extracting file from RCS)
CPP = $(CC) -E (C preprocessor)
FC = f77 (Fortran compiler)
LEX = lex (LEX processor)
PC = pc (Pascal compiler)
YACC = yacc (YACC processor)
TEX = tex (TEX processor)
TEXI2DVI = texi2dvi (Texiinfo file processor)
WEAVE = weave (Web file processor)
RM = rm -f (remove file)
이미 두번째 장에서 밝혔지만 위의 명령어에서 사용될 FLAG(옵션)에 정의한 매크로에 대해서도 알아보기로 한다.

ARFLAGS = (ar achiver의 플래그) *
ASFLAGS = (as 어셈블러의 플래그)
CFLAGS = (C 컴파일러의 플래그) *
CXXFLAGS = (C++ 컴파일러의 플래그) *
COFLAGS = (co 유틸리티의 플래그)
CPPFLAGS = (C 전처리기의 플래그)
FFLAGS = (Fortran 컴파일러의 플래그)
LDFLAGS = (ld 링커의 플래그) *
LFLAGS = (lex 의 플래그) *
PFLAGS = (Pascal 컴파일러의 플래그)
YFLAGS = (yacc 의 플래그) *
위애서 '*'표시한 것은 자주 쓰이게 될 플래그이다. 위에서 표시한 여러 가지 매크로들을 무조건 재정의 하라는 배려에서인지, 대부분 값이 설정되어 있지 않다. 가령 C프로그램을 짤 때 CFLAGS를 재정의 해야 할 것이다.

>매크로 치환
매크로를 지정하고, 그것을 이용하는 것을 이미 알고 있다. 그런데, 필요에 의해 이미 매크로의 내용을 조그만 바꾸어야 할 때가 있다. 매크로 내용의 일부만 바꾸기 위해서는 $(MACRO_NAME:OLD=NEW)과 같은 형식을 이용하면 된다.

MY_NAME = Michael Jackson
YOUR_NAME = $(NAME:Jack=Jook)

위의 예제에서는 Jack이란 부분이 Jook으로 바뀌게 된다. 즉 YOUR_NAME 이란 매크로의 값은 Michael Jookson 이 된다. 아래의 예제를 하나 더 보기로 한다.

OBJS = main.o read.o write.o
SRCS = $(OBJS:.o=.c)

위의 예제에서는 OBJS에서 .c가 .o로 바뀌게 된다. 즉 아래와 같다.

SRCS = main.c read.c write.c

위의 예제는 실제로 사용하면 아주 편할 때가 많다. 가령 .o 파일 100개에 .c 파일이 각각 있을 때 이들을 다 적으려면 무척이나 짜증나는 일이 될 것이다.
>자동의존관계 생성
일반적인 make의 구조는 아래와 같이 target, dependency, command가 연쇄적으로 정의되어 있는 것과 같다고 하였다.

--------------------------------------------------------------------------------
target : dependency
                command
                ...

--------------------------------------------------------------------------------

그런데 위에서 command가 없이 타겟과 의존 관계만 표시가 되면 이는 타겟이 어느 파일에 의존하고 있는지 표시해 주는 정보의 역할을 한다. 이런 정보는 Makefile을 작성할 때 없어서는 안되는 부분이다. (이 부분이 없으면, make는 정말 바보처럼 행동합니다.)
그런데 일일이 이런 정보를 만든다는 것은 쉬운 일이 아니다. 파일이 1000개라고 할 때 이것을 어케 다 표시하누...
이런 단조롭고 귀찮은 일을 자동으로 해주는 좋은 유틸리티가 있다. 우선 gccmakedep가 있는지 확인해 보자. gccmakedep는 어떤 파일의 의존 관계를 자동으로 조사해서 Makefile의 뒷부분에 자동으로 붙여 주는 유틸리티이다. gccmakedep가 없다면 gcc -M XX.c 라고 해보자. 그러면 XX.c의 의존 관계가 화면에 출력됨을 알 수 있을 것이다. (gccmakedep 도 내부적으로 gcc -M 을 사용한다.)
프로그램을 설치할 때 make dep 라는 것을 친 기억이 있을 것이다. 파일들의 의존 관계를 작성해 준다는 의미이다. 그럼 우리의 Makefile에도 이런 기능을 첨가해 보기로 한다.
예제 14

--------------------------------------------------------------------------------
.SUFFIXES = .c .o
CFLAGS = -O2 -g
OBJS = main.o read.o write.o
SRCS = $(OBJS:.o=.c)
test : $(OBJS)
                $(CC) -o test $(OBJS)
dep :
                gccmakedep $(SRCS)

--------------------------------------------------------------------------------

위의 Makefile을 이해할 수 있다면 이제 Makefile에 대해서 어느 정도 도가 텄다고 해도 무방할 것이다. 위의 예제에서 파일들간의 의존 관계가 없다. 그럼 이제 make dep 을 써서 자동적으로 생성시켜 보자.

% make dep
% vi(emacs) Makefile

Makefile의 뒷부분에 다음과 같은 내용이 붙어 있는 것을 알게 될 것이다.
예제 15

--------------------------------------------------------------------------------
# DO NOT DELETE
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/libio.h \
/usr/include/_G_config.h io.h
read.o: read.c io.h
write.o: write.c io.h

--------------------------------------------------------------------------------

main.o에는 조금 자질구레한 헤더 파일까지 붙어 있다. 이것은 헤더 파일 안에서 include 하는 파일들을 다 찾다 보니까 그런 것이다. 별로 신경쓸 것은 없고... 대충 우리가 지금까지 손으로 작성해 온 것과 거의 흡사함을 알 수있다. 아니 오히려 더 정확함을 알 수 있다. (이제부터 make는 스마트하게 동작한다.)

댓글

이 블로그의 인기 게시물

[메모] PostgreSQL에서 Insert 하는 경우 자동채번 PK가 중복에러 나는 경우

[C# & LINQ] 랜덤으로 데이터를 한 개 추출하는 방법

[react-native] uuid 생성이 에러가 날 때 대처법