흠… c로 작성된 코드를 빌드하는 과정에 대해 알아보기 위해 정리한다
C언어 빌드 과정 (Build Process)
GCC란?
GCC(GNU Compiler Collection)는 GNU 프로젝트의 일환으로 개발되어 널리 쓰이고 있는 컴파일러
여기서 말하는 GNU는 GNU’s not UNIX의 재귀약자로, 리처드 스톨먼이 각종 자유 소프트웨어들이 돌아가고 번영할 수 있는 기반 생태계를 구축하기 위해 시작한 프로젝트
GCC는 C만을 지원한 컴파일러였지만 후에 다른 언어들도 컴파일 가능해졌다.
컴파일러란
어떤 프로그래밍 언어로 쓰여진 소스 파일을 다른 언어로 바꾸어주는 번역기
컴파일(Compile) : 어떤 언어의 코드를 다른 언어로 바꿔주는 과정
ex) 사람이 작성한 c언어 코드 -> 기계어
먼저 c로 간단한 프로그램 작성 후 gcc로 빌드 해보자
#include <stdio.h>
int main() {
printf("hello world!");
return 0;
}
gcc hello.c -o hello
hello 실행 파일이 생기고 명령어 실행 시 해당 파일이 실행된다
./hello
# hello world!
자세한 과정에 대해 알아보자
소스 코드를 실행 파일로 만들기 위해선 4단계를 거친다.
- 전처리 단계
- 컴파일 단계
- 어셈블 단계
- 링크 단계
gcc hello.c
명령어를 입력하면 실행 파일이 생성되지만, 각 단계의 파일들은 임시 파일로 생성되었다가 사라진다.
궁금하면 아래 명령어 실행 시 모든 중간파일 확인 가능하다
$ gcc -Wall -save-temps {파일 이름}.c –o {파일 이름}
-W
, -Wall
은 컴파일 시 컴파일 되지 않을 정도의 오류라도 모두 출력하는 옵션
1. 전처리 단계
전처리 된 파일은 {파일 이름}.i
에 저장된다.
#
으로 시작하고, 세미콜론 없이 개행문자로 종료되는 라인을 의미한다.
전처리 지시자 | 기능 |
---|---|
#include | 프로그램 외부 파일을 불러옴 |
#define | 매크로 상수/함수 정의 |
#undef | 정의한 매크로 취소 |
#if ~ (#elif ~ #else ~) #endif | 조건부 컴파일 |
전처리 단계에선
-
주석 제거
- 주석을 제거해 실제 코드만을 대상으로 컴파일
-
매크로 확장
- 소스 코드에 정의된 매크로는 해당 매크로가 사용된 곳에 매크로의 내용을 대체하는 과정을 거침
- 매크로는
#define
지시문을 통해 정의되며, 컴파일러는 소스 코드를 처리할 때 해당 매크로를 그에 대응하는 내용으로 치환
-
포함된 파일을 확장
- 소스 파일에서 다른 헤더 파일을
#include
지시문을 사용해 포함
- 소스 파일에서 다른 헤더 파일을
-
조건부 컴파일
#ifdef
같은 전처리기 지시문을 사용해 소스 코드의 일부를 컴파일하는지 여부 결정
4단계를 거쳐 hello.i
파일이 생성된다.
2. 컴파일 단계
전처리 된 hello.i
파일을 통해 어셈블리어
로 된 hello.s
파일을 생성한다.
3. 어셈블 단계
어셈블리 파일 hello.s
를 기계어로 된 오브젝트 파일 hello.o
파일로 변환한다.
즉, 0과1로 이루어진 2진수 코드로 변환 (바이너리 코드)
4. 링크 단계
작성한 프로그램이 사용하는 다른 프로그램이나 라이브러리를 가져와서 연결
-> 실행 가능한 파일 생성
더 자세한 과정이 궁금하면 아래 포스팅을 참고하자
C Build Process in details - Abdelaziz Moustafa
Make란?
파일 관리 유틸리티
- 반복적인 명령 자동화를 위한 것
- 파일간의 종속관계를 파악해
MakeFile(기술파일)
에 적힌대로 컴파일러에 명령해 SHELL 명령이 순차적으로 실행된다.
즉, 반복적인 명령의 자동화로 인한 단숙 반복 작업 및 재작성 최소화
가 되고,
프로그램 종속 구조를 빠르게 파악할 수 있어 관리가 용이하다.
(또한 gcc -o main.c main.c
같은 실수를 피할 수 있다… ㅋㅋ)
Makefile이란?
Makefile
은 프로그램을 빌드하기 위해make
문법에 맞춰 작성하는 문서이다.
Make파일의 구조는 다음과 같은 구조를 가진다.
- 목적파일(Target) : 명령어가 수행되어 나온 결과를 저장
- 의존성(Dependency) : 목적파일을 만들기 위해 필요한 재료
- 명령어(Command) : 실행 되어야 할 명령어들
- 매크로(Macro) : 코드를 단순화 시키기 위한 방법
기본적으로 목표(target), 의존 관계(dependency), 명령(command)세개로 이루어진기본 규칙들이 계속 나열
CC = gcc # 매크로 정의
target1 : dependency1 dependency2 # 타겟절 : 의존성
command1 # 명령어
command2 # 명령어
target2 : dependency3 dependency4 # 타겟절 : 의존성
command3 # 명령어
command4 # 명령어
Makefile 규칙
작성할 때 규칙은 다음과 같다
- 명령의 시작은 반드시 TAB으로 시작
- make 규칙으로 명령은 TAB으로 시작
- TAB으로 시작하지 않은 명령 절을 만나면 make는 명령 절이 아니라 타겟절로 해석
-
비어있는 행은 무시
-
#
을 만나면 개행 문자를 만날 때 까지 무시한다 -
기술 행이 길어지면
\
을 사용해 이을 수 있다.
target1 : dependency1 dependency2 \
dependency3 dependency4
-
;
은 명령라인을 나눌 때 사용 가능하다 -
종속 항목이 없는 타겟도 사용 가능하다
clean:
rm -rf *.o target1 target2
- 종속 항목이 없기 때문에 명령 절은 바로 수행
- 레이블 사용 (명령 부분에는 어떤 명령이 와도 상관없다.)
target1 : dependency1 dependency2
cp -f file1 file2
gcc -o target1 dependency1 dependency2
gdb target1
- 어떤 명령이라도 알맞게 사용 가능
- 매크로 사용
매크로 사용은 ${...}
, $(...)
, $...
모두 사용 가능
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
- 내장 규칙(Built-in Rule)
Make는 자주 사용되는 빌드 규칙들은 내장을 해서 자동으로 처리
(ex - 소스 파일(.c)을 컴파일해서 Object 파일(.o)로 만들어 주는 규칙)
-
Makefile은 아래에서부터 위로 실행
-
환경 변수
변수 | 설명 |
---|---|
CC | 컴파일러 지정 |
INC | include 되는 헤더 파일의 패스를 추가 |
LIBS | 링크할 때 필요한 라이브러리를 추가 |
CFLAGS | 컴파일에 필요한 각종 옵션을 추가 |
OBJS | 목적 파일 이름 |
SRCS | 소스 파일 이름 |
TARGET | 링크 후에 생성될 실행 파일의 이름 |
clean | makefile실행 후 지울 파일 목록 |
기본적인 makefile 규칙들이고 더 자세한 내용은 아래 글 참고
3. 매크로(Magro)와 확장자 규칙
4. Makefile를 작성할 때 알면 좋은 것들
5. make 중요 옵션 정리
간단한 예시
.SUFFIXES : .c .o --+
CFLAGS = -g |
|
OBJS = main.o \ |
read.o \ | 매크로 정의 부분
write.o |
SRCS = $(OBJS:.o=.c) |
|
TARGET = test --+
$(TARGET): $(OBJS) --+
$(CC) -o $@ $(OBJS) |
dep : |
gccmakedpend $(SRCS) |
new : | 명령어 정의 부분
touch $(SRCS) ; $(MAKE) |
clean : |
$(RM) $(OBJS) $(TARGET) core --+
OBJS=main.o foo.o hello.o
TARGET=app.out
all: $(TARGET)
clean:
rm -f *.o
rm -f $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS)
main.o: foo.h hello.h main.c
foo.o: foo.h foo.c
hello.o: hello.h hello.c
2번째 예시에서
make명령어 입력 시 아래의 결과를 확인 할 수 있다.
➜ test git:(main) ✗ make clean
rm -f *.o
rm -f app.out
➜ test git:(main) ✗ make
cc -c -o main.o main.c
cc -c -o foo.o foo.c
cc -c -o hello.o hello.c
cc -o app.out main.o foo.o hello.o
➜ test git:(main) ✗ ls
Makefile app.out foo.c foo.h foo.o hello.c hello.h hello.o main.c main.h main.o
➜ test git:(main) ✗ ./app.out
main, world!
➜ test git:(main) ✗
MakeFile을 생성해 make명령을 관리하면
- 입력 파일 변경 시 결과 파일 자동 변경을 원할 때 배치 작업 관리
- gcc명령 없이 간편한 컴파일 가능
더욱 자세한건 GNU Make 강좌, GNU make documnet 참고
(실제 예제, make 에러)
CMake란?
빌드 파일을 생성해주는 프로그램
프로젝트의 규모가 커질수록 Makefile
로 유지/보수하는 작업이 힘들어진다.
- 프로그램의 버전을 명시하는 이유로 빌드 시 전에 사용하는 헤더 파일들을 자동 생성하는 경우
- 실행 파일 외에 공유 라이브러리들을 함께 생성 해 빌드 대상물이 여러개인 경우
- 프로젝트에 포함 된 서브모듈(써드파티 프로그램)들이 다단계로 존재하는 경우
- 빌드 전 프로그램에 사용되는 리소스 파일들을 묶어서 가상의 파일 시스템을 만들어야 하는 경우
- 빌드 완료 된 실행 파일로 부터 임베디드 프로세서에 퓨징하기 위한 바이너리를 생성해야 하는 경우
CMake
는 Makefile
보다 추상화된 기술 문법으로 Build step
을 기술하면, 이로부터 Makefile
을 자동으로 생성해준다.
-> CMake를 사용하면 소스코드 - 결과물 사이를 깔끔하게 추상화
해준다.
Build Step
만 잘 구성해 놓으면, 이후에는 소스 파일(*.c)을 처음 추가할 때만 CMakeLists.txt
파일을 열어서 등록
-> 빌드에서 제외하지 않는 한 스크립트 수정이 필요 없다.
CMake
도 Make
처럼 의존성 검사 후 Incremental Build
를 수행하지만,
소스 파일 내부까지 들여다보고 분석해서 의존성 정보를 스스로 파악
헤더파일을 추가하면, 의존성 관계를 자동으로 추적해서 해더 파일까지 추적
즉, CMake
는 Makefile
을 보다 쉽게 기술해주는 일종의 Meta-Makefile
CMake
로 프로젝트를 관리하더라도 최종 빌드는 Make와 마찬가지로 make명령으로 수행한다.
간단한 예제
mac환경에서 brew를 사용해 install한다
brew install cmake
그 후엔 CMake빌드 스크립트인 CMakeLists.txt파일 작성
ADD_EXECUTABLE( app.out main.c foo.c hello.c)
cmake CMakeLists.txt
위 명령어를 실행하면
- 개별 Object파일들 생성 ex)
make main.c.o
make all
,make clean
과 같은 매크로도 모두 정의
cmake CMakeLists.txt 명령은 자동 생성된 Makefile을 삭제하지 않는 한 최초 한 번만 실행하면
Makefile을 실행할 때 CMakeLists.txt파일의 변경 여부를 검사해서 필요한 경우 Makefile을 자동으로 재생성한다.
CMake 내부 동작
- 최초 1회
cmake CMakeLists.txt
명령어 실행 - make명령으로 CMake로 생성한 Makefile 실행
- CMakeLists.txt 파일 변경 여부 검사 -> 변경된 경우
Makefile
다시 생성 후 실행 Makefile
에 정의된 각 Target별로 빌드를 수행- 내부
Build Step
에 따라 cmake명령으로 각 Target을 빌드하는 데 필요한Sub-Makefile
생성 - 이 때 생기는
Sub-Makefile
또한CMakeFiles
디렉토리 내부에 저장되고, 재실행 시 변경 여부 검사 -> 필요 시 재 생성
- 내부
–
Git으로 버전 관리한다면, .gitihnore파일에 다음 4줄을 추가한다.
/CMakeCache.txt
/cmake_install.cmake
/CMakeFiles/
/Makefile
CMake 변수 및 명령어
[CMake 튜토리얼] 2. CMakeLists.txt 주요 명령과 변수 정리 참고하자
여기까지가 대략적으로 이해해야 할 명렁어들이고 더 찾아볼 일이 생기면 CMake 문서가서 읽어보자
CMakeLists.txt 기본 패턴
- CMake 빌드 스크립트를 작성할 때도 Makefile을 비롯한 여타 스크립트와 마찬가지로 상단에는 설정 변수를 정의 하단에서는 이 설정에 따라 빌드 절차를 결정하도록 구성
- 이렇게 하면 빌드 환경이나 구성이 바뀌어서 빌드 스크립트를 수정해야 할 필요가 있을 때, 필요한 부분을 금방 찾아서 고치기 가능
예시
# 요구 CMake 최소 버전
CMAKE_MINIMUM_REQUIRED ( VERSION 2.8 )
# 프로젝트 이름 및 버전
PROJECT ( "andromeda" )
SET ( PROJECT_VERSION_MAJOR 0 )
SET ( PROJECT_VERSION_MINOR 1 )
# 빌드 형상(Configuration) 및 주절주절 Makefile 생성 여부
SET ( CMAKE_BUILD_TYPE Debug )
SET ( CMAKE_VERBOSE_MAKEFILE true )
# 빌드 대상 바이너리 파일명 및 소스 파일 목록
SET ( OUTPUT_ELF
"${CMAKE_PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.out"
)
SET ( SRC_FILES
bar.c
foo.c
main.c
)
# 공통 컴파일러
SET ( CMAKE_C_COMPILER "gcc" )
# 공통 헤더 파일 Include 디렉토리 (-I)
INCLUDE_DIRECTORIES ( include driver/include )
# 공통 컴파일 옵션, 링크 옵션
ADD_COMPILE_OPTIONS ( -g -Wall )
SET ( CMAKE_EXE_LINKER_FLAGS "-static -Wl,--gc-sections" )
# 공통 링크 라이브러리 (-l)
LINK_LIBRARIES( uart andromeda )
# 공통 링크 라이브러리 디렉토리 (-L)
LINK_DIRECTORIES ( /usr/lib )
# "Debug" 형상 한정 컴파일 옵션, 링크 옵션
SET ( CMAKE_C_FLAGS_DEBUG "-DDEBUG -DC_FLAGS" )
SET ( CMAKE_EXE_LINKER_FLAGS_DEBUG "-DDEBUG -DLINKER_FLAGS" )
# "Release" 형상 한정 컴파일 옵션, 링크 옵션
SET ( CMAKE_C_FLAGS_RELEASE "-DRELEASE -DC_FLAGS" )
SET ( CMAKE_EXE_LINKER_FLAGS_RELEASE "-DRELEASE -DLINKER_FLAGS" )
# 출력 디렉토리
SET ( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BUILD_TYPE} )
SET ( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BUILD_TYPE}/lib )
SET ( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BUILD_TYPE}/lib )
# 빌드 대상 바이너리 추가
ADD_EXECUTABLE( ${OUTPUT_ELF} ${SRC_FILES} )
빌드 파일 생성할 때는 build
라는 폴더를 하나 만들어서 cmake ..
를 실행한다.
좋은 자료가 있어서 링크 남김 (나중에 필요하면 또보자)
[CMake 튜토리얼] 1. CMake 소개와 예제, 내부 동작 원리
CMake할 때 쪼오오오금 도움 되는 문서 - luncliff
examples_CMake - jacking75 github
Reference
Compiling a C Program: Behind the Scenes - geeksforgeeks
make와 Makefile - bowbowbow
GNU Make강좌 - 임대영
make Makefile개념 및 사용법 정리
CMake 튜토리얼 1. CMake소개와 예제, 내부 동작 원리
CMake할 때 쪼오오오금 도움 되는 문서 - luncliff
examples_CMake - jacking75 github
Ninja 빌드 시스템의 소개와 hello 예제 - makepluscode
리눅스상 C/C++ 빌드툴 정리.
Ninja document
GN(빌드시스템) - 위키백과
닌자 (빌드 시스템) - 위키백과
모두의 코드
find_package 와 pkg_check_modules에 의한 라이브러리 탐색