diff options
Diffstat (limited to 'src/buildstream/buildelement.py')
-rw-r--r-- | src/buildstream/buildelement.py | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/src/buildstream/buildelement.py b/src/buildstream/buildelement.py new file mode 100644 index 000000000..158f5fc11 --- /dev/null +++ b/src/buildstream/buildelement.py @@ -0,0 +1,299 @@ +# +# Copyright (C) 2016 Codethink Limited +# Copyright (C) 2018 Bloomberg Finance LP +# +# 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: +# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> +""" +BuildElement - Abstract class for build elements +================================================ +The BuildElement class is a convenience element one can derive from for +implementing the most common case of element. + +.. _core_buildelement_builtins: + +Built-in functionality +---------------------- + +The BuildElement base class provides built in functionality that could be +overridden by the individual plugins. + +This section will give a brief summary of how some of the common features work, +some of them or the variables they use will be further detailed in the following +sections. + +The `strip-binaries` variable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The `strip-binaries` variable is by default **empty**. You need to use the +appropiate commands depending of the system you are building. +If you are targetting Linux, ones known to work are the ones used by the +`freedesktop-sdk <https://freedesktop-sdk.io/>`_, you can take a look to them in their +`project.conf <https://gitlab.com/freedesktop-sdk/freedesktop-sdk/blob/freedesktop-sdk-18.08.21/project.conf#L74>`_ + +Location for running commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``command-subdir`` variable sets where the build commands will be executed, +if the directory does not exist it will be created, it is defined relative to +the buildroot. + +Location for configuring the project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``conf-root`` is defined by default as ``.`` and is the location that +specific build element can use to look for build configuration files. This is +used by elements such as autotools, cmake, distutils, meson, pip and qmake. + +The configuration commands are run in ``command-subdir`` and by default +``conf-root`` is ``.`` so if ``conf-root`` is not set the configuration files +in ``command-subdir`` will be used. + +By setting ``conf-root`` to ``"%{build-root}/Source/conf_location"`` and your +source elements ``directory`` variable to ``Source`` then the configuration +files in the directory ``conf_location`` with in your Source will be used. +The current working directory when your configuration command is run will still +be wherever you set your ``command-subdir`` to be, regardless of where the +configure scripts are set with ``conf-root``. + +.. note:: + + The ``conf-root`` variable is available since :ref:`format version 17 <project_format_version>` + +Install Location +~~~~~~~~~~~~~~~~ + +You should not change the ``install-root`` variable as it is a special +writeable location in the sandbox but it is useful when writing custom +install instructions as it may need to be supplied as the ``DESTDIR``, please +see the :mod:`cmake <elements.cmake>` build element for example. + +Abstract method implementations +------------------------------- + +Element.configure_sandbox() +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In :func:`Element.configure_sandbox() <buildstream.element.Element.configure_sandbox>`, +the BuildElement will ensure that the sandbox locations described by the ``%{build-root}`` +and ``%{install-root}`` variables are marked and will be mounted read-write for the +:func:`assemble phase<buildstream.element.Element.configure_sandbox>`. + +The working directory for the sandbox will be configured to be the ``%{build-root}``, +unless the ``%{command-subdir}`` variable is specified for the element in question, +in which case the working directory will be configured as ``%{build-root}/%{command-subdir}``. + + +Element.stage() +~~~~~~~~~~~~~~~ +In :func:`Element.stage() <buildstream.element.Element.stage>`, the BuildElement +will do the following operations: + +* Stage all the dependencies in the :func:`Scope.BUILD <buildstream.element.Scope.BUILD>` + scope into the sandbox root. + +* Run the integration commands for all staged dependencies using + :func:`Element.integrate() <buildstream.element.Element.integrate>` + +* Stage any Source on the given element to the ``%{build-root}`` location + inside the sandbox, using + :func:`Element.stage_sources() <buildstream.element.Element.integrate>` + + +Element.prepare() +~~~~~~~~~~~~~~~~~ +In :func:`Element.prepare() <buildstream.element.Element.prepare>`, +the BuildElement will run ``configure-commands``, which are used to +run one-off preparations that should not be repeated for a single +build directory. + + +Element.assemble() +~~~~~~~~~~~~~~~~~~ +In :func:`Element.assemble() <buildstream.element.Element.assemble>`, the +BuildElement will proceed to run sandboxed commands which are expected to be +found in the element configuration. + +Commands are run in the following order: + +* ``build-commands``: Commands to build the element +* ``install-commands``: Commands to install the results into ``%{install-root}`` +* ``strip-commands``: Commands to strip debugging symbols installed binaries + +The result of the build is expected to end up in ``%{install-root}``, and +as such; Element.assemble() method will return the ``%{install-root}`` for +artifact collection purposes. +""" + +import os + +from .element import Element +from .sandbox import SandboxFlags +from .types import Scope + + +# This list is preserved because of an unfortunate situation, we +# need to remove these older commands which were secret and never +# documented, but without breaking the cache keys. +_legacy_command_steps = ['bootstrap-commands', + 'configure-commands', + 'build-commands', + 'test-commands', + 'install-commands', + 'strip-commands'] + +_command_steps = ['configure-commands', + 'build-commands', + 'install-commands', + 'strip-commands'] + + +class BuildElement(Element): + + ############################################################# + # Abstract Method Implementations # + ############################################################# + def configure(self, node): + + self.__commands = {} # pylint: disable=attribute-defined-outside-init + + # FIXME: Currently this forcefully validates configurations + # for all BuildElement subclasses so they are unable to + # extend the configuration + self.node_validate(node, _command_steps) + + for command_name in _legacy_command_steps: + if command_name in _command_steps: + self.__commands[command_name] = self.__get_commands(node, command_name) + else: + self.__commands[command_name] = [] + + def preflight(self): + pass + + def get_unique_key(self): + dictionary = {} + + for command_name, command_list in self.__commands.items(): + dictionary[command_name] = command_list + + # Specifying notparallel for a given element effects the + # cache key, while having the side effect of setting max-jobs to 1, + # which is normally automatically resolved and does not affect + # the cache key. + if self.get_variable('notparallel'): + dictionary['notparallel'] = True + + return dictionary + + def configure_sandbox(self, sandbox): + build_root = self.get_variable('build-root') + install_root = self.get_variable('install-root') + + # Tell the sandbox to mount the build root and install root + sandbox.mark_directory(build_root) + sandbox.mark_directory(install_root) + + # Allow running all commands in a specified subdirectory + command_subdir = self.get_variable('command-subdir') + if command_subdir: + command_dir = os.path.join(build_root, command_subdir) + else: + command_dir = build_root + sandbox.set_work_directory(command_dir) + + # Tell sandbox which directory is preserved in the finished artifact + sandbox.set_output_directory(install_root) + + # Setup environment + sandbox.set_environment(self.get_environment()) + + def stage(self, sandbox): + + # Stage deps in the sandbox root + with self.timed_activity("Staging dependencies", silent_nested=True): + self.stage_dependency_artifacts(sandbox, Scope.BUILD) + + # Run any integration commands provided by the dependencies + # once they are all staged and ready + with sandbox.batch(SandboxFlags.NONE, label="Integrating sandbox"): + for dep in self.dependencies(Scope.BUILD): + dep.integrate(sandbox) + + # Stage sources in the build root + self.stage_sources(sandbox, self.get_variable('build-root')) + + def assemble(self, sandbox): + # Run commands + for command_name in _command_steps: + commands = self.__commands[command_name] + if not commands or command_name == 'configure-commands': + continue + + with sandbox.batch(SandboxFlags.ROOT_READ_ONLY, label="Running {}".format(command_name)): + for cmd in commands: + self.__run_command(sandbox, cmd) + + # %{install-root}/%{build-root} should normally not be written + # to - if an element later attempts to stage to a location + # that is not empty, we abort the build - in this case this + # will almost certainly happen. + staged_build = os.path.join(self.get_variable('install-root'), + self.get_variable('build-root')) + + if os.path.isdir(staged_build) and os.listdir(staged_build): + self.warn("Writing to %{install-root}/%{build-root}.", + detail="Writing to this directory will almost " + + "certainly cause an error, since later elements " + + "will not be allowed to stage to %{build-root}.") + + # Return the payload, this is configurable but is generally + # always the /buildstream-install directory + return self.get_variable('install-root') + + def prepare(self, sandbox): + commands = self.__commands['configure-commands'] + if commands: + with sandbox.batch(SandboxFlags.ROOT_READ_ONLY, label="Running configure-commands"): + for cmd in commands: + self.__run_command(sandbox, cmd) + + def generate_script(self): + script = "" + for command_name in _command_steps: + commands = self.__commands[command_name] + + for cmd in commands: + script += "(set -ex; {}\n) || exit 1\n".format(cmd) + + return script + + ############################################################# + # Private Local Methods # + ############################################################# + def __get_commands(self, node, name): + list_node = self.node_get_member(node, list, name, []) + commands = [] + + for i in range(len(list_node)): + command = self.node_subst_list_element(node, name, [i]) + commands.append(command) + + return commands + + def __run_command(self, sandbox, cmd): + # Note the -e switch to 'sh' means to exit with an error + # if any untested command fails. + # + sandbox.run(['sh', '-c', '-e', cmd + '\n'], + SandboxFlags.ROOT_READ_ONLY, + label=cmd) |