Hi Joshua, thanks for the quick review.
On Tue, 2026-09-22 at 09:29 -0600, Joshua Watt via lists.openembedded.org wrote: > > > On Tue, Sep 22, 2026 at 8:43 AM <[email protected]> wrote: > > From: Daniel Turull <[email protected]> > > > > Add SPDX_INCLUDE_RELEASE_DATE (default enabled), which records each > > recipe's release date in the releaseTime property of its > > software_Package object, using the SOURCE_DATE_EPOCH already computed > > for reproducible builds. SPDX_RELEASE_DATE_INCLUDE_PATCHES (default > > disabled) additionally considers the Date: header of applied patches, > > using the newest of SOURCE_DATE_EPOCH and all patch dates found; > > SPDX_COMPONENT_RELEASE_DATE overrides both. > > > > Tested with oe-selftest -r spdx (full suite, including the new > > release-date cases) with no regressions. > > > > AI-Generated: Uses Kiro with Claude Sonnet 5 > > Signed-off-by: Daniel Turull <[email protected]> > > --- > > meta/classes/create-spdx-3.0.bbclass | 20 +++- > > meta/lib/oe/spdx30_tasks.py | 65 ++++++++++++ > > meta/lib/oeqa/selftest/cases/spdx.py | 142 +++++++++++++++++++++++++++ > > 3 files changed, 226 insertions(+), 1 deletion(-) > > > > diff --git a/meta/classes/create-spdx-3.0.bbclass > > b/meta/classes/create-spdx-3.0.bbclass > > index 56fd01fd53..9955ffad85 100644 > > --- a/meta/classes/create-spdx-3.0.bbclass > > +++ b/meta/classes/create-spdx-3.0.bbclass > > @@ -163,6 +163,23 @@ SPDX_GIT_PURL_MAPPINGS[doc] = "A space separated list > > of domain:purl_type \ > > on gitlab.example.com to the pkg:gitlab PURL type. \ > > github.com is always mapped to pkg:github by default." > > > > +SPDX_INCLUDE_RELEASE_DATE ??= "1" > > +SPDX_INCLUDE_RELEASE_DATE[doc] = "If set to '1', record the release date > > of \ > > + each recipe in the software_Package releaseTime property, derived from > > \ > > + SOURCE_DATE_EPOCH. Set to '0' to omit the property entirely." > > > > > There's little value in making this optional. The stuff that is optional is > so because it's slow to calculate or makes the SBoM output much larger; this > does neither. > Ok. I'll make it not optional in a v2. > > + > > +SPDX_RELEASE_DATE_INCLUDE_PATCHES ??= "0" > > > > > Is this necessary? I don't feel that patches should really affect the > "release date" I got the request that the release date should be the last time that the package was modified, which includes the backported patches on top. That's why I made it optional, and off by default. First I wanted to also fallback at the git log date of the recipe but that made it not working with shallow clones. I can make a simpler version without the patches and a follow up with them. > > > +SPDX_RELEASE_DATE_INCLUDE_PATCHES[doc] = "If set to '1', also consider the > > \ > > + Date: header of applied patches in SRC_URI when determining the > > release \ > > + date recorded in releaseTime, using the newest of SOURCE_DATE_EPOCH > > and \ > > + all patch dates found. Has no effect if SPDX_INCLUDE_RELEASE_DATE is > > '0'." > > + > > +SPDX_COMPONENT_RELEASE_DATE ??= "" > > > > +SPDX_COMPONENT_RELEASE_DATE[doc] = "Overrides the release date recorded in > > the \ > > + releaseTime property for this component. Expected format is full ISO > > 8601 \ > > + UTC (YYYY-MM-DDTHH:MM:SSZ). Takes precedence over > > SPDX_INCLUDE_RELEASE_DATE \ > > + and SPDX_RELEASE_DATE_INCLUDE_PATCHES." > > > > > I don't really like having a variable for this; it feels like it's too easy > to bit-rot or just not get updated when a recipe is updated. I also don't > think falling back to Source Date Epoch necessarily makes sense for this; > it's better to have no release date than SDE (IMHO). Also, the way you are > calling this means that it will always use the fallback (see below). > Good point. I'll drop the variable. My idea is that one can potentially find the release date from another source, for example the official website in case the tarfile was created a few days before the release. But I agree it can rot quickly if people doesn't update it. > I think we *could* use the SDE library code to get the release time (calling > get_source_date_epoch_from_*), but instead of falling back to the SDE > fallback, just omit the releaseTime property > Ok. I'll look into how to do it. > > + > > IMAGE_CLASSES:append = " create-spdx-image-3.0" > > SDK_CLASSES += "create-spdx-sdk-3.0" > > > > @@ -192,7 +209,7 @@ python do_create_recipe_spdx() { > > import oe.spdx30_tasks > > oe.spdx30_tasks.create_recipe_spdx(d) > > } > > -addtask do_create_recipe_spdx > > +addtask do_create_recipe_spdx after do_deploy_source_date_epoch > > > > SSTATETASKS += "do_create_recipe_spdx" > > do_create_recipe_spdx[sstate-inputdirs] = "${SPDXRECIPEDEPLOY}" > > @@ -201,6 +218,7 @@ do_create_recipe_spdx[file-checksums] += > > "${SPDX3_DEP_FILES}" > > do_create_recipe_spdx[cleandirs] = "${SPDXRECIPEDEPLOY}" > > do_create_recipe_spdx[deptask] += "do_create_recipe_spdx" > > do_create_recipe_spdx[vardeps] += "${SPDX3_VAR_DEPS}" > > +do_create_recipe_spdx[vardeps] += "SPDX_INCLUDE_RELEASE_DATE > > SPDX_RELEASE_DATE_INCLUDE_PATCHES SPDX_COMPONENT_RELEASE_DATE" > > do_create_recipe_spdx[file-checksums] = > > "${@bb.fetch.get_checksum_file_list(d)}" > > > > python do_create_recipe_spdx_setscene () { > > diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py > > index 9978ae731c..e6340c3678 100644 > > --- a/meta/lib/oe/spdx30_tasks.py > > +++ b/meta/lib/oe/spdx30_tasks.py > > @@ -36,6 +36,65 @@ def set_timestamp_now(d, o, prop): > > delattr(o, prop) > > > > > > +def get_release_date(d): > > + """Resolve the release date to record in a recipe's releaseTime > > property. > > + > > + Returns a datetime, or None if no release date should be recorded. > > + """ > > + override = d.getVar("SPDX_COMPONENT_RELEASE_DATE") > > + if override: > > + try: > > + return datetime.strptime(override, > > "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc) > > + except ValueError: > > + bb.fatal( > > + "SPDX_COMPONENT_RELEASE_DATE value '%s' is not valid. " > > + "Expected format: YYYY-MM-DDTHH:MM:SSZ (e.g. > > 2024-03-15T12:00:00Z)" % override > > + ) > > + > > + if d.getVar("SPDX_INCLUDE_RELEASE_DATE") != "1": > > + return None > > + > > + source_date_epoch = d.getVar("SOURCE_DATE_EPOCH") > > + if not source_date_epoch: > > + return None > > + > > + release_date = datetime.fromtimestamp(int(source_date_epoch), > > tz=timezone.utc) > > + > > + if d.getVar("SPDX_RELEASE_DATE_INCLUDE_PATCHES") == "1": > > + # Only the static Date: header is used, never "git log" on the > > + # layer's repo, since that varies with clone depth/history. > > + for url in oe.patch.src_patches(d): > > + patch_path = bb.fetch.decodeurl(url)[2] > > + patch_date = _get_patch_date_header(patch_path) > > + if patch_date and patch_date > release_date: > > + release_date = patch_date > > + > > + return release_date > > + > > + > > +def _get_patch_date_header(patch_path): > > + """Parse the 'Date:' header from a git-format-patch style file, if > > + present, and return it as a timezone-aware datetime, or None.""" > > + from email.utils import parsedate_to_datetime > > + > > + try: > > + with open(patch_path, errors="replace") as f: > > + for line in f: > > + if line.startswith("Date:"): > > + try: > > + parsed = > > parsedate_to_datetime(line[len("Date:"):].strip()) > > + except (ValueError, TypeError): > > + return None > > + if parsed.tzinfo is None: > > + return None > > + return parsed.astimezone(timezone.utc) > > + if line.startswith("---") or line.startswith("diff --git"): > > + break > > + except OSError: > > + return None > > + return None > > + > > + > > def add_license_expression( > > d, objset, license_expression, license_data, search_objsets=[] > > ): > > @@ -631,6 +690,12 @@ def create_recipe_spdx(d): > > if val := d.getVar("DESCRIPTION"): > > recipe.description = val > > > > + release_date = get_release_date(d) > > + if release_date is not None: > > + recipe.releaseTime = release_date > > + else: > > + delattr(recipe, "releaseTime") > > + > > > > > This can't really function here as expected; do_create_recipe_spdx runs > before do_unpack so it will never have access to sources. I think this means > it will only use the fallback source date epoch. I have added modified the addtask do_create_recipe_spdx after do_deploy_source_date_epoch. Will this cause problems? > > > > for cpe_id in oe.cve_check.get_cpe_ids( > > d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION") > > ): > > diff --git a/meta/lib/oeqa/selftest/cases/spdx.py > > b/meta/lib/oeqa/selftest/cases/spdx.py > > index 8285189382..3b6d02d38c 100644 > > --- a/meta/lib/oeqa/selftest/cases/spdx.py > > +++ b/meta/lib/oeqa/selftest/cases/spdx.py > > @@ -6,8 +6,11 @@ > > > > import textwrap > > import hashlib > > +import os > > +from datetime import datetime, timezone > > from oeqa.selftest.case import OESelftestTestCase > > from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars > > +import oeqa.utils.ftools as ftools > > import oe.spdx30 > > > > > > @@ -443,3 +446,142 @@ 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 by > > default.""" > > + objset = self.check_recipe_spdx( > > + "base-files", > > + > > "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/static/static-base-files.spdx.json", > > + task="create_recipe_spdx", > > + ) > > + > > + # Query after the build so the do_unpack stamp file exists. > > + source_date_epoch = get_bb_var("SOURCE_DATE_EPOCH", "base-files") > > + expected = datetime.fromtimestamp(int(source_date_epoch), > > tz=timezone.utc) > > + > > + recipe = None > > + for pkg in objset.foreach_type(oe.spdx30.software_Package): > > + if pkg.name == "base-files": > > + recipe = pkg > > + break > > + > > + self.assertIsNotNone(recipe, "Unable to find base-files > > software_Package") > > + self.assertEqual(recipe.releaseTime, expected) > > + > > + def test_release_date_disabled(self): > > + """SPDX_INCLUDE_RELEASE_DATE = "0" should omit releaseTime > > entirely.""" > > + objset = self.check_recipe_spdx( > > + "base-files", > > + > > "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/static/static-base-files.spdx.json", > > + task="create_recipe_spdx", > > + extraconf="""\ > > + SPDX_INCLUDE_RELEASE_DATE = "0" > > + """, > > + ) > > + > > + recipe = None > > + for pkg in objset.foreach_type(oe.spdx30.software_Package): > > + if pkg.name == "base-files": > > + recipe = pkg > > + break > > + > > + self.assertIsNotNone(recipe, "Unable to find base-files > > software_Package") > > + self.assertIsNone( > > + recipe.releaseTime, > > + "releaseTime should not be set when SPDX_INCLUDE_RELEASE_DATE > > is '0'", > > + ) > > + > > + def test_release_date_override(self): > > + """SPDX_COMPONENT_RELEASE_DATE overrides the resolved date.""" > > + override_date = "2020-01-01T00:00:00Z" > > + > > + objset = self.check_recipe_spdx( > > + "base-files", > > + > > "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/static/static-base-files.spdx.json", > > + task="create_recipe_spdx", > > + extraconf=f"""\ > > + SPDX_COMPONENT_RELEASE_DATE = "{override_date}" > > + """, > > + ) > > + > > + recipe = None > > + for pkg in objset.foreach_type(oe.spdx30.software_Package): > > + if pkg.name == "base-files": > > + recipe = pkg > > + break > > + > > + self.assertIsNotNone(recipe, "Unable to find base-files > > software_Package") > > + self.assertEqual( > > + recipe.releaseTime, > > + datetime(2020, 1, 1, tzinfo=timezone.utc), > > + ) > > + > > + def _write_test_patch(self, recipe, date_header): > > + """Write a no-op patch against a fixture file (not part of the > > + recipe's real source, so it isn't coupled to its content) with the > > + given Date: header, and point FILESEXTRAPATHS at it.""" > > + inc_file = self.write_recipeinc( > > + recipe, > > + textwrap.dedent( > > + """\ > > + FILESEXTRAPATHS:prepend := "${THISDIR}/files:" > > + SRC_URI += "file://release-date-test-file" > > + SRC_URI += "file://release-date-test.patch" > > + """ > > + ), > > + ) > > + patch_dir = os.path.join(os.path.dirname(inc_file), "files") > > + os.makedirs(patch_dir, exist_ok=True) > > + ftools.write_file( > > + os.path.join(patch_dir, "release-date-test-file"), > > + "original content\n", > > + ) > > + patch_path = os.path.join(patch_dir, "release-date-test.patch") > > + ftools.write_file( > > + patch_path, > > + textwrap.dedent( > > + f"""\ > > + From: Test Author <[email protected]> > > + Date: {date_header} > > + Subject: [PATCH] modify release-date-test-file > > + > > + Upstream-Status: Inappropriate [test patch, not intended > > for upstream] > > + > > + --- > > + --- a/release-date-test-file > > + +++ b/release-date-test-file > > + @@ -1 +1 @@ > > + -original content > > + +patched content > > + """ > > + ), > > + ) > > + self.track_for_cleanup(patch_dir) > > + return patch_path > > + > > + def test_release_date_include_patches(self): > > + """SPDX_RELEASE_DATE_INCLUDE_PATCHES = "1" makes a newer patch > > + Date: header win over SOURCE_DATE_EPOCH.""" > > + newer_patch_date = "Mon, 15 Jun 2099 00:00:00 +0000" > > + > > + self._write_test_patch("base-files", newer_patch_date) > > + self.add_command_to_tearDown("bitbake -c clean base-files") > > + > > + objset = self.check_recipe_spdx( > > + "base-files", > > + > > "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/static/static-base-files.spdx.json", > > + task="create_recipe_spdx", > > + extraconf="""\ > > + SPDX_RELEASE_DATE_INCLUDE_PATCHES = "1" > > + """, > > + ) > > + recipe = None > > + for pkg in objset.foreach_type(oe.spdx30.software_Package): > > + if pkg.name == "base-files": > > + recipe = pkg > > + break > > + self.assertIsNotNone(recipe, "Unable to find base-files > > software_Package") > > + self.assertEqual( > > + recipe.releaseTime, > > + datetime(2099, 6, 15, tzinfo=timezone.utc), > > + ) > -=-=-=-=-=-=-=-=-=-=-=- > Thanks Daniel
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#246450): https://lists.openembedded.org/g/openembedded-core/message/246450 Mute This Topic: https://lists.openembedded.org/mt/121375938/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
