Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/buildstream/_loader/metasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ class MetaSource:
# element_index: The index of the source in the owning element's source list
# element_kind: The kind of the owning element
# kind: The kind of the source
# directory: A subdirectory where to stage the source
# provenance: The user provided provenance information (e.g. homepage, issue tracking, etc).
# config: The configuration data for the source
# first_pass: This source will be used with first project pass configuration (used for junctions).
#
def __init__(self, element_name, element_index, element_kind, kind, config, directory, first_pass):
def __init__(self, element_name, element_index, element_kind, kind, directory, provenance, config, first_pass):
self.element_name = element_name
self.element_index = element_index
self.element_kind = element_kind
self.kind = kind
self.config = config
self.directory = directory
self.provenance = provenance
self.config = config
self.first_pass = first_pass
1 change: 1 addition & 0 deletions src/buildstream/_loader/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ class Symbol:
JUNCTION = "junction"
SANDBOX = "sandbox"
STRICT = "strict"
PROVENANCE = "provenance"
22 changes: 19 additions & 3 deletions src/buildstream/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
from .sandbox import _SandboxFlags, SandboxCommandError
from .sandbox._config import SandboxConfig
from .sandbox._sandboxremote import SandboxRemote
from .types import _Scope, _CacheBuildTrees, _KeyStrength, OverlapAction, _DisplayKey
from .types import _Scope, _CacheBuildTrees, _KeyStrength, OverlapAction, _DisplayKey, _SourceProvenance
from ._artifact import Artifact
from ._elementproxy import ElementProxy
from ._elementsources import ElementSources
Expand Down Expand Up @@ -2584,8 +2584,9 @@ def __load_sources(self, load_element):
0,
self.get_kind(),
"workspace",
Node.from_dict(workspace_node),
None,
None,
Node.from_dict(workspace_node),
load_element.first_pass,
)
meta_sources.append(meta)
Expand All @@ -2606,8 +2607,23 @@ def __load_sources(self, load_element):
directory = source.get_str(Symbol.DIRECTORY, default=None)
if directory:
del source[Symbol.DIRECTORY]

# Provenance is optional
provenance_node = source.get_mapping(Symbol.PROVENANCE, default=None)
provenance = None
if provenance_node:
del source[Symbol.PROVENANCE]
provenance = _SourceProvenance.new_from_node(provenance_node)

meta_source = MetaSource(
self.name, index, self.get_kind(), kind.as_str(), source, directory, load_element.first_pass
self.name,
index,
self.get_kind(),
kind.as_str(),
directory,
provenance,
source,
load_element.first_pass,
)
meta_sources.append(meta_source)

Expand Down
75 changes: 68 additions & 7 deletions src/buildstream/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@
This sets the location within the build root that the content of the source
will be loaded in to. If the location does not exist, it will be created.

* Provenance

The ``provenance`` attribute depicts a dictionary which is used for users
to provide additional source provenance related metadata which will later
be reported in :class:`.SourceInfo` objects.

The ``provenance`` dictionary supports the following fields:

* Homepage

The ``homepage`` attribute can be used to specify the project homepage URL

* Issue Tracker

The ``issue-tracker`` attribute can be used to specify the project's issue tracking URL

*Since: 2.5*


.. _core_source_abstract_methods:

Abstract Methods
Expand Down Expand Up @@ -359,7 +378,7 @@
from .node import MappingNode
from .plugin import Plugin
from .sourcemirror import SourceMirror
from .types import SourceRef, CoreWarnings, FastEnum
from .types import SourceRef, CoreWarnings, FastEnum, _SourceProvenance
from ._exceptions import BstError, ImplError, PluginError
from .exceptions import ErrorDomain
from ._loader.metasource import MetaSource
Expand Down Expand Up @@ -537,10 +556,18 @@ class SourceInfo:
*Since: 2.5*
"""

#
# NOTE: The constructor is not public API, and plugins must
# call Source.create_source_info(), the docstring above
# starting with `SourceInfo()` ensures that documentation
# does not show constructor arguments.
#
def __init__(
self,
kind: str,
url: str,
homepage: Optional[str],
issue_tracker: Optional[str],
medium: Union[SourceInfoMedium, str],
version_type: Union[SourceVersionType, str],
version: str,
Expand All @@ -558,6 +585,16 @@ def __init__(
The url of the source input
"""

self.homepage: Optional[str] = homepage
"""
The project homepage URL
"""

self.issue_tracker: Optional[str] = issue_tracker
"""
The project issue tracking URL
"""

self.medium: Union[SourceInfoMedium, str] = medium
"""
The :class:`.SourceInfoMedium` of the source input, or in the case
Expand Down Expand Up @@ -618,11 +655,17 @@ def _serialize(self) -> Dict[str, Any]:
version_info = {
"kind": self.kind,
"url": self.url,
"medium": medium_str,
"version-type": version_type_str,
"version": self.version,
}

if self.homepage is not None:
version_info["homepage"] = self.homepage
if self.issue_tracker is not None:
version_info["issue-tracker"] = self.issue_tracker

version_info["medium"] = medium_str
version_info["version-type"] = version_type_str
version_info["version"] = self.version

if self.version_guess is not None:
version_info["version-guess"] = self.version_guess

Expand Down Expand Up @@ -798,6 +841,9 @@ def __init__(
self.__element_kind = meta.element_kind # The kind of the element owning this source
self._directory = meta.directory # Staging relative directory
self.__variables = variables # The variables used to resolve the source's config
self.__provenance: Optional[
_SourceProvenance
] = meta.provenance # The _SourceProvenance for general user provided SourceInfo

self.__key = None # Cache key for source

Expand All @@ -822,7 +868,7 @@ def __init__(

self.__is_cached = None

COMMON_CONFIG_KEYS = ["kind", "directory"]
COMMON_CONFIG_KEYS = ["kind", "directory", "provenance"]
"""Common source config keys

Source config keys that must not be accessed in configure(), and
Expand Down Expand Up @@ -1357,8 +1403,22 @@ def create_source_info(

*Since: 2.5*
"""
homepage = None
issue_tracker = None
if self.__provenance is not None:
homepage = self.__provenance.homepage
issue_tracker = self.__provenance.issue_tracker

return SourceInfo(
self.get_kind(), url, medium, version_type, version, version_guess=version_guess, extra_data=extra_data
self.get_kind(),
url,
homepage,
issue_tracker,
medium,
version_type,
version,
version_guess=version_guess,
extra_data=extra_data,
)

#############################################################
Expand Down Expand Up @@ -1850,8 +1910,9 @@ def __clone_for_uri(self, mirror):
self.__element_index,
self.__element_kind,
self.get_kind(),
self.__config,
self._directory,
self.__provenance,
self.__config,
self.__first_pass,
)

Expand Down
36 changes: 36 additions & 0 deletions src/buildstream/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,42 @@ def new_from_node(cls, node: MappingNode) -> "_SourceMirror":
return cls(name, aliases)


# _SourceProvenance()
#
# A simple object describing user provided source provenance information
#
# Args:
# homepage: The project homepage URL
# issue_tracker: The project issue reporting URL
#
class _SourceProvenance:
def __init__(self, homepage: Optional[str], issue_tracker: Optional[str]):
self.homepage: Optional[str] = homepage
self.issue_tracker: Optional[str] = issue_tracker

# new_from_node():
#
# Creates a _SourceProvenance() from a YAML loaded node.
#
# Args:
# node: The configuration node describing the spec.
#
# Returns:
# The described _SourceProvenance instance.
#
# Raises:
# LoadError: If the node is malformed.
#
@classmethod
def new_from_node(cls, node: MappingNode) -> "_SourceProvenance":
node.validate_keys(["homepage", "issue-tracker"])

homepage: Optional[str] = node.get_str("homepage", None)
issue_tracker: Optional[str] = node.get_str("issue-tracker", None)

return cls(homepage, issue_tracker)


########################################
# Type aliases #
########################################
Expand Down
44 changes: 39 additions & 5 deletions tests/frontend/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ def test_invalid_alias(cli, tmpdir, datafiles):

@pytest.mark.datafiles(os.path.join(DATA_DIR, "source-info"))
@pytest.mark.parametrize(
"target, expected_kind, expected_url, expected_medium, expected_version_type, expected_version, expected_guess_version",
"target, expected_kind, expected_url, expected_medium, expected_version_type, expected_version, expected_guess_version, expected_homepage, expected_issue_tracker",
[
(
"local.bst",
Expand All @@ -590,6 +590,8 @@ def test_invalid_alias(cli, tmpdir, datafiles):
"cas-digest",
"9391a5943daf287b46520c4289d41cab5f6b33e643f7661bcf620de7f02c1c9b/82",
None,
None,
None,
),
(
"tar.bst",
Expand All @@ -599,6 +601,8 @@ def test_invalid_alias(cli, tmpdir, datafiles):
"sha256",
"9d0c936c78d0dfe3a67cae372c9a2330476ea87a2eec16b2daada64a664ca501",
"1.2.3",
None,
None,
),
(
"tar-no-micro.bst",
Expand All @@ -608,6 +612,8 @@ def test_invalid_alias(cli, tmpdir, datafiles):
"sha256",
"9d0c936c78d0dfe3a67cae372c9a2330476ea87a2eec16b2daada64a664ca501",
"1.2",
None,
None,
),
(
"tar-custom-version.bst",
Expand All @@ -617,6 +623,8 @@ def test_invalid_alias(cli, tmpdir, datafiles):
"sha256",
"9d0c936c78d0dfe3a67cae372c9a2330476ea87a2eec16b2daada64a664ca501",
"2.4.93",
None,
None,
),
(
"tar-explicit.bst",
Expand All @@ -626,6 +634,8 @@ def test_invalid_alias(cli, tmpdir, datafiles):
"sha256",
"9d0c936c78d0dfe3a67cae372c9a2330476ea87a2eec16b2daada64a664ca501",
"3.2.1",
None,
None,
),
(
"testsource.bst",
Expand All @@ -635,9 +645,30 @@ def test_invalid_alias(cli, tmpdir, datafiles):
"pony-age",
"1234567",
"12",
None,
None,
),
(
"user-provenance.bst",
"tar",
"https://flying-ponies.com/releases/1.2/pony-flight-1.2.3.tgz",
"remote-file",
"sha256",
"9d0c936c78d0dfe3a67cae372c9a2330476ea87a2eec16b2daada64a664ca501",
"1.2.3",
"https://flying-ponies.com/index.html",
"https://bugs.flying-ponies.com/issues",
),
],
ids=["local", "tar-full-version", "tar-no-micro", "tar-custom-version", "tar-explicit", "testsource"],
ids=[
"local",
"tar-full-version",
"tar-no-micro",
"tar-custom-version",
"tar-explicit",
"testsource",
"user-provenance",
],
)
def test_source_info(
cli,
Expand All @@ -649,6 +680,8 @@ def test_source_info(
expected_version_type,
expected_version,
expected_guess_version,
expected_homepage,
expected_issue_tracker,
):
project = str(datafiles)
result = cli.run(project=project, silent=True, args=["show", "--format", "%{name}:\n%{source-info}", target])
Expand All @@ -664,9 +697,10 @@ def test_source_info(
assert source_info.get_str("version-type") == expected_version_type
assert source_info.get_str("version") == expected_version

guess_version = source_info.get_str("version-guess", None)
if guess_version or expected_guess_version:
assert guess_version == expected_guess_version
# Optional fields
assert source_info.get_str("version-guess", None) == expected_guess_version
assert source_info.get_str("homepage", None) == expected_homepage
assert source_info.get_str("issue-tracker", None) == expected_issue_tracker


@pytest.mark.datafiles(os.path.join(DATA_DIR, "source-info"))
Expand Down
9 changes: 9 additions & 0 deletions tests/frontend/source-info/elements/user-provenance.bst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
kind: import

sources:
- kind: tar
url: https://flying-ponies.com/releases/1.2/pony-flight-1.2.3.tgz
ref: 9d0c936c78d0dfe3a67cae372c9a2330476ea87a2eec16b2daada64a664ca501
provenance:
homepage: https://flying-ponies.com/index.html
issue-tracker: https://bugs.flying-ponies.com/issues
Loading