Skip to content

Commit 51ff651

Browse files
joshua-zivkovicjuergbiJoshuaZivkovic
committed
Make source provenance generic
Remove harcoded SPDX attributes and make them be generic instead. Project allowed attributes are configured via the project config, these supported values a determined by buildstream-sbom's support Co-authored-by: Jürg Billeter <j@bitron.ch> Co-authored-by: Joshua Zivkovic <joshuazivkovic@codethink.co.uk>
1 parent 4a04dac commit 51ff651

File tree

5 files changed

+64
-79
lines changed

5 files changed

+64
-79
lines changed

src/buildstream/_project.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ def __init__(
125125
self.sandbox: Optional[MappingNode] = None
126126
self.splits: Optional[MappingNode] = None
127127

128+
self.source_provenance_fields: Optional[MappingNode] = None # Source provenance fields and their description
129+
128130
#
129131
# Private members
130132
#
@@ -726,6 +728,7 @@ def _validate_toplevel_node(self, node, *, first_pass=False):
726728
"sources",
727729
"source-caches",
728730
"junctions",
731+
"source-provenance-fields",
729732
"(@)",
730733
"(?)",
731734
]
@@ -1005,6 +1008,7 @@ def _load_second_pass(self):
10051008
mount = _HostMount(path, host_path, optional)
10061009

10071010
self._shell_host_files.append(mount)
1011+
self.source_provenance_fields = config.get_mapping("source-provenance-fields")
10081012

10091013
# _load_pass():
10101014
#

src/buildstream/data/projectconfig.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ shell:
175175
#
176176
command: [ 'sh', '-i' ]
177177

178+
# Define the set of fields accepted in `provenance` dictionaries of sources.
179+
#
180+
source-provenance-fields:
181+
homepage: "The project homepage URL"
182+
issue-tracker: "The project's issue tracking URL"
183+
178184
# Defaults for bst commands
179185
#
180186
defaults:

src/buildstream/element.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@
8585
from . import utils
8686
from . import _cachekey
8787
from . import _site
88-
from .node import Node
88+
from .node import Node, MappingNode
8989
from .plugin import Plugin
9090
from .sandbox import _SandboxFlags, SandboxCommandError
9191
from .sandbox._config import SandboxConfig
9292
from .sandbox._sandboxremote import SandboxRemote
93-
from .types import _Scope, _CacheBuildTrees, _KeyStrength, OverlapAction, _DisplayKey, _SourceProvenance
93+
from .types import _Scope, _CacheBuildTrees, _KeyStrength, OverlapAction, _DisplayKey
9494
from ._artifact import Artifact
9595
from ._elementproxy import ElementProxy
9696
from ._elementsources import ElementSources
@@ -102,7 +102,7 @@
102102

103103
if TYPE_CHECKING:
104104
from typing import Tuple
105-
from .node import MappingNode, ScalarNode, SequenceNode
105+
from .node import ScalarNode, SequenceNode
106106
from .types import SourceRef
107107

108108
# pylint: disable=cyclic-import
@@ -2635,19 +2635,21 @@ def __load_sources(self, load_element):
26352635
del source[Symbol.DIRECTORY]
26362636

26372637
# Provenance is optional
2638-
provenance_node = source.get_mapping(Symbol.PROVENANCE, default=None)
2639-
provenance = None
2638+
provenance_node: MappingNode = source.get_mapping(Symbol.PROVENANCE, default=None)
26402639
if provenance_node:
26412640
del source[Symbol.PROVENANCE]
2642-
provenance = _SourceProvenance.new_from_node(provenance_node)
2641+
provenance_node.validate_keys(project.source_provenance_fields.keys())
2642+
2643+
# make sure everything is a string
2644+
provenance_node = MappingNode.from_dict({key: value.as_str() for key, value in provenance_node.items()})
26432645

26442646
meta_source = MetaSource(
26452647
self.name,
26462648
index,
26472649
self.get_kind(),
26482650
kind.as_str(),
26492651
directory,
2650-
provenance,
2652+
provenance_node,
26512653
source,
26522654
load_element.first_pass,
26532655
)

src/buildstream/source.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,14 @@
3838
to provide additional source provenance related metadata which will later
3939
be reported in :class:`.SourceInfo` objects.
4040
41-
The ``provenance`` dictionary supports the following fields:
41+
The ``provenance`` dictionary itself does not have any specific required keys
42+
but users should refer to the ``provenance`` attributes that are supported
43+
by buildstream-sbom to ensure alignment with source plugins, especially for
44+
use with source tracking of these attributes.
4245
43-
* Homepage
44-
45-
The ``homepage`` attribute can be used to specify the project homepage URL
46-
47-
* Issue Tracker
48-
49-
The ``issue-tracker`` attribute can be used to specify the project's issue tracking URL
46+
Any attribute used in the ``provenance`` dictionary of a source must be
47+
defined in the project.conf using the ``source-provenance-fields`` dictionary
48+
to define the attribute and its significance.
5049
5150
*Since: 2.5*
5251
@@ -378,9 +377,9 @@
378377
from .node import MappingNode
379378
from .plugin import Plugin
380379
from .sourcemirror import SourceMirror
381-
from .types import SourceRef, CoreWarnings, FastEnum, _SourceProvenance
382-
from ._exceptions import BstError, ImplError, PluginError
383-
from .exceptions import ErrorDomain
380+
from .types import SourceRef, CoreWarnings, FastEnum
381+
from ._exceptions import BstError, ImplError, PluginError, LoadError
382+
from .exceptions import ErrorDomain, LoadErrorReason
384383
from ._loader.metasource import MetaSource
385384
from ._projectrefs import ProjectRefStorage
386385
from ._cachekey import generate_key
@@ -396,6 +395,8 @@
396395

397396
# pylint: enable=cyclic-import
398397

398+
SourceProvenance = MappingNode
399+
399400

400401
class SourceError(BstError):
401402
"""This exception should be raised by :class:`.Source` implementations
@@ -553,8 +554,7 @@ def __init__(
553554
self,
554555
kind: str,
555556
url: str,
556-
homepage: Optional[str],
557-
issue_tracker: Optional[str],
557+
provenance: Optional[SourceProvenance],
558558
medium: Union[SourceInfoMedium, str],
559559
version_type: Union[SourceVersionType, str],
560560
version: str,
@@ -572,14 +572,9 @@ def __init__(
572572
The url of the source input
573573
"""
574574

575-
self.homepage: Optional[str] = homepage
575+
self.provenance = provenance
576576
"""
577-
The project homepage URL
578-
"""
579-
580-
self.issue_tracker: Optional[str] = issue_tracker
581-
"""
582-
The project issue tracking URL
577+
The optional YAML node with source provenance attributes
583578
"""
584579

585580
self.medium: Union[SourceInfoMedium, str] = medium
@@ -642,10 +637,14 @@ def serialize(self) -> Dict[str, Union[str, Dict[str, str]]]:
642637
"url": self.url,
643638
}
644639

645-
if self.homepage is not None:
646-
version_info["homepage"] = self.homepage
647-
if self.issue_tracker is not None:
648-
version_info["issue-tracker"] = self.issue_tracker
640+
if self.provenance is not None:
641+
# need to keep homepage/issue-tracker [also] at the top-level for backward compat
642+
if (homepage := self.provenance.get_str("homepage", None)) is not None:
643+
version_info["homepage"] = homepage
644+
if (issue_tracker := self.provenance.get_str("issue-tracker", None)) is not None:
645+
version_info["issue-tracker"] = issue_tracker
646+
647+
version_info["provenance"] = self.provenance
649648

650649
version_info["medium"] = medium_str
651650
version_info["version-type"] = version_type_str
@@ -824,9 +823,9 @@ def __init__(
824823
self.__element_kind = meta.element_kind # The kind of the element owning this source
825824
self._directory = meta.directory # Staging relative directory
826825
self.__variables = variables # The variables used to resolve the source's config
827-
self.__provenance: Optional[
828-
_SourceProvenance
829-
] = meta.provenance # The _SourceProvenance for general user provided SourceInfo
826+
self.__provenance: Optional[SourceProvenance] = (
827+
meta.provenance
828+
) # The source provenance for general user provided SourceInfo
830829

831830
self.__key = None # Cache key for source
832831

@@ -1393,23 +1392,33 @@ def create_source_info(
13931392
13941393
*Since: 2.5*
13951394
"""
1396-
homepage = None
1397-
issue_tracker = None
1395+
project = self._get_project()
13981396

13991397
if provenance_node is not None:
1400-
provenance: Optional[_SourceProvenance] = _SourceProvenance.new_from_node(provenance_node)
1398+
# Ensure provenance node keys are valid and values are all strings
1399+
defined_provenance_fields = (
1400+
project._project_conf.get_mapping("source-provenance-fields", None)
1401+
or project.source_provenance_fields
1402+
)
1403+
1404+
try:
1405+
provenance_node.validate_keys(defined_provenance_fields.keys())
1406+
except LoadError as E:
1407+
raise LoadError(
1408+
"Specified source attribute not defined in project config\n {}".format(E),
1409+
LoadErrorReason.UNDEFINED_SOURCE_PROVENANCE_ATTRIBUTE,
1410+
)
1411+
1412+
# Make sure everything is a string
1413+
provenance = MappingNode.from_dict({key: value.as_str() for key, value in provenance_node.items()})
1414+
14011415
else:
14021416
provenance = self.__provenance
14031417

1404-
if provenance is not None:
1405-
homepage = provenance.homepage
1406-
issue_tracker = provenance.issue_tracker
1407-
14081418
return SourceInfo(
14091419
self.get_kind(),
14101420
url,
1411-
homepage,
1412-
issue_tracker,
1421+
provenance,
14131422
medium,
14141423
version_type,
14151424
version,

src/buildstream/types.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -390,42 +390,6 @@ def new_from_node(cls, node: MappingNode) -> "_SourceMirror":
390390
return cls(name, aliases)
391391

392392

393-
# _SourceProvenance()
394-
#
395-
# A simple object describing user provided source provenance information
396-
#
397-
# Args:
398-
# homepage: The project homepage URL
399-
# issue_tracker: The project issue reporting URL
400-
#
401-
class _SourceProvenance:
402-
def __init__(self, homepage: Optional[str], issue_tracker: Optional[str]):
403-
self.homepage: Optional[str] = homepage
404-
self.issue_tracker: Optional[str] = issue_tracker
405-
406-
# new_from_node():
407-
#
408-
# Creates a _SourceProvenance() from a YAML loaded node.
409-
#
410-
# Args:
411-
# node: The configuration node describing the spec.
412-
#
413-
# Returns:
414-
# The described _SourceProvenance instance.
415-
#
416-
# Raises:
417-
# LoadError: If the node is malformed.
418-
#
419-
@classmethod
420-
def new_from_node(cls, node: MappingNode) -> "_SourceProvenance":
421-
node.validate_keys(["homepage", "issue-tracker"])
422-
423-
homepage: Optional[str] = node.get_str("homepage", None)
424-
issue_tracker: Optional[str] = node.get_str("issue-tracker", None)
425-
426-
return cls(homepage, issue_tracker)
427-
428-
429393
########################################
430394
# Type aliases #
431395
########################################

0 commit comments

Comments
 (0)