Skip to content

Commit fd38499

Browse files
authored
Merge pull request #2036 from apache/abderrahim/digest-environment
Add the digest-environment dependency configuration
2 parents 578534c + a2f076f commit fd38499

File tree

16 files changed

+460
-39
lines changed

16 files changed

+460
-39
lines changed

src/buildstream/_artifact.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ def get_extract_key(self):
200200
# variables (Variables): The element's Variables
201201
# environment (dict): dict of the element's environment variables
202202
# sandboxconfig (SandboxConfig): The element's SandboxConfig
203+
# buildsandbox (Sandbox): The element's configured build sandbox
203204
#
204205
def cache(
205206
self,
@@ -213,6 +214,7 @@ def cache(
213214
variables,
214215
environment,
215216
sandboxconfig,
217+
buildsandbox,
216218
):
217219

218220
context = self._context
@@ -317,6 +319,19 @@ def cache(
317319
rootvdir._import_files_internal(buildrootvdir, properties=properties, collect_result=False)
318320
artifact.buildroot.CopyFrom(rootvdir._get_digest())
319321

322+
if buildsandbox is not None:
323+
sandbox_env = buildsandbox._get_configured_environment()
324+
if sandbox_env:
325+
for key, value in sorted(sandbox_env.items()):
326+
artifact.buildsandbox.environment.add(name=key, value=value)
327+
328+
artifact.buildsandbox.working_directory = buildsandbox._get_work_directory()
329+
330+
for subsandbox in buildsandbox._get_subsandboxes():
331+
vdir = subsandbox.get_virtual_directory()
332+
digest = artifact.buildsandbox.subsandbox_digests.add()
333+
digest.CopyFrom(vdir._get_digest())
334+
320335
os.makedirs(os.path.dirname(os.path.join(self._artifactdir, element.get_artifact_name())), exist_ok=True)
321336
keys = utils._deduplicate([self._cache_key, self._weak_cache_key])
322337
for key in keys:
@@ -681,6 +696,21 @@ def pull(self, *, pull_buildtrees):
681696

682697
return True
683698

699+
def configure_sandbox(self, sandbox):
700+
artifact = self._get_proto()
701+
702+
if artifact.buildsandbox and artifact.buildsandbox.environment:
703+
env = {}
704+
for env_var in artifact.buildsandbox.environment:
705+
env[env_var.name] = env_var.value
706+
else:
707+
env = self.load_environment()
708+
709+
sandbox.set_environment(env)
710+
711+
if artifact.buildsandbox and artifact.buildsandbox.working_directory:
712+
sandbox.set_work_directory(artifact.buildsandbox.working_directory)
713+
684714
# load_proto()
685715
#
686716
# Returns:

src/buildstream/_artifactcache.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ def _push_artifact_blobs(self, artifact, artifact_digest, remote):
303303
except FileNotFoundError:
304304
pass
305305

306+
if artifact_proto.buildsandbox:
307+
for subsandbox_digest in artifact_proto.buildsandbox.subsandbox_digests:
308+
self.cas._send_directory(remote, subsandbox_digest)
309+
306310
digests = [artifact_digest, artifact_proto.low_diversity_meta, artifact_proto.high_diversity_meta]
307311

308312
if str(artifact_proto.public_data):
@@ -361,6 +365,9 @@ def _push_artifact_proto(self, element, artifact, artifact_digest, remote):
361365
referenced_directories.append(artifact_proto.sources)
362366
if artifact_proto.buildroot:
363367
referenced_directories.append(artifact_proto.buildroot)
368+
if artifact_proto.buildsandbox:
369+
for subsandbox_digest in artifact_proto.buildsandbox.subsandbox_digests:
370+
referenced_directories.append(subsandbox_digest)
364371

365372
referenced_blobs = [artifact_proto.low_diversity_meta, artifact_proto.high_diversity_meta] + [
366373
log_file.digest for log_file in artifact_proto.logs
@@ -419,6 +426,9 @@ def _pull_artifact_storage(self, element, key, artifact_digest, remote, pull_bui
419426
self.cas.fetch_directory(remote, artifact.buildtree)
420427
if str(artifact.buildroot):
421428
self.cas.fetch_directory(remote, artifact.buildroot)
429+
if artifact.buildsandbox:
430+
for subsandbox_digest in artifact.buildsandbox.subsandbox_digests:
431+
self.cas.fetch_directory(remote, subsandbox_digest)
422432

423433
digests = [artifact.low_diversity_meta, artifact.high_diversity_meta]
424434
if str(artifact.public_data):

src/buildstream/_elementproxy.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,10 @@ def stage_artifact(
104104
owner = cast("Element", self._owner)
105105
element = cast("Element", self._plugin)
106106

107-
assert owner._overlap_collector is not None, "Attempted to stage artifacts outside of Element.stage()"
107+
overlap_collector = owner._overlap_collectors.get(sandbox)
108+
assert overlap_collector is not None, "Attempted to stage artifacts outside of Element.stage()"
108109

109-
with owner._overlap_collector.session(action, path):
110+
with overlap_collector.session(action, path):
110111
result = element._stage_artifact(
111112
sandbox, path=path, action=action, include=include, exclude=exclude, orphans=orphans, owner=owner
112113
)

src/buildstream/_protos/buildstream/v2/artifact.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,11 @@ message Artifact {
8585

8686
// digest of a directory
8787
build.bazel.remote.execution.v2.Digest buildroot = 17; // optional
88+
89+
message SandboxState {
90+
repeated build.bazel.remote.execution.v2.Command.EnvironmentVariable environment = 1;
91+
string working_directory = 2;
92+
repeated build.bazel.remote.execution.v2.Digest subsandbox_digests = 3;
93+
};
94+
SandboxState buildsandbox = 18; // optional
8895
}

src/buildstream/_protos/buildstream/v2/artifact_pb2.py

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/buildstream/_protos/buildstream/v2/artifact_pb2.pyi

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Map
88
DESCRIPTOR: _descriptor.FileDescriptor
99

1010
class Artifact(_message.Message):
11-
__slots__ = ("version", "build_success", "build_error", "build_error_details", "strong_key", "weak_key", "was_workspaced", "files", "build_deps", "public_data", "logs", "buildtree", "sources", "low_diversity_meta", "high_diversity_meta", "strict_key", "buildroot")
11+
__slots__ = ("version", "build_success", "build_error", "build_error_details", "strong_key", "weak_key", "was_workspaced", "files", "build_deps", "public_data", "logs", "buildtree", "sources", "low_diversity_meta", "high_diversity_meta", "strict_key", "buildroot", "buildsandbox")
1212
class Dependency(_message.Message):
1313
__slots__ = ("project_name", "element_name", "cache_key", "was_workspaced")
1414
PROJECT_NAME_FIELD_NUMBER: _ClassVar[int]
@@ -27,6 +27,15 @@ class Artifact(_message.Message):
2727
name: str
2828
digest: _remote_execution_pb2.Digest
2929
def __init__(self, name: _Optional[str] = ..., digest: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ...) -> None: ...
30+
class SandboxState(_message.Message):
31+
__slots__ = ("environment", "working_directory", "subsandbox_digests")
32+
ENVIRONMENT_FIELD_NUMBER: _ClassVar[int]
33+
WORKING_DIRECTORY_FIELD_NUMBER: _ClassVar[int]
34+
SUBSANDBOX_DIGESTS_FIELD_NUMBER: _ClassVar[int]
35+
environment: _containers.RepeatedCompositeFieldContainer[_remote_execution_pb2.Command.EnvironmentVariable]
36+
working_directory: str
37+
subsandbox_digests: _containers.RepeatedCompositeFieldContainer[_remote_execution_pb2.Digest]
38+
def __init__(self, environment: _Optional[_Iterable[_Union[_remote_execution_pb2.Command.EnvironmentVariable, _Mapping]]] = ..., working_directory: _Optional[str] = ..., subsandbox_digests: _Optional[_Iterable[_Union[_remote_execution_pb2.Digest, _Mapping]]] = ...) -> None: ...
3039
VERSION_FIELD_NUMBER: _ClassVar[int]
3140
BUILD_SUCCESS_FIELD_NUMBER: _ClassVar[int]
3241
BUILD_ERROR_FIELD_NUMBER: _ClassVar[int]
@@ -44,6 +53,7 @@ class Artifact(_message.Message):
4453
HIGH_DIVERSITY_META_FIELD_NUMBER: _ClassVar[int]
4554
STRICT_KEY_FIELD_NUMBER: _ClassVar[int]
4655
BUILDROOT_FIELD_NUMBER: _ClassVar[int]
56+
BUILDSANDBOX_FIELD_NUMBER: _ClassVar[int]
4757
version: int
4858
build_success: bool
4959
build_error: str
@@ -61,4 +71,5 @@ class Artifact(_message.Message):
6171
high_diversity_meta: _remote_execution_pb2.Digest
6272
strict_key: str
6373
buildroot: _remote_execution_pb2.Digest
64-
def __init__(self, version: _Optional[int] = ..., build_success: bool = ..., build_error: _Optional[str] = ..., build_error_details: _Optional[str] = ..., strong_key: _Optional[str] = ..., weak_key: _Optional[str] = ..., was_workspaced: bool = ..., files: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., build_deps: _Optional[_Iterable[_Union[Artifact.Dependency, _Mapping]]] = ..., public_data: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., logs: _Optional[_Iterable[_Union[Artifact.LogFile, _Mapping]]] = ..., buildtree: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., sources: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., low_diversity_meta: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., high_diversity_meta: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., strict_key: _Optional[str] = ..., buildroot: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ...) -> None: ...
74+
buildsandbox: Artifact.SandboxState
75+
def __init__(self, version: _Optional[int] = ..., build_success: bool = ..., build_error: _Optional[str] = ..., build_error_details: _Optional[str] = ..., strong_key: _Optional[str] = ..., weak_key: _Optional[str] = ..., was_workspaced: bool = ..., files: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., build_deps: _Optional[_Iterable[_Union[Artifact.Dependency, _Mapping]]] = ..., public_data: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., logs: _Optional[_Iterable[_Union[Artifact.LogFile, _Mapping]]] = ..., buildtree: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., sources: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., low_diversity_meta: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., high_diversity_meta: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., strict_key: _Optional[str] = ..., buildroot: _Optional[_Union[_remote_execution_pb2.Digest, _Mapping]] = ..., buildsandbox: _Optional[_Union[Artifact.SandboxState, _Mapping]] = ...) -> None: ...

src/buildstream/buildelement.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,31 @@
7676
directories before subdirectories.
7777
7878
79+
`digest-environment` for dependencies
80+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
81+
The BuildElement supports the ``digest-environment`` :term:`dependency configuration <Dependency configuration>`,
82+
which sets the specified environment variable in the build sandbox to the CAS digest
83+
corresponding to a directory that contains all dependencies that are configured
84+
with the same ``digest-environment``.
85+
86+
This is useful for REAPI clients in the sandbox such as `recc <https://buildgrid.gitlab.io/recc>`_,
87+
see ``remote-apis-socket`` in the :ref:`sandbox configuration <format_sandbox>`.
88+
89+
**Example:**
90+
91+
Here is an example of how to set the environment variable `GCC_DIGEST` to the
92+
CAS digest of a directory that contains ``gcc.bst`` and its runtime dependencies.
93+
The ``libpony.bst`` dependency will not be included in that CAS directory.
94+
95+
.. code:: yaml
96+
97+
build-depends:
98+
- baseproject.bst:gcc.bst
99+
config:
100+
digest-environment: GCC_DIGEST
101+
- libpony.bst
102+
103+
79104
Location for running commands
80105
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
81106
The ``command-subdir`` variable sets where commands will be executed,
@@ -219,6 +244,7 @@ def configure(self, node):
219244
def configure_dependencies(self, dependencies):
220245

221246
self.__layout = {} # pylint: disable=attribute-defined-outside-init
247+
self.__digest_environment = {} # pylint: disable=attribute-defined-outside-init
222248

223249
# FIXME: Currently this forcefully validates configurations
224250
# for all BuildElement subclasses so they are unable to
@@ -227,9 +253,18 @@ def configure_dependencies(self, dependencies):
227253
for dep in dependencies:
228254
# Determine the location to stage each element, default is "/"
229255
location = "/"
256+
230257
if dep.config:
231-
dep.config.validate_keys(["location"])
232-
location = dep.config.get_str("location")
258+
dep.config.validate_keys(["digest-environment", "location"])
259+
260+
location = dep.config.get_str("location", "/")
261+
262+
digest_var_name = dep.config.get_str("digest-environment", None)
263+
264+
if digest_var_name is not None:
265+
element_list = self.__digest_environment.setdefault(digest_var_name, [])
266+
element_list.append((dep.element, dep.path))
267+
233268
try:
234269
element_list = self.__layout[location]
235270
except KeyError:
@@ -268,6 +303,16 @@ def get_unique_key(self):
268303
}
269304
dictionary["layout"] = layout_key
270305

306+
# Specify the layout in the key, if buildstream is to generate an environment
307+
# variable with the digest
308+
#
309+
if self.__digest_environment:
310+
sorted_envs = sorted(self.__digest_environment)
311+
digest_key = {
312+
env: [dependency_path for _, dependency_path in self.__digest_environment[env]] for env in sorted_envs
313+
}
314+
dictionary["digest-enviornment"] = digest_key
315+
271316
return dictionary
272317

273318
def configure_sandbox(self, sandbox):
@@ -286,7 +331,20 @@ def configure_sandbox(self, sandbox):
286331
sandbox.set_work_directory(command_dir)
287332

288333
# Setup environment
289-
sandbox.set_environment(self.get_environment())
334+
env = self.get_environment()
335+
336+
# Add "CAS digest" environment variables
337+
sorted_envs = sorted(self.__digest_environment)
338+
for digest_variable in sorted_envs:
339+
element_list = [element for element, _ in self.__digest_environment[digest_variable]]
340+
with self.timed_activity(
341+
f"Staging dependencies for '{digest_variable}' in subsandbox", silent_nested=True
342+
), self.subsandbox(sandbox) as subsandbox:
343+
self.stage_dependency_artifacts(subsandbox, element_list)
344+
digest = subsandbox.get_virtual_directory()._get_digest()
345+
env[digest_variable] = "{}/{}".format(digest.hash, digest.size_bytes)
346+
347+
sandbox.set_environment(env)
290348

291349
def stage(self, sandbox):
292350

0 commit comments

Comments
 (0)