Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F133762561
D53318.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
21 KB
Referenced Files
None
Subscribers
None
D53318.id.diff
View Options
diff --git a/Mk/LuaScripts/ports-spdx-traverse-deps.lua b/Mk/LuaScripts/ports-spdx-traverse-deps.lua
new file mode 100755
--- /dev/null
+++ b/Mk/LuaScripts/ports-spdx-traverse-deps.lua
@@ -0,0 +1,179 @@
+#!/usr/libexec/flua
+
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright(c) 2025 The FreeBSD Foundation.
+--
+-- This software was developed by Tuukka Pasanen <tuukka.pasanen@ilmi.fi>
+-- under sponsorship from the FreeBSD Foundation.
+--
+-- Traverse package dependencies for SDPX Lite 3.0.1 SBOM
+--
+
+local Logging = require("logging")
+local ucl = require("ucl")
+
+logger = Logging.new(nil, "INFO")
+
+require("ports-make")
+require("ports-spdx")
+
+-------------------------------------------------------------------------------
+-- Run 'describe-json' with make and parse output.
+-- @param location If something else than nil then make if runned with '-C'
+-- @return Parse output which contains name, version and licenses array
+-------------------------------------------------------------------------------
+local function spdx_traverse_describe_json(location)
+ local addition_str = ""
+
+ if location ~= nil then
+ addition_str = "-C " .. location .. " "
+ end
+
+ local output_table = ports_make_target(addition_str .. "describe-json")
+ local parser = ucl.parser()
+ local parsed_json, err = parser:parse_string(output_table)
+
+ ucl_obj = parser:get_object()
+ rtn_table = {}
+
+ -- Example for this one is glib 2.0 package which
+ -- outputs JSON with object that has okeys 'default-glib20' and 'bootstrap-glib20'
+ -- To simplify we choose first which should be 'default'.
+ -- otherwise there is only one object so use that one
+ if ucl_obj["pkgbase"] == nil then
+ logger:debug("Multiple packages describe in JSON")
+ for key, cur_obj in pairs(ucl_obj) do
+ if cur_obj["pkgbase"] ~= nil then
+ rtn_table["name"] = cur_obj["pkgbase"]
+ rtn_table["version"] = cur_obj["distversion"]
+ rtn_table["license"] = cur_obj["license"]
+ break
+ end
+ end
+ else
+ rtn_table["name"] = ucl_obj["pkgbase"]
+ rtn_table["version"] = ucl_obj["distversion"]
+ rtn_table["license"] = ucl_obj["license"]
+ end
+
+ logger:debug("Described name: " .. rtn_table["name"])
+ logger:debug("Described version: " .. rtn_table["version"])
+
+ for _, license_str in ipairs(rtn_table["license"]) do
+ logger:debug("Described license: " .. license_str)
+ end
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- Parses 'package-depends-list' one line and creates unified table
+-- @param table_string One line of output which should be parsed
+-- @return Parse output as table which contains name, version, full_location
+-- and location
+-------------------------------------------------------------------------------
+local function spdx_traverse_split_output(table_string)
+ local splitted_table = ports_make_split_string(table_string, " ")
+ local version_table = ports_make_split_string(splitted_table[1], "-")
+
+ version_table_len = #version_table
+ local name_len = (string.len(splitted_table[1]) - string.len(version_table[version_table_len])) - 1
+
+ local version_str = version_table[version_table_len]
+ local name_str = string.sub(splitted_table[1], 0, name_len)
+
+ rtn_table = {}
+ rtn_table["name"] = name_str
+ rtn_table["version"] = version_str
+ rtn_table["full_location"] = splitted_table[2]
+ rtn_table["location"] = splitted_table[3]
+
+ logger:debug("Package name: " .. rtn_table["name"])
+ logger:debug("Package version: " .. rtn_table["version"])
+ logger:debug("Package full location: " .. rtn_table["full_location"])
+ logger:debug("Package location: " .. rtn_table["location"])
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- Add depends for some packge to Graph from 'package-depends-list' output
+-- @param deps_table Output of package-depends-list as table
+-- @param package Current package for relationship from-key
+-- @param software_sbom Current softwareSbom
+-- @param spdx_document Current spdxDocument
+-- @param creation_info Current creationInfo
+-- @return none
+-------------------------------------------------------------------------------
+local function spdx_traverse_add_depends_on(deps_table, package, software_sbom, spdx_document, creation_info)
+ local depends_on_table = nil
+ for _, output_str in ipairs(deps_table) do
+ package_dep_table = spdx_traverse_split_output(output_str)
+ to_spdx_id = ports_spdx_get_spdxId("software_Package", package_dep_table["name"])
+
+ if depends_on_table == nil then
+ depends_on_table = ports_spdx_add_relationship(
+ root_graph,
+ package.name,
+ package.spdxId,
+ to_spdx_id,
+ "packages",
+ "dependsOn",
+ software_sbom,
+ spdx_document,
+ creation_info
+ )
+ else
+ table.insert(depends_on_table.to, to_spdx_id)
+ end
+ end
+end
+
+root_graph = {}
+-- Create Graph root objects
+local agent, creation_info, spdx_document = ports_spdx_create_root(root_graph)
+
+local output_table = ports_make_target_as_table("package-depends-list")
+local package_data = spdx_traverse_describe_json()
+
+-- Create SBOM object for package we are currently SBOMing
+local software_sbom, package = ports_spdx_create_sbom(
+ root_graph,
+ package_data["name"],
+ package_data["version"],
+ package_data["license"],
+ spdx_document,
+ creation_info,
+ agent,
+ "build"
+)
+
+-- Add depends for current SBOM
+spdx_traverse_add_depends_on(output_table, package, software_sbom, spdx_document, creation_info)
+
+-- Add depends to current package
+for _, output_str in ipairs(output_table) do
+ local package_dep_table = spdx_traverse_split_output(output_str)
+ local package_data = spdx_traverse_describe_json(package_dep_table["full_location"])
+ local dep_software_sbom, dep_package = ports_spdx_create_sbom(
+ root_graph,
+ package_data["name"],
+ package_data["version"],
+ package_data["license"],
+ spdx_document,
+ creation_info,
+ agent,
+ "build"
+ )
+
+ local deps_table =
+ ports_make_target_as_table("-C " .. package_dep_table["full_location"] .. " package-depends-list")
+ spdx_traverse_add_depends_on(deps_table, dep_package, software_sbom, spdx_document, creation_info)
+end
+
+-- Add licenses to Graph
+ports_spdx_add_liceses(root_graph, spdx_document, creation_info)
+
+json_ld = ports_spdx_json_ld(root_graph)
+print(ucl.to_format(json_ld, "json"))
diff --git a/Mk/LuaScripts/ports-spdx.lua b/Mk/LuaScripts/ports-spdx.lua
new file mode 100644
--- /dev/null
+++ b/Mk/LuaScripts/ports-spdx.lua
@@ -0,0 +1,364 @@
+-- SPDX-License-Identifier: BSD-2-Clause
+--
+-- Copyright(c) 2025 The FreeBSD Foundation.
+--
+-- This software was developed by Tuukka Pasanen <tuukka.pasanen@ilmi.fi>
+-- under sponsorship from the FreeBSD Foundation.
+--
+-- Lua script for creating SPDX Lite profile version 3.0.1 files
+--
+-- SPDX documentation can be found from:
+-- https://spdx.github.io/spdx-spec/v3.0.1/
+--
+
+local ucl = require("ucl")
+
+local spdx_version = "3.0.1"
+local use_uri = "https://cgit.freebsd.org/ports"
+local agent_id = ""
+-- license_table contains all licenses in SPDX license format
+-- license_spxd_id_table contains them in spdxId format
+-- These are only have once every license nor many occurances
+local license_table = {}
+local license_spxd_id_table = {}
+local freebsd_document_license = "FreeBSD-DOC"
+local freebsd_document_license_url = "https://spdx.org/licenses/FreeBSD-DOC.html"
+
+-------------------------------------------------------------------------------
+-- get spdxId URI with part and id
+-- It produces URI: start/part/id
+-- @param part Prepresents part of SBOM like 'Package' or 'Relationship'
+-- @param id Id is something unique id for this part like package name
+-- @return URI: start/part/id or https://start/part/id
+-------------------------------------------------------------------------------
+function ports_spdx_get_spdxId(part, id)
+ rtn_string = use_uri .. "/" .. part .. "/" .. id
+ return rtn_string
+end
+
+-------------------------------------------------------------------------------
+-- Create basic table with correct structure to extend in
+-- specific create functions
+-- @param spdx_id spdxId for this SpdxDocument
+-- @param obj_type Type of object
+-- @param creation_info_id Which creation info we are using
+-- @return table which holds object
+-------------------------------------------------------------------------------
+local function ports_spdx_create_table(spdx_id, obj_type, creation_info_id)
+ rtn_table = {
+ creationInfo = creation_info_id,
+ spdxId = spdx_id,
+ }
+
+ rtn_table["@type"] = obj_type
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get /Core/SpdxDocument element
+-- Note: There can be only one SpdxDocument in SPDX Lite 3.0.1 document
+-- @param spdx_id spdxId for this SpdxDocument
+-- @param creation_info_id Which creation info we are using
+-- @return table which holds object
+-------------------------------------------------------------------------------
+function ports_spdx_core_spdx_document(spdx_id, creation_info_id)
+ rtn_table = ports_spdx_create_table(spdx_id, "SpdxDocument", creation_info_id)
+
+ rtn_table["rootElement"] = {}
+ rtn_table["element"] = {}
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get /Classes/Sbom/ element which holds one package information
+-- @param spdx_id spdxId for this SpdxDocument
+-- @param creation_info_id Which creation info we are using
+-- @param sbom_type_str mainly 'build' but see documenation for extra info
+-- @return table which holds object
+-------------------------------------------------------------------------------
+function ports_spdx_software_sbom(spdx_id, creation_info_id, sbom_type_str)
+ rtn_table = ports_spdx_create_table(spdx_id, "software_Sbom", creation_info_id)
+
+ rtn_table["rootElement"] = {}
+ rtn_table["element"] = {}
+ rtn_table["sbom_type"] = { sbom_type_str }
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get /SimpleLicensing/LicenseExpression element which holds SPDX license name
+-- @param creation_info_id Which creation info we are using
+-- @param license SPDX license expression
+-- @return table which holds object
+-------------------------------------------------------------------------------
+function ports_spdx_simplelicensing_license_expression(creation_info_id, license)
+ spdx_id = ports_spdx_get_spdxId("simplelicensing_LicenseExpression", string.lower(license))
+
+ rtn_table = ports_spdx_create_table(spdx_id, "simplelicensing_LicenseExpression", creation_info_id)
+
+ rtn_table["license_expression"] = license
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get /Software/Package element which holds one package information
+-- There can be lot of extra information but this holds only bare minimum
+-- @param creation_info_id Which creation info we are using
+-- @param agent_id Some agent information
+-- @param package_name Name of package
+-- @param package_version Package version number
+-- @return table which holds object
+-------------------------------------------------------------------------------
+function ports_spdx_software_package(creation_info_id, agent_id, package_name, package_version)
+ spdx_id = ports_spdx_get_spdxId("software_Package", package_name)
+
+ rtn_table = ports_spdx_create_table(spdx_id, "software_Package", creation_info_id)
+
+ rtn_table["originatedBy"] = { agent_id }
+ rtn_table["name"] = package_name
+ rtn_table["software_copyrightText"] = "NOASSERTION"
+ rtn_table["software_packageVersion"] = package_version
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get /Core/Relationship element which holds somekind of relationship.
+-- @param spdx_id spdxId for relationship
+-- @param creation_info_id Which creation info we are using
+-- @param from_id From this spdxId
+-- @param to_id To this spdxId
+-- @param relationship_type which kind of relation ship. See documentation.
+-- @return table which holds object
+-------------------------------------------------------------------------------
+function ports_spdx_core_relationship(spdx_id, creation_info_id, from_id, to_id, relationship_type)
+ rtn_table = ports_spdx_create_table(spdx_id, "Relationship", creation_info_id)
+
+ rtn_table["from"] = from_id
+ rtn_table["to"] = { to_id }
+ rtn_table["relationshipType"] = relationship_type
+
+ rtn_table["@type"] = "Relationship"
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get /Core/Agent element which is actor in system.
+-- @param creation_info_id Which creation info we are using
+-- @param name Name of actor
+-- @return table which holds object
+-------------------------------------------------------------------------------
+function ports_spdx_core_agent(creation_info_id, name)
+ -- assert(type(name) ~= "string", "Name must be string, got: %s.", type(name))
+ spdxId = ports_spdx_get_spdxId("Agent", string.lower(name):gsub(" ", "_"))
+
+ rtn_table = ports_spdx_create_table(spdx_id, "Agent", creation_info_id)
+
+ rtn_table["name"] = name
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get /Core/CreationInfo element which is creation info for this document.
+-- @param creation_id Which creation info we are using
+-- @param agent_id Which agent have made this document
+-- @return table which holds object
+-------------------------------------------------------------------------------
+function ports_spdx_core_creation_info(creation_id, agent_id)
+ local now = os.time()
+ local formatted_date = os.date("%FT%TZ", now)
+
+ rtn_table = {
+ created = formatted_date,
+ created_by = { agent_id },
+ created_using = "FreeBSD Port SPDX tool 1.0.0",
+ spec_version = spdx_version,
+ }
+
+ rtn_table["@type"] = "CreationInfo"
+ rtn_table["@id"] = creation_id
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- get JSON-LD table for creating RDF
+-- @param graph Holds table for @graph
+-- @return JSON-LD table
+-------------------------------------------------------------------------------
+function ports_spdx_json_ld(graph)
+ rtn_table = {}
+
+ rtn_table["@context"] = "https://spdx.org/rdf/3.0.1/spdx-context.jsonld"
+
+ rtn_table["@graph"] = graph
+
+ return rtn_table
+end
+
+-------------------------------------------------------------------------------
+-- Add to object 'element' or 'rootElement'
+-- @param object_table Object table
+-- @param spdx_id spdxId to add
+-- @return is_root if false then add to 'element' and if true 'rootElement'
+-------------------------------------------------------------------------------
+local function ports_spdx_add_to_element(object_table, spdx_id, is_root)
+ if is_root then
+ table.insert(object_table.rootElement, spdx_id)
+ else
+ table.insert(object_table.element, spdx_id)
+ end
+end
+
+local function ports_spdx_add_to_graph(root_graph, object_table)
+ table.insert(root_graph, object_table)
+end
+
+function ports_spdx_add_liceses(root_graph, spdx_document, creation_info)
+ for key, license_str in pairs(license_spxd_id_table) do
+ if license_str ~= "" then
+ license_table = ports_spdx_simplelicensing_license_expression(creation_info["@id"], license_str)
+ ports_spdx_add_to_element(spdx_document, license_table.spdxId, false)
+ ports_spdx_add_to_graph(root_graph, license_table)
+ end
+ end
+end
+
+-------------------------------------------------------------------------------
+-- Add relationship to Graph. It makes all necesery adds to SBOM and
+-- spdxDocument
+-- @param root_graph Graph object
+-- @param package_name Package name from we want to make relationship
+-- @param from From spdxId
+-- @param to to spdxId
+-- @param to_string This is what comes at last in spdxId
+-- @param relationship_type What kind of relationship is this
+-- @param software_sbom Current software SBOM
+-- @param spdx_document Current SPDX document
+-- @param creation_info Current creation info
+-- @return Relationship table
+-------------------------------------------------------------------------------
+function ports_spdx_add_relationship(
+ root_graph,
+ package_name,
+ from,
+ to,
+ to_string,
+ relationship_type,
+ software_sbom,
+ spdx_document,
+ creation_info
+)
+ relation_spdx_id_str = package_name .. "/" .. relationship_type .. "/" .. string.lower(to_string)
+ relation_spdx_id = ports_spdx_get_spdxId("Relationship", relation_spdx_id_str)
+
+ relation_table = ports_spdx_core_relationship(relation_spdx_id, creation_info["@id"], from, to, relationship_type)
+
+ ports_spdx_add_to_element(spdx_document, relation_table.spdxId, false)
+ ports_spdx_add_to_element(software_sbom, relation_table.spdxId, false)
+ ports_spdx_add_to_graph(root_graph, relation_table)
+
+ return relation_table
+end
+
+-------------------------------------------------------------------------------
+-- Add SBOM to Graph. It makes all necesery adds to SBOM and
+-- spdxDocument
+-- @param root_graph Graph object
+-- @param package_name Package name
+-- @param package_version Package version
+-- @param package_license Package license array (Comes from JSON)
+-- @param spdx_document Current SPDX document
+-- @param creation_info Current creation info
+-- @param agent Current agent
+-- @param type Type of SBOM
+-- @return SBOM table and package table
+-------------------------------------------------------------------------------
+function ports_spdx_create_sbom(
+ root_graph,
+ package_name,
+ package_version,
+ package_license,
+ spdx_document,
+ creation_info,
+ agent,
+ type
+)
+ logger:debug("Create SBOM with package name: '" .. package_name .. "' and version: '" .. package_version)
+
+ software_sbom =
+ ports_spdx_software_sbom(ports_spdx_get_spdxId("software_Sbom", package_name), creation_info["@id"], type)
+
+ package = ports_spdx_software_package(creation_info["@id"], default_agent.spdxId, package_name, package_version)
+
+ ports_spdx_add_to_element(spdx_document, software_sbom.spdxId, false)
+ ports_spdx_add_to_element(spdx_document, package.spdxId, false)
+ ports_spdx_add_to_element(spdx_document, software_sbom.spdxId, true)
+ ports_spdx_add_to_element(software_sbom, package.spdxId, true)
+
+ ports_spdx_add_to_graph(root_graph, software_sbom)
+ ports_spdx_add_to_graph(root_graph, package)
+
+ for _, license_str in ipairs(package_license) do
+ license_spdx_id = ports_spdx_get_spdxId("simplelicensing_LicenseExpression", string.lower(license_str))
+
+ -- If we don't have this kind of license then just create one
+ -- otherwise bail out
+ if license_spxd_id_table[license_str] == nil then
+ logger:debug("SBOM package license: " .. license_str)
+ license_spxd_id_table[license_str] = license_str
+ end
+
+ license = ports_spdx_simplelicensing_license_expression(creation_info["@id"], license_str)
+
+ ports_spdx_add_relationship(
+ root_graph,
+ package_name,
+ package.spdxId,
+ license_spdx_id,
+ license_str,
+ "hasDeclaredLicense",
+ software_sbom,
+ spdx_document,
+ creation_info
+ )
+ ports_spdx_add_relationship(
+ root_graph,
+ package_name,
+ package.spdxId,
+ license_spdx_id,
+ license_str,
+ "hasConcludedLicense",
+ software_sbom,
+ spdx_document,
+ creation_info
+ )
+ end
+
+ return software_sbom, package
+end
+
+-------------------------------------------------------------------------------
+-- Add SpdxDocument, Agent and creationInfo to Graph. It makes all necesery
+-- adds to SBOM and spdxDocument
+-- @param root_graph Graph object
+-- @return Agent table, creationInfo Table, spdxDocument table
+-------------------------------------------------------------------------------
+function ports_spdx_create_root(root_graph)
+ default_agent = ports_spdx_core_agent("_:creationinfo_1", "Default agent")
+ creation_info = ports_spdx_core_creation_info("_:creationinfo_1", default_agent.spdxId)
+ spdx_document = ports_spdx_core_spdx_document(ports_spdx_get_spdxId("SpdxDocument", "core"), creation_info["@id"])
+
+ ports_spdx_add_to_element(spdx_document, default_agent.spdxId, false)
+ ports_spdx_add_to_graph(root_graph, spdx_document)
+ ports_spdx_add_to_graph(root_graph, creation_info)
+ ports_spdx_add_to_graph(root_graph, default_agent)
+
+ return default_agent, creation_info, spdx_document
+end
diff --git a/Mk/bsd.commands.mk b/Mk/bsd.commands.mk
--- a/Mk/bsd.commands.mk
+++ b/Mk/bsd.commands.mk
@@ -55,6 +55,7 @@
ID?= /usr/bin/id
IDENT?= /usr/bin/ident
JOT?= /usr/bin/jot
+LUA?= /usr/libexec/flua
LDCONFIG?= /sbin/ldconfig
LHA_CMD?= ${LOCALBASE}/bin/lha
LN?= /bin/ln
diff --git a/Mk/bsd.port.mk b/Mk/bsd.port.mk
--- a/Mk/bsd.port.mk
+++ b/Mk/bsd.port.mk
@@ -613,6 +613,8 @@
# config-recursive
# - Configure options for this port for a port and all its
# dependencies.
+# sbom - Create SPDX Lite 3.x compatible Software Bill Of material
+# From current package.
# showconfig - Display options config for this port.
# showconfig-recursive
# - Display options config for this port and all its
@@ -1012,6 +1014,7 @@
SRC_BASE?= /usr/src
USESDIR?= ${PORTSDIR}/Mk/Uses
SCRIPTSDIR?= ${PORTSDIR}/Mk/Scripts
+LUASCRIPTSDIR?= ${PORTSDIR}/Mk/LuaScripts
LIB_DIRS?= /lib /usr/lib ${LOCALBASE}/lib
STAGEDIR?= ${WRKDIR}/stage
NOTPHONY?=
@@ -5139,6 +5142,17 @@
. endif
. endif # config-conditional
+# Package (recursive runtime) dependency list. Print out both directory names
+# and package names.
+
+sbom:
+. if !exists(/usr/lib/flua/ucl.so) && !exists(${LOCALBASE}/lib/lua/5.4/ucl.so)
+ @${ECHO_MSG} "===> Lua UCL library not found, cannot create SPDX Lite SBOM."
+ @${ECHO_MSG} "===> Please install textproc/libucl ports."
+. else
+ @${SETENV} LUA_PATH="${LUASCRIPTSDIR}/?.lua;;" ${LUA} ${LUASCRIPTSDIR}/ports-spdx-traverse-deps.lua
+. endif
+
. if !target(showconfig) && (make(*config*) || (!empty(.MAKEFLAGS:M-V) && !empty(.MAKEFLAGS:M*_DESC)))
.include "${PORTSDIR}/Mk/bsd.options.desc.mk"
MULTI_EOL= : you have to choose at least one of them
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Oct 29, 5:22 AM (1 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
24390026
Default Alt Text
D53318.id.diff (21 KB)
Attached To
Mode
D53318: Add sbom target to Makefile and needed Lua scripts
Attached
Detach File
Event Timeline
Log In to Comment