feat: add Requires(Post) for RPMS (#1085)

* feat: add Requires(Post) for RPMS
https://rpm-software-management.github.io/rpm/manual/spec.html section Dependencies

* test: add acceptance test

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Ted Delin
2026-06-18 05:51:31 +02:00
committed by GitHub
parent 1bd2fed8fb
commit 64b788a887
8 changed files with 130 additions and 0 deletions
+1
View File
@@ -202,6 +202,7 @@ func TestRPMSpecific(t *testing.T) {
"release",
"directories",
"verify",
"postrequires",
}
for _, name := range testNames {
for _, arch := range formatArchs[format] {
+8
View File
@@ -220,6 +220,7 @@ func (c *Config) expandEnvVars() {
c.Overrides[or].Recommends = c.expandEnvVarsStringSlice(c.Overrides[or].Recommends)
c.Overrides[or].Provides = c.expandEnvVarsStringSlice(c.Overrides[or].Provides)
c.Overrides[or].Suggests = c.expandEnvVarsStringSlice(c.Overrides[or].Suggests)
c.Overrides[or].RPM.Requires.Post = c.expandEnvVarsStringSlice(c.Overrides[or].RPM.Requires.Post)
c.Overrides[or].Contents = c.expandEnvVarsContents(c.Overrides[or].Contents)
}
c.Conflicts = c.expandEnvVarsStringSlice(c.Conflicts)
@@ -228,6 +229,7 @@ func (c *Config) expandEnvVars() {
c.Recommends = c.expandEnvVarsStringSlice(c.Recommends)
c.Provides = c.expandEnvVarsStringSlice(c.Provides)
c.Suggests = c.expandEnvVarsStringSlice(c.Suggests)
c.RPM.Requires.Post = c.expandEnvVarsStringSlice(c.RPM.Requires.Post)
c.Contents = c.expandEnvVarsContents(c.Contents)
// Basic metadata fields
@@ -392,6 +394,7 @@ type RPM struct {
Arch string `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"title=architecture in rpm nomenclature"`
BuildHost string `yaml:"buildhost,omitempty" json:"buildhost,omitempty" jsonschema:"title=host name of the build environment, default=os.Hostname()"`
Scripts RPMScripts `yaml:"scripts,omitempty" json:"scripts,omitempty" jsonschema:"title=rpm-specific scripts"`
Requires RPMRequires `yaml:"requires,omitempty" json:"requires,omitempty" jsonschema:"title=rpm-specific requires"`
Group string `yaml:"group,omitempty" json:"group,omitempty" jsonschema:"title=package group,example=Unspecified"`
Summary string `yaml:"summary,omitempty" json:"summary,omitempty" jsonschema:"title=package summary"`
Compression string `yaml:"compression,omitempty" json:"compression,omitempty" jsonschema:"title=compression algorithm to be used,enum=gzip,enum=lzma,enum=xz,enum=zstd,default=gzip:-1"`
@@ -407,6 +410,11 @@ type RPMScripts struct {
Verify string `yaml:"verify,omitempty" json:"verify,omitempty" jsonschema:"title=verify script"`
}
// RPMRequires represents qualified RPM Requires dependencies.
type RPMRequires struct {
Post []string `yaml:"post,omitempty" json:"post,omitempty" jsonschema:"title=post requires directive,example=nfpm"`
}
type PackageSignature struct {
// PGP secret key, can be ASCII-armored
KeyFile string `yaml:"key_file,omitempty" json:"key_file,omitempty" jsonschema:"title=key file,example=key.gpg"`
+19
View File
@@ -30,6 +30,9 @@ const (
tagChangelogText = 1082
// https://github.com/rpm-software-management/rpm/blob/master/include/rpm/rpmtag.h#L183
tagSourcePackage = 1106
// RPMSENSE_SCRIPT_POST marks a dependency as Requires(post).
// https://github.com/rpm-software-management/rpm/blob/master/include/rpm/rpmds.h
rpmSenseScriptPost = 1 << 10
// Symbolic link
tagLink = 0o120000
@@ -248,6 +251,9 @@ func buildRPMMeta(info *nfpm.Info) (*rpmpack.RPMMetaData, error) {
if depends, err = toRelation(info.Depends); err != nil {
return nil, err
}
if err = addPostRequires(&depends, info.RPM.Requires.Post); err != nil {
return nil, err
}
if recommends, err = toRelation(info.Recommends); err != nil {
return nil, err
}
@@ -328,6 +334,19 @@ func toRelation(items []string) (rpmpack.Relations, error) {
return relations, nil
}
func addPostRequires(relations *rpmpack.Relations, items []string) error {
for idx := range items {
relation, err := rpmpack.NewRelation(items[idx])
if err != nil {
return err
}
relation.Sense |= rpmSenseScriptPost
*relations = append(*relations, relation)
}
return nil
}
func addScriptFiles(info *nfpm.Info, rpm *rpmpack.RPM) error {
if info.RPM.Scripts.PreTrans != "" {
data, err := os.ReadFile(info.RPM.Scripts.PreTrans)
+50
View File
@@ -22,6 +22,12 @@ import (
"github.com/stretchr/testify/require"
)
const (
tagRequireFlags = 1048
tagRequireName = 1049
tagRequireVersion = 1050
)
func exampleInfo() *nfpm.Info {
return setDefaults(nfpm.WithDefaults(&nfpm.Info{
Name: "foo",
@@ -154,6 +160,50 @@ func TestRPM(t *testing.T) {
require.Equal(t, "Foo does things", description)
}
func TestRPMPostRequires(t *testing.T) {
info := exampleInfo()
info.RPM.Requires.Post = []string{"systemd", "coreutils >= 9.0"}
var buf bytes.Buffer
err := DefaultRPM.Package(info, &buf)
require.NoError(t, err)
rpm, err := rpmutils.ReadRpm(&buf)
require.NoError(t, err)
namesRaw, err := rpm.Header.Get(tagRequireName)
require.NoError(t, err)
names, ok := namesRaw.([]string)
require.True(t, ok)
versionsRaw, err := rpm.Header.Get(tagRequireVersion)
require.NoError(t, err)
versions, ok := versionsRaw.([]string)
require.True(t, ok)
flagsRaw, err := rpm.Header.Get(tagRequireFlags)
require.NoError(t, err)
flags, ok := flagsRaw.([]uint32)
require.True(t, ok)
requireRPMRequire(t, names, versions, flags, "systemd", "", rpmSenseScriptPost)
requireRPMRequire(t, names, versions, flags, "coreutils", "9.0", rpmSenseScriptPost|4|8)
}
func requireRPMRequire(
t *testing.T,
names, versions []string,
flags []uint32,
name, version string,
flag uint32,
) {
t.Helper()
for idx := range names {
if names[idx] == name && versions[idx] == version && flags[idx] == flag {
return
}
}
require.Failf(t, "RPM require not found", "name=%s version=%s flag=%d", name, version, flag)
}
func TestRPMRiscv64(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "test-riscv.rpm")
require.NoError(t, err)
+6
View File
@@ -226,3 +226,9 @@ FROM min AS verify
RUN rpm -V foo
RUN rm /tmp/postinstall-proof
RUN ! rpm -V foo
# ---- postrequires test ----
FROM min AS postrequires
RUN rpm -qp --qf '[%{REQUIRENAME} %{REQUIREFLAGS} %{REQUIREVERSION}\n]' /tmp/foo.rpm
RUN rpm -qp --qf '[%{REQUIRENAME} %{REQUIREFLAGS} %{REQUIREVERSION}\n]' /tmp/foo.rpm | grep -E '^bash 1024 ?$'
RUN rpm -qp --qf '[%{REQUIRENAME} %{REQUIREFLAGS} %{REQUIREVERSION}\n]' /tmp/foo.rpm | grep -E '^coreutils 1036 9\.0$'
+20
View File
@@ -0,0 +1,20 @@
name: "foo"
arch: "${BUILD_ARCH}"
platform: "linux"
version: "v1.2.3"
maintainer: "Foo Bar"
release: "4"
description: |
Foo bar
Multiple lines
vendor: "foobar"
homepage: "https://foobar.org"
license: "MIT"
contents:
- src: ./testdata/fake
dst: /usr/bin/fake
rpm:
requires:
post:
- bash
- coreutils >= 9.0
+6
View File
@@ -340,6 +340,12 @@ rpm:
# The verify script runs when verifying packages using `rpm -V`.
verify: ./scripts/verify.sh
# RPM specific qualified Requires dependencies.
requires:
# Adds `Requires(post): systemd`.
post:
- systemd
# The package group. This option is deprecated by most distros
# but required by old distros like CentOS 5 / EL 5 and earlier.
group: Unspecified
+20
View File
@@ -974,6 +974,10 @@
"$ref": "#/$defs/RPMScripts",
"title": "rpm-specific scripts"
},
"requires": {
"$ref": "#/$defs/RPMRequires",
"title": "rpm-specific requires"
},
"group": {
"type": "string",
"title": "package group",
@@ -1015,6 +1019,22 @@
"additionalProperties": false,
"type": "object"
},
"RPMRequires": {
"properties": {
"post": {
"items": {
"type": "string",
"examples": [
"nfpm"
]
},
"type": "array",
"title": "post requires directive"
}
},
"additionalProperties": false,
"type": "object"
},
"RPMScripts": {
"properties": {
"pretrans": {