본문 바로가기

4.개발 및 운영 환경/빌드

cmake에서 정적 라이브러리를 하나로 합치기

아래 내용은 cmake를 하면서 여러 정적 라이브러리를 하나로 합쳐서 배포할 때 사용한 방식이다. 왜 이렇게 했을까? 처음에는 필요한 라이브러리만 링크할 때에 사용하려는 것이다. 실제로는 이들 라이브러리 간에 종속성도 발생하여 링크 순서도 주의가 필요하다. 그리고 배포할 때에도 파일이 여러 개가 되므로 관리하는데 신경쓸 부분이 많다.
중요한 것은 라이브러리 크기도 크지도 않은데 여러 개로 나눈 점이다. 결국 하나로 배포 관리하고 링크하는데 더 좋다고 판단했다.
그렇다고 해서 cmake에서 이런 작업이 쉽게 한 큐에 되지 않는다는 점! ㅡ.ㅡ;;;

작성자: http://ospace.tistory.com/,2013.09.24 (ospace114@empal.com)

기본 환경

먼저 작업을 하기 전에 다음과 같이 프로젝트가 구성되었다고 가정하자.

여러 개의 서브프로젝트로 존재하고 프로젝트 명은 아래와 같다.
- sub1
- sub2
- sub3

각 서브 프로젝트를 각각 정적 라이브러리를 생성하면 라이브러리 명은 아래와 같다.

  • mysub1
  • mysub2
  • mysub3

상위 프로젝트에서 위의 3개 서브 프로젝트의 라이브러리를 하나의 정적 라이브러로 합친다.

  • 라이브러리명: my

서브 프로젝트 명과 디렉토리 명은 같다. 그리고 각 서브 프로젝트의 라이브러리 명도 일정하게 유지하도록 했다. 만약 서브 프로젝트 마다 프로젝트 명과 디렉토리와 라이브러리 명이 제각기 다르다면 작업이 힙들어 진다. 즉, 일일이 맞춰줘야하기 때문이다. 이정도는 정리해줘야 추후 새로운 서브 프로젝트가 추가되어도 작업이 간편해진다.

그러면 cmake에서 해당 서브 프로젝트를 아래와 같이 포함시키면 된다.

add_subdirectory(sub1)
add_subdirectory(sub2)
add_subdirectory(sub3)

방법1: SET_TARGET_PROPERTIES 사용

cmake에서 라이브러리를 하나로 합치는 방법은 몇가지가 있다. add_library()와 target_link_libraries()를 사용할 수 있는데 이는 사용법이 간단하지만 실제로는 라이브러리 파일은 별도로 존재한다. 이는 링크할 때 하나의 라이브러리를 통해서 사용하는데 도움을 줄 뿐이다.
그래서 cmake 2.8.8에서 add_library()에서 OBJECT라는게 추가되었다. 실제로 2.8.9에서 테스트해보았는데 잘 되지 않는다. 혹이 이 방식에 관심이 있다면 아래 참조[1]을 보면 된다.
그리고 특정 플랫폼에 한정 된다면 해당 플랫폼에서 정적 링크 라이브러리 합치는 라이브러리 툴을 사용하면 된다.
여기에서 필자가 설명하는 방식은 다중 플랫폼을 지원하기 위해 cmake 자체의 기능을 최대한 활용한 add_library()와 set_target_properties()를 사용한 방식이다.
먼저 dummy.c라는 빈 파일을 생성해야 한다. add_library()의 형식을 맞추기 위한 것이다.

set(SUB_LIBS sub1 sub2 sub3)
add_library(my static dummy.c)
foreach(LIB_ITEM ${SUB_LIBS})
  set(LIB_LIST "${LIB_LIST} ${LIB_ITEM}/${CMAKE_STATIC_LIBRARY_PREFIX}my${LIB_ITEM}${CMAKE_STATIC_LIBRARY_SUFFIX}")
  add_dependencies(my my${LIB_ITEM})
endforeach()
set_target_properties(my properties static_library_flags "${LIB_LIST}")

이 방법은 특정 플랫폼(리눅스)에서는 유효하지 않다. 즉, 잘 합쳐지지만 라이브러리로서 기능을 상실한다. 실제로 리눅스에서 여러 라이브러리 들을 하나의 라이브러리로 합칠려면 기존 라이브러리를 오브젝트로 풀고(ar x 명령) 다시 라이브러리로 합치는 과정(ar rc 명령)이 필요하다.
그래서 위의 방법으로 생성된 라이브러리를 사용하면 링크오류가 발생한다. 또한 라이브러리의 심볼 정보를 확인해보면 "file format not recognized"라는 메시지가 보인다. 제대로 합쳐지지 못했다는 말이다.
그렇기에 리눅스에서는 아래 방법2가 필요하다.

방법2: ADD_LIBRARY()의 OBJECT 사용

이번 기능은 버전 2.8.8에 추가된 것으로 이하 버전에서는 지원하지 않는다는 점을 주의해야 한다.
ADD_LIBRARY()에서 OBJECT라는 키워드가 추가되었다. 방법1에서 라이브러리를 하나로 합치기 위해서 object들을 분리해내었다. 여기서는 이런 object 들을 분리할 필요 없이 object 라이브러리를 생성한다. 이 라이브러리는 일반 라이브러리 처럼 합쳐진 상태가 아니고 링크도 불가능하다.
대신에 다른 add_library()이나 add_executable()에서 사용할 수 있다. 이 때에는 아래와 같이 특수한 표기법으로 기술해야 한다.

$<TARGET_OBJECTS:이름>

예를 들어

add_library(... $<TARGET_OBJECTS:이름> ...)

그럼 앞에 가상 프로젝트 환경에 적용해보자.
서브 프로젝트에서 라이브러리 생성하는 방법이 달라진다. 기존 add_library()에서 OBJECT가 추가된다. 각 프로젝트 별로 아래와 같이 add_library()가 변경될 것이다.

sub1
add_library(mysub1 OBJECT <src>)
sub2
add_library(mysub2 OBJECT <src>)
sub3
add_library(mysub3 OBJECT <src>)

상위 프로젝트에서 빌드 스크립트는 아래처럼 사용할 수 있다.

set(SUB_LIBS sub1 sub2 sub3)
foreach(LIB_ITEM ${SUB_LIBS})
  set(LIB_LIST ${LIB_LIST} $<TARGET_OBJECTS:my${LIB_ITEM}>)
endforeach()
add_library(my static ${LIB_LIST})

방법1보다 많이 간결해졌다. 생성된 라이브러리로 빌드하면 성공적으로 수행이 된다.

결론

cmake에서 정적 라이브러리를 합치는 방식을 살펴보았다.
방법1은 멀티 플랫폼에서 적용하기 문제점이 있지만 오래된 버전에 적용할 수 있다는 점이다. 그래서 어떤 빌드 스크립트에서는 리눅스(유닉스 포함)환경에서는 직접 ar 명령어를 사용하고 라이브러리를 합치고 그외에는 방법 1을 적용한 경우도 있다. 이런 방법은 조금이라도 라이브리 생성에 문제가 있다면 플랫폼 별로 구현해야하는 귀찮은 문제점이 발생한다.
방법2는 멀티 플랫폼 지원이 좋다(?) 단, 2.8.8 이상 버전을 사용해야하는 문제점이 있다. cmake 버전을 올릴 수 있는 환경이라면 방법2를 사용하는 좋을 지도....
위 버전은 아직 다중 플랫폼에 대해서 미흡한 부분이 많다. 플랫폼에 대한 영향도 좀더 살펴볼 필요가 있다. 여러 플랫폼에서 제대로 수행되었는지 검증하지 못한 부분도 있기에 다른 환경에서 어떤 문제점이 발생할지 아직 확인하지 못했다. 잘 되길 기도할 뿐이다. ㅡ.ㅡ;;;
추후 시간이 되면 다시 한번더 검증하여 업데이트하려고 하지만 언제 될지.. ㅉㅉㅉ
모드 즐프~ ospace.

참조

[1] CMake/Tutorials/Object Library, http://www.cmake.org/Wiki/CMake/Tutorials/Object_Library

[2] Linking multiple static .lib files into one monolithic .lib file using VS2008 SP1 using CMake 2.8.x, http://stackoverflow.com/questions/4415460/linking-multiple-static-lib-files-into-one-monolithic-lib-file-using-vs2008-sp

[3] CMake 2.8.8 Documentation, http://www.cmake.org/cmake/help/v2.8.8/cmake.html#command:add_library

반응형