summaryrefslogtreecommitdiff
path: root/buildstream
diff options
context:
space:
mode:
authorJonathan Maw <jonathan.maw@codethink.co.uk>2017-06-06 10:40:48 +0000
committerTristan Van Berkom <tristan.van.berkom@gmail.com>2017-06-06 10:40:48 +0000
commitd39122416192c94fa1deef5053b5e288f8bbf826 (patch)
tree98281d1d845badc24e7886460ab534d3a193dcc7 /buildstream
parent0c9291c0b6cd3a2d41468b04fb2cd672c7b1d39f (diff)
downloadbuildstream-d39122416192c94fa1deef5053b5e288f8bbf826.tar.gz
Jonathan/enhance script element
Diffstat (limited to 'buildstream')
-rw-r--r--buildstream/__init__.py1
-rw-r--r--buildstream/element.py20
-rw-r--r--buildstream/plugins/elements/script.py134
-rw-r--r--buildstream/plugins/elements/script.yaml53
-rw-r--r--buildstream/plugins/elements/x86image.py69
-rw-r--r--buildstream/plugins/elements/x86image.yaml140
-rw-r--r--buildstream/scriptelement.py246
7 files changed, 522 insertions, 141 deletions
diff --git a/buildstream/__init__.py b/buildstream/__init__.py
index 714ca1f6d..cda2808d3 100644
--- a/buildstream/__init__.py
+++ b/buildstream/__init__.py
@@ -32,3 +32,4 @@ from .plugin import Plugin
from .source import Source, Consistency
from .element import Element, Scope
from .buildelement import BuildElement
+from .scriptelement import ScriptElement
diff --git a/buildstream/element.py b/buildstream/element.py
index 34cf44ac2..15ce644cb 100644
--- a/buildstream/element.py
+++ b/buildstream/element.py
@@ -224,6 +224,26 @@ class Element(Plugin):
value = self.node_get_member(node, str, member_name, default_value=default_value)
return self.__variables.subst(value)
+ def node_subst_list(self, node, member_name):
+ """Fetch a list from a node member, substituting any variables in the list
+
+ Args:
+ node (dict): A dictionary loaded from YAML
+ member_name (str): The name of the member to fetch (a list)
+
+ Returns:
+ The list in *member_name*
+
+ Raises:
+ :class:`.LoadError`
+
+ This is essentially the same as :func:`~buildstream.plugin.Plugin.node_get_member`
+ except that it assumes the expected type is a list of strings and will also
+ perform variable substitutions.
+ """
+ value = self.node_get_member(node, list, member_name)
+ return [self.__variables.subst(x) for x in value]
+
def node_subst_list_element(self, node, member_name, indices):
"""Fetch the value of a list element from a node member, substituting any variables
in the loaded value with the element contextual variables.
diff --git a/buildstream/plugins/elements/script.py b/buildstream/plugins/elements/script.py
index 1f452a86f..0fa06c802 100644
--- a/buildstream/plugins/elements/script.py
+++ b/buildstream/plugins/elements/script.py
@@ -17,135 +17,43 @@
#
# Authors:
# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
+# Jonathan Maw <jonathan.maw@codethink.co.uk>
"""Script element
This element allows one to run some commands to mutate the
input and create some output.
-As with build elements, the output created by a script element is
-collected from the ``%{install-root}`` directory.
-
The default configuration and possible options are as such:
.. literalinclude:: ../../../buildstream/plugins/elements/script.yaml
:language: yaml
"""
-import os
-from buildstream import utils
-from buildstream import Element, ElementError, Scope
-from buildstream import SandboxFlags
+import buildstream
# Element implementation for the 'script' kind.
-class ScriptElement(Element):
-
+class ScriptElement(buildstream.ScriptElement):
def configure(self, node):
- self.base_dep = self.node_get_member(node, str, 'base')
- self.input_dep = self.node_get_member(node, str, 'input', '') or None
- self.stage_mode = self.node_get_member(node, str, 'stage-mode')
- self.collect = self.node_subst_member(node, 'collect', '%{install-root}')
-
- # Assert stage mode is valid
- if self.stage_mode and self.stage_mode not in ['build', 'install']:
- p = self.node_provenance(node, 'stage-mode')
- raise ElementError("{}: Stage mode must be either 'build' or 'install'"
- .format(p))
-
- # Collect variable substituted commands
- self.commands = []
- command_list = self.node_get_member(node, list, 'commands', default_value=[])
- for i in range(len(command_list)):
- self.commands.append(
- self.node_subst_list_element(node, 'commands', [i])
- )
-
- # To be resolved in preflight when the pipeline is built
- self.base_elt = None
- self.input_elt = None
-
- def preflight(self):
-
- # Assert that the user did not list any runtime dependencies
- runtime_deps = list(self.dependencies(Scope.RUN, recurse=False))
- if runtime_deps:
- raise ElementError("{}: Only build type dependencies supported by script elements"
+ for n in self.node_get_member(node, list, 'layout', []):
+ dst = self.node_subst_member(n, 'destination')
+ elm = self.node_subst_member(n, 'element')
+ self.layout_add(elm, dst)
+
+ cmds = []
+ prefixes = ["pre-", "", "post-"]
+ if "commands" not in node:
+ raise ElementError("{}: Unexpectedly missing command group 'commands'"
.format(self))
-
- # Assert that the user did not specify any sources, as they will
- # be ignored by this element type anyway
- sources = list(self.sources())
- if sources:
- raise ElementError("Script elements may not have sources")
-
- # Assert that a base and an input were specified
- if not self.base_dep:
- raise ElementError("{}: No base dependencies were specified".format(self))
-
- # Now resolve the base and input elements
- self.base_elt = self.search(Scope.BUILD, self.base_dep)
- if self.input_dep:
- self.input_elt = self.search(Scope.BUILD, self.input_dep)
-
- if self.base_elt is None:
- raise ElementError("{}: Could not find base dependency {}".format(self, self.base_dep))
-
- def get_unique_key(self):
- return {
- 'commands': self.commands,
- 'base': self.base_dep,
- 'input': self.input_dep,
- 'stage-mode': self.stage_mode,
- 'collect': self.collect
- }
-
- def assemble(self, sandbox):
-
- directory = sandbox.get_directory()
- environment = self.get_environment()
-
- # Stage the base in the sandbox root
- with self.timed_activity("Staging {} as base".format(self.base_dep), silent_nested=True):
- self.base_elt.stage_dependencies(sandbox, Scope.RUN)
-
- # Run any integration commands on the base
- with self.timed_activity("Integrating sandbox", silent_nested=True):
- for dep in self.base_elt.dependencies(Scope.RUN):
- dep.integrate(sandbox)
-
- # Ensure some directories we'll need
- cmd_dir = '/'
- if self.stage_mode:
- os.makedirs(os.path.join(directory,
- 'buildstream',
- 'build'), exist_ok=True)
- os.makedirs(os.path.join(directory,
- 'buildstream',
- 'install'), exist_ok=True)
-
- # Stage the input
- input_dir = os.path.join(os.sep, 'buildstream', self.stage_mode)
- cmd_dir = input_dir
- with self.timed_activity("Staging {} as input at {}"
- .format(self.input_dep, input_dir), silent_nested=True):
- self.input_elt.stage_dependencies(sandbox, Scope.RUN, path=input_dir)
-
- # Run the scripts
- with self.timed_activity("Running script commands"):
- for cmd in self.commands:
- self.status("Running command", 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'],
- 0,
- cwd=cmd_dir,
- env=environment)
- if exitcode != 0:
- raise ElementError("Command '{}' failed with exitcode {}".format(cmd, exitcode))
-
- # Return the install dir
- return self.collect
+ for prefix in prefixes:
+ if prefix + "commands" in node:
+ cmds += self.node_subst_list(node, prefix + "commands")
+ self.add_commands("commands", cmds)
+
+ self.set_work_dir()
+ self.set_install_root()
+ self.set_root_read_only(self.node_get_member(node, bool,
+ 'root-read-only', False))
# Plugin entry point
diff --git a/buildstream/plugins/elements/script.yaml b/buildstream/plugins/elements/script.yaml
index 17e0cfa54..56a656cf6 100644
--- a/buildstream/plugins/elements/script.yaml
+++ b/buildstream/plugins/elements/script.yaml
@@ -1,36 +1,33 @@
+# Common script element variables
+variables:
+ # Defines the directory that output is collected from once commands
+ # have been run.
+ install-root: /buildstream/install
+ #
+ # Defines the directory commands will be run from.
+ cwd: /
+ #
+ # Not directly used, but expected to be used when staging elements to be
+ # worked on.
+ build-root: /buildstream/build
# Script element configuration
config:
- # A dependency of this element to use as the root filesystem
- # for scripting, expressed as a project relative element bst
- # filename.
- #
- # This will define what tools are available to use in
- # the commands. At minimum, a shell should exist in
- # the base dependencies in order to run any commands.
- #
- # base: foo.bst
-
- # A dependency of this element to manipulate as input,
- # expressed as a project relative element bst filename
- #
- # input: bar.bst
+ # Defines whether to run the sandbox with '/' read-only.
+ # It is recommended to set root as read-only wherever possible.
+ root-read-only: False
- # Defines where to stage the 'input' dependencies, the
- # working directory for running commands will also be
- # set to this directory.
- #
- # build: Stage the input in %{build-root}
- #
- # install: Stage the input directly at %{install-root}
- #
- stage-mode: build
+ # Defines where to stage elements which are direct or indirect dependencies.
+ # By default, all direct dependencies are staged to '/'.
+ # This is also commonly used to take one element as an environment
+ # containing the tools used to operate on the other element.
+ # layout:
+ # - element: foo-tools.bst
+ # destination: /
+ # - element: foo-system.bst
+ # destination: %{build-root}
- # List of commands to run in the sandbox
- #
+ # List of commands to run in the sandbox.
commands: []
- # Directory to collect the output artifact from,
- # default is %{install-root}
- # collect:
diff --git a/buildstream/plugins/elements/x86image.py b/buildstream/plugins/elements/x86image.py
new file mode 100644
index 000000000..bd6962dcd
--- /dev/null
+++ b/buildstream/plugins/elements/x86image.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2017 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+# Jonathan Maw <jonathan.maw@codethink.co.uk>
+
+"""x86 image build element
+
+A :mod:`ScriptElement <buildstream.scriptelement>` implementation for creating
+x86 disk images
+
+The x86-image default configuration:
+ .. literalinclude:: ../../../buildstream/plugins/elements/x86image.yaml
+ :language: yaml
+"""
+
+from buildstream import ScriptElement
+
+
+# Element implementation for the 'x86image' kind.
+class X86ImageElement(ScriptElement):
+ def configure(self, node):
+ prefixes = ["pre-", "", "post-"]
+ groups = [
+ "filesystem-tree-setup-commands",
+ "filesystem-image-creation-commands",
+ "partition-commands",
+ "final-commands"
+ ]
+ for group in groups:
+ cmds = []
+ if group not in node:
+ raise ElementError("{}: Unexpectedly missing command group '{}'"
+ .format(self, group))
+ for prefix in prefixes:
+ if prefix + group in node:
+ cmds += self.node_subst_list(node, prefix + group)
+ self.add_commands(group, cmds)
+
+ self.layout_add(self.node_subst_member(node, 'base'), "/")
+ self.layout_add(self.node_subst_member(node, 'input'),
+ self.get_variable('build-root'))
+
+ self.set_work_dir()
+ self.set_install_root()
+ self.set_root_read_only(True)
+
+ def preflight(self):
+ super(X86ImageElement, self).preflight()
+ self.validate_layout()
+
+
+# Plugin entry point
+def setup():
+ return X86ImageElement
diff --git a/buildstream/plugins/elements/x86image.yaml b/buildstream/plugins/elements/x86image.yaml
new file mode 100644
index 000000000..9ca6d0e89
--- /dev/null
+++ b/buildstream/plugins/elements/x86image.yaml
@@ -0,0 +1,140 @@
+#x86 image default configuration
+
+variables:
+ # Size of the disk to create
+ #
+ # Should be able to calculate this based on the space
+ # used, however it must be a multiple of (63 * 512) bytes
+ # as mtools wants a size that is devisable by sectors (512 bytes)
+ # per track (63).
+ boot-size: 252000K
+ rootfs-size: 4G
+ swap-size: 1G
+ sector-size: 512
+
+config:
+ # The element that should be staged into "/". It must contain
+ # all the tools required to generate the image
+ # base: image-tools.bst
+
+ # The element that should be staged into %{build-root}. It is expected
+ # to be the system that you're planning to turn into an image.
+ # input: foo-system.bst
+
+ root-read-only: True
+ filesystem-tree-setup-commands:
+ - |
+ # XXX Split up the boot directory and the other
+ #
+ # This should be changed so that the /boot directory
+ # is created separately.
+
+ cd /buildstream
+ mkdir -p /buildstream/sda1
+ mkdir -p /buildstream/sda2
+
+ mv %{build-root}/boot/* /buildstream/sda1
+ mv %{build-root}/* /buildstream/sda2
+
+ - |
+ # Generate an fstab
+ cat > /buildstream/sda2/etc/fstab << EOF
+ /dev/sda2 / ext4 defaults,rw,noatime 0 1
+ /dev/sda1 /boot vfat defaults 0 2
+ /dev/sda3 none swap defaults 0 0
+ EOF
+
+ - |
+ # Create the syslinux config
+ mkdir -p /buildstream/sda1/syslinux
+ cat > /buildstream/sda1/syslinux/syslinux.cfg << EOF
+ PROMPT 0
+ TIMEOUT 5
+
+ ALLOWOPTIONS 1
+ SERIAL 0 115200
+
+ DEFAULT boot
+ LABEL boot
+
+ KERNEL /vmlinuz
+ INITRD /initramfs.gz
+
+ APPEND root=/dev/sda2 rootfstype=ext4 rootdelay=20 init=/usr/lib/systemd/systemd
+ EOF
+ filesystem-image-creation-commands:
+ - |
+ # Create the vfat image
+ truncate -s %{boot-size} /buildstream/sda1.img
+ mkdosfs /buildstream/sda1.img
+
+ - |
+ # Copy all that stuff into the image
+ mcopy -D s -i /buildstream/sda1.img -s /buildstream/sda1/* ::/
+
+ - |
+ # Install the bootloader on the image, it should get the config file
+ # from inside the vfat image, I think
+ syslinux --directory /syslinux/ /buildstream/sda1.img
+
+ - |
+ # Now create the root filesys on sda2
+ truncate -s %{rootfs-size} /buildstream/sda2.img
+ mkfs.ext4 -F -i 8192 /buildstream/sda2.img -L root -d /buildstream/sda2
+
+ - |
+ # Create swap
+ truncate -s %{swap-size} /buildstream/sda3.img
+ mkswap -L swap /buildstream/sda3.img
+ partition-commands:
+ - |
+ ########################################
+ # Partition the disk #
+ ########################################
+
+ # First get the size in bytes
+ sda1size=$(stat --printf="%s" /buildstream/sda1.img)
+ sda2size=$(stat --printf="%s" /buildstream/sda2.img)
+ sda3size=$(stat --printf="%s" /buildstream/sda3.img)
+
+ # Now convert to sectors
+ sda1sec=$(( ${sda1size} / %{sector-size} ))
+ sda2sec=$(( ${sda2size} / %{sector-size} ))
+ sda3sec=$(( ${sda3size} / %{sector-size} ))
+
+ # Now get the offsets in sectors, first sector is MBR
+ sda1offset=1
+ sda2offset=$(( ${sda1offset} + ${sda1sec} ))
+ sda3offset=$(( ${sda2offset} + ${sda2sec} ))
+
+ # Get total disk size in sectors and bytes
+ sdasectors=$(( ${sda3offset} + ${sda3sec} ))
+ sdabytes=$(( ${sdasectors} * %{sector-size} ))
+
+ # Create the main disk and do the partitioning
+ truncate -s ${sdabytes} /buildstream/sda.img
+ parted -s /buildstream/sda.img mklabel msdos
+ parted -s /buildstream/sda.img unit s mkpart primary fat32 ${sda1offset} $(( ${sda1offset} + ${sda1sec} - 1 ))
+ parted -s /buildstream/sda.img unit s mkpart primary ext2 ${sda2offset} $(( ${sda2offset} + ${sda2sec} - 1 ))
+ parted -s /buildstream/sda.img unit s mkpart primary linux-swap ${sda3offset} $(( ${sda3offset} + ${sda3sec} - 1 ))
+
+ # Make partition 1 the boot partition
+ parted -s /buildstream/sda.img set 1 boot on
+
+ # Now splice the existing filesystems directly into the image
+ dd if=/buildstream/sda1.img of=/buildstream/sda.img \
+ ibs=%{sector-size} obs=%{sector-size} conv=notrunc \
+ count=${sda1sec} seek=${sda1offset}
+
+ dd if=/buildstream/sda2.img of=/buildstream/sda.img \
+ ibs=%{sector-size} obs=%{sector-size} conv=notrunc \
+ count=${sda2sec} seek=${sda2offset}
+
+ dd if=/buildstream/sda3.img of=/buildstream/sda.img \
+ ibs=%{sector-size} obs=%{sector-size} conv=notrunc \
+ count=${sda3sec} seek=${sda3offset}
+ final-commands:
+ - |
+ # Move the image where it will be collected
+ mv /buildstream/sda.img %{install-root}
+ chmod 0644 %{install-root}/sda.img
diff --git a/buildstream/scriptelement.py b/buildstream/scriptelement.py
new file mode 100644
index 000000000..a4144f7af
--- /dev/null
+++ b/buildstream/scriptelement.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2017 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+# Jonathan Maw <jonathan.maw@codethink.co.uk>
+
+"""The ScriptElement class is a convenience class one can derive for
+implementing elements that stage elements and run command-lines on them.
+
+Any derived classes must write their own configure() implementation, using
+the public APIs exposed in this class.
+
+Derived classes must also chain up to the parent method in their preflight()
+implementations.
+
+
+"""
+
+import os
+from collections import OrderedDict
+
+from . import Element, ElementError, Scope, SandboxFlags
+
+
+class ScriptElement(Element):
+ __install_root = "/"
+ __cwd = "/"
+ __root_read_only = False
+ __commands = None
+ __layout = None
+
+ def set_work_dir(self, work_dir=None):
+ """Sets the working dir
+
+ The working dir (a.k.a. cwd) is the directory which commands will be
+ called from.
+
+ Args:
+ work_dir (str): The working directory. If called without this argument
+ set, it'll default to the value of the variable ``cwd``.
+ """
+ if work_dir is None:
+ self.__cwd = self.get_variable("cwd") or "/"
+ else:
+ self.__cwd = work_dir
+
+ def set_install_root(self, install_root=None):
+ """Sets the install root
+
+ The install root is the directory which output will be collected from
+ once the commands have been run.
+
+ Args:
+ install_root(str): The install root. If called without this argument
+ set, it'll default to the value of the variable ``install-root``.
+ """
+ if install_root is None:
+ self.__install_root = self.get_variable("install-root") or "/"
+ else:
+ self.__install_root = install_root
+
+ def set_root_read_only(self, root_read_only):
+ """Sets root read-only
+
+ When commands are run, if root_read_only is true, then the root of the
+ filesystem will be protected. This is strongly recommended whenever
+ possible.
+
+ If this variable is not set, the default permission is read-write.
+
+ Args:
+ root_read_only (bool): Whether to mark the root filesystem as
+ read-only.
+ """
+ self.__root_read_only = root_read_only
+
+ def layout_add(self, element, destination):
+ """Adds an element-destination pair to the layout.
+
+ Layout is a way of defining how dependencies should be added to the
+ staging area for running commands.
+
+ Args:
+ element (str): The name of the element to stage. This may be any
+ element found in the dependencies, whether it is a direct or indirect
+ dependency.
+ destination (str): The path inside the staging area for where to
+ stage this element. If it is not "/", then integration commands will
+ not be run.
+
+ If this function is never called, then the default behavior is to just
+ stage the Scope.BUILD dependencies of the element in question at the
+ sandbox root. Otherwise, the Scope.RUN dependencies of each specified
+ element will be staged in their specified destination directories.
+ """
+ if not self.__layout:
+ self.__layout = []
+ self.__layout.append({"element": element,
+ "destination": destination})
+
+ def add_commands(self, group_name, command_list):
+ """Adds a list of commands under the group-name.
+
+ .. note::
+
+ Command groups will be run in the order they were added.
+
+ .. note::
+
+ This does not perform substitutions automatically. They must
+ be performed beforehand (see
+ :func:`~buildstream.element.Element.node_subst_list`)
+
+ Args:
+ group_name (str): The name of the group of commands.
+ command_list (list): The list of commands to be run.
+ """
+ if not self.__commands:
+ self.__commands = OrderedDict()
+ self.__commands[group_name] = command_list
+
+ def __validate_layout(self):
+ if self.__layout:
+ # Cannot proceeed if layout is used, but none are for "/"
+ root_defined = any([(entry['destination'] == '/') for entry in self.__layout])
+ if not root_defined:
+ raise ElementError("{}: Using layout, but none are staged as '/'"
+ .format(self))
+
+ # Cannot proceed if layout specifies an element that isn't part
+ # of the dependencies.
+ rundeps = set()
+ for rd in self.dependencies(Scope.BUILD, recurse=False):
+ rundeps |= set(rd.dependencies(Scope.RUN))
+ for item in self.__layout:
+ element_name = item['element']
+ element_found = any([(rd.name == element_name) for rd in rundeps])
+ if not element_found:
+ raise ElementError("{}: '{}' in layout not found in dependencies"
+ .format(self, element_name))
+
+ def preflight(self):
+ # All dependencies on script elements must be BUILD only, otherwise
+ # the element will get pulled into dependencies
+ if any(self.dependencies(Scope.RUN, recurse=False)):
+ raise ElementError("{}: Only build type dependencies supported by script elements"
+ .format(self))
+
+ # Script elements have no use for sources, so they should not be present.
+ if any(self.sources()):
+ raise ElementError("{}: Script elements should not have sources".format(self))
+
+ # The layout, if set, must make sense.
+ self.__validate_layout()
+
+ def get_unique_key(self):
+ return {
+ 'commands': self.__commands,
+ 'cwd': self.__cwd,
+ 'install-root': self.__install_root,
+ 'layout': self.__layout,
+ 'root-read-only': self.__root_read_only
+ }
+
+ def assemble(self, sandbox):
+ # Stage the elements, and run integration commands where appropriate.
+ if not self.__layout:
+ # if no layout set, stage all dependencies into /
+ for build_dep in self.dependencies(Scope.BUILD, recurse=False):
+ with self.timed_activity("Staging {} at /"
+ .format(build_dep), silent_nested=True):
+ build_dep.stage_dependencies(sandbox, Scope.RUN, path="/")
+
+ for build_dep in self.dependencies(Scope.BUILD, recurse=False):
+ with self.timed_activity("Integrating {}".format(build_dep), silent_nested=True):
+ for dep in build_dep.dependencies(Scope.RUN):
+ dep.integrate(sandbox)
+ else:
+ # If layout, follow its rules.
+ for item in self.__layout:
+ for bd in self.dependencies(Scope.BUILD, recurse=False):
+ element = bd.search(Scope.RUN, item['element'])
+ if element:
+ break
+ if item['destination'] == '/':
+ with self.timed_activity("Staging {} at /".format(item['element']),
+ silent_nested=True):
+ element.stage_dependencies(sandbox, Scope.RUN)
+ else:
+ with self.timed_activity("Staging {} at {}"
+ .format(item['element'], item['destination']),
+ silent_nested=True):
+ real_dstdir = os.path.join(sandbox.get_directory(),
+ item['destination'].lstrip(os.sep))
+ os.makedirs(os.path.dirname(real_dstdir), exist_ok=True)
+ element.stage_dependencies(sandbox, Scope.RUN, path=item['destination'])
+
+ for item in self.__layout:
+ for bd in self.dependencies(Scope.BUILD, recurse=False):
+ element = bd.search(Scope.RUN, item['element'])
+ if element:
+ break
+ # Integration commands can only be run for elements staged to /
+ if item['destination'] == '/':
+ with self.timed_activity("Integrating {}".format(item['element']),
+ silent_nested=True):
+ for dep in element.dependencies(Scope.RUN):
+ dep.integrate(sandbox)
+
+ os.makedirs(os.path.join(sandbox.get_directory(), self.__install_root.lstrip(os.sep)),
+ exist_ok=True)
+
+ environment = self.get_environment()
+ for groupname, commands in self.__commands.items():
+ with self.timed_activity("Running '{}'".format(groupname)):
+ for cmd in commands:
+ self.status("Running command", 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 self.__root_read_only else 0,
+ cwd=self.__cwd,
+ env=environment)
+ if exitcode != 0:
+ raise ElementError("Command '{}' failed with exitcode {}".format(cmd, exitcode))
+
+ # Return where the result can be collected from
+ return self.__install_root
+
+
+def setup():
+ return ScriptElement