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
378374from .node import MappingNode
379375from .plugin import Plugin
380376from .sourcemirror import SourceMirror
381- from .types import SourceRef , CoreWarnings , FastEnum , _SourceProvenance
382- from ._exceptions import BstError , ImplError , PluginError
383- from .exceptions import ErrorDomain
377+ from .types import SourceRef , CoreWarnings , FastEnum
378+ from ._exceptions import BstError , ImplError , PluginError , LoadError
379+ from .exceptions import ErrorDomain , LoadErrorReason
384380from ._loader .metasource import MetaSource
385381from ._projectrefs import ProjectRefStorage
386382from ._cachekey import generate_key
396392
397393 # pylint: enable=cyclic-import
398394
395+ SourceProvenance = MappingNode
396+
399397
400398class SourceError (BstError ):
401399 """This exception should be raised by :class:`.Source` implementations
@@ -553,8 +551,7 @@ def __init__(
553551 self ,
554552 kind : str ,
555553 url : str ,
556- homepage : Optional [str ],
557- issue_tracker : Optional [str ],
554+ provenance : Optional [SourceProvenance ],
558555 medium : Union [SourceInfoMedium , str ],
559556 version_type : Union [SourceVersionType , str ],
560557 version : str ,
@@ -572,14 +569,9 @@ def __init__(
572569 The url of the source input
573570 """
574571
575- self .homepage : Optional [ str ] = homepage
572+ self .provenance = provenance
576573 """
577- The project homepage URL
578- """
579-
580- self .issue_tracker : Optional [str ] = issue_tracker
581- """
582- The project issue tracking URL
574+ The optional YAML node with source provenance attributes
583575 """
584576
585577 self .medium : Union [SourceInfoMedium , str ] = medium
@@ -642,10 +634,14 @@ def serialize(self) -> Dict[str, Union[str, Dict[str, str]]]:
642634 "url" : self .url ,
643635 }
644636
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
637+ if self .provenance is not None :
638+ # need to keep homepage/issue-tracker [also] at the top-level for backward compat
639+ if (homepage := self .provenance .get_str ("homepage" , None )) is not None :
640+ version_info ["homepage" ] = homepage
641+ if (issue_tracker := self .provenance .get_str ("issue-tracker" , None )) is not None :
642+ version_info ["issue-tracker" ] = issue_tracker
643+
644+ version_info ["provenance" ] = self .provenance
649645
650646 version_info ["medium" ] = medium_str
651647 version_info ["version-type" ] = version_type_str
@@ -824,9 +820,9 @@ def __init__(
824820 self .__element_kind = meta .element_kind # The kind of the element owning this source
825821 self ._directory = meta .directory # Staging relative directory
826822 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
823+ self .__provenance : Optional [SourceProvenance ] = (
824+ meta . provenance
825+ ) # The source provenance for general user provided SourceInfo
830826
831827 self .__key = None # Cache key for source
832828
@@ -1393,23 +1389,33 @@ def create_source_info(
13931389
13941390 *Since: 2.5*
13951391 """
1396- homepage = None
1397- issue_tracker = None
1392+ project = self ._get_project ()
13981393
13991394 if provenance_node is not None :
1400- provenance : Optional [_SourceProvenance ] = _SourceProvenance .new_from_node (provenance_node )
1395+ # Ensure provenance node keys are valid and values are all strings
1396+ defined_provenance_fields = (
1397+ project ._project_conf .get_mapping ("source-provenance-fields" , None )
1398+ or project .source_provenance_fields
1399+ )
1400+
1401+ try :
1402+ provenance_node .validate_keys (defined_provenance_fields .keys ())
1403+ except LoadError as E :
1404+ raise LoadError (
1405+ "Specified source attribute not defined in project config\n {}" .format (E ),
1406+ LoadErrorReason .UNDEFINED_SOURCE_PROVENANCE_ATTRIBUTE ,
1407+ )
1408+
1409+ # Make sure everything is a string
1410+ provenance = MappingNode .from_dict ({key : value .as_str () for key , value in provenance_node .items ()})
1411+
14011412 else :
14021413 provenance = self .__provenance
14031414
1404- if provenance is not None :
1405- homepage = provenance .homepage
1406- issue_tracker = provenance .issue_tracker
1407-
14081415 return SourceInfo (
14091416 self .get_kind (),
14101417 url ,
1411- homepage ,
1412- issue_tracker ,
1418+ provenance ,
14131419 medium ,
14141420 version_type ,
14151421 version ,
0 commit comments