안녕하세요 이번에는 리눅스 환경에서 Makefile이라는 걸 사용해보려고 합니다.
그전에 컴파일 과정을 미리 알고 가면 좋을 거 같아서 사진을 하나 가져왔습니다.
먼저 인간이 이해할 수 있는 코드를 작성한걸 소스파일이라고 하고 이를 컴파일러(리눅스에서는 gcc가 해줍니다.)를 통해 기계가 이해할 수 있는 언어 오브젝트 파일(*. o)을 만들어줍니다. 그리고 링커를 통해 기계어와 라이브러리를 묶어서 실행파일(*. out)을 만들어줍니다. 이러한 과정이 컴파일 과정이라고 하네요.
make를 쓰는 이유는 대표적으로 3가지 이유가 있다고 합니다.
1. 반복되는 컴파일 작업이 지겹고 시간이 오래 걸려서
2. 수정된 파일만 컴파일할 수 있어서
3. 대규모 프로젝트, 공동 프로젝트에서 반드시 필요하다.
그래서 예제를 한번 만들어보려고 하는데요 먼저 main.c main.h kor.c usa.c 4개의 소스파일을 작성하고 전 게시글에 올린 것처럼 gcc명령어를 사용하여 컴파일을 해보겠습니다.
make라는 디렉토리를 만들고 거기에 위의 사진처럼 4개의 소스파일을 작성해봤습니다. 정상적으로 컴파일된다면 실행파일에서 간단한 printf()가 출력되는 걸 확인할 수 있겠습니다.
위의 사진을 보면 gcc -c 명령어를 통해 컴파일했더니 main.o kor.o usa.o라는 오브젝트 파일이 만들어진 걸 확인할 수 있습니다.
그리고 gcc -o 명령어를 통해 app.out라는 실행파일이 만들어진 걸 확인할 수 있었고 마지막으로 실행파일을 실행시켜봤더니 정상적으로 출력되는 걸 확인할 수 있습니다.
위 사진처럼 rm -f app.out *. o 명령어를 사용해서 app.out 실행파일과. o형식의 파일을 모두 지워보겠습니다.
그리고 바로 gcc -o 명령을 주고 확인해봤더니 오브젝트 파일 생성은 생략하고 바로 실행파일이 만들어진 걸 확인할 수 있습니다. gcc가 이러한 과정도 지원해준다고 하니 편하네요
이제 위 사진과 같이 Makefile을 작성해봅시다.
Makefile의 기본 구성을 위의 사진과 같습니다. 먼저 만들고 싶은 개념을 TARGET이라고 하고 TARGET을 만들기 위해 필요한 조건이 DEPENDENCY라고 합니다. 마지막으로 command를 써서 조건을 만족시키면 TARGET이 결과적으로 만들어진다. 정도로만 이해하면 됩니다.
위 사진의 첫째 줄을 보면 app.out이 (TARGET), main.o kor.o usa.o가 타겟을 만들기 위한 조건(DEPENDENCY)
밑에 조건을 충족시키기 위한 명령어 gcc -o app.out main.o kor.o usa.o (command)
같은 논리로 *. o 만들기 위해 gcc -c *. c 표현했다는 걸 알 수 있습니다.
매크로 개념을 알면 이해하기 쉬운 것 같습니다.
위 Makefile을 저장하고 make라는 명령을 주게 되면 main.o kor.o usa.o라는 타겟이 만들어지고 마지막으로 app.out이라는 타겟이 결과적으로 만들어질 겁니다.
위 사진을 보면 make가 설치돼있지 않다고 메시지가 뜨네요 ㅋㅋ sudo apt-get install gcc make를 통해 설치해줍시다.
설치 후에 make를 해보니 정상적으로 오브젝트 파일과 실행파일이 만들어진 걸 확인할 수 있습니다.
이제 원리를 알았으니 Makefile을 좀 더 엔지니어스럽게 꾸며봅시다.
위 사진처럼 첫 번째 줄에 all : app.out을 작성했습니다. (all: 바이너리 이름 -> 결과적으로 만들고 싶은 옵션)
make는 Makefile을 순차적으로 읽어서 가장 처음에 나오는 규칙을 수행하게 됩니다.
all 옵션이 없고 >> app.out: app.out 이 가장 밑줄에 있으면 가장 위에 있는 타겟(main.o)만 실행시키고 종료해버립니다.
그래서 app.out을 가장 위에 있거나 all 옵션을 써서 순서 관련 없이 결과적으로 만들어주게 작성해주는 게 정석입니다.
다음으로 반복되는 부분을 변 수화시켜서 나중에 수정할 일 있을 때 환경 변수만 수정할 수 있게 함으로써 불편함이 없게 해 보겠습니다.
저는 CC라는 환경변수를 만들고 gcc라고 선언하겠습니다. 그리고 gcc로 작성돼있는 부분을 $(CC) 바꿔줬습니다.(app.out 쪽 gcc도 바꿨습니다... 캡처를 못했네요)
다음으로 TARGET이라는 환경변수를 만들고 app.out이라고 선언하고 $(TARGET)로 바꿔줍니다.
다음으로 OBJS = main.o kor.o usa.o 선언 후 $(OBJS) 바꿔주기
다음으로 위 왼쪽 사진의 초록 박스와 노랑 박스처럼 불필요하게 두 번씩 작성돼있습니다. 저는 오른쪽 사진처럼 바꾸었습니다. $@은 TARGET을 의미하고 $^은 DEPENDENCY를 의미해서 이렇게 바꿔도 같은 걸로 해석된다고 합니다.
다음으론 만약에 소스파일을 또 만들게 되면 오브젝트 파일을 만들기 위한 코드를 Makefile에서 몇 줄 더 작성해야 하는 불편함이 있기 때문에 오브젝트 파일을 만드는 코드도 줄여보겠습니다.
위 사진처럼. c.o:으로 타겟을 생성하면 make파일에 위치한 공간에 c파일들을 o파일들로 바꿔준다고 합니다.
command는 $(CC) -c -o $@ $< 이렇게 작성하면 $(CC) -c 까지는 이전과 동일하고 -o $@ $< 에서 $@는 아까 타겟이라고했으니 오브젝트 파일, $<은 디렉토리내의 *. c파일을 의미한다고 합니다.
마지막으로 CFLAGS = -Wall를 작성하는데 CFLAGS는 컴파일할 때 옵션을 줄 수 있습니다. 만약에 컴파일할 때 Warning이 있을 때 전부 띄워주는 기능을 하고
LDFLAGS는 링크할 때 참조되는데 보통 라이브러리가 들어갑니다. 저는 현재 C 라이브러리만 사용하므로 -lc로 작성했습니다.
clean이라는 것도 있습니다. 위 사진처럼 작성하고 make clean이라는 명령어를 주게 되면 오브젝트 파일과 실행파일을 지울 수 있는 매크로를 추가해줬습니다.
최종 저장 후 make를 하니 정상적으로 만들어지고 make clean 기능까지 잘 되는 걸 알 수 있습니다.
위 사진은 make의 장점 중 하나입니다. touch main.c 명령을 주어 main.c의 타임스탬프를 건들고 make를 하니 이전에 make 작업과 달리 main.c만 컴파일하는 걸 볼 수 있습니다. 이를 통해 내가 수정한 소스파일만 컴파일해주는 걸 알 수 있죠 나중에 프로젝트가 커지고 파일량이 많아질 때 효율적이다고 볼 수 있습니다.
'Linux Embedded' 카테고리의 다른 글
우분투 리눅스 듀얼부팅을 해보자 (0) | 2021.12.07 |
---|---|
우분투에서 C코딩을 해보자 (0) | 2021.11.01 |
Windows 10에 Ubuntu를 설치해보자 (0) | 2021.10.26 |
리눅스를 시작하면서 (1) | 2021.10.25 |