이 시리즈의 원 저자인 Tom Ryder의 허락을 받고 올리는 번역글입니다. IDE가 할 수 있는 기능을 UNIX 계열의 shell 안에서도 원활하게 할 수 있는 비결을 초보자도 알기 쉽게 잘 설명한 글일 뿐만 아니라 UNIX 자체의 철학이나 기본 사용법을 따라잡기에도 굉장히 좋은 글이라 생각되어 우리말로 옮기고자 합니다. 프로그래밍 용어는 웬만하면 원래 영단어로 쓰겠습니다. 언제든지 더 좋은 표현에 대한 의견은 감사합니다.



UNIX as IDE: Building

2012년 2월 13일 Tom Ryder가 작성


프로젝트를 compile하는 것은 꽤나 복잡하고 반복적인 과정이기 때문에 훌륭한 IDE는 프로젝트 build 단계를 추상화, 단순화, 더욱이 자동화시키는 도구를 제공한다. UNIX와 그 후손들은 Makefile을 통해서 이를 수행한다. Makefile은 소스 파일과 object 파일을 통해서 실행 가능한 파일을 만드는 레시피를 정해진 형식에 맞추어 등록하는 곳이다. 또한 파일이 변경된 점을 인식해서 필요한 곳만 다시 build하기 때문에 다시 compile하는 시간을 최소화할 수 있다.


make의 재미있는 점 중 하나는 make가 compile/build 자동화를 위해 주로 쓰이는 동시에, compile이라는 개념에서 벗어나기도 한다는 것이다. 즉 make는 어느 파일들을 통해서 어떤 파일을 만들어내는 어떤 상황에서도 쓰일 수 있다. 대표적인 예로, 웹사이트 배포를 위해서 원본 파일을 통해 웹페이지에 최적화된 그래픽을 생성한다든지, markdown 코드를 통해서 정적인 웹페이지를 생성하는 것을 들 수 있다. 이렇게 소프트웨어 "building"의 개념을 어떠한 코드 및 파일 등을 설치하고 만들어내는 일반적인 자동화의 개념으로 훨씬 유연하게 해석하면서, Ruby의 rake와 같은 현대적인 make 도구들이 대중적으로 받아들여지게 되었다.



Makefile 파헤쳐 보기

Makefile의 일반적인 패턴은 변수를 나열하고, 타겟을 나열하고, 그 타겟을 위한 source/object 파일을 제공하는 것이다. 타겟은 굳이 linked binary 파일일 필요는 없을 뿐더러, 재료 파일들을 이용해서 무엇인가 작업을 하는 행동들로 구성할 수도 있다. 그 예로 install 타겟은 이미 build된 파일들을 시스템에 집어 넣는 작업이 될 수 있고, clean 타겟은 build된 파일을 제거하는 일을 할 수 있는 것이다.


타겟을 유연하게 정의할 수 있다는 점은 소프트웨어의 production build를 구성하는 데 필요한 어떠한 작업이라도 자동화시킬 수 있는 원동력이 된다. 전형적으로 compiler가 수행하는 parsing/preprocessing/compiling/linking 단계 뿐만 아니라 테스트를 수행(make test)한다든지, 문서 소스 파일을 목적에 맞는 형식으로 compile하거나, production 시스템에 배포를 자동화시키는 작업, git push와 비슷한 코드 추적 방법을 통해 웹에 업로드하는 작업을 지정할 수도 있는 것이다.


간단한 소프트웨어 프로젝트의 Makefile의 예를 들어보자.

all: example

example: main.o example.o library.o
    gcc main.o example.o library.o -o example

main.o: main.c
    gcc -c main.c -o main.o

example.o: example.c
    gcc -c example.c -o example.o

library.o: library.c
    gcc -c library.c -o library.o

clean:
    rm *.o example

install: example
    cp example /usr/bin


최적 또는 최고의 Makefile은 아니겠지만, 그래도 위 Makefile은 단순히 make 명령을 호출하는 것만으로도 linked binary 파일을 build 후 install하는 일을 수행할 수 있다. 각 타겟은 작업을 수행하기 위해 스스로 필요로 하는 타겟 리스트를 갖고 있기 때문에 make 호출을 하는 가장 윗 타겟부터 필요한 의존 타겟을 따라가서 필요한 작업을 수행한다. 따라서 타겟들은 특정 순서로 정의가 되어있어야 할 필요가 없게 된다.


위 예제의 상당 부분은 반복적으로 쓰일 필요가 없다. 예를 들어 object 파일이 단 하나의 C 파일로부터 같은 이름으로 만들어진다면, 그 타겟은 아예 Makefile에 정의될 필요도 없다. Makefile이 자동적으로 할 수 있는 일이다. 또한 반복적으로 사용되는 옵션들을 환경 변수로 올리면 여러 옵션을 일괄적으로 적용할 수 있는 구조로 바꿀 수 있다. 조금 더 조금 더 간결하게 개선된 Makefile을 보자.

CC = gcc
OBJECTS = main.o example.o library.o
BINARY = example

all: example

example: $(OBJECTS)
    $(CC) $(OBJECTS) -o $(BINARY)

clean:
    rm -f $(BINARY) $(OBJECTS)

install: example
    cp $(BINARY) /usr/bin


make를 더 폭 넓게 사용하기

자동화에 대해 조금 더 생각해보면 자동화는 단순히 compiling과 linking 수준보다는 넓은 의미에서 생각하는 것이 좋다. 간단한 웹 프로젝트를 예로 들어 생각해보면 PHP 소스 코드를 실제 가동하는 웹 서버에 배포하는 일을 자동화하는 것에 대해 생각해볼 수 있다. 이 배포 과정을 자동화하기 위해 make를 사용해야겠다는 생각이 보통 들지는 않을 수도 있다. 하지만 배포를 위한 소스 코드가 준비되어 있고, 이 재료를 배포하기 위해 타겟을 정하고 이를 수행하기 위한 작업을 한다는 그 원칙은 거의 같다.


PHP 파일에겐 물론 compilation이 필요하지 않지만, 웹 자산들은 종종 필요할 때가 있다. 웹 개발자들에게 친숙한 예로 벡터 소스 파일에서 최적화된 래스터 이미지를 생성하는 것을 들 수 있겠다. 원본 소스 파일을 버전 별로 간직하고 있다가 배포가 필요한 시점에서 알맞는 버전으로 이미지를 생성하는 것이다.


어떤 웹 프로젝트에서 4개의 아이콘이 필요하다고 가정하자. 그리고 그 아이콘은 64×64 픽셀이다. 우리는 SVG 벡터 형식의 원본 파일을 가지고 있고 버전 관리 시스템을 통해 안전하게 잘 간직하고 있다. 이제 배포를 위해서 크기가 작은 비트맵 이미지로 만들 필요가 있어졌다. 이를 위해서 타겟 아이콘을 정의하고, 그 의존 요소들을 정하고, 타겟을 위해 어떤 명령을 실행해야 하는지 써야 한다. 여기서 UNIX의 명령 도구가 Makefile syntax를 통해 빛을 발할 수 있는 순간이 왔다.

icons: create.png read.png update.png delete.png

create.png: create.svg
    convert create.svg create.raw.png && \
    pngcrush create.raw.png create.png

read.png: read.svg
    convert read.svg read.raw.png && \
    pngcrush read.raw.png read.png

update.png: update.svg
    convert update.svg update.raw.png && \
    pngcrush update.raw.png update.png

delete.png: delete.svg
    convert delete.svg delete.raw.png && \
    pngcrush delete.raw.png delete.png


Makefile 작성이 끝나면, make icons 명령이 4개의 원본 파일을 Bash loop으로 돌아보면서 ImageMagick의 convert를 이용해서 SVG를 PNG로 변환시키고 pngcrush로 이미지 최적화를 시켜서 배포에 필요한 이미지를 생성할 것이다.

이와 비슷한 방법으로 다양한 형식의 도움말을 생성할 수도 있다. 아래 예제는 markdown 원본 파일로부터 HTML 파일을 생성하는 Makefile이다.

docs: README.html credits.html

README.html: README.md
    markdown README.md > README.html

credits.html: credits.md
    markdown credits.md > credits.html


마지막으로 git push web을 통해서 웹사이트를 배포한다면, 배포용 아이콘이 만들어지고 도움말이 변환된 후에 해야 할 것이다.

deploy: icons docs
    git push web


지금까지 해왔던 확장자를 바꾸는 작업을 위해 더 간결하고 압축된 방식을 소개하려 한다. .SUFFIXES pragma (compiler directive)를 사용해서 특별한 기호로 확장자를 정의하는 것이다. 아이콘을 변환하는 Makefile 코드는 아래처럼 간단해질 수 있다. $<는 소스 파일, $*는 확장자 없는 파일명, $@는 타겟을 나타낸다.

icons: create.png read.png update.png delete.png

.SUFFIXES: .svg .png

.svg.png:
    convert $< $*.raw.png && \
    pngcrush $*.raw.png $@


Tools for building a Makefile

configure 스크립트를 구성하고 대형 프로젝트를 상위 레벨에서 다룰 수 있도록 하는 make파일을 만들기 위해 GNU Autotools toolchain에서는 다양한 도구를 찾아볼 수 있다. 특히나 autoconfautomake가 대표적인 도구이다. 큰 프로젝트를 구성할 때에는 이러한 도구들을 통해서 기존에 커다란 Makefile를 직접 작성하는 노고를 덜고 다양한 운영 체제에서 호환되고 compile 가능한 소스 코드인지 검사할 수 있도록 자동화하기 위해서 configure 스크립트나 make 파일을 생성할 수 있다.


이에 관한 상세한 설명을 하자면 또 하나의 시리즈가 필요할 정도로 복잡하고 긴 설명이 필요하므로 이번 UNIX as IDE 시리즈에서 다루지는 않겠다.



UNIX as IDE




'GNU-Linux' 카테고리의 다른 글

UNIX as IDE: 7. Revisions  (0) 2017.11.09
UNIX as IDE: 6. Debugging  (0) 2017.11.08
UNIX as IDE: 4. Compiling  (0) 2017.11.06
UNIX as IDE: 3. Editing  (0) 2017.11.06
UNIX as IDE: 2. Files  (0) 2017.11.06