summaryrefslogtreecommitdiff
path: root/src/buildstream/plugins/elements/stack.py
blob: bd914ed0abafc644bcfe4767e7d70d7cd6c5dbb5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#
#  Copyright (C) 2020 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:
#        Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>

"""
stack - Symbolic Element for dependency grouping
================================================
Stack elements are simply a symbolic element used for representing
a logical group of elements.

All dependencies declared in stack elements must always be both
:ref:`build and runtime dependencies <format_dependencies_types>`.

**Example:**

.. code:: yaml

   kind: stack

   # Declare all of your dependencies in the `depends` list.
   depends:
   - libc.bst
   - coreutils.bst

.. note::

   Unlike other elements, whose cache keys are a unique identifier
   of the contents of the artifacts they produce, stack elements do
   not produce any artifact content. Instead, the cache key of an artifact
   is a unique identifier for the assembly of its own dependencies.


Using intermediate stacks
-------------------------
Using a stack element at intermediate levels of your build graph
allows you to abstract away some parts of your project into logical
subsystems which elements can more conveniently depend on as a whole.

In addition to the added convenience, it will allow you to more
easily change the implementation of a subsystem later on, without needing
to update many reverse dependencies to depend on new elements, or even
allow you to conditionally implement a subsystem with various implementations
depending on what :ref:`project options <project_options>` were specified at
build time.


Using toplevel stacks
---------------------
Stack elements can also be useful as toplevel targets in your build graph
to simply indicate all of the components which need to be built for a given
system to be complete, or for your integration pipeline to be successful.


Checking out and deploying toplevel stacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In case that your software is built remotely, it is possible to checkout
the built content of a stack on your own machine for the purposes of
inspection or further deployment.

To accomplish this, you will need to know the cache key of the stack element
which was built remotely, possibly by inspecting the remote build log or by
deriving it with an equally configured BuildStream project, and you will
need read access to the artifact cache server which the build was uploaded to,
this should be configured in your :ref:`user configuration file <config_artifacts>`.

You can then checkout the remotely built stack using the
:ref:`bst artifact checkout <invoking_artifact_checkout>` command and providing
it with the :ref:`artifact name <artifact_names>`:

**Example:**

.. code:: shell

   bst artifact checkout --deps build --pull --integrate \\
       --directory `pwd`/checkout \\
       project/stack/788da21e7c1b5818b7e7b60f7eb75841057ff7e45d362cc223336c606fe47f27

.. note::

   It is possible to checkout other elements in the same way, however stack
   elements are uniquely suited to this purpose, as they cannot have
   :ref:`runtime only dependencies <format_dependencies_types>`, and consequently
   their cache keys are always a unique representation of their collective
   dependencies.
"""

from buildstream import Element, ElementError
from buildstream.types import _Scope


# Element implementation for the 'stack' kind.
class StackElement(Element):
    # pylint: disable=attribute-defined-outside-init

    BST_MIN_VERSION = "2.0"

    # This plugin does not produce any artifacts when built
    BST_ELEMENT_HAS_ARTIFACT = False

    # This element does not allow sources
    BST_FORBID_SOURCES = True

    # Stack elements do not run any commands
    BST_RUN_COMMANDS = False

    def configure(self, node):
        pass

    def preflight(self):

        # Assert that all dependencies are both build and runtime dependencies.
        #
        all_deps = list(self._dependencies(_Scope.ALL, recurse=False))
        run_deps = list(self._dependencies(_Scope.RUN, recurse=False))
        build_deps = list(self._dependencies(_Scope.BUILD, recurse=False))
        if any(dep not in run_deps for dep in all_deps) or any(dep not in build_deps for dep in all_deps):
            # There is no need to specify the `self` provenance here in preflight() errors, as the base class
            # will take care of prefixing these for plugin author convenience.
            raise ElementError(
                "All dependencies of 'stack' elements must be both build and runtime dependencies",
                detail="Make sure you declare all dependencies in the `depends` list, without specifying any `type`.",
                reason="stack-requires-build-and-run",
            )

    def get_unique_key(self):
        # We do not add anything to the build, only our dependencies
        # do, so our unique key is just a constant.
        return 1

    def configure_sandbox(self, sandbox):
        pass

    def stage(self, sandbox):
        pass

    def assemble(self, sandbox):

        # Just create a dummy empty artifact, its existence is a statement
        # that all this stack's dependencies are built.
        vrootdir = sandbox.get_virtual_directory()
        vrootdir.descend("output", create=True)

        # And we're done
        return "/output"


# Plugin entry point
def setup():
    return StackElement