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
6 changes: 5 additions & 1 deletion .github/workflows/selftest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ jobs:
tests/test_compdb_sbom.py \
tests/test_sbom_cmake_install.py \
tests/test_wolfssl_compliance_tooling_sync.py \
tests/test_sbom_autotools.py
tests/test_sbom_autotools.py \
tests/test_sbom_mk.py

- name: Run generator unit tests
run: python -m unittest tests/test_gen_sbom.py
Expand Down Expand Up @@ -99,5 +100,8 @@ jobs:
- name: Run autotools sbom.am test
run: python -m unittest tests/test_sbom_autotools.py

- name: Run plain-Make sbom.mk test
run: python -m unittest tests/test_sbom_mk.py

- name: Run self-test
run: python tests/test_sbom.py
172 changes: 90 additions & 82 deletions share/build/sbom.mk
Original file line number Diff line number Diff line change
Expand Up @@ -133,91 +133,99 @@ SBOM_ROOT ?= $(CURDIR)
SBOM_LICENSE_FILE ?= $(SBOM_ROOT)/LICENSE
HOSTCC ?= cc

# wolfSSL's source check rejects literal tabs in .mk files. Recipe lines in
# the rule below start with $(SBOM_TAB) instead. $(call) expands it before
# $(eval) parses the rule, so make still sees a tab.
override SBOM_TAB := $(shell printf '\t')
ifeq ($(SBOM_TAB),)
$(error sbom.mk needs a POSIX shell with printf to build its recipes)
endif

define wolfssl_compliance_tooling_sbom_rule
Comment thread
Frauschi marked this conversation as resolved.
Resolved
.PHONY: $(1)
$(1): $($(2)PREREQS)
@test -n "$($(2)NAME)" || { echo "ERROR: set $(2)NAME"; exit 1; }
@if [ "$($(2)NO_ARTIFACT_HASH)" = "1" ]; then \
test -z "$(strip $($(2)SRCS))$($(2)LIB)" || \
{ echo "ERROR: $(2)NO_ARTIFACT_HASH stands alone. Do not set $(2)SRCS or $(2)LIB."; exit 1; }; \
else \
test -n "$(strip $($(2)SRCS))$($(2)LIB)" || \
{ echo "ERROR: set $(2)SRCS or $(2)LIB"; exit 1; }; \
fi
@set -e; \
if [ -n "$(strip $($(2)SRCS))" ]; then \
trap 'rm -f "$(CURDIR)/.$(1)-wolfssl-compliance-tooling-srcs.txt"' EXIT INT TERM HUP; \
printf '%s\n' $($(2)SRCS) > "$(CURDIR)/.$(1)-wolfssl-compliance-tooling-srcs.txt"; \
fi; \
dep_ver=""; \
if [ -n "$($(2)DEP_VERSION)" ]; then \
for dv in $($(2)DEP_VERSION); do \
dep_ver="$$$$dep_ver --dep-version $$$$dv"; \
done; \
fi; \
if [ "$($(2)DEP_WOLFSSL)" = "yes" ] || [ "$($(2)DEP_WOLFSSL)" = "1" ]; then \
wv="$($(2)WOLFSSL_VERSION)"; \
if [ -z "$$$$wv" ] && [ -n "$(WOLFSSL_DIR)" ] && \
[ -f "$(WOLFSSL_DIR)/wolfssl/version.h" ]; then \
wv=`sed -n 's/.*LIBWOLFSSL_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
"$(WOLFSSL_DIR)/wolfssl/version.h" | head -1`; \
fi; \
if [ -n "$$$$wv" ]; then \
dep_ver="$$$$dep_ver --dep-version wolfssl=$$$$wv"; \
fi; \
fi; \
if [ "$($(2)DEP_WOLFCRYPT)" = "yes" ] || [ "$($(2)DEP_WOLFCRYPT)" = "1" ]; then \
case " $$$$dep_ver " in \
*" --dep-version wolfcrypt="*) ;; \
*) \
cv="$($(2)WOLFSSL_VERSION)"; \
if [ -z "$$$$cv" ] && [ -n "$(WOLFSSL_DIR)" ] && \
[ -f "$(WOLFSSL_DIR)/wolfssl/version.h" ]; then \
cv=`sed -n 's/.*LIBWOLFSSL_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
"$(WOLFSSL_DIR)/wolfssl/version.h" | head -1`; \
fi; \
if [ -n "$$$$cv" ]; then \
dep_ver="$$$$dep_ver --dep-version wolfcrypt=$$$$cv"; \
fi; \
;; \
esac; \
fi; \
CRA_PYTHON="$(CRA_PYTHON)" HOSTCC="$(or $($(2)HOSTCC),$(HOSTCC))" \
"$(or $($(2)DRIVER),$(SBOM_DRIVER))" \
--name "$($(2)NAME)" \
--root "$(or $($(2)ROOT),$(SBOM_ROOT))" \
--license-file "$(or $($(2)LICENSE_FILE),$(SBOM_LICENSE_FILE))" \
--skip-missing \
$(if $(filter 1,$($(2)NO_ARTIFACT_HASH)),--no-artifact-hash) \
$(if $(filter 1,$($(2)NO_ARTIFACT_HASH)),, \
$(if $(strip $($(2)SRCS)),--srcs-file "$(CURDIR)/.$(1)-wolfssl-compliance-tooling-srcs.txt")) \
$(if $(filter 1,$($(2)NO_ARTIFACT_HASH)),, \
$(if $($(2)LIB),--lib "$($(2)LIB)")) \
$(if $(filter 1,$($(2)SOURCE_ONLY)),--source-only) \
$(if $($(2)CFLAGS),--cflags="$$($(2)CFLAGS)") \
$(if $($(2)SETTINGS_H),--settings-h "$($(2)SETTINGS_H)") \
$(foreach d,$($(2)INCLUDE_DIRS),--include-dir "$(d)") \
$(if $($(2)OPTIONS_H),--options-h "$($(2)OPTIONS_H)") \
$(if $($(2)USER_SETTINGS),--user-settings "$($(2)USER_SETTINGS)") \
$(foreach d,$($(2)USER_SETTINGS_INCLUDES),--user-settings-include "$(d)") \
$(foreach d,$($(2)USER_SETTINGS_DEFINES),--user-settings-define "$(d)") \
$(if $($(2)COMPONENT_TYPE),--component-type "$($(2)COMPONENT_TYPE)") \
$(if $($(2)COPYRIGHT_START),--copyright-start "$($(2)COPYRIGHT_START)") \
$(if $($(2)LICENSE_OVERRIDE),--license-override '$($(2)LICENSE_OVERRIDE)') \
$(if $($(2)LICENSE_TEXT),--license-text "$($(2)LICENSE_TEXT)") \
$(if $($(2)VERSION),--version "$($(2)VERSION)") \
$(if $($(2)VERSION_FILE),--version-file "$($(2)VERSION_FILE)") \
$(if $($(2)VERSION_MACRO),--version-macro "$($(2)VERSION_MACRO)") \
$(if $($(2)DEP_WOLFSSL),--dep-wolfssl "$($(2)DEP_WOLFSSL)") \
$(if $($(2)DEP_WOLFCRYPT),--dep-wolfcrypt "$($(2)DEP_WOLFCRYPT)") \
$(if $($(2)DEP_OPENSSL),--dep-openssl "$($(2)DEP_OPENSSL)") \
$(if $($(2)DEP_LIBZ),--dep-libz "$($(2)DEP_LIBZ)") \
$(if $($(2)DOCUMENT_NAMESPACE),--document-namespace "$($(2)DOCUMENT_NAMESPACE)") \
$(if $($(2)CRYPTO_ONLY),--crypto-only "$($(2)CRYPTO_ONLY)") \
$$$$dep_ver \
$(if $(or $($(2)GEN),$(GEN_SBOM)),--gen-sbom "$(or $($(2)GEN),$(GEN_SBOM))") \
$(if $($(2)CDX_OUT),--cdx-out "$($(2)CDX_OUT)") \
$(if $($(2)SPDX_OUT),--spdx-out "$($(2)SPDX_OUT)")
$(SBOM_TAB)@test -n "$($(2)NAME)" || { echo "ERROR: set $(2)NAME"; exit 1; }
$(SBOM_TAB)@if [ "$($(2)NO_ARTIFACT_HASH)" = "1" ]; then \
test -z "$(strip $($(2)SRCS))$($(2)LIB)" || \
{ echo "ERROR: $(2)NO_ARTIFACT_HASH stands alone. Do not set $(2)SRCS or $(2)LIB."; exit 1; }; \
else \
test -n "$(strip $($(2)SRCS))$($(2)LIB)" || \
{ echo "ERROR: set $(2)SRCS or $(2)LIB"; exit 1; }; \
fi
$(SBOM_TAB)@set -e; \
if [ -n "$(strip $($(2)SRCS))" ]; then \
trap 'rm -f "$(CURDIR)/.$(1)-wolfssl-compliance-tooling-srcs.txt"' EXIT INT TERM HUP; \
printf '%s\n' $($(2)SRCS) > "$(CURDIR)/.$(1)-wolfssl-compliance-tooling-srcs.txt"; \
fi; \
dep_ver=""; \
if [ -n "$($(2)DEP_VERSION)" ]; then \
for dv in $($(2)DEP_VERSION); do \
dep_ver="$$$$dep_ver --dep-version $$$$dv"; \
done; \
fi; \
if [ "$($(2)DEP_WOLFSSL)" = "yes" ] || [ "$($(2)DEP_WOLFSSL)" = "1" ]; then \
wv="$($(2)WOLFSSL_VERSION)"; \
if [ -z "$$$$wv" ] && [ -n "$(WOLFSSL_DIR)" ] && \
[ -f "$(WOLFSSL_DIR)/wolfssl/version.h" ]; then \
wv=`sed -n 's/.*LIBWOLFSSL_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
"$(WOLFSSL_DIR)/wolfssl/version.h" | head -1`; \
fi; \
if [ -n "$$$$wv" ]; then \
dep_ver="$$$$dep_ver --dep-version wolfssl=$$$$wv"; \
fi; \
fi; \
if [ "$($(2)DEP_WOLFCRYPT)" = "yes" ] || [ "$($(2)DEP_WOLFCRYPT)" = "1" ]; then \
case " $$$$dep_ver " in \
*" --dep-version wolfcrypt="*) ;; \
*) \
cv="$($(2)WOLFSSL_VERSION)"; \
if [ -z "$$$$cv" ] && [ -n "$(WOLFSSL_DIR)" ] && \
[ -f "$(WOLFSSL_DIR)/wolfssl/version.h" ]; then \
cv=`sed -n 's/.*LIBWOLFSSL_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
"$(WOLFSSL_DIR)/wolfssl/version.h" | head -1`; \
fi; \
if [ -n "$$$$cv" ]; then \
dep_ver="$$$$dep_ver --dep-version wolfcrypt=$$$$cv"; \
fi; \
;; \
esac; \
fi; \
CRA_PYTHON="$(CRA_PYTHON)" HOSTCC="$(or $($(2)HOSTCC),$(HOSTCC))" \
"$(or $($(2)DRIVER),$(SBOM_DRIVER))" \
--name "$($(2)NAME)" \
--root "$(or $($(2)ROOT),$(SBOM_ROOT))" \
--license-file "$(or $($(2)LICENSE_FILE),$(SBOM_LICENSE_FILE))" \
--skip-missing \
$(if $(filter 1,$($(2)NO_ARTIFACT_HASH)),--no-artifact-hash) \
$(if $(filter 1,$($(2)NO_ARTIFACT_HASH)),, \
$(if $(strip $($(2)SRCS)),--srcs-file "$(CURDIR)/.$(1)-wolfssl-compliance-tooling-srcs.txt")) \
$(if $(filter 1,$($(2)NO_ARTIFACT_HASH)),, \
$(if $($(2)LIB),--lib "$($(2)LIB)")) \
$(if $(filter 1,$($(2)SOURCE_ONLY)),--source-only) \
$(if $($(2)CFLAGS),--cflags="$$($(2)CFLAGS)") \
$(if $($(2)SETTINGS_H),--settings-h "$($(2)SETTINGS_H)") \
$(foreach d,$($(2)INCLUDE_DIRS),--include-dir "$(d)") \
$(if $($(2)OPTIONS_H),--options-h "$($(2)OPTIONS_H)") \
$(if $($(2)USER_SETTINGS),--user-settings "$($(2)USER_SETTINGS)") \
$(foreach d,$($(2)USER_SETTINGS_INCLUDES),--user-settings-include "$(d)") \
$(foreach d,$($(2)USER_SETTINGS_DEFINES),--user-settings-define "$(d)") \
$(if $($(2)COMPONENT_TYPE),--component-type "$($(2)COMPONENT_TYPE)") \
$(if $($(2)COPYRIGHT_START),--copyright-start "$($(2)COPYRIGHT_START)") \
$(if $($(2)LICENSE_OVERRIDE),--license-override '$($(2)LICENSE_OVERRIDE)') \
$(if $($(2)LICENSE_TEXT),--license-text "$($(2)LICENSE_TEXT)") \
$(if $($(2)VERSION),--version "$($(2)VERSION)") \
$(if $($(2)VERSION_FILE),--version-file "$($(2)VERSION_FILE)") \
$(if $($(2)VERSION_MACRO),--version-macro "$($(2)VERSION_MACRO)") \
$(if $($(2)DEP_WOLFSSL),--dep-wolfssl "$($(2)DEP_WOLFSSL)") \
$(if $($(2)DEP_WOLFCRYPT),--dep-wolfcrypt "$($(2)DEP_WOLFCRYPT)") \
$(if $($(2)DEP_OPENSSL),--dep-openssl "$($(2)DEP_OPENSSL)") \
$(if $($(2)DEP_LIBZ),--dep-libz "$($(2)DEP_LIBZ)") \
$(if $($(2)DOCUMENT_NAMESPACE),--document-namespace "$($(2)DOCUMENT_NAMESPACE)") \
$(if $($(2)CRYPTO_ONLY),--crypto-only "$($(2)CRYPTO_ONLY)") \
$$$$dep_ver \
$(if $(or $($(2)GEN),$(GEN_SBOM)),--gen-sbom "$(or $($(2)GEN),$(GEN_SBOM))") \
$(if $($(2)CDX_OUT),--cdx-out "$($(2)CDX_OUT)") \
$(if $($(2)SPDX_OUT),--spdx-out "$($(2)SPDX_OUT)")
endef

SBOM_TARGET ?= sbom
Expand Down
134 changes: 134 additions & 0 deletions tests/test_sbom_mk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""share/build/sbom.mk: no literal tabs, and the recipes it builds still run."""

import os
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
SBOM_MK = ROOT / "share" / "build" / "sbom.mk"
MAKE = shutil.which("make")

STUB_DRIVER = """#!/bin/sh
for a in "$@"; do printf '%s\\n' "$a"; done > "$STUB_OUT"
"""


class NoTabTests(unittest.TestCase):
def test_sbom_mk_has_no_literal_tab(self):
# wolfSSL's source check rejects literal tabs in .mk files.
for number, line in enumerate(
SBOM_MK.read_text().splitlines(), start=1):
self.assertNotIn("\t", line, f"sbom.mk:{number} has a tab")


class RecipeTests(unittest.TestCase):
def setUp(self):
if not MAKE:
self.skipTest("make is not installed")
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.dir = Path(self.tmp.name)
driver = self.dir / "stub-driver"
driver.write_text(STUB_DRIVER)
driver.chmod(0o755)
self.out = self.dir / "args.txt"

def _make(self, body, *args, after=""):
(self.dir / "Makefile").write_text(
f"SBOM_DRIVER := {self.dir / 'stub-driver'}\n"
f"{body}\n"
f"include {SBOM_MK}\n"
f"{after}\n")
env = os.environ.copy()
env["STUB_OUT"] = str(self.out)
return subprocess.run(
[MAKE, "--no-print-directory", *args], cwd=self.dir, env=env,
text=True, capture_output=True, check=False)

def _args(self):
return self.out.read_text().splitlines()

def test_default_target_forwards_the_driver_arguments(self):
result = self._make(
"SBOM_NAME := demo\n"
"SBOM_SRCS := a.c b.c\n"
"SBOM_CFLAGS := -DFOO=1 -DSIZE=$$(( 4 - 1 ))\n"
"SBOM_VERSION := 1.2.3\n"
"SBOM_DEP_VERSION := one=1 two=2\n")
self.assertEqual(result.returncode, 0, result.stderr)
args = self._args()
self.assertEqual(args[:2], ["--name", "demo"])
self.assertIn("--srcs-file", args)
# The shell does the arithmetic. Make must not read $(( as a
# variable reference.
self.assertIn("--cflags=-DFOO=1 -DSIZE=3", args)
self.assertEqual(args[args.index("--version") + 1], "1.2.3")
joined = " ".join(args)
self.assertIn("--dep-version one=1 --dep-version two=2", joined)
self.assertNotIn("--lib", args)

def test_second_target_from_call(self):
result = self._make(
"SBOM_NAME := demo\n"
"SBOM_LIB := libdemo.a\n"
"SBOM_HAL_NAME := demo-hal\n"
"SBOM_HAL_LIB := libhal.a\n",
"sbom-hal",
after="$(eval $(call wolfssl_compliance_tooling_sbom_rule,"
"sbom-hal,SBOM_HAL_))")
self.assertEqual(result.returncode, 0, result.stderr)
args = self._args()
self.assertEqual(args[:2], ["--name", "demo-hal"])
self.assertEqual(args[args.index("--lib") + 1], "libhal.a")

def test_command_line_sbom_tab_does_not_break_the_rule(self):
result = self._make(
"SBOM_NAME := demo\nSBOM_LIB := libdemo.a\n", "SBOM_TAB=x")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("--lib", self._args())

def test_shell_without_printf_stops_with_a_clear_error(self):
false = shutil.which("false")
if not false:
self.skipTest("false is not installed")
result = self._make(
"SBOM_NAME := demo\nSBOM_LIB := libdemo.a\n", f"SHELL={false}")
self.assertNotEqual(result.returncode, 0)
self.assertIn("needs a POSIX shell with printf", result.stderr)

def test_missing_name(self):
result = self._make("SBOM_LIB := libdemo.a\n")
self.assertNotEqual(result.returncode, 0)
self.assertIn("ERROR: set SBOM_NAME", result.stdout)
self.assertFalse(self.out.exists())

def test_missing_composition(self):
result = self._make("SBOM_NAME := demo\n")
self.assertNotEqual(result.returncode, 0)
self.assertIn("ERROR: set SBOM_SRCS or SBOM_LIB", result.stdout)
self.assertFalse(self.out.exists())

def test_no_artifact_hash_stands_alone(self):
result = self._make(
"SBOM_NAME := demo\nSBOM_LIB := libdemo.a\n"
"SBOM_NO_ARTIFACT_HASH := 1\n")
self.assertNotEqual(result.returncode, 0)
self.assertIn("SBOM_NO_ARTIFACT_HASH stands alone", result.stdout)
self.assertFalse(self.out.exists())

def test_no_artifact_hash_alone(self):
result = self._make(
"SBOM_NAME := demo\nSBOM_NO_ARTIFACT_HASH := 1\n")
self.assertEqual(result.returncode, 0, result.stderr)
args = self._args()
self.assertIn("--no-artifact-hash", args)
self.assertNotIn("--lib", args)
self.assertNotIn("--srcs-file", args)


if __name__ == "__main__":
unittest.main()
Loading