간단한 rpmbuild의 사용과 private rpm package 배포
Perlmint @perlmint@hackers.pub
마지못해 패키지를 만들어야 할 것 같은 사람을 위한 설명입니다. 제대로된 패키지를 만들고 싶은 경우에는 부족한 점이 많습니다.
대부분의 경우에는 프로그램을 직접 소스에서 빌드하는 일도 적고, 그걸 시스템 전역에 설치하는 일도 흔치는 않을 것입니다. 좋은 패키지매니저와 관리가 잘되는 패키지 저장소들을 두고 아주 가끔은 직접 빌드를 할 일이 생기고, 흔치 않게 시스템 전역에 설치할 일이 생길 수 있습니다. 어지간한 프로그램들은 요즈음의 장비에서는 별 불만 없이 빌드 할 만한 시간이 소요되나, 컴파일러처럼 한번 빌드했으면 다시는 하고 싶지 않은 프로그램을 다시 설치해야하는 경우도 있을 수 있습니다. 하필 이런 프로그램들은 결과물도 덩치가 매우 큽니다. 이럴 때는 최대한 간단하고 필요한 항목만 패키지에 넣어서 만들어두고 다시 활용하면 좋을 것이기에 이런 경우를 위한 rpmbuild에 대한 나름 최소한의 사용 방법을 정리해봅니다.
rpmbuild
rpmbuild는 rpm-build
패키지로 설치가 가능하며, 나름 단순하게 rpm으로 패키징을 할 수 있는 유틸리티입니다. spec파일에 패키지 정보, 빌드 명령, 설치 명령, 패키지가 포함해야 할 파일 목록을 작성해서 rpmbuild에 입력으로 넣어주면 빌드부터 시작해서 rpm패키지를 만들어줍니다. native 프로그램의 경우 디버그 심볼을 알아서 분리해서 별도의 패키지로 만들어주고, 필요한 의존성도 추정해서 명시해줍니다. 또한, 필요한 경우 하나의 spec 명세로 연관된 서브 패키지도(ex. 실행파일 패키지인 curl과 라이브러리 패키지 libcurl, 라이브러리를 사용하기 위한 개발 패키지 libcurl-devel) 같이 만들 수 있습니다.
작업 환경
rpmbuild는 기본으로 ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS,BUILDROOT}
의 경로에서 동작하며 각 경로의 용도는 다음과 같습니다.
SOURCES
에는 압축된 소스코드가 위치합니다.SPECS
에는 패키지 정의인 spec파일을 둡니다.BUILD
밑에서 빌드 작업이 진행됩니다.RPMS
에 바이너리 rpm결과물이 생성됩니다.SRPMS
에는 소스 rpm결과물이 생성됩니다.BUILDROOT
는 패키징 하기 위해 빌드 결과물을 모으는 경로입니다.
spec파일
spec파일은 패키지를 어떻게 빌드하고 어떤 항목들이 패키지에 포함될지, 패키지의 이름, 설명 및 의존성 등의 메타데이터, 패키지 설치, 삭제시의 스크립트를 정의할 수 있습니다. 보통 시작 부분에는 메타데이터 정의로 시작하며, 다음과 같은 기본적인 형태를 취합니다. 나름 단순하게 만든 python을 위한 spec을 예시로 들어보겠습니다.
Summary: Python %{version}
Name: python-alternative
Version: %{version}
Release: 1%{?dist}
Obsoletes: %{name} <= %{version}
Provides: %{name} = %{version}
URL: https://www.python.org
Requires: libffi openssl
AutoReq: no
License: PSFL
Source: https://www.python.org/ftp/python/%{version}/Python-%{version}.tgz
BuildRequires: libffi-devel openssl-devel
BuiltRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
%define major_version %(echo "%{version}" | sed -E 's/^([0-9]+)\\..+/\1/' | tr -d)
%define minor_version %(echo "%{version}" | sed -E 's/^[0-9]+\\.([0-9]+)\\..+/\1/' | tr -d)
%description
Python
%package devel
Summary: python development files
Requires: %{name} = %{version}-%{release}
%description devel
Python development package
%prep
%setup -q -n Python-%{version}
%build
./configure --prefix=%{_prefix}
%install
%{__make} altinstall DESTDIR=%{buildroot}
%{__ln_s} -f %{_bindir}/python%{major_version}.%{minor_version} %{buildroot}/%{_bindir}/python%{major_version}
%clean
%{__rm} -rf %{buildroot}
%files
%{_bindir}/python*
%exclude %{_bindir}/idle*
%{_bindir}/pip*
%{_bindir}/pydoc*
%exclude %{_bindir}/2to3*
%{_libdir}/libpython*
%{_prefix}/lib/libpython*
%{_prefix}/lib/python*
%{_mandir}/man1/python*
%files devel
%{_includedir}/python*
%{_prefix}/lib/pkgconfig/python*
%
로 매크로를 사용할 수 있으며, %package
, %description
, %files
같은 매크로는 인자를 주어서 서브 패키지를 정의하는데도 쓸 수 있습니다.
앞선 예제처럼 devel
이라고작성하면 메인 패키지이름 뒤에 붙여서 python-alternative-devel
가 되며, curl
- libcurl
과 같은 경우에는 메인의 이름은 curl
이고, 딸린 패키지를 정의할 때는 %package -n libcurl
과 같이 -n
옵션을 추가해서 지정할 수 있습니다. 몇몇 매크로는 단계를 정의하는 것과 같은 동작을 하며 다음과 같습니다.
%package
유사성을 보면 spec파일의 맨 첫부분은 메인 패키지의 %package
에 해당하는 것이 아닌가 싶습니다. <Key>: <Value>
의 형태로 메타정보를 작성합니다. 대부분은 Key를 보면 무슨 값인지 추측 할 만합니다.
나중에 설명할 %files
에서 나열한 파일을 rpmbuild가 분석하여 자동으로 패키지가 필요로 하는 의존성을 추정해서 추가 해 줍니다. python 스크립트, perl 스크립트, native 실행파일 등을 분석해서 알아서 추가해주는 것 같은데, 경우에 따라서는 틀린 의존성을 추가해주기도 합니다. 이 때는 AutoReq: no
를 설정하여 자동 의존성 추가를 막을 수 있습니다. 이 python-alternative 패키지는 /usr/local/bin/python%{version}
을 설치하는데 아마도 같이 포함되는 python 스크립트에 의해서 /bin/python
을 의존성으로 추정하여 요구합니다. 패키지 스스로가 제공하는 의존성은 미리 설치 되어있기를 요구하지 않게 동작하는 것 같으니 보통은 문제가 없습니다만, 이 경우에는 스스로 제공을 하지 않기 때문에 python을 설치하기 위해서 python이 필요한 경우가 발생하므로 AutoReq
를 껐습니다.
%prep
준비단계로 소스코드의 압축을 해제하고 필요한경우 패치를 적용합니다.
%setup
매크로를 이 안에서 보통 사용하며, %setup
은 Source
에 명시된 파일명의 압축 파일을 SOURCES
밑에서 찾아서 압축을 풉니다. 그리고 동일한 이름의 디렉토리로 이동을 합니다. 앞선 예제에서는 SOURCES/Python-%{version}.tgz
의 압축을 풀고 Python-%{version}
으로 이동을 합니다.
패치가 필요한 경우 보통 이 뒤에 패치를 적용하는 명령들을 추가 합니다.
%build
설정, 컴파일 등을 수행하는 단계입니다. 이곳에서 자주 하는 매크로로 %configure
, %make_build
등이 있습니다. %configure
는 configure를 prefix 및 기타 몇가지 일반적으로 쓰이는 옵션을 추가하여 실행해주며, %make_build
는 make
와 비슷하게 모든 타겟을 빌드 합니다. 예제에서는 둘다 안쓰고 있고, 심지어 실제 빌드는 안하는데 어쨌든 이후의 %install
까지 지나고나서 빌드 결과물만 맞는 위치에 만들어지면 대충 패키지를 만드는데는 별 문제는 없는 것 같습니다.
%install
여기서 빌드 결과물을 설치하는 명령을 작성합니다. 일반적으로 %make_install
을 사용하여 make install DESTDIR=%{buildroot}
와 비슷한 명령을 수행하여 %{buildroot}
밑에 빌드 결과물이 prefix를 유지하여 설치되게 합니다. 예제의
%{__ln_s} -f %{_bindir}/python%{major_version}.%{minor_version} %{buildroot}/%{_bindir}/python%{major_version}
을 보면 추정 할 수 있듯이, 패키지에 포함시킬 파일들을 %{buildroot}밑에 생성을 하면 되며, 추가적인 심볼릭 링크는 패키지를 빌드하는 시점에는 존재하지 않지만, 패키지를 설치하게되면 존재하게 될 %{_bindir}/python%{major_version}.%{minor_version}
를 향하는 것을 %{buildroot}
밑인 %{buildroot}/%{_bindir}/python%{major_version}
에 만듭니다.
%files
패키지에 포함될 파일 목록을 작성합니다. glob 양식으로 파일 목록을 작성할 수 있습니다. %{buildroot}
밑에 생성 되었지만 어느 %files
에도 포함되지 않은 파일이 있는 경우에는 빌드를 실패합니다. 그러므로 %exclude
를 사용해서 명시적으로 제외해줘야 합니다.
기타 매크로
rpmbuild에서는 기본으로 다양한 매크로를 제공하고 있습니다. --define "_libdir %{_prefix}/lib64"
와 같은 옵션을 실행시에 주어서 실행시점에 매크로를 덮어 쓸 수도 있고, 앞선 spec파일 내의 %define major_version
와 같이 다른 매크로와 셸 명령을 활용하여 매크로를 정의 할 수도 있습니다.
원하는 동작을 안하는 것 같은 경우에는 --show-rc
옵션을 사용하여 매크로가 어떻게 정의되어있는지 확인해 볼 수 있습니다.
빌드
rpmbuild의 매뉴얼을 보면 자세하게 나와있지만 가장 단순하게는
rpmbuild -bb <specfile>
로 바이너리 패키지를 빌드할 수 있습다. 이 때, 압축된 소스코드는 미리 SOURCES밑에 두어야 합니다.
private rpm package 배포
직접 비공개 패키지 저장소 프로그램을 실행하여 제공하는 방법도 있겠지만, 최대한 간단하게 할 수 있는 방법으로, rpm관련 패키지 설치 명령이 입력으로 http등의 URL도 받는 것을 활용하여 적당한 장비에서 http로 서빙을 해주면 됩니다.