Commit 299df233 authored by Tristan Maat's avatar Tristan Maat Committed by Tristan Maat

Add element.prepare method

This is one of the tasks of #209
parent 596264d1
......@@ -25,10 +25,11 @@ from . import _yaml
from ._exceptions import LoadError, LoadErrorReason
BST_WORKSPACE_FORMAT_VERSION = 2
BST_WORKSPACE_FORMAT_VERSION = 3
# Hold on to a list of members which get serialized
_WORKSPACE_MEMBERS = [
'prepared',
'path',
'last_successful',
'running_files'
......@@ -53,7 +54,8 @@ _WORKSPACE_MEMBERS = [
# made obsolete with failed build artifacts.
#
class Workspace():
def __init__(self, project, *, path=None, last_successful=None, running_files=None):
def __init__(self, project, *, last_successful=None, path=None, prepared=False, running_files=None):
self.prepared = prepared
self.last_successful = last_successful
self.path = path
self.running_files = running_files if running_files is not None else {}
......@@ -376,7 +378,7 @@ class Workspaces():
for element, config in _yaml.node_items(workspaces)
}
elif version == 1 or version == BST_WORKSPACE_FORMAT_VERSION:
elif version >= 1 and version <= BST_WORKSPACE_FORMAT_VERSION:
workspaces = _yaml.node_get(workspaces, dict, "workspaces", default_value={})
res = {element: self._load_workspace(self._project, node)
for element, node in _yaml.node_items(workspaces)}
......@@ -402,6 +404,7 @@ class Workspaces():
#
def _load_workspace(self, project, node):
dictionary = {
'prepared': _yaml.node_get(node, bool, 'prepared', default_value=False),
'path': _yaml.node_get(node, str, 'path'),
'last_successful': _yaml.node_get(node, str, 'last_successful', default_value=None),
'running_files': _yaml.node_get(node, dict, 'running_files', default_value=None),
......
......@@ -172,20 +172,12 @@ class BuildElement(Element):
# Run commands
for command_name in _command_steps:
commands = self.commands[command_name]
if not commands:
if not commands or command_name == 'configure-commands':
continue
with self.timed_activity("Running {}".format(command_name)):
for cmd in commands:
self.status("Running {}".format(command_name), detail=cmd)
# Note the -e switch to 'sh' means to exit with an error
# if any untested command fails.
#
exitcode = sandbox.run(['sh', '-c', '-e', cmd + '\n'],
SandboxFlags.ROOT_READ_ONLY)
if exitcode != 0:
raise ElementError("Command '{}' failed with exitcode {}".format(cmd, exitcode))
self._run_command(sandbox, cmd, command_name)
# %{install-root}/%{build-root} should normally not be written
# to - if an element later attempts to stage to a location
......@@ -204,6 +196,12 @@ class BuildElement(Element):
# always the /buildstream-install directory
return self.get_variable('install-root')
def prepare(self, sandbox):
commands = self.commands['configure-commands']
if commands:
for cmd in commands:
self._run_command(sandbox, cmd, 'configure-commands')
def generate_script(self):
script = ""
for command_name in _command_steps:
......@@ -226,3 +224,15 @@ class BuildElement(Element):
commands.append(command)
return commands
def _run_command(self, sandbox, cmd, cmd_name):
with self.timed_activity("Running {}".format(cmd_name)):
self.status("Running {}".format(cmd_name), detail=cmd)
# Note the -e switch to 'sh' means to exit with an error
# if any untested command fails.
#
exitcode = sandbox.run(['sh', '-c', '-e', cmd + '\n'],
SandboxFlags.ROOT_READ_ONLY)
if exitcode != 0:
raise ElementError("Command '{}' failed with exitcode {}".format(cmd, exitcode))
......@@ -281,6 +281,25 @@ class Element(Plugin):
raise ImplError("element plugin '{kind}' does not implement stage()".format(
kind=self.get_kind()))
def prepare(self, sandbox):
"""Run one-off preparation commands.
This is run before assemble(), but is guaranteed to run only
the first time if we build incrementally - this makes it
possible to run configure-like commands without causing the
entire element to rebuild.
Args:
sandbox (:class:`.Sandbox`): The build sandbox
Raises:
(:class:`.ElementError`): When the element raises an error
By default, this method does nothing, but may be overriden to
allow configure-like commands.
"""
pass
def assemble(self, sandbox):
"""Assemble the output artifact
......@@ -1167,6 +1186,23 @@ class Element(Plugin):
return refs
# _prepare():
#
# Internal method for calling public abstract prepare() method.
#
def _prepare(self, sandbox):
workspace = self._get_workspace()
# We need to ensure that the prepare() method is only called
# once in workspaces, because the changes will persist across
# incremental builds - not desirable, for example, in the case
# of autotools' `./configure`.
if not (workspace and workspace.prepared):
self.prepare(sandbox)
if workspace:
workspace.prepared = True
# _assemble():
#
# Internal method for calling public abstract assemble() method.
......@@ -1202,7 +1238,9 @@ class Element(Plugin):
self.configure_sandbox(sandbox)
# Step 2 - Stage
self.stage(sandbox)
# Step 3 - Assemble
# Step 3 - Prepare
self._prepare(sandbox)
# Step 4 - Assemble
collect = self.assemble(sandbox)
except BstError as e:
# If an error occurred assembling an element in a sandbox,
......
......@@ -32,7 +32,7 @@ The empty configuration is as such:
import os
import shutil
from buildstream import BuildElement, ElementError
from buildstream import Element, BuildElement, ElementError
# Element implementation for the 'import' kind.
......@@ -92,6 +92,10 @@ class ImportElement(BuildElement):
# And we're done
return '/output'
def prepare(self, sandbox):
# We inherit a non-default prepare from BuildElement.
Element.prepare(self, sandbox)
def generate_script(self):
build_root = self.get_variable('build-root')
install_root = self.get_variable('install-root')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment