From: <daniel.turull@ericsson.com>
To: <openembedded-core@lists.openembedded.org>
Cc: <JPEWhacker@gmail.com>, Daniel Turull <daniel.turull@ericsson.com>
Subject: [PATCH v4 2/3] create-spdx-3.0: record component release date in SPDX output
Date: Fri, 2 Oct 2026 09:54:13 +0200 [thread overview]
Message-ID: <20261002075414.2311840-3-daniel.turull@ericsson.com> (raw)
In-Reply-To: <20261002075414.2311840-1-daniel.turull@ericsson.com>
From: Daniel Turull <daniel.turull@ericsson.com>
Record each package's release date in the releaseTime property of its
software_Package object, using the SOURCE_DATE_EPOCH already computed
for reproducible builds.
Accuracy depends on how SOURCE_DATE_EPOCH was derived: exact for
git-tagged recipes, best-effort for tarball/http(s) sources. Some
Python sdists (e.g. cryptography, hypothesis, maturin) normalize all
file mtimes to a fixed placeholder, so their releaseTime reflects
packaging-tool behavior, not the real release date.
AI-Generated: Uses Kiro with Claude Sonnet 5
Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
---
v2:
- Dropped all options per Joshua's feedback; read SDE_FILE directly.
- Dropped the redundant else: delattr(recipe, "releaseTime") branch.
- Selftest compares against SDE_FILE content directly instead of
SOURCE_DATE_EPOCH, which can diverge from it.
- Fixed a leak: recipes with no git checkout and no fetched source
had SDE_FILE holding only SOURCE_DATE_EPOCH_FALLBACK, showing a
bogus 2011-04-05T23:00:00Z releaseTime instead of none.
v3:
- Also run after do_unpack: do_deploy_source_date_epoch's setscene
shortcut can skip it, leaving SOURCE_DATE_EPOCH unset.
- get_release_date() reads SOURCE_DATE_EPOCH again instead of
SDE_FILE, now that they're guaranteed equivalent.
- test_release_date_source_date_epoch: switched to tar (base-files
has S == UNPACKDIR and never gets a real SOURCE_DATE_EPOCH).
- Added test_release_date_omitted_for_fallback_value.
v4:
- Per Richard's feedback, moved releaseTime from do_create_recipe_spdx
to do_create_spdx to keep the recipe-only task fetch-free.
- Per Joshua's suggestion added into each downloaded source software_Package
- Updated selftests accordingly.
---
meta/lib/oe/spdx30_tasks.py | 22 +++++++++++++++
meta/lib/oeqa/selftest/cases/spdx.py | 41 ++++++++++++++++++++++++++++
2 files changed, 63 insertions(+)
diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
index b6456a214a..dc82f21fb0 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -36,6 +36,24 @@ def set_timestamp_now(d, o, prop):
delattr(o, prop)
+def get_release_date(d):
+ """Resolve the release date to record in a package's releaseTime property.
+
+ Uses SOURCE_DATE_EPOCH, which by this point (do_create_spdx runs after
+ do_unpack and do_deploy_source_date_epoch) reflects the source mtimes.
+ Omits the fallback value since it is not a meaningful release date.
+
+ Returns a datetime, or None if no release date should be recorded.
+ """
+ source_date_epoch = d.getVar("SOURCE_DATE_EPOCH")
+ if not source_date_epoch or source_date_epoch == d.getVar(
+ "SOURCE_DATE_EPOCH_FALLBACK"
+ ):
+ return None
+
+ return datetime.fromtimestamp(int(source_date_epoch), tz=timezone.utc)
+
+
def add_license_expression(
d, objset, license_expression, license_data, search_objsets=[]
):
@@ -441,6 +459,7 @@ def _enrich_source_package(d, dl, fd, file_name, primary_purpose):
def add_download_files(d, objset):
inputs = set()
+ release_date = get_release_date(d)
urls = d.getVar("SRC_URI").split()
fetch = bb.fetch.Fetch(urls, d)
@@ -504,6 +523,9 @@ def add_download_files(d, objset):
_enrich_source_package(d, dl, fd, file_name, primary_purpose)
+ if release_date is not None:
+ dl.releaseTime = release_date
+
if fd.method.supports_checksum(fd):
for checksum_id in bb.fetch.CHECKSUM_LIST:
if checksum_id not in oe.spdx30.HashAlgorithm.NAMED_INDIVIDUALS:
diff --git a/meta/lib/oeqa/selftest/cases/spdx.py b/meta/lib/oeqa/selftest/cases/spdx.py
index 8285189382..88c3eb6b15 100644
--- a/meta/lib/oeqa/selftest/cases/spdx.py
+++ b/meta/lib/oeqa/selftest/cases/spdx.py
@@ -6,6 +6,7 @@
import textwrap
import hashlib
+from datetime import datetime, timezone
from oeqa.selftest.case import OESelftestTestCase
from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars
import oe.spdx30
@@ -443,3 +444,43 @@ class SPDX30Check(SPDX3CheckBase, OESelftestTestCase):
r'\d',
f"Version '{version}' for package '{name}' should contain digits"
)
+
+ def test_release_date_source_date_epoch(self):
+ """releaseTime should be derived from SOURCE_DATE_EPOCH.
+
+ This is recorded on the downloaded source software_Package objects
+ produced by do_create_spdx (not the recipe-only do_create_recipe_spdx
+ task), since the release date can only be known once sources are
+ available and SOURCE_DATE_EPOCH has been computed.
+ """
+ # base-files has S == UNPACKDIR and never gets a real
+ # SOURCE_DATE_EPOCH, so use tar, which unpacks a real tarball.
+ objset = self.check_recipe_spdx(
+ "tar",
+ "{DEPLOY_DIR_SPDX}/{SSTATE_PKGARCH}/builds/build-tar.spdx.json",
+ )
+
+ source_date_epoch = get_bb_var("SOURCE_DATE_EPOCH", "tar")
+ expected = datetime.fromtimestamp(int(source_date_epoch), tz=timezone.utc)
+
+ pkg = None
+ for candidate in objset.foreach_type(oe.spdx30.software_Package):
+ if candidate.software_downloadLocation:
+ pkg = candidate
+ break
+
+ self.assertIsNotNone(pkg, "Unable to find downloaded source software_Package")
+ self.assertEqual(pkg.releaseTime, expected)
+
+ def test_release_date_omitted_without_downloaded_source(self):
+ """releaseTime only applies to downloaded source packages; recipes
+ with only local file:// sources have none, and get no releaseTime."""
+ # base-files only has file:// sources, so it has no downloaded
+ # source software_Package and thus no releaseTime anywhere.
+ objset = self.check_recipe_spdx(
+ "base-files",
+ "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/builds/build-base-files.spdx.json",
+ )
+
+ for candidate in objset.foreach_type(oe.spdx30.software_Package):
+ self.assertIsNone(candidate.releaseTime)
next prev parent reply other threads:[~2026-10-02 7:54 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-02 7:54 [PATCH v4 0/3] spdx: add support to include releaseTime daniel.turull
2026-10-02 7:54 ` [PATCH v4 1/3] classes/base: exclude __CACHED_SOURCE_DATE_EPOCH from task hash daniel.turull
2026-10-02 7:54 ` daniel.turull [this message]
2026-10-02 7:54 ` [PATCH v4 3/3] scripts/contrib: add spdx-release-date-report.py daniel.turull
2026-10-03 16:50 ` [OE-core] [PATCH v4 0/3] spdx: add support to include releaseTime Mathieu Dubois-Briand
2026-10-05 7:11 ` Daniel Turull
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261002075414.2311840-3-daniel.turull@ericsson.com \
--to=daniel.turull@ericsson.com \
--cc=JPEWhacker@gmail.com \
--cc=openembedded-core@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.