Skip to content
Open
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
36 changes: 15 additions & 21 deletions share/build/sbom-with-sde.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# build time.
#
# Keep a value that is already in the environment. When it is empty, use
# the last git commit time of SBOM_ROOT when that directory is the git
# toplevel. A tarball unpacked inside another checkout is not that
# toplevel, so the outer commit time is not used. A tree with no git
# the last commit time of the enclosing checkout when SBOM_ROOT holds
# files tracked there. A tarball unpacked into an untracked directory of
# another checkout does not take that checkout's time. A tree with no git
# leaves the variable empty, and gen-sbom uses its own default.
#
# Arguments after "--" are the driver command.
Expand All @@ -20,26 +20,20 @@ if(_sde STREQUAL "" AND NOT SBOM_ROOT STREQUAL "")
set(_sde "")
execute_process(
COMMAND "${_wolfssl_compliance_tooling_git}" -C "${SBOM_ROOT}"
rev-parse --show-toplevel
OUTPUT_VARIABLE _git_top
OUTPUT_STRIP_TRAILING_WHITESPACE
ls-files --error-unmatch -- .
OUTPUT_QUIET
ERROR_QUIET
RESULT_VARIABLE _git_rc)
if(_git_rc EQUAL 0 AND NOT _git_top STREQUAL "")
# REALPATH is available on CMake 3.16. file(REAL_PATH) needs 3.19.
get_filename_component(_root_real "${SBOM_ROOT}" REALPATH)
get_filename_component(_top_real "${_git_top}" REALPATH)
if(_root_real STREQUAL _top_real)
execute_process(
COMMAND "${_wolfssl_compliance_tooling_git}" -C "${SBOM_ROOT}"
log -1 --format=%ct
OUTPUT_VARIABLE _sde
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _git_rc)
if(NOT _git_rc EQUAL 0)
set(_sde "")
endif()
if(_git_rc EQUAL 0)
execute_process(
COMMAND "${_wolfssl_compliance_tooling_git}" -C "${SBOM_ROOT}"
log -1 --format=%ct
OUTPUT_VARIABLE _sde
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _git_rc)
if(NOT _git_rc EQUAL 0)
set(_sde "")
endif()
endif()
if(NOT _sde STREQUAL "")
Expand Down
28 changes: 24 additions & 4 deletions share/build/sbom.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ get_filename_component(_WOLFSSL_COMPLIANCE_TOOLING_DRIVER
"${_WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR}/../sbom-driver.py" ABSOLUTE)
find_package(Python3 COMPONENTS Interpreter)

# Fallbacks for a call from a directory that did not include() this file.
set_property(GLOBAL PROPERTY _WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR
"${CMAKE_CURRENT_LIST_DIR}")
set_property(GLOBAL PROPERTY _WOLFSSL_COMPLIANCE_TOOLING_PYTHON3
"${Python3_EXECUTABLE}")

function(wolfssl_compliance_tooling_add_sbom)
set(_opts NO_ARTIFACT_HASH SOURCE_ONLY NO_INSTALL)
set(_one NAME TARGET_NAME VERSION VERSION_FILE VERSION_MACRO LICENSE
Expand Down Expand Up @@ -166,14 +172,24 @@ function(wolfssl_compliance_tooling_add_sbom)
endforeach()
list(REMOVE_DUPLICATES _srcs)

if(NOT Python3_EXECUTABLE)
get_property(Python3_EXECUTABLE GLOBAL PROPERTY
_WOLFSSL_COMPLIANCE_TOOLING_PYTHON3)
endif()
if(NOT Python3_EXECUTABLE)
message(FATAL_ERROR
"wolfssl_compliance_tooling_add_sbom: python3 was not found")
endif()

set(_sbom_dir "${_WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR}")
if(NOT _sbom_dir)
get_property(_sbom_dir GLOBAL PROPERTY _WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR)
endif()
get_filename_component(_driver "${_sbom_dir}/../sbom-driver.py" ABSOLUTE)

set(_cmd ${CMAKE_COMMAND} -E env HOSTCC=${SB_HOSTCC}
${Python3_EXECUTABLE}
${_WOLFSSL_COMPLIANCE_TOOLING_DRIVER}
${_driver}
--name ${SB_NAME}
--root ${SB_ROOT}
--license-file ${SB_LICENSE}
Expand Down Expand Up @@ -305,7 +321,7 @@ function(wolfssl_compliance_tooling_add_sbom)
add_custom_target(${SB_TARGET_NAME}
COMMAND ${CMAKE_COMMAND}
-DSBOM_ROOT=${SB_ROOT}
-P "${_WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR}/sbom-with-sde.cmake"
-P "${_sbom_dir}/sbom-with-sde.cmake"
--
${_cmd}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
Expand Down Expand Up @@ -371,6 +387,10 @@ function(wolfssl_compliance_tooling_add_sbom_install)
if(NOT IS_ABSOLUTE "${_docdir}")
set(_docdir "${CMAKE_INSTALL_PREFIX}/${_docdir}")
endif()
set(_sbom_dir "${_WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR}")
if(NOT _sbom_dir)
get_property(_sbom_dir GLOBAL PROPERTY _WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR)
endif()

add_custom_target(${SI_INSTALL_TARGET}
COMMAND ${CMAKE_COMMAND}
Expand All @@ -383,7 +403,7 @@ function(wolfssl_compliance_tooling_add_sbom_install)
-DWOLFSSL_COMPLIANCE_TOOLING_SBOM_VERSION_FILE=${SI_VERSION_FILE}
-DWOLFSSL_COMPLIANCE_TOOLING_SBOM_VERSION_MACRO=${SI_VERSION_MACRO}
-DWOLFSSL_COMPLIANCE_TOOLING_SBOM_BINDIR=${SI_BINDIR}
-P "${_WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR}/install-sbom.cmake"
-P "${_sbom_dir}/install-sbom.cmake"
VERBATIM
COMMENT "Installing ${SI_NAME} SBOM to ${_docdir}")
add_dependencies(${SI_INSTALL_TARGET} ${SI_DEPENDS})
Expand All @@ -399,7 +419,7 @@ function(wolfssl_compliance_tooling_add_sbom_install)
-DWOLFSSL_COMPLIANCE_TOOLING_SBOM_VERSION_FILE=${SI_VERSION_FILE}
-DWOLFSSL_COMPLIANCE_TOOLING_SBOM_VERSION_MACRO=${SI_VERSION_MACRO}
-DWOLFSSL_COMPLIANCE_TOOLING_SBOM_BINDIR=${SI_BINDIR}
-P "${_WOLFSSL_COMPLIANCE_TOOLING_SBOM_DIR}/uninstall-sbom.cmake"
-P "${_sbom_dir}/uninstall-sbom.cmake"
VERBATIM
COMMENT "Uninstalling ${SI_NAME} SBOM from ${_docdir}")
endfunction()
2 changes: 1 addition & 1 deletion share/sbom.am
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ sbom:
-x c /dev/null > "$$_defines"; \
fi; \
if test -z "$${SOURCE_DATE_EPOCH:-}" && test -n "$(GIT)" && \
$(GIT) -C "$(srcdir)" rev-parse --git-dir >/dev/null 2>&1; then \
$(GIT) -C "$(srcdir)" ls-files --error-unmatch -- . >/dev/null 2>&1; then \
sde=`$(GIT) -C "$(srcdir)" log -1 --format=%ct 2>/dev/null`; \
if test -n "$$sde"; then SOURCE_DATE_EPOCH="$$sde"; export SOURCE_DATE_EPOCH; fi; \
fi; \
Expand Down
93 changes: 93 additions & 0 deletions tests/test_sbom_cmake_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,25 @@ def test_empty_value_uses_git_commit_time(self):
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), stamp.stdout.strip())

def test_tracked_subdirectory_uses_the_commit_time(self):
git = shutil.which("git")
if not git:
self.skipTest("git is not installed")
run([git, "init"], cwd=self.dir)
run([git, "config", "user.email", "sbom@example.invalid"], cwd=self.dir)
run([git, "config", "user.name", "sbom"], cwd=self.dir)
sub = self.dir / "lib"
sub.mkdir()
(sub / "x.c").write_text("int x;\n")
run([git, "add", "lib/x.c"], cwd=self.dir)
run([git, "commit", "-m", "init"], cwd=self.dir)
stamp = run([git, "log", "-1", "--format=%ct"], cwd=self.dir)
env = os.environ.copy()
env.pop("SOURCE_DATE_EPOCH", None)
result = self._print_sde(sub, env)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), stamp.stdout.strip())

def test_nested_directory_does_not_use_the_outer_commit(self):
# A release tarball unpacked inside another checkout must not
# take that checkout's commit time.
Expand Down Expand Up @@ -303,6 +322,80 @@ def test_source_only_sbom_target_builds(self):
self.assertTrue((build / "t-1.cdx.json").is_file())
self.assertTrue((build / "t-1.spdx.json").is_file())

def test_call_from_a_sibling_directory_builds(self):
# sbom.cmake is included in one subdirectory and the helper is
# called from another, where its directory variables are unset.
src = self.dir / "src"
(src / "inc").mkdir(parents=True)
(src / "app").mkdir()
(src / "app" / "x.c").write_text("int x(void) { return 1; }\n")
(src / "LICENSE").write_text("demo\n")
(src / "CMakeLists.txt").write_text("""
cmake_minimum_required(VERSION 3.16)
project(sbomtest C)
add_subdirectory(inc)
add_subdirectory(app)
""")
(src / "inc" / "CMakeLists.txt").write_text(
f'include("{BUILD / "sbom.cmake"}")\n')
(src / "app" / "CMakeLists.txt").write_text("""
add_library(x STATIC x.c)
wolfssl_compliance_tooling_add_sbom(
NAME t
VERSION 1
TARGETS x
SOURCE_ONLY
ROOT ${CMAKE_SOURCE_DIR}
LICENSE ${CMAKE_SOURCE_DIR}/LICENSE
INSTALL_DIR ${CMAKE_BINARY_DIR}/doc)
""")
build = self.dir / "build"
configured = run([
CMAKE, "-S", str(src), "-B", str(build), "-G", "Unix Makefiles",
])
self.assertEqual(configured.returncode, 0,
configured.stderr + configured.stdout)
for target in ("sbom", "install-sbom"):
built = run([CMAKE, "--build", str(build), "--target", target])
self.assertEqual(built.returncode, 0, built.stderr + built.stdout)
self.assertTrue((build / "app" / "t-1.cdx.json").is_file())
self.assertTrue((build / "doc" / "t-1.cdx.json").is_file())

def test_call_uses_its_own_directory_copy(self):
# A subproject that vendors its own copy must not redirect the
# top project's SBOM to that copy's driver.
src = self.dir / "src"
(src / "dep").mkdir(parents=True)
shutil.copytree(BUILD.parent, src / "dep" / "sbom")
(src / "x.c").write_text("int x(void) { return 1; }\n")
(src / "LICENSE").write_text("demo\n")
(src / "dep" / "CMakeLists.txt").write_text(
'include("${CMAKE_CURRENT_SOURCE_DIR}/sbom/build/sbom.cmake")\n')
(src / "CMakeLists.txt").write_text(f"""
cmake_minimum_required(VERSION 3.16)
project(sbomtest C)
include("{BUILD / "sbom.cmake"}")
add_subdirectory(dep)
add_library(x STATIC x.c)
wolfssl_compliance_tooling_add_sbom(
NAME t
VERSION 1
TARGETS x
SOURCE_ONLY
ROOT ${{CMAKE_CURRENT_SOURCE_DIR}}
LICENSE ${{CMAKE_CURRENT_SOURCE_DIR}}/LICENSE)
""")
build = self.dir / "build"
configured = run([
CMAKE, "-S", str(src), "-B", str(build), "-G", "Unix Makefiles",
])
self.assertEqual(configured.returncode, 0,
configured.stderr + configured.stdout)
text = (build / "CMakeFiles" / "sbom.dir" / "build.make").read_text()
self.assertIn(str(BUILD / "sbom-with-sde.cmake"), text)
self.assertIn(str(BUILD.parent / "sbom-driver.py"), text)
self.assertNotIn(str(src / "dep"), text)

def test_second_call_keeps_its_install_target_name(self):
build = self._configure(f"""
cmake_minimum_required(VERSION 3.16)
Expand Down
Loading