Havoc Pennington
hp@pobox.com 2002
Philip Withnall
philip.withnall@collabora.co.uk 2015
동시 설치 방식으로 나중에 검증할 라이브러리를 작성합니다
조성호
shcho@gnome.org 2016-2018.

# 동시 설치 가능성

#

        요약

        두 패키지를 동시에 설치할 수 있다면, 일반적인 파일 이름이 붙어있지 않으며 패키지를 개발한 사람은 원하는 버전에 따라 컴파일합니다. 이 방식은 헤더 파일, 라이브러리 이진 파일에 적용하는 방식대로 데몬, 유틸리티 프로그램, 설정 파일에 적용합니다.

          모든 라이브러리 버전을 동시에 설치할 수 있는지 확인하십시오().
          라이브러리로 설치한 모든 파일에 버전을 부여하십시오().
          so 이름 또는 libtool 버전 번호와는 별개로 패키지 버전 번호를 관리하십시오. 어떤 패키지 버전 번호 부분이 API와 바뀌는지 분명히 하십시오().
          $(includedir)/liblibrary-version/library/에 C 헤더 파일을 설치하십시오().
          $(libdir)/liblibrary-version.so.soname에 라이브러리 파일을 설치하십시오().
          $(libdir)/pkgconfig/library-version.pc에 pkg-config 파일을 설치하십시오().
          설정 파일을 전후 호환성에 맞추든지 $(sysconfdir)/library-version/에 설치하십시오().
          GETTEXT_PACKAGE 변수 값을 library-version에 설정하십시오().
          모든 D-Bus 인터페이스 이름, 서비스 이름, 객체 경로에 버전 번호를 넣으십시오. 이를 테면 org.domain.LibraryVersion.Interface, org.domain.LibraryVersion, /org/domain/LibraryVersion/ 과 같이 넣으십시오().
          데몬 바이너리를 $(libexecdir)/library-daemon-version에 설치하십시오().
          유틸리티 바이너리를 $(bindir)/library-utility-version and install symbolic links to $(bindir)/library-utility에 설치하십시오().

# 명분

모든 공개 라이브러리는 라이브러리의 생명 주기 이후 API 구조 변경이 쉽게 함께 설치할 수 있도록 설계해야합니다. 라이브러리를 여러 프로젝트에서 사용하는 상황에서 API 구조를 깨야 한다면, 모든 프로젝트를 동시에 설치한 새 API에 맞춰 이식해야 하며, 이렇게 하지 않으면 일부 프로젝트는 의존 관계상 라이브러리 버전이 맞지 않는 문제로 더이상 라이브러리와 설치할 수 없습니다.

이 상태가 바로 관리할 수 없는 상태이며, 대부분의 API가 이식의 동기가 될 눈에 띄는 새 기능을 넣지 않기에, 모든 프로젝트에 새 API에 맞춰 동시에 이식을 요청하기 어렵고, 이식의 의욕을 꺾어버립니다.

API의 이전 버전과 새 버전을 동시에 설치하고 컴파일 할 수 있게끔 모든 라이브러리를 동시에 설치할 수 있게 했는지 확인하면 이 문제가 처리됩니다. 동시에 설치할 수 있게 빌드하면, 굳이 근본 부분을 건드리지 않고도 프로젝트의 시작단계를 쉽게 처리할 수 있습니다.

이 해결 방식은 또한 라이브러리의 어떤 버전과 그 다음 버전을 설치하는 문제에서 여러 프로그램을 이식하는 ‘닭이 먼저냐 달걀이 먼저냐’의 문제를 해결하며, 라이브러리 관리 집단이 원한다면 개발 주기를 더 빠르게 진행할 수 있으며 개발 과정상 새 기능을 넣을 수 있도록 API 구조를 훨씬 쉽게 바꿀 수 있습니다.

이와 동일한 라이브러리 대상 해결책은 API 구조를 그냥 안바꾸는 방법입니다. 이 접근 방식은 `libc`에서 왔습니다.

# 해결책

문제 해결책은 근본적으로 라이브러리의 이름을 바꾸는 방식이며, 대부분의 경우 모든 파일을 설치하는 경로의 버전 번호를 넣는 방식입니다. 이는 라이브러리의 여러 버전을 동시에 설치할 수 있다는 의미입니다.

예를 들어 `Foo` 라이브러리에서 기존에 설치한 파일이 다음과 같다고 하겠습니다:

/usr/include/foo.h

/usr/include/foo-utils.h

/usr/lib/libfoo.so

/usr/lib/pkgconfig/foo.pc

/usr/share/doc/foo/foo-manual.txt

/usr/bin/foo-utility

이 파일을 설치하려면 위 대신 `Foo` 버전 4로 수정해야합니다:

/usr/include/foo-4/foo/foo.h

/usr/include/foo-4/foo/utils.h

/usr/lib/libfoo-4.so

/usr/lib/pkgconfig/foo-4.pc

/usr/share/doc/foo-4/foo-manual.txt

/usr/bin/foo-utility-4

수정하고 나면 버전 5로 동시에 설치할 수 있습니다:

/usr/include/foo-5/foo/foo.h

/usr/include/foo-5/foo/utils.h

/usr/lib/libfoo-5.so

/usr/lib/pkgconfig/foo-5.pc

/usr/share/doc/foo-5/foo-manual.txt

/usr/bin/foo-utility-5

pkg-config에서 쉽게 기능을 빌려 쓸 수 있습니다. foo-4.pc 파일은 include 경로에 /usr/include/foo-4 경로를 추가하고 링크할 라이브러리 목록에 libfoo-4.so 파일을 추가합니다. foo-5.pc 파일은 /usr/include/foo-5 경로와 libfoo-5.so 파일을 추가합니다.

# 버전 번호

파일 이름에 붙은 버전 번호는 ABI/API 버전입니다. 패키지의 전체 버전 번호는 아닙니다. API 유지 상태가 깨졌는지 여부를 나타내는 부분일 뿐입니다. 프로젝트 버전 부여시 `주.부.마이크로` 표준 버전 부여 형식을 사용한다면 API 버전은 보통 주 버전 번호입니다.

부 버전 출시판(보통 API를 추가하지만 바꾸거나 제거하지 않음)과 마이크로 버전 출시판(보통 버그 수정)은 API 이전 호환성에 영향을 주지 않으므로 모든 파일을 옮길 필요가 없습니다.

다음 부분의 예제는 다음 코드를 활용하여 configure.ac 파일에서 API 버전과 so 이름을 내보낸다고 가정합니다:

## autoconf의 API 버전 부여

configure.ac
파일에 API 버전 및 so 이름을 내보낼 코드를 작성하십시오
# Before making a release, the LIBRARY_LT_VERSION string should be modified. # The string is of the form c:r:a. Follow these instructions sequentially: # # 1. If the library source code has changed at all since the last update, # then increment revision (‘c:r:a’ becomes ‘c:r+1:a’). # 2. If any interfaces have been added, removed, or changed since the last update, # increment current, and set revision to 0. # 3. If any interfaces have been added since the last public release, # then increment age. # 4. If any interfaces have been removed or changed since the last public release, # then set age to 0. AC_SUBST([LIBRARY_LT_VERSION],[1:0:0])

AC_SUBST([LIBRARY_API_VERSION],[4])

# C 헤더 파일

헤더 파일은 C 컴파일러의 -I 플래그가 요구하는 버전이 붙은 하위 디렉터리에 항상 설치해야합니다. 예를 들어, foo.h 헤더 파일이 있다면 프로그램에서는 다음과 같이 씁니다:

#include <foo/foo.h>

그 다음 이 파일을 설치하겠습니다:

/usr/include/foo-4/foo/foo.h

/usr/include/foo-5/foo/foo.h

프로그램에서는 -I/usr/include/foo-4 또는 -I/usr/include/foo-5 플래그를 C 컴파일러에 보내야합니다. 다시 말하지만, pkg-config를 활용하면 됩니다.

foo/ 추가 하위 디렉터리를 살펴보십시오. 다른 라이브러리의 파일 이름이 겹치는 일을 피하도록 `#include` 절에 이름 영역을 부여합니다. 예를 들자면, 서로 다른 두 라이브러리에서 utils.h 헤더 파일을 설치한다면 `#include <utils.h>` 헤더를 사용할 때 무얼 넣을까요?

어떤 하위 디렉터리 외에 있는 헤더 파일 중 하나를 두어야 할 지 망설임이 옵니다:

/usr/include/foo.h

/usr/include/foo-5/foo.h

문제는 사용자가 언제나 부주의하게 잘못된 헤더를 참조하는데, 컴파일 명령행에 -I/usr/include를 넣는걸 규칙인 것 처럼 생각하기 때문입니다. 만약 이렇게 해야 한다면, 최소한 라이브러리를 초기화할 때 잘못된 헤더 파일을 사용하여 프로그램을 감지하는 라이브러리가 있는지 검사하는 부분을 추가하십시오.

버전을 부여한 헤더 파일은 다음 코드로 automake에서 설치할 수 있습니다:

## automake의 헤더 파일 처리

Makefile.am
파일에 버전을 부여한 헤더 파일을 설치할 코드를 작성하십시오
library
includedir = $(includedir)/liblibrary-@LIBRARY_API_VERSION@/library library_headers = \ liblibrary/example1.h \ liblibrary/example2.h \ $(NULL)

# The following headers are private, and shouldn't be installed: private_headers = \ liblibrary/example-private.h \ $(NULL) # The main header simply #includes all other public headers: main_header = liblibrary/library.h public_headers = \ $(main_header) \ $(library_headers) \ $(NULL)

library
include_HEADERS = $(public_headers)

버전을 올바르게 부여하는 것처럼, 설치한 헤더에 있는 모든 API의 공간 이름을 올바르게 부여해야합니다.

# 라이브러리

라이브러리 객체 파일은 다음과 같이 버전을 붙인 이름을 달고 있어야합니다:

/usr/lib/libfoo-4.so

/usr/lib/libfoo-5.so

이 과정으로 컴파일을 진행하는 동안 원하는 프로그램이 제대로 나오며, 일반적으로 버전 4 및 버전 5에 파일이 없는지 확인합니다.

버전을 매긴 라이브러리는 다음 코드로 automake에서 빌드하고 설치할 수 있습니다:

## automake의 라이브러리 처리

Makefile.am
에 버전을 매긴 파일을 빌드하고 설치하는 코드를 작성하십시오
lib_LTLIBRARIES = liblibrary/liblibrary-@LIBRARY_API_VERSION@.la

liblibrary_liblibrary_@LIBRARY_API_VERSION@_la_SOURCES = \ $(private_headers) \ $(library_sources) \ $(NULL) liblibrary_liblibrary_@LIBRARY_API_VERSION@_la_CPPFLAGS = … liblibrary_liblibrary_@LIBRARY_API_VERSION@_la_CFLAGS = … liblibrary_liblibrary_@LIBRARY_API_VERSION@_la_LIBADD = … liblibrary_liblibrary_@LIBRARY_API_VERSION@_la_LDFLAGS = \ -version-info $(LIBRARY_LT_VERSION) \ $(AM_LDFLAGS) \ $(NULL)

## 라이브러리 so 이름

라이브러리 so 이름(libtool 버전 번호로도 알려짐)은 앞서 컴파일한 프로그램을 연결하는 실행시 문제만을 해결합니다. 이전 버전에서 필요한 프로그램을 컴파일하는 문제를 바로잡을 수 없으며, 다른 라이브러리의 문제는 바로 잡지 않습니다.

이 때문에, so 이름을 활용하지만, 추가로 라이브러리의 이름에 버전을 붙입니다. 각각의 문제를 두가지 해결책으로 바로 잡습니다.

# pkg-config 파일

다음과 같이 pkg-config 파일에 버전을 매긴 이름을 넣어야합니다:

/usr/lib/pkgconfig/foo-4.pc

/usr/lib/pkgconfig/foo-5.pc

각각의 pkg-config 파일에 라이브러리 이름과 수반 경로의 버전 정보가 있기 때문에 라이브러리에 의존하는 어떤 프로젝트든지간에 foo-4 파일에서 foo-5 파일로 pkg-config check를 바꾸(고 API 이식 필요 과정을 진행하)는 방법만으로 버전을 바꿀 수 있어야합니다.

버전을 부여한 pkg-config 파일은 다음 코드로 autoconf 및 automake에서 설치할 수 있어야합니다:

## autoconf와 automake의 pkg-config 파일 처리

configure.ac
파일과 Makefile.am 파일에 버전을 부여한 pkg-config 파일을 설치하는 코드를 작성하십시오
AC_CONFIG_FILES([ liblibrary/library-$LIBRARY_API_VERSION.pc:liblibrary/library.pc.in ],[], [LIBRARY_API_VERSION='$LIBRARY_API_VERSION'])
# Note that the template file is called library.pc.in, but generates a # versioned .pc file using some magic in AC_CONFIG_FILES. pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = liblibrary/library-$(LIBRARY_API_VERSION).pc

DISTCLEANFILES += $(pkgconfig_DATA) EXTRA_DIST += liblibrary/library.pc.in

# 설정 파일

사용자 관점에서, 전후 호환성(정확히 동일한 설정 파일 및 문맥을 각 라이브러리 버전에서 이해)에 따라 설정 파일 형식을 유지하는게 좋습니다. 그 다음 동일한 설정 파일을 모든 라이브러리 버전에서 활용할 수 있으며, 설정 파일 자체를 버전 관리할 필요가 없습니다.

이렇게 할 수 있다면, 간단하게 설정 파일의 이름을 바꿔야 하며, 사용자는 각 라이브러리 버전 별로 따로 설정해야합니다.

# gettext 번역

autoconf와 automake의 번역 기반으로 gettext를 사용한다면 보통 /usr/share/locale/lang/LC_MESSAGES/package 경로에 번역 파일을 설치합니다. 여기서 package 변수를 추가해야합니다. 그놈에서는 configure.ac에 이 변수를 넣는 방식을 활용합니다:

GETTEXT_PACKAGE=foo-4 AC_SUBST([GETTEXT_PACKAGE]) AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["$GETTEXT_PACKAGE"])

그 다음 `bindtextdomain()`, `textdomain()`, `dgettext()`에 패키지 이름을 전달할 목적으로 `GETTEXT_PACKAGE`를 활용합니다.

# D-Bus 인터페이스

D-Bus 인터페이스는 컴파일 시간이 아닌 실행 시간에 버전 관리를 처리할 때를 제외하고는 C API와 유사한 API의 한 유형입니다. D-Bus 인터페이스 버전 부여는 다른 C API와 별반 차이가 없습니다. 버전 숫자는 인터페이스 이름, 서비스 이름, 객체 경로에 있어야합니다.

이를테면, 다음과 같이 `org.example.Foo` 서비스에는 `Controller`와 `Client`의 `A` 와 `B`, D-Bus API 버전 4와 5가 나타나야합니다:

## 서비스 이름

`org.example.Foo4`

`org.example.Foo5`

## 인터페이스 이름

`org.example.Foo4.InterfaceA`

`org.example.Foo4.InterfaceB`

`org.example.Foo5.InterfaceA`

`org.example.Foo5.InterfaceB`

## 객체 경로

`/org/example/Foo4/Controller`

`/org/example/Foo4/Client`

`/org/example/Foo5/Controller`

`/org/example/Foo5/Client`

# 프로그램, 데몬, 유틸리티

데스크톱 프로그램은 보통 다른 모듈과 의존 관계가 없기 때문에 버전을 매길 필요가 없습니다. 그러나 데몬과 유틸리티 프로그램은 시스템의 다른 부분과 함께 동작하므로 버전을 매겨야 합니다.

다음의 데몬 및 유틸리티 프로그램을:

/usr/libexec/foo-daemon

/usr/bin/foo-lookup-utility

다음과 같이 버전을 부여합니다:

/usr/libexec/foo-daemon-4

/usr/bin/foo-lookup-utility-4

사용자가 편리하게 쓸 수 있게 /usr/bin/foo-lookup-utility를 유틸리티의 추천 버전 사본으로 심볼릭 링크 사본을 설치하고 싶을 수도 있습니다.