This is an automated email from the ASF dual-hosted git repository. juergbi pushed a commit to branch jbilleter/action-cache in repository https://gitbox.apache.org/repos/asf/buildstream.git
commit 40c328c5bb2b4ac436c22f75211c81bb761a011b Author: Jürg Billeter <[email protected]> AuthorDate: Sat Jun 28 14:23:43 2025 +0200 wip: action cache support for local execution --- src/buildstream/_remotespec.py | 21 +++++++++--- src/buildstream/element.py | 2 +- src/buildstream/sandbox/_reremote.py | 6 ++++ src/buildstream/sandbox/_sandboxbuildboxrun.py | 47 ++++++++++++++++++++++++-- src/buildstream/sandbox/_sandboxremote.py | 2 +- 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/buildstream/_remotespec.py b/src/buildstream/_remotespec.py index 1a22ee043..ca79b979e 100644 --- a/src/buildstream/_remotespec.py +++ b/src/buildstream/_remotespec.py @@ -500,9 +500,9 @@ class RemoteSpec: # class RemoteExecutionSpec: def __init__( - self, exec_spec: RemoteSpec, storage_spec: Optional[RemoteSpec], action_spec: Optional[RemoteSpec] + self, exec_spec: Optional[RemoteSpec], storage_spec: Optional[RemoteSpec], action_spec: Optional[RemoteSpec] ) -> None: - self.exec_spec: RemoteSpec = exec_spec + self.exec_spec: Optional[RemoteSpec] = exec_spec self.storage_spec: Optional[RemoteSpec] = storage_spec self.action_spec: Optional[RemoteSpec] = action_spec @@ -526,7 +526,7 @@ class RemoteExecutionSpec: ) -> "RemoteExecutionSpec": node.validate_keys(["execution-service", "storage-service", "action-cache-service"]) - exec_node = node.get_mapping("execution-service") + exec_node = node.get_mapping("execution-service", default=None) storage_node = node.get_mapping("storage-service", default=None) if not storage_node and not remote_cache: provenance = node.get_provenance() @@ -538,7 +538,20 @@ class RemoteExecutionSpec: ) action_node = node.get_mapping("action-cache-service", default=None) - exec_spec = RemoteSpec.new_from_node(exec_node, basedir, remote_execution=True) + if not exec_node and not action_node: + provenance = node.get_provenance() + raise LoadError( + "{}: At least one of `execution-service` or `action-service` need to be specified in the 'remote-execution' section".format( + provenance + ), + LoadErrorReason.INVALID_DATA, + ) + + exec_spec: Optional[RemoteSpec] + if exec_node: + exec_spec = RemoteSpec.new_from_node(exec_node, basedir, remote_execution=True) + else: + exec_spec = None storage_spec: Optional[RemoteSpec] if storage_node: diff --git a/src/buildstream/element.py b/src/buildstream/element.py index ca8080314..f02f45f1a 100644 --- a/src/buildstream/element.py +++ b/src/buildstream/element.py @@ -2822,7 +2822,7 @@ class Element(Plugin): else: output_node_properties = None - if allow_remote and context.remote_execution_specs: + if allow_remote and context.remote_execution_specs and context.remote_execution_specs.exec_spec: with SandboxRemote( context, project, diff --git a/src/buildstream/sandbox/_reremote.py b/src/buildstream/sandbox/_reremote.py index d977e6696..0bdf50984 100644 --- a/src/buildstream/sandbox/_reremote.py +++ b/src/buildstream/sandbox/_reremote.py @@ -39,10 +39,16 @@ class RERemote(CASRemote): self.remote_execution_specs.storage_spec.to_localcas_remote(request.cas) else: self.spec.to_localcas_remote(request.cas) + request.cas.read_only = True if self.remote_execution_specs.exec_spec: self.remote_execution_specs.exec_spec.to_localcas_remote(request.execution) + request.cas.read_only = False if self.remote_execution_specs.action_spec: self.remote_execution_specs.action_spec.to_localcas_remote(request.action_cache) + if self.remote_execution_specs.action_spec.push: + request.cas.read_only = False + else: + request.action_cache.read_only = True response = local_cas.GetInstanceNameForRemotes(request) self.local_cas_instance_name = response.instance_name diff --git a/src/buildstream/sandbox/_sandboxbuildboxrun.py b/src/buildstream/sandbox/_sandboxbuildboxrun.py index d79f54b2f..6c86584ab 100644 --- a/src/buildstream/sandbox/_sandboxbuildboxrun.py +++ b/src/buildstream/sandbox/_sandboxbuildboxrun.py @@ -24,6 +24,7 @@ from . import _SandboxFlags from .._exceptions import SandboxError, SandboxUnavailableError from .._platform import Platform from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2 +from ._reremote import RERemote from ._sandboxreapi import SandboxREAPI @@ -32,6 +33,28 @@ from ._sandboxreapi import SandboxREAPI # BuildBox-based sandbox implementation. # class SandboxBuildBoxRun(SandboxREAPI): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + context = self._get_context() + cascache = context.get_cascache() + + specs = context.remote_execution_specs + self.action_spec = specs.action_spec + + if specs and specs.action_spec and not specs.exec_spec: + self.re_remote = RERemote(context.remote_cache_spec, specs, cascache) + try: + self.re_remote.init() + except grpc.RpcError as e: + urls = set() + if specs.storage_spec: + urls.add(specs.storage_spec.url) + urls.add(specs.action_spec.url) + raise SandboxError("Failed to contact remote cache endpoint at {}: {}".format(sorted(urls), e)) from e + else: + self.re_remote = None + @classmethod def __buildbox_run(cls): return utils._get_host_tool_internal("buildbox-run", search_subprojects_dir="buildbox") @@ -91,8 +114,17 @@ class SandboxBuildBoxRun(SandboxREAPI): casd = cascache.get_casd() config = self._get_config() - if config.remote_apis_socket_path and context.remote_cache_spec: - raise SandboxError("'remote-apis-socket' is not currently supported with 'storage-service'.") + if config.remote_apis_socket_path and context.remote_cache_spec and not self.re_remote: + raise SandboxError( + "'remote-apis-socket' is not supported with 'storage-service' without 'action-cache-service'." + ) + + if self.re_remote: + # TODO check action cache + # If there is a cache hit, fetch outputs, stdout and stderr + # Move some of the logic from SandboxRemote to SandboxREAPI, so it + # can be used by both Sandbox implementations + pass with utils._tempnamedfile() as action_file, utils._tempnamedfile() as result_file: action_file.write(action.SerializeToString()) @@ -105,6 +137,9 @@ class SandboxBuildBoxRun(SandboxREAPI): "--action-result={}".format(result_file.name), ] + if self.re_remote: + buildbox_command.append("--instance={}".format(self.re_remote.local_cas_instance_name)) + # Do not redirect stdout/stderr if "no-logs-capture" in self._capabilities: buildbox_command.append("--no-logs-capture") @@ -145,7 +180,13 @@ class SandboxBuildBoxRun(SandboxREAPI): interactive=(flags & _SandboxFlags.INTERACTIVE), ) - return remote_execution_pb2.ActionResult().FromString(result_file.read()) + action_result = remote_execution_pb2.ActionResult().FromString(result_file.read()) + + if self.re_remote and self.action_spec.push: + # TODO update action cache (if `push` enabled) + pass + + return action_result def _run_buildbox(self, argv, stdin, stdout, stderr, *, interactive): def kill_proc(): diff --git a/src/buildstream/sandbox/_sandboxremote.py b/src/buildstream/sandbox/_sandboxremote.py index dbd967a95..4adf1a77d 100644 --- a/src/buildstream/sandbox/_sandboxremote.py +++ b/src/buildstream/sandbox/_sandboxremote.py @@ -40,7 +40,7 @@ class SandboxRemote(SandboxREAPI): cascache = context.get_cascache() specs = context.remote_execution_specs - if specs is None: + if specs is None or specs.exec_spec is None: return self.storage_spec = specs.storage_spec
