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.
4242
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
43+ Any attribute used in the ``provenance`` dictionary of a source must be
44+ defined in the project.conf using the ``source-provenance-fields`` dictionary
45+ to define the attribute and its significance.
5046
5147 *Since: 2.5*
5248
371367
372368import os
373369from contextlib import contextmanager
374- from typing import Iterable , Iterator , Optional , Tuple , Dict , Any , Set , TYPE_CHECKING , Union
370+ from typing import (
371+ Iterable ,
372+ Iterator ,
373+ Optional ,
374+ Tuple ,
375+ Dict ,
376+ Any ,
377+ Set ,
378+ TYPE_CHECKING ,
379+ Union ,
380+ )
375381from dataclasses import dataclass
376382
377383from . import _yaml , utils
378384from .node import MappingNode
379385from .plugin import Plugin
380386from .sourcemirror import SourceMirror
381- from .types import SourceRef , CoreWarnings , FastEnum , _SourceProvenance
382- from ._exceptions import BstError , ImplError , PluginError
383- from .exceptions import ErrorDomain
387+ from .types import SourceRef , CoreWarnings , FastEnum
388+ from ._exceptions import BstError , ImplError , PluginError , LoadError
389+ from .exceptions import ErrorDomain , LoadErrorReason
384390from ._loader .metasource import MetaSource
385391from ._projectrefs import ProjectRefStorage
386392from ._cachekey import generate_key
396402
397403 # pylint: enable=cyclic-import
398404
405+ SourceProvenance = MappingNode
406+
399407
400408class SourceError (BstError ):
401409 """This exception should be raised by :class:`.Source` implementations
@@ -409,9 +417,20 @@ class SourceError(BstError):
409417 """
410418
411419 def __init__ (
412- self , message : str , * , detail : Optional [str ] = None , reason : Optional [str ] = None , temporary : bool = False
420+ self ,
421+ message : str ,
422+ * ,
423+ detail : Optional [str ] = None ,
424+ reason : Optional [str ] = None ,
425+ temporary : bool = False ,
413426 ):
414- super ().__init__ (message , detail = detail , domain = ErrorDomain .SOURCE , reason = reason , temporary = temporary )
427+ super ().__init__ (
428+ message ,
429+ detail = detail ,
430+ domain = ErrorDomain .SOURCE ,
431+ reason = reason ,
432+ temporary = temporary ,
433+ )
415434
416435
417436@dataclass
@@ -553,8 +572,7 @@ def __init__(
553572 self ,
554573 kind : str ,
555574 url : str ,
556- homepage : Optional [str ],
557- issue_tracker : Optional [str ],
575+ provenance : Optional [SourceProvenance ],
558576 medium : Union [SourceInfoMedium , str ],
559577 version_type : Union [SourceVersionType , str ],
560578 version : str ,
@@ -572,14 +590,9 @@ def __init__(
572590 The url of the source input
573591 """
574592
575- self .homepage : Optional [str ] = homepage
576- """
577- The project homepage URL
578- """
579-
580- self .issue_tracker : Optional [str ] = issue_tracker
593+ self .provenance = provenance
581594 """
582- The project issue tracking URL
595+ The optional YAML node with source provenance attributes
583596 """
584597
585598 self .medium : Union [SourceInfoMedium , str ] = medium
@@ -642,10 +655,14 @@ def serialize(self) -> Dict[str, Union[str, Dict[str, str]]]:
642655 "url" : self .url ,
643656 }
644657
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
658+ if self .provenance is not None :
659+ # need to keep homepage/issue-tracker [also] at the top-level for backward compat
660+ if (homepage := self .provenance .get_str ("homepage" , None )) is not None :
661+ version_info ["homepage" ] = homepage
662+ if (issue_tracker := self .provenance .get_str ("issue-tracker" , None )) is not None :
663+ version_info ["issue-tracker" ] = issue_tracker
664+
665+ version_info ["provenance" ] = self .provenance .strip_node_info ()
649666
650667 version_info ["medium" ] = medium_str
651668 version_info ["version-type" ] = version_type_str
@@ -824,9 +841,9 @@ def __init__(
824841 self .__element_kind = meta .element_kind # The kind of the element owning this source
825842 self ._directory = meta .directory # Staging relative directory
826843 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
844+ self .__provenance : Optional [SourceProvenance ] = (
845+ meta . provenance
846+ ) # The source provenance for general user provided SourceInfo
830847
831848 self .__key = None # Cache key for source
832849
@@ -1393,23 +1410,33 @@ def create_source_info(
13931410
13941411 *Since: 2.5*
13951412 """
1396- homepage = None
1397- issue_tracker = None
1413+ project = self ._get_project ()
13981414
1415+ provenance : SourceProvenance | None
13991416 if provenance_node is not None :
1400- provenance : Optional [_SourceProvenance ] = _SourceProvenance .new_from_node (provenance_node )
1417+ # Ensure provenance node keys are valid and values are all strings
1418+ defined_provenance_fields = (
1419+ project ._project_conf .get_mapping ("source-provenance-fields" , None ) or project .source_provenance_fields
1420+ )
1421+
1422+ try :
1423+ provenance_node .validate_keys (defined_provenance_fields .keys ())
1424+ except LoadError as E :
1425+ raise LoadError (
1426+ "Specified source attribute not defined in project config\n {}" .format (E ),
1427+ LoadErrorReason .UNDEFINED_SOURCE_PROVENANCE_ATTRIBUTE ,
1428+ )
1429+
1430+ # Make sure everything is a string
1431+ provenance = MappingNode .from_dict ({key : value .as_str () for key , value in provenance_node .items ()})
1432+
14011433 else :
14021434 provenance = self .__provenance
14031435
1404- if provenance is not None :
1405- homepage = provenance .homepage
1406- issue_tracker = provenance .issue_tracker
1407-
14081436 return SourceInfo (
14091437 self .get_kind (),
14101438 url ,
1411- homepage ,
1412- issue_tracker ,
1439+ provenance ,
14131440 medium ,
14141441 version_type ,
14151442 version ,
@@ -1767,7 +1794,8 @@ def process_value(action, container, path, key, new_value):
17671794 _yaml .roundtrip_dump (data , filename )
17681795 except OSError as e :
17691796 raise SourceError (
1770- "{}: Error saving source reference to '{}': {}" .format (self , filename , e ), reason = "save-ref-error"
1797+ "{}: Error saving source reference to '{}': {}" .format (self , filename , e ),
1798+ reason = "save-ref-error" ,
17711799 ) from e
17721800
17731801 return True
0 commit comments