Skip to content

Commit

Permalink
Make eext builds reproducible
Browse files Browse the repository at this point in the history
Using the standard methods that make rpmbuild reproducible.

The idea here is to use the SOURCE_DATE_EPOCH environment to set build
times, and the _buildhost rpm macro to freeze the build host.

We set the following macros to 1 and enable the behavior described
below:
1. source_date_epoch_from_changelog: SOURCE_DATE_EPOCH is set by
   rpmbuild to match the the latest changelog entry in the spec file.
   Note that if there's no changelog SOURCE_DATE_EPOCH is not set. It's
   upto the users to make sure there's a changelog entry.
2. use_source_date_epoch_as_buildtime: This sets the build time as per
   thr variable.
3. clamp_mtime_to_source_date_epoch: All file mtimes are clamped to this
   value.

References:
https://rpm-software-management.github.io/rpm/manual/buildprocess.html#Reproducability
https://fedoraproject.org/wiki/Changes/ReproducibleBuildsClampMtimes
rpm-software-management/mock#692

Fixes: BUG925340
  • Loading branch information
aajith-arista committed Mar 15, 2024
1 parent dfa8940 commit 23df709
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 11 deletions.
34 changes: 27 additions & 7 deletions cmd/create_srpm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package cmd

import (
"fmt"
"os"
"path/filepath"
"testing"
Expand All @@ -12,11 +13,13 @@ import (
"github.com/stretchr/testify/require"

"code.arista.io/eos/tools/eext/testutil"
"code.arista.io/eos/tools/eext/util"
)

func testCreateSrpm(t *testing.T,
repoName string, expectedPkgName string, quiet bool,
expectedFiles []string,
expectedOutputFile string,
expectedTags map[string]string,
sources []string) {
t.Log("Create temporary working directory")
workingDir, err := os.MkdirTemp("", "createSrpm-wd-test")
Expand Down Expand Up @@ -59,17 +62,26 @@ func testCreateSrpm(t *testing.T,
testutil.RunCmd(t, rootCmd, args, quiet, true)

require.DirExists(t, expectedSrpmDestDir)
for _, filename := range expectedFiles {
path := filepath.Join(expectedSrpmDestDir, filename)
require.FileExists(t, path)
expectedPath := filepath.Join(expectedSrpmDestDir, expectedOutputFile)
require.FileExists(t, expectedPath)

for tag, expVal := range expectedTags {
qfField := fmt.Sprintf("--qf=%%{%s}", tag)
tagVal, rpmErr := util.CheckOutput("rpm", "-q", qfField, "-p", expectedPath)
require.NoError(t, rpmErr)
require.Equal(t, expVal, tagVal)
}
}

func TestCreateSrpmFromSrpm(t *testing.T) {
t.Log("Test createSrpm from SRPM")
testCreateSrpm(t,
"debugedit-1", "debugedit", false,
[]string{"debugedit-5.0-eng.src.rpm"},
"debugedit-5.0-eng.src.rpm",
map[string]string{
"BUILDHOST": testutil.ExpectedBuildHost,
"BUILDTIME": testutil.DebugeditChangeLogTs,
},
nil)
}

Expand All @@ -81,7 +93,11 @@ func TestCreateSrpmFromTarball(t *testing.T) {
}
testCreateSrpm(t,
"mrtparse-1", "mrtparse", true,
[]string{"mrtparse-2.0.1-deadbee_beefdea.src.rpm"},
"mrtparse-2.0.1-deadbee_beefdea.src.rpm",
map[string]string{
"BUILDHOST": testutil.ExpectedBuildHost,
"BUILDTIME": testutil.MrtParseChangeLogTs,
},
sources)
}

Expand All @@ -93,6 +109,10 @@ func TestCreateSrpmFromUnmodifiedSrpm(t *testing.T) {
}
testCreateSrpm(t,
"debugedit-2", "debugedit", true,
[]string{"debugedit-5.0-3.el9.deadbee_beefdea.src.rpm"},
"debugedit-5.0-3.el9.deadbee_beefdea.src.rpm",
map[string]string{
"BUILDHOST": testutil.ExpectedBuildHost,
"BUILDTIME": testutil.DebugeditChangeLogTs,
},
sources)
}
2 changes: 2 additions & 0 deletions cmd/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ func testMock(t *testing.T, setupSrcEnv bool) {
expectedTags := map[string]string{
"release": expectedRelease,
"distribution": expectedDistribution,
"BUILDHOST": testutil.ExpectedBuildHost,
"BUILDTIME": testutil.MrtParseChangeLogTs,
}
runMockAndVerify(t, destDir, repoName, expectedPkgName, false,
expectedRpmFiles, expectedTags)
Expand Down
3 changes: 3 additions & 0 deletions cmd/testData/mrtparse-1/spec/mrtparse.spec
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ information base contents, and is standardized in RFC6396. Programs like Quagga
%{python3_sitelib}/mrtparse/params.py*
%{python3_sitelib}/mrtparse/__pycache__/*cpython*

%changelog
* Fri Mar 15 2024 Arun Ajith S <[email protected]>
- Dummy changelog for tests
27 changes: 25 additions & 2 deletions configfiles/mock.cfg.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,34 @@ config_opts['use_bootstrap_image'] = False
config_opts['cleanup_on_failure'] = False
config_opts['cleanup_on_success'] = False

# Make sure static libraries are deterministic
config_opts['macros']['__brp_strip_static_archive'] = '/usr/lib/rpm/brp-strip-static-archive "%{__strip} -D"'
# We add in FILE_OFFSET_BITS for 32 bit to be able to handle larger inodes
config_opts['macros']['_preprocessor_defines'] = '-Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -Wp,-D_FILE_OFFSET_BITS=64'

# Reproducible builds - BEGIN

# Make sure static libraries are deterministic
config_opts['macros']['__brp_strip_static_archive'] = '/usr/lib/rpm/brp-strip-static-archive "%{__strip} -D"'

# If true, set the SOURCE_DATE_EPOCH environment variable
# to the timestamp of the topmost changelog entry
config_opts['macros']['source_date_epoch_from_changelog'] = '1'

# If true, make sure that buildtime in built rpms
# is set to the value of SOURCE_DATE_EPOCH.
# Is ignored when SOURCE_DATE_EPOCH is not set.
config_opts['macros']['use_source_date_epoch_as_buildtime'] = '1'


# If true, make sure that timestamps in built rpms
# are not later than the value of SOURCE_DATE_EPOCH.
# Is ignored when SOURCE_DATE_EPOCH is not set.
config_opts['macros']['clamp_mtime_to_source_date_epoch'] = '1'

# Freeze BUILDHOST in rpm metadata
config_opts['macros']['_buildhost'] = 'eext-buildhost'

# Reproducible builds - END

# Autogenerated common configuration
{{- range $key,$val := .DefaultCommonCfg}}
config_opts['{{$key}}'] = "{{$val}}"
Expand Down
14 changes: 14 additions & 0 deletions impl/create_srpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,13 @@ func (bldr *srpmBuilder) setupRpmbuildTree() error {
return nil
}

var reproducibleBuildMacros = map[string]string{
"source_date_epoch_from_changelog": "1",
"use_source_date_epoch_as_buildtime": "1",
"clamp_mtime_to_source_date_epoch": "1",
"_buildhost": "eext-buildhost",
}

func (bldr *srpmBuilder) build(prep bool) error {
bldr.log("starting")

Expand Down Expand Up @@ -461,6 +468,13 @@ func (bldr *srpmBuilder) build(prep bool) error {
"--define", fmt.Sprintf("eext_release %s", rpmReleaseMacro),
}...)
}

for macro, macroVal := range reproducibleBuildMacros {
rpmbuildArgs = append(rpmbuildArgs, []string{
"--define", fmt.Sprintf("%s %s", macro, macroVal),
}...)
}

rpmbuildArgs = append(rpmbuildArgs, specFile)

if err := util.RunSystemCmd("rpmbuild", rpmbuildArgs...); err != nil {
Expand Down
27 changes: 25 additions & 2 deletions impl/testData/expected-mock.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,34 @@ config_opts['use_bootstrap_image'] = False
config_opts['cleanup_on_failure'] = False
config_opts['cleanup_on_success'] = False

# Make sure static libraries are deterministic
config_opts['macros']['__brp_strip_static_archive'] = '/usr/lib/rpm/brp-strip-static-archive "%{__strip} -D"'
# We add in FILE_OFFSET_BITS for 32 bit to be able to handle larger inodes
config_opts['macros']['_preprocessor_defines'] = '-Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -Wp,-D_FILE_OFFSET_BITS=64'

# Reproducible builds - BEGIN

# Make sure static libraries are deterministic
config_opts['macros']['__brp_strip_static_archive'] = '/usr/lib/rpm/brp-strip-static-archive "%{__strip} -D"'

# If true, set the SOURCE_DATE_EPOCH environment variable
# to the timestamp of the topmost changelog entry
config_opts['macros']['source_date_epoch_from_changelog'] = '1'

# If true, make sure that buildtime in built rpms
# is set to the value of SOURCE_DATE_EPOCH.
# Is ignored when SOURCE_DATE_EPOCH is not set.
config_opts['macros']['use_source_date_epoch_as_buildtime'] = '1'


# If true, make sure that timestamps in built rpms
# are not later than the value of SOURCE_DATE_EPOCH.
# Is ignored when SOURCE_DATE_EPOCH is not set.
config_opts['macros']['clamp_mtime_to_source_date_epoch'] = '1'

# Freeze BUILDHOST in rpm metadata
config_opts['macros']['_buildhost'] = 'eext-buildhost'

# Reproducible builds - END

# Autogenerated common configuration
config_opts['resultdir'] = "{{.TestWorkingDir}}/work/pkg1/mock-x86_64/mock-results"
config_opts['root'] = "pkg1-x86_64"
Expand Down
10 changes: 10 additions & 0 deletions testutil/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ import (
"github.com/stretchr/testify/require"
)

const ExpectedBuildHost string = "eext-buildhost"

// $ date -d @1710460800
// Fri Mar 15 00:00:00 UTC 2024
const MrtParseChangeLogTs string = "1710460800"

// $ date -d @1628467200
// Mon Aug 9 00:00:00 UTC 2021
const DebugeditChangeLogTs string = "1628467200"

var r, w, rescueStdout *(os.File)

// SetupManifest used to setup a test manifest from testdata for manifest functionality testing
Expand Down

0 comments on commit 23df709

Please sign in to comment.