summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Schubert <contact@benschubert.me>2019-10-09 17:01:57 +0100
committerBenjamin Schubert <contact@benschubert.me>2019-10-14 15:20:30 +0100
commitcc8c05bdfa64db469860ee27673ea2ad2203cd1f (patch)
tree6df5b1751a5d6dee3385eb7ef4ba59d438943d2b
parent13d9ab50e96d4a22f26ba9e4b67e7f2088b51edf (diff)
downloadbuildstream-bschubert/buildboxrun-sandbox.tar.gz
-rw-r--r--src/buildstream/_platform/linux.py18
-rw-r--r--src/buildstream/sandbox/_sandboxbuildboxrun.py159
2 files changed, 177 insertions, 0 deletions
diff --git a/src/buildstream/_platform/linux.py b/src/buildstream/_platform/linux.py
index b400bfaac..be8b771c5 100644
--- a/src/buildstream/_platform/linux.py
+++ b/src/buildstream/_platform/linux.py
@@ -33,6 +33,7 @@ class Linux(Platform):
sandbox_setups = {
'bwrap': self._setup_bwrap_sandbox,
'buildbox': self._setup_buildbox_sandbox,
+ 'buildbox-run': self._setup_buildboxrun_sandbox,
'chroot': self._setup_chroot_sandbox,
'dummy': self._setup_dummy_sandbox,
}
@@ -143,3 +144,20 @@ class Linux(Platform):
self.check_sandbox_config = self._check_sandbox_config_buildbox
self.create_sandbox = self._create_buildbox_sandbox
return True
+
+ # Buildbox run sandbox methods
+ def _check_sandbox_config_buildboxrun(self, config):
+ from ..sandbox._sandboxbuildboxrun import SandboxBuildBoxRun
+ return SandboxBuildBoxRun.check_sandbox_config(self, config)
+
+ @staticmethod
+ def _create_buildboxrun_sandbox(*args, **kwargs):
+ from ..sandbox._sandboxbuildboxrun import SandboxBuildBoxRun
+ return SandboxBuildBoxRun(*args, **kwargs)
+
+ def _setup_buildboxrun_sandbox(self):
+ from ..sandbox._sandboxbuildboxrun import SandboxBuildBoxRun
+ self._check_sandbox(SandboxBuildBoxRun)
+ self.check_sandbox_config = self._check_sandbox_config_buildboxrun
+ self.create_sandbox = self._create_buildboxrun_sandbox
+ return True
diff --git a/src/buildstream/sandbox/_sandboxbuildboxrun.py b/src/buildstream/sandbox/_sandboxbuildboxrun.py
new file mode 100644
index 000000000..400d519a3
--- /dev/null
+++ b/src/buildstream/sandbox/_sandboxbuildboxrun.py
@@ -0,0 +1,159 @@
+import os
+import subprocess
+
+from .. import utils
+from .._exceptions import SandboxError
+from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
+from .._protos.google.rpc import code_pb2
+from . import Sandbox, SandboxCommandError
+from .._cas import CASRemote
+from ..storage._casbaseddirectory import CasBasedDirectory
+
+
+# SandboxBuildBoxRun()
+#
+# BuildBox-based sandbox implementation.
+#
+class SandboxBuildBoxRun(Sandbox):
+ build_root = "/persistent-cache/userchroot/buildstream"
+
+ @classmethod
+ def check_available(cls):
+ try:
+ utils.get_host_tool("buildbox-run")
+ except utils.ProgramNotFoundError as Error:
+ cls._dummy_reasons += ["buildbox-run not found"]
+ raise SandboxError(" and ".join(cls._dummy_reasons),
+ reason="unavailable-local-sandbox") from Error
+
+ @classmethod
+ def check_sandbox_config(cls, platform, config):
+ # Report error for elements requiring non-0 UID/GID
+ if config.build_uid != 0 or config.build_gid != 0:
+ return False
+
+ # Check host os and architecture match
+ if config.build_os != platform.get_host_os():
+ raise SandboxError("Configured and host OS don't match.")
+ if config.build_arch != platform.get_host_arch():
+ raise SandboxError("Configured and host architecture don't match.")
+
+ # FIXME: check that buildbox-casd is configured correctly
+ # FIXME: check that userchroot is configured correctly
+
+ return True
+
+ def _run(self, command, flags, *, cwd, env):
+ stdout, stderr = self._get_output()
+
+ if not self._has_command(command[0], env):
+ raise SandboxCommandError("Staged artifacts do not provide command "
+ "'{}'".format(command[0]),
+ reason="missing-command")
+
+ cas_cache = self.get_virtual_directory().cas_cache
+
+ with utils._tempnamedfile() as action_file, utils._tempnamedfile() as result_file, utils._tempdir(dir=self.build_root) as bldroot:
+ command = self._create_command(command, cwd, env).SerializeToString()
+ cas_cache.add_object(buffer=command)
+ # FIXME: we need to have
+ self._create_action(action_file, command)
+
+ buildbox_command = [
+ utils.get_host_tool("buildbox-run"),
+ "--userchroot-bin={}".format(utils.get_host_tool("userchroot")),
+ "--use-localcas",
+ "--remote=unix://{}".format(cas_cache._casd_socket_path),
+ "--action={}".format(action_file.name),
+ "--action-result={}".format(result_file.name),
+ "--log-level=trace",
+ "--workspace-path={}".format(bldroot),
+ ]
+
+ # FIXME: handle pausing, sigint, etc correctly
+ subprocess.check_call(buildbox_command, stdout=stdout, stderr=stderr)
+
+ action_result = remote_execution_pb2.ActionResult().FromString(result_file.read())
+
+ # Get output of build
+ self.process_job_output(action_result.output_directories, action_result.output_files,
+ failure=action_result.exit_code != 0)
+
+ if stdout:
+ if action_result.stdout_raw:
+ stdout.write(str(action_result.stdout_raw, 'utf-8', errors='ignore'))
+ if stderr:
+ if action_result.stderr_raw:
+ stderr.write(str(action_result.stderr_raw, 'utf-8', errors='ignore'))
+
+ if action_result.exit_code != 0:
+ # A normal error during the build: the remote execution system
+ # has worked correctly but the command failed.
+ return action_result.exit_code
+
+ return 0
+
+ def _create_command(self, command, working_directory, environment):
+ # Creates a command proto
+ environment_variables = [remote_execution_pb2.Command.
+ EnvironmentVariable(name=k, value=v)
+ for (k, v) in environment.items()]
+
+ # Request the whole directory tree as output
+ output_directory = os.path.relpath(os.path.sep, start=working_directory)
+
+ return remote_execution_pb2.Command(arguments=command,
+ working_directory=working_directory,
+ environment_variables=environment_variables,
+ output_files=[],
+ output_directories=[output_directory],
+ platform=None)
+
+ def _create_action(self, action_file, command):
+ command_digest = utils._message_digest(command)
+ input_root_digest = self.get_virtual_directory()._get_digest()
+
+ action = remote_execution_pb2.Action(command_digest=command_digest, input_root_digest=input_root_digest)
+
+ action_file.write(action.SerializeToString())
+ action_file.seek(0)
+
+ def _use_cas_based_directory(self):
+ # Always use CasBasedDirectory for BuildBoxRun
+ return True
+
+ def process_job_output(self, output_directories, output_files, *, failure):
+ # Reads the remote execution server response to an execution request.
+ #
+ # output_directories is an array of OutputDirectory objects.
+ # output_files is an array of OutputFile objects.
+ #
+ # We only specify one output_directory, so it's an error
+ # for there to be any output files or more than one directory at the moment.
+ #
+ if output_files:
+ raise SandboxError("Output files were returned when we didn't request any.")
+ if not output_directories:
+ error_text = "No output directory was returned from the build server."
+ raise SandboxError(error_text)
+ if len(output_directories) > 1:
+ error_text = "More than one output directory was returned from the build server: {}."
+ raise SandboxError(error_text.format(output_directories))
+
+ dir_digest = output_directories[0].tree_digest
+ if dir_digest is None or not dir_digest.hash:
+ raise SandboxError("Output directory structure had no digest attached.")
+
+ context = self._get_context()
+ cascache = context.get_cascache()
+ artifactcache = context.artifactcache
+
+ if dir_digest is None or not dir_digest.hash or not dir_digest.size_bytes:
+ raise SandboxError("Output directory structure pulling from remote failed.")
+
+ # At the moment, we will get the whole directory back in the first directory argument and we need
+ # to replace the sandbox's virtual directory with that. Creating a new virtual directory object
+ # from another hash will be interesting, though...
+
+ new_dir = CasBasedDirectory(artifactcache.cas, digest=dir_digest)
+ self._set_virtual_directory(new_dir)