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 408d0ac53d5e7c7cd6e0be8293798dba10ecc362
Author: Jürg Billeter <[email protected]>
AuthorDate: Sat Jun 28 14:23:43 2025 +0200

    Support remote action cache for nested execution
---
 src/buildstream/_frontend/widget.py            |  3 ++-
 src/buildstream/_remotespec.py                 | 35 ++++++++++++++++++++------
 src/buildstream/element.py                     |  2 +-
 src/buildstream/sandbox/_reremote.py           |  6 +++++
 src/buildstream/sandbox/_sandboxbuildboxrun.py | 31 +++++++++++++++++++++--
 src/buildstream/sandbox/_sandboxremote.py      |  2 +-
 6 files changed, 67 insertions(+), 12 deletions(-)

diff --git a/src/buildstream/_frontend/widget.py 
b/src/buildstream/_frontend/widget.py
index 09b824ff3..e061f0c2f 100644
--- a/src/buildstream/_frontend/widget.py
+++ b/src/buildstream/_frontend/widget.py
@@ -534,7 +534,8 @@ class LogLine(Widget):
             text += "\n"
             text += self.content_profile.fmt("Remote Execution 
Configuration\n", bold=True)
             values = {}
-            values["Execution Service"] = format_spec(specs.exec_spec)
+            if specs.exec_spec:
+                values["Execution Service"] = format_spec(specs.exec_spec)
             re_storage_spec = specs.storage_spec or context.remote_cache_spec
             values["Storage Service"] = format_spec(re_storage_spec)
             if specs.action_spec:
diff --git a/src/buildstream/_remotespec.py b/src/buildstream/_remotespec.py
index 1a22ee043..7bb43ae16 100644
--- a/src/buildstream/_remotespec.py
+++ b/src/buildstream/_remotespec.py
@@ -239,6 +239,7 @@ class RemoteSpec:
     #    spec_node: The configuration node describing the spec.
     #    basedir: The base directory from which to find certificates.
     #    remote_execution: Whether this spec is used for remote execution 
(some keys are invalid)
+    #    action_cache: Whether this spec is used for remote execution action 
cache
     #
     # Returns:
     #    The described RemoteSpec instance.
@@ -248,7 +249,12 @@ class RemoteSpec:
     #
     @classmethod
     def new_from_node(
-        cls, spec_node: MappingNode, basedir: Optional[str] = None, *, 
remote_execution: bool = False
+        cls,
+        spec_node: MappingNode,
+        basedir: Optional[str] = None,
+        *,
+        remote_execution: bool = False,
+        action_cache: bool = False,
     ) -> "RemoteSpec":
         server_cert: Optional[str] = None
         client_key: Optional[str] = None
@@ -261,8 +267,10 @@ class RemoteSpec:
         valid_keys: List[str] = ["url", "instance-name", "auth", 
"connection-config"]
         if not remote_execution:
             remote_type = cast(str, spec_node.get_enum("type", RemoteType, 
default=RemoteType.ALL))
+            valid_keys += ["type"]
+        if not remote_execution or action_cache:
             push = spec_node.get_bool("push", default=False)
-            valid_keys += ["push", "type"]
+            valid_keys += ["push"]
 
         spec_node.validate_keys(valid_keys)
 
@@ -500,9 +508,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 +534,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 +546,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:
@@ -548,7 +569,7 @@ class RemoteExecutionSpec:
 
         action_spec: Optional[RemoteSpec]
         if action_node:
-            action_spec = RemoteSpec.new_from_node(action_node, basedir, 
remote_execution=True)
+            action_spec = RemoteSpec.new_from_node(action_node, basedir, 
remote_execution=True, action_cache=True)
         else:
             action_spec = None
 
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 4c7773fec..b9bfd8860 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.content_addressable_storage)
         else:
             self.spec.to_localcas_remote(request.content_addressable_storage)
+        request.content_addressable_storage.read_only = True
         if self.remote_execution_specs.exec_spec:
             
self.remote_execution_specs.exec_spec.to_localcas_remote(request.execution)
+            request.content_addressable_storage.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.content_addressable_storage.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..632a166ca 100644
--- a/src/buildstream/sandbox/_sandboxbuildboxrun.py
+++ b/src/buildstream/sandbox/_sandboxbuildboxrun.py
@@ -17,6 +17,7 @@ import subprocess
 import sys
 from contextlib import ExitStack
 
+import grpc
 import psutil
 
 from .. import utils, _signals
@@ -24,6 +25,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 +34,26 @@ 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()
+
+        re_specs = context.remote_execution_specs
+        if re_specs and re_specs.action_spec and not re_specs.exec_spec:
+            self.re_remote = RERemote(context.remote_cache_spec, re_specs, 
cascache)
+            try:
+                self.re_remote.init()
+            except grpc.RpcError as e:
+                urls = set()
+                if re_specs.storage_spec:
+                    urls.add(re_specs.storage_spec.url)
+                urls.add(re_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 +113,10 @@ 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'."
+            )
 
         with utils._tempnamedfile() as action_file, utils._tempnamedfile() as 
result_file:
             action_file.write(action.SerializeToString())
@@ -105,6 +129,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")
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

Reply via email to