rein's world

MSBuild로 suffix rule 흉내내기

Microsoft VisualStudio 2005 / .net Framework 2.0과 함께 배포되기 시작한 빌드 유틸리티로 MSBuild란 녀석이 있다. MS의 악명높은(…) 커맨드라인 빌드 유틸리티인 nmake를 대체하려는 목적도 포함한 녀석이다.

뭐 이 얘기를 다 하려는 것은 아니고 내가 프로그래밍을 시작했던 *nix환경에는 make라는 command-line 툴로 대부분의 빌드 작업이 이루어진다. 그 기능 중에 하나가 suffix rule이란 건데, 파일 확장자를 보고 일련의 법칙을 따라 자동으로 빌드를 수행하는 녀석이다.

간단하게 C source코드를 컴파일하는 규칙1은 이런 식이다.2

.SUFFIXES : .o .c .s
.c.o :
 $(CC) $(CFLAGS) -c $<
.s.o :
 $(AS) $(ASFLAGS) -o $@ $<

C compiler와 assembler를 써서 컴파일 하게 된다.3

nmake에서도 되긴하지만(물론 make수준은 아님), nmake자체가 좀 부족한게 많고 MS 에서도 MSBuild를 권장하는지라 — 사실 nmake는 뭔가 제대로 된 빌드툴이 아니다. MSBuild 쯤은 되야 _현대적인 빌드툴_이 아닐까 make따라잡는데 너무 오래 걸렸다 — MSBuild로 비슷한 구현을 해봤다.

MSBuild에는 batching이란 개념이 있는데, 그 중 가장 간단한 구현으로, 다음과 같은게 가능하다.

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
  <ItemGroup>
    <TextFile Include='*.py'/>
  </ItemGroup>
  <Target Name='All'>
    <MSBuild Projects='build.xml' Targets='Reverse'/>
  </Target>
  <Target Name='Reverse' Inputs='@(TextFile)'
      Outputs='%(TextFile.Filename).rev'>
    <Exec Comand='python reverse.py @(TextFile)'/>
  </Target>
</Project>

오늘 회사에서 쓰는 코드 생성기 수정하다 나온 스크립트…의 변형판인데. 입력->출력으로 가면서 .py를 .rev로 바꾸고, 내용이 수정되는 그런 의미로 썼다. .suffix rule이라면 .py .rev 정도의 모양이 될 것이다.

그리고 .suffix rule 처럼 모든 python파일에 대해 동작하도록, ItemGroup과 Include문에 wildcard (*)를 사용했다.4 그리고 Targets에 Inputs/Outputs attributed을 지정해 주고(batching), 이름이 바뀌는 규칙을 부여했다. 그리곤 실행해주면 끝.

실제 사용되는 빌드 스크립트는 원래 nmake기반이었는데, 경로 관리, 출력파일에 대한 의존성 정리같은게 너무 복잡해져서 (입력보다 출력이 좀 많아서), 아예 python코드에 make의 시간 비교 기능을 넣어버리고 MSBuild의 batching5 으로 Exec task를 만들고, 이걸로 python을 실행하게 했다..

Python 코드에 make의 기능 일부가 중복되서 들어간건, 출력이 더 많아서였는데(덕분에 nmake가 너저분한 코드로), 이걸 python에 살짝 떠 넘기고, suffix 규칙 비슷한걸 흉내내서 상당히 간단하게(위 xml 설정이랑 거의 똑같음) 만들어낼 수 있었다.

MSBuild는 올해 초에야 제대로 쓰기 시작한 거 같은데, 이 작업으로 nmake기반의 빌드는 다 없어진듯… 다만 문서화는 아직 .bat인데, 이건 아직 안복잡(?)하니 천천히 신경써야지;


  1. 대부분의 경우 내장되어 있다. ↩︎

  2. 이건 2개의 규칙이다. C -> Object code, Object code -> a.out–like format (coff, elf, …). ↩︎

  3. $<는 자동을 치환된는 것이고 $@이 최종 출력에 해당한다. ↩︎

  4. TextFile은 내가 정의한 태그다. Include는 attribute지만 미리 정의되어있다. ↩︎

  5. 같은 명령을 반복하게 하거나(특정 컬렉션에 대해), 두 개의 집합의 Cartesian product에 대해 순회하면서 특정 연산을 하게 할 수 있다. ↩︎