Introduction

Many administrators prefer deploying services using RPM packages on Kylin Advanced Server Operating System V11 due to their simplicity and efficiency. However, the default base and update repositories in KylinOS V11 often lack specific versions of RPM packages required for particular projects. In such scenarios, creating custom RPM packages becomes necessary to achieve package deployment flexibility.

This comprehensive guide walks through the complete process of building RPM packages from source code on KylinOS V11, using CRI-O version 1.35.0 as a practical demonstration. The workflow closely mirrors the V10 process, utilizing rpmbuild as the primary build tool.

Preparation Phase

Downloading CRI-O Source Code

Begin by obtaining the CRI-O source code from the official GitHub repository:

wget https://github.com/cri-o/cri-o/archive/refs/tags/v1.35.0.zip

This specific version serves as our example, but the process applies to any source package requiring RPM compilation.

Installing the Compilation Environment

Golang Installation

Since CRI-O is written in Go and version 1.35.0 requires Go 1.25.0, which isn't available in V11's yum repositories, we must install Go via binary distribution.

Step 1: Download the Go Binary Package

Select the appropriate architecture-specific package:

wget https://go.dev/dl/go1.25.0.linux-amd64.tar.gz

Step 2: Extract to System Directory

tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz

Step 3: Configure Environment Variables

Edit your bash configuration file:

vim ~/.bashrc

Add the following environment variable configurations:

# Go installation path (default)
export GOROOT=/usr/local/go

# Add executables to system PATH
export PATH=$PATH:$GOROOT/bin

# Workspace directory (for source code, compiled binaries, dependencies)
export GOPATH=$HOME/go

# Module cache path
export GOCACHE=$HOME/.cache/go-build

# Domestic module proxy (essential for fast dependency downloads in China)
export GOPROXY=https://goproxy.cn,direct

# Enable Go Modules (enabled by default in 1.16+)
export GO111MODULE=on

Step 4: Apply Configuration Changes

source ~/.bashrc

Step 5: Create Workspace Directories

cd $HOME/go
mkdir src bin pkg

Directory purposes:

  • src/: Source code storage
  • bin/: Compiled executables
  • pkg/: Dependency package cache

Step 6: Verify Installation

go version
go env

These commands confirm proper Go environment configuration.

Installing CRI-O Compilation Dependencies

Install the required system libraries and development tools:

yum install -y \
    containers-common \
    git \
    make \
    glib2-devel \
    glibc-devel \
    glibc-static \
    crun

Install Go dependencies:

go get github.com/cpuguy83/go-md2man

Install additional required libraries:

yum install -y \
    libassuan \
    libassuan-devel \
    libgpg-error \
    libseccomp-devel \
    libselinux \
    pkgconf-pkg-config \
    gpgme-devel

Installing RPM Build Environment

Install the RPM building tools:

yum install -y rpm-build rpmdevtools brp-chrpath

These packages provide the essential infrastructure for RPM package creation.

Building Process

Creating Standard rpmbuild Directory Structure

Execute the following command as a regular user (root privileges not required):

rpmdev-setuptree

This command generates the standard RPM build directory structure in your home directory:

~/rpmbuild/
├── BUILD       # Temporary compilation directory
├── BUILDROOT   # Installation root directory
├── RPMS        # Final generated RPM packages (primary output)
├── SOURCES     # Source code packages placed here
├── SPECS       # Compilation script spec files placed here
└── SRPMS       # Source RPM packages

Understanding each directory's purpose proves crucial for successful package building.

Preparing the Build Directory

Step 1: Place Source Code in SOURCES

Move the CRI-O source package to the SOURCES directory:

mv cri-o-1.35.0.zip ~/rpmbuild/SOURCES/

Step 2: Create the Spec File

Create the cri-o.spec file in the SPECS directory:

vim ~/rpmbuild/SPECS/cri-o.spec

The complete specification file configuration appears below:

# Disable automatic debuginfo package generation
%global debug_package %{nil}
%define _enable_debug_packages 0

Name: cri-o
Version: 1.35.0
Release: 1%{?dist}
Summary: Kubernetes CRI implementation for OCI

License: ASL 2.0
URL: https://cri-o.io/
# Note: Package name must match source package name in SOURCES directory
Source0: https://github.com/cri-o/cri-o/cri-o-%{version}.zip

# Build dependencies (required on compilation machine)
BuildRequires: golang >= 1.25.0
BuildRequires: make
BuildRequires: gcc

# Runtime dependencies (required on installation machine)
Requires: conmon >= 2.1
Requires: runc
Requires: container-selinux

%description
CRI-O is an implementation of the Kubernetes CRI (Container Runtime Interface)
to enable using OCI (Open Container Initiative) compatible runtime.

%prep
%setup -q

%build
# Compile CRI-O (official compilation command)
make

%install
# 1. Official installation command (critical - automatically installs all files)
make install DESTDIR=%{buildroot}

# 2. Create standard systemd directories (paths recognized by host system)
mkdir -p %{buildroot}/usr/lib/systemd/system

# 3. Move service files from default path to standard system path
mv %{buildroot}/usr/local/lib/systemd/system/crio.service %{buildroot}/usr/lib/systemd/system/
mv %{buildroot}/usr/local/lib/systemd/system/crio-wipe.service %{buildroot}/usr/lib/systemd/system/

# 4. Delete empty directories from default path (mandatory - prevents "unpacked files" errors)
rm -rf %{buildroot}/usr/local/lib/systemd

%files
# ===================== Binary Files =====================
/usr/local/bin/crio
/usr/local/bin/pinns

# ===================== systemd Services =====================
/usr/lib/systemd/system/crio.service
/usr/lib/systemd/system/crio-wipe.service

# ===================== Configuration Files =====================
%config(noreplace) /etc/crio/crio.conf
%dir /etc/crio/crio.conf.d
%config(noreplace) /etc/crictl.yaml

# ===================== Auxiliary Files =====================
/usr/local/share/oci-umount/oci-umount.d/crio-umount.conf
/usr/local/share/containers/oci/hooks.d

# ===================== Manual Pages =====================
/usr/local/share/man/man5/crio.conf.5*
/usr/local/share/man/man5/crio.conf.d.5*
/usr/local/share/man/man8/crio.8*

# ===================== Command Completion (bash/fish/zsh) =====================
/usr/local/share/bash-completion/completions/crio
/usr/local/share/fish/completions/crio.fish
/usr/local/share/zsh/site-functions/_crio

%changelog
* Wed Apr 08 2026 Packager - 1.35.0-1
- Initial build for cri-o 1.35.0

Understanding Spec File Sections

Header Section: Defines package metadata including name, version, release number, summary, license, and URL.

Source Declaration: Specifies the source code location. The %{version} variable automatically substitutes the Version field value.

BuildRequires: Lists dependencies needed during compilation. These must exist on the build machine.

Requires: Lists runtime dependencies. These must exist on machines where the package installs.

%description: Provides a detailed package description visible in package managers.

%prep: Prepares the build environment. The %setup -q command extracts the source archive quietly.

%build: Contains compilation commands. For CRI-O, the official make command suffices.

%install: Handles installation into the build root directory. Critical steps include:

  1. Running the official installation command with DESTDIR=%{buildroot}
  2. Creating standard systemd directories
  3. Moving service files to system-recognized paths
  4. Cleaning up empty directories to prevent packaging errors

%files: Lists all files to include in the final package. The %config(noreplace) directive marks configuration files that shouldn't be overwritten during upgrades.

%changelog: Documents package version history and changes.

Building the RPM Package

Execute the build command:

cd ~/rpmbuild/SPECS
rpmbuild -ba --nodeps cri-o.spec

The --nodeps flag proves essential in this scenario. Since our Go environment was installed via binary rather than RPM, omitting this parameter would cause rpmbuild to fail dependency checks, believing Go isn't installed.

Build Output

Upon successful completion, rpmbuild generates the following structure:

.
├── BUILD
├── BUILDROOT
├── RPMS
│   └── x86_64
│       └── cri-o-1.35.0-1.ky11.x86_64.rpm
├── SOURCES
│   └── cri-o-1.35.0.zip
├── SPECS
│   └── cri-o.spec
└── SRPMS
    └── cri-o-1.35.0-1.ky11.src.rpm

The final RPM package resides in RPMS/x86_64/, while the source RPM package locates in SRPMS/.

Installation and Testing

Package Installation

Install the generated RPM package:

yum -y install ./cri-o-1.35.0-1.ky11.x86_64.rpm

The yum command handles dependency resolution automatically.

Service Startup

Start the CRI-O service:

systemctl start crio.service

Enable the service for automatic startup on boot:

systemctl enable crio.service

Functional Testing

Test image pulling using crictl:

crictl pull swr.cn-east-3.myhuaweicloud.com/itho/docker.io/busybox:latest-amd64

Verify the image downloaded successfully:

crictl images

Expected output:

IMAGE                                                       TAG
swr.cn-east-3.myhuaweicloud.com/itho/docker.io/busybox      latest-amd64

If both the image pull and service status function correctly, the RPM package build and installation succeeded.

Troubleshooting Common Issues

Dependency Resolution Failures

If rpmbuild reports missing dependencies despite installation:

  1. Verify all BuildRequires packages are installed
  2. Check Go environment variables are properly configured
  3. Ensure --nodeps flag is used when appropriate

File Packaging Errors

"Unpacked files" errors typically indicate:

  1. Files installed to unexpected locations during %install
  2. Missing entries in the %files section
  3. Empty directories not properly cleaned up

Review the build log carefully to identify specific problematic paths.

Service Startup Failures

If the service fails to start:

  1. Check systemd service file paths match system expectations
  2. Verify all runtime dependencies are installed
  3. Review journal logs: journalctl -u crio.service
  4. Ensure configuration files contain valid settings

Build Environment Issues

For consistent build failures:

  1. Clean the build environment: rm -rf ~/rpmbuild/BUILD/*
  2. Verify source archive integrity
  3. Check available disk space
  4. Review spec file syntax for errors

Advanced Considerations

Spec File Optimization

For production deployments, consider:

  • Adding version detection logic for dynamic dependency versions
  • Implementing conditional compilation for different architectures
  • Including comprehensive documentation in the package
  • Adding post-install and post-uninstall scripts for cleanup

Automation Possibilities

The entire build process lends itself well to automation:

  • CI/CD pipeline integration for automatic package generation
  • Version detection and spec file generation scripts
  • Automated testing of generated packages
  • Repository management for package distribution

Future enhancements might include AI-assisted spec file generation, though current attempts reveal challenges with AI-generated spec files often containing outdated fields and instructions. However, combining AI generation with automated error recognition and correction shows promise for streamlining the process.

Conclusion

The complete RPM package building workflow on KylinOS V11 involves straightforward steps:

  1. Preparation: Download source code and install compilation environment
  2. Setup: Create standard rpmbuild directory structure
  3. Configuration: Write comprehensive spec file
  4. Build: Execute rpmbuild with appropriate flags
  5. Testing: Install and verify package functionality

While the process appears simple, spec file writing often requires iterative debugging to resolve build errors. The key lies in understanding each section's purpose and carefully managing file paths during the installation phase.

Mastering RPM package building provides significant advantages for system administrators managing KylinOS deployments, enabling custom package creation for applications unavailable in standard repositories or requiring specific versions not provided by default sources.

The skills developed through this process transfer to other RPM-based distributions, making this knowledge valuable across the broader Linux ecosystem.