Dan Nicholson
https://people.freedesktop.org/~dbn/pkg-config-guide.html
본 문서는 개발자와 사용자 양쪽에서 pkg-config
툴을 사용하는데 있어 큰 그림을 제시하고자 한다. 우선 pkg-config
툴이 내포하는 개념을 설명하고 프로젝트가 제공할 pkg-config
파일을 어떻게 작성하는지, 작성된 pkg-config
파일을 어떻게 프로젝트에 가져다 사용하는지 알아보려 한다.
pkg-config
에 대한 자세한 내용는 https://pkg-config.freedesktop.org 이나 pkg-config(1)
man 페이지에서 볼 수 있다.
본 문서는 pkg-config
툴을 리눅스나 유닉스 계열의 운영체제에서 사용하는 것을 가정한다. 다른 운영체제나 플랫폼에서는 사용법이 본 문서의 내용과 다를 수 있다.
요즘의 시스템이나 프레임워크는 다수의 컴포넌트를 계층적으로 구성해서 사용자에게 기능을 제공한다. 다수의 컴포넌트를 적절히 결합시키는 일은 짜증날 정도로 힘들다. 시스템에 설치된 컴포넌트 혹은 라이브러리의 메타정보를 pkg-config
가 관리하도록 하면 좀 더 쉽게 컴포넌트를 가져다 쓸 수 있다.
pkg-config
같은 메타정보 관리 툴이 없이 컴포넌트를 가져다 쓰려면 라이브러리가 어디에 위치해 있고 어떻게 가져다 쓸 수 있는지 자세한 내용을 알기 쉽지 않다. 컴포넌트를 배포하는 개발자 입장에서도 pkg-config
메타데이터를 함께 배포하면 가져다 쓰는 사람이 훨씬 쉽게 API를 호출할 수 있다.
pkg-config
의 핵심 내용은 라이브러리를 가져다 쓰는 사람에게 컴파일과 링크에 대한 적절한 정보를 쉽게 제공하는 것이다. 이 메타데이터 정보는 pkg-config
파일에 작성되어 있다. .pc
확장자를 가지며 pkg-config
툴이 알만한 특정 위치에 설치된다. 뒷부분에서 이 내용은 더 자세히 살펴본다.
파일 형식은 아래와 같은데 미리 정의된 몇 가지 메타데이터 키워드와 자유롭게 정의한 변수를 볼 수 있다.
prefix=/usr/local
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: foo
Description: The foo library
Version: 1.0.0
Cflags: -I${includedir}/foo
Libs: -L${libdir} -lfoo
메타데이터 키워드 정의는 Name:
과 같이 지정된 키워드와 :
기호로 시작하며 해당 키워드에 대한 적절한 값을 작성한다. 변수는 prefix=
처럼 일반적인 변수 할당 형식을 사용한다.
다음은 각 키워드가 어떤 값을 가져야 하는지에 대한 간단한 설명이다. 키워드를 효율적으로 사용하기 위한 자세한 디테일은 이어지는 'pkg-config 파일 작성' 부분에서 알아보자.
- Name: 라이브러리나 패키지 이름으로 사람에게 읽힐 용도로 사용한다.
pkg-config
툴에서 사용하는.pc
파일의 파일이름과는 별개로 사용된다. - Description: 패캐지에 대한 간단한 설명이다.
- URL: 웹사이트 URL로 패키지에 대한 자세한 정보가 알고 싶은 사람들을 위해 제공한다.
- Version: 패키지의 버전을 정의한다.
- Requires: 패키지가 의존하여 사용하는 다른 패키지의 이름의 목록으로 공백으로 구분한다. 비교 연산자(=, <, >, <=, >=)를 사용하여 버전을 지정할 수도 있다.
- Requires.private: 패키지가 의존하여 사용하는 다른 패키지의 이름의 목록으로 공백으로 구분한다. 단 Requires와 다르게 패키지 안에서만 사용하며 이 패키지를 가져다가 사용하는 애플리케이션에는 사용할 필요 없을 패키지를 나열한다. 버전을 지정하는 형식은
Requires
와 동일하다. - Conflicts: 옵션으로 정의하여 사용하는 키워드로 패키지와 충동을 일으키는 다른 패키지를 목록 형식으로 정의한다. 버전을 지정하는 형식은
Requires
와 동일하다.Conflicts: bar < 1.2.3, bar >= 1.3.0
형식으로 같은 패키지를 두 번 정의하는 것도 가능하다. - Cflags: 외부에서 이 패키지를 가져다가 컴파일할 때 사용하는 컴파일러 옵션 플래그로, 이 패키지에서 의존하는 컴포넌트가
pkg-config
파일을 지원하지 않아서 컴파일러 옵션을 지정하도록 전달하기 위해 사용한다. 이 패키지에서 의존하는 컴포넌트가pkg-config
파일을 지원한다면Requires
나Requires.private
에 이름만을 나열해야 한다. - Libs: 외부에서 이 패키지를 가져다가 링크할 때 사용하는 링크 옵션 플래그다.
Cflags
와 마찬가지로 이 패키지에서 Public하게 의존하는 컴포넌트가pkg-config
파일을 지원하지 않는 경우 링크 옵션을 지정하도록 전달하기 위해 사용한다. 이 패키지에서 의존하는 컴포넌트가pkg-config
파일을 지원한다면Requires
나Requires.private
에 지정해야 한다. - Libs.private: 외부에서 이 패키지를 가져다가 정적으로 링크할 때 사용하는 링크 옵션 플래그다. 이 패키지가 의존하여 사용하는 다른 패키지를 링크하는 옵션 플래그로 이 패키지를 가져다가 사용하는 애플리케이션에서 직접 사용할 필요 없을 패키지에 대한 링크 옵션 플래그이다. 이 패키지에서 의존하는 컴포넌트가
pkg-config
파일을 지원한다면Requires
나Requires.private
에 지정해야 한다.
어떤 패키지에 대한 pkg-config 파일을 작성하려면 우선 패키지 혹은 라이브러리가 어떤 형태로 배포될 지 생각해봐야 한다. pkg-cnofig 파일이 라이브러리 하나만을 대표하는 것이 가장 좋으며 이 때에는 패키지 혹은 라이브러리 마다 pkg-config 파일 하나씩 생성해야 한다.
pkg-config 시스템에서 패키지 이름은 pkg-config 파일의 이름에서 가져와 사용된다. .pc
확장자 앞 부분 말이다. 일반적으로 라이브러리 이름은 .pc
앞의 파일 이름과 같은 것으로 맞추는 것이 좋다. 예를 들어 패키지가 설치하게 되는 라이브러리 파일 이름이 libfoo.so
라면 libfoo.pc
라고 메타데이터 파일의 이름을 짓는 것이다. 반드시 이렇게 해야 하는 것은 아니다. .pc
파일의 이름이 내 패키지를 가리킬 수 있도록 다른 .pc
파일과 겹치지 않는 파일 이름을 가지면 된다. foo.pc
도 foolib.pc
도 파일 이름으로 사용할 수 있다.
Name
, Description
, URL
항목은 패키지를 설명하는 부분으로 어렵지 않게 작성할 수 있으며 pkg-config 시스템에서 기술적으로 사용되는 부분은 아니다. Version
항목은 이 패키지를 가져다가 사용하는 다른 패키지에 영향을 줄 수 있는 부분이다. pkg-config
시스템은 RPM의 버전 비교 알고리즘을 사용한다. 버전 값은 점과 숫자로만 이루어진 형태를 사용해야 한다. 숫자가 아닌 알파벳 글자를 사용하면 제대로 동작하지 않을 수 있다. 버전 숫자는 증가하는 값을 사용하며 라이브러리의 변화를 구체적으로 반영해야 한다. 버전만으로 충분히 이 패키지를 가져다 사용하는 다른 패키지에 갱신 정보를 제공할 수 있다.
다른 키워드 항목을 살펴보기 전에 우선 변수를 정의하는 것을 살펴보는 것이 도움이 된다. 메타데이터 파일에서 주로 설치 경로등을 지정할때 사용하며 변수를 사용하면 파일 내용이 지저분해지는 것을 다소 막아줄 수 있다. 변수는 재귀적으로 적용이 가능하므로 autoconf
등으로부터 가져온 변수와 결합하셔 사용할 때 매우 유용하다.
prefix=/usr/local
includedir=${prefix}/include
Cflags: -I${includedir}/foo
Requires
, Requires.private
, Cflags
, Libs
, Libs.private
항목은 pkg-config 메타데이터에서 가장 중요한 정보를 다룬다. 이 패키지를 가져다가 사용하는 다른 패키지에서 컴파일하거나 링크할 때 여기에 작성한 정보를 가져다 사용하기 때문이다.
Requires
, Requires.private
항목은 이 패키지가 필요로 하는 다른 패키지를 설명한다. 이 패키지를 링크하여 사용하는데 굳이 노출시키지 않아도 될 의존 라이브러리라면 Requires.private
항목에 작성하는 것이 좋다. 이 패키지를 가져다 사용하는 다른 패키지에서 이 패키지가 의존하는 패키지의 심볼을 직접 사용하는 것이 아니라면 굳이 링크를 하지 않아도 되기 때문이다. 이어지는 내용에서 이 .private
붙은 항목을 왜 써야하는 지를 설명하고자 한다.
pkg-config 시스템은 항상 Requires
항목에 작성된 라이브러리의 Link 플래그를 노출시킨다. 이는 이 라이브러리를 가져다가 사용하는 패키지에 직접적인 의존관계를 설정해주게 된다. Requires.private
항목에 작성된 라이브러리의 Link 플래그는 정적 Link를 할 때에만 노출이 된다. 이런 설정에 따라 이 패키지를 사용하는 다른 패키지에서 Requires
에 적은 이 패키지의 이름이 동일하더라도 상황에 따라서 적절한 Link 플래그가 생성될 수 있게 된다.
Libs
항목은 이 패키지를 링크하기 위해 필요한 Link 플래그를 작성한다. Libs
, Libs.private
의 Link 플래그는 Requires
로 가져오지 못한 이 패키지가 의존하는 라이브러리에 대한 Link 플래그 정보도 포함한다. Requires
항목과 마찬가지로 반드시 Libs
에 작성해야 할 내용이 아니라면 Libs.private
항목을 사용하는 것이 직접적인 의존관계를 만들지 않기 때문에 좋다.
마지막으로 Cflags
항목은 이 패키지의 라이브러를 가져다 사용할 때 컴파일러 옵션으로 전달되는 내용이다. 여기에는 .private
키워드가 붙은 항목이 없다. 데이터 형식이나 매크로 정의는 링크와 관련없이 컴파일 할 때 필요하기 때문이다.
.pc
파일이 시스템에 적절히 설치되어 있다면 pkg-config
툴로 메타데이터를 적절하게 추출하여 사용하는 것이 가능합니다. pkg-config --help
명령으로 각 옵션을 어떻게 사용할 수 있는지에 대한 간단한 도움말을 살펴볼 수 있습니다. 자세한 내용은 pkg-config(1)
man 페이지에서 살펴볼 수 있습니다. 이 페이지에서는 주로 사용하는 간단한 예제로 설명합니다.
예를 들어 시스템에 foo
와 bar
라는 모듈이 설치되어 있고 .pc
파일이 아래와 같은 내용을 담고 있습니다.
foo.pc:
prefix=/usr
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: foo
Description: The foo library
Version: 1.0.0
Cflags: -I${includedir}/foo
Libs: -L${libdir} -lfoo
bar.pc:
prefix=/usr
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: bar
Description: The bar library
Version: 2.1.2
Requires.private: foo >= 0.7
Cflags: -I${includedir}
Libs: -L${libdir} -lbar
각 모듈의 버전은 --modversion
옵션을 사용하여 다음과 같이 확인할 수 있습니다.
$ pkg-config --modversion foo
1.0.0
$ pkg-config --modversion bar
2.1.2
각 모듈의 링크 옵션 플래그는 --libs
옵션을 사용하여 다음과 같이 확인할 수 있습니다.
$ pkg-config --libs foo
-lfoo
$ pkg-config --libs bar
-lbar
pkg-config
툴이 Libs
부분에서 일부 정보를 가져오지 않았음을 확인할 수 있습니다. 가져오지 않은 -L
옵션을 좀 특별하게 다루게 되는데, -L
플래그에서 지정한 ${libdir}
실제 값인 /usr/lib
디렉토리가 시스템 link 명령의 검색 디렉토리 목록 안에 포함되어 있음을 알기 때문입니다. 이런식으로 시스템의 기본 Link 도구를 방해하지 않도록 pkg-config
는 동작합니다.
그리고 bar
라이브러리가 foo
라이브러리를 필요로 하지만 링크 옵션 플래그로 출력되지 않았습니다. bar
라이브러리를 가져다가 사용하는 애플리케이션이 bar
의 API만 사용하고 foo
의 API를 사용하지 않는다면 이러한 출력이 적절하기 때문입니다. 하지만 정적(static)으로 애플리케이션을 컴파일하려면 반드시 두 라이브러리의 링크 옵션이 필요합니다.
$ pkg-config --libs --static bar
-lbar -lfoo
pkg-config
는 --static
옵션을 통해 정적 컴파일에서 필요로 하는 모든 심볼을 찾을 수 있는 링크 옵션 플래그를 출력할 수 있습니다. 이와는 다르게 Cflags
는 동적이든 정적이든 상관없이 항상 전달됩니다.
$ pkg-config --cflags bar
-I/usr/include/foo
$ pkg-config --cflags --static bar
-I/usr/include/foo
--exists
옵션을 사용하여 찾고자 하는 모듈이 시스템에 설치되어 있는지 확인할 수 있습니다.
$ pkg-config --exists foo
$ echo $?
0
pkg-config
툴을 사용하면 버전 정보를 사용하여 적절한 버전이 설치되어 있는지 확인할 수도 있습니다. 필요로 하는 버전 이상의 모듈이 설치되어 있는지 확인하려면 다음과 같이 실행합니다.
$ pkg-config --libs "bar >= 2.7"
Requested 'bar >= 2.7' but version of bar is 2.1.2
pkg-config
를 사용하여 오류가 발생할 옵션을 실행하는 경우 --print-errors
옵션을 같이 사용하면 좀 더 자세한 내용의 오류 메시지를 확인할 수 있습니다.
$ pkg-config --exists --print-errors xoxo
Package xoxo was not found in the pkg-config search path.
Perhaps you should add the directory containing `xoxo.pc'
to the PKG_CONFIG_PATH environment variable
No package 'xoxo' found
위의 오류메시지를 확인하면 PKG_CONFIG_PATH
환경 변수를 참조한다는 것을 볼 수 있습니다. PKG_CONFIG_PATH
변수는 pkg-config 파일을 검색하는데 사용합니다. 일반적인 유닉스나 리눅스 시스템에서라면 /usr/lib/pkgconfig
, /usr/share/pkgconfig
디렉토리를 참조합니다. 대부분 시스템에 설치된 라이브러리 모듈은 보통 위의 디렉토리에 pkg-config
파일을 위치시킵니다. 다만 일부 모듈의 경우 설치 디렉토리가 /usr/local
인 경우가 있습니다. 이런 경우 pkg-config
가 파일을 잘 찾을 수 있도록 PKG_CONFIG_PATH
환경변수를 설정하게 됩니다.
$ pkg-config --modversion hello
Package hello was not found in the pkg-config search path.
Perhaps you should add the directory containing `hello.pc'
to the PKG_CONFIG_PATH environment variable
No package 'hello' found
$ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
$ pkg-config --modversion hello
1.0.0
autoconf
에서 아래와 같은 추가적인 매크로를 사용해서 pkg-config
툴의 기능을 결합하여 활용할 수 있습니다.
PKG_PROG_PKG_CONFIG([MIN-VERSION])
:pkg-config
프로그램의 위치를 확인하며 해당 버전 이상의 버전이 설치되어 있는지 확인합니다.PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
: 찾고자 하는 모듈이 설치되어 있는지 확인합니다.PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
: 찾고자 하는 모듈이 설치되어 있는지 확인합니다. 설치되어 있다면pkg-config --cflags
결과를<VARIABLE-PREFIX>_CFLAGS
변수에,pkg-config --libs
결과를<VARIABLE-PREFIX>_LIBS
변수에 설정합니다.
pkg-config
툴의 출력을 컴파일러 명령 옵션으로 어렵지 않게 사용할 수 있습니다. 라이브러리 x가 pkg-config
를 지원하여 x.pc 파일이 존재한다면 다음과 같이 컴파일러 옵션에 사용할 수 있습니다.
cc `pkg-config --cflags --libs x` -o myapp myapp.c
autoconf나 automake 툴을 사용한다면 좀 더 든든한 형식으로 컴파일러 옵션 플래그 결합을 시킬 수 있습니다. PKG_CHECK_MODULES
매크로를 사용하여 빌드 프로세스에서 메타데이터를 쉽게 가져다 사용할 수 있습니다.
configure.ac:
PKG_CHECK_MODULES([X], [x])
Makefile.am:
myapp_CFLAGS = $(X_CFLAGS)
myapp_LDADD = $(X_LIBS)
pkg-config
툴이 라이브러리 x에 관련된 메타정보를 찾은 경우 X_CFLAGS
변수와 X_LIBS
변수에 관련 내용이 설정됩니다. 라이브러리 x가 설치되지 않았거나 어떤 이유든 간에 찾을 수 없는 경우에는 설정 과정에서 오류가 발생합니다. PKG_CHECK_MODULES 매크로의 3번째와 4번째 인자를 통해 찾고자 하는 라이브러리를 찾지 못했을 때의 상황을 제어할 수 있습니다.
libx가 pkg-config
형식을 지원한다면 Requires.private
에 x
를 추가하세요. 그렇지 않은 경우에는 Cflags
에 libx의 헤더를 사용하기 위한 적절한 컴파일러 옵션을 적어주세요. 이렇게 설정한 내용은 --static
옵션을 사용하든 안하든 컴파일 옵션으로 출력됩니다.
마찬가지로 libx가 pkg-config
형식을 지원한다면 Requires.private
에 x
를 추가하세요. 이 경우 컴파일러 플래그 옵션은 전달되지 않지만 정적 링크를 할 때 Link 옵션 플래그는 전달됩니다. libx
가 pkg-config
를 지원하지 않는다면 Libs.private
에 libx
를 링크할 수 있는 링크 옵션 플래그를 작성합니다.
Dan Nocholson <dbn.lists (at) gmail (dot) com>
Copyright (C) 2010 Dan Nicholson
This docuemtn is licensed under GNU General Public License, Version 2 the or any last version.