Search
Duplicate

20210125(월)

내용
42) Make 와 Makefile
공부장소
2021/03/20 18:22
Shell에서 컴파일을 할 때, make 명령어를 활용해서 컴파일을 할 수 있다. Makefile이 디렉토리에 있다면 해당 디렉토리에서 make 명령어만 치면 컴파일이 실행이 된다.
프로그램들과 달리 Shell에서 컴파일을 하려면 어떤 파일들을 컴파일 하고, 어떠한 방식으로 컴파일 할 지 직접 컴파일러에게 알려줘야 한다.
예를들어 아래와 같다.
$gcc a.c b.c
Shell
복사
하지만 컴파일 해야하는 파일이 엄청 많아진다면 이것들을 매번 치기도 어려울 것이다. 따라서 이러한 문제들을 해결하기 위해 make 라는 프로그램을 제공하는 것이다.
여기서 make는 파일 관리 유틸리티 이다.
파일 간의 종속관계를 파악하여 Makefile( 기술파일 )에 적힌 대로 컴파일러에 명령하여 SHELL 명령이 순차적으로 실행될 수 있게 합니다.

make를 쓰는 이유

각 파일에 대한 반복적 명령의 자동화로 인한 시간 절약
프로그램의 종속 구조를 빠르게 파악 할 수 있으며 관리가 용이
단순 반복 작업 및 재작성을 최소화

컴파일(Compile)

컴파일(Compile)이라는 과정은 바로 소스 코드를 컴퓨터가 이해할 수 있는 어셈블리어로 변환하는 과정이다.
gcc 에 전달하는 -c 명령어는 다음에 오는 파일을 컴파일해서 목적 파일 (Object file) 을 생성하라는 의미이다. 아래의 코드를 보자.
$gcc -c main.c
Shell
복사
이 코드를 실행하면
$ls main.c main.o
Shell
복사
이처럼 main.o 파일이 생성된 것을 알 수 있다. 이 main.o 파일은 main.c 파일을 컴파일 한 어셈블리 코드가 담겨있는 파일 입니다.
여러 파일을 컴파일하는 경우 아래의 사진과 같이 진행된다고 보면 된다.

링킹(Linking)

링킹이 이름 그대로 링크 하는 작업 인 이유는 실제로 서로 다른 파일에 흩어져 있던 함수나 클래스들을 한 데 묶어서 링크해주는 작업이기 때문이다. 이 과정에서 main 함수 안에 foo 함수가 어디에 정의되어 있는지 위치를 찾고 제대로 된 함수를 호출할 수 있게 된다.
$gcc a.o b.o c.o -o test
Shell
복사
여기서 -o 옵션은 뒤에오는 이름으로 실행파일을 만든다. 만약 이 옵션을 넣지 않는다면 디폴트로 a.out이라는 파일이 생긴다.
.c 파일을 .o파일로 변환하는 과정을 컴파일이라고 하고 만들어진 목적 파일들을 엮어서 실행파일로 만들어 주는 과정을 링킹이라고 한다.
이제 이러한 과정을 make를 이용해서 컴파일 해 볼 것이다.

make를 이용한 컴파일

make를 사용해서 컴파일 하기 위해서는 먼저 makefile이 있어야 한다.
makefile을 작성할 때 필요한 요소들을 살펴보자.
목적파일(target)
명령어가 수행되어 나온 결과를 저장할 파일
의존성(dependency)
목적 파일을 만들기 위해 필요한 재료
명령어(recipes)
실행 되어야 할 명령어들
매크로(macro)
코드를 단순화 시키기 위한 방법

Makefile의 기본 구조

아래의 Makefile을 보고 위의 요소들이 어떻게 작성되는지 살펴 보자.

Makefile의 작성 규칙

[target] : [dependency 1] [dependency 2] ... [command 1] [command 2]
Shell
복사
명령어(command)의 시작은 반드시 으로 시작한다.
의존성(dependency)가 없는 목적파일(target)도 사용 가능하다.

make파일 직접 작성해보기

아래는 make파일 연습을 위한 기본적인 소스코드들이다.
ab.h
# include <stdio.h> void a(); void b();
C
복사
a.c
#include "ab.h" void a() { printf("this is a.c\n"); }
C
복사
b.c
#include "ab.h" void b() { printf("this is b.c\n"); }
C
복사
main.c
#include "ab.h" int main(void) { a(); b(); return (0); }
C
복사
이제 코드들이 다 완성되었으니 Makefile을 만들어 보자!!
Makefile
CC = gcc test : a.o b.o main.o CC a.o b.o main.o -o test a.o : a.c CC -c a.c -o a.o b.o : b.o CC -c b.c -o b.o main.o : main.c CC -c main.c -o main.o clean : rm -rf *.o rm -rf test
Makefile
복사
이제 make 명령어를 사용해서 컴파일을 해보자!!
ls 명령어를 사용해서 실행파일과 목적파일들이 잘 생성되었는지 확인해보자.
모든 목적 파일들과 실행파일인 test가 잘 생성되었음을 알 수 있다. 이제 test파일을 실행시켜서 우리가 원하는 결과가 나오는지 확인해보자.
결과도 기대한대로 나온걸 알 수 있다.

Makefile 개선하기! Macro 사용(1)

위의 예제에서도 CC = gcc 를 사용하면서 Macro에 대한 감을 익혔을 것이다. 이제는 조금 더 많은 매크로를 활용해서 코드에서 반복적인 요소들을 줄이고 가독성을 높여보자!
작성 규칙
매크로를 참조 할 때는 소괄호나 중괄호 둘러싸고 앞에 ‘$’를 붙인다.
탭으로 시작해서는 안되고 , :,=,#,”” 등은 매크로 이름에 사용할 수 없다.
매크로는 반드시 치환될 위치보다 먼저 정의 되어야 한다.
Makefile
CC = gcc CFLAGS = -Werror -Wall -Wextra TARGET = test $(TARGET) : a.o b.o main.o $(CC) $(CFLAGS) a.o b.o main.o -o $(TARGET) a.o : a.c $(CC) $(CFLAGS) -c a.c -o a.o b.o : b.o $(CC) $(CFLAGS) -c b.c -o b.o main.o : main.c $(CC) $(CFLAGS) -c main.c -o main.o clean : rm -rf *.o rm -rf test
Makefile
복사
CFLAGS = -Werror -Wall -Wextra
TARGET = test

Makefile 개선하기! Macro 사용(2)

CC = gcc CFLAGS = -Werror -Wall -Wextra TARGET = test OBJECTS = a.o b.o main.o all : $(TARGET) $(TARGET) : $(OBJECTS) $(CC) $(CFLAGS) $(OBJECTS) -o $(TARGET) clean : rm -f $(OBJECTS) $(TARGET)
Makefile
복사
조금 더 많은 매크로를 활용하니 코드가 훨씬 간단해졌다.
위의 코드를 순차적으로 해석해 보자.
1.
gcc 컴파일러 사용
2.
오류들 출력
3.
최종 타겟 파일은 test
4.
OBJECTS로 정의할 파일들은 a.o b.o main.o
5.
all은 타겟이 여러개일 때 유용
6.
타겟 파일을 만들기 위해 OBJECT 들을 사용한다. ( 단 OBJECT 파일이 없다면 OBJECT 파일과 이름이 동일한 C파일을 찾아 OBJECT파일을 생성한다. )
7.
gcc -Werror -Wall -Wextra a.o b.o main.o -o test 와 동일

TARGET names

make에서 자주 사용하는 가장 일반적인 Target names은 아래와 같다.
all: the name of the default target
check: runs tests, linters, and style enforcers
clean: removes files created by all
fclean: removes files created by all and target file
re: call fclean and all

.PHONY target

포니 타겟은 실제 파일 이름을 나타내는 것이 아니다. 이것은 make 요청을 하는 경우에 실행되는 명령을 위한 목적으로 사용된다. 포니 타겟을 사용하는 이유는 두가지이다.
1) 동일한 이름의 파일이 있는 경우 출돌을 피하기 위해서
2) make의 성능 향상을 위해서
clean : rm -f $(OBJECTS) $(TARGET)
Makefile
복사
위의 clean이라는 명령어를 보자. rm 명령은 clean 이라는 이름의 파일을 생성하지 않기 때문에, 그런 파일은 존재하지 않을 것이다. 그래서 rm 명령은 make clean 을 수행할 때, 매번 실행될 것이다.
하지만, 타겟 clean 은 다른 누군가가 디렉토리에 clean 이라는 이름의 파일을 생성하게 된다면, 작업을 멈추게 될 것이다. clean 파일은 이전에 존재하지 않았기 때문에, 필연적으로 최신이라고 간주될 것이다. 그리고 rm 명령은 실행되지 않을 것이다. 이러한 문제를 해결하기 위해서, 다음과 같이 특수 타겟 .PHONY를 사용해서, 해당 타겟을 명백하게 가짜로(phony) 선언할 수 있다.
.PHONY : clean
Makefile
복사
이렇게 되면 make clean명령은 clean이라는 파일이 존재하는지 여부와 상관 없이 무조건 명령을 수행할 것이다.
Clark Grubb의 Makefile style guide에서는 아래와 같은 룰을 추천한다.
All phony targets should be declared by making them prerequisites of .PHONY.
add each phony target as a prerequisite of .PHONY immediately before the target declaration, rather than listing all the phony targets in a single place.
No file targets should be prerequisites of .PHONY.
phony targets should not be prerequisites of file targets.
예를들면 이런 코드이다.
.PHONY: all all: echo "Executing all ..." .PHONY: of of: echo "Executing of ..." .PHONY: my my: echo "Executing my ..." .PHONY: rules rules: echo "Executing rules ..."
Makefile
복사
한 줄에 여러 PHONY target을 작성하여도 문제는 없다.
.PHONY: all of my rules
Makefile
복사
하지만 관리할 파일들이 많아지고 코드 라인이 길어지면 가독성이 떨어질 수 있기 때문에 한 줄씩 나눠서 PHONY target을 알려주는 것이 좋다고 생각된다.

Makefile 다양한 옵션들

헤더파일들을 따로 뽑는 경우
#헤더파일 경로 INCLUDE = -I[파일경로]
Makefile
복사
현재 디렉토리아래에 헤더파일이 있다면 아래와 같이 표현할 수 있다.
CC = gcc CFLAGS = -Werror -Wall -Wextra TARGET = test SOURCES = a.c \ b.c \ main.c OBJECTS = $(SOURCES: %.c=%.o) INCLUDE = -I./ all : $(TARGET) $(TARGET) : $(OBJECTS) $(CC) $(CFLAGS) $(OBJECTS) $(INCLUDE) -o $(TARGET) clean : rm -f $(OBJECTS) $(TARGET)
Makefile
복사
OBJECTS = $(SOURCES: %.c=%.o)의 의미
OBJECTS는 SOURCES를 target name으로 가지는 .c파일들의 이름과 같은 .o파일을 target file로 가진다. 여기서는 a.o, b.o, main.o가 될 것이다.

참고 사이트