Page MenuHomeFreeBSD

D53318.id.diff
No OneTemporary

D53318.id.diff

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

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)

Event Timeline