2011년 12월 26일 월요일

향상된 makefile

출처 : http://www.hanb.co.kr/network/view.html?bi_id=351

저자: 제니퍼 베스퍼만, 역 이호재 

이번 기사에서는 꽤 복잡한 makefile을 분석할 것입니다. 이 makefile은 실제 프로젝트에서 사용된 것입니다. 프로젝트에서만 적용된 일반적이지 않은 내용은 생략하고 전반적인 makefile 분석에 대해 다루어 보고자 합니다. 

이 기사는 「make 소개」와 관련된 연작 기사이며 거기서 다룬 주제를 여기서도 다룹니다. makefile에 대한 소개와 간단한 makefile 작성 및 사용법을 알고 싶다면 「make 소개」부터 읽어보시기 바랍니다. 

「make 소개」와 이번 기사 모두 개발자를 위한 make 툴에 대해 다루고 있습니다. 시스템 관리자에게 있어서도 make는 역시 유용한 도구가 될 수 있습니다. makefile에 대해서 살펴보는 동안, 설정 스크립트, 설치 스크립트, 자동 업데이트 도구를 재생성하는데 있어 이러한 기술들을 적용할 수 있는 방법에 대해 생각해 봅시다. 

자신의 파일을 사용하도록 허락해준 미치 데이비스(Mitch Davis)에게 이 자리를 빌어 감사의 말을 전합니다. 수정된 makefile은 필자가 몇몇 라인을 제거 하였기 때문에 정상적으로 동작하지는 않지만, 다양한 테크닉을 알아보기에는 충분할 것입니다. 물론, makefile은 원래부터 많은 사람들이 사용하고 있습니다. 

머릿말 주석
# TODO: Add a section for installation

# Makefile for (the project)
# Version 3.0
# Jennifer Vesperman
# 14 August 2001
#
# Version 4.0
# Mitch Davis
# 10 January 2002
makefile에 헤더를 포함하고 향상시키고자 하는 것을 기록해 놓으면 정말 유용하게 사용할 수 있습니다. 필자의 makefile을 통해 우리가 하고자 하는 일이 무엇인지 알 수 있습니다 . 

위치 정의 


# Set up the compiler directories
xmlc_j:=            /usr/local/lib/xmlc2.0.1/xmlc.jar
install_d:=         /usr/local/apps/jakarta-tomcat-3.2.2/webapps/example
servlet_d:=         $(tomcathome)/lib
class_d:=           $(install_d)/WEB-INF/classes
class_example_d=    $(class_d)/example
# (additional directory definitions snipped.)
컴파일에 필요한 경로는 변수로 설정할 수 있습니다. 이를 통해 설치가 변경되더라도 쉽게 수정할 수 있습니다. 그리고 다른 종류의 컴퓨터에서 설정을 자동으로 변경하기 위해 조건문과 셸 함수를 사용할 수 있습니다. 조건문과 셸 함수는 뒤에서 자세히 다루겠습니다. 


컴퓨터에 종속적인 변수를 설정하기 위해 조건문 사용하기
ifeq ($(shell uname -n),install)
  # For compiling on install machine
  install_d:= /usr/local/jakarta-tomcat-3.2.2/webapps/example
endif
재귀적으로 확장되는 변수 


변수에는 두 가지 종류가 있습니다. 재귀적으로 확장되는 변수 (Recursively expanded variable)와 간단히 확장되는 변수(Simply expanded variable)가 그것입니다. 재귀적으로 확장되는 변수는 이름과 값 사이에 "="를 사용하고 변수가 필요할 때 그 값이 결정됩니다. 이 변수들은 재귀적으로 정의 될 수 없습니다. 왜냐하면 무한 루프에 빠지게 되기 때문입니다. (한번 테스트 해보시고 다음부터는 이런 방법을 사용하지 마십시오.) 함수는 make가 변수 값을 필요로 할 때마다 수행될 것입니다.
  • OK: class_example_d = $(class_d)/example
    (정의문에서 다른 변수를 참조한 경우)
  • Not OK: class_d = $(class_d)/example
    (재귀적 정의: 정의문에서 자신을 참조한 경우)
간단히 확장되는 변수 

간단히 확장되는 변수는 이름과 값 사이에 ":="를 사용하고 make가 이 변수를 만날 때 바로 값이 평가됩니다. 재귀적 정의도 가능하며, 이때 오른쪽에 있는 변수 값은 이 변수의 이전 값이 됩니다. 간단히 확장되는 변수는 GNU make의 고유한 기능이며 다른 버전의 make에는 존재하지 않을 수도 있습니다.
  • OK: class_example_d := $(class_d)/example
  • OK: class := $(class)/example
    (이전에 class가 정의되어 있다면 OK)
환경 변수
# These exports are for xmlc, which is 
# a java program which calls javac.

# javac를 호출하는 xmlc라는 프로그램을 위한 설정

export JAVAC:=  $(java_h)/bin/javac
export JAVA:=   $(java_h)/bin/java
때때로 makefile 내에서 환경 변수를 설정해 주어야 합니다. 이것은 보통 특별 버전의 컴파일러를 사용해야만 하는 프로젝트가 있을 경우에만 그렇습니다. 이때에는 변수 값을 신중히 선택하십시오. EDITOR 변수를 바꾸어서 다른 개발 팀원들을 당혹스럽게 만들 수 있기 때문입니다. 


Make 함수
# Set up the classpaths for Javac

# Javac를 위한 classpaths 설정

classpath:= \
        $(cookie_d)/cookiebuster.jar \
        $(example_j) \
        $(hsqldb_d)/hsqldb.jar \
        $(xmlc_j) \
        $(xml_j) \
        $(jetty_d)/com.mortbay.jetty.jar

# Convert the spaces to colons.  This trick is from 
# the make info file.

# 빈칸을 콜론으로 바꿉니다. 이 트릭은 make info 파일에 있습니다.

empty:=
space:= $(empty) $(empty)
classpath:=     $(subst $(space),:,$(classpath))
classpath 변수는 표준이고 간단히 확장되는 변수입니다. 이 변수의 정의는 여러 라인에 걸쳐 있습니다. 여기서 미치(Mitch)는 path를 구분하는 빈칸을 콜론으로 바꾸기 위해 make 함수를 이용했습니다. 그는 이렇게 정의하는 것이 한 라인에서 콜론으로 구분지어 정의하는 것보다 훨씬 더 알아보기에 좋다고 생각합니다. 물론 classpath 내에 빈공간이 있다고 하더라도 이를 사용해서는 안됩니다.
러닝 리눅스 3판
참고 도서
make에는 유용한 내장 함수들이 많이 있습니다. 함수를 부르는 문법은 $(함수 인자) 입니다. 함수는 함수를 둘러싸고 있는 문장들이 평가될 때 같이 평가됩니다. 간단히 확장되는 변수 안에 있는 함수는 make가 처음으로 그 변수를 만날 때 평가됩니다. 또한 재귀적으로 확장되는 변수 안에 있는 함수는 make가 그 변수의 값을 필요로 할 때 평가됩니다. 그리고 규칙에 있는 함수는 make가 그 부분의 규칙을 실행할 때 평가됩니다. 


조건문
# If there's already a CLASSPATH, put it on the front

# 만약 CLASSPATH가 이미 존재한다면 가장 앞쪽에 붙힙니다.

ifneq ($(CLASSPATH),)
        classpath:=     $(CLASSPATH):$(classpath)
endif

# Re-export the CLASSPATH.

# CLASSPATH를 다시 export합니다.

export CLASSPATH:=$(classpath)
Make에는 조건문이 있으며 종종 변수를 설정하는데 쓰입니다. 사용 할 수 있는 조건문으로는 ifeqifneqifdefifndef가 있습니다. else 절은 옵션이고 인자는 괄호([ ])나 작은 따옴표 또는 큰 따옴표 사이에 올 수 있습니다. 


예제에서는 classpath 환경 변수가 정의 되어 있는지 알아보기 위해 ifneq 보다는 ifdef를 사용하는 것이 더 좋습니다. 

이 makefile에서 사용된 문법은 아래와 같습니다.
ifeq (arg1, arg2) 
        statement/s
else
        statement/s
endif
셸 함수와 기타 make 함수 


Make는 자바 컴파일에 있어서는 이상적인 도구가 아닙니다. 자바 컴파일러는 의존성 분석을 좋아하는데, 이는 make가 오작동을 일으키게 할 수 있습니다. 의존성을 분석할 때, 자바는 소스 파일의 정보로부터 작업을 하는 반면 make는 타겟의 정보로부터 작업을 합니다. 자바 구조상 새로운 소스 파일이 우연히 생길 수 있지만 make는 새로운 소스 파일을 수동으로만 추가할 수 있습니다. 

각각의 새로운 파일이 make 스크립트에 추가될 때 변수 선언으로 추가되는 것이 가장 이상적입니다. 그렇지만 이 프로젝트는 실무에서 행해진 것이기 때문에 make는 소스 디렉토리를 검색해서 타겟 리스트를 자동으로 생성하도록 되어 있습니다. 자바의 특징과 이 프로젝트의 특성상 이는 네 개의 변수와 셸 함수와 make 함를 필요로 합니다. 

이 makefile의 이전 버전에서는 외부 셸 스크립트를 가지고 타겟을 생성했고, 리스트를 make 변수처럼 파일로 임포트했습니다. 그리고 난 후 make 변수를 makefile로 임포트했습니다. 미치는 필자의 makefile을 개선하였기 때문에 타겟은 훨씬 더 깔끔한 방법으로 생성됩니다.
# 소스를 담고 있는 디렉토리를 검색하고
# 생성된 .class 파일에 맞는 이름을 생성합니다.
# example/util/*에 있는 클래스들이 먼저 컴파일되게 합니다.
CLASSFILES_view:=
  $(patsubst %.java,$(class_d)/%.class,
  $(shell find example/util -name '*.java'))

# 유틸이나 뷰가 아닌 소스를 찾습니다.
# grep -v 대신 -apth -prune을 사용하십시요.
# find(1) man page의 예제를 참조하시기 바랍니다.
CLASSFILES_nonview:=    
  $(patsubst %.java,$(class_d)/%.class,
  $(shell find example -path example/util -prune -o 
  -path example/views -prune -o -name '*.java' -a -print))

# xmlc에 의해 .html 파일로부터 생성된 class를 찾습니다.
# 단, mockup 디렉토리에 있는 것은 제외합니다.
CLASSFILES:=    
  $(CLASSFILES_view) $(CLASSFILES_nonview)
VIEWFILES:=     
  $(patsubst %.html,$(class_d)/%.class,$(shell find 
  example -path example/views/mockups -prune -o -name 
  '*.html' -a -print))
이 makefile은 표준 make 함수뿐만 아니라 셸 함수도 포함하고 있습니다. 셸 함수 문법은 $(쉘 명령어)입니다. 이는 셸 함수 결과를 리턴합니다. 이때 엔터는 제거됩니다. 


patsubst 함수는 $(patsubst 패턴, 변경하고자 하는 값, 텍스트)의 문법을 가지고 있습니다. 이 함수는 패턴 룰이 하는 것처럼 % 기호를 사용합니다. % 기호는 패턴과 변경하고자 하는 텍스트에서 일치하는 문자열을 가리킵니다(† 역자 주: %.html을 %.class로 바꾼다는 것은 aa.html일 경우 aa.class로 바꾼다는 의미). 

명령어로서 Makefile 변수와 자동 변수
# .html과 .java 파일을 어떻게 컴파일 할 것인가
htmlcompile=$(XMLC) -d $(class_d) -class $(subst /,.,
  $(basename $<)) $<
javacompile=javac -sourcepath . -d $(class_d) 
  $(filter %.java,$?)
이는 재귀적으로 확장되는 변수들로 변수가 실질적으로 필요할 때 계산됩니다 . 여기서 변수들은 재귀적으로 확장되어야 하는데 이는 변수들이 전제조건 리스트에 의존적인 자동 변수들을 포함하고 있고 룰에서 명령어 문자열로 사용될 것이기 때문입니다.(자동 변수에 대해서는 「make 소개」에 잘 설명되어 있다.) 


$<와 $?는 자동 변수입니다. $<는 첫 전제조건의 이름으로 확장됩니다. $?는 타겟보다 더 새로운 전제조건을 빈칸으로 구분한 리스트로 확장됩니다. 

$(filter %.java,$?) 는 필수적입니다. 왜냐하면 javacompile이 룰에서 사용될 때, 룰의 전제조건 중 하나는 자바 파일이 컴파일되어 들어갈 디렉토리이기 때문입니다. 필터는 디렉토리를 제거하고 .java파일만을 남겨 놓을 것입니다. 

변수 선언에서 make 함수의 사용을 주위해서 살펴보시기 바랍니다. 

패턴 룰
$(VIEWFILES): $(class_example_view_d)/%.class: 
  example/views/%.html $(class_d)
        $(htmlcompile)

$(CLASSFILES): $(class_example_d)/%.class: 
  example/%.java $(class_d)
        $(javacompile)
이 룰의 첫번째 라인은 세 부분으로 구성되어 있습니다. 이는 정적 패턴 룰(static pattern rules)이라 불리는 GNU make의 특별한 룰입니다. 「make 소개」를 보시면 패턴 룰에 대한 소개가 간단하게 되어 있습니다. 정적 패턴 룰의 문법은 다음과 같습니다.
targets: target-pattern: dependency-patterns commands
$(class_example_view_d) 디렉토리에 있는 .class 파일은 example/views/%.html 디렉토리에 있는 .class와 상응하는 .html 파일로부터 생성할 수 있습니다. 하지만 이는 .class 파일이 $(VIEWFILES) 리스트의 일부분일 때만 가능합니다. 파일 타입이 .A인 파일을 .B로 변경하는 방법에는 여러 가지가 있는데 그 선택은 변경하는 파일에 따라 의존적이라면 정적 패턴 룰을 사용하기 바랍니다. 


패턴 룰에서 중요한 부분은 바로 %입니다. 이는 타겟과 필요조건 파일의 같은 부분을 가리킵니다. 

간단한 룰
# Make가 알아야 할 것들
.SUFFIXES : .html .java .class
.PHONY : clean all show_classpath

# 중요 타겟 : make all
all: $(VIEWFILES) $(CLASSFILES)

$(class_d):
        mkdir $@

show_classpath:
        @echo Here is the CLASSPATH passed to javac:
        @echo $$CLASSPATH
# $$ 표기법에 주목

clean:
        -rm -rf $(class_d)
all 타겟은 패턴 룰에 의존적입니다. 다른 룰은 아래와 같은 형식으로 되어 있는 간단한 룰입니다.
target: prerequisites
        command
간단한 룰과 phony 룰은 이미 「make 소개」에서 설명했습니다. 


Caveats과 gotchas
  • 이번 기사에서 소개된 테크닉은 GNU make를 위한 것입니다. 다른 유닉스 시스템의 make 프로그램은 여기서 소개된 모든 기능을 제공하지 않을 수도 있습니다.
  • make 룰은 각 명령어의 처음에 탭을 주어야 합니다. 빈칸은 동작하지 않습니다.
  • make는 타겟으로부터 전제조건으로 거꾸로 동작합니다.
  • 컴파일러가 의존성을 검사하지 않는 언어에서 make는 더 훌륭히 동작합니다.
마지막 한마디 

지금까지 make와 관련된 고급 기술들을 살펴보았습니다. makefiles을 직접 작성할 때에는 간단한 것에서부터 시작하여 한번에 하나씩 새로운 기능을 추가해보기 바랍니다. 

하나의 파일이 다른 파일로부터 생성될 필요가 있는 곳이라면 어디에서든지 make는 사용할 수 있습니다. 소프트웨어 개발 뿐만 아니라 설정 스크립트와 업데이트 스크립트에도 시험삼아 make를 사용해보기 바랍니다. 

관련 링크 

좀 더 자세한 정보를 원한다면 다음을 참조하기 바랍니다.
제니퍼 베스퍼만(Jennifer Vesperman)은 생각하기 좋아하는 몽상가로 부모들은 인정하기 힘들었지만 척추에 실리콘을 붙인채 태어나야 했다. 그녀는 유저이자 옹호자로서 오픈 소스에 기여하고 있다. 현재는 Linuxchix.org의 책임자로 일하고 있다.

댓글 없음:

댓글 쓰기