Skip to content

Commit 6894c4f

Browse files
juergbiJoshuaZivkovic
authored andcommitted
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 <[email protected]> Co-authored-by: Joshua Zivkovic <[email protected]>
1 parent bbc05ba commit 6894c4f

File tree

9 files changed

+207
-94
lines changed

9 files changed

+207
-94
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/downloadablefilesource.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ class DownloadableFileSource(Source):
269269
def configure(self, node):
270270
self.original_url = node.get_str("url")
271271
self.ref = node.get_str("ref", None)
272+
self.source_provenance = node.get_mapping("provenance", None)
272273

273274
extra_data = {}
274275
self.url = self.translate_url(self.original_url, extra_data=extra_data)
@@ -310,6 +311,15 @@ def get_ref(self):
310311
def set_ref(self, ref, node):
311312
node["ref"] = self.ref = ref
312313

314+
def load_source_provenance(self, node):
315+
self.source_provenance = node.get_str("provenance", None)
316+
317+
def get_source_provenance(self):
318+
return self.source_provenance
319+
320+
def set_source_provenance(self, source_provenance, node):
321+
node["provenance"] = self.source_provenance = source_provenance
322+
313323
def track(self): # pylint: disable=arguments-differ
314324
# there is no 'track' field in the source to determine what/whether
315325
# or not to update refs, because tracking a ref is always a conscious
@@ -325,7 +335,7 @@ def track(self): # pylint: disable=arguments-differ
325335
)
326336
self.warn("Potential man-in-the-middle attack!", detail=detail)
327337

328-
return new_ref
338+
return new_ref, None
329339

330340
def fetch(self): # pylint: disable=arguments-differ
331341

src/buildstream/element.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
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
@@ -2635,19 +2635,18 @@ 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())
26432642

26442643
meta_source = MetaSource(
26452644
self.name,
26462645
index,
26472646
self.get_kind(),
26482647
kind.as_str(),
26492648
directory,
2650-
provenance,
2649+
provenance_node,
26512650
source,
26522651
load_element.first_pass,
26532652
)

src/buildstream/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,8 @@ class LoadErrorReason(Enum):
155155
This warning will be produced when a filename for a target contains invalid
156156
characters in its name.
157157
"""
158+
159+
UNDEFINED_SOURCE_PROVENANCE_ATTRIBUTE = 29
160+
"""
161+
Thee source provenance attribute specified was not defined in the project config
162+
"""

src/buildstream/plugin.py

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,19 @@
129129
import sys
130130
import traceback
131131
from contextlib import contextmanager, suppress
132-
from typing import IO, Any, Callable, Generator, Optional, Sequence, Dict, Tuple, TypeVar, Union, TYPE_CHECKING
132+
from typing import (
133+
IO,
134+
Any,
135+
Callable,
136+
Generator,
137+
Optional,
138+
Sequence,
139+
Dict,
140+
Tuple,
141+
TypeVar,
142+
Union,
143+
TYPE_CHECKING,
144+
)
133145
from weakref import WeakValueDictionary
134146

135147
from . import utils, _signals
@@ -160,6 +172,7 @@
160172
_STR_BYTES_PATH = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
161173
_CMD = Union[_STR_BYTES_PATH, Sequence[_STR_BYTES_PATH]]
162174

175+
163176
# _background_job_wrapper()
164177
#
165178
# Wrapper for running jobs in the background, transparently for users
@@ -172,7 +185,9 @@
172185
# target: function to execute in the background
173186
# args: positional arguments to give to the target function
174187
#
175-
def _background_job_wrapper(result_queue: multiprocessing.Queue, target: Callable[..., T1], args: Any) -> None:
188+
def _background_job_wrapper(
189+
result_queue: multiprocessing.Queue, target: Callable[..., T1], args: Any
190+
) -> None:
176191
result = None
177192

178193
try:
@@ -265,7 +280,8 @@ class Foo(Source):
265280

266281
try:
267282
__multiprocessing_context: Union[
268-
multiprocessing.context.ForkServerContext, multiprocessing.context.SpawnContext
283+
multiprocessing.context.ForkServerContext,
284+
multiprocessing.context.SpawnContext,
269285
] = multiprocessing.get_context("forkserver")
270286
except ValueError:
271287
# We are on a system without `forkserver` support. Let's default to
@@ -286,7 +302,6 @@ def __init__(
286302
type_tag: str,
287303
unique_id: Optional[int] = None,
288304
):
289-
290305
self.name = name
291306
"""The plugin name
292307
@@ -371,7 +386,9 @@ def configure(self, node: MappingNode) -> None:
371386
372387
"""
373388
raise ImplError(
374-
"{tag} plugin '{kind}' does not implement configure()".format(tag=self.__type_tag, kind=self.get_kind())
389+
"{tag} plugin '{kind}' does not implement configure()".format(
390+
tag=self.__type_tag, kind=self.get_kind()
391+
)
375392
)
376393

377394
def preflight(self) -> None:
@@ -393,7 +410,9 @@ def preflight(self) -> None:
393410
will raise an error automatically informing the user that a host tool is needed.
394411
"""
395412
raise ImplError(
396-
"{tag} plugin '{kind}' does not implement preflight()".format(tag=self.__type_tag, kind=self.get_kind())
413+
"{tag} plugin '{kind}' does not implement preflight()".format(
414+
tag=self.__type_tag, kind=self.get_kind()
415+
)
397416
)
398417

399418
def get_unique_key(self) -> SourceRef:
@@ -440,7 +459,9 @@ def get_kind(self) -> str:
440459
"""
441460
return self.__kind
442461

443-
def node_get_project_path(self, node, *, check_is_file=False, check_is_dir=False) -> str:
462+
def node_get_project_path(
463+
self, node, *, check_is_file=False, check_is_dir=False
464+
) -> str:
444465
"""Fetches a project path from a dictionary node and validates it
445466
446467
Paths are asserted to never lead to a directory outside of the
@@ -476,7 +497,9 @@ def node_get_project_path(self, node, *, check_is_file=False, check_is_dir=False
476497
477498
"""
478499

479-
return self.__project.get_path_from_node(node, check_is_file=check_is_file, check_is_dir=check_is_dir)
500+
return self.__project.get_path_from_node(
501+
node, check_is_file=check_is_file, check_is_dir=check_is_dir
502+
)
480503

481504
def debug(self, brief: str, *, detail: Optional[str] = None) -> None:
482505
"""Print a debugging message
@@ -514,7 +537,13 @@ def info(self, brief: str, *, detail: Optional[str] = None) -> None:
514537
"""
515538
self.__message(MessageType.INFO, brief, detail=detail)
516539

517-
def warn(self, brief: str, *, detail: Optional[str] = None, warning_token: Optional[str] = None) -> None:
540+
def warn(
541+
self,
542+
brief: str,
543+
*,
544+
detail: Optional[str] = None,
545+
warning_token: Optional[str] = None,
546+
) -> None:
518547
"""Print a warning message, checks warning_token against project configuration
519548
520549
Args:
@@ -533,7 +562,9 @@ def warn(self, brief: str, *, detail: Optional[str] = None, warning_token: Optio
533562

534563
if project._warning_is_fatal(warning_token):
535564
detail = detail if detail else ""
536-
raise PluginError(message="{}\n{}".format(brief, detail), reason=warning_token)
565+
raise PluginError(
566+
message="{}\n{}".format(brief, detail), reason=warning_token
567+
)
537568

538569
self.__message(MessageType.WARN, brief=brief, detail=detail)
539570

@@ -551,7 +582,11 @@ def log(self, brief: str, *, detail: Optional[str] = None) -> None:
551582

552583
@contextmanager
553584
def timed_activity(
554-
self, activity_name: str, *, detail: Optional[str] = None, silent_nested: bool = False
585+
self,
586+
activity_name: str,
587+
*,
588+
detail: Optional[str] = None,
589+
silent_nested: bool = False,
555590
) -> Generator[None, None, None]:
556591
"""Context manager for performing timed activities in plugins
557592
@@ -589,7 +624,7 @@ def blocking_activity(
589624
activity_name: str,
590625
*,
591626
detail: Optional[str] = None,
592-
silent_nested: bool = False
627+
silent_nested: bool = False,
593628
) -> T1:
594629
"""Execute a blocking activity in the background.
595630
@@ -616,7 +651,10 @@ def blocking_activity(
616651
the return value from `target`.
617652
"""
618653
with self.__context.messenger.timed_activity(
619-
activity_name, element_name=self._get_full_name(), detail=detail, silent_nested=silent_nested
654+
activity_name,
655+
element_name=self._get_full_name(),
656+
detail=detail,
657+
silent_nested=silent_nested,
620658
):
621659
result_queue = self.__multiprocessing_context.Queue()
622660
proc = None
@@ -636,7 +674,10 @@ def resume_proc():
636674
with suppress(ProcessLookupError):
637675
os.kill(proc.pid, signal.SIGCONT)
638676

639-
with _signals.suspendable(suspend_proc, resume_proc), _signals.terminator(kill_proc):
677+
with (
678+
_signals.suspendable(suspend_proc, resume_proc),
679+
_signals.terminator(kill_proc),
680+
):
640681
proc = self.__multiprocessing_context.Process(
641682
target=_background_job_wrapper, args=(result_queue, target, args)
642683
)
@@ -659,16 +700,24 @@ def resume_proc():
659700
should_continue = False
660701
continue
661702
else:
662-
raise PluginError("Background process died with error code {}".format(proc.exitcode))
703+
raise PluginError(
704+
"Background process died with error code {}".format(
705+
proc.exitcode
706+
)
707+
)
663708

664709
try:
665710
proc.join(timeout=15)
666711
proc.terminate()
667712
except TimeoutError:
668-
raise PluginError("Background process didn't exit after 15 seconds and got killed.")
713+
raise PluginError(
714+
"Background process didn't exit after 15 seconds and got killed."
715+
)
669716

670717
if err is not None:
671-
raise PluginError("An error happened while running a blocking activity", detail=err)
718+
raise PluginError(
719+
"An error happened while running a blocking activity", detail=err
720+
)
672721

673722
return result
674723

@@ -944,10 +993,15 @@ def __call(
944993

945994
self.__note_command(output_file, args, cwd)
946995

947-
exit_code, output = utils._call(args, cwd=cwd, env=env, stdin=stdin, stdout=stdout, stderr=stderr)
996+
exit_code, output = utils._call(
997+
args, cwd=cwd, env=env, stdin=stdin, stdout=stdout, stderr=stderr
998+
)
948999

9491000
if fail and exit_code:
950-
raise PluginError("{plugin}: {message}".format(plugin=self, message=fail), temporary=fail_temporarily)
1001+
raise PluginError(
1002+
"{plugin}: {message}".format(plugin=self, message=fail),
1003+
temporary=fail_temporarily,
1004+
)
9511005

9521006
return (exit_code, output)
9531007

@@ -999,7 +1053,9 @@ def __get_full_name(self):
9991053

10001054
# A local table for _prefix_warning()
10011055
#
1002-
__CORE_WARNINGS = [value for name, value in CoreWarnings.__dict__.items() if not name.startswith("__")]
1056+
__CORE_WARNINGS = [
1057+
value for name, value in CoreWarnings.__dict__.items() if not name.startswith("__")
1058+
]
10031059

10041060

10051061
# _prefix_warning():

src/buildstream/plugins/sources/local.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ def get_ref(self):
9393
def set_ref(self, ref, node):
9494
pass # pragma: nocover
9595

96+
def load_source_provenance(self, node):
97+
pass
98+
99+
def get_source_provenance(self):
100+
return None
101+
102+
def set_source_provenance(self, source_provenance, node):
103+
pass
104+
96105
def fetch(self): # pylint: disable=arguments-differ
97106
# Nothing to do here for a local source
98107
pass # pragma: nocover

0 commit comments

Comments
 (0)