Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions relenv/build/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
finalize,
create_archive,
patch_file,
update_sbom_checksums,
)

from .builder import (
Expand All @@ -41,6 +42,7 @@
"create_archive",
"update_ensurepip",
"patch_file",
"update_sbom_checksums",
# Builders (specific build functions)
"build_openssl",
"build_openssl_fips",
Expand Down
78 changes: 78 additions & 0 deletions relenv/build/common/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from __future__ import annotations

import fnmatch
import hashlib
import io
import json
import logging
import os
import os.path
Expand Down Expand Up @@ -75,6 +77,82 @@ def patch_file(path: PathLike, old: str, new: str) -> None:
fp.write(new_content)


def update_sbom_checksums(
source_dir: PathLike, files_to_update: MutableMapping[str, PathLike]
) -> None:
"""
Update checksums in sbom.spdx.json for modified files.

Python 3.12+ includes an SBOM (Software Bill of Materials) that tracks
file checksums. When we update files (e.g., expat sources), we need to
recalculate their checksums.

:param source_dir: Path to the Python source directory
:type source_dir: PathLike
:param files_to_update: Mapping of SBOM relative paths to actual file paths
:type files_to_update: MutableMapping[str, PathLike]
"""
source_path = pathlib.Path(source_dir)
spdx_json = source_path / "Misc" / "sbom.spdx.json"

# SBOM only exists in Python 3.12+
if not spdx_json.exists():
log.debug("SBOM file not found, skipping checksum updates")
return

# Read the SBOM JSON
with open(spdx_json, "r") as f:
data = json.load(f)

# Compute checksums for each file
checksums = {}
for relative_path, file_path in files_to_update.items():
file_path = pathlib.Path(file_path)
if not file_path.exists():
log.warning("File not found for checksum: %s", file_path)
continue

# Compute SHA1 and SHA256
sha1 = hashlib.sha1()
sha256 = hashlib.sha256()
with open(file_path, "rb") as f:
content = f.read()
sha1.update(content)
sha256.update(content)

checksums[relative_path] = [
{
"algorithm": "SHA1",
"checksumValue": sha1.hexdigest(),
},
{
"algorithm": "SHA256",
"checksumValue": sha256.hexdigest(),
},
]
log.debug(
"Computed checksums for %s: SHA1=%s, SHA256=%s",
relative_path,
sha1.hexdigest(),
sha256.hexdigest(),
)

# Update the SBOM with new checksums
updated_count = 0
for file_entry in data.get("files", []):
file_name = file_entry.get("fileName")
if file_name in checksums:
file_entry["checksums"] = checksums[file_name]
updated_count += 1
log.info("Updated SBOM checksums for %s", file_name)

# Write back the updated SBOM
with open(spdx_json, "w") as f:
json.dump(data, f, indent=2)

log.info("Updated %d file checksums in SBOM", updated_count)


def patch_shebang(path: PathLike, old: str, new: str) -> bool:
"""
Replace a file's shebang.
Expand Down
31 changes: 25 additions & 6 deletions relenv/build/darwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
"""
from __future__ import annotations

import glob
import io
import os
import pathlib
import shutil
import tarfile
import time
import urllib.request
from typing import IO, MutableMapping

from ..common import DARWIN, MACOS_DEVELOPMENT_TARGET, arches, runcmd
Expand All @@ -17,6 +24,7 @@
builds,
finalize,
get_dependency_version,
update_sbom_checksums,
)

ARCHES = arches[DARWIN]
Expand Down Expand Up @@ -52,12 +60,6 @@ def update_expat(dirs: Dirs, env: MutableMapping[str, str]) -> None:
Python ships with an older bundled expat. This function updates it
to the latest version for security and bug fixes.
"""
import pathlib
import shutil
import glob
import urllib.request
import tarfile

# Get version from JSON
expat_info = get_dependency_version("expat", "darwin")
if not expat_info:
Expand All @@ -84,13 +86,30 @@ def update_expat(dirs: Dirs, env: MutableMapping[str, str]) -> None:

# Copy source files to Modules/expat/
expat_source_dir = tmpbuild / f"expat-{version}" / "lib"
updated_files = []
for source_file in ["*.h", "*.c"]:
for file_path in glob.glob(str(expat_source_dir / source_file)):
target_file = expat_dir / pathlib.Path(file_path).name
# Remove old file if it exists
if target_file.exists():
target_file.unlink()
shutil.copy2(file_path, str(expat_dir))
updated_files.append(target_file)

# Touch all updated files to ensure make rebuilds them
# (The tarball may contain files with newer timestamps)
now = time.time()
for target_file in updated_files:
os.utime(target_file, (now, now))

# Update SBOM with correct checksums for updated expat files
files_to_update = {}
for target_file in updated_files:
# SBOM uses relative paths from Python source root
relative_path = f"Modules/expat/{target_file.name}"
files_to_update[relative_path] = target_file

update_sbom_checksums(dirs.source, files_to_update)


def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> None:
Expand Down
29 changes: 22 additions & 7 deletions relenv/build/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
"""
from __future__ import annotations

import glob
import io
import os
import pathlib
import shutil
import tarfile
import tempfile
import time
import urllib.request
from typing import IO, MutableMapping

from .common import (
Expand All @@ -21,6 +25,7 @@
builds,
finalize,
get_dependency_version,
update_sbom_checksums,
)
from ..common import LINUX, Version, arches, runcmd

Expand Down Expand Up @@ -367,13 +372,6 @@ def update_expat(dirs: Dirs, env: EnvMapping) -> None:
Python ships with an older bundled expat. This function updates it
to the latest version for security and bug fixes.
"""
from .common import get_dependency_version
import urllib.request
import tarfile
import glob
import pathlib
import shutil

# Get version from JSON
expat_info = get_dependency_version("expat", "linux")
if not expat_info:
Expand All @@ -400,13 +398,30 @@ def update_expat(dirs: Dirs, env: EnvMapping) -> None:

# Copy source files to Modules/expat/
expat_source_dir = tmpbuild / f"expat-{version}" / "lib"
updated_files = []
for source_file in ["*.h", "*.c"]:
for file_path in glob.glob(str(expat_source_dir / source_file)):
target_file = expat_dir / pathlib.Path(file_path).name
# Remove old file if it exists
if target_file.exists():
target_file.unlink()
shutil.copy2(file_path, str(expat_dir))
updated_files.append(target_file)

# Touch all updated files to ensure make rebuilds them
# (The tarball may contain files with newer timestamps)
now = time.time()
for target_file in updated_files:
os.utime(target_file, (now, now))

# Update SBOM with correct checksums for updated expat files
files_to_update = {}
for target_file in updated_files:
# SBOM uses relative paths from Python source root
relative_path = f"Modules/expat/{target_file.name}"
files_to_update[relative_path] = target_file

update_sbom_checksums(dirs.source, files_to_update)


def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None:
Expand Down
Loading
Loading