Skip to content

Commit 56a4b79

Browse files
authored
Merge pull request #2098 from joshua-zivkovic/JZ/multi-source-provenance
Support multi source source provenance info
2 parents 86a950a + 84e490e commit 56a4b79

File tree

10 files changed

+218
-3
lines changed

10 files changed

+218
-3
lines changed

src/buildstream/source.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,14 @@ class Source(Plugin):
759759
# The defaults from the project
760760
__defaults: Optional[Dict[str, Any]] = None
761761

762+
BST_CUSTOM_SOURCE_PROVENANCE = False
763+
"""Whether multiple sources' provenance information are provided
764+
765+
Used primarily to override the usage of top-level source
766+
provenance of a source where individual sub-source's
767+
provenance should instead be provided
768+
"""
769+
762770
BST_REQUIRES_PREVIOUS_SOURCES_TRACK = False
763771
"""Whether access to previous sources is required during track
764772
@@ -828,6 +836,12 @@ def __init__(
828836
_SourceProvenance
829837
] = meta.provenance # The _SourceProvenance for general user provided SourceInfo
830838

839+
if self.__provenance is not None and self.BST_CUSTOM_SOURCE_PROVENANCE:
840+
raise SourceError(
841+
f"{self._get_provenance()} Custom source provenance plugin: Refusing to use top level source provenance",
842+
reason="top-level-provenance-on-custom-implementation",
843+
)
844+
831845
self.__key = None # Cache key for source
832846

833847
# The alias_override is only set on a re-instantiated Source
@@ -1368,6 +1382,7 @@ def create_source_info(
13681382
*,
13691383
version_guess: Optional[str] = None,
13701384
extra_data: Optional[Dict[str, str]] = None,
1385+
provenance_node: Optional[MappingNode] = None,
13711386
) -> SourceInfo:
13721387
"""Create a :class:`.SourceInfo` object
13731388
@@ -1387,14 +1402,22 @@ def create_source_info(
13871402
version: A string which represents a unique version of this source input
13881403
version_guess: An optional string representing the guessed human readable version
13891404
extra_data: Additional plugin defined key/values
1405+
provenance_node: An optional :class:`Node <buildstream.node.Node>` with source provenance attributes,
1406+
defaults to the provenance specified at the top level of the source.
13901407
13911408
*Since: 2.5*
13921409
"""
13931410
homepage = None
13941411
issue_tracker = None
1395-
if self.__provenance is not None:
1396-
homepage = self.__provenance.homepage
1397-
issue_tracker = self.__provenance.issue_tracker
1412+
1413+
if provenance_node is not None:
1414+
source_provenance: Optional[_SourceProvenance] = _SourceProvenance.new_from_node(provenance_node)
1415+
else:
1416+
source_provenance = self.__provenance
1417+
1418+
if source_provenance is not None:
1419+
homepage = source_provenance.homepage
1420+
issue_tracker = source_provenance.issue_tracker
13981421

13991422
return SourceInfo(
14001423
self.get_kind(),

tests/frontend/show.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,3 +814,33 @@ def test_source_info_workspace(cli, datafiles, tmpdir):
814814

815815
# There is no version guessing for a workspace
816816
assert source_info.get_str("version-guess", None) is None
817+
818+
819+
@pytest.mark.datafiles(os.path.join(DATA_DIR, "source-info"))
820+
def test_multi_source_info(cli, datafiles):
821+
project = str(datafiles)
822+
result = cli.run(
823+
project=project, silent=True, args=["show", "--format", "%{name}:\n%{source-info}", "multisource.bst"]
824+
)
825+
result.assert_success()
826+
827+
loaded = _yaml.load_data(result.output)
828+
sources = loaded.get_sequence("multisource.bst")
829+
830+
source_info = sources.mapping_at(0)
831+
assert source_info.get_str("kind") == "multisource"
832+
assert source_info.get_str("url") == "http://ponyfarm.com/ponies"
833+
assert source_info.get_str("medium") == "pony-ride"
834+
assert source_info.get_str("version-type") == "pony-age"
835+
assert source_info.get_str("version") == "1234567"
836+
assert source_info.get_str("version-guess", None) == "12"
837+
assert "homepage" not in source_info
838+
839+
source_info = sources.mapping_at(1)
840+
assert source_info.get_str("kind") == "multisource"
841+
assert source_info.get_str("url") == "http://ponyfarm.com/happy"
842+
assert source_info.get_str("medium") == "pony-ride"
843+
assert source_info.get_str("version-type") == "pony-age"
844+
assert source_info.get_str("version") == "1234567"
845+
assert source_info.get_str("version-guess", None) == "12"
846+
assert source_info.get_str("homepage") == "http://happy.ponyfarm.com"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: import
2+
3+
sources:
4+
- kind: multisource
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from buildstream import Node, Source
2+
3+
4+
class MultiSource(Source):
5+
BST_MIN_VERSION = "2.0"
6+
7+
BST_CUSTOM_SOURCE_PROVENANCE = True
8+
9+
def configure(self, node):
10+
pass
11+
12+
def preflight(self):
13+
pass
14+
15+
def get_unique_key(self):
16+
return {}
17+
18+
def load_ref(self, node):
19+
pass
20+
21+
def get_ref(self):
22+
return {}
23+
24+
def set_ref(self, ref, node):
25+
pass
26+
27+
def is_cached(self):
28+
return False
29+
30+
def collect_source_info(self):
31+
return [
32+
self.create_source_info(
33+
"http://ponyfarm.com/ponies", "pony-ride", "pony-age", "1234567", version_guess="12"
34+
),
35+
self.create_source_info(
36+
"http://ponyfarm.com/happy",
37+
"pony-ride",
38+
"pony-age",
39+
"1234567",
40+
version_guess="12",
41+
provenance_node=Node.from_dict({"homepage": "http://happy.ponyfarm.com"}),
42+
),
43+
]
44+
45+
46+
# Plugin entry point
47+
def setup():
48+
return MultiSource

tests/frontend/source-info/project.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ plugins:
1212
path: plugins
1313
sources:
1414
- extradata
15+
- multisource
1516
- testsource
1617
- unimplemented
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
#
14+
15+
# Pylint doesn't play well with fixtures and dependency injection from pytest
16+
# pylint: disable=redefined-outer-name
17+
18+
import os
19+
import pytest
20+
21+
from buildstream._testing import generate_project, load_yaml
22+
from buildstream._testing import cli # pylint: disable=unused-import
23+
from buildstream.exceptions import ErrorDomain
24+
25+
26+
DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "source_provenance_attributes")
27+
28+
29+
##################################################################
30+
# Tests #
31+
##################################################################
32+
# Test that no defined source provenance attributes blocks all source provenance data
33+
@pytest.mark.datafiles(DATA_DIR)
34+
def test_source_provenance_disallow_top_level(cli, datafiles):
35+
project = str(datafiles)
36+
37+
# Set the project_dir alias in project.conf to the path to the tested project
38+
project_config_path = os.path.join(project, "project.conf")
39+
project_config = load_yaml(project_config_path)
40+
aliases = project_config.get_mapping("aliases")
41+
aliases["project_dir"] = "file://{}".format(project)
42+
43+
generate_project(project, project_config)
44+
45+
# Make sure disallowed usage of top-level source proveance fails
46+
result = cli.run(
47+
project=project,
48+
args=["show", "target.bst"],
49+
)
50+
51+
result.assert_main_error(ErrorDomain.SOURCE, "top-level-provenance-on-custom-implementation")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
kind: import
2+
3+
sources:
4+
- kind: multisource-plugin
5+
url: project_dir:/files/file
6+
provenance:
7+
homepage: bar
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello World!
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from buildstream import Node, Source
2+
3+
4+
class MultiSource(Source):
5+
BST_MIN_VERSION = "2.0"
6+
7+
BST_CUSTOM_SOURCE_PROVENANCE = True
8+
9+
def configure(self, node):
10+
pass
11+
12+
def preflight(self):
13+
pass
14+
15+
def get_unique_key(self):
16+
return {}
17+
18+
def load_ref(self, node):
19+
pass
20+
21+
def get_ref(self):
22+
return {}
23+
24+
def set_ref(self, ref, node):
25+
pass
26+
27+
def is_cached(self):
28+
return False
29+
30+
def collect_source_info(self):
31+
return []
32+
33+
34+
# Plugin entry point
35+
def setup():
36+
return MultiSource
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Project with source provenance attributes
2+
name: foo
3+
min-version: 2.0
4+
5+
element-path: elements
6+
7+
aliases:
8+
project_dir: file://{project_dir}
9+
10+
plugins:
11+
- origin: local
12+
path: plugins
13+
sources:
14+
- multisource-plugin

0 commit comments

Comments
 (0)