CentOSでRPMパッケージを自作する

## 概要
RPMパッケージを自作する手順です。
Go言語で、`hello, world`と表示するだけのプログラムをインストールします。

## 環境
- CentOS 6 or 7

## Installation
rpmパッケージを作成するための環境を作成します。

```shell-session
$ sudo yum install -y rpm-build
$ sudo yum groupinstall -y 'Development tools'
```

## Goでソース作成
以下を参考に、Go言語の環境と、helloを表示するプログラムを作成してください。

- [Golang環境構築](https://qiita.com/Esfahan/items/30f801dc6e5bada9ba54)

## rpmdev-setuptreeのインストール
**rpmdev-setuptree**を使うと、コマンド1つでrpmの開発環境を作ることができます。

```
$ sudo yum install -y rpmdevtools yum-utils
```

### rpmdev-setuptree実行

```
$ rpmdev-setuptree
```

すると、以下のディレクトリが自動生成されます。

```shell-session
$ ls -1 ~/rpmbuild/
BUILD
RPMS
SOURCES
SPECS
SRPMS
```

### GOのパッケージを移動
GOのソースをtarで固めたら、生成されたディレクトリにtarファイルを移動します。

```shell-session
$ tar czvf hello-1.0.tar.gz hello
```

```shell-session
$ cp hello-1.0.tar.gz ~/rpmbuild/SOURCES
```

## rpmdev-setuptreeのtopdirを変更する場合
デフォルトでは、~/rpmbuildが開発ディレクトリとなっています。

```shell-session
$ rpmbuild --showrc | grep _topdir
-14: _topdir	%(echo $HOME)/rpmbuild
```

変更する場合は、~/.rpmmacrosの`%_topdir %(echo $HOME)/rpmbuild`のパスを変更します。
以下は`/usr/src/rpmbuild/`になるように変更する例です。

```diff:~/.rpmmacros
-%_topdir %(echo $HOME)/rpmbuild
+%_topdir %{_usrsrc}/rpmbuild

%_smp_mflags %( \
    [ -z "$RPM_BUILD_NCPUS" ] \\\
        && RPM_BUILD_NCPUS="`/usr/bin/nproc 2>/dev/null || \\\
                             /usr/bin/getconf _NPROCESSORS_ONLN`"; \\\
    if [ "$RPM_BUILD_NCPUS" -gt 16 ]; then \\\
        echo "-j16"; \\\
    elif [ "$RPM_BUILD_NCPUS" -gt 3 ]; then \\\
        echo "-j$RPM_BUILD_NCPUS"; \\\
    else \\\
        echo "-j3"; \\\
    fi )

%__arch_install_post \
    [ "%{buildarch}" = "noarch" ] || QA_CHECK_RPATHS=1 ; \
    case "${QA_CHECK_RPATHS:-}" in [1yY]*) /usr/lib/rpm/check-rpaths ;; esac \
    /usr/lib/rpm/check-buildroot
```

変更されたことを確認

```shell-session
$ rpmbuild --showrc | grep _topdir
-14: _topdir	%{_usrsrc}/rpmbuild
```

これでrpmdev-setuptreeを実行すると、`/usr/src/rpmbuild/`が生成されます。

```shell-session
$ rpmdev-setuptree
$ ls -1 /usr/src/rpmbuild/
BUILD
RPMS
SOURCES
SPECS
SRPMS
```

## SPEC
以降、/root/rpmbuild/をtopディレクトリとしている前提とします。

rpmの定義ファイルとなる、`.spec`ファイルを作成します。

```
$ vi /root/rpmbuild/SPECS/hello.spec
```

```yaml:hello.spec
%define name hello
%define version 1.0
%define unmangled_version 1.0
%define release 1
%define _binaries_in_noarch_packages_terminate_build 0
 
Summary: sample hello world program
Name: %{name}
Version: %{version}
Release: %{release}
License: MIT
Source0: %{name}-%{unmangled_version}.tar.gz
Group: Applications/File
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
 
%define INSTALLDIR %{buildroot}/usr/local/bin
 
%description
This is a sample program to learn how to make a rpm package.
 
%prep
rm -rf ${RPM_BUILD_ROOT}
 
%build
 
%install
rm -rf %{INSTALLDIR}
mkdir -p %{INSTALLDIR}
tar -C %{INSTALLDIR} -xzf  /root/rpmbuild/SOURCES/%{name}-%{unmangled_version}.tar.gz
 
%clean
rm -rf %{buildroot}
 
%files
/usr/local/bin
%defattr(-,root,root)
```

### インストール除外したいファイル、ディレクトリの指定方法
/optにパッケージをインストールした場合の例。
%{buildroot}より下から指定する。

ファイルの場合

```
%files
/opt
%defattr(-,root,root)
%exclude /opt/appname/.gitignore
```

アスタリスクも可能

```
%exclude /opt/appname/.git/*
```

ディレクトリも同じ要領で指定可能

```
%exclude /opt/appname/.git
```

### その他、設定値は詳しい内容は、以下を参照

- 参考：[SPECファイルの記述](https://vinelinux.org/docs/vine6/making-rpm/make-spec.html)
- それか、[以下](https://qiita.com/Esfahan/items/0aec0c6302fb77a94892#spec%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90)を参照


## Build
作成した`.spec`ファイルを使って、buildをします。
エラーが起きた場合は、適宜対処してください。

```shell-session
$ rpmbuild -bb /root/rpmbuild/SPECS/hello.spec

Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.5EA7HS
+ umask 022
+ cd /root/rpmbuild/BUILD
+ rm -rf /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.j7oCjB
+ umask 022
+ cd /root/rpmbuild/BUILD
+ exit 0
Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.lARvVj
+ umask 022
+ cd /root/rpmbuild/BUILD
+ '[' /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64 '!=' / ']'
+ rm -rf /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64
++ dirname /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64
+ mkdir -p /root/rpmbuild/BUILDROOT
+ mkdir /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64
+ rm -rf /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64/usr/local/bin
+ mkdir -p /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64/usr/local/bin
+ tar -C /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64/usr/local/bin -xzf /root/rpmbuild/SOURCES/hello-1.0.tar.gz
+ /usr/lib/rpm/check-buildroot
+ /usr/lib/rpm/redhat/brp-compress
+ /usr/lib/rpm/redhat/brp-strip /usr/bin/strip
+ /usr/lib/rpm/redhat/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump
+ /usr/lib/rpm/redhat/brp-strip-static-archive /usr/bin/strip
+ /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1
+ /usr/lib/rpm/redhat/brp-python-hardlink
+ /usr/lib/rpm/redhat/brp-java-repack-jars
Processing files: hello-1.0-1.noarch
Provides: hello = 1.0-1
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
warning: Arch dependent binaries in noarch package
Checking for unpackaged file(s): /usr/lib/rpm/check-files /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64
Wrote: /root/rpmbuild/RPMS/noarch/hello-1.0-1.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.t4IR5L
+ umask 022
+ cd /root/rpmbuild/BUILD
+ rm -rf /root/rpmbuild/BUILDROOT/hello-1.0-1.x86_64
+ exit 0
```

buildをすると、/rootの下に以下のディレクトリが生成されます。

```
BUILD
BUILDROOT
RPMS
SRPMS
```

また、以下のファイルが生成されます。

```shell-session
$ ls -l /root/rpmbuild/RPMS/noarch/hello-1.0-1.noarch.rpm
-rw-r--r-- 1 root root 370740 Aug 13 08:15 RPMS/noarch/hello-1.0-1.noarch.rpm
```

## Install
作成された`.noarch.rpm`ファイルを使ってインストールをします。

```shell-session
$ rpm -ivh /root/rpmbuild/RPMS/noarch/hello-1.0-1.noarch.rpm
Preparing...                          ################################# [100%]
Updating / installing...
   1:hello-1.0-1                      ################################# [100%]
```

## インストール確認

```shell-session
$ rpm -qa | grep hello
hello-1.0-1.noarch
 
$ rpm -qp /root/rpmbuild/RPMS/noarch/hello-1.0-1.noarch.rpm
hello-1.0-1.noarch
```

## Run
`hello`コマンドを実行して、`hello, world`と表示されれば成功です。

```shell-session
$ hello
hello, world
```

## Uninstall
アンイストールは以下の手順出来ます。

```shell-session 
$ rpm -ev hello-1.0-1.noarch
Preparing packages...
hello-1.0-1.noarch
```

## 応用
ソースファイル以外に、設定ファイルや、binファイルを、それぞれソースとは別のディレクトリに設置する例です。

### 要件

このようなディレクトリ構造のアプリケーションを、

```
sample_app
├── bin
├── conf
├── src
└── systemd
```

以下の様なディレクトリ構造で展開したい。

| 元 | インストール先 |
| --- | --- |
| bin/* | /usr/local/bin |
| conf/* | /etc/sample_app |
| src/* | /opt/sample_app |
| systemd/*  | /etc/systemd/system |

1行目の説明をすると、bin配下のものは全て/usr/local/bin配下に設置という意味です。

### tarファイルを作成

```
$ tar zcf /root/rpmbuild/SOURCES/sample_app.tar.gz sample_app/*
```


### specファイルを作成
/root/rpmbuild/SPECS/sample_app.spec

```yaml:sample_app.spec
%define name sample_app
%define version 1.0
# buildコマンドの引数で渡す。後述参照。version 1.0-1となる。
%define release 1%{?dist}
%define _binaries_in_noarch_packages_terminate_build 0

Summary: This is a sample python package.
Name: %{name}
Version: %{version}
Release: %{release}
License: MIT
Source0: %{name}.tar.gz
Group: Applications/File
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch

# BUILDディレクトリ
%define BUILD_DIR ${HOME}/rpmbuild/BUILD/sample_app
# ソースのインストールディレクトリ
%define INSTALL_DIR %{buildroot}/opt
# Pythonのsite-packagesのディレクトリ
%define PY_PKG_DIR %{INSTALL_DIR}/%{name}/site-packages
# binファイルを設置するディレクトリ
%define BIN_DIR %{buildroot}/usr/local/bin
# 設定ファイルを設置するディレクトリ
%define CONF_DIR %{buildroot}/etc/sample_app
# systemdを設置するディレクトリ
%define SYSD_DIR %{buildroot}/etc/systemd/system

%description
This is a sample python package.

# rpmをbuildする前の準備
%prep
rm -rf ${RPM_BUILD_ROOT}

# makeのための手順
%build

# installのための手順
%install
rm -rf %{INSTALL_DIR}
mkdir -p %{INSTALL_DIR}
mkdir -p %{PY_PKG_DIR}
mkdir -p %{BIN_DIR}
mkdir -p %{CONF_DIR}
mkdir -p %{SYSD_DIR}

# tarファイルを解凍
tar zxfv %{SOURCE0}

# 各ファイルを設置
mv %{BUILD_DIR}/src/*  %{INSTALL_DIR}/sample_app
mv %{BUILD_DIR}/bin/*  %{BIN_DIR}
mv %{BUILD_DIR}/conf/*  %{CONF_DIR}
mv %{BUILD_DIR}/systemd/*  %{SYSD_DIR}

# pipモジュールをインストール
cd %{INSTALL_DIR}/%{name}/pkg && PYTHONPATH='%{PY_PKG_DIR}' python setup.py install --install-lib %{PY_PKG_DIR}
pip install -r %{INSTALL_DIR}/%{name}/requirements.txt -t %{PY_PKG_DIR}

# rpmを作った後の処理
%clean
rm -rf %{buildroot}

# rpm展開後に行うコマンド
%post
systemctl enable service_name
systemctl start service_name

# rpmパッケージアンイストール後に実行するコマンド
# アンイストールのときだけ実行されるようにif文を追加
# 参考: https://qiita.com/ymko/items/80e78bfc8cbf4cee2186
%postun
if [ $1 -eq 0 ]; then
  rm -f /usr/local/bin/sample01
  rm -f /usr/local/bin/sample02
fi

# 設置するファイル。${RPM_BUILD_ROOT}より下から書く
%files
# ソースのインストールディレクトリ
/opt
# binファイルを設置するディレクトリ
/usr/local/bin
# 設定ファイルを設置するディレクトリ
/etc/sample_app
# systemdを設置するディレクトリ
/etc/systemd/system
# ディレクトリの権限
%defattr(-,root,root)
# 設置をしない、除外するディレクトリ
%exclude /opt/%{name}/wrapper/tests
%exclude /opt/%{name}/luigi/tests
# ${RPM_BUILD_ROOT}/usr/doc/sample_app-1.0-1にコピーされる
%doc README.md
```

### Build
`--define "dist .el7"`のオプションを付けることで、specファイル内の`%{?dist}`に値が渡ります。

```
$ sudo rpmbuild -bb /root/rpmbuild/SPECS/sample_app.spec --define "dist .el7"
```

ファイルが作成されたことを確認

```
$ sudo ls /root/rpmbuild/RPMS/noarch/
sample_app-1.0-1.el7.noarch.rpm
```


### Install

```
$ sudo rpm -ivh /root/rpmbuild/RPMS/noarch/sample_app-1.0-1.el7.noarch.rpm
```

これで、以下のそれぞれのディレクトリの下に、ファイルが展開されます。

- /usr/local/bin
- /etc/sample_app
- /opt/sample_app
- /etc/systemd/system

## 追記
python3でビルドする場合は、.specに以下を追記して、インタプリタをpython3にする必要あり。

```
%global __python %{__python3}
```

確認

```
%install
%{__python} -V
%{__python} -m pip -V
```

参考：https://fedoraproject.org/wiki/Archive:PackagingDrafts/Python3?rd=PackagingDrafts/Python3

## 参考
- [rpmbuild環境の構築](https://qiita.com/IK12_info/items/bcf695460363bae87eb9)
- http://www.kakiro-web.com/linux/rpmbuild.html
- [自作のコマンドをrpmパッケージ化 - specファイルの編集](https://qiita.com/gucci3/items/f9fbab8396295385541f)
- https://vinelinux.org/docs/vine6/making-rpm/make-spec.html
- [specファイル大解剖](https://qiita.com/mypaceshun/items/5067561d6739cc9e5199)