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

Reply via email to