Skip to content

Commit f6b0d0c

Browse files
author
Adrien Plazas
committed
_frontend/widget: Add artifact-cas-digest show format
This adds the artifact-cas-digest format string to the show command, allowing to show the CAS digest of the built artifact.
1 parent 6c06e1b commit f6b0d0c

File tree

16 files changed

+181
-2
lines changed

16 files changed

+181
-2
lines changed

man/bst-show.1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Show elements in the pipeline
4545
%{deps} A list of all dependencies
4646
%{build-deps} A list of build dependencies
4747
%{runtime-deps} A list of runtime dependencies
48+
%{artifact-cas-digest} The CAS digest of the built artifact
4849
.PP
4950
The value of the %{symbol} without the leading '%' character is understood
5051
as a pythonic formatting string, so python formatting features apply,

src/buildstream/_frontend/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ def show(app, elements, deps, except_, order, format_):
613613
%{deps} A list of all dependencies
614614
%{build-deps} A list of build dependencies
615615
%{runtime-deps} A list of runtime dependencies
616+
%{artifact-cas-digest} The CAS digest of the built artifact
616617
617618
The value of the %{symbol} without the leading '%' character is understood
618619
as a pythonic formatting string, so python formatting features apply,
@@ -638,7 +639,8 @@ def show(app, elements, deps, except_, order, format_):
638639
state_match = re.search(r"%(\{(state)[^%]*?\})", format_)
639640
key_match = re.search(r"%(\{(key)[^%]*?\})", format_)
640641
full_key_match = re.search(r"%(\{(full-key)[^%]*?\})", format_)
641-
need_state = bool(state_match or key_match or full_key_match)
642+
artifact_cas_digest_match = re.search(r"%(\{(artifact-cas-digest)[^%]*?\})", format_)
643+
need_state = bool(state_match or key_match or full_key_match or artifact_cas_digest_match)
642644

643645
if not elements:
644646
elements = app.project.get_default_targets()

src/buildstream/_frontend/widget.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,25 @@ def show_pipeline(self, dependencies, format_):
437437
runtime_deps = [e._get_full_name() for e in element._dependencies(_Scope.RUN, recurse=False)]
438438
line = p.fmt_subst(line, "runtime-deps", _yaml.roundtrip_dump_string(runtime_deps).rstrip("\n"))
439439

440+
# Artifact CAS Digest
441+
if "%{artifact-cas-digest" in format_:
442+
artifact = element._get_artifact()
443+
if not artifact.query_cache():
444+
artifact = None
445+
if artifact is not None:
446+
artifact_files = artifact.get_files()
447+
# We call the private CasBasedDirectory._get_digest() for
448+
# the moment, we should make it public on Directory.
449+
artifact_digest = artifact_files._get_digest()
450+
formated_artifact_digest = "{}/{}".format(artifact_digest.hash, artifact_digest.size_bytes)
451+
line = p.fmt_subst(line, "artifact-cas-digest", formated_artifact_digest)
452+
else:
453+
# FIXME
454+
# We could instead collect all of the elements that are not
455+
# cached and issue a single warning message.
456+
line = p.fmt_subst(line, "artifact-cas-digest", "")
457+
element.warn("Cannot obtain CAS digest because artifact is not cached")
458+
440459
report += line + "\n"
441460

442461
return report.rstrip("\n")

tests/frontend/show.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,8 +438,9 @@ def setup_test():
438438
("%{deps}", "- import-dev.bst\n- import-links.bst\n- import-bin.bst"),
439439
("%{build-deps}", "- import-dev.bst\n- import-links.bst"),
440440
("%{runtime-deps}", "- import-links.bst\n- import-bin.bst"),
441+
("%{artifact-cas-digest}", "(no artifact CAS digest)"),
441442
],
442-
ids=["deps", "build-deps", "runtime-deps"],
443+
ids=["deps", "build-deps", "runtime-deps", "artifact-cas-digest"],
443444
)
444445
def test_format_deps(cli, datafiles, dep_kind, expected_deps):
445446
project = str(datafiles)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 shutil
20+
21+
import pytest
22+
23+
from buildstream._testing import cli # pylint: disable=unused-import
24+
from buildstream.exceptions import ErrorDomain
25+
26+
from tests.testutils import (
27+
create_artifact_share,
28+
assert_shared,
29+
assert_not_shared,
30+
)
31+
32+
33+
DATA_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "show_artifact_cas_digest_project")
34+
35+
# This tests that cache keys behave as expected when
36+
# dependencies have been specified as `strict` and
37+
# when building in strict mode.
38+
#
39+
# This test will:
40+
#
41+
# * Build the target once (and assert that it is cached)
42+
# * Modify some local files which are imported
43+
# by an import element which the target depends on
44+
# * Assert that the cached state of the target element
45+
# is as expected
46+
#
47+
# We run the test twice, once with an element which strict
48+
# depends on the changing import element, and one which
49+
# depends on it regularly.
50+
#
51+
@pytest.mark.datafiles(DATA_DIR)
52+
@pytest.mark.parametrize(
53+
"target, expected_digests",
54+
[
55+
("import-basic-files.bst", {
56+
"import-basic-files.bst": "7093d3c89029932ce1518bd2192e1d3cf60fd88e356b39195d10b87b598c78f0/168",
57+
}),
58+
("import-executable-files.bst", {
59+
"import-executable-files.bst": "133a9ae2eda30945a363272ac14bb2c8a941770b5a37c2847c99934f2972ce4f/170",
60+
}),
61+
("import-symlinks.bst", {
62+
"import-symlinks.bst": "95947ea55021e26cec4fd4f9de90d2b7f4f7d803ccc91656b3e1f2c9923ddf19/131",
63+
}),
64+
],
65+
)
66+
def test_show_artifact_cas_digest(cli, tmpdir, datafiles, target, expected_digests):
67+
project = str(datafiles)
68+
expected_no_digest = ""
69+
70+
# Configure a local cache
71+
local_cache = os.path.join(str(tmpdir), "cache")
72+
cli.configure({"cachedir": local_cache})
73+
74+
with create_artifact_share(os.path.join(str(tmpdir), "artifactshare")) as share:
75+
76+
cli.configure({"artifacts": {"servers": [{"url": share.repo}]}})
77+
78+
# Check the element and its dependencies have not been built locally and are not existing in the remote cache
79+
for component in sorted(expected_digests.keys()):
80+
assert cli.get_element_state(project, component) == "buildable"
81+
assert_not_shared(cli, share, project, component)
82+
83+
# Check the element and its dependencies have no artifact digest
84+
result = cli.run(project=project, silent=True, args=["show", "--format", "%{name},%{artifact-cas-digest}", target])
85+
result.assert_success()
86+
87+
digests = dict(line.split(",", 2) for line in result.output.splitlines())
88+
assert len(digests) == len(expected_digests)
89+
90+
for component, received in sorted(digests.items()):
91+
assert received == expected_no_digest
92+
93+
# Build the element locally
94+
result = cli.run(project=project, silent=True, args=["build", target])
95+
result.assert_success()
96+
97+
# Check the element and its dependencies have been built locally and are not existing in the remote cache
98+
for component in sorted(expected_digests.keys()):
99+
assert cli.get_element_state(project, component) == "cached"
100+
assert_not_shared(cli, share, project, component)
101+
102+
# Check the element and its dependencies have an artifact digest
103+
result = cli.run(project=project, silent=True, args=["show", "--format", "%{name},%{artifact-cas-digest}", target])
104+
result.assert_success()
105+
106+
digests = dict(line.split(",", 2) for line in result.output.splitlines())
107+
assert len(digests) == len(expected_digests)
108+
109+
for component, received in sorted(digests.items()):
110+
assert received == expected_digests[component]
111+
112+
# Push the built artifacts to the remote cache
113+
for component in sorted(expected_digests.keys()):
114+
result = cli.run(project=project, args=["artifact", "push", component, "--artifact-remote", share.repo])
115+
#result.assert_main_error(ErrorDomain.STREAM, None)
116+
117+
# Check the element and its dependencies have been built locally and are existing in the remote cache
118+
for component in sorted(expected_digests.keys()):
119+
assert cli.get_element_state(project, component) == "cached"
120+
assert_shared(cli, share, project, component)
121+
122+
# Delete the locally cached element but not its dependencies
123+
result = cli.run(project=project, silent=True, args=["artifact", "delete", target])
124+
result.assert_success()
125+
126+
# Check the element has been deleted locally
127+
for component, received in sorted(digests.items()):
128+
assert cli.get_element_state(project, target) == ("buildable" if component == target else "cached")
129+
assert_shared(cli, share, project, target)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: import
2+
sources:
3+
- kind: local
4+
path: files/basic-files
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: import
2+
sources:
3+
- kind: local
4+
path: files/executable-files
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: import
2+
sources:
3+
- kind: local
4+
path: files/symlinks
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
file contents

tests/frontend/show_artifact_cas_digest_project/files/basic-files/basicfolder/subdir-file

Whitespace-only changes.

0 commit comments

Comments
 (0)