summaryrefslogtreecommitdiff
path: root/buildstream
diff options
context:
space:
mode:
authorChandan Singh <csingh43@bloomberg.net>2019-04-24 22:53:19 +0100
committerChandan Singh <csingh43@bloomberg.net>2019-05-21 12:41:18 +0100
commit070d053e5cc47e572e9f9e647315082bd7a15c63 (patch)
tree7fb0fdff52f9b5f8a18ec8fe9c75b661f9e0839e /buildstream
parent6c59e7901a52be961c2a1b671cf2b30f90bc4d0a (diff)
downloadbuildstream-070d053e5cc47e572e9f9e647315082bd7a15c63.tar.gz
Move source from 'buildstream' to 'src/buildstream'
This was discussed in #1008. Fixes #1009.
Diffstat (limited to 'buildstream')
-rw-r--r--buildstream/__init__.py41
-rw-r--r--buildstream/__main__.py17
-rw-r--r--buildstream/_artifact.py449
-rw-r--r--buildstream/_artifactcache.py617
-rw-r--r--buildstream/_artifactelement.py92
-rw-r--r--buildstream/_basecache.py307
-rw-r--r--buildstream/_cachekey.py68
-rw-r--r--buildstream/_cas/__init__.py21
-rw-r--r--buildstream/_cas/cascache.py1462
-rw-r--r--buildstream/_cas/casremote.py391
-rw-r--r--buildstream/_cas/casserver.py619
-rw-r--r--buildstream/_context.py766
-rw-r--r--buildstream/_elementfactory.py63
-rw-r--r--buildstream/_exceptions.py370
-rw-r--r--buildstream/_frontend/__init__.py25
-rw-r--r--buildstream/_frontend/app.py870
-rw-r--r--buildstream/_frontend/cli.py1277
-rw-r--r--buildstream/_frontend/complete.py338
-rw-r--r--buildstream/_frontend/linuxapp.py64
-rw-r--r--buildstream/_frontend/profile.py77
-rw-r--r--buildstream/_frontend/status.py523
-rw-r--r--buildstream/_frontend/widget.py806
-rw-r--r--buildstream/_fuse/__init__.py20
-rw-r--r--buildstream/_fuse/fuse.py1006
-rw-r--r--buildstream/_fuse/hardlinks.py218
-rw-r--r--buildstream/_fuse/mount.py196
-rw-r--r--buildstream/_gitsourcebase.py683
-rw-r--r--buildstream/_includes.py145
-rw-r--r--buildstream/_loader/__init__.py22
-rw-r--r--buildstream/_loader/loadelement.py181
-rw-r--r--buildstream/_loader/loader.py710
-rw-r--r--buildstream/_loader/metaelement.py60
-rw-r--r--buildstream/_loader/metasource.py42
-rw-r--r--buildstream/_loader/types.py112
-rw-r--r--buildstream/_message.py80
-rw-r--r--buildstream/_options/__init__.py20
-rw-r--r--buildstream/_options/option.py112
-rw-r--r--buildstream/_options/optionarch.py84
-rw-r--r--buildstream/_options/optionbool.py58
-rw-r--r--buildstream/_options/optioneltmask.py46
-rw-r--r--buildstream/_options/optionenum.py77
-rw-r--r--buildstream/_options/optionflags.py86
-rw-r--r--buildstream/_options/optionos.py41
-rw-r--r--buildstream/_options/optionpool.py295
-rw-r--r--buildstream/_pipeline.py516
-rw-r--r--buildstream/_platform/__init__.py20
-rw-r--r--buildstream/_platform/darwin.py48
-rw-r--r--buildstream/_platform/linux.py150
-rw-r--r--buildstream/_platform/platform.py164
-rw-r--r--buildstream/_platform/unix.py56
-rw-r--r--buildstream/_plugincontext.py239
-rw-r--r--buildstream/_profile.py160
-rw-r--r--buildstream/_project.py975
-rw-r--r--buildstream/_projectrefs.py155
-rw-r--r--buildstream/_protos/__init__.py0
-rw-r--r--buildstream/_protos/build/__init__.py0
-rw-r--r--buildstream/_protos/build/bazel/__init__.py0
-rw-r--r--buildstream/_protos/build/bazel/remote/__init__.py0
-rw-r--r--buildstream/_protos/build/bazel/remote/execution/__init__.py0
-rw-r--r--buildstream/_protos/build/bazel/remote/execution/v2/__init__.py0
-rw-r--r--buildstream/_protos/build/bazel/remote/execution/v2/remote_execution.proto1331
-rw-r--r--buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2.py2660
-rw-r--r--buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py593
-rw-r--r--buildstream/_protos/build/bazel/semver/__init__.py0
-rw-r--r--buildstream/_protos/build/bazel/semver/semver.proto24
-rw-r--r--buildstream/_protos/build/bazel/semver/semver_pb2.py90
-rw-r--r--buildstream/_protos/build/bazel/semver/semver_pb2_grpc.py3
-rw-r--r--buildstream/_protos/buildstream/__init__.py0
-rw-r--r--buildstream/_protos/buildstream/v2/__init__.py0
-rw-r--r--buildstream/_protos/buildstream/v2/artifact.proto88
-rw-r--r--buildstream/_protos/buildstream/v2/artifact_pb2.py387
-rw-r--r--buildstream/_protos/buildstream/v2/artifact_pb2_grpc.py68
-rw-r--r--buildstream/_protos/buildstream/v2/buildstream.proto95
-rw-r--r--buildstream/_protos/buildstream/v2/buildstream_pb2.py325
-rw-r--r--buildstream/_protos/buildstream/v2/buildstream_pb2_grpc.py89
-rw-r--r--buildstream/_protos/google/__init__.py0
-rw-r--r--buildstream/_protos/google/api/__init__.py0
-rw-r--r--buildstream/_protos/google/api/annotations.proto31
-rw-r--r--buildstream/_protos/google/api/annotations_pb2.py46
-rw-r--r--buildstream/_protos/google/api/annotations_pb2_grpc.py3
-rw-r--r--buildstream/_protos/google/api/http.proto313
-rw-r--r--buildstream/_protos/google/api/http_pb2.py243
-rw-r--r--buildstream/_protos/google/api/http_pb2_grpc.py3
-rw-r--r--buildstream/_protos/google/bytestream/__init__.py0
-rw-r--r--buildstream/_protos/google/bytestream/bytestream.proto181
-rw-r--r--buildstream/_protos/google/bytestream/bytestream_pb2.py353
-rw-r--r--buildstream/_protos/google/bytestream/bytestream_pb2_grpc.py160
-rw-r--r--buildstream/_protos/google/longrunning/__init__.py0
-rw-r--r--buildstream/_protos/google/longrunning/operations.proto160
-rw-r--r--buildstream/_protos/google/longrunning/operations_pb2.py391
-rw-r--r--buildstream/_protos/google/longrunning/operations_pb2_grpc.py132
-rw-r--r--buildstream/_protos/google/rpc/__init__.py0
-rw-r--r--buildstream/_protos/google/rpc/code.proto186
-rw-r--r--buildstream/_protos/google/rpc/code_pb2.py133
-rw-r--r--buildstream/_protos/google/rpc/code_pb2_grpc.py3
-rw-r--r--buildstream/_protos/google/rpc/status.proto92
-rw-r--r--buildstream/_protos/google/rpc/status_pb2.py88
-rw-r--r--buildstream/_protos/google/rpc/status_pb2_grpc.py3
-rw-r--r--buildstream/_scheduler/__init__.py30
-rw-r--r--buildstream/_scheduler/jobs/__init__.py23
-rw-r--r--buildstream/_scheduler/jobs/cachesizejob.py41
-rw-r--r--buildstream/_scheduler/jobs/cleanupjob.py50
-rw-r--r--buildstream/_scheduler/jobs/elementjob.py115
-rw-r--r--buildstream/_scheduler/jobs/job.py682
-rw-r--r--buildstream/_scheduler/queues/__init__.py1
-rw-r--r--buildstream/_scheduler/queues/artifactpushqueue.py44
-rw-r--r--buildstream/_scheduler/queues/buildqueue.py117
-rw-r--r--buildstream/_scheduler/queues/fetchqueue.py80
-rw-r--r--buildstream/_scheduler/queues/pullqueue.py66
-rw-r--r--buildstream/_scheduler/queues/queue.py328
-rw-r--r--buildstream/_scheduler/queues/sourcepushqueue.py42
-rw-r--r--buildstream/_scheduler/queues/trackqueue.py62
-rw-r--r--buildstream/_scheduler/resources.py166
-rw-r--r--buildstream/_scheduler/scheduler.py602
-rw-r--r--buildstream/_signals.py203
-rw-r--r--buildstream/_site.py67
-rw-r--r--buildstream/_sourcecache.py249
-rw-r--r--buildstream/_sourcefactory.py64
-rw-r--r--buildstream/_stream.py1512
-rw-r--r--buildstream/_variables.py251
-rw-r--r--buildstream/_version.py522
-rw-r--r--buildstream/_versions.py36
-rw-r--r--buildstream/_workspaces.py650
-rw-r--r--buildstream/_yaml.py1432
-rw-r--r--buildstream/buildelement.py299
-rw-r--r--buildstream/data/bst21
-rw-r--r--buildstream/data/build-all.sh.in40
-rw-r--r--buildstream/data/build-module.sh.in43
-rw-r--r--buildstream/data/projectconfig.yaml183
-rw-r--r--buildstream/data/userconfig.yaml113
-rw-r--r--buildstream/element.py3062
-rw-r--r--buildstream/plugin.py929
-rw-r--r--buildstream/plugins/elements/__init__.py0
-rw-r--r--buildstream/plugins/elements/autotools.py75
-rw-r--r--buildstream/plugins/elements/autotools.yaml129
-rw-r--r--buildstream/plugins/elements/cmake.py74
-rw-r--r--buildstream/plugins/elements/cmake.yaml72
-rw-r--r--buildstream/plugins/elements/compose.py194
-rw-r--r--buildstream/plugins/elements/compose.yaml34
-rw-r--r--buildstream/plugins/elements/distutils.py51
-rw-r--r--buildstream/plugins/elements/distutils.yaml49
-rw-r--r--buildstream/plugins/elements/filter.py256
-rw-r--r--buildstream/plugins/elements/filter.yaml29
-rw-r--r--buildstream/plugins/elements/import.py129
-rw-r--r--buildstream/plugins/elements/import.yaml14
-rw-r--r--buildstream/plugins/elements/junction.py229
-rw-r--r--buildstream/plugins/elements/make.py56
-rw-r--r--buildstream/plugins/elements/make.yaml42
-rw-r--r--buildstream/plugins/elements/makemaker.py51
-rw-r--r--buildstream/plugins/elements/makemaker.yaml48
-rw-r--r--buildstream/plugins/elements/manual.py51
-rw-r--r--buildstream/plugins/elements/manual.yaml22
-rw-r--r--buildstream/plugins/elements/meson.py71
-rw-r--r--buildstream/plugins/elements/meson.yaml79
-rw-r--r--buildstream/plugins/elements/modulebuild.py51
-rw-r--r--buildstream/plugins/elements/modulebuild.yaml48
-rw-r--r--buildstream/plugins/elements/pip.py51
-rw-r--r--buildstream/plugins/elements/pip.yaml36
-rw-r--r--buildstream/plugins/elements/qmake.py51
-rw-r--r--buildstream/plugins/elements/qmake.yaml50
-rw-r--r--buildstream/plugins/elements/script.py69
-rw-r--r--buildstream/plugins/elements/script.yaml25
-rw-r--r--buildstream/plugins/elements/stack.py66
-rw-r--r--buildstream/plugins/sources/__init__.py0
-rw-r--r--buildstream/plugins/sources/_downloadablefilesource.py250
-rw-r--r--buildstream/plugins/sources/bzr.py210
-rw-r--r--buildstream/plugins/sources/deb.py83
-rw-r--r--buildstream/plugins/sources/git.py168
-rw-r--r--buildstream/plugins/sources/local.py147
-rw-r--r--buildstream/plugins/sources/patch.py101
-rw-r--r--buildstream/plugins/sources/pip.py254
-rw-r--r--buildstream/plugins/sources/remote.py93
-rw-r--r--buildstream/plugins/sources/tar.py202
-rw-r--r--buildstream/plugins/sources/zip.py181
-rw-r--r--buildstream/sandbox/__init__.py22
-rw-r--r--buildstream/sandbox/_config.py62
-rw-r--r--buildstream/sandbox/_mount.py149
-rw-r--r--buildstream/sandbox/_mounter.py147
-rw-r--r--buildstream/sandbox/_sandboxbwrap.py433
-rw-r--r--buildstream/sandbox/_sandboxchroot.py325
-rw-r--r--buildstream/sandbox/_sandboxdummy.py36
-rw-r--r--buildstream/sandbox/_sandboxremote.py577
-rw-r--r--buildstream/sandbox/sandbox.py717
-rw-r--r--buildstream/scriptelement.py297
-rw-r--r--buildstream/source.py1274
-rw-r--r--buildstream/storage/__init__.py22
-rw-r--r--buildstream/storage/_casbaseddirectory.py622
-rw-r--r--buildstream/storage/_filebaseddirectory.py273
-rw-r--r--buildstream/storage/directory.py211
-rw-r--r--buildstream/testing/__init__.py121
-rw-r--r--buildstream/testing/_sourcetests/__init__.py0
-rw-r--r--buildstream/testing/_sourcetests/build_checkout.py83
-rw-r--r--buildstream/testing/_sourcetests/fetch.py107
-rw-r--r--buildstream/testing/_sourcetests/mirror.py427
-rw-r--r--buildstream/testing/_sourcetests/project/elements/base.bst5
-rw-r--r--buildstream/testing/_sourcetests/project/elements/base/base-alpine.bst17
-rw-r--r--buildstream/testing/_sourcetests/project/elements/import-bin.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/import-dev.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/horsey.bst3
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/pony.bst1
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/zebry.bst3
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/0.bst7
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/1.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/2.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/3.bst6
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/4.bst2
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/5.bst2
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/6.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/7.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/8.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/9.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/elements/multiple_targets/order/run.bst2
-rw-r--r--buildstream/testing/_sourcetests/project/files/bar0
-rwxr-xr-xbuildstream/testing/_sourcetests/project/files/bin-files/usr/bin/hello3
-rw-r--r--buildstream/testing/_sourcetests/project/files/dev-files/usr/include/pony.h12
-rw-r--r--buildstream/testing/_sourcetests/project/files/etc-files/etc/buildstream/config1
-rw-r--r--buildstream/testing/_sourcetests/project/files/foo0
-rw-r--r--buildstream/testing/_sourcetests/project/files/source-bundle/llamas.txt1
-rw-r--r--buildstream/testing/_sourcetests/project/files/sub-project/elements/import-etc.bst4
-rw-r--r--buildstream/testing/_sourcetests/project/files/sub-project/files/etc-files/etc/animal.conf1
-rw-r--r--buildstream/testing/_sourcetests/project/files/sub-project/project.conf4
-rw-r--r--buildstream/testing/_sourcetests/project/project.conf27
-rw-r--r--buildstream/testing/_sourcetests/source_determinism.py114
-rw-r--r--buildstream/testing/_sourcetests/track.py420
-rw-r--r--buildstream/testing/_sourcetests/track_cross_junction.py186
-rw-r--r--buildstream/testing/_sourcetests/workspace.py161
-rw-r--r--buildstream/testing/_utils/__init__.py10
-rw-r--r--buildstream/testing/_utils/junction.py83
-rw-r--r--buildstream/testing/_utils/site.py46
-rw-r--r--buildstream/testing/integration.py97
-rw-r--r--buildstream/testing/repo.py109
-rw-r--r--buildstream/testing/runcli.py883
-rw-r--r--buildstream/types.py177
-rw-r--r--buildstream/utils.py1293
234 files changed, 0 insertions, 49947 deletions
diff --git a/buildstream/__init__.py b/buildstream/__init__.py
deleted file mode 100644
index 62890a62f..000000000
--- a/buildstream/__init__.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-# Plugin author facing APIs
-import os
-if "_BST_COMPLETION" not in os.environ:
-
- # Special sauce to get the version from versioneer
- from ._version import get_versions
- __version__ = get_versions()['version']
- del get_versions
-
- from .utils import UtilError, ProgramNotFoundError
- from .sandbox import Sandbox, SandboxFlags, SandboxCommandError
- from .types import Scope, Consistency, CoreWarnings
- from .plugin import Plugin
- from .source import Source, SourceError, SourceFetcher
- from .element import Element, ElementError
- from .buildelement import BuildElement
- from .scriptelement import ScriptElement
-
- # XXX We are exposing a private member here as we expect it to move to a
- # separate package soon. See the following discussion for more details:
- # https://gitlab.com/BuildStream/buildstream/issues/739#note_124819869
- from ._gitsourcebase import _GitSourceBase, _GitMirror
diff --git a/buildstream/__main__.py b/buildstream/__main__.py
deleted file mode 100644
index 4b0fdabfe..000000000
--- a/buildstream/__main__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-##################################################################
-# Private Entry Point #
-##################################################################
-#
-# This allows running the cli when BuildStream is uninstalled,
-# as long as BuildStream repo is in PYTHONPATH, one can run it
-# with:
-#
-# python3 -m buildstream [program args]
-#
-# This is used when we need to run BuildStream before installing,
-# like when we build documentation.
-#
-if __name__ == '__main__':
- # pylint: disable=no-value-for-parameter
- from ._frontend.cli import cli
- cli()
diff --git a/buildstream/_artifact.py b/buildstream/_artifact.py
deleted file mode 100644
index c353a5151..000000000
--- a/buildstream/_artifact.py
+++ /dev/null
@@ -1,449 +0,0 @@
-#
-# Copyright (C) 2019 Codethink Limited
-# Copyright (C) 2019 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:
-# Tom Pollard <tom.pollard@codethink.co.uk>
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-"""
-Artifact
-=========
-
-Implementation of the Artifact class which aims to 'abstract' direct
-artifact composite interaction away from Element class
-
-"""
-
-import os
-import tempfile
-
-from ._protos.buildstream.v2.artifact_pb2 import Artifact as ArtifactProto
-from . import _yaml
-from . import utils
-from .types import Scope
-from .storage._casbaseddirectory import CasBasedDirectory
-
-
-# An Artifact class to abtract artifact operations
-# from the Element class
-#
-# Args:
-# element (Element): The Element object
-# context (Context): The BuildStream context
-# strong_key (str): The elements strong cache key, dependant on context
-# weak_key (str): The elements weak cache key
-#
-class Artifact():
-
- version = 0
-
- def __init__(self, element, context, *, strong_key=None, weak_key=None):
- self._element = element
- self._context = context
- self._artifacts = context.artifactcache
- self._cache_key = strong_key
- self._weak_cache_key = weak_key
- self._artifactdir = context.artifactdir
- self._cas = context.get_cascache()
- self._tmpdir = context.tmpdir
- self._proto = None
-
- self._metadata_keys = None # Strong and weak key tuple extracted from the artifact
- self._metadata_dependencies = None # Dictionary of dependency strong keys from the artifact
- self._metadata_workspaced = None # Boolean of whether it's a workspaced artifact
- self._metadata_workspaced_dependencies = None # List of which dependencies are workspaced from the artifact
- self._cached = None # Boolean of whether the artifact is cached
-
- # get_files():
- #
- # Get a virtual directory for the artifact files content
- #
- # Returns:
- # (Directory): The virtual directory object
- #
- def get_files(self):
- files_digest = self._get_field_digest("files")
-
- return CasBasedDirectory(self._cas, digest=files_digest)
-
- # get_buildtree():
- #
- # Get a virtual directory for the artifact buildtree content
- #
- # Returns:
- # (Directory): The virtual directory object
- #
- def get_buildtree(self):
- buildtree_digest = self._get_field_digest("buildtree")
-
- return CasBasedDirectory(self._cas, digest=buildtree_digest)
-
- # get_extract_key():
- #
- # Get the key used to extract the artifact
- #
- # Returns:
- # (str): The key
- #
- def get_extract_key(self):
- return self._cache_key or self._weak_cache_key
-
- # cache():
- #
- # Create the artifact and commit to cache
- #
- # Args:
- # rootdir (str): An absolute path to the temp rootdir for artifact construct
- # sandbox_build_dir (Directory): Virtual Directory object for the sandbox build-root
- # collectvdir (Directory): Virtual Directoy object from within the sandbox for collection
- # buildresult (tuple): bool, short desc and detailed desc of result
- # publicdata (dict): dict of public data to commit to artifact metadata
- #
- # Returns:
- # (int): The size of the newly cached artifact
- #
- def cache(self, rootdir, sandbox_build_dir, collectvdir, buildresult, publicdata):
-
- context = self._context
- element = self._element
- size = 0
-
- filesvdir = None
- buildtreevdir = None
-
- artifact = ArtifactProto()
-
- artifact.version = self.version
-
- # Store result
- artifact.build_success = buildresult[0]
- artifact.build_error = buildresult[1]
- artifact.build_error_details = "" if not buildresult[2] else buildresult[2]
-
- # Store keys
- artifact.strong_key = self._cache_key
- artifact.weak_key = self._weak_cache_key
-
- artifact.was_workspaced = bool(element._get_workspace())
-
- # Store files
- if collectvdir:
- filesvdir = CasBasedDirectory(cas_cache=self._cas)
- filesvdir.import_files(collectvdir)
- artifact.files.CopyFrom(filesvdir._get_digest())
- size += filesvdir.get_size()
-
- # Store public data
- with tempfile.NamedTemporaryFile(dir=self._tmpdir) as tmp:
- _yaml.dump(_yaml.node_sanitize(publicdata), tmp.name)
- public_data_digest = self._cas.add_object(path=tmp.name, link_directly=True)
- artifact.public_data.CopyFrom(public_data_digest)
- size += public_data_digest.size_bytes
-
- # store build dependencies
- for e in element.dependencies(Scope.BUILD):
- new_build = artifact.build_deps.add()
- new_build.element_name = e.name
- new_build.cache_key = e._get_cache_key()
- new_build.was_workspaced = bool(e._get_workspace())
-
- # Store log file
- log_filename = context.get_log_filename()
- if log_filename:
- digest = self._cas.add_object(path=log_filename)
- element._build_log_path = self._cas.objpath(digest)
- log = artifact.logs.add()
- log.name = os.path.basename(log_filename)
- log.digest.CopyFrom(digest)
- size += log.digest.size_bytes
-
- # Store build tree
- if sandbox_build_dir:
- buildtreevdir = CasBasedDirectory(cas_cache=self._cas)
- buildtreevdir.import_files(sandbox_build_dir)
- artifact.buildtree.CopyFrom(buildtreevdir._get_digest())
- size += buildtreevdir.get_size()
-
- os.makedirs(os.path.dirname(os.path.join(
- self._artifactdir, element.get_artifact_name())), exist_ok=True)
- keys = utils._deduplicate([self._cache_key, self._weak_cache_key])
- for key in keys:
- path = os.path.join(self._artifactdir, element.get_artifact_name(key=key))
- with open(path, mode='w+b') as f:
- f.write(artifact.SerializeToString())
-
- return size
-
- # cached_buildtree()
- #
- # Check if artifact is cached with expected buildtree. A
- # buildtree will not be present if the rest of the partial artifact
- # is not cached.
- #
- # Returns:
- # (bool): True if artifact cached with buildtree, False if
- # missing expected buildtree. Note this only confirms
- # if a buildtree is present, not its contents.
- #
- def cached_buildtree(self):
-
- buildtree_digest = self._get_field_digest("buildtree")
- if buildtree_digest:
- return self._cas.contains_directory(buildtree_digest, with_files=True)
- else:
- return False
-
- # buildtree_exists()
- #
- # Check if artifact was created with a buildtree. This does not check
- # whether the buildtree is present in the local cache.
- #
- # Returns:
- # (bool): True if artifact was created with buildtree
- #
- def buildtree_exists(self):
-
- artifact = self._get_proto()
- return bool(str(artifact.buildtree))
-
- # load_public_data():
- #
- # Loads the public data from the cached artifact
- #
- # Returns:
- # (dict): The artifacts cached public data
- #
- def load_public_data(self):
-
- # Load the public data from the artifact
- artifact = self._get_proto()
- meta_file = self._cas.objpath(artifact.public_data)
- data = _yaml.load(meta_file, shortname='public.yaml')
-
- return data
-
- # load_build_result():
- #
- # Load the build result from the cached artifact
- #
- # Returns:
- # (bool): Whether the artifact of this element present in the artifact cache is of a success
- # (str): Short description of the result
- # (str): Detailed description of the result
- #
- def load_build_result(self):
-
- artifact = self._get_proto()
- build_result = (artifact.build_success,
- artifact.build_error,
- artifact.build_error_details)
-
- return build_result
-
- # get_metadata_keys():
- #
- # Retrieve the strong and weak keys from the given artifact.
- #
- # Returns:
- # (str): The strong key
- # (str): The weak key
- #
- def get_metadata_keys(self):
-
- if self._metadata_keys is not None:
- return self._metadata_keys
-
- # Extract proto
- artifact = self._get_proto()
-
- strong_key = artifact.strong_key
- weak_key = artifact.weak_key
-
- self._metadata_keys = (strong_key, weak_key)
-
- return self._metadata_keys
-
- # get_metadata_dependencies():
- #
- # Retrieve the hash of dependency keys from the given artifact.
- #
- # Returns:
- # (dict): A dictionary of element names and their keys
- #
- def get_metadata_dependencies(self):
-
- if self._metadata_dependencies is not None:
- return self._metadata_dependencies
-
- # Extract proto
- artifact = self._get_proto()
-
- self._metadata_dependencies = {dep.element_name: dep.cache_key for dep in artifact.build_deps}
-
- return self._metadata_dependencies
-
- # get_metadata_workspaced():
- #
- # Retrieve the hash of dependency from the given artifact.
- #
- # Returns:
- # (bool): Whether the given artifact was workspaced
- #
- def get_metadata_workspaced(self):
-
- if self._metadata_workspaced is not None:
- return self._metadata_workspaced
-
- # Extract proto
- artifact = self._get_proto()
-
- self._metadata_workspaced = artifact.was_workspaced
-
- return self._metadata_workspaced
-
- # get_metadata_workspaced_dependencies():
- #
- # Retrieve the hash of workspaced dependencies keys from the given artifact.
- #
- # Returns:
- # (list): List of which dependencies are workspaced
- #
- def get_metadata_workspaced_dependencies(self):
-
- if self._metadata_workspaced_dependencies is not None:
- return self._metadata_workspaced_dependencies
-
- # Extract proto
- artifact = self._get_proto()
-
- self._metadata_workspaced_dependencies = [dep.element_name for dep in artifact.build_deps
- if dep.was_workspaced]
-
- return self._metadata_workspaced_dependencies
-
- # cached():
- #
- # Check whether the artifact corresponding to the stored cache key is
- # available. This also checks whether all required parts of the artifact
- # are available, which may depend on command and configuration. The cache
- # key used for querying is dependant on the current context.
- #
- # Returns:
- # (bool): Whether artifact is in local cache
- #
- def cached(self):
-
- if self._cached is not None:
- return self._cached
-
- context = self._context
-
- artifact = self._get_proto()
-
- if not artifact:
- self._cached = False
- return False
-
- # Determine whether directories are required
- require_directories = context.require_artifact_directories
- # Determine whether file contents are required as well
- require_files = (context.require_artifact_files or
- self._element._artifact_files_required())
-
- # Check whether 'files' subdirectory is available, with or without file contents
- if (require_directories and str(artifact.files) and
- not self._cas.contains_directory(artifact.files, with_files=require_files)):
- self._cached = False
- return False
-
- self._cached = True
- return True
-
- # cached_logs()
- #
- # Check if the artifact is cached with log files.
- #
- # Returns:
- # (bool): True if artifact is cached with logs, False if
- # element not cached or missing logs.
- #
- def cached_logs(self):
- if not self._element._cached():
- return False
-
- artifact = self._get_proto()
-
- for logfile in artifact.logs:
- if not self._cas.contains(logfile.digest.hash):
- return False
-
- return True
-
- # reset_cached()
- #
- # Allow the Artifact to query the filesystem to determine whether it
- # is cached or not.
- #
- # NOTE: Due to the fact that a normal buildstream run does not make an
- # artifact *not* cached (`bst artifact delete` can do so, but doesn't
- # query the Artifact afterwards), it does not update_cached if the
- # artifact is already cached. If a cached artifact ever has its key
- # changed, this will need to be revisited.
- #
- def reset_cached(self):
- if self._cached is False:
- self._cached = None
-
- # _get_proto()
- #
- # Returns:
- # (Artifact): Artifact proto
- #
- def _get_proto(self):
- # Check if we've already cached the proto object
- if self._proto is not None:
- return self._proto
-
- key = self.get_extract_key()
-
- proto_path = os.path.join(self._artifactdir,
- self._element.get_artifact_name(key=key))
- artifact = ArtifactProto()
- try:
- with open(proto_path, mode='r+b') as f:
- artifact.ParseFromString(f.read())
- except FileNotFoundError:
- return None
-
- os.utime(proto_path)
- # Cache the proto object
- self._proto = artifact
-
- return self._proto
-
- # _get_artifact_field()
- #
- # Returns:
- # (Digest): Digest of field specified
- #
- def _get_field_digest(self, field):
- artifact_proto = self._get_proto()
- digest = getattr(artifact_proto, field)
- if not str(digest):
- return None
-
- return digest
diff --git a/buildstream/_artifactcache.py b/buildstream/_artifactcache.py
deleted file mode 100644
index 091b44dda..000000000
--- a/buildstream/_artifactcache.py
+++ /dev/null
@@ -1,617 +0,0 @@
-#
-# Copyright (C) 2017-2018 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 Maat <tristan.maat@codethink.co.uk>
-
-import os
-import grpc
-
-from ._basecache import BaseCache
-from .types import _KeyStrength
-from ._exceptions import ArtifactError, CASError, CASCacheError
-from ._protos.buildstream.v2 import artifact_pb2, artifact_pb2_grpc
-
-from ._cas import CASRemoteSpec
-from .storage._casbaseddirectory import CasBasedDirectory
-from ._artifact import Artifact
-from . import utils
-
-
-# An ArtifactCacheSpec holds the user configuration for a single remote
-# artifact cache.
-#
-# Args:
-# url (str): Location of the remote artifact cache
-# push (bool): Whether we should attempt to push artifacts to this cache,
-# in addition to pulling from it.
-#
-class ArtifactCacheSpec(CASRemoteSpec):
- pass
-
-
-# An ArtifactCache manages artifacts.
-#
-# Args:
-# context (Context): The BuildStream context
-#
-class ArtifactCache(BaseCache):
-
- spec_class = ArtifactCacheSpec
- spec_name = "artifact_cache_specs"
- spec_error = ArtifactError
- config_node_name = "artifacts"
-
- def __init__(self, context):
- super().__init__(context)
-
- self._required_elements = set() # The elements required for this session
-
- # create artifact directory
- self.artifactdir = context.artifactdir
- os.makedirs(self.artifactdir, exist_ok=True)
-
- self.casquota.add_remove_callbacks(self.unrequired_artifacts, self.remove)
- self.casquota.add_list_refs_callback(self.list_artifacts)
-
- self.cas.add_reachable_directories_callback(self._reachable_directories)
- self.cas.add_reachable_digests_callback(self._reachable_digests)
-
- # mark_required_elements():
- #
- # Mark elements whose artifacts are required for the current run.
- #
- # Artifacts whose elements are in this list will be locked by the artifact
- # cache and not touched for the duration of the current pipeline.
- #
- # Args:
- # elements (iterable): A set of elements to mark as required
- #
- def mark_required_elements(self, elements):
-
- # We risk calling this function with a generator, so we
- # better consume it first.
- #
- elements = list(elements)
-
- # Mark the elements as required. We cannot know that we know the
- # cache keys yet, so we only check that later when deleting.
- #
- self._required_elements.update(elements)
-
- # For the cache keys which were resolved so far, we bump
- # the mtime of them.
- #
- # This is just in case we have concurrent instances of
- # BuildStream running with the same artifact cache, it will
- # reduce the likelyhood of one instance deleting artifacts
- # which are required by the other.
- for element in elements:
- strong_key = element._get_cache_key(strength=_KeyStrength.STRONG)
- weak_key = element._get_cache_key(strength=_KeyStrength.WEAK)
- for key in (strong_key, weak_key):
- if key:
- ref = element.get_artifact_name(key)
-
- try:
- self.update_mtime(ref)
- except ArtifactError:
- pass
-
- def update_mtime(self, ref):
- try:
- os.utime(os.path.join(self.artifactdir, ref))
- except FileNotFoundError as e:
- raise ArtifactError("Couldn't find artifact: {}".format(ref)) from e
-
- # unrequired_artifacts()
- #
- # Returns iterator over artifacts that are not required in the build plan
- #
- # Returns:
- # (iter): Iterator over tuples of (float, str) where float is the time
- # and str is the artifact ref
- #
- def unrequired_artifacts(self):
- required_artifacts = set(map(lambda x: x.get_artifact_name(),
- self._required_elements))
- for (mtime, artifact) in self._list_refs_mtimes(self.artifactdir):
- if artifact not in required_artifacts:
- yield (mtime, artifact)
-
- def required_artifacts(self):
- # Build a set of the cache keys which are required
- # based on the required elements at cleanup time
- #
- # We lock both strong and weak keys - deleting one but not the
- # other won't save space, but would be a user inconvenience.
- for element in self._required_elements:
- yield element._get_cache_key(strength=_KeyStrength.STRONG)
- yield element._get_cache_key(strength=_KeyStrength.WEAK)
-
- def full(self):
- return self.casquota.full()
-
- # add_artifact_size()
- #
- # Adds the reported size of a newly cached artifact to the
- # overall estimated size.
- #
- # Args:
- # artifact_size (int): The size to add.
- #
- def add_artifact_size(self, artifact_size):
- cache_size = self.casquota.get_cache_size()
- cache_size += artifact_size
-
- self.casquota.set_cache_size(cache_size)
-
- # preflight():
- #
- # Preflight check.
- #
- def preflight(self):
- self.cas.preflight()
-
- # contains():
- #
- # Check whether the artifact for the specified Element is already available
- # in the local artifact cache.
- #
- # Args:
- # element (Element): The Element to check
- # key (str): The cache key to use
- #
- # Returns: True if the artifact is in the cache, False otherwise
- #
- def contains(self, element, key):
- ref = element.get_artifact_name(key)
-
- return os.path.exists(os.path.join(self.artifactdir, ref))
-
- # list_artifacts():
- #
- # List artifacts in this cache in LRU order.
- #
- # Args:
- # glob (str): An option glob expression to be used to list artifacts satisfying the glob
- #
- # Returns:
- # ([str]) - A list of artifact names as generated in LRU order
- #
- def list_artifacts(self, *, glob=None):
- return [ref for _, ref in sorted(list(self._list_refs_mtimes(self.artifactdir, glob_expr=glob)))]
-
- # remove():
- #
- # Removes the artifact for the specified ref from the local
- # artifact cache.
- #
- # Args:
- # ref (artifact_name): The name of the artifact to remove (as
- # generated by `Element.get_artifact_name`)
- # defer_prune (bool): Optionally declare whether pruning should
- # occur immediately after the ref is removed.
- #
- # Returns:
- # (int): The amount of space recovered in the cache, in bytes
- #
- def remove(self, ref, *, defer_prune=False):
- try:
- return self.cas.remove(ref, basedir=self.artifactdir, defer_prune=defer_prune)
- except CASCacheError as e:
- raise ArtifactError("{}".format(e)) from e
-
- # prune():
- #
- # Prune the artifact cache of unreachable refs
- #
- def prune(self):
- return self.cas.prune()
-
- # diff():
- #
- # Return a list of files that have been added or modified between
- # the artifacts described by key_a and key_b. This expects the
- # provided keys to be strong cache keys
- #
- # Args:
- # element (Element): The element whose artifacts to compare
- # key_a (str): The first artifact strong key
- # key_b (str): The second artifact strong key
- #
- def diff(self, element, key_a, key_b):
- context = self.context
- artifact_a = Artifact(element, context, strong_key=key_a)
- artifact_b = Artifact(element, context, strong_key=key_b)
- digest_a = artifact_a._get_proto().files
- digest_b = artifact_b._get_proto().files
-
- added = []
- removed = []
- modified = []
-
- self.cas.diff_trees(digest_a, digest_b, added=added, removed=removed, modified=modified)
-
- return modified, removed, added
-
- # push():
- #
- # Push committed artifact to remote repository.
- #
- # Args:
- # element (Element): The Element whose artifact is to be pushed
- # artifact (Artifact): The artifact being pushed
- #
- # Returns:
- # (bool): True if any remote was updated, False if no pushes were required
- #
- # Raises:
- # (ArtifactError): if there was an error
- #
- def push(self, element, artifact):
- project = element._get_project()
-
- push_remotes = [r for r in self._remotes[project] if r.spec.push]
-
- pushed = False
-
- for remote in push_remotes:
- remote.init()
- display_key = element._get_brief_display_key()
- element.status("Pushing artifact {} -> {}".format(display_key, remote.spec.url))
-
- if self._push_artifact(element, artifact, remote):
- element.info("Pushed artifact {} -> {}".format(display_key, remote.spec.url))
- pushed = True
- else:
- element.info("Remote ({}) already has artifact {} cached".format(
- remote.spec.url, element._get_brief_display_key()
- ))
-
- return pushed
-
- # pull():
- #
- # Pull artifact from one of the configured remote repositories.
- #
- # Args:
- # element (Element): The Element whose artifact is to be fetched
- # key (str): The cache key to use
- # pull_buildtrees (bool): Whether to pull buildtrees or not
- #
- # Returns:
- # (bool): True if pull was successful, False if artifact was not available
- #
- def pull(self, element, key, *, pull_buildtrees=False):
- display_key = key[:self.context.log_key_length]
- project = element._get_project()
-
- for remote in self._remotes[project]:
- remote.init()
- try:
- element.status("Pulling artifact {} <- {}".format(display_key, remote.spec.url))
-
- if self._pull_artifact(element, key, remote, pull_buildtrees=pull_buildtrees):
- element.info("Pulled artifact {} <- {}".format(display_key, remote.spec.url))
- # no need to pull from additional remotes
- return True
- else:
- element.info("Remote ({}) does not have artifact {} cached".format(
- remote.spec.url, display_key
- ))
-
- except CASError as e:
- raise ArtifactError("Failed to pull artifact {}: {}".format(
- display_key, e)) from e
-
- return False
-
- # pull_tree():
- #
- # Pull a single Tree rather than an artifact.
- # Does not update local refs.
- #
- # Args:
- # project (Project): The current project
- # digest (Digest): The digest of the tree
- #
- def pull_tree(self, project, digest):
- for remote in self._remotes[project]:
- digest = self.cas.pull_tree(remote, digest)
-
- if digest:
- # no need to pull from additional remotes
- return digest
-
- return None
-
- # push_message():
- #
- # Push the given protobuf message to all remotes.
- #
- # Args:
- # project (Project): The current project
- # message (Message): A protobuf message to push.
- #
- # Raises:
- # (ArtifactError): if there was an error
- #
- def push_message(self, project, message):
-
- if self._has_push_remotes:
- push_remotes = [r for r in self._remotes[project] if r.spec.push]
- else:
- push_remotes = []
-
- if not push_remotes:
- raise ArtifactError("push_message was called, but no remote artifact " +
- "servers are configured as push remotes.")
-
- for remote in push_remotes:
- message_digest = remote.push_message(message)
-
- return message_digest
-
- # link_key():
- #
- # Add a key for an existing artifact.
- #
- # Args:
- # element (Element): The Element whose artifact is to be linked
- # oldkey (str): An existing cache key for the artifact
- # newkey (str): A new cache key for the artifact
- #
- def link_key(self, element, oldkey, newkey):
- oldref = element.get_artifact_name(oldkey)
- newref = element.get_artifact_name(newkey)
-
- if not os.path.exists(os.path.join(self.artifactdir, newref)):
- os.link(os.path.join(self.artifactdir, oldref),
- os.path.join(self.artifactdir, newref))
-
- # get_artifact_logs():
- #
- # Get the logs of an existing artifact
- #
- # Args:
- # ref (str): The ref of the artifact
- #
- # Returns:
- # logsdir (CasBasedDirectory): A CasBasedDirectory containing the artifact's logs
- #
- def get_artifact_logs(self, ref):
- cache_id = self.cas.resolve_ref(ref, update_mtime=True)
- vdir = CasBasedDirectory(self.cas, digest=cache_id).descend('logs')
- return vdir
-
- # fetch_missing_blobs():
- #
- # Fetch missing blobs from configured remote repositories.
- #
- # Args:
- # project (Project): The current project
- # missing_blobs (list): The Digests of the blobs to fetch
- #
- def fetch_missing_blobs(self, project, missing_blobs):
- for remote in self._remotes[project]:
- if not missing_blobs:
- break
-
- remote.init()
-
- # fetch_blobs() will return the blobs that are still missing
- missing_blobs = self.cas.fetch_blobs(remote, missing_blobs)
-
- if missing_blobs:
- raise ArtifactError("Blobs not found on configured artifact servers")
-
- # find_missing_blobs():
- #
- # Find missing blobs from configured push remote repositories.
- #
- # Args:
- # project (Project): The current project
- # missing_blobs (list): The Digests of the blobs to check
- #
- # Returns:
- # (list): The Digests of the blobs missing on at least one push remote
- #
- def find_missing_blobs(self, project, missing_blobs):
- if not missing_blobs:
- return []
-
- push_remotes = [r for r in self._remotes[project] if r.spec.push]
-
- remote_missing_blobs_set = set()
-
- for remote in push_remotes:
- remote.init()
-
- remote_missing_blobs = self.cas.remote_missing_blobs(remote, missing_blobs)
- remote_missing_blobs_set.update(remote_missing_blobs)
-
- return list(remote_missing_blobs_set)
-
- ################################################
- # Local Private Methods #
- ################################################
-
- # _reachable_directories()
- #
- # Returns:
- # (iter): Iterator over directories digests available from artifacts.
- #
- def _reachable_directories(self):
- for root, _, files in os.walk(self.artifactdir):
- for artifact_file in files:
- artifact = artifact_pb2.Artifact()
- with open(os.path.join(root, artifact_file), 'r+b') as f:
- artifact.ParseFromString(f.read())
-
- if str(artifact.files):
- yield artifact.files
-
- if str(artifact.buildtree):
- yield artifact.buildtree
-
- # _reachable_digests()
- #
- # Returns:
- # (iter): Iterator over single file digests in artifacts
- #
- def _reachable_digests(self):
- for root, _, files in os.walk(self.artifactdir):
- for artifact_file in files:
- artifact = artifact_pb2.Artifact()
- with open(os.path.join(root, artifact_file), 'r+b') as f:
- artifact.ParseFromString(f.read())
-
- if str(artifact.public_data):
- yield artifact.public_data
-
- for log_file in artifact.logs:
- yield log_file.digest
-
- # _push_artifact()
- #
- # Pushes relevant directories and then artifact proto to remote.
- #
- # Args:
- # element (Element): The element
- # artifact (Artifact): The related artifact being pushed
- # remote (CASRemote): Remote to push to
- #
- # Returns:
- # (bool): whether the push was successful
- #
- def _push_artifact(self, element, artifact, remote):
-
- artifact_proto = artifact._get_proto()
-
- keys = list(utils._deduplicate([artifact_proto.strong_key, artifact_proto.weak_key]))
-
- # Check whether the artifact is on the server
- present = False
- for key in keys:
- get_artifact = artifact_pb2.GetArtifactRequest()
- get_artifact.cache_key = element.get_artifact_name(key)
- try:
- artifact_service = artifact_pb2_grpc.ArtifactServiceStub(remote.channel)
- artifact_service.GetArtifact(get_artifact)
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.NOT_FOUND:
- raise ArtifactError("Error checking artifact cache: {}"
- .format(e.details()))
- else:
- present = True
- if present:
- return False
-
- try:
- self.cas._send_directory(remote, artifact_proto.files)
-
- if str(artifact_proto.buildtree):
- try:
- self.cas._send_directory(remote, artifact_proto.buildtree)
- except FileNotFoundError:
- pass
-
- digests = []
- if str(artifact_proto.public_data):
- digests.append(artifact_proto.public_data)
-
- for log_file in artifact_proto.logs:
- digests.append(log_file.digest)
-
- self.cas.send_blobs(remote, digests)
-
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.RESOURCE_EXHAUSTED:
- raise ArtifactError("Failed to push artifact blobs: {}".format(e.details()))
- return False
-
- # finally need to send the artifact proto
- for key in keys:
- update_artifact = artifact_pb2.UpdateArtifactRequest()
- update_artifact.cache_key = element.get_artifact_name(key)
- update_artifact.artifact.CopyFrom(artifact_proto)
-
- try:
- artifact_service = artifact_pb2_grpc.ArtifactServiceStub(remote.channel)
- artifact_service.UpdateArtifact(update_artifact)
- except grpc.RpcError as e:
- raise ArtifactError("Failed to push artifact: {}".format(e.details()))
-
- return True
-
- # _pull_artifact()
- #
- # Args:
- # element (Element): element to pull
- # key (str): specific key of element to pull
- # remote (CASRemote): remote to pull from
- # pull_buildtree (bool): whether to pull buildtrees or not
- #
- # Returns:
- # (bool): whether the pull was successful
- #
- def _pull_artifact(self, element, key, remote, pull_buildtrees=False):
-
- def __pull_digest(digest):
- self.cas._fetch_directory(remote, digest)
- required_blobs = self.cas.required_blobs_for_directory(digest)
- missing_blobs = self.cas.local_missing_blobs(required_blobs)
- if missing_blobs:
- self.cas.fetch_blobs(remote, missing_blobs)
-
- request = artifact_pb2.GetArtifactRequest()
- request.cache_key = element.get_artifact_name(key=key)
- try:
- artifact_service = artifact_pb2_grpc.ArtifactServiceStub(remote.channel)
- artifact = artifact_service.GetArtifact(request)
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.NOT_FOUND:
- raise ArtifactError("Failed to pull artifact: {}".format(e.details()))
- return False
-
- try:
- if str(artifact.files):
- __pull_digest(artifact.files)
-
- if pull_buildtrees and str(artifact.buildtree):
- __pull_digest(artifact.buildtree)
-
- digests = []
- if str(artifact.public_data):
- digests.append(artifact.public_data)
-
- for log_digest in artifact.logs:
- digests.append(log_digest.digest)
-
- self.cas.fetch_blobs(remote, digests)
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.NOT_FOUND:
- raise ArtifactError("Failed to pull artifact: {}".format(e.details()))
- return False
-
- # Write the artifact proto to cache
- artifact_path = os.path.join(self.artifactdir, request.cache_key)
- os.makedirs(os.path.dirname(artifact_path), exist_ok=True)
- with open(artifact_path, 'w+b') as f:
- f.write(artifact.SerializeToString())
-
- return True
diff --git a/buildstream/_artifactelement.py b/buildstream/_artifactelement.py
deleted file mode 100644
index d65d46173..000000000
--- a/buildstream/_artifactelement.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#
-# Copyright (C) 2019 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:
-# James Ennis <james.ennis@codethink.co.uk>
-from . import Element
-from . import _cachekey
-from ._exceptions import ArtifactElementError
-from ._loader.metaelement import MetaElement
-
-
-# ArtifactElement()
-#
-# Object to be used for directly processing an artifact
-#
-# Args:
-# context (Context): The Context object
-# ref (str): The artifact ref
-#
-class ArtifactElement(Element):
- def __init__(self, context, ref):
- _, element, key = verify_artifact_ref(ref)
-
- self._ref = ref
- self._key = key
-
- project = context.get_toplevel_project()
- meta = MetaElement(project, element) # NOTE element has no .bst suffix
- plugin_conf = None
-
- super().__init__(context, project, meta, plugin_conf)
-
- # Override Element.get_artifact_name()
- def get_artifact_name(self, key=None):
- return self._ref
-
- # Dummy configure method
- def configure(self, node):
- pass
-
- # Dummy preflight method
- def preflight(self):
- pass
-
- # Override Element._calculate_cache_key
- def _calculate_cache_key(self, dependencies=None):
- return self._key
-
- # Override Element._get_cache_key()
- def _get_cache_key(self, strength=None):
- return self._key
-
-
-# verify_artifact_ref()
-#
-# Verify that a ref string matches the format of an artifact
-#
-# Args:
-# ref (str): The artifact ref
-#
-# Returns:
-# project (str): The project's name
-# element (str): The element's name
-# key (str): The cache key
-#
-# Raises:
-# ArtifactElementError if the ref string does not match
-# the expected format
-#
-def verify_artifact_ref(ref):
- try:
- project, element, key = ref.split('/', 2) # This will raise a Value error if unable to split
- # Explicitly raise a ValueError if the key length is not as expected
- if not _cachekey.is_key(key):
- raise ValueError
- except ValueError:
- raise ArtifactElementError("Artifact: {} is not of the expected format".format(ref))
-
- return project, element, key
diff --git a/buildstream/_basecache.py b/buildstream/_basecache.py
deleted file mode 100644
index 68654b2a0..000000000
--- a/buildstream/_basecache.py
+++ /dev/null
@@ -1,307 +0,0 @@
-# Copyright (C) 2019 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:
-# Raoul Hidalgo Charman <raoul.hidalgocharman@codethink.co.uk>
-#
-import multiprocessing
-import os
-from fnmatch import fnmatch
-
-from . import utils
-from . import _yaml
-from ._cas import CASRemote
-from ._message import Message, MessageType
-from ._exceptions import LoadError
-
-
-# Base Cache for Caches to derive from
-#
-class BaseCache():
-
- # None of these should ever be called in the base class, but this appeases
- # pylint to some degree
- spec_class = None
- spec_name = None
- spec_error = None
- config_node_name = None
-
- def __init__(self, context):
- self.context = context
- self.cas = context.get_cascache()
- self.casquota = context.get_casquota()
- self.casquota._calculate_cache_quota()
-
- self._remotes_setup = False # Check to prevent double-setup of remotes
- # Per-project list of _CASRemote instances.
- self._remotes = {}
-
- self.global_remote_specs = []
- self.project_remote_specs = {}
-
- self._has_fetch_remotes = False
- self._has_push_remotes = False
-
- # specs_from_config_node()
- #
- # Parses the configuration of remote artifact caches from a config block.
- #
- # Args:
- # config_node (dict): The config block, which may contain the 'artifacts' key
- # basedir (str): The base directory for relative paths
- #
- # Returns:
- # A list of ArtifactCacheSpec instances.
- #
- # Raises:
- # LoadError, if the config block contains invalid keys.
- #
- @classmethod
- def specs_from_config_node(cls, config_node, basedir=None):
- cache_specs = []
-
- try:
- artifacts = [_yaml.node_get(config_node, dict, cls.config_node_name)]
- except LoadError:
- try:
- artifacts = _yaml.node_get(config_node, list, cls.config_node_name, default_value=[])
- except LoadError:
- provenance = _yaml.node_get_provenance(config_node, key=cls.config_node_name)
- raise _yaml.LoadError(_yaml.LoadErrorReason.INVALID_DATA,
- "%s: 'artifacts' must be a single 'url:' mapping, or a list of mappings" %
- (str(provenance)))
-
- for spec_node in artifacts:
- cache_specs.append(cls.spec_class._new_from_config_node(spec_node, basedir))
-
- return cache_specs
-
- # _configured_remote_cache_specs():
- #
- # Return the list of configured remotes for a given project, in priority
- # order. This takes into account the user and project configuration.
- #
- # Args:
- # context (Context): The BuildStream context
- # project (Project): The BuildStream project
- #
- # Returns:
- # A list of ArtifactCacheSpec instances describing the remote artifact caches.
- #
- @classmethod
- def _configured_remote_cache_specs(cls, context, project):
- project_overrides = context.get_overrides(project.name)
- project_extra_specs = cls.specs_from_config_node(project_overrides)
-
- project_specs = getattr(project, cls.spec_name)
- context_specs = getattr(context, cls.spec_name)
-
- return list(utils._deduplicate(
- project_extra_specs + project_specs + context_specs))
-
- # setup_remotes():
- #
- # Sets up which remotes to use
- #
- # Args:
- # use_config (bool): Whether to use project configuration
- # remote_url (str): Remote cache URL
- #
- # This requires that all of the projects which are to be processed in the session
- # have already been loaded and are observable in the Context.
- #
- def setup_remotes(self, *, use_config=False, remote_url=None):
-
- # Ensure we do not double-initialise since this can be expensive
- assert not self._remotes_setup
- self._remotes_setup = True
-
- # Initialize remote caches. We allow the commandline to override
- # the user config in some cases (for example `bst artifact push --remote=...`).
- has_remote_caches = False
- if remote_url:
- # pylint: disable=not-callable
- self._set_remotes([self.spec_class(remote_url, push=True)])
- has_remote_caches = True
- if use_config:
- for project in self.context.get_projects():
- caches = self._configured_remote_cache_specs(self.context, project)
- if caches: # caches is a list of spec_class instances
- self._set_remotes(caches, project=project)
- has_remote_caches = True
- if has_remote_caches:
- self._initialize_remotes()
-
- # initialize_remotes():
- #
- # This will contact each remote cache.
- #
- # Args:
- # on_failure (callable): Called if we fail to contact one of the caches.
- #
- def initialize_remotes(self, *, on_failure=None):
- remote_specs = self.global_remote_specs
-
- for project in self.project_remote_specs:
- remote_specs += self.project_remote_specs[project]
-
- remote_specs = list(utils._deduplicate(remote_specs))
-
- remotes = {}
- q = multiprocessing.Queue()
- for remote_spec in remote_specs:
-
- error = CASRemote.check_remote(remote_spec, q)
-
- if error and on_failure:
- on_failure(remote_spec.url, error)
- elif error:
- raise self.spec_error(error) # pylint: disable=not-callable
- else:
- self._has_fetch_remotes = True
- if remote_spec.push:
- self._has_push_remotes = True
-
- remotes[remote_spec.url] = CASRemote(remote_spec)
-
- for project in self.context.get_projects():
- remote_specs = self.global_remote_specs
- if project in self.project_remote_specs:
- remote_specs = list(utils._deduplicate(remote_specs + self.project_remote_specs[project]))
-
- project_remotes = []
-
- for remote_spec in remote_specs:
- # Errors are already handled in the loop above,
- # skip unreachable remotes here.
- if remote_spec.url not in remotes:
- continue
-
- remote = remotes[remote_spec.url]
- project_remotes.append(remote)
-
- self._remotes[project] = project_remotes
-
- # has_fetch_remotes():
- #
- # Check whether any remote repositories are available for fetching.
- #
- # Args:
- # plugin (Plugin): The Plugin to check
- #
- # Returns: True if any remote repositories are configured, False otherwise
- #
- def has_fetch_remotes(self, *, plugin=None):
- if not self._has_fetch_remotes:
- # No project has fetch remotes
- return False
- elif plugin is None:
- # At least one (sub)project has fetch remotes
- return True
- else:
- # Check whether the specified element's project has fetch remotes
- remotes_for_project = self._remotes[plugin._get_project()]
- return bool(remotes_for_project)
-
- # has_push_remotes():
- #
- # Check whether any remote repositories are available for pushing.
- #
- # Args:
- # element (Element): The Element to check
- #
- # Returns: True if any remote repository is configured, False otherwise
- #
- def has_push_remotes(self, *, plugin=None):
- if not self._has_push_remotes:
- # No project has push remotes
- return False
- elif plugin is None:
- # At least one (sub)project has push remotes
- return True
- else:
- # Check whether the specified element's project has push remotes
- remotes_for_project = self._remotes[plugin._get_project()]
- return any(remote.spec.push for remote in remotes_for_project)
-
- ################################################
- # Local Private Methods #
- ################################################
-
- # _message()
- #
- # Local message propagator
- #
- def _message(self, message_type, message, **kwargs):
- args = dict(kwargs)
- self.context.message(
- Message(None, message_type, message, **args))
-
- # _set_remotes():
- #
- # Set the list of remote caches. If project is None, the global list of
- # remote caches will be set, which is used by all projects. If a project is
- # specified, the per-project list of remote caches will be set.
- #
- # Args:
- # remote_specs (list): List of ArtifactCacheSpec instances, in priority order.
- # project (Project): The Project instance for project-specific remotes
- def _set_remotes(self, remote_specs, *, project=None):
- if project is None:
- # global remotes
- self.global_remote_specs = remote_specs
- else:
- self.project_remote_specs[project] = remote_specs
-
- # _initialize_remotes()
- #
- # An internal wrapper which calls the abstract method and
- # reports takes care of messaging
- #
- def _initialize_remotes(self):
- def remote_failed(url, error):
- self._message(MessageType.WARN, "Failed to initialize remote {}: {}".format(url, error))
-
- with self.context.timed_activity("Initializing remote caches", silent_nested=True):
- self.initialize_remotes(on_failure=remote_failed)
-
- # _list_refs_mtimes()
- #
- # List refs in a directory, given a base path. Also returns the
- # associated mtimes
- #
- # Args:
- # base_path (str): Base path to traverse over
- # glob_expr (str|None): Optional glob expression to match against files
- #
- # Returns:
- # (iter (mtime, filename)]): iterator of tuples of mtime and refs
- #
- def _list_refs_mtimes(self, base_path, *, glob_expr=None):
- path = base_path
- if glob_expr is not None:
- globdir = os.path.dirname(glob_expr)
- if not any(c in "*?[" for c in globdir):
- # path prefix contains no globbing characters so
- # append the glob to optimise the os.walk()
- path = os.path.join(base_path, globdir)
-
- for root, _, files in os.walk(path):
- for filename in files:
- ref_path = os.path.join(root, filename)
- relative_path = os.path.relpath(ref_path, base_path) # Relative to refs head
- if not glob_expr or fnmatch(relative_path, glob_expr):
- # Obtain the mtime (the time a file was last modified)
- yield (os.path.getmtime(ref_path), relative_path)
diff --git a/buildstream/_cachekey.py b/buildstream/_cachekey.py
deleted file mode 100644
index e56b582fa..000000000
--- a/buildstream/_cachekey.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# Copyright (C) 2018 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>
-
-
-import hashlib
-
-import ujson
-
-from . import _yaml
-
-# Internal record of the size of a cache key
-_CACHEKEY_SIZE = len(hashlib.sha256().hexdigest())
-
-
-# Hex digits
-_HEX_DIGITS = "0123456789abcdef"
-
-
-# is_key()
-#
-# Check if the passed in string *could be* a cache key. This basically checks
-# that the length matches a sha256 hex digest, and that the string does not
-# contain any non-hex characters and is fully lower case.
-#
-# Args:
-# key (str): The string to check
-#
-# Returns:
-# (bool): Whether or not `key` could be a cache key
-#
-def is_key(key):
- if len(key) != _CACHEKEY_SIZE:
- return False
- return not any(ch not in _HEX_DIGITS for ch in key)
-
-
-# generate_key()
-#
-# Generate an sha256 hex digest from the given value. The value
-# can be a simple value or recursive dictionary with lists etc,
-# anything simple enough to serialize.
-#
-# Args:
-# value: A value to get a key for
-#
-# Returns:
-# (str): An sha256 hex digest of the given value
-#
-def generate_key(value):
- ordered = _yaml.node_sanitize(value)
- ustring = ujson.dumps(ordered, sort_keys=True, escape_forward_slashes=False).encode('utf-8')
- return hashlib.sha256(ustring).hexdigest()
diff --git a/buildstream/_cas/__init__.py b/buildstream/_cas/__init__.py
deleted file mode 100644
index 46bd9567f..000000000
--- a/buildstream/_cas/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Copyright (C) 2017-2018 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>
-
-from .cascache import CASCache, CASQuota, CASCacheUsage
-from .casremote import CASRemote, CASRemoteSpec
diff --git a/buildstream/_cas/cascache.py b/buildstream/_cas/cascache.py
deleted file mode 100644
index ad8013d18..000000000
--- a/buildstream/_cas/cascache.py
+++ /dev/null
@@ -1,1462 +0,0 @@
-#
-# Copyright (C) 2018 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:
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-import hashlib
-import itertools
-import os
-import stat
-import errno
-import uuid
-import contextlib
-
-import grpc
-
-from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
-from .._protos.buildstream.v2 import buildstream_pb2
-
-from .. import utils
-from .._exceptions import CASCacheError, LoadError, LoadErrorReason
-from .._message import Message, MessageType
-
-from .casremote import BlobNotFound, _CASBatchRead, _CASBatchUpdate
-
-_BUFFER_SIZE = 65536
-
-
-CACHE_SIZE_FILE = "cache_size"
-
-
-# CASCacheUsage
-#
-# A simple object to report the current CAS cache usage details.
-#
-# Note that this uses the user configured cache quota
-# rather than the internal quota with protective headroom
-# removed, to provide a more sensible value to display to
-# the user.
-#
-# Args:
-# cas (CASQuota): The CAS cache to get the status of
-#
-class CASCacheUsage():
-
- def __init__(self, casquota):
- self.quota_config = casquota._config_cache_quota # Configured quota
- self.quota_size = casquota._cache_quota_original # Resolved cache quota in bytes
- self.used_size = casquota.get_cache_size() # Size used by artifacts in bytes
- self.used_percent = 0 # Percentage of the quota used
- if self.quota_size is not None:
- self.used_percent = int(self.used_size * 100 / self.quota_size)
-
- # Formattable into a human readable string
- #
- def __str__(self):
- return "{} / {} ({}%)" \
- .format(utils._pretty_size(self.used_size, dec_places=1),
- self.quota_config,
- self.used_percent)
-
-
-# A CASCache manages a CAS repository as specified in the Remote Execution API.
-#
-# Args:
-# path (str): The root directory for the CAS repository
-# cache_quota (int): User configured cache quota
-#
-class CASCache():
-
- def __init__(self, path):
- self.casdir = os.path.join(path, 'cas')
- self.tmpdir = os.path.join(path, 'tmp')
- os.makedirs(os.path.join(self.casdir, 'refs', 'heads'), exist_ok=True)
- os.makedirs(os.path.join(self.casdir, 'objects'), exist_ok=True)
- os.makedirs(self.tmpdir, exist_ok=True)
-
- self.__reachable_directory_callbacks = []
- self.__reachable_digest_callbacks = []
-
- # preflight():
- #
- # Preflight check.
- #
- def preflight(self):
- headdir = os.path.join(self.casdir, 'refs', 'heads')
- objdir = os.path.join(self.casdir, 'objects')
- if not (os.path.isdir(headdir) and os.path.isdir(objdir)):
- raise CASCacheError("CAS repository check failed for '{}'".format(self.casdir))
-
- # contains():
- #
- # Check whether the specified ref is already available in the local CAS cache.
- #
- # Args:
- # ref (str): The ref to check
- #
- # Returns: True if the ref is in the cache, False otherwise
- #
- def contains(self, ref):
- refpath = self._refpath(ref)
-
- # This assumes that the repository doesn't have any dangling pointers
- return os.path.exists(refpath)
-
- # contains_directory():
- #
- # Check whether the specified directory and subdirecotires are in the cache,
- # i.e non dangling.
- #
- # Args:
- # digest (Digest): The directory digest to check
- # with_files (bool): Whether to check files as well
- #
- # Returns: True if the directory is available in the local cache
- #
- def contains_directory(self, digest, *, with_files):
- try:
- directory = remote_execution_pb2.Directory()
- with open(self.objpath(digest), 'rb') as f:
- directory.ParseFromString(f.read())
-
- # Optionally check presence of files
- if with_files:
- for filenode in directory.files:
- if not os.path.exists(self.objpath(filenode.digest)):
- return False
-
- # Check subdirectories
- for dirnode in directory.directories:
- if not self.contains_directory(dirnode.digest, with_files=with_files):
- return False
-
- return True
- except FileNotFoundError:
- return False
-
- # checkout():
- #
- # Checkout the specified directory digest.
- #
- # Args:
- # dest (str): The destination path
- # tree (Digest): The directory digest to extract
- # can_link (bool): Whether we can create hard links in the destination
- #
- def checkout(self, dest, tree, *, can_link=False):
- os.makedirs(dest, exist_ok=True)
-
- directory = remote_execution_pb2.Directory()
-
- with open(self.objpath(tree), 'rb') as f:
- directory.ParseFromString(f.read())
-
- for filenode in directory.files:
- # regular file, create hardlink
- fullpath = os.path.join(dest, filenode.name)
- if can_link:
- utils.safe_link(self.objpath(filenode.digest), fullpath)
- else:
- utils.safe_copy(self.objpath(filenode.digest), fullpath)
-
- if filenode.is_executable:
- os.chmod(fullpath, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
- stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
-
- for dirnode in directory.directories:
- fullpath = os.path.join(dest, dirnode.name)
- self.checkout(fullpath, dirnode.digest, can_link=can_link)
-
- for symlinknode in directory.symlinks:
- # symlink
- fullpath = os.path.join(dest, symlinknode.name)
- os.symlink(symlinknode.target, fullpath)
-
- # commit():
- #
- # Commit directory to cache.
- #
- # Args:
- # refs (list): The refs to set
- # path (str): The directory to import
- #
- def commit(self, refs, path):
- tree = self._commit_directory(path)
-
- for ref in refs:
- self.set_ref(ref, tree)
-
- # diff():
- #
- # Return a list of files that have been added or modified between
- # the refs described by ref_a and ref_b.
- #
- # Args:
- # ref_a (str): The first ref
- # ref_b (str): The second ref
- # subdir (str): A subdirectory to limit the comparison to
- #
- def diff(self, ref_a, ref_b):
- tree_a = self.resolve_ref(ref_a)
- tree_b = self.resolve_ref(ref_b)
-
- added = []
- removed = []
- modified = []
-
- self.diff_trees(tree_a, tree_b, added=added, removed=removed, modified=modified)
-
- return modified, removed, added
-
- # pull():
- #
- # Pull a ref from a remote repository.
- #
- # Args:
- # ref (str): The ref to pull
- # remote (CASRemote): The remote repository to pull from
- #
- # Returns:
- # (bool): True if pull was successful, False if ref was not available
- #
- def pull(self, ref, remote):
- try:
- remote.init()
-
- request = buildstream_pb2.GetReferenceRequest(instance_name=remote.spec.instance_name)
- request.key = ref
- response = remote.ref_storage.GetReference(request)
-
- tree = response.digest
-
- # Fetch Directory objects
- self._fetch_directory(remote, tree)
-
- # Fetch files, excluded_subdirs determined in pullqueue
- required_blobs = self.required_blobs_for_directory(tree)
- missing_blobs = self.local_missing_blobs(required_blobs)
- if missing_blobs:
- self.fetch_blobs(remote, missing_blobs)
-
- self.set_ref(ref, tree)
-
- return True
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.NOT_FOUND:
- raise CASCacheError("Failed to pull ref {}: {}".format(ref, e)) from e
- else:
- return False
- except BlobNotFound as e:
- return False
-
- # pull_tree():
- #
- # Pull a single Tree rather than a ref.
- # Does not update local refs.
- #
- # Args:
- # remote (CASRemote): The remote to pull from
- # digest (Digest): The digest of the tree
- #
- def pull_tree(self, remote, digest):
- try:
- remote.init()
-
- digest = self._fetch_tree(remote, digest)
-
- return digest
-
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.NOT_FOUND:
- raise
-
- return None
-
- # link_ref():
- #
- # Add an alias for an existing ref.
- #
- # Args:
- # oldref (str): An existing ref
- # newref (str): A new ref for the same directory
- #
- def link_ref(self, oldref, newref):
- tree = self.resolve_ref(oldref)
-
- self.set_ref(newref, tree)
-
- # push():
- #
- # Push committed refs to remote repository.
- #
- # Args:
- # refs (list): The refs to push
- # remote (CASRemote): The remote to push to
- #
- # Returns:
- # (bool): True if any remote was updated, False if no pushes were required
- #
- # Raises:
- # (CASCacheError): if there was an error
- #
- def push(self, refs, remote):
- skipped_remote = True
- try:
- for ref in refs:
- tree = self.resolve_ref(ref)
-
- # Check whether ref is already on the server in which case
- # there is no need to push the ref
- try:
- request = buildstream_pb2.GetReferenceRequest(instance_name=remote.spec.instance_name)
- request.key = ref
- response = remote.ref_storage.GetReference(request)
-
- if response.digest.hash == tree.hash and response.digest.size_bytes == tree.size_bytes:
- # ref is already on the server with the same tree
- continue
-
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.NOT_FOUND:
- # Intentionally re-raise RpcError for outer except block.
- raise
-
- self._send_directory(remote, tree)
-
- request = buildstream_pb2.UpdateReferenceRequest(instance_name=remote.spec.instance_name)
- request.keys.append(ref)
- request.digest.CopyFrom(tree)
- remote.ref_storage.UpdateReference(request)
-
- skipped_remote = False
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.RESOURCE_EXHAUSTED:
- raise CASCacheError("Failed to push ref {}: {}".format(refs, e), temporary=True) from e
-
- return not skipped_remote
-
- # objpath():
- #
- # Return the path of an object based on its digest.
- #
- # Args:
- # digest (Digest): The digest of the object
- #
- # Returns:
- # (str): The path of the object
- #
- def objpath(self, digest):
- return os.path.join(self.casdir, 'objects', digest.hash[:2], digest.hash[2:])
-
- # add_object():
- #
- # Hash and write object to CAS.
- #
- # Args:
- # digest (Digest): An optional Digest object to populate
- # path (str): Path to file to add
- # buffer (bytes): Byte buffer to add
- # link_directly (bool): Whether file given by path can be linked
- #
- # Returns:
- # (Digest): The digest of the added object
- #
- # Either `path` or `buffer` must be passed, but not both.
- #
- def add_object(self, *, digest=None, path=None, buffer=None, link_directly=False):
- # Exactly one of the two parameters has to be specified
- assert (path is None) != (buffer is None)
-
- if digest is None:
- digest = remote_execution_pb2.Digest()
-
- try:
- h = hashlib.sha256()
- # Always write out new file to avoid corruption if input file is modified
- with contextlib.ExitStack() as stack:
- if path is not None and link_directly:
- tmp = stack.enter_context(open(path, 'rb'))
- for chunk in iter(lambda: tmp.read(_BUFFER_SIZE), b""):
- h.update(chunk)
- else:
- tmp = stack.enter_context(self._temporary_object())
-
- if path:
- with open(path, 'rb') as f:
- for chunk in iter(lambda: f.read(_BUFFER_SIZE), b""):
- h.update(chunk)
- tmp.write(chunk)
- else:
- h.update(buffer)
- tmp.write(buffer)
-
- tmp.flush()
-
- digest.hash = h.hexdigest()
- digest.size_bytes = os.fstat(tmp.fileno()).st_size
-
- # Place file at final location
- objpath = self.objpath(digest)
- os.makedirs(os.path.dirname(objpath), exist_ok=True)
- os.link(tmp.name, objpath)
-
- except FileExistsError as e:
- # We can ignore the failed link() if the object is already in the repo.
- pass
-
- except OSError as e:
- raise CASCacheError("Failed to hash object: {}".format(e)) from e
-
- return digest
-
- # set_ref():
- #
- # Create or replace a ref.
- #
- # Args:
- # ref (str): The name of the ref
- #
- def set_ref(self, ref, tree):
- refpath = self._refpath(ref)
- os.makedirs(os.path.dirname(refpath), exist_ok=True)
- with utils.save_file_atomic(refpath, 'wb', tempdir=self.tmpdir) as f:
- f.write(tree.SerializeToString())
-
- # resolve_ref():
- #
- # Resolve a ref to a digest.
- #
- # Args:
- # ref (str): The name of the ref
- # update_mtime (bool): Whether to update the mtime of the ref
- #
- # Returns:
- # (Digest): The digest stored in the ref
- #
- def resolve_ref(self, ref, *, update_mtime=False):
- refpath = self._refpath(ref)
-
- try:
- with open(refpath, 'rb') as f:
- if update_mtime:
- os.utime(refpath)
-
- digest = remote_execution_pb2.Digest()
- digest.ParseFromString(f.read())
- return digest
-
- except FileNotFoundError as e:
- raise CASCacheError("Attempt to access unavailable ref: {}".format(e)) from e
-
- # update_mtime()
- #
- # Update the mtime of a ref.
- #
- # Args:
- # ref (str): The ref to update
- #
- def update_mtime(self, ref):
- try:
- os.utime(self._refpath(ref))
- except FileNotFoundError as e:
- raise CASCacheError("Attempt to access unavailable ref: {}".format(e)) from e
-
- # list_objects():
- #
- # List cached objects in Least Recently Modified (LRM) order.
- #
- # Returns:
- # (list) - A list of objects and timestamps in LRM order
- #
- def list_objects(self):
- objs = []
- mtimes = []
-
- for root, _, files in os.walk(os.path.join(self.casdir, 'objects')):
- for filename in files:
- obj_path = os.path.join(root, filename)
- try:
- mtimes.append(os.path.getmtime(obj_path))
- except FileNotFoundError:
- pass
- else:
- objs.append(obj_path)
-
- # NOTE: Sorted will sort from earliest to latest, thus the
- # first element of this list will be the file modified earliest.
- return sorted(zip(mtimes, objs))
-
- def clean_up_refs_until(self, time):
- ref_heads = os.path.join(self.casdir, 'refs', 'heads')
-
- for root, _, files in os.walk(ref_heads):
- for filename in files:
- ref_path = os.path.join(root, filename)
- # Obtain the mtime (the time a file was last modified)
- if os.path.getmtime(ref_path) < time:
- os.unlink(ref_path)
-
- # remove():
- #
- # Removes the given symbolic ref from the repo.
- #
- # Args:
- # ref (str): A symbolic ref
- # basedir (str): Path of base directory the ref is in, defaults to
- # CAS refs heads
- # defer_prune (bool): Whether to defer pruning to the caller. NOTE:
- # The space won't be freed until you manually
- # call prune.
- #
- # Returns:
- # (int|None) The amount of space pruned from the repository in
- # Bytes, or None if defer_prune is True
- #
- def remove(self, ref, *, basedir=None, defer_prune=False):
-
- if basedir is None:
- basedir = os.path.join(self.casdir, 'refs', 'heads')
- # Remove cache ref
- self._remove_ref(ref, basedir)
-
- if not defer_prune:
- pruned = self.prune()
- return pruned
-
- return None
-
- # adds callback of iterator over reachable directory digests
- def add_reachable_directories_callback(self, callback):
- self.__reachable_directory_callbacks.append(callback)
-
- # adds callbacks of iterator over reachable file digests
- def add_reachable_digests_callback(self, callback):
- self.__reachable_digest_callbacks.append(callback)
-
- # prune():
- #
- # Prune unreachable objects from the repo.
- #
- def prune(self):
- ref_heads = os.path.join(self.casdir, 'refs', 'heads')
-
- pruned = 0
- reachable = set()
-
- # Check which objects are reachable
- for root, _, files in os.walk(ref_heads):
- for filename in files:
- ref_path = os.path.join(root, filename)
- ref = os.path.relpath(ref_path, ref_heads)
-
- tree = self.resolve_ref(ref)
- self._reachable_refs_dir(reachable, tree)
-
- # check callback directory digests that are reachable
- for digest_callback in self.__reachable_directory_callbacks:
- for digest in digest_callback():
- self._reachable_refs_dir(reachable, digest)
-
- # check callback file digests that are reachable
- for digest_callback in self.__reachable_digest_callbacks:
- for digest in digest_callback():
- reachable.add(digest.hash)
-
- # Prune unreachable objects
- for root, _, files in os.walk(os.path.join(self.casdir, 'objects')):
- for filename in files:
- objhash = os.path.basename(root) + filename
- if objhash not in reachable:
- obj_path = os.path.join(root, filename)
- pruned += os.stat(obj_path).st_size
- os.unlink(obj_path)
-
- return pruned
-
- def update_tree_mtime(self, tree):
- reachable = set()
- self._reachable_refs_dir(reachable, tree, update_mtime=True)
-
- # remote_missing_blobs_for_directory():
- #
- # Determine which blobs of a directory tree are missing on the remote.
- #
- # Args:
- # digest (Digest): The directory digest
- #
- # Returns: List of missing Digest objects
- #
- def remote_missing_blobs_for_directory(self, remote, digest):
- required_blobs = self.required_blobs_for_directory(digest)
-
- return self.remote_missing_blobs(remote, required_blobs)
-
- # remote_missing_blobs():
- #
- # Determine which blobs are missing on the remote.
- #
- # Args:
- # blobs (Digest): The directory digest
- #
- # Returns: List of missing Digest objects
- #
- def remote_missing_blobs(self, remote, blobs):
- missing_blobs = dict()
- # Limit size of FindMissingBlobs request
- for required_blobs_group in _grouper(blobs, 512):
- request = remote_execution_pb2.FindMissingBlobsRequest(instance_name=remote.spec.instance_name)
-
- for required_digest in required_blobs_group:
- d = request.blob_digests.add()
- d.CopyFrom(required_digest)
-
- response = remote.cas.FindMissingBlobs(request)
- for missing_digest in response.missing_blob_digests:
- d = remote_execution_pb2.Digest()
- d.CopyFrom(missing_digest)
- missing_blobs[d.hash] = d
-
- return missing_blobs.values()
-
- # local_missing_blobs():
- #
- # Check local cache for missing blobs.
- #
- # Args:
- # digests (list): The Digests of blobs to check
- #
- # Returns: Missing Digest objects
- #
- def local_missing_blobs(self, digests):
- missing_blobs = []
- for digest in digests:
- objpath = self.objpath(digest)
- if not os.path.exists(objpath):
- missing_blobs.append(digest)
- return missing_blobs
-
- # required_blobs_for_directory():
- #
- # Generator that returns the Digests of all blobs in the tree specified by
- # the Digest of the toplevel Directory object.
- #
- def required_blobs_for_directory(self, directory_digest, *, excluded_subdirs=None):
- if not excluded_subdirs:
- excluded_subdirs = []
-
- # parse directory, and recursively add blobs
-
- yield directory_digest
-
- directory = remote_execution_pb2.Directory()
-
- with open(self.objpath(directory_digest), 'rb') as f:
- directory.ParseFromString(f.read())
-
- for filenode in directory.files:
- yield filenode.digest
-
- for dirnode in directory.directories:
- if dirnode.name not in excluded_subdirs:
- yield from self.required_blobs_for_directory(dirnode.digest)
-
- def diff_trees(self, tree_a, tree_b, *, added, removed, modified, path=""):
- dir_a = remote_execution_pb2.Directory()
- dir_b = remote_execution_pb2.Directory()
-
- if tree_a:
- with open(self.objpath(tree_a), 'rb') as f:
- dir_a.ParseFromString(f.read())
- if tree_b:
- with open(self.objpath(tree_b), 'rb') as f:
- dir_b.ParseFromString(f.read())
-
- a = 0
- b = 0
- while a < len(dir_a.files) or b < len(dir_b.files):
- if b < len(dir_b.files) and (a >= len(dir_a.files) or
- dir_a.files[a].name > dir_b.files[b].name):
- added.append(os.path.join(path, dir_b.files[b].name))
- b += 1
- elif a < len(dir_a.files) and (b >= len(dir_b.files) or
- dir_b.files[b].name > dir_a.files[a].name):
- removed.append(os.path.join(path, dir_a.files[a].name))
- a += 1
- else:
- # File exists in both directories
- if dir_a.files[a].digest.hash != dir_b.files[b].digest.hash:
- modified.append(os.path.join(path, dir_a.files[a].name))
- a += 1
- b += 1
-
- a = 0
- b = 0
- while a < len(dir_a.directories) or b < len(dir_b.directories):
- if b < len(dir_b.directories) and (a >= len(dir_a.directories) or
- dir_a.directories[a].name > dir_b.directories[b].name):
- self.diff_trees(None, dir_b.directories[b].digest,
- added=added, removed=removed, modified=modified,
- path=os.path.join(path, dir_b.directories[b].name))
- b += 1
- elif a < len(dir_a.directories) and (b >= len(dir_b.directories) or
- dir_b.directories[b].name > dir_a.directories[a].name):
- self.diff_trees(dir_a.directories[a].digest, None,
- added=added, removed=removed, modified=modified,
- path=os.path.join(path, dir_a.directories[a].name))
- a += 1
- else:
- # Subdirectory exists in both directories
- if dir_a.directories[a].digest.hash != dir_b.directories[b].digest.hash:
- self.diff_trees(dir_a.directories[a].digest, dir_b.directories[b].digest,
- added=added, removed=removed, modified=modified,
- path=os.path.join(path, dir_a.directories[a].name))
- a += 1
- b += 1
-
- ################################################
- # Local Private Methods #
- ################################################
-
- def _refpath(self, ref):
- return os.path.join(self.casdir, 'refs', 'heads', ref)
-
- # _remove_ref()
- #
- # Removes a ref.
- #
- # This also takes care of pruning away directories which can
- # be removed after having removed the given ref.
- #
- # Args:
- # ref (str): The ref to remove
- # basedir (str): Path of base directory the ref is in
- #
- # Raises:
- # (CASCacheError): If the ref didnt exist, or a system error
- # occurred while removing it
- #
- def _remove_ref(self, ref, basedir):
-
- # Remove the ref itself
- refpath = os.path.join(basedir, ref)
-
- try:
- os.unlink(refpath)
- except FileNotFoundError as e:
- raise CASCacheError("Could not find ref '{}'".format(ref)) from e
-
- # Now remove any leading directories
-
- components = list(os.path.split(ref))
- while components:
- components.pop()
- refdir = os.path.join(basedir, *components)
-
- # Break out once we reach the base
- if refdir == basedir:
- break
-
- try:
- os.rmdir(refdir)
- except FileNotFoundError:
- # The parent directory did not exist, but it's
- # parent directory might still be ready to prune
- pass
- except OSError as e:
- if e.errno == errno.ENOTEMPTY:
- # The parent directory was not empty, so we
- # cannot prune directories beyond this point
- break
-
- # Something went wrong here
- raise CASCacheError("System error while removing ref '{}': {}".format(ref, e)) from e
-
- # _commit_directory():
- #
- # Adds local directory to content addressable store.
- #
- # Adds files, symbolic links and recursively other directories in
- # a local directory to the content addressable store.
- #
- # Args:
- # path (str): Path to the directory to add.
- # dir_digest (Digest): An optional Digest object to use.
- #
- # Returns:
- # (Digest): Digest object for the directory added.
- #
- def _commit_directory(self, path, *, dir_digest=None):
- directory = remote_execution_pb2.Directory()
-
- for name in sorted(os.listdir(path)):
- full_path = os.path.join(path, name)
- mode = os.lstat(full_path).st_mode
- if stat.S_ISDIR(mode):
- dirnode = directory.directories.add()
- dirnode.name = name
- self._commit_directory(full_path, dir_digest=dirnode.digest)
- elif stat.S_ISREG(mode):
- filenode = directory.files.add()
- filenode.name = name
- self.add_object(path=full_path, digest=filenode.digest)
- filenode.is_executable = (mode & stat.S_IXUSR) == stat.S_IXUSR
- elif stat.S_ISLNK(mode):
- symlinknode = directory.symlinks.add()
- symlinknode.name = name
- symlinknode.target = os.readlink(full_path)
- elif stat.S_ISSOCK(mode):
- # The process serving the socket can't be cached anyway
- pass
- else:
- raise CASCacheError("Unsupported file type for {}".format(full_path))
-
- return self.add_object(digest=dir_digest,
- buffer=directory.SerializeToString())
-
- def _get_subdir(self, tree, subdir):
- head, name = os.path.split(subdir)
- if head:
- tree = self._get_subdir(tree, head)
-
- directory = remote_execution_pb2.Directory()
-
- with open(self.objpath(tree), 'rb') as f:
- directory.ParseFromString(f.read())
-
- for dirnode in directory.directories:
- if dirnode.name == name:
- return dirnode.digest
-
- raise CASCacheError("Subdirectory {} not found".format(name))
-
- def _reachable_refs_dir(self, reachable, tree, update_mtime=False, check_exists=False):
- if tree.hash in reachable:
- return
- try:
- if update_mtime:
- os.utime(self.objpath(tree))
-
- reachable.add(tree.hash)
-
- directory = remote_execution_pb2.Directory()
-
- with open(self.objpath(tree), 'rb') as f:
- directory.ParseFromString(f.read())
-
- except FileNotFoundError:
- # Just exit early if the file doesn't exist
- return
-
- for filenode in directory.files:
- if update_mtime:
- os.utime(self.objpath(filenode.digest))
- if check_exists:
- if not os.path.exists(self.objpath(filenode.digest)):
- raise FileNotFoundError
- reachable.add(filenode.digest.hash)
-
- for dirnode in directory.directories:
- self._reachable_refs_dir(reachable, dirnode.digest, update_mtime=update_mtime, check_exists=check_exists)
-
- # _temporary_object():
- #
- # Returns:
- # (file): A file object to a named temporary file.
- #
- # Create a named temporary file with 0o0644 access rights.
- @contextlib.contextmanager
- def _temporary_object(self):
- with utils._tempnamedfile(dir=self.tmpdir) as f:
- os.chmod(f.name,
- stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
- yield f
-
- # _ensure_blob():
- #
- # Fetch and add blob if it's not already local.
- #
- # Args:
- # remote (Remote): The remote to use.
- # digest (Digest): Digest object for the blob to fetch.
- #
- # Returns:
- # (str): The path of the object
- #
- def _ensure_blob(self, remote, digest):
- objpath = self.objpath(digest)
- if os.path.exists(objpath):
- # already in local repository
- return objpath
-
- with self._temporary_object() as f:
- remote._fetch_blob(digest, f)
-
- added_digest = self.add_object(path=f.name, link_directly=True)
- assert added_digest.hash == digest.hash
-
- return objpath
-
- def _batch_download_complete(self, batch, *, missing_blobs=None):
- for digest, data in batch.send(missing_blobs=missing_blobs):
- with self._temporary_object() as f:
- f.write(data)
- f.flush()
-
- added_digest = self.add_object(path=f.name, link_directly=True)
- assert added_digest.hash == digest.hash
-
- # Helper function for _fetch_directory().
- def _fetch_directory_batch(self, remote, batch, fetch_queue, fetch_next_queue):
- self._batch_download_complete(batch)
-
- # All previously scheduled directories are now locally available,
- # move them to the processing queue.
- fetch_queue.extend(fetch_next_queue)
- fetch_next_queue.clear()
- return _CASBatchRead(remote)
-
- # Helper function for _fetch_directory().
- def _fetch_directory_node(self, remote, digest, batch, fetch_queue, fetch_next_queue, *, recursive=False):
- in_local_cache = os.path.exists(self.objpath(digest))
-
- if in_local_cache:
- # Skip download, already in local cache.
- pass
- elif (digest.size_bytes >= remote.max_batch_total_size_bytes or
- not remote.batch_read_supported):
- # Too large for batch request, download in independent request.
- self._ensure_blob(remote, digest)
- in_local_cache = True
- else:
- if not batch.add(digest):
- # Not enough space left in batch request.
- # Complete pending batch first.
- batch = self._fetch_directory_batch(remote, batch, fetch_queue, fetch_next_queue)
- batch.add(digest)
-
- if recursive:
- if in_local_cache:
- # Add directory to processing queue.
- fetch_queue.append(digest)
- else:
- # Directory will be available after completing pending batch.
- # Add directory to deferred processing queue.
- fetch_next_queue.append(digest)
-
- return batch
-
- # _fetch_directory():
- #
- # Fetches remote directory and adds it to content addressable store.
- #
- # This recursively fetches directory objects but doesn't fetch any
- # files.
- #
- # Args:
- # remote (Remote): The remote to use.
- # dir_digest (Digest): Digest object for the directory to fetch.
- #
- def _fetch_directory(self, remote, dir_digest):
- # TODO Use GetTree() if the server supports it
-
- fetch_queue = [dir_digest]
- fetch_next_queue = []
- batch = _CASBatchRead(remote)
-
- while len(fetch_queue) + len(fetch_next_queue) > 0:
- if not fetch_queue:
- batch = self._fetch_directory_batch(remote, batch, fetch_queue, fetch_next_queue)
-
- dir_digest = fetch_queue.pop(0)
-
- objpath = self._ensure_blob(remote, dir_digest)
-
- directory = remote_execution_pb2.Directory()
- with open(objpath, 'rb') as f:
- directory.ParseFromString(f.read())
-
- for dirnode in directory.directories:
- batch = self._fetch_directory_node(remote, dirnode.digest, batch,
- fetch_queue, fetch_next_queue, recursive=True)
-
- # Fetch final batch
- self._fetch_directory_batch(remote, batch, fetch_queue, fetch_next_queue)
-
- def _fetch_tree(self, remote, digest):
- # download but do not store the Tree object
- with utils._tempnamedfile(dir=self.tmpdir) as out:
- remote._fetch_blob(digest, out)
-
- tree = remote_execution_pb2.Tree()
-
- with open(out.name, 'rb') as f:
- tree.ParseFromString(f.read())
-
- tree.children.extend([tree.root])
- for directory in tree.children:
- dirbuffer = directory.SerializeToString()
- dirdigest = self.add_object(buffer=dirbuffer)
- assert dirdigest.size_bytes == len(dirbuffer)
-
- return dirdigest
-
- # fetch_blobs():
- #
- # Fetch blobs from remote CAS. Returns missing blobs that could not be fetched.
- #
- # Args:
- # remote (CASRemote): The remote repository to fetch from
- # digests (list): The Digests of blobs to fetch
- #
- # Returns: The Digests of the blobs that were not available on the remote CAS
- #
- def fetch_blobs(self, remote, digests):
- missing_blobs = []
-
- batch = _CASBatchRead(remote)
-
- for digest in digests:
- if (digest.size_bytes >= remote.max_batch_total_size_bytes or
- not remote.batch_read_supported):
- # Too large for batch request, download in independent request.
- try:
- self._ensure_blob(remote, digest)
- except grpc.RpcError as e:
- if e.code() == grpc.StatusCode.NOT_FOUND:
- missing_blobs.append(digest)
- else:
- raise CASCacheError("Failed to fetch blob: {}".format(e)) from e
- else:
- if not batch.add(digest):
- # Not enough space left in batch request.
- # Complete pending batch first.
- self._batch_download_complete(batch, missing_blobs=missing_blobs)
-
- batch = _CASBatchRead(remote)
- batch.add(digest)
-
- # Complete last pending batch
- self._batch_download_complete(batch, missing_blobs=missing_blobs)
-
- return missing_blobs
-
- # send_blobs():
- #
- # Upload blobs to remote CAS.
- #
- # Args:
- # remote (CASRemote): The remote repository to upload to
- # digests (list): The Digests of Blobs to upload
- #
- def send_blobs(self, remote, digests, u_uid=uuid.uuid4()):
- batch = _CASBatchUpdate(remote)
-
- for digest in digests:
- with open(self.objpath(digest), 'rb') as f:
- assert os.fstat(f.fileno()).st_size == digest.size_bytes
-
- if (digest.size_bytes >= remote.max_batch_total_size_bytes or
- not remote.batch_update_supported):
- # Too large for batch request, upload in independent request.
- remote._send_blob(digest, f, u_uid=u_uid)
- else:
- if not batch.add(digest, f):
- # Not enough space left in batch request.
- # Complete pending batch first.
- batch.send()
- batch = _CASBatchUpdate(remote)
- batch.add(digest, f)
-
- # Send final batch
- batch.send()
-
- def _send_directory(self, remote, digest, u_uid=uuid.uuid4()):
- missing_blobs = self.remote_missing_blobs_for_directory(remote, digest)
-
- # Upload any blobs missing on the server
- self.send_blobs(remote, missing_blobs, u_uid)
-
-
-class CASQuota:
- def __init__(self, context):
- self.context = context
- self.cas = context.get_cascache()
- self.casdir = self.cas.casdir
- self._config_cache_quota = context.config_cache_quota
- self._config_cache_quota_string = context.config_cache_quota_string
- self._cache_size = None # The current cache size, sometimes it's an estimate
- self._cache_quota = None # The cache quota
- self._cache_quota_original = None # The cache quota as specified by the user, in bytes
- self._cache_quota_headroom = None # The headroom in bytes before reaching the quota or full disk
- self._cache_lower_threshold = None # The target cache size for a cleanup
- self.available_space = None
-
- self._message = context.message
-
- self._remove_callbacks = [] # Callbacks to remove unrequired refs and their remove method
- self._list_refs_callbacks = [] # Callbacks to all refs
-
- self._calculate_cache_quota()
-
- # compute_cache_size()
- #
- # Computes the real artifact cache size.
- #
- # Returns:
- # (int): The size of the artifact cache.
- #
- def compute_cache_size(self):
- self._cache_size = utils._get_dir_size(self.casdir)
- return self._cache_size
-
- # get_cache_size()
- #
- # Fetches the cached size of the cache, this is sometimes
- # an estimate and periodically adjusted to the real size
- # when a cache size calculation job runs.
- #
- # When it is an estimate, the value is either correct, or
- # it is greater than the actual cache size.
- #
- # Returns:
- # (int) An approximation of the artifact cache size, in bytes.
- #
- def get_cache_size(self):
-
- # If we don't currently have an estimate, figure out the real cache size.
- if self._cache_size is None:
- stored_size = self._read_cache_size()
- if stored_size is not None:
- self._cache_size = stored_size
- else:
- self.compute_cache_size()
-
- return self._cache_size
-
- # set_cache_size()
- #
- # Forcefully set the overall cache size.
- #
- # This is used to update the size in the main process after
- # having calculated in a cleanup or a cache size calculation job.
- #
- # Args:
- # cache_size (int): The size to set.
- # write_to_disk (bool): Whether to write the value to disk.
- #
- def set_cache_size(self, cache_size, *, write_to_disk=True):
-
- assert cache_size is not None
-
- self._cache_size = cache_size
- if write_to_disk:
- self._write_cache_size(self._cache_size)
-
- # full()
- #
- # Checks if the artifact cache is full, either
- # because the user configured quota has been exceeded
- # or because the underlying disk is almost full.
- #
- # Returns:
- # (bool): True if the artifact cache is full
- #
- def full(self):
-
- if self.get_cache_size() > self._cache_quota:
- return True
-
- _, volume_avail = self._get_cache_volume_size()
- if volume_avail < self._cache_quota_headroom:
- return True
-
- return False
-
- # add_remove_callbacks()
- #
- # This adds tuples of iterators over unrequired objects (currently
- # artifacts and source refs), and a callback to remove them.
- #
- # Args:
- # callback (iter(unrequired), remove): tuple of iterator and remove
- # method associated.
- #
- def add_remove_callbacks(self, list_unrequired, remove_method):
- self._remove_callbacks.append((list_unrequired, remove_method))
-
- def add_list_refs_callback(self, list_callback):
- self._list_refs_callbacks.append(list_callback)
-
- ################################################
- # Local Private Methods #
- ################################################
-
- # _read_cache_size()
- #
- # Reads and returns the size of the artifact cache that's stored in the
- # cache's size file
- #
- # Returns:
- # (int): The size of the artifact cache, as recorded in the file
- #
- def _read_cache_size(self):
- size_file_path = os.path.join(self.casdir, CACHE_SIZE_FILE)
-
- if not os.path.exists(size_file_path):
- return None
-
- with open(size_file_path, "r") as f:
- size = f.read()
-
- try:
- num_size = int(size)
- except ValueError as e:
- raise CASCacheError("Size '{}' parsed from '{}' was not an integer".format(
- size, size_file_path)) from e
-
- return num_size
-
- # _write_cache_size()
- #
- # Writes the given size of the artifact to the cache's size file
- #
- # Args:
- # size (int): The size of the artifact cache to record
- #
- def _write_cache_size(self, size):
- assert isinstance(size, int)
- size_file_path = os.path.join(self.casdir, CACHE_SIZE_FILE)
- with utils.save_file_atomic(size_file_path, "w", tempdir=self.cas.tmpdir) as f:
- f.write(str(size))
-
- # _get_cache_volume_size()
- #
- # Get the available space and total space for the volume on
- # which the artifact cache is located.
- #
- # Returns:
- # (int): The total number of bytes on the volume
- # (int): The number of available bytes on the volume
- #
- # NOTE: We use this stub to allow the test cases
- # to override what an artifact cache thinks
- # about it's disk size and available bytes.
- #
- def _get_cache_volume_size(self):
- return utils._get_volume_size(self.casdir)
-
- # _calculate_cache_quota()
- #
- # Calculates and sets the cache quota and lower threshold based on the
- # quota set in Context.
- # It checks that the quota is both a valid expression, and that there is
- # enough disk space to satisfy that quota
- #
- def _calculate_cache_quota(self):
- # Headroom intended to give BuildStream a bit of leeway.
- # This acts as the minimum size of cache_quota and also
- # is taken from the user requested cache_quota.
- #
- if 'BST_TEST_SUITE' in os.environ:
- self._cache_quota_headroom = 0
- else:
- self._cache_quota_headroom = 2e9
-
- total_size, available_space = self._get_cache_volume_size()
- cache_size = self.get_cache_size()
- self.available_space = available_space
-
- # Ensure system has enough storage for the cache_quota
- #
- # If cache_quota is none, set it to the maximum it could possibly be.
- #
- # Also check that cache_quota is at least as large as our headroom.
- #
- cache_quota = self._config_cache_quota
- if cache_quota is None:
- # The user has set no limit, so we may take all the space.
- cache_quota = min(cache_size + available_space, total_size)
- if cache_quota < self._cache_quota_headroom: # Check minimum
- raise LoadError(
- LoadErrorReason.INVALID_DATA,
- "Invalid cache quota ({}): BuildStream requires a minimum cache quota of {}.".format(
- utils._pretty_size(cache_quota),
- utils._pretty_size(self._cache_quota_headroom)))
- elif cache_quota > total_size:
- # A quota greater than the total disk size is certianly an error
- raise CASCacheError("Your system does not have enough available " +
- "space to support the cache quota specified.",
- detail=("You have specified a quota of {quota} total disk space.\n" +
- "The filesystem containing {local_cache_path} only " +
- "has {total_size} total disk space.")
- .format(
- quota=self._config_cache_quota,
- local_cache_path=self.casdir,
- total_size=utils._pretty_size(total_size)),
- reason='insufficient-storage-for-quota')
-
- elif cache_quota > cache_size + available_space:
- # The quota does not fit in the available space, this is a warning
- if '%' in self._config_cache_quota_string:
- available = (available_space / total_size) * 100
- available = '{}% of total disk space'.format(round(available, 1))
- else:
- available = utils._pretty_size(available_space)
-
- self._message(Message(
- None,
- MessageType.WARN,
- "Your system does not have enough available " +
- "space to support the cache quota specified.",
- detail=("You have specified a quota of {quota} total disk space.\n" +
- "The filesystem containing {local_cache_path} only " +
- "has {available_size} available.")
- .format(quota=self._config_cache_quota,
- local_cache_path=self.casdir,
- available_size=available)))
-
- # Place a slight headroom (2e9 (2GB) on the cache_quota) into
- # cache_quota to try and avoid exceptions.
- #
- # Of course, we might still end up running out during a build
- # if we end up writing more than 2G, but hey, this stuff is
- # already really fuzzy.
- #
- self._cache_quota_original = cache_quota
- self._cache_quota = cache_quota - self._cache_quota_headroom
- self._cache_lower_threshold = self._cache_quota / 2
-
- # clean():
- #
- # Clean the artifact cache as much as possible.
- #
- # Args:
- # progress (callable): A callback to call when a ref is removed
- #
- # Returns:
- # (int): The size of the cache after having cleaned up
- #
- def clean(self, progress=None):
- context = self.context
-
- # Some accumulative statistics
- removed_ref_count = 0
- space_saved = 0
-
- total_refs = 0
- for refs in self._list_refs_callbacks:
- total_refs += len(list(refs()))
-
- # Start off with an announcement with as much info as possible
- volume_size, volume_avail = self._get_cache_volume_size()
- self._message(Message(
- None, MessageType.STATUS, "Starting cache cleanup",
- detail=("Elements required by the current build plan:\n" + "{}\n" +
- "User specified quota: {} ({})\n" +
- "Cache usage: {}\n" +
- "Cache volume: {} total, {} available")
- .format(
- total_refs,
- context.config_cache_quota,
- utils._pretty_size(self._cache_quota, dec_places=2),
- utils._pretty_size(self.get_cache_size(), dec_places=2),
- utils._pretty_size(volume_size, dec_places=2),
- utils._pretty_size(volume_avail, dec_places=2))))
-
- # Do a real computation of the cache size once, just in case
- self.compute_cache_size()
- usage = CASCacheUsage(self)
- self._message(Message(None, MessageType.STATUS,
- "Cache usage recomputed: {}".format(usage)))
-
- # Collect digests and their remove method
- all_unrequired_refs = []
- for (unrequired_refs, remove) in self._remove_callbacks:
- for (mtime, ref) in unrequired_refs():
- all_unrequired_refs.append((mtime, ref, remove))
-
- # Pair refs and their remove method sorted in time order
- all_unrequired_refs = [(ref, remove) for (_, ref, remove) in sorted(all_unrequired_refs)]
-
- # Go through unrequired refs and remove them, oldest first
- made_space = False
- for (ref, remove) in all_unrequired_refs:
- size = remove(ref)
- removed_ref_count += 1
- space_saved += size
-
- self._message(Message(
- None, MessageType.STATUS,
- "Freed {: <7} {}".format(
- utils._pretty_size(size, dec_places=2),
- ref)))
-
- self.set_cache_size(self._cache_size - size)
-
- # User callback
- #
- # Currently this process is fairly slow, but we should
- # think about throttling this progress() callback if this
- # becomes too intense.
- if progress:
- progress()
-
- if self.get_cache_size() < self._cache_lower_threshold:
- made_space = True
- break
-
- if not made_space and self.full():
- # If too many artifacts are required, and we therefore
- # can't remove them, we have to abort the build.
- #
- # FIXME: Asking the user what to do may be neater
- #
- default_conf = os.path.join(os.environ['XDG_CONFIG_HOME'],
- 'buildstream.conf')
- detail = ("Aborted after removing {} refs and saving {} disk space.\n"
- "The remaining {} in the cache is required by the {} references in your build plan\n\n"
- "There is not enough space to complete the build.\n"
- "Please increase the cache-quota in {} and/or make more disk space."
- .format(removed_ref_count,
- utils._pretty_size(space_saved, dec_places=2),
- utils._pretty_size(self.get_cache_size(), dec_places=2),
- total_refs,
- (context.config_origin or default_conf)))
-
- raise CASCacheError("Cache too full. Aborting.",
- detail=detail,
- reason="cache-too-full")
-
- # Informational message about the side effects of the cleanup
- self._message(Message(
- None, MessageType.INFO, "Cleanup completed",
- detail=("Removed {} refs and saving {} disk space.\n" +
- "Cache usage is now: {}")
- .format(removed_ref_count,
- utils._pretty_size(space_saved, dec_places=2),
- utils._pretty_size(self.get_cache_size(), dec_places=2))))
-
- return self.get_cache_size()
-
-
-def _grouper(iterable, n):
- while True:
- try:
- current = next(iterable)
- except StopIteration:
- return
- yield itertools.chain([current], itertools.islice(iterable, n - 1))
diff --git a/buildstream/_cas/casremote.py b/buildstream/_cas/casremote.py
deleted file mode 100644
index aac0d2802..000000000
--- a/buildstream/_cas/casremote.py
+++ /dev/null
@@ -1,391 +0,0 @@
-from collections import namedtuple
-import io
-import os
-import multiprocessing
-import signal
-from urllib.parse import urlparse
-import uuid
-
-import grpc
-
-from .. import _yaml
-from .._protos.google.rpc import code_pb2
-from .._protos.google.bytestream import bytestream_pb2, bytestream_pb2_grpc
-from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
-from .._protos.buildstream.v2 import buildstream_pb2, buildstream_pb2_grpc
-
-from .._exceptions import CASRemoteError, LoadError, LoadErrorReason
-from .. import _signals
-from .. import utils
-
-# The default limit for gRPC messages is 4 MiB.
-# Limit payload to 1 MiB to leave sufficient headroom for metadata.
-_MAX_PAYLOAD_BYTES = 1024 * 1024
-
-
-class CASRemoteSpec(namedtuple('CASRemoteSpec', 'url push server_cert client_key client_cert instance_name')):
-
- # _new_from_config_node
- #
- # Creates an CASRemoteSpec() from a YAML loaded node
- #
- @staticmethod
- def _new_from_config_node(spec_node, basedir=None):
- _yaml.node_validate(spec_node, ['url', 'push', 'server-cert', 'client-key', 'client-cert', 'instance-name'])
- url = _yaml.node_get(spec_node, str, 'url')
- push = _yaml.node_get(spec_node, bool, 'push', default_value=False)
- if not url:
- provenance = _yaml.node_get_provenance(spec_node, 'url')
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: empty artifact cache URL".format(provenance))
-
- instance_name = _yaml.node_get(spec_node, str, 'instance-name', default_value=None)
-
- server_cert = _yaml.node_get(spec_node, str, 'server-cert', default_value=None)
- if server_cert and basedir:
- server_cert = os.path.join(basedir, server_cert)
-
- client_key = _yaml.node_get(spec_node, str, 'client-key', default_value=None)
- if client_key and basedir:
- client_key = os.path.join(basedir, client_key)
-
- client_cert = _yaml.node_get(spec_node, str, 'client-cert', default_value=None)
- if client_cert and basedir:
- client_cert = os.path.join(basedir, client_cert)
-
- if client_key and not client_cert:
- provenance = _yaml.node_get_provenance(spec_node, 'client-key')
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: 'client-key' was specified without 'client-cert'".format(provenance))
-
- if client_cert and not client_key:
- provenance = _yaml.node_get_provenance(spec_node, 'client-cert')
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: 'client-cert' was specified without 'client-key'".format(provenance))
-
- return CASRemoteSpec(url, push, server_cert, client_key, client_cert, instance_name)
-
-
-CASRemoteSpec.__new__.__defaults__ = (None, None, None, None)
-
-
-class BlobNotFound(CASRemoteError):
-
- def __init__(self, blob, msg):
- self.blob = blob
- super().__init__(msg)
-
-
-# Represents a single remote CAS cache.
-#
-class CASRemote():
- def __init__(self, spec):
- self.spec = spec
- self._initialized = False
- self.channel = None
- self.instance_name = None
- self.bytestream = None
- self.cas = None
- self.ref_storage = None
- self.batch_update_supported = None
- self.batch_read_supported = None
- self.capabilities = None
- self.max_batch_total_size_bytes = None
-
- def init(self):
- if not self._initialized:
- url = urlparse(self.spec.url)
- if url.scheme == 'http':
- port = url.port or 80
- self.channel = grpc.insecure_channel('{}:{}'.format(url.hostname, port))
- elif url.scheme == 'https':
- port = url.port or 443
-
- if self.spec.server_cert:
- with open(self.spec.server_cert, 'rb') as f:
- server_cert_bytes = f.read()
- else:
- server_cert_bytes = None
-
- if self.spec.client_key:
- with open(self.spec.client_key, 'rb') as f:
- client_key_bytes = f.read()
- else:
- client_key_bytes = None
-
- if self.spec.client_cert:
- with open(self.spec.client_cert, 'rb') as f:
- client_cert_bytes = f.read()
- else:
- client_cert_bytes = None
-
- credentials = grpc.ssl_channel_credentials(root_certificates=server_cert_bytes,
- private_key=client_key_bytes,
- certificate_chain=client_cert_bytes)
- self.channel = grpc.secure_channel('{}:{}'.format(url.hostname, port), credentials)
- else:
- raise CASRemoteError("Unsupported URL: {}".format(self.spec.url))
-
- self.instance_name = self.spec.instance_name or None
-
- self.bytestream = bytestream_pb2_grpc.ByteStreamStub(self.channel)
- self.cas = remote_execution_pb2_grpc.ContentAddressableStorageStub(self.channel)
- self.capabilities = remote_execution_pb2_grpc.CapabilitiesStub(self.channel)
- self.ref_storage = buildstream_pb2_grpc.ReferenceStorageStub(self.channel)
-
- self.max_batch_total_size_bytes = _MAX_PAYLOAD_BYTES
- try:
- request = remote_execution_pb2.GetCapabilitiesRequest()
- if self.instance_name:
- request.instance_name = self.instance_name
- response = self.capabilities.GetCapabilities(request)
- server_max_batch_total_size_bytes = response.cache_capabilities.max_batch_total_size_bytes
- if 0 < server_max_batch_total_size_bytes < self.max_batch_total_size_bytes:
- self.max_batch_total_size_bytes = server_max_batch_total_size_bytes
- except grpc.RpcError as e:
- # Simply use the defaults for servers that don't implement GetCapabilities()
- if e.code() != grpc.StatusCode.UNIMPLEMENTED:
- raise
-
- # Check whether the server supports BatchReadBlobs()
- self.batch_read_supported = False
- try:
- request = remote_execution_pb2.BatchReadBlobsRequest()
- if self.instance_name:
- request.instance_name = self.instance_name
- response = self.cas.BatchReadBlobs(request)
- self.batch_read_supported = True
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.UNIMPLEMENTED:
- raise
-
- # Check whether the server supports BatchUpdateBlobs()
- self.batch_update_supported = False
- try:
- request = remote_execution_pb2.BatchUpdateBlobsRequest()
- if self.instance_name:
- request.instance_name = self.instance_name
- response = self.cas.BatchUpdateBlobs(request)
- self.batch_update_supported = True
- except grpc.RpcError as e:
- if (e.code() != grpc.StatusCode.UNIMPLEMENTED and
- e.code() != grpc.StatusCode.PERMISSION_DENIED):
- raise
-
- self._initialized = True
-
- # check_remote
- #
- # Used when checking whether remote_specs work in the buildstream main
- # thread, runs this in a seperate process to avoid creation of gRPC threads
- # in the main BuildStream process
- # See https://github.com/grpc/grpc/blob/master/doc/fork_support.md for details
- @classmethod
- def check_remote(cls, remote_spec, q):
-
- def __check_remote():
- try:
- remote = cls(remote_spec)
- remote.init()
-
- request = buildstream_pb2.StatusRequest()
- response = remote.ref_storage.Status(request)
-
- if remote_spec.push and not response.allow_updates:
- q.put('CAS server does not allow push')
- else:
- # No error
- q.put(None)
-
- except grpc.RpcError as e:
- # str(e) is too verbose for errors reported to the user
- q.put(e.details())
-
- except Exception as e: # pylint: disable=broad-except
- # Whatever happens, we need to return it to the calling process
- #
- q.put(str(e))
-
- p = multiprocessing.Process(target=__check_remote)
-
- try:
- # Keep SIGINT blocked in the child process
- with _signals.blocked([signal.SIGINT], ignore=False):
- p.start()
-
- error = q.get()
- p.join()
- except KeyboardInterrupt:
- utils._kill_process_tree(p.pid)
- raise
-
- return error
-
- # push_message():
- #
- # Push the given protobuf message to a remote.
- #
- # Args:
- # message (Message): A protobuf message to push.
- #
- # Raises:
- # (CASRemoteError): if there was an error
- #
- def push_message(self, message):
-
- message_buffer = message.SerializeToString()
- message_digest = utils._message_digest(message_buffer)
-
- self.init()
-
- with io.BytesIO(message_buffer) as b:
- self._send_blob(message_digest, b)
-
- return message_digest
-
- ################################################
- # Local Private Methods #
- ################################################
- def _fetch_blob(self, digest, stream):
- if self.instance_name:
- resource_name = '/'.join([self.instance_name, 'blobs',
- digest.hash, str(digest.size_bytes)])
- else:
- resource_name = '/'.join(['blobs',
- digest.hash, str(digest.size_bytes)])
-
- request = bytestream_pb2.ReadRequest()
- request.resource_name = resource_name
- request.read_offset = 0
- for response in self.bytestream.Read(request):
- stream.write(response.data)
- stream.flush()
-
- assert digest.size_bytes == os.fstat(stream.fileno()).st_size
-
- def _send_blob(self, digest, stream, u_uid=uuid.uuid4()):
- if self.instance_name:
- resource_name = '/'.join([self.instance_name, 'uploads', str(u_uid), 'blobs',
- digest.hash, str(digest.size_bytes)])
- else:
- resource_name = '/'.join(['uploads', str(u_uid), 'blobs',
- digest.hash, str(digest.size_bytes)])
-
- def request_stream(resname, instream):
- offset = 0
- finished = False
- remaining = digest.size_bytes
- while not finished:
- chunk_size = min(remaining, _MAX_PAYLOAD_BYTES)
- remaining -= chunk_size
-
- request = bytestream_pb2.WriteRequest()
- request.write_offset = offset
- # max. _MAX_PAYLOAD_BYTES chunks
- request.data = instream.read(chunk_size)
- request.resource_name = resname
- request.finish_write = remaining <= 0
-
- yield request
-
- offset += chunk_size
- finished = request.finish_write
-
- response = self.bytestream.Write(request_stream(resource_name, stream))
-
- assert response.committed_size == digest.size_bytes
-
-
-# Represents a batch of blobs queued for fetching.
-#
-class _CASBatchRead():
- def __init__(self, remote):
- self._remote = remote
- self._max_total_size_bytes = remote.max_batch_total_size_bytes
- self._request = remote_execution_pb2.BatchReadBlobsRequest()
- if remote.instance_name:
- self._request.instance_name = remote.instance_name
- self._size = 0
- self._sent = False
-
- def add(self, digest):
- assert not self._sent
-
- new_batch_size = self._size + digest.size_bytes
- if new_batch_size > self._max_total_size_bytes:
- # Not enough space left in current batch
- return False
-
- request_digest = self._request.digests.add()
- request_digest.hash = digest.hash
- request_digest.size_bytes = digest.size_bytes
- self._size = new_batch_size
- return True
-
- def send(self, *, missing_blobs=None):
- assert not self._sent
- self._sent = True
-
- if not self._request.digests:
- return
-
- batch_response = self._remote.cas.BatchReadBlobs(self._request)
-
- for response in batch_response.responses:
- if response.status.code == code_pb2.NOT_FOUND:
- if missing_blobs is None:
- raise BlobNotFound(response.digest.hash, "Failed to download blob {}: {}".format(
- response.digest.hash, response.status.code))
- else:
- missing_blobs.append(response.digest)
-
- if response.status.code != code_pb2.OK:
- raise CASRemoteError("Failed to download blob {}: {}".format(
- response.digest.hash, response.status.code))
- if response.digest.size_bytes != len(response.data):
- raise CASRemoteError("Failed to download blob {}: expected {} bytes, received {} bytes".format(
- response.digest.hash, response.digest.size_bytes, len(response.data)))
-
- yield (response.digest, response.data)
-
-
-# Represents a batch of blobs queued for upload.
-#
-class _CASBatchUpdate():
- def __init__(self, remote):
- self._remote = remote
- self._max_total_size_bytes = remote.max_batch_total_size_bytes
- self._request = remote_execution_pb2.BatchUpdateBlobsRequest()
- if remote.instance_name:
- self._request.instance_name = remote.instance_name
- self._size = 0
- self._sent = False
-
- def add(self, digest, stream):
- assert not self._sent
-
- new_batch_size = self._size + digest.size_bytes
- if new_batch_size > self._max_total_size_bytes:
- # Not enough space left in current batch
- return False
-
- blob_request = self._request.requests.add()
- blob_request.digest.hash = digest.hash
- blob_request.digest.size_bytes = digest.size_bytes
- blob_request.data = stream.read(digest.size_bytes)
- self._size = new_batch_size
- return True
-
- def send(self):
- assert not self._sent
- self._sent = True
-
- if not self._request.requests:
- return
-
- batch_response = self._remote.cas.BatchUpdateBlobs(self._request)
-
- for response in batch_response.responses:
- if response.status.code != code_pb2.OK:
- raise CASRemoteError("Failed to upload blob {}: {}".format(
- response.digest.hash, response.status.code))
diff --git a/buildstream/_cas/casserver.py b/buildstream/_cas/casserver.py
deleted file mode 100644
index c08a4d577..000000000
--- a/buildstream/_cas/casserver.py
+++ /dev/null
@@ -1,619 +0,0 @@
-#
-# Copyright (C) 2018 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:
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-from concurrent import futures
-import logging
-import os
-import signal
-import sys
-import tempfile
-import uuid
-import errno
-import threading
-
-import grpc
-from google.protobuf.message import DecodeError
-import click
-
-from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
-from .._protos.google.bytestream import bytestream_pb2, bytestream_pb2_grpc
-from .._protos.google.rpc import code_pb2
-from .._protos.buildstream.v2 import buildstream_pb2, buildstream_pb2_grpc, \
- artifact_pb2, artifact_pb2_grpc
-
-from .._exceptions import CASError
-
-from .cascache import CASCache
-
-
-# The default limit for gRPC messages is 4 MiB.
-# Limit payload to 1 MiB to leave sufficient headroom for metadata.
-_MAX_PAYLOAD_BYTES = 1024 * 1024
-
-
-# Trying to push an artifact that is too large
-class ArtifactTooLargeException(Exception):
- pass
-
-
-# create_server():
-#
-# Create gRPC CAS artifact server as specified in the Remote Execution API.
-#
-# Args:
-# repo (str): Path to CAS repository
-# enable_push (bool): Whether to allow blob uploads and artifact updates
-#
-def create_server(repo, *, enable_push,
- max_head_size=int(10e9),
- min_head_size=int(2e9)):
- cas = CASCache(os.path.abspath(repo))
- artifactdir = os.path.join(os.path.abspath(repo), 'artifacts', 'refs')
-
- # Use max_workers default from Python 3.5+
- max_workers = (os.cpu_count() or 1) * 5
- server = grpc.server(futures.ThreadPoolExecutor(max_workers))
-
- cache_cleaner = _CacheCleaner(cas, max_head_size, min_head_size)
-
- bytestream_pb2_grpc.add_ByteStreamServicer_to_server(
- _ByteStreamServicer(cas, cache_cleaner, enable_push=enable_push), server)
-
- remote_execution_pb2_grpc.add_ContentAddressableStorageServicer_to_server(
- _ContentAddressableStorageServicer(cas, cache_cleaner, enable_push=enable_push), server)
-
- remote_execution_pb2_grpc.add_CapabilitiesServicer_to_server(
- _CapabilitiesServicer(), server)
-
- buildstream_pb2_grpc.add_ReferenceStorageServicer_to_server(
- _ReferenceStorageServicer(cas, enable_push=enable_push), server)
-
- artifact_pb2_grpc.add_ArtifactServiceServicer_to_server(
- _ArtifactServicer(cas, artifactdir), server)
-
- return server
-
-
-@click.command(short_help="CAS Artifact Server")
-@click.option('--port', '-p', type=click.INT, required=True, help="Port number")
-@click.option('--server-key', help="Private server key for TLS (PEM-encoded)")
-@click.option('--server-cert', help="Public server certificate for TLS (PEM-encoded)")
-@click.option('--client-certs', help="Public client certificates for TLS (PEM-encoded)")
-@click.option('--enable-push', default=False, is_flag=True,
- help="Allow clients to upload blobs and update artifact cache")
-@click.option('--head-room-min', type=click.INT,
- help="Disk head room minimum in bytes",
- default=2e9)
-@click.option('--head-room-max', type=click.INT,
- help="Disk head room maximum in bytes",
- default=10e9)
-@click.argument('repo')
-def server_main(repo, port, server_key, server_cert, client_certs, enable_push,
- head_room_min, head_room_max):
- server = create_server(repo,
- max_head_size=head_room_max,
- min_head_size=head_room_min,
- enable_push=enable_push)
-
- use_tls = bool(server_key)
-
- if bool(server_cert) != use_tls:
- click.echo("ERROR: --server-key and --server-cert are both required for TLS", err=True)
- sys.exit(-1)
-
- if client_certs and not use_tls:
- click.echo("ERROR: --client-certs can only be used with --server-key", err=True)
- sys.exit(-1)
-
- if use_tls:
- # Read public/private key pair
- with open(server_key, 'rb') as f:
- server_key_bytes = f.read()
- with open(server_cert, 'rb') as f:
- server_cert_bytes = f.read()
-
- if client_certs:
- with open(client_certs, 'rb') as f:
- client_certs_bytes = f.read()
- else:
- client_certs_bytes = None
-
- credentials = grpc.ssl_server_credentials([(server_key_bytes, server_cert_bytes)],
- root_certificates=client_certs_bytes,
- require_client_auth=bool(client_certs))
- server.add_secure_port('[::]:{}'.format(port), credentials)
- else:
- server.add_insecure_port('[::]:{}'.format(port))
-
- # Run artifact server
- server.start()
- try:
- while True:
- signal.pause()
- except KeyboardInterrupt:
- server.stop(0)
-
-
-class _ByteStreamServicer(bytestream_pb2_grpc.ByteStreamServicer):
- def __init__(self, cas, cache_cleaner, *, enable_push):
- super().__init__()
- self.cas = cas
- self.enable_push = enable_push
- self.cache_cleaner = cache_cleaner
-
- def Read(self, request, context):
- resource_name = request.resource_name
- client_digest = _digest_from_download_resource_name(resource_name)
- if client_digest is None:
- context.set_code(grpc.StatusCode.NOT_FOUND)
- return
-
- if request.read_offset > client_digest.size_bytes:
- context.set_code(grpc.StatusCode.OUT_OF_RANGE)
- return
-
- try:
- with open(self.cas.objpath(client_digest), 'rb') as f:
- if os.fstat(f.fileno()).st_size != client_digest.size_bytes:
- context.set_code(grpc.StatusCode.NOT_FOUND)
- return
-
- if request.read_offset > 0:
- f.seek(request.read_offset)
-
- remaining = client_digest.size_bytes - request.read_offset
- while remaining > 0:
- chunk_size = min(remaining, _MAX_PAYLOAD_BYTES)
- remaining -= chunk_size
-
- response = bytestream_pb2.ReadResponse()
- # max. 64 kB chunks
- response.data = f.read(chunk_size)
- yield response
- except FileNotFoundError:
- context.set_code(grpc.StatusCode.NOT_FOUND)
-
- def Write(self, request_iterator, context):
- response = bytestream_pb2.WriteResponse()
-
- if not self.enable_push:
- context.set_code(grpc.StatusCode.PERMISSION_DENIED)
- return response
-
- offset = 0
- finished = False
- resource_name = None
- with tempfile.NamedTemporaryFile(dir=self.cas.tmpdir) as out:
- for request in request_iterator:
- if finished or request.write_offset != offset:
- context.set_code(grpc.StatusCode.FAILED_PRECONDITION)
- return response
-
- if resource_name is None:
- # First request
- resource_name = request.resource_name
- client_digest = _digest_from_upload_resource_name(resource_name)
- if client_digest is None:
- context.set_code(grpc.StatusCode.NOT_FOUND)
- return response
-
- while True:
- if client_digest.size_bytes == 0:
- break
- try:
- self.cache_cleaner.clean_up(client_digest.size_bytes)
- except ArtifactTooLargeException as e:
- context.set_code(grpc.StatusCode.RESOURCE_EXHAUSTED)
- context.set_details(str(e))
- return response
-
- try:
- os.posix_fallocate(out.fileno(), 0, client_digest.size_bytes)
- break
- except OSError as e:
- # Multiple upload can happen in the same time
- if e.errno != errno.ENOSPC:
- raise
-
- elif request.resource_name:
- # If it is set on subsequent calls, it **must** match the value of the first request.
- if request.resource_name != resource_name:
- context.set_code(grpc.StatusCode.FAILED_PRECONDITION)
- return response
-
- if (offset + len(request.data)) > client_digest.size_bytes:
- context.set_code(grpc.StatusCode.FAILED_PRECONDITION)
- return response
-
- out.write(request.data)
- offset += len(request.data)
- if request.finish_write:
- if client_digest.size_bytes != offset:
- context.set_code(grpc.StatusCode.FAILED_PRECONDITION)
- return response
- out.flush()
- digest = self.cas.add_object(path=out.name, link_directly=True)
- if digest.hash != client_digest.hash:
- context.set_code(grpc.StatusCode.FAILED_PRECONDITION)
- return response
- finished = True
-
- assert finished
-
- response.committed_size = offset
- return response
-
-
-class _ContentAddressableStorageServicer(remote_execution_pb2_grpc.ContentAddressableStorageServicer):
- def __init__(self, cas, cache_cleaner, *, enable_push):
- super().__init__()
- self.cas = cas
- self.enable_push = enable_push
- self.cache_cleaner = cache_cleaner
-
- def FindMissingBlobs(self, request, context):
- response = remote_execution_pb2.FindMissingBlobsResponse()
- for digest in request.blob_digests:
- objpath = self.cas.objpath(digest)
- try:
- os.utime(objpath)
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise
- else:
- d = response.missing_blob_digests.add()
- d.hash = digest.hash
- d.size_bytes = digest.size_bytes
-
- return response
-
- def BatchReadBlobs(self, request, context):
- response = remote_execution_pb2.BatchReadBlobsResponse()
- batch_size = 0
-
- for digest in request.digests:
- batch_size += digest.size_bytes
- if batch_size > _MAX_PAYLOAD_BYTES:
- context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
- return response
-
- blob_response = response.responses.add()
- blob_response.digest.hash = digest.hash
- blob_response.digest.size_bytes = digest.size_bytes
- try:
- with open(self.cas.objpath(digest), 'rb') as f:
- if os.fstat(f.fileno()).st_size != digest.size_bytes:
- blob_response.status.code = code_pb2.NOT_FOUND
- continue
-
- blob_response.data = f.read(digest.size_bytes)
- except FileNotFoundError:
- blob_response.status.code = code_pb2.NOT_FOUND
-
- return response
-
- def BatchUpdateBlobs(self, request, context):
- response = remote_execution_pb2.BatchUpdateBlobsResponse()
-
- if not self.enable_push:
- context.set_code(grpc.StatusCode.PERMISSION_DENIED)
- return response
-
- batch_size = 0
-
- for blob_request in request.requests:
- digest = blob_request.digest
-
- batch_size += digest.size_bytes
- if batch_size > _MAX_PAYLOAD_BYTES:
- context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
- return response
-
- blob_response = response.responses.add()
- blob_response.digest.hash = digest.hash
- blob_response.digest.size_bytes = digest.size_bytes
-
- if len(blob_request.data) != digest.size_bytes:
- blob_response.status.code = code_pb2.FAILED_PRECONDITION
- continue
-
- try:
- self.cache_cleaner.clean_up(digest.size_bytes)
-
- with tempfile.NamedTemporaryFile(dir=self.cas.tmpdir) as out:
- out.write(blob_request.data)
- out.flush()
- server_digest = self.cas.add_object(path=out.name)
- if server_digest.hash != digest.hash:
- blob_response.status.code = code_pb2.FAILED_PRECONDITION
-
- except ArtifactTooLargeException:
- blob_response.status.code = code_pb2.RESOURCE_EXHAUSTED
-
- return response
-
-
-class _CapabilitiesServicer(remote_execution_pb2_grpc.CapabilitiesServicer):
- def GetCapabilities(self, request, context):
- response = remote_execution_pb2.ServerCapabilities()
-
- cache_capabilities = response.cache_capabilities
- cache_capabilities.digest_function.append(remote_execution_pb2.SHA256)
- cache_capabilities.action_cache_update_capabilities.update_enabled = False
- cache_capabilities.max_batch_total_size_bytes = _MAX_PAYLOAD_BYTES
- cache_capabilities.symlink_absolute_path_strategy = remote_execution_pb2.CacheCapabilities.ALLOWED
-
- response.deprecated_api_version.major = 2
- response.low_api_version.major = 2
- response.high_api_version.major = 2
-
- return response
-
-
-class _ReferenceStorageServicer(buildstream_pb2_grpc.ReferenceStorageServicer):
- def __init__(self, cas, *, enable_push):
- super().__init__()
- self.cas = cas
- self.enable_push = enable_push
-
- def GetReference(self, request, context):
- response = buildstream_pb2.GetReferenceResponse()
-
- try:
- tree = self.cas.resolve_ref(request.key, update_mtime=True)
- try:
- self.cas.update_tree_mtime(tree)
- except FileNotFoundError:
- self.cas.remove(request.key, defer_prune=True)
- context.set_code(grpc.StatusCode.NOT_FOUND)
- return response
-
- response.digest.hash = tree.hash
- response.digest.size_bytes = tree.size_bytes
- except CASError:
- context.set_code(grpc.StatusCode.NOT_FOUND)
-
- return response
-
- def UpdateReference(self, request, context):
- response = buildstream_pb2.UpdateReferenceResponse()
-
- if not self.enable_push:
- context.set_code(grpc.StatusCode.PERMISSION_DENIED)
- return response
-
- for key in request.keys:
- self.cas.set_ref(key, request.digest)
-
- return response
-
- def Status(self, request, context):
- response = buildstream_pb2.StatusResponse()
-
- response.allow_updates = self.enable_push
-
- return response
-
-
-class _ArtifactServicer(artifact_pb2_grpc.ArtifactServiceServicer):
-
- def __init__(self, cas, artifactdir):
- super().__init__()
- self.cas = cas
- self.artifactdir = artifactdir
- os.makedirs(artifactdir, exist_ok=True)
-
- def GetArtifact(self, request, context):
- artifact_path = os.path.join(self.artifactdir, request.cache_key)
- if not os.path.exists(artifact_path):
- context.abort(grpc.StatusCode.NOT_FOUND, "Artifact proto not found")
-
- artifact = artifact_pb2.Artifact()
- with open(artifact_path, 'rb') as f:
- artifact.ParseFromString(f.read())
-
- # Now update mtimes of files present.
- try:
-
- if str(artifact.files):
- self.cas.update_tree_mtime(artifact.files)
-
- if str(artifact.buildtree):
- # buildtrees might not be there
- try:
- self.cas.update_tree_mtime(artifact.buildtree)
- except FileNotFoundError:
- pass
-
- if str(artifact.public_data):
- os.utime(self.cas.objpath(artifact.public_data))
-
- for log_file in artifact.logs:
- os.utime(self.cas.objpath(log_file.digest))
-
- except FileNotFoundError:
- os.unlink(artifact_path)
- context.abort(grpc.StatusCode.NOT_FOUND,
- "Artifact files incomplete")
- except DecodeError:
- context.abort(grpc.StatusCode.NOT_FOUND,
- "Artifact files not valid")
-
- return artifact
-
- def UpdateArtifact(self, request, context):
- artifact = request.artifact
-
- # Check that the files specified are in the CAS
- self._check_directory("files", artifact.files, context)
-
- # Unset protocol buffers don't evaluated to False but do return empty
- # strings, hence str()
- if str(artifact.public_data):
- self._check_file("public data", artifact.public_data, context)
-
- for log_file in artifact.logs:
- self._check_file("log digest", log_file.digest, context)
-
- # Add the artifact proto to the cas
- artifact_path = os.path.join(self.artifactdir, request.cache_key)
- os.makedirs(os.path.dirname(artifact_path), exist_ok=True)
- with open(artifact_path, 'wb') as f:
- f.write(artifact.SerializeToString())
-
- return artifact
-
- def _check_directory(self, name, digest, context):
- try:
- directory = remote_execution_pb2.Directory()
- with open(self.cas.objpath(digest), 'rb') as f:
- directory.ParseFromString(f.read())
- except FileNotFoundError:
- context.abort(grpc.StatusCode.FAILED_PRECONDITION,
- "Artifact {} specified but no files found".format(name))
- except DecodeError:
- context.abort(grpc.StatusCode.FAILED_PRECONDITION,
- "Artifact {} specified but directory not found".format(name))
-
- def _check_file(self, name, digest, context):
- if not os.path.exists(self.cas.objpath(digest)):
- context.abort(grpc.StatusCode.FAILED_PRECONDITION,
- "Artifact {} specified but not found".format(name))
-
-
-def _digest_from_download_resource_name(resource_name):
- parts = resource_name.split('/')
-
- # Accept requests from non-conforming BuildStream 1.1.x clients
- if len(parts) == 2:
- parts.insert(0, 'blobs')
-
- if len(parts) != 3 or parts[0] != 'blobs':
- return None
-
- try:
- digest = remote_execution_pb2.Digest()
- digest.hash = parts[1]
- digest.size_bytes = int(parts[2])
- return digest
- except ValueError:
- return None
-
-
-def _digest_from_upload_resource_name(resource_name):
- parts = resource_name.split('/')
-
- # Accept requests from non-conforming BuildStream 1.1.x clients
- if len(parts) == 2:
- parts.insert(0, 'uploads')
- parts.insert(1, str(uuid.uuid4()))
- parts.insert(2, 'blobs')
-
- if len(parts) < 5 or parts[0] != 'uploads' or parts[2] != 'blobs':
- return None
-
- try:
- uuid_ = uuid.UUID(hex=parts[1])
- if uuid_.version != 4:
- return None
-
- digest = remote_execution_pb2.Digest()
- digest.hash = parts[3]
- digest.size_bytes = int(parts[4])
- return digest
- except ValueError:
- return None
-
-
-class _CacheCleaner:
-
- __cleanup_cache_lock = threading.Lock()
-
- def __init__(self, cas, max_head_size, min_head_size=int(2e9)):
- self.__cas = cas
- self.__max_head_size = max_head_size
- self.__min_head_size = min_head_size
-
- def __has_space(self, object_size):
- stats = os.statvfs(self.__cas.casdir)
- free_disk_space = (stats.f_bavail * stats.f_bsize) - self.__min_head_size
- total_disk_space = (stats.f_blocks * stats.f_bsize) - self.__min_head_size
-
- if object_size > total_disk_space:
- raise ArtifactTooLargeException("Artifact of size: {} is too large for "
- "the filesystem which mounts the remote "
- "cache".format(object_size))
-
- return object_size <= free_disk_space
-
- # _clean_up_cache()
- #
- # Keep removing Least Recently Pushed (LRP) artifacts in a cache until there
- # is enough space for the incoming artifact
- #
- # Args:
- # object_size: The size of the object being received in bytes
- #
- # Returns:
- # int: The total bytes removed on the filesystem
- #
- def clean_up(self, object_size):
- if self.__has_space(object_size):
- return 0
-
- with _CacheCleaner.__cleanup_cache_lock:
- if self.__has_space(object_size):
- # Another thread has done the cleanup for us
- return 0
-
- stats = os.statvfs(self.__cas.casdir)
- target_disk_space = (stats.f_bavail * stats.f_bsize) - self.__max_head_size
-
- # obtain a list of LRP artifacts
- LRP_objects = self.__cas.list_objects()
-
- removed_size = 0 # in bytes
- last_mtime = 0
-
- while object_size - removed_size > target_disk_space:
- try:
- last_mtime, to_remove = LRP_objects.pop(0) # The first element in the list is the LRP artifact
- except IndexError:
- # This exception is caught if there are no more artifacts in the list
- # LRP_artifacts. This means the the artifact is too large for the filesystem
- # so we abort the process
- raise ArtifactTooLargeException("Artifact of size {} is too large for "
- "the filesystem which mounts the remote "
- "cache".format(object_size))
-
- try:
- size = os.stat(to_remove).st_size
- os.unlink(to_remove)
- removed_size += size
- except FileNotFoundError:
- pass
-
- self.__cas.clean_up_refs_until(last_mtime)
-
- if removed_size > 0:
- logging.info("Successfully removed %d bytes from the cache", removed_size)
- else:
- logging.info("No artifacts were removed from the cache.")
-
- return removed_size
diff --git a/buildstream/_context.py b/buildstream/_context.py
deleted file mode 100644
index 151ea636a..000000000
--- a/buildstream/_context.py
+++ /dev/null
@@ -1,766 +0,0 @@
-#
-# Copyright (C) 2016-2018 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>
-
-import os
-import shutil
-import datetime
-from collections import deque
-from collections.abc import Mapping
-from contextlib import contextmanager
-from . import utils
-from . import _cachekey
-from . import _signals
-from . import _site
-from . import _yaml
-from ._exceptions import LoadError, LoadErrorReason, BstError
-from ._message import Message, MessageType
-from ._profile import Topics, PROFILER
-from ._artifactcache import ArtifactCache
-from ._sourcecache import SourceCache
-from ._cas import CASCache, CASQuota, CASCacheUsage
-from ._workspaces import Workspaces, WorkspaceProjectCache
-from .plugin import Plugin
-from .sandbox import SandboxRemote
-
-
-# Context()
-#
-# The Context object holds all of the user preferences
-# and context for a given invocation of BuildStream.
-#
-# This is a collection of data from configuration files and command
-# line arguments and consists of information such as where to store
-# logs and artifacts, where to perform builds and cache downloaded sources,
-# verbosity levels and basically anything pertaining to the context
-# in which BuildStream was invoked.
-#
-# Args:
-# directory (str): The directory that buildstream was invoked in
-#
-class Context():
-
- def __init__(self, directory=None):
-
- # Filename indicating which configuration file was used, or None for the defaults
- self.config_origin = None
-
- # The directory under which other directories are based
- self.cachedir = None
-
- # The directory where various sources are stored
- self.sourcedir = None
-
- # specs for source cache remotes
- self.source_cache_specs = None
-
- # The directory where build sandboxes will be created
- self.builddir = None
-
- # The directory for CAS
- self.casdir = None
-
- # The directory for artifact protos
- self.artifactdir = None
-
- # The directory for temporary files
- self.tmpdir = None
-
- # Default root location for workspaces
- self.workspacedir = None
-
- # The locations from which to push and pull prebuilt artifacts
- self.artifact_cache_specs = None
-
- # The global remote execution configuration
- self.remote_execution_specs = None
-
- # The directory to store build logs
- self.logdir = None
-
- # The abbreviated cache key length to display in the UI
- self.log_key_length = None
-
- # Whether debug mode is enabled
- self.log_debug = None
-
- # Whether verbose mode is enabled
- self.log_verbose = None
-
- # Maximum number of lines to print from build logs
- self.log_error_lines = None
-
- # Maximum number of lines to print in the master log for a detailed message
- self.log_message_lines = None
-
- # Format string for printing the pipeline at startup time
- self.log_element_format = None
-
- # Format string for printing message lines in the master log
- self.log_message_format = None
-
- # Maximum number of fetch or refresh tasks
- self.sched_fetchers = None
-
- # Maximum number of build tasks
- self.sched_builders = None
-
- # Maximum number of push tasks
- self.sched_pushers = None
-
- # Maximum number of retries for network tasks
- self.sched_network_retries = None
-
- # What to do when a build fails in non interactive mode
- self.sched_error_action = None
-
- # Size of the artifact cache in bytes
- self.config_cache_quota = None
-
- # User specified cache quota, used for display messages
- self.config_cache_quota_string = None
-
- # Whether or not to attempt to pull build trees globally
- self.pull_buildtrees = None
-
- # Whether or not to cache build trees on artifact creation
- self.cache_buildtrees = None
-
- # Whether directory trees are required for all artifacts in the local cache
- self.require_artifact_directories = True
-
- # Whether file contents are required for all artifacts in the local cache
- self.require_artifact_files = True
-
- # Whether elements must be rebuilt when their dependencies have changed
- self._strict_build_plan = None
-
- # Make sure the XDG vars are set in the environment before loading anything
- self._init_xdg()
-
- # Private variables
- self._cache_key = None
- self._message_handler = None
- self._message_depth = deque()
- self._artifactcache = None
- self._sourcecache = None
- self._projects = []
- self._project_overrides = _yaml.new_empty_node()
- self._workspaces = None
- self._workspace_project_cache = WorkspaceProjectCache()
- self._log_handle = None
- self._log_filename = None
- self._cascache = None
- self._casquota = None
- self._directory = directory
-
- # load()
- #
- # Loads the configuration files
- #
- # Args:
- # config (filename): The user specified configuration file, if any
- #
-
- # Raises:
- # LoadError
- #
- # This will first load the BuildStream default configuration and then
- # override that configuration with the configuration file indicated
- # by *config*, if any was specified.
- #
- @PROFILER.profile(Topics.LOAD_CONTEXT, "load")
- def load(self, config=None):
- # If a specific config file is not specified, default to trying
- # a $XDG_CONFIG_HOME/buildstream.conf file
- #
- if not config:
- default_config = os.path.join(os.environ['XDG_CONFIG_HOME'],
- 'buildstream.conf')
- if os.path.exists(default_config):
- config = default_config
-
- # Load default config
- #
- defaults = _yaml.load(_site.default_user_config)
-
- if config:
- self.config_origin = os.path.abspath(config)
- user_config = _yaml.load(config)
- _yaml.composite(defaults, user_config)
-
- # Give obsoletion warnings
- if 'builddir' in defaults:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "builddir is obsolete, use cachedir")
-
- if 'artifactdir' in defaults:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "artifactdir is obsolete")
-
- _yaml.node_validate(defaults, [
- 'cachedir', 'sourcedir', 'builddir', 'logdir', 'scheduler',
- 'artifacts', 'source-caches', 'logging', 'projects', 'cache', 'prompt',
- 'workspacedir', 'remote-execution',
- ])
-
- for directory in ['cachedir', 'sourcedir', 'logdir', 'workspacedir']:
- # Allow the ~ tilde expansion and any environment variables in
- # path specification in the config files.
- #
- path = _yaml.node_get(defaults, str, directory)
- path = os.path.expanduser(path)
- path = os.path.expandvars(path)
- path = os.path.normpath(path)
- setattr(self, directory, path)
-
- # add directories not set by users
- self.tmpdir = os.path.join(self.cachedir, 'tmp')
- self.casdir = os.path.join(self.cachedir, 'cas')
- self.builddir = os.path.join(self.cachedir, 'build')
- self.artifactdir = os.path.join(self.cachedir, 'artifacts', 'refs')
-
- # Move old artifact cas to cas if it exists and create symlink
- old_casdir = os.path.join(self.cachedir, 'artifacts', 'cas')
- if (os.path.exists(old_casdir) and not os.path.islink(old_casdir) and
- not os.path.exists(self.casdir)):
- os.rename(old_casdir, self.casdir)
- os.symlink(self.casdir, old_casdir)
-
- # Cleanup old extract directories
- old_extractdirs = [os.path.join(self.cachedir, 'artifacts', 'extract'),
- os.path.join(self.cachedir, 'extract')]
- for old_extractdir in old_extractdirs:
- if os.path.isdir(old_extractdir):
- shutil.rmtree(old_extractdir, ignore_errors=True)
-
- # Load quota configuration
- # We need to find the first existing directory in the path of our
- # cachedir - the cachedir may not have been created yet.
- cache = _yaml.node_get(defaults, Mapping, 'cache')
- _yaml.node_validate(cache, ['quota', 'pull-buildtrees', 'cache-buildtrees'])
-
- self.config_cache_quota_string = _yaml.node_get(cache, str, 'quota')
- try:
- self.config_cache_quota = utils._parse_size(self.config_cache_quota_string,
- self.casdir)
- except utils.UtilError as e:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}\nPlease specify the value in bytes or as a % of full disk space.\n"
- "\nValid values are, for example: 800M 10G 1T 50%\n"
- .format(str(e))) from e
-
- # Load artifact share configuration
- self.artifact_cache_specs = ArtifactCache.specs_from_config_node(defaults)
-
- # Load source cache config
- self.source_cache_specs = SourceCache.specs_from_config_node(defaults)
-
- self.remote_execution_specs = SandboxRemote.specs_from_config_node(defaults)
-
- # Load pull build trees configuration
- self.pull_buildtrees = _yaml.node_get(cache, bool, 'pull-buildtrees')
-
- # Load cache build trees configuration
- self.cache_buildtrees = _node_get_option_str(
- cache, 'cache-buildtrees', ['always', 'auto', 'never'])
-
- # Load logging config
- logging = _yaml.node_get(defaults, Mapping, 'logging')
- _yaml.node_validate(logging, [
- 'key-length', 'verbose',
- 'error-lines', 'message-lines',
- 'debug', 'element-format', 'message-format'
- ])
- self.log_key_length = _yaml.node_get(logging, int, 'key-length')
- self.log_debug = _yaml.node_get(logging, bool, 'debug')
- self.log_verbose = _yaml.node_get(logging, bool, 'verbose')
- self.log_error_lines = _yaml.node_get(logging, int, 'error-lines')
- self.log_message_lines = _yaml.node_get(logging, int, 'message-lines')
- self.log_element_format = _yaml.node_get(logging, str, 'element-format')
- self.log_message_format = _yaml.node_get(logging, str, 'message-format')
-
- # Load scheduler config
- scheduler = _yaml.node_get(defaults, Mapping, 'scheduler')
- _yaml.node_validate(scheduler, [
- 'on-error', 'fetchers', 'builders',
- 'pushers', 'network-retries'
- ])
- self.sched_error_action = _node_get_option_str(
- scheduler, 'on-error', ['continue', 'quit', 'terminate'])
- self.sched_fetchers = _yaml.node_get(scheduler, int, 'fetchers')
- self.sched_builders = _yaml.node_get(scheduler, int, 'builders')
- self.sched_pushers = _yaml.node_get(scheduler, int, 'pushers')
- self.sched_network_retries = _yaml.node_get(scheduler, int, 'network-retries')
-
- # Load per-projects overrides
- self._project_overrides = _yaml.node_get(defaults, dict, 'projects', default_value={})
-
- # Shallow validation of overrides, parts of buildstream which rely
- # on the overrides are expected to validate elsewhere.
- for _, overrides in _yaml.node_items(self._project_overrides):
- _yaml.node_validate(overrides,
- ['artifacts', 'source-caches', 'options',
- 'strict', 'default-mirror',
- 'remote-execution'])
-
- @property
- def artifactcache(self):
- if not self._artifactcache:
- self._artifactcache = ArtifactCache(self)
-
- return self._artifactcache
-
- # get_cache_usage()
- #
- # Fetches the current usage of the artifact cache
- #
- # Returns:
- # (CASCacheUsage): The current status
- #
- def get_cache_usage(self):
- return CASCacheUsage(self.get_casquota())
-
- @property
- def sourcecache(self):
- if not self._sourcecache:
- self._sourcecache = SourceCache(self)
-
- return self._sourcecache
-
- # add_project():
- #
- # Add a project to the context.
- #
- # Args:
- # project (Project): The project to add
- #
- def add_project(self, project):
- if not self._projects:
- self._workspaces = Workspaces(project, self._workspace_project_cache)
- self._projects.append(project)
-
- # get_projects():
- #
- # Return the list of projects in the context.
- #
- # Returns:
- # (list): The list of projects
- #
- def get_projects(self):
- return self._projects
-
- # get_toplevel_project():
- #
- # Return the toplevel project, the one which BuildStream was
- # invoked with as opposed to a junctioned subproject.
- #
- # Returns:
- # (Project): The Project object
- #
- def get_toplevel_project(self):
- return self._projects[0]
-
- # get_workspaces():
- #
- # Return a Workspaces object containing a list of workspaces.
- #
- # Returns:
- # (Workspaces): The Workspaces object
- #
- def get_workspaces(self):
- return self._workspaces
-
- # get_workspace_project_cache():
- #
- # Return the WorkspaceProjectCache object used for this BuildStream invocation
- #
- # Returns:
- # (WorkspaceProjectCache): The WorkspaceProjectCache object
- #
- def get_workspace_project_cache(self):
- return self._workspace_project_cache
-
- # get_overrides():
- #
- # Fetch the override dictionary for the active project. This returns
- # a node loaded from YAML and as such, values loaded from the returned
- # node should be loaded using the _yaml.node_get() family of functions.
- #
- # Args:
- # project_name (str): The project name
- #
- # Returns:
- # (Mapping): The overrides dictionary for the specified project
- #
- def get_overrides(self, project_name):
- return _yaml.node_get(self._project_overrides, Mapping, project_name, default_value={})
-
- # get_strict():
- #
- # Fetch whether we are strict or not
- #
- # Returns:
- # (bool): Whether or not to use strict build plan
- #
- def get_strict(self):
- if self._strict_build_plan is None:
- # Either we're not overridden or we've never worked it out before
- # so work out if we should be strict, and then cache the result
- toplevel = self.get_toplevel_project()
- overrides = self.get_overrides(toplevel.name)
- self._strict_build_plan = _yaml.node_get(overrides, bool, 'strict', default_value=True)
-
- # If it was set by the CLI, it overrides any config
- # Ditto if we've already computed this, then we return the computed
- # value which we cache here too.
- return self._strict_build_plan
-
- # get_cache_key():
- #
- # Returns the cache key, calculating it if necessary
- #
- # Returns:
- # (str): A hex digest cache key for the Context
- #
- def get_cache_key(self):
- if self._cache_key is None:
-
- # Anything that alters the build goes into the unique key
- self._cache_key = _cachekey.generate_key(_yaml.new_empty_node())
-
- return self._cache_key
-
- # set_message_handler()
- #
- # Sets the handler for any status messages propagated through
- # the context.
- #
- # The message handler should have the same signature as
- # the message() method
- def set_message_handler(self, handler):
- self._message_handler = handler
-
- # silent_messages():
- #
- # Returns:
- # (bool): Whether messages are currently being silenced
- #
- def silent_messages(self):
- for silent in self._message_depth:
- if silent:
- return True
- return False
-
- # message():
- #
- # Proxies a message back to the caller, this is the central
- # point through which all messages pass.
- #
- # Args:
- # message: A Message object
- #
- def message(self, message):
-
- # Tag message only once
- if message.depth is None:
- message.depth = len(list(self._message_depth))
-
- # If we are recording messages, dump a copy into the open log file.
- self._record_message(message)
-
- # Send it off to the log handler (can be the frontend,
- # or it can be the child task which will propagate
- # to the frontend)
- assert self._message_handler
-
- self._message_handler(message, context=self)
-
- # silence()
- #
- # A context manager to silence messages, this behaves in
- # the same way as the `silent_nested` argument of the
- # Context._timed_activity() context manager: especially
- # important messages will not be silenced.
- #
- @contextmanager
- def silence(self):
- self._push_message_depth(True)
- try:
- yield
- finally:
- self._pop_message_depth()
-
- # timed_activity()
- #
- # Context manager for performing timed activities and logging those
- #
- # Args:
- # context (Context): The invocation context object
- # activity_name (str): The name of the activity
- # detail (str): An optional detailed message, can be multiline output
- # silent_nested (bool): If specified, nested messages will be silenced
- #
- @contextmanager
- def timed_activity(self, activity_name, *, unique_id=None, detail=None, silent_nested=False):
-
- starttime = datetime.datetime.now()
- stopped_time = None
-
- def stop_time():
- nonlocal stopped_time
- stopped_time = datetime.datetime.now()
-
- def resume_time():
- nonlocal stopped_time
- nonlocal starttime
- sleep_time = datetime.datetime.now() - stopped_time
- starttime += sleep_time
-
- with _signals.suspendable(stop_time, resume_time):
- try:
- # Push activity depth for status messages
- message = Message(unique_id, MessageType.START, activity_name, detail=detail)
- self.message(message)
- self._push_message_depth(silent_nested)
- yield
-
- except BstError:
- # Note the failure in status messages and reraise, the scheduler
- # expects an error when there is an error.
- elapsed = datetime.datetime.now() - starttime
- message = Message(unique_id, MessageType.FAIL, activity_name, elapsed=elapsed)
- self._pop_message_depth()
- self.message(message)
- raise
-
- elapsed = datetime.datetime.now() - starttime
- message = Message(unique_id, MessageType.SUCCESS, activity_name, elapsed=elapsed)
- self._pop_message_depth()
- self.message(message)
-
- # recorded_messages()
- #
- # Records all messages in a log file while the context manager
- # is active.
- #
- # In addition to automatically writing all messages to the
- # specified logging file, an open file handle for process stdout
- # and stderr will be available via the Context.get_log_handle() API,
- # and the full logfile path will be available via the
- # Context.get_log_filename() API.
- #
- # Args:
- # filename (str): A logging directory relative filename,
- # the pid and .log extension will be automatically
- # appended
- #
- # Yields:
- # (str): The fully qualified log filename
- #
- @contextmanager
- def recorded_messages(self, filename):
-
- # We dont allow recursing in this context manager, and
- # we also do not allow it in the main process.
- assert self._log_handle is None
- assert self._log_filename is None
- assert not utils._is_main_process()
-
- # Create the fully qualified logfile in the log directory,
- # appending the pid and .log extension at the end.
- self._log_filename = os.path.join(self.logdir,
- '{}.{}.log'.format(filename, os.getpid()))
-
- # Ensure the directory exists first
- directory = os.path.dirname(self._log_filename)
- os.makedirs(directory, exist_ok=True)
-
- with open(self._log_filename, 'a') as logfile:
-
- # Write one last line to the log and flush it to disk
- def flush_log():
-
- # If the process currently had something happening in the I/O stack
- # then trying to reenter the I/O stack will fire a runtime error.
- #
- # So just try to flush as well as we can at SIGTERM time
- try:
- logfile.write('\n\nForcefully terminated\n')
- logfile.flush()
- except RuntimeError:
- os.fsync(logfile.fileno())
-
- self._log_handle = logfile
- with _signals.terminator(flush_log):
- yield self._log_filename
-
- self._log_handle = None
- self._log_filename = None
-
- # get_log_handle()
- #
- # Fetches the active log handle, this will return the active
- # log file handle when the Context.recorded_messages() context
- # manager is active
- #
- # Returns:
- # (file): The active logging file handle, or None
- #
- def get_log_handle(self):
- return self._log_handle
-
- # get_log_filename()
- #
- # Fetches the active log filename, this will return the active
- # log filename when the Context.recorded_messages() context
- # manager is active
- #
- # Returns:
- # (str): The active logging filename, or None
- #
- def get_log_filename(self):
- return self._log_filename
-
- # set_artifact_directories_optional()
- #
- # This indicates that the current context (command or configuration)
- # does not require directory trees of all artifacts to be available in the
- # local cache.
- #
- def set_artifact_directories_optional(self):
- self.require_artifact_directories = False
- self.require_artifact_files = False
-
- # set_artifact_files_optional()
- #
- # This indicates that the current context (command or configuration)
- # does not require file contents of all artifacts to be available in the
- # local cache.
- #
- def set_artifact_files_optional(self):
- self.require_artifact_files = False
-
- # _record_message()
- #
- # Records the message if recording is enabled
- #
- # Args:
- # message (Message): The message to record
- #
- def _record_message(self, message):
-
- if self._log_handle is None:
- return
-
- INDENT = " "
- EMPTYTIME = "--:--:--"
- template = "[{timecode: <8}] {type: <7}"
-
- # If this message is associated with a plugin, print what
- # we know about the plugin.
- plugin_name = ""
- if message.unique_id:
- template += " {plugin}"
- plugin = Plugin._lookup(message.unique_id)
- plugin_name = plugin.name
-
- template += ": {message}"
-
- detail = ''
- if message.detail is not None:
- template += "\n\n{detail}"
- detail = message.detail.rstrip('\n')
- detail = INDENT + INDENT.join(detail.splitlines(True))
-
- timecode = EMPTYTIME
- if message.message_type in (MessageType.SUCCESS, MessageType.FAIL):
- hours, remainder = divmod(int(message.elapsed.total_seconds()), 60**2)
- minutes, seconds = divmod(remainder, 60)
- timecode = "{0:02d}:{1:02d}:{2:02d}".format(hours, minutes, seconds)
-
- text = template.format(timecode=timecode,
- plugin=plugin_name,
- type=message.message_type.upper(),
- message=message.message,
- detail=detail)
-
- # Write to the open log file
- self._log_handle.write('{}\n'.format(text))
- self._log_handle.flush()
-
- # _push_message_depth() / _pop_message_depth()
- #
- # For status messages, send the depth of timed
- # activities inside a given task through the message
- #
- def _push_message_depth(self, silent_nested):
- self._message_depth.appendleft(silent_nested)
-
- def _pop_message_depth(self):
- assert self._message_depth
- self._message_depth.popleft()
-
- # Force the resolved XDG variables into the environment,
- # this is so that they can be used directly to specify
- # preferred locations of things from user configuration
- # files.
- def _init_xdg(self):
- if not os.environ.get('XDG_CACHE_HOME'):
- os.environ['XDG_CACHE_HOME'] = os.path.expanduser('~/.cache')
- if not os.environ.get('XDG_CONFIG_HOME'):
- os.environ['XDG_CONFIG_HOME'] = os.path.expanduser('~/.config')
- if not os.environ.get('XDG_DATA_HOME'):
- os.environ['XDG_DATA_HOME'] = os.path.expanduser('~/.local/share')
-
- def get_cascache(self):
- if self._cascache is None:
- self._cascache = CASCache(self.cachedir)
- return self._cascache
-
- def get_casquota(self):
- if self._casquota is None:
- self._casquota = CASQuota(self)
- return self._casquota
-
-
-# _node_get_option_str()
-#
-# Like _yaml.node_get(), but also checks value is one of the allowed option
-# strings. Fetches a value from a dictionary node, and makes sure it's one of
-# the pre-defined options.
-#
-# Args:
-# node (dict): The dictionary node
-# key (str): The key to get a value for in node
-# allowed_options (iterable): Only accept these values
-#
-# Returns:
-# The value, if found in 'node'.
-#
-# Raises:
-# LoadError, when the value is not of the expected type, or is not found.
-#
-def _node_get_option_str(node, key, allowed_options):
- result = _yaml.node_get(node, str, key)
- if result not in allowed_options:
- provenance = _yaml.node_get_provenance(node, key)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: {} should be one of: {}".format(
- provenance, key, ", ".join(allowed_options)))
- return result
diff --git a/buildstream/_elementfactory.py b/buildstream/_elementfactory.py
deleted file mode 100644
index d6591bf4c..000000000
--- a/buildstream/_elementfactory.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-from . import _site
-from ._plugincontext import PluginContext
-from .element import Element
-
-
-# A ElementFactory creates Element instances
-# in the context of a given factory
-#
-# Args:
-# plugin_base (PluginBase): The main PluginBase object to work with
-# plugin_origins (list): Data used to search for external Element plugins
-#
-class ElementFactory(PluginContext):
-
- def __init__(self, plugin_base, *,
- format_versions={},
- plugin_origins=None):
-
- super().__init__(plugin_base, Element, [_site.element_plugins],
- plugin_origins=plugin_origins,
- format_versions=format_versions)
-
- # create():
- #
- # Create an Element object, the pipeline uses this to create Element
- # objects on demand for a given pipeline.
- #
- # Args:
- # context (object): The Context object for processing
- # project (object): The project object
- # meta (object): The loaded MetaElement
- #
- # Returns: A newly created Element object of the appropriate kind
- #
- # Raises:
- # PluginError (if the kind lookup failed)
- # LoadError (if the element itself took issue with the config)
- #
- def create(self, context, project, meta):
- element_type, default_config = self.lookup(meta.kind)
- element = element_type(context, project, meta, default_config)
- version = self._format_versions.get(meta.kind, 0)
- self._assert_plugin_format(element, version)
- return element
diff --git a/buildstream/_exceptions.py b/buildstream/_exceptions.py
deleted file mode 100644
index f2d34bcba..000000000
--- a/buildstream/_exceptions.py
+++ /dev/null
@@ -1,370 +0,0 @@
-#
-# Copyright (C) 2018 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>
-# Tiago Gomes <tiago.gomes@codethink.co.uk>
-
-from enum import Enum
-import os
-
-# Disable pylint warnings for whole file here:
-# pylint: disable=global-statement
-
-# The last raised exception, this is used in test cases only
-_last_exception = None
-_last_task_error_domain = None
-_last_task_error_reason = None
-
-
-# get_last_exception()
-#
-# Fetches the last exception from the main process
-#
-# Used by regression tests
-#
-def get_last_exception():
- global _last_exception
-
- le = _last_exception
- _last_exception = None
- return le
-
-
-# get_last_task_error()
-#
-# Fetches the last exception from a task
-#
-# Used by regression tests
-#
-def get_last_task_error():
- if 'BST_TEST_SUITE' not in os.environ:
- raise BstError("Getting the last task error is only supported when running tests")
-
- global _last_task_error_domain
- global _last_task_error_reason
-
- d = _last_task_error_domain
- r = _last_task_error_reason
- _last_task_error_domain = _last_task_error_reason = None
- return (d, r)
-
-
-# set_last_task_error()
-#
-# Sets the last exception of a task
-#
-# This is set by some internals to inform regression
-# tests about how things failed in a machine readable way
-#
-def set_last_task_error(domain, reason):
- if 'BST_TEST_SUITE' in os.environ:
- global _last_task_error_domain
- global _last_task_error_reason
-
- _last_task_error_domain = domain
- _last_task_error_reason = reason
-
-
-class ErrorDomain(Enum):
- PLUGIN = 1
- LOAD = 2
- IMPL = 3
- PLATFORM = 4
- SANDBOX = 5
- ARTIFACT = 6
- PIPELINE = 7
- OSTREE = 8
- UTIL = 9
- PROG_NOT_FOUND = 12
- SOURCE = 10
- ELEMENT = 11
- APP = 12
- STREAM = 13
- VIRTUAL_FS = 14
- CAS = 15
-
-
-# BstError is an internal base exception class for BuildSream
-# exceptions.
-#
-# The sole purpose of using the base class is to add additional
-# context to exceptions raised by plugins in child tasks, this
-# context can then be communicated back to the main process.
-#
-class BstError(Exception):
-
- def __init__(self, message, *, detail=None, domain=None, reason=None, temporary=False):
- global _last_exception
-
- super().__init__(message)
-
- # Additional error detail, these are used to construct detail
- # portions of the logging messages when encountered.
- #
- self.detail = detail
-
- # A sandbox can be created to debug this error
- self.sandbox = False
-
- # When this exception occurred during the handling of a job, indicate
- # whether or not there is any point retrying the job.
- #
- self.temporary = temporary
-
- # Error domain and reason
- #
- self.domain = domain
- self.reason = reason
-
- # Hold on to the last raised exception for testing purposes
- if 'BST_TEST_SUITE' in os.environ:
- _last_exception = self
-
-
-# PluginError
-#
-# Raised on plugin related errors.
-#
-# This exception is raised either by the plugin loading process,
-# or by the base :class:`.Plugin` element itself.
-#
-class PluginError(BstError):
- def __init__(self, message, reason=None, temporary=False):
- super().__init__(message, domain=ErrorDomain.PLUGIN, reason=reason, temporary=False)
-
-
-# LoadErrorReason
-#
-# Describes the reason why a :class:`.LoadError` was raised.
-#
-class LoadErrorReason(Enum):
-
- # A file was not found.
- MISSING_FILE = 1
-
- # The parsed data was not valid YAML.
- INVALID_YAML = 2
-
- # Data was malformed, a value was not of the expected type, etc
- INVALID_DATA = 3
-
- # An error occurred during YAML dictionary composition.
- #
- # This can happen by overriding a value with a new differently typed
- # value, or by overwriting some named value when that was not allowed.
- ILLEGAL_COMPOSITE = 4
-
- # An circular dependency chain was detected
- CIRCULAR_DEPENDENCY = 5
-
- # A variable could not be resolved. This can happen if your project
- # has cyclic dependencies in variable declarations, or, when substituting
- # a string which refers to an undefined variable.
- UNRESOLVED_VARIABLE = 6
-
- # BuildStream does not support the required project format version
- UNSUPPORTED_PROJECT = 7
-
- # Project requires a newer version of a plugin than the one which was loaded
- UNSUPPORTED_PLUGIN = 8
-
- # A conditional expression failed to resolve
- EXPRESSION_FAILED = 9
-
- # An assertion was intentionally encoded into project YAML
- USER_ASSERTION = 10
-
- # A list composition directive did not apply to any underlying list
- TRAILING_LIST_DIRECTIVE = 11
-
- # Conflicting junctions in subprojects
- CONFLICTING_JUNCTION = 12
-
- # Failure to load a project from a specified junction
- INVALID_JUNCTION = 13
-
- # Subproject needs to be fetched
- SUBPROJECT_FETCH_NEEDED = 14
-
- # Subproject has no ref
- SUBPROJECT_INCONSISTENT = 15
-
- # An invalid symbol name was encountered
- INVALID_SYMBOL_NAME = 16
-
- # A project.conf file was missing
- MISSING_PROJECT_CONF = 17
-
- # Try to load a directory not a yaml file
- LOADING_DIRECTORY = 18
-
- # A project path leads outside of the project directory
- PROJ_PATH_INVALID = 19
-
- # A project path points to a file of the not right kind (e.g. a
- # socket)
- PROJ_PATH_INVALID_KIND = 20
-
- # A recursive include has been encountered.
- RECURSIVE_INCLUDE = 21
-
- # A recursive variable has been encountered
- RECURSIVE_VARIABLE = 22
-
- # An attempt so set the value of a protected variable
- PROTECTED_VARIABLE_REDEFINED = 23
-
-
-# LoadError
-#
-# Raised while loading some YAML.
-#
-# Args:
-# reason (LoadErrorReason): machine readable error reason
-# message (str): human readable error explanation
-#
-# This exception is raised when loading or parsing YAML, or when
-# interpreting project YAML
-#
-class LoadError(BstError):
- def __init__(self, reason, message, *, detail=None):
- super().__init__(message, detail=detail, domain=ErrorDomain.LOAD, reason=reason)
-
-
-# ImplError
-#
-# Raised when a :class:`.Source` or :class:`.Element` plugin fails to
-# implement a mandatory method
-#
-class ImplError(BstError):
- def __init__(self, message, reason=None):
- super().__init__(message, domain=ErrorDomain.IMPL, reason=reason)
-
-
-# PlatformError
-#
-# Raised if the current platform is not supported.
-class PlatformError(BstError):
- def __init__(self, message, reason=None):
- super().__init__(message, domain=ErrorDomain.PLATFORM, reason=reason)
-
-
-# SandboxError
-#
-# Raised when errors are encountered by the sandbox implementation
-#
-class SandboxError(BstError):
- def __init__(self, message, detail=None, reason=None):
- super().__init__(message, detail=detail, domain=ErrorDomain.SANDBOX, reason=reason)
-
-
-# SourceCacheError
-#
-# Raised when errors are encountered in the source caches
-#
-class SourceCacheError(BstError):
- def __init__(self, message, detail=None, reason=None):
- super().__init__(message, detail=detail, domain=ErrorDomain.SANDBOX, reason=reason)
-
-
-# ArtifactError
-#
-# Raised when errors are encountered in the artifact caches
-#
-class ArtifactError(BstError):
- def __init__(self, message, *, detail=None, reason=None, temporary=False):
- super().__init__(message, detail=detail, domain=ErrorDomain.ARTIFACT, reason=reason, temporary=True)
-
-
-# CASError
-#
-# Raised when errors are encountered in the CAS
-#
-class CASError(BstError):
- def __init__(self, message, *, detail=None, reason=None, temporary=False):
- super().__init__(message, detail=detail, domain=ErrorDomain.CAS, reason=reason, temporary=True)
-
-
-# CASRemoteError
-#
-# Raised when errors are encountered in the remote CAS
-class CASRemoteError(CASError):
- pass
-
-
-# CASCacheError
-#
-# Raised when errors are encountered in the local CASCacheError
-#
-class CASCacheError(CASError):
- pass
-
-
-# PipelineError
-#
-# Raised from pipeline operations
-#
-class PipelineError(BstError):
-
- def __init__(self, message, *, detail=None, reason=None):
- super().__init__(message, detail=detail, domain=ErrorDomain.PIPELINE, reason=reason)
-
-
-# StreamError
-#
-# Raised when a stream operation fails
-#
-class StreamError(BstError):
-
- def __init__(self, message=None, *, detail=None, reason=None, terminated=False):
-
- # The empty string should never appear to a user,
- # this only allows us to treat this internal error as
- # a BstError from the frontend.
- if message is None:
- message = ""
-
- super().__init__(message, detail=detail, domain=ErrorDomain.STREAM, reason=reason)
-
- self.terminated = terminated
-
-
-# AppError
-#
-# Raised from the frontend App directly
-#
-class AppError(BstError):
- def __init__(self, message, detail=None, reason=None):
- super().__init__(message, detail=detail, domain=ErrorDomain.APP, reason=reason)
-
-
-# SkipJob
-#
-# Raised from a child process within a job when the job should be
-# considered skipped by the parent process.
-#
-class SkipJob(Exception):
- pass
-
-
-# ArtifactElementError
-#
-# Raised when errors are encountered by artifact elements
-#
-class ArtifactElementError(BstError):
- def __init__(self, message, *, detail=None, reason=None):
- super().__init__(message, detail=detail, domain=ErrorDomain.ELEMENT, reason=reason)
diff --git a/buildstream/_frontend/__init__.py b/buildstream/_frontend/__init__.py
deleted file mode 100644
index febd4979d..000000000
--- a/buildstream/_frontend/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-import os
-from .cli import cli
-
-if "_BST_COMPLETION" not in os.environ:
- from .profile import Profile
- from .status import Status
- from .widget import LogLine
diff --git a/buildstream/_frontend/app.py b/buildstream/_frontend/app.py
deleted file mode 100644
index d4ea83871..000000000
--- a/buildstream/_frontend/app.py
+++ /dev/null
@@ -1,870 +0,0 @@
-#
-# Copyright (C) 2016-2018 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>
-
-from contextlib import contextmanager
-import os
-import sys
-import traceback
-import datetime
-from textwrap import TextWrapper
-import click
-from click import UsageError
-
-# Import buildstream public symbols
-from .. import Scope
-
-# Import various buildstream internals
-from .._context import Context
-from .._platform import Platform
-from .._project import Project
-from .._exceptions import BstError, StreamError, LoadError, LoadErrorReason, AppError
-from .._message import Message, MessageType, unconditional_messages
-from .._stream import Stream
-from .._versions import BST_FORMAT_VERSION
-from .. import _yaml
-from .._scheduler import ElementJob, JobStatus
-
-# Import frontend assets
-from .profile import Profile
-from .status import Status
-from .widget import LogLine
-
-# Intendation for all logging
-INDENT = 4
-
-
-# App()
-#
-# Main Application State
-#
-# Args:
-# main_options (dict): The main CLI options of the `bst`
-# command, before any subcommand
-#
-class App():
-
- def __init__(self, main_options):
-
- #
- # Public members
- #
- self.context = None # The Context object
- self.stream = None # The Stream object
- self.project = None # The toplevel Project object
- self.logger = None # The LogLine object
- self.interactive = None # Whether we are running in interactive mode
- self.colors = None # Whether to use colors in logging
-
- #
- # Private members
- #
- self._session_start = datetime.datetime.now()
- self._session_name = None
- self._main_options = main_options # Main CLI options, before any command
- self._status = None # The Status object
- self._fail_messages = {} # Failure messages by unique plugin id
- self._interactive_failures = None # Whether to handle failures interactively
- self._started = False # Whether a session has started
-
- # UI Colors Profiles
- self._content_profile = Profile(fg='yellow')
- self._format_profile = Profile(fg='cyan', dim=True)
- self._success_profile = Profile(fg='green')
- self._error_profile = Profile(fg='red', dim=True)
- self._detail_profile = Profile(dim=True)
-
- #
- # Earily initialization
- #
- is_a_tty = sys.stdout.isatty() and sys.stderr.isatty()
-
- # Enable interactive mode if we're attached to a tty
- if main_options['no_interactive']:
- self.interactive = False
- else:
- self.interactive = is_a_tty
-
- # Handle errors interactively if we're in interactive mode
- # and --on-error was not specified on the command line
- if main_options.get('on_error') is not None:
- self._interactive_failures = False
- else:
- self._interactive_failures = self.interactive
-
- # Use color output if we're attached to a tty, unless
- # otherwise specified on the comand line
- if main_options['colors'] is None:
- self.colors = is_a_tty
- elif main_options['colors']:
- self.colors = True
- else:
- self.colors = False
-
- # create()
- #
- # Should be used instead of the regular constructor.
- #
- # This will select a platform specific App implementation
- #
- # Args:
- # The same args as the App() constructor
- #
- @classmethod
- def create(cls, *args, **kwargs):
- if sys.platform.startswith('linux'):
- # Use an App with linux specific features
- from .linuxapp import LinuxApp # pylint: disable=cyclic-import
- return LinuxApp(*args, **kwargs)
- else:
- # The base App() class is default
- return App(*args, **kwargs)
-
- # initialized()
- #
- # Context manager to initialize the application and optionally run a session
- # within the context manager.
- #
- # This context manager will take care of catching errors from within the
- # context and report them consistently, so the CLI need not take care of
- # reporting the errors and exiting with a consistent error status.
- #
- # Args:
- # session_name (str): The name of the session, or None for no session
- #
- # Note that the except_ argument may have a subtly different meaning depending
- # on the activity performed on the Pipeline. In normal circumstances the except_
- # argument excludes elements from the `elements` list. In a build session, the
- # except_ elements are excluded from the tracking plan.
- #
- # If a session_name is provided, we treat the block as a session, and print
- # the session header and summary, and time the main session from startup time.
- #
- @contextmanager
- def initialized(self, *, session_name=None):
- directory = self._main_options['directory']
- config = self._main_options['config']
-
- self._session_name = session_name
-
- #
- # Load the Context
- #
- try:
- self.context = Context(directory)
- self.context.load(config)
- except BstError as e:
- self._error_exit(e, "Error loading user configuration")
-
- # Override things in the context from our command line options,
- # the command line when used, trumps the config files.
- #
- override_map = {
- 'strict': '_strict_build_plan',
- 'debug': 'log_debug',
- 'verbose': 'log_verbose',
- 'error_lines': 'log_error_lines',
- 'message_lines': 'log_message_lines',
- 'on_error': 'sched_error_action',
- 'fetchers': 'sched_fetchers',
- 'builders': 'sched_builders',
- 'pushers': 'sched_pushers',
- 'network_retries': 'sched_network_retries',
- 'pull_buildtrees': 'pull_buildtrees',
- 'cache_buildtrees': 'cache_buildtrees'
- }
- for cli_option, context_attr in override_map.items():
- option_value = self._main_options.get(cli_option)
- if option_value is not None:
- setattr(self.context, context_attr, option_value)
- try:
- Platform.get_platform()
- except BstError as e:
- self._error_exit(e, "Error instantiating platform")
-
- # Create the logger right before setting the message handler
- self.logger = LogLine(self.context,
- self._content_profile,
- self._format_profile,
- self._success_profile,
- self._error_profile,
- self._detail_profile,
- indent=INDENT)
-
- # Propagate pipeline feedback to the user
- self.context.set_message_handler(self._message_handler)
-
- # Preflight the artifact cache after initializing logging,
- # this can cause messages to be emitted.
- try:
- self.context.artifactcache.preflight()
- except BstError as e:
- self._error_exit(e, "Error instantiating artifact cache")
-
- #
- # Load the Project
- #
- try:
- self.project = Project(directory, self.context, cli_options=self._main_options['option'],
- default_mirror=self._main_options.get('default_mirror'))
- except LoadError as e:
-
- # Help users that are new to BuildStream by suggesting 'init'.
- # We don't want to slow down users that just made a mistake, so
- # don't stop them with an offer to create a project for them.
- if e.reason == LoadErrorReason.MISSING_PROJECT_CONF:
- click.echo("No project found. You can create a new project like so:", err=True)
- click.echo("", err=True)
- click.echo(" bst init", err=True)
-
- self._error_exit(e, "Error loading project")
-
- except BstError as e:
- self._error_exit(e, "Error loading project")
-
- # Now that we have a logger and message handler,
- # we can override the global exception hook.
- sys.excepthook = self._global_exception_handler
-
- # Create the stream right away, we'll need to pass it around
- self.stream = Stream(self.context, self.project, self._session_start,
- session_start_callback=self.session_start_cb,
- interrupt_callback=self._interrupt_handler,
- ticker_callback=self._tick,
- job_start_callback=self._job_started,
- job_complete_callback=self._job_completed)
-
- # Create our status printer, only available in interactive
- self._status = Status(self.context,
- self._content_profile, self._format_profile,
- self._success_profile, self._error_profile,
- self.stream, colors=self.colors)
-
- # Mark the beginning of the session
- if session_name:
- self._message(MessageType.START, session_name)
-
- # Run the body of the session here, once everything is loaded
- try:
- yield
- except BstError as e:
-
- # Print a nice summary if this is a session
- if session_name:
- elapsed = self.stream.elapsed_time
-
- if isinstance(e, StreamError) and e.terminated: # pylint: disable=no-member
- self._message(MessageType.WARN, session_name + ' Terminated', elapsed=elapsed)
- else:
- self._message(MessageType.FAIL, session_name, elapsed=elapsed)
-
- # Notify session failure
- self._notify("{} failed".format(session_name), e)
-
- if self._started:
- self._print_summary()
-
- # Exit with the error
- self._error_exit(e)
- except RecursionError:
- click.echo("RecursionError: Dependency depth is too large. Maximum recursion depth exceeded.",
- err=True)
- sys.exit(-1)
-
- else:
- # No exceptions occurred, print session time and summary
- if session_name:
- self._message(MessageType.SUCCESS, session_name, elapsed=self.stream.elapsed_time)
- if self._started:
- self._print_summary()
-
- # Notify session success
- self._notify("{} succeeded".format(session_name), "")
-
- # init_project()
- #
- # Initialize a new BuildStream project, either with the explicitly passed options,
- # or by starting an interactive session if project_name is not specified and the
- # application is running in interactive mode.
- #
- # Args:
- # project_name (str): The project name, must be a valid symbol name
- # format_version (int): The project format version, default is the latest version
- # element_path (str): The subdirectory to store elements in, default is 'elements'
- # force (bool): Allow overwriting an existing project.conf
- #
- def init_project(self, project_name, format_version=BST_FORMAT_VERSION, element_path='elements', force=False):
- directory = self._main_options['directory']
- directory = os.path.abspath(directory)
- project_path = os.path.join(directory, 'project.conf')
-
- try:
- # Abort if the project.conf already exists, unless `--force` was specified in `bst init`
- if not force and os.path.exists(project_path):
- raise AppError("A project.conf already exists at: {}".format(project_path),
- reason='project-exists')
-
- if project_name:
- # If project name was specified, user interaction is not desired, just
- # perform some validation and write the project.conf
- _yaml.assert_symbol_name(None, project_name, 'project name')
- self._assert_format_version(format_version)
- self._assert_element_path(element_path)
-
- elif not self.interactive:
- raise AppError("Cannot initialize a new project without specifying the project name",
- reason='unspecified-project-name')
- else:
- # Collect the parameters using an interactive session
- project_name, format_version, element_path = \
- self._init_project_interactive(project_name, format_version, element_path)
-
- # Create the directory if it doesnt exist
- try:
- os.makedirs(directory, exist_ok=True)
- except IOError as e:
- raise AppError("Error creating project directory {}: {}".format(directory, e)) from e
-
- # Create the elements sub-directory if it doesnt exist
- elements_path = os.path.join(directory, element_path)
- try:
- os.makedirs(elements_path, exist_ok=True)
- except IOError as e:
- raise AppError("Error creating elements sub-directory {}: {}"
- .format(elements_path, e)) from e
-
- # Dont use ruamel.yaml here, because it doesnt let
- # us programatically insert comments or whitespace at
- # the toplevel.
- try:
- with open(project_path, 'w') as f:
- f.write("# Unique project name\n" +
- "name: {}\n\n".format(project_name) +
- "# Required BuildStream format version\n" +
- "format-version: {}\n\n".format(format_version) +
- "# Subdirectory where elements are stored\n" +
- "element-path: {}\n".format(element_path))
- except IOError as e:
- raise AppError("Error writing {}: {}".format(project_path, e)) from e
-
- except BstError as e:
- self._error_exit(e)
-
- click.echo("", err=True)
- click.echo("Created project.conf at: {}".format(project_path), err=True)
- sys.exit(0)
-
- # shell_prompt():
- #
- # Creates a prompt for a shell environment, using ANSI color codes
- # if they are available in the execution context.
- #
- # Args:
- # element (Element): The Element object to resolve a prompt for
- #
- # Returns:
- # (str): The formatted prompt to display in the shell
- #
- def shell_prompt(self, element):
- _, key, dim = element._get_display_key()
- element_name = element._get_full_name()
-
- if self.colors:
- prompt = self._format_profile.fmt('[') + \
- self._content_profile.fmt(key, dim=dim) + \
- self._format_profile.fmt('@') + \
- self._content_profile.fmt(element_name) + \
- self._format_profile.fmt(':') + \
- self._content_profile.fmt('$PWD') + \
- self._format_profile.fmt(']$') + ' '
- else:
- prompt = '[{}@{}:${{PWD}}]$ '.format(key, element_name)
-
- return prompt
-
- # cleanup()
- #
- # Cleans up application state
- #
- # This is called by Click at exit time
- #
- def cleanup(self):
- if self.stream:
- self.stream.cleanup()
-
- ############################################################
- # Abstract Class Methods #
- ############################################################
-
- # notify()
- #
- # Notify the user of something which occurred, this
- # is intended to grab attention from the user.
- #
- # This is guaranteed to only be called in interactive mode
- #
- # Args:
- # title (str): The notification title
- # text (str): The notification text
- #
- def notify(self, title, text):
- pass
-
- ############################################################
- # Local Functions #
- ############################################################
-
- # Local function for calling the notify() virtual method
- #
- def _notify(self, title, text):
- if self.interactive:
- self.notify(str(title), str(text))
-
- # Local message propagator
- #
- def _message(self, message_type, message, **kwargs):
- args = dict(kwargs)
- self.context.message(
- Message(None, message_type, message, **args))
-
- # Exception handler
- #
- def _global_exception_handler(self, etype, value, tb):
-
- # Print the regular BUG message
- formatted = "".join(traceback.format_exception(etype, value, tb))
- self._message(MessageType.BUG, str(value),
- detail=formatted)
-
- # If the scheduler has started, try to terminate all jobs gracefully,
- # otherwise exit immediately.
- if self.stream.running:
- self.stream.terminate()
- else:
- sys.exit(-1)
-
- #
- # Render the status area, conditional on some internal state
- #
- def _maybe_render_status(self):
-
- # If we're suspended or terminating, then dont render the status area
- if self._status and self.stream and \
- not (self.stream.suspended or self.stream.terminated):
- self._status.render()
-
- #
- # Handle ^C SIGINT interruptions in the scheduling main loop
- #
- def _interrupt_handler(self):
-
- # Only handle ^C interactively in interactive mode
- if not self.interactive:
- self._status.clear()
- self.stream.terminate()
- return
-
- # Here we can give the user some choices, like whether they would
- # like to continue, abort immediately, or only complete processing of
- # the currently ongoing tasks. We can also print something more
- # intelligent, like how many tasks remain to complete overall.
- with self._interrupted():
- click.echo("\nUser interrupted with ^C\n" +
- "\n"
- "Choose one of the following options:\n" +
- " (c)ontinue - Continue queueing jobs as much as possible\n" +
- " (q)uit - Exit after all ongoing jobs complete\n" +
- " (t)erminate - Terminate any ongoing jobs and exit\n" +
- "\n" +
- "Pressing ^C again will terminate jobs and exit\n",
- err=True)
-
- try:
- choice = click.prompt("Choice:",
- value_proc=_prefix_choice_value_proc(['continue', 'quit', 'terminate']),
- default='continue', err=True)
- except click.Abort:
- # Ensure a newline after automatically printed '^C'
- click.echo("", err=True)
- choice = 'terminate'
-
- if choice == 'terminate':
- click.echo("\nTerminating all jobs at user request\n", err=True)
- self.stream.terminate()
- else:
- if choice == 'quit':
- click.echo("\nCompleting ongoing tasks before quitting\n", err=True)
- self.stream.quit()
- elif choice == 'continue':
- click.echo("\nContinuing\n", err=True)
-
- def _tick(self, elapsed):
- self._maybe_render_status()
-
- def _job_started(self, job):
- self._status.add_job(job)
- self._maybe_render_status()
-
- def _job_completed(self, job, status):
- self._status.remove_job(job)
- self._maybe_render_status()
-
- # Dont attempt to handle a failure if the user has already opted to
- # terminate
- if status == JobStatus.FAIL and not self.stream.terminated:
-
- if isinstance(job, ElementJob):
- element = job.element
- queue = job.queue
-
- # Get the last failure message for additional context
- failure = self._fail_messages.get(element._unique_id)
-
- # XXX This is dangerous, sometimes we get the job completed *before*
- # the failure message reaches us ??
- if not failure:
- self._status.clear()
- click.echo("\n\n\nBUG: Message handling out of sync, " +
- "unable to retrieve failure message for element {}\n\n\n\n\n"
- .format(element), err=True)
- else:
- self._handle_failure(element, queue, failure)
- else:
- click.echo("\nTerminating all jobs\n", err=True)
- self.stream.terminate()
-
- def _handle_failure(self, element, queue, failure):
-
- # Handle non interactive mode setting of what to do when a job fails.
- if not self._interactive_failures:
-
- if self.context.sched_error_action == 'terminate':
- self.stream.terminate()
- elif self.context.sched_error_action == 'quit':
- self.stream.quit()
- elif self.context.sched_error_action == 'continue':
- pass
- return
-
- # Interactive mode for element failures
- with self._interrupted():
-
- summary = ("\n{} failure on element: {}\n".format(failure.action_name, element.name) +
- "\n" +
- "Choose one of the following options:\n" +
- " (c)ontinue - Continue queueing jobs as much as possible\n" +
- " (q)uit - Exit after all ongoing jobs complete\n" +
- " (t)erminate - Terminate any ongoing jobs and exit\n" +
- " (r)etry - Retry this job\n")
- if failure.logfile:
- summary += " (l)og - View the full log file\n"
- if failure.sandbox:
- summary += " (s)hell - Drop into a shell in the failed build sandbox\n"
- summary += "\nPressing ^C will terminate jobs and exit\n"
-
- choices = ['continue', 'quit', 'terminate', 'retry']
- if failure.logfile:
- choices += ['log']
- if failure.sandbox:
- choices += ['shell']
-
- choice = ''
- while choice not in ['continue', 'quit', 'terminate', 'retry']:
- click.echo(summary, err=True)
-
- self._notify("BuildStream failure", "{} on element {}"
- .format(failure.action_name, element.name))
-
- try:
- choice = click.prompt("Choice:", default='continue', err=True,
- value_proc=_prefix_choice_value_proc(choices))
- except click.Abort:
- # Ensure a newline after automatically printed '^C'
- click.echo("", err=True)
- choice = 'terminate'
-
- # Handle choices which you can come back from
- #
- if choice == 'shell':
- click.echo("\nDropping into an interactive shell in the failed build sandbox\n", err=True)
- try:
- prompt = self.shell_prompt(element)
- self.stream.shell(element, Scope.BUILD, prompt, isolate=True, usebuildtree='always')
- except BstError as e:
- click.echo("Error while attempting to create interactive shell: {}".format(e), err=True)
- elif choice == 'log':
- with open(failure.logfile, 'r') as logfile:
- content = logfile.read()
- click.echo_via_pager(content)
-
- if choice == 'terminate':
- click.echo("\nTerminating all jobs\n", err=True)
- self.stream.terminate()
- else:
- if choice == 'quit':
- click.echo("\nCompleting ongoing tasks before quitting\n", err=True)
- self.stream.quit()
- elif choice == 'continue':
- click.echo("\nContinuing with other non failing elements\n", err=True)
- elif choice == 'retry':
- click.echo("\nRetrying failed job\n", err=True)
- queue.failed_elements.remove(element)
- queue.enqueue([element])
-
- #
- # Print the session heading if we've loaded a pipeline and there
- # is going to be a session
- #
- def session_start_cb(self):
- self._started = True
- if self._session_name:
- self.logger.print_heading(self.project,
- self.stream,
- log_file=self._main_options['log_file'],
- styling=self.colors)
-
- #
- # Print a summary of the queues
- #
- def _print_summary(self):
- click.echo("", err=True)
- self.logger.print_summary(self.stream,
- self._main_options['log_file'],
- styling=self.colors)
-
- # _error_exit()
- #
- # Exit with an error
- #
- # This will print the passed error to stderr and exit the program
- # with -1 status
- #
- # Args:
- # error (BstError): A BstError exception to print
- # prefix (str): An optional string to prepend to the error message
- #
- def _error_exit(self, error, prefix=None):
- click.echo("", err=True)
- main_error = str(error)
- if prefix is not None:
- main_error = "{}: {}".format(prefix, main_error)
-
- click.echo(main_error, err=True)
- if error.detail:
- indent = " " * INDENT
- detail = '\n' + indent + indent.join(error.detail.splitlines(True))
- click.echo(detail, err=True)
-
- sys.exit(-1)
-
- #
- # Handle messages from the pipeline
- #
- def _message_handler(self, message, context):
-
- # Drop status messages from the UI if not verbose, we'll still see
- # info messages and status messages will still go to the log files.
- if not context.log_verbose and message.message_type == MessageType.STATUS:
- return
-
- # Hold on to the failure messages
- if message.message_type in [MessageType.FAIL, MessageType.BUG] and message.unique_id is not None:
- self._fail_messages[message.unique_id] = message
-
- # Send to frontend if appropriate
- if self.context.silent_messages() and (message.message_type not in unconditional_messages):
- return
-
- if self._status:
- self._status.clear()
-
- text = self.logger.render(message)
- click.echo(text, color=self.colors, nl=False, err=True)
-
- # Maybe render the status area
- self._maybe_render_status()
-
- # Additionally log to a file
- if self._main_options['log_file']:
- click.echo(text, file=self._main_options['log_file'], color=False, nl=False)
-
- @contextmanager
- def _interrupted(self):
- self._status.clear()
- try:
- with self.stream.suspend():
- yield
- finally:
- self._maybe_render_status()
-
- # Some validation routines for project initialization
- #
- def _assert_format_version(self, format_version):
- message = "The version must be supported by this " + \
- "version of buildstream (0 - {})\n".format(BST_FORMAT_VERSION)
-
- # Validate that it is an integer
- try:
- number = int(format_version)
- except ValueError as e:
- raise AppError(message, reason='invalid-format-version') from e
-
- # Validate that the specified version is supported
- if number < 0 or number > BST_FORMAT_VERSION:
- raise AppError(message, reason='invalid-format-version')
-
- def _assert_element_path(self, element_path):
- message = "The element path cannot be an absolute path or contain any '..' components\n"
-
- # Validate the path is not absolute
- if os.path.isabs(element_path):
- raise AppError(message, reason='invalid-element-path')
-
- # Validate that the path does not contain any '..' components
- path = element_path
- while path:
- split = os.path.split(path)
- path = split[0]
- basename = split[1]
- if basename == '..':
- raise AppError(message, reason='invalid-element-path')
-
- # _init_project_interactive()
- #
- # Collect the user input for an interactive session for App.init_project()
- #
- # Args:
- # project_name (str): The project name, must be a valid symbol name
- # format_version (int): The project format version, default is the latest version
- # element_path (str): The subdirectory to store elements in, default is 'elements'
- #
- # Returns:
- # project_name (str): The user selected project name
- # format_version (int): The user selected format version
- # element_path (str): The user selected element path
- #
- def _init_project_interactive(self, project_name, format_version=BST_FORMAT_VERSION, element_path='elements'):
-
- def project_name_proc(user_input):
- try:
- _yaml.assert_symbol_name(None, user_input, 'project name')
- except LoadError as e:
- message = "{}\n\n{}\n".format(e, e.detail)
- raise UsageError(message) from e
- return user_input
-
- def format_version_proc(user_input):
- try:
- self._assert_format_version(user_input)
- except AppError as e:
- raise UsageError(str(e)) from e
- return user_input
-
- def element_path_proc(user_input):
- try:
- self._assert_element_path(user_input)
- except AppError as e:
- raise UsageError(str(e)) from e
- return user_input
-
- w = TextWrapper(initial_indent=' ', subsequent_indent=' ', width=79)
-
- # Collect project name
- click.echo("", err=True)
- click.echo(self._content_profile.fmt("Choose a unique name for your project"), err=True)
- click.echo(self._format_profile.fmt("-------------------------------------"), err=True)
- click.echo("", err=True)
- click.echo(self._detail_profile.fmt(
- w.fill("The project name is a unique symbol for your project and will be used "
- "to distinguish your project from others in user preferences, namspaceing "
- "of your project's artifacts in shared artifact caches, and in any case where "
- "BuildStream needs to distinguish between multiple projects.")), err=True)
- click.echo("", err=True)
- click.echo(self._detail_profile.fmt(
- w.fill("The project name must contain only alphanumeric characters, "
- "may not start with a digit, and may contain dashes or underscores.")), err=True)
- click.echo("", err=True)
- project_name = click.prompt(self._content_profile.fmt("Project name"),
- value_proc=project_name_proc, err=True)
- click.echo("", err=True)
-
- # Collect format version
- click.echo(self._content_profile.fmt("Select the minimum required format version for your project"), err=True)
- click.echo(self._format_profile.fmt("-----------------------------------------------------------"), err=True)
- click.echo("", err=True)
- click.echo(self._detail_profile.fmt(
- w.fill("The format version is used to provide users who build your project "
- "with a helpful error message in the case that they do not have a recent "
- "enough version of BuildStream supporting all the features which your "
- "project might use.")), err=True)
- click.echo("", err=True)
- click.echo(self._detail_profile.fmt(
- w.fill("The lowest version allowed is 0, the currently installed version of BuildStream "
- "supports up to format version {}.".format(BST_FORMAT_VERSION))), err=True)
-
- click.echo("", err=True)
- format_version = click.prompt(self._content_profile.fmt("Format version"),
- value_proc=format_version_proc,
- default=format_version, err=True)
- click.echo("", err=True)
-
- # Collect element path
- click.echo(self._content_profile.fmt("Select the element path"), err=True)
- click.echo(self._format_profile.fmt("-----------------------"), err=True)
- click.echo("", err=True)
- click.echo(self._detail_profile.fmt(
- w.fill("The element path is a project subdirectory where element .bst files are stored "
- "within your project.")), err=True)
- click.echo("", err=True)
- click.echo(self._detail_profile.fmt(
- w.fill("Elements will be displayed in logs as filenames relative to "
- "the element path, and similarly, dependencies must be expressed as filenames "
- "relative to the element path.")), err=True)
- click.echo("", err=True)
- element_path = click.prompt(self._content_profile.fmt("Element path"),
- value_proc=element_path_proc,
- default=element_path, err=True)
-
- return (project_name, format_version, element_path)
-
-
-#
-# Return a value processor for partial choice matching.
-# The returned values processor will test the passed value with all the item
-# in the 'choices' list. If the value is a prefix of one of the 'choices'
-# element, the element is returned. If no element or several elements match
-# the same input, a 'click.UsageError' exception is raised with a description
-# of the error.
-#
-# Note that Click expect user input errors to be signaled by raising a
-# 'click.UsageError' exception. That way, Click display an error message and
-# ask for a new input.
-#
-def _prefix_choice_value_proc(choices):
-
- def value_proc(user_input):
- remaining_candidate = [choice for choice in choices if choice.startswith(user_input)]
-
- if not remaining_candidate:
- raise UsageError("Expected one of {}, got {}".format(choices, user_input))
- elif len(remaining_candidate) == 1:
- return remaining_candidate[0]
- else:
- raise UsageError("Ambiguous input. '{}' can refer to one of {}".format(user_input, remaining_candidate))
-
- return value_proc
diff --git a/buildstream/_frontend/cli.py b/buildstream/_frontend/cli.py
deleted file mode 100644
index cc8cd5e54..000000000
--- a/buildstream/_frontend/cli.py
+++ /dev/null
@@ -1,1277 +0,0 @@
-import os
-import sys
-from contextlib import ExitStack
-from functools import partial
-from tempfile import TemporaryDirectory
-
-import click
-from .. import _yaml
-from .._exceptions import BstError, LoadError, AppError
-from .._versions import BST_FORMAT_VERSION
-from .complete import main_bashcomplete, complete_path, CompleteUnhandled
-
-
-##################################################################
-# Override of click's main entry point #
-##################################################################
-
-# search_command()
-#
-# Helper function to get a command and context object
-# for a given command.
-#
-# Args:
-# commands (list): A list of command words following `bst` invocation
-# context (click.Context): An existing toplevel context, or None
-#
-# Returns:
-# context (click.Context): The context of the associated command, or None
-#
-def search_command(args, *, context=None):
- if context is None:
- context = cli.make_context('bst', args, resilient_parsing=True)
-
- # Loop into the deepest command
- command = cli
- command_ctx = context
- for cmd in args:
- command = command_ctx.command.get_command(command_ctx, cmd)
- if command is None:
- return None
- command_ctx = command.make_context(command.name, [command.name],
- parent=command_ctx,
- resilient_parsing=True)
-
- return command_ctx
-
-
-# Completion for completing command names as help arguments
-def complete_commands(cmd, args, incomplete):
- command_ctx = search_command(args[1:])
- if command_ctx and command_ctx.command and isinstance(command_ctx.command, click.MultiCommand):
- return [subcommand + " " for subcommand in command_ctx.command.list_commands(command_ctx)
- if not command_ctx.command.get_command(command_ctx, subcommand).hidden]
-
- return []
-
-
-# Special completion for completing the bst elements in a project dir
-def complete_target(args, incomplete):
- """
- :param args: full list of args typed before the incomplete arg
- :param incomplete: the incomplete text to autocomplete
- :return: all the possible user-specified completions for the param
- """
-
- from .. import utils
- project_conf = 'project.conf'
-
- # First resolve the directory, in case there is an
- # active --directory/-C option
- #
- base_directory = '.'
- idx = -1
- try:
- idx = args.index('-C')
- except ValueError:
- try:
- idx = args.index('--directory')
- except ValueError:
- pass
-
- if idx >= 0 and len(args) > idx + 1:
- base_directory = args[idx + 1]
- else:
- # Check if this directory or any of its parent directories
- # contain a project config file
- base_directory, _ = utils._search_upward_for_files(base_directory, [project_conf])
-
- if base_directory is None:
- # No project_conf was found in base_directory or its parents, no need
- # to try loading any project conf and avoid os.path NoneType TypeError.
- return []
- else:
- project_file = os.path.join(base_directory, project_conf)
- try:
- project = _yaml.load(project_file)
- except LoadError:
- # If there is no project conf in context, just dont
- # even bother trying to complete anything.
- return []
-
- # The project is not required to have an element-path
- element_directory = _yaml.node_get(project, str, 'element-path', default_value='')
-
- # If a project was loaded, use its element-path to
- # adjust our completion's base directory
- if element_directory:
- base_directory = os.path.join(base_directory, element_directory)
-
- complete_list = []
- for p in complete_path("File", incomplete, base_directory=base_directory):
- if p.endswith(".bst ") or p.endswith("/"):
- complete_list.append(p)
- return complete_list
-
-
-def complete_artifact(orig_args, args, incomplete):
- from .._context import Context
- ctx = Context()
-
- config = None
- if orig_args:
- for i, arg in enumerate(orig_args):
- if arg in ('-c', '--config'):
- try:
- config = orig_args[i + 1]
- except IndexError:
- pass
- if args:
- for i, arg in enumerate(args):
- if arg in ('-c', '--config'):
- try:
- config = args[i + 1]
- except IndexError:
- pass
- ctx.load(config)
-
- # element targets are valid artifact names
- complete_list = complete_target(args, incomplete)
- complete_list.extend(ref for ref in ctx.artifactcache.list_artifacts() if ref.startswith(incomplete))
-
- return complete_list
-
-
-def override_completions(orig_args, cmd, cmd_param, args, incomplete):
- """
- :param orig_args: original, non-completion args
- :param cmd_param: command definition
- :param args: full list of args typed before the incomplete arg
- :param incomplete: the incomplete text to autocomplete
- :return: all the possible user-specified completions for the param
- """
-
- if cmd.name == 'help':
- return complete_commands(cmd, args, incomplete)
-
- # We can't easily extend click's data structures without
- # modifying click itself, so just do some weak special casing
- # right here and select which parameters we want to handle specially.
- if isinstance(cmd_param.type, click.Path):
- if (cmd_param.name == 'elements' or
- cmd_param.name == 'element' or
- cmd_param.name == 'except_' or
- cmd_param.opts == ['--track'] or
- cmd_param.opts == ['--track-except']):
- return complete_target(args, incomplete)
- if cmd_param.name == 'artifacts':
- return complete_artifact(orig_args, args, incomplete)
-
- raise CompleteUnhandled()
-
-
-def override_main(self, args=None, prog_name=None, complete_var=None,
- standalone_mode=True, **extra):
-
- # Hook for the Bash completion. This only activates if the Bash
- # completion is actually enabled, otherwise this is quite a fast
- # noop.
- if main_bashcomplete(self, prog_name, partial(override_completions, args)):
-
- # If we're running tests we cant just go calling exit()
- # from the main process.
- #
- # The below is a quicker exit path for the sake
- # of making completions respond faster.
- if 'BST_TEST_SUITE' not in os.environ:
- sys.stdout.flush()
- sys.stderr.flush()
- os._exit(0)
-
- # Regular client return for test cases
- return
-
- original_main(self, args=args, prog_name=prog_name, complete_var=None,
- standalone_mode=standalone_mode, **extra)
-
-
-original_main = click.BaseCommand.main
-click.BaseCommand.main = override_main
-
-
-##################################################################
-# Main Options #
-##################################################################
-def print_version(ctx, param, value):
- if not value or ctx.resilient_parsing:
- return
-
- from .. import __version__
- click.echo(__version__)
- ctx.exit()
-
-
-@click.group(context_settings=dict(help_option_names=['-h', '--help']))
-@click.option('--version', is_flag=True, callback=print_version,
- expose_value=False, is_eager=True)
-@click.option('--config', '-c',
- type=click.Path(exists=True, dir_okay=False, readable=True),
- help="Configuration file to use")
-@click.option('--directory', '-C', default=os.getcwd(),
- type=click.Path(file_okay=False, readable=True),
- help="Project directory (default: current directory)")
-@click.option('--on-error', default=None,
- type=click.Choice(['continue', 'quit', 'terminate']),
- help="What to do when an error is encountered")
-@click.option('--fetchers', type=click.INT, default=None,
- help="Maximum simultaneous download tasks")
-@click.option('--builders', type=click.INT, default=None,
- help="Maximum simultaneous build tasks")
-@click.option('--pushers', type=click.INT, default=None,
- help="Maximum simultaneous upload tasks")
-@click.option('--network-retries', type=click.INT, default=None,
- help="Maximum retries for network tasks")
-@click.option('--no-interactive', is_flag=True, default=False,
- help="Force non interactive mode, otherwise this is automatically decided")
-@click.option('--verbose/--no-verbose', default=None,
- help="Be extra verbose")
-@click.option('--debug/--no-debug', default=None,
- help="Print debugging output")
-@click.option('--error-lines', type=click.INT, default=None,
- help="Maximum number of lines to show from a task log")
-@click.option('--message-lines', type=click.INT, default=None,
- help="Maximum number of lines to show in a detailed message")
-@click.option('--log-file',
- type=click.File(mode='w', encoding='UTF-8'),
- help="A file to store the main log (allows storing the main log while in interactive mode)")
-@click.option('--colors/--no-colors', default=None,
- help="Force enable/disable ANSI color codes in output")
-@click.option('--strict/--no-strict', default=None, is_flag=True,
- help="Elements must be rebuilt when their dependencies have changed")
-@click.option('--option', '-o', type=click.Tuple([str, str]), multiple=True, metavar='OPTION VALUE',
- help="Specify a project option")
-@click.option('--default-mirror', default=None,
- help="The mirror to fetch from first, before attempting other mirrors")
-@click.option('--pull-buildtrees', is_flag=True, default=None,
- help="Include an element's build tree when pulling remote element artifacts")
-@click.option('--cache-buildtrees', default=None,
- type=click.Choice(['always', 'auto', 'never']),
- help="Cache artifact build tree content on creation")
-@click.pass_context
-def cli(context, **kwargs):
- """Build and manipulate BuildStream projects
-
- Most of the main options override options in the
- user preferences configuration file.
- """
-
- from .app import App
-
- # Create the App, giving it the main arguments
- context.obj = App.create(dict(kwargs))
- context.call_on_close(context.obj.cleanup)
-
-
-##################################################################
-# Help Command #
-##################################################################
-@cli.command(name="help", short_help="Print usage information",
- context_settings={"help_option_names": []})
-@click.argument("command", nargs=-1, metavar='COMMAND')
-@click.pass_context
-def help_command(ctx, command):
- """Print usage information about a given command
- """
- command_ctx = search_command(command, context=ctx.parent)
- if not command_ctx:
- click.echo("Not a valid command: '{} {}'"
- .format(ctx.parent.info_name, " ".join(command)), err=True)
- sys.exit(-1)
-
- click.echo(command_ctx.command.get_help(command_ctx), err=True)
-
- # Hint about available sub commands
- if isinstance(command_ctx.command, click.MultiCommand):
- detail = " "
- if command:
- detail = " {} ".format(" ".join(command))
- click.echo("\nFor usage on a specific command: {} help{}COMMAND"
- .format(ctx.parent.info_name, detail), err=True)
-
-
-##################################################################
-# Init Command #
-##################################################################
-@cli.command(short_help="Initialize a new BuildStream project")
-@click.option('--project-name', type=click.STRING,
- help="The project name to use")
-@click.option('--format-version', type=click.INT, default=BST_FORMAT_VERSION,
- help="The required format version (default: {})".format(BST_FORMAT_VERSION))
-@click.option('--element-path', type=click.Path(), default="elements",
- help="The subdirectory to store elements in (default: elements)")
-@click.option('--force', '-f', default=False, is_flag=True,
- help="Allow overwriting an existing project.conf")
-@click.pass_obj
-def init(app, project_name, format_version, element_path, force):
- """Initialize a new BuildStream project
-
- Creates a new BuildStream project.conf in the project
- directory.
-
- Unless `--project-name` is specified, this will be an
- interactive session.
- """
- app.init_project(project_name, format_version, element_path, force)
-
-
-##################################################################
-# Build Command #
-##################################################################
-@cli.command(short_help="Build elements in a pipeline")
-@click.option('--all', 'all_', default=False, is_flag=True,
- help="Build elements that would not be needed for the current build plan")
-@click.option('--track', 'track_', multiple=True,
- type=click.Path(readable=False),
- help="Specify elements to track during the build. Can be used "
- "repeatedly to specify multiple elements")
-@click.option('--track-all', default=False, is_flag=True,
- help="Track all elements in the pipeline")
-@click.option('--track-except', multiple=True,
- type=click.Path(readable=False),
- help="Except certain dependencies from tracking")
-@click.option('--track-cross-junctions', '-J', default=False, is_flag=True,
- help="Allow tracking to cross junction boundaries")
-@click.option('--track-save', default=False, is_flag=True,
- help="Deprecated: This is ignored")
-@click.option('--remote', '-r', default=None,
- help="The URL of the remote cache (defaults to the first configured cache)")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def build(app, elements, all_, track_, track_save, track_all, track_except, track_cross_junctions, remote):
- """Build elements in a pipeline
-
- Specifying no elements will result in building the default targets
- of the project. If no default targets are configured, all project
- elements will be built.
-
- When this command is executed from a workspace directory, the default
- is to build the workspace element.
- """
-
- if (track_except or track_cross_junctions) and not (track_ or track_all):
- click.echo("ERROR: The --track-except and --track-cross-junctions options "
- "can only be used with --track or --track-all", err=True)
- sys.exit(-1)
-
- if track_save:
- click.echo("WARNING: --track-save is deprecated, saving is now unconditional", err=True)
-
- with app.initialized(session_name="Build"):
- ignore_junction_targets = False
-
- if not elements:
- elements = app.project.get_default_targets()
- # Junction elements cannot be built, exclude them from default targets
- ignore_junction_targets = True
-
- if track_all:
- track_ = elements
-
- app.stream.build(elements,
- track_targets=track_,
- track_except=track_except,
- track_cross_junctions=track_cross_junctions,
- ignore_junction_targets=ignore_junction_targets,
- build_all=all_,
- remote=remote)
-
-
-##################################################################
-# Show Command #
-##################################################################
-@cli.command(short_help="Show elements in the pipeline")
-@click.option('--except', 'except_', multiple=True,
- type=click.Path(readable=False),
- help="Except certain dependencies")
-@click.option('--deps', '-d', default='all',
- type=click.Choice(['none', 'plan', 'run', 'build', 'all']),
- help='The dependencies to show (default: all)')
-@click.option('--order', default="stage",
- type=click.Choice(['stage', 'alpha']),
- help='Staging or alphabetic ordering of dependencies')
-@click.option('--format', '-f', 'format_', metavar='FORMAT', default=None,
- type=click.STRING,
- help='Format string for each element')
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def show(app, elements, deps, except_, order, format_):
- """Show elements in the pipeline
-
- Specifying no elements will result in showing the default targets
- of the project. If no default targets are configured, all project
- elements will be shown.
-
- When this command is executed from a workspace directory, the default
- is to show the workspace element.
-
- By default this will show all of the dependencies of the
- specified target element.
-
- Specify `--deps` to control which elements to show:
-
- \b
- none: No dependencies, just the element itself
- plan: Dependencies required for a build plan
- run: Runtime dependencies, including the element itself
- build: Build time dependencies, excluding the element itself
- all: All dependencies
-
- \b
- FORMAT
- ~~~~~~
- The --format option controls what should be printed for each element,
- the following symbols can be used in the format string:
-
- \b
- %{name} The element name
- %{key} The abbreviated cache key (if all sources are consistent)
- %{full-key} The full cache key (if all sources are consistent)
- %{state} cached, buildable, waiting or inconsistent
- %{config} The element configuration
- %{vars} Variable configuration
- %{env} Environment settings
- %{public} Public domain data
- %{workspaced} If the element is workspaced
- %{workspace-dirs} A list of workspace directories
- %{deps} A list of all dependencies
- %{build-deps} A list of build dependencies
- %{runtime-deps} A list of runtime dependencies
-
- The value of the %{symbol} without the leading '%' character is understood
- as a pythonic formatting string, so python formatting features apply,
- examle:
-
- \b
- bst show target.bst --format \\
- 'Name: %{name: ^20} Key: %{key: ^8} State: %{state}'
-
- If you want to use a newline in a format string in bash, use the '$' modifier:
-
- \b
- bst show target.bst --format \\
- $'---------- %{name} ----------\\n%{vars}'
- """
- with app.initialized():
- # Do not require artifact directory trees or file contents to be present for `bst show`
- app.context.set_artifact_directories_optional()
-
- if not elements:
- elements = app.project.get_default_targets()
-
- dependencies = app.stream.load_selection(elements,
- selection=deps,
- except_targets=except_)
-
- if order == "alpha":
- dependencies = sorted(dependencies)
-
- if not format_:
- format_ = app.context.log_element_format
-
- report = app.logger.show_pipeline(dependencies, format_)
- click.echo(report, color=app.colors)
-
-
-##################################################################
-# Shell Command #
-##################################################################
-@cli.command(short_help="Shell into an element's sandbox environment")
-@click.option('--build', '-b', 'build_', is_flag=True, default=False,
- help='Stage dependencies and sources to build')
-@click.option('--sysroot', '-s', default=None,
- type=click.Path(exists=True, file_okay=False, readable=True),
- help="An existing sysroot")
-@click.option('--mount', type=click.Tuple([click.Path(exists=True), str]), multiple=True,
- metavar='HOSTPATH PATH',
- help="Mount a file or directory into the sandbox")
-@click.option('--isolate', is_flag=True, default=False,
- help='Create an isolated build sandbox')
-@click.option('--use-buildtree', '-t', 'cli_buildtree', type=click.Choice(['ask', 'try', 'always', 'never']),
- default='ask',
- help='Defaults to ask but if set to always the function will fail if a build tree is not available')
-@click.argument('element', required=False,
- type=click.Path(readable=False))
-@click.argument('command', type=click.STRING, nargs=-1)
-@click.pass_obj
-def shell(app, element, sysroot, mount, isolate, build_, cli_buildtree, command):
- """Run a command in the target element's sandbox environment
-
- When this command is executed from a workspace directory, the default
- is to shell into the workspace element.
-
- This will stage a temporary sysroot for running the target
- element, assuming it has already been built and all required
- artifacts are in the local cache.
-
- Use '--' to separate a command from the options to bst,
- otherwise bst may respond to them instead. e.g.
-
- \b
- bst shell example.bst -- df -h
-
- Use the --build option to create a temporary sysroot for
- building the element instead.
-
- Use the --sysroot option with an existing failed build
- directory or with a checkout of the given target, in order
- to use a specific sysroot.
-
- If no COMMAND is specified, the default is to attempt
- to run an interactive shell.
- """
- from ..element import Scope
- from .._project import HostMount
- from .._pipeline import PipelineSelection
-
- if build_:
- scope = Scope.BUILD
- else:
- scope = Scope.RUN
-
- use_buildtree = None
-
- with app.initialized():
- if not element:
- element = app.project.get_default_target()
- if not element:
- raise AppError('Missing argument "ELEMENT".')
-
- dependencies = app.stream.load_selection((element,), selection=PipelineSelection.NONE,
- use_artifact_config=True)
- element = dependencies[0]
- prompt = app.shell_prompt(element)
- mounts = [
- HostMount(path, host_path)
- for host_path, path in mount
- ]
-
- cached = element._cached_buildtree()
- buildtree_exists = element._buildtree_exists()
- if cli_buildtree in ("always", "try"):
- use_buildtree = cli_buildtree
- if not cached and buildtree_exists and use_buildtree == "always":
- click.echo("WARNING: buildtree is not cached locally, will attempt to pull from available remotes",
- err=True)
- else:
- # If the value has defaulted to ask and in non interactive mode, don't consider the buildtree, this
- # being the default behaviour of the command
- if app.interactive and cli_buildtree == "ask":
- if cached and bool(click.confirm('Do you want to use the cached buildtree?')):
- use_buildtree = "always"
- elif buildtree_exists:
- try:
- choice = click.prompt("Do you want to pull & use a cached buildtree?",
- type=click.Choice(['try', 'always', 'never']),
- err=True, show_choices=True)
- except click.Abort:
- click.echo('Aborting', err=True)
- sys.exit(-1)
-
- if choice != "never":
- use_buildtree = choice
-
- # Raise warning if the element is cached in a failed state
- if use_buildtree and element._cached_failure():
- click.echo("WARNING: using a buildtree from a failed build.", err=True)
-
- try:
- exitcode = app.stream.shell(element, scope, prompt,
- directory=sysroot,
- mounts=mounts,
- isolate=isolate,
- command=command,
- usebuildtree=use_buildtree)
- except BstError as e:
- raise AppError("Error launching shell: {}".format(e), detail=e.detail) from e
-
- # If there were no errors, we return the shell's exit code here.
- sys.exit(exitcode)
-
-
-##################################################################
-# Source Command #
-##################################################################
-@cli.group(short_help="Manipulate sources for an element")
-def source():
- """Manipulate sources for an element"""
-
-
-##################################################################
-# Source Fetch Command #
-##################################################################
-@source.command(name="fetch", short_help="Fetch sources in a pipeline")
-@click.option('--except', 'except_', multiple=True,
- type=click.Path(readable=False),
- help="Except certain dependencies from fetching")
-@click.option('--deps', '-d', default='plan',
- type=click.Choice(['none', 'plan', 'all']),
- help='The dependencies to fetch (default: plan)')
-@click.option('--track', 'track_', default=False, is_flag=True,
- help="Track new source references before fetching")
-@click.option('--track-cross-junctions', '-J', default=False, is_flag=True,
- help="Allow tracking to cross junction boundaries")
-@click.option('--remote', '-r', default=None,
- help="The URL of the remote source cache (defaults to the first configured cache)")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def source_fetch(app, elements, deps, track_, except_, track_cross_junctions, remote):
- """Fetch sources required to build the pipeline
-
- Specifying no elements will result in fetching the default targets
- of the project. If no default targets are configured, all project
- elements will be fetched.
-
- When this command is executed from a workspace directory, the default
- is to fetch the workspace element.
-
- By default this will only try to fetch sources which are
- required for the build plan of the specified target element,
- omitting sources for any elements which are already built
- and available in the artifact cache.
-
- Specify `--deps` to control which sources to fetch:
-
- \b
- none: No dependencies, just the element itself
- plan: Only dependencies required for the build plan
- all: All dependencies
- """
- from .._pipeline import PipelineSelection
-
- if track_cross_junctions and not track_:
- click.echo("ERROR: The --track-cross-junctions option can only be used with --track", err=True)
- sys.exit(-1)
-
- if track_ and deps == PipelineSelection.PLAN:
- click.echo("WARNING: --track specified for tracking of a build plan\n\n"
- "Since tracking modifies the build plan, all elements will be tracked.", err=True)
- deps = PipelineSelection.ALL
-
- with app.initialized(session_name="Fetch"):
- if not elements:
- elements = app.project.get_default_targets()
-
- app.stream.fetch(elements,
- selection=deps,
- except_targets=except_,
- track_targets=track_,
- track_cross_junctions=track_cross_junctions,
- remote=remote)
-
-
-##################################################################
-# Source Track Command #
-##################################################################
-@source.command(name="track", short_help="Track new source references")
-@click.option('--except', 'except_', multiple=True,
- type=click.Path(readable=False),
- help="Except certain dependencies from tracking")
-@click.option('--deps', '-d', default='none',
- type=click.Choice(['none', 'all']),
- help='The dependencies to track (default: none)')
-@click.option('--cross-junctions', '-J', default=False, is_flag=True,
- help="Allow crossing junction boundaries")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def source_track(app, elements, deps, except_, cross_junctions):
- """Consults the specified tracking branches for new versions available
- to build and updates the project with any newly available references.
-
- Specifying no elements will result in tracking the default targets
- of the project. If no default targets are configured, all project
- elements will be tracked.
-
- When this command is executed from a workspace directory, the default
- is to track the workspace element.
-
- If no default is declared, all elements in the project will be tracked
-
- By default this will track just the specified element, but you can also
- update a whole tree of dependencies in one go.
-
- Specify `--deps` to control which sources to track:
-
- \b
- none: No dependencies, just the specified elements
- all: All dependencies of all specified elements
- """
- with app.initialized(session_name="Track"):
- if not elements:
- elements = app.project.get_default_targets()
-
- # Substitute 'none' for 'redirect' so that element redirections
- # will be done
- if deps == 'none':
- deps = 'redirect'
- app.stream.track(elements,
- selection=deps,
- except_targets=except_,
- cross_junctions=cross_junctions)
-
-
-##################################################################
-# Source Checkout Command #
-##################################################################
-@source.command(name='checkout', short_help='Checkout sources for an element')
-@click.option('--force', '-f', default=False, is_flag=True,
- help="Allow files to be overwritten")
-@click.option('--except', 'except_', multiple=True,
- type=click.Path(readable=False),
- help="Except certain dependencies")
-@click.option('--deps', '-d', default='none',
- type=click.Choice(['build', 'none', 'run', 'all']),
- help='The dependencies whose sources to checkout (default: none)')
-@click.option('--fetch', 'fetch_', default=False, is_flag=True,
- help='Fetch elements if they are not fetched')
-@click.option('--tar', 'tar', default=False, is_flag=True,
- help='Create a tarball from the element\'s sources instead of a '
- 'file tree.')
-@click.option('--include-build-scripts', 'build_scripts', is_flag=True)
-@click.argument('element', required=False, type=click.Path(readable=False))
-@click.argument('location', type=click.Path(), required=False)
-@click.pass_obj
-def source_checkout(app, element, location, force, deps, fetch_, except_,
- tar, build_scripts):
- """Checkout sources of an element to the specified location
-
- When this command is executed from a workspace directory, the default
- is to checkout the sources of the workspace element.
- """
- if not element and not location:
- click.echo("ERROR: LOCATION is not specified", err=True)
- sys.exit(-1)
-
- if element and not location:
- # Nasty hack to get around click's optional args
- location = element
- element = None
-
- with app.initialized():
- if not element:
- element = app.project.get_default_target()
- if not element:
- raise AppError('Missing argument "ELEMENT".')
-
- app.stream.source_checkout(element,
- location=location,
- force=force,
- deps=deps,
- fetch=fetch_,
- except_targets=except_,
- tar=tar,
- include_build_scripts=build_scripts)
-
-
-##################################################################
-# Workspace Command #
-##################################################################
-@cli.group(short_help="Manipulate developer workspaces")
-def workspace():
- """Manipulate developer workspaces"""
-
-
-##################################################################
-# Workspace Open Command #
-##################################################################
-@workspace.command(name='open', short_help="Open a new workspace")
-@click.option('--no-checkout', default=False, is_flag=True,
- help="Do not checkout the source, only link to the given directory")
-@click.option('--force', '-f', default=False, is_flag=True,
- help="The workspace will be created even if the directory in which it will be created is not empty " +
- "or if a workspace for that element already exists")
-@click.option('--track', 'track_', default=False, is_flag=True,
- help="Track and fetch new source references before checking out the workspace")
-@click.option('--directory', type=click.Path(file_okay=False), default=None,
- help="Only for use when a single Element is given: Set the directory to use to create the workspace")
-@click.argument('elements', nargs=-1, type=click.Path(readable=False), required=True)
-@click.pass_obj
-def workspace_open(app, no_checkout, force, track_, directory, elements):
- """Open a workspace for manual source modification"""
-
- with app.initialized():
- app.stream.workspace_open(elements,
- no_checkout=no_checkout,
- track_first=track_,
- force=force,
- custom_dir=directory)
-
-
-##################################################################
-# Workspace Close Command #
-##################################################################
-@workspace.command(name='close', short_help="Close workspaces")
-@click.option('--remove-dir', default=False, is_flag=True,
- help="Remove the path that contains the closed workspace")
-@click.option('--all', '-a', 'all_', default=False, is_flag=True,
- help="Close all open workspaces")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def workspace_close(app, remove_dir, all_, elements):
- """Close a workspace"""
-
- removed_required_element = False
-
- with app.initialized():
- if not (all_ or elements):
- # NOTE: I may need to revisit this when implementing multiple projects
- # opening one workspace.
- element = app.project.get_default_target()
- if element:
- elements = (element,)
- else:
- raise AppError('No elements specified')
-
- # Early exit if we specified `all` and there are no workspaces
- if all_ and not app.stream.workspace_exists():
- click.echo('No open workspaces to close', err=True)
- sys.exit(0)
-
- if all_:
- elements = [element_name for element_name, _ in app.context.get_workspaces().list()]
-
- elements = app.stream.redirect_element_names(elements)
-
- # Check that the workspaces in question exist, and that it's safe to
- # remove them.
- nonexisting = []
- for element_name in elements:
- if not app.stream.workspace_exists(element_name):
- nonexisting.append(element_name)
- if nonexisting:
- raise AppError("Workspace does not exist", detail="\n".join(nonexisting))
-
- for element_name in elements:
- app.stream.workspace_close(element_name, remove_dir=remove_dir)
- if app.stream.workspace_is_required(element_name):
- removed_required_element = True
-
- # This message is echo'd last, as it's most relevant to the next
- # thing the user will type.
- if removed_required_element:
- click.echo(
- "Removed '{}', therefore you can no longer run BuildStream "
- "commands from the current directory.".format(element_name), err=True)
-
-
-##################################################################
-# Workspace Reset Command #
-##################################################################
-@workspace.command(name='reset', short_help="Reset a workspace to its original state")
-@click.option('--soft', default=False, is_flag=True,
- help="Reset workspace state without affecting its contents")
-@click.option('--track', 'track_', default=False, is_flag=True,
- help="Track and fetch the latest source before resetting")
-@click.option('--all', '-a', 'all_', default=False, is_flag=True,
- help="Reset all open workspaces")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def workspace_reset(app, soft, track_, all_, elements):
- """Reset a workspace to its original state"""
-
- # Check that the workspaces in question exist
- with app.initialized():
-
- if not (all_ or elements):
- element = app.project.get_default_target()
- if element:
- elements = (element,)
- else:
- raise AppError('No elements specified to reset')
-
- if all_ and not app.stream.workspace_exists():
- raise AppError("No open workspaces to reset")
-
- if all_:
- elements = tuple(element_name for element_name, _ in app.context.get_workspaces().list())
-
- app.stream.workspace_reset(elements, soft=soft, track_first=track_)
-
-
-##################################################################
-# Workspace List Command #
-##################################################################
-@workspace.command(name='list', short_help="List open workspaces")
-@click.pass_obj
-def workspace_list(app):
- """List open workspaces"""
-
- with app.initialized():
- app.stream.workspace_list()
-
-
-#############################################################
-# Artifact Commands #
-#############################################################
-@cli.group(short_help="Manipulate cached artifacts")
-def artifact():
- """Manipulate cached artifacts"""
-
-
-#####################################################################
-# Artifact Checkout Command #
-#####################################################################
-@artifact.command(name='checkout', short_help="Checkout contents of an artifact")
-@click.option('--force', '-f', default=False, is_flag=True,
- help="Allow files to be overwritten")
-@click.option('--deps', '-d', default=None,
- type=click.Choice(['run', 'build', 'none']),
- help='The dependencies to checkout (default: run)')
-@click.option('--integrate/--no-integrate', default=None, is_flag=True,
- help="Whether to run integration commands")
-@click.option('--hardlinks', default=False, is_flag=True,
- help="Checkout hardlinks instead of copying if possible")
-@click.option('--tar', default=None, metavar='LOCATION',
- type=click.Path(),
- help="Create a tarball from the artifact contents instead "
- "of a file tree. If LOCATION is '-', the tarball "
- "will be dumped to the standard output.")
-@click.option('--directory', default=None,
- type=click.Path(file_okay=False),
- help="The directory to checkout the artifact to")
-@click.argument('element', required=False,
- type=click.Path(readable=False))
-@click.pass_obj
-def artifact_checkout(app, force, deps, integrate, hardlinks, tar, directory, element):
- """Checkout contents of an artifact
-
- When this command is executed from a workspace directory, the default
- is to checkout the artifact of the workspace element.
- """
- from ..element import Scope
-
- if hardlinks and tar is not None:
- click.echo("ERROR: options --hardlinks and --tar conflict", err=True)
- sys.exit(-1)
-
- if tar is None and directory is None:
- click.echo("ERROR: One of --directory or --tar must be provided", err=True)
- sys.exit(-1)
-
- if tar is not None and directory is not None:
- click.echo("ERROR: options --directory and --tar conflict", err=True)
- sys.exit(-1)
-
- if tar is not None:
- location = tar
- tar = True
- else:
- location = os.getcwd() if directory is None else directory
- tar = False
-
- if deps == "build":
- scope = Scope.BUILD
- elif deps == "none":
- scope = Scope.NONE
- else:
- scope = Scope.RUN
-
- with app.initialized():
- if not element:
- element = app.project.get_default_target()
- if not element:
- raise AppError('Missing argument "ELEMENT".')
-
- app.stream.checkout(element,
- location=location,
- force=force,
- scope=scope,
- integrate=True if integrate is None else integrate,
- hardlinks=hardlinks,
- tar=tar)
-
-
-################################################################
-# Artifact Pull Command #
-################################################################
-@artifact.command(name="pull", short_help="Pull a built artifact")
-@click.option('--deps', '-d', default='none',
- type=click.Choice(['none', 'all']),
- help='The dependency artifacts to pull (default: none)')
-@click.option('--remote', '-r', default=None,
- help="The URL of the remote cache (defaults to the first configured cache)")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def artifact_pull(app, elements, deps, remote):
- """Pull a built artifact from the configured remote artifact cache.
-
- Specifying no elements will result in pulling the default targets
- of the project. If no default targets are configured, all project
- elements will be pulled.
-
- When this command is executed from a workspace directory, the default
- is to pull the workspace element.
-
- By default the artifact will be pulled one of the configured caches
- if possible, following the usual priority order. If the `--remote` flag
- is given, only the specified cache will be queried.
-
- Specify `--deps` to control which artifacts to pull:
-
- \b
- none: No dependencies, just the element itself
- all: All dependencies
- """
-
- with app.initialized(session_name="Pull"):
- ignore_junction_targets = False
-
- if not elements:
- elements = app.project.get_default_targets()
- # Junction elements cannot be pulled, exclude them from default targets
- ignore_junction_targets = True
-
- app.stream.pull(elements, selection=deps, remote=remote,
- ignore_junction_targets=ignore_junction_targets)
-
-
-##################################################################
-# Artifact Push Command #
-##################################################################
-@artifact.command(name="push", short_help="Push a built artifact")
-@click.option('--deps', '-d', default='none',
- type=click.Choice(['none', 'all']),
- help='The dependencies to push (default: none)')
-@click.option('--remote', '-r', default=None,
- help="The URL of the remote cache (defaults to the first configured cache)")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def artifact_push(app, elements, deps, remote):
- """Push a built artifact to a remote artifact cache.
-
- Specifying no elements will result in pushing the default targets
- of the project. If no default targets are configured, all project
- elements will be pushed.
-
- When this command is executed from a workspace directory, the default
- is to push the workspace element.
-
- The default destination is the highest priority configured cache. You can
- override this by passing a different cache URL with the `--remote` flag.
-
- If bst has been configured to include build trees on artifact pulls,
- an attempt will be made to pull any required build trees to avoid the
- skipping of partial artifacts being pushed.
-
- Specify `--deps` to control which artifacts to push:
-
- \b
- none: No dependencies, just the element itself
- all: All dependencies
- """
- with app.initialized(session_name="Push"):
- ignore_junction_targets = False
-
- if not elements:
- elements = app.project.get_default_targets()
- # Junction elements cannot be pushed, exclude them from default targets
- ignore_junction_targets = True
-
- app.stream.push(elements, selection=deps, remote=remote,
- ignore_junction_targets=ignore_junction_targets)
-
-
-################################################################
-# Artifact Log Command #
-################################################################
-@artifact.command(name='log', short_help="Show logs of artifacts")
-@click.argument('artifacts', type=click.Path(), nargs=-1)
-@click.pass_obj
-def artifact_log(app, artifacts):
- """Show logs of artifacts.
-
- Note that 'artifacts' can be element references like "hello.bst", and they
- can also be artifact references. You may use shell-style wildcards for
- either.
-
- Here are some examples of element references:
-
- \b
- - `hello.bst`
- - `*.bst`
-
- Note that element references must end with '.bst' to distinguish them from
- artifact references. Anything that does not end in '.bst' is an artifact
- ref.
-
- Artifact references follow the format `<project_name>/<element>/<key>`.
- Note that 'element' is without the `.bst` extension.
-
- Here are some examples of artifact references:
-
- \b
- - `myproject/hello/*`
- - `myproject/*`
- - `*`
- - `myproject/hello/827637*`
- - `myproject/he*/827637*`
- - `myproject/he??o/827637*`
- - `m*/h*/8276376b077eda104c812e6ec2f488c7c9eea211ce572c83d734c10bf241209f`
-
- """
- # Note that the backticks in the above docstring are important for the
- # generated docs. When sphinx is generating rst output from the help output
- # of this command, the asterisks will be interpreted as emphasis tokens if
- # they are not somehow escaped.
-
- with app.initialized():
- logsdirs = app.stream.artifact_log(artifacts)
-
- with ExitStack() as stack:
- extractdirs = []
- for logsdir in logsdirs:
- # NOTE: If reading the logs feels unresponsive, here would be a good place
- # to provide progress information.
- td = stack.enter_context(TemporaryDirectory())
- logsdir.export_files(td, can_link=True)
- extractdirs.append(td)
-
- for extractdir in extractdirs:
- for log in (os.path.join(extractdir, log) for log in os.listdir(extractdir)):
- # NOTE: Should click gain the ability to pass files to the pager this can be optimised.
- with open(log) as f:
- data = f.read()
- click.echo_via_pager(data)
-
-
-###################################################################
-# Artifact Delete Command #
-###################################################################
-@artifact.command(name='delete', short_help="Remove artifacts from the local cache")
-@click.option('--no-prune', 'no_prune', default=False, is_flag=True,
- help="Do not prune the local cache of unreachable refs")
-@click.argument('artifacts', type=click.Path(), nargs=-1)
-@click.pass_obj
-def artifact_delete(app, artifacts, no_prune):
- """Remove artifacts from the local cache"""
- with app.initialized():
- app.stream.artifact_delete(artifacts, no_prune)
-
-
-##################################################################
-# DEPRECATED Commands #
-##################################################################
-
-# XXX: The following commands are now obsolete, but they are kept
-# here along with all the options so that we can provide nice error
-# messages when they are called.
-# Also, note that these commands are hidden from the top-level help.
-
-##################################################################
-# Fetch Command #
-##################################################################
-@cli.command(short_help="COMMAND OBSOLETE - Fetch sources in a pipeline", hidden=True)
-@click.option('--except', 'except_', multiple=True,
- type=click.Path(readable=False),
- help="Except certain dependencies from fetching")
-@click.option('--deps', '-d', default='plan',
- type=click.Choice(['none', 'plan', 'all']),
- help='The dependencies to fetch (default: plan)')
-@click.option('--track', 'track_', default=False, is_flag=True,
- help="Track new source references before fetching")
-@click.option('--track-cross-junctions', '-J', default=False, is_flag=True,
- help="Allow tracking to cross junction boundaries")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def fetch(app, elements, deps, track_, except_, track_cross_junctions):
- click.echo("This command is now obsolete. Use `bst source fetch` instead.", err=True)
- sys.exit(1)
-
-
-##################################################################
-# Track Command #
-##################################################################
-@cli.command(short_help="COMMAND OBSOLETE - Track new source references", hidden=True)
-@click.option('--except', 'except_', multiple=True,
- type=click.Path(readable=False),
- help="Except certain dependencies from tracking")
-@click.option('--deps', '-d', default='none',
- type=click.Choice(['none', 'all']),
- help='The dependencies to track (default: none)')
-@click.option('--cross-junctions', '-J', default=False, is_flag=True,
- help="Allow crossing junction boundaries")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def track(app, elements, deps, except_, cross_junctions):
- click.echo("This command is now obsolete. Use `bst source track` instead.", err=True)
- sys.exit(1)
-
-
-##################################################################
-# Checkout Command #
-##################################################################
-@cli.command(short_help="COMMAND OBSOLETE - Checkout a built artifact", hidden=True)
-@click.option('--force', '-f', default=False, is_flag=True,
- help="Allow files to be overwritten")
-@click.option('--deps', '-d', default='run',
- type=click.Choice(['run', 'build', 'none']),
- help='The dependencies to checkout (default: run)')
-@click.option('--integrate/--no-integrate', default=True, is_flag=True,
- help="Whether to run integration commands")
-@click.option('--hardlinks', default=False, is_flag=True,
- help="Checkout hardlinks instead of copies (handle with care)")
-@click.option('--tar', default=False, is_flag=True,
- help="Create a tarball from the artifact contents instead "
- "of a file tree. If LOCATION is '-', the tarball "
- "will be dumped to the standard output.")
-@click.argument('element', required=False,
- type=click.Path(readable=False))
-@click.argument('location', type=click.Path(), required=False)
-@click.pass_obj
-def checkout(app, element, location, force, deps, integrate, hardlinks, tar):
- click.echo("This command is now obsolete. Use `bst artifact checkout` instead " +
- "and use the --directory option to specify LOCATION", err=True)
- sys.exit(1)
-
-
-################################################################
-# Pull Command #
-################################################################
-@cli.command(short_help="COMMAND OBSOLETE - Pull a built artifact", hidden=True)
-@click.option('--deps', '-d', default='none',
- type=click.Choice(['none', 'all']),
- help='The dependency artifacts to pull (default: none)')
-@click.option('--remote', '-r',
- help="The URL of the remote cache (defaults to the first configured cache)")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def pull(app, elements, deps, remote):
- click.echo("This command is now obsolete. Use `bst artifact pull` instead.", err=True)
- sys.exit(1)
-
-
-##################################################################
-# Push Command #
-##################################################################
-@cli.command(short_help="COMMAND OBSOLETE - Push a built artifact", hidden=True)
-@click.option('--deps', '-d', default='none',
- type=click.Choice(['none', 'all']),
- help='The dependencies to push (default: none)')
-@click.option('--remote', '-r', default=None,
- help="The URL of the remote cache (defaults to the first configured cache)")
-@click.argument('elements', nargs=-1,
- type=click.Path(readable=False))
-@click.pass_obj
-def push(app, elements, deps, remote):
- click.echo("This command is now obsolete. Use `bst artifact push` instead.", err=True)
- sys.exit(1)
diff --git a/buildstream/_frontend/complete.py b/buildstream/_frontend/complete.py
deleted file mode 100644
index bf9324812..000000000
--- a/buildstream/_frontend/complete.py
+++ /dev/null
@@ -1,338 +0,0 @@
-#
-# Copyright (c) 2014 by Armin Ronacher.
-# Copyright (C) 2016 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/>.
-#
-# This module was forked from the python click library, Included
-# original copyright notice from the Click library and following disclaimer
-# as per their LICENSE requirements.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-import collections.abc
-import copy
-import os
-
-import click
-from click.core import MultiCommand, Option, Argument
-from click.parser import split_arg_string
-
-WORDBREAK = '='
-
-COMPLETION_SCRIPT = '''
-%(complete_func)s() {
- local IFS=$'\n'
- COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
- COMP_CWORD=$COMP_CWORD \\
- %(autocomplete_var)s=complete $1 ) )
- return 0
-}
-
-complete -F %(complete_func)s -o nospace %(script_names)s
-'''
-
-
-# An exception for our custom completion handler to
-# indicate that it does not want to handle completion
-# for this parameter
-#
-class CompleteUnhandled(Exception):
- pass
-
-
-def complete_path(path_type, incomplete, base_directory='.'):
- """Helper method for implementing the completions() method
- for File and Path parameter types.
- """
-
- # Try listing the files in the relative or absolute path
- # specified in `incomplete` minus the last path component,
- # otherwise list files starting from the current working directory.
- entries = []
- base_path = ''
-
- # This is getting a bit messy
- listed_base_directory = False
-
- if os.path.sep in incomplete:
- split = incomplete.rsplit(os.path.sep, 1)
- base_path = split[0]
-
- # If there was nothing on the left of the last separator,
- # we are completing files in the filesystem root
- base_path = os.path.join(base_directory, base_path)
- else:
- incomplete_base_path = os.path.join(base_directory, incomplete)
- if os.path.isdir(incomplete_base_path):
- base_path = incomplete_base_path
-
- try:
- if base_path:
- if os.path.isdir(base_path):
- entries = [os.path.join(base_path, e) for e in os.listdir(base_path)]
- else:
- entries = os.listdir(base_directory)
- listed_base_directory = True
- except OSError:
- # If for any reason the os reports an error from os.listdir(), just
- # ignore this and avoid a stack trace
- pass
-
- base_directory_slash = base_directory
- if not base_directory_slash.endswith(os.sep):
- base_directory_slash += os.sep
- base_directory_len = len(base_directory_slash)
-
- def entry_is_dir(entry):
- if listed_base_directory:
- entry = os.path.join(base_directory, entry)
- return os.path.isdir(entry)
-
- def fix_path(path):
-
- # Append slashes to any entries which are directories, or
- # spaces for other files since they cannot be further completed
- if entry_is_dir(path) and not path.endswith(os.sep):
- path = path + os.sep
- else:
- path = path + " "
-
- # Remove the artificial leading path portion which
- # may have been prepended for search purposes.
- if path.startswith(base_directory_slash):
- path = path[base_directory_len:]
-
- return path
-
- return [
- # Return an appropriate path for each entry
- fix_path(e) for e in sorted(entries)
-
- # Filter out non directory elements when searching for a directory,
- # the opposite is fine, however.
- if not (path_type == 'Directory' and not entry_is_dir(e))
- ]
-
-
-# Instead of delegating completions to the param type,
-# hard code all of buildstream's completions here.
-#
-# This whole module should be removed in favor of more
-# generic code in click once this issue is resolved:
-# https://github.com/pallets/click/issues/780
-#
-def get_param_type_completion(param_type, incomplete):
-
- if isinstance(param_type, click.Choice):
- return [c + " " for c in param_type.choices]
- elif isinstance(param_type, click.File):
- return complete_path("File", incomplete)
- elif isinstance(param_type, click.Path):
- return complete_path(param_type.path_type, incomplete)
-
- return []
-
-
-def resolve_ctx(cli, prog_name, args):
- """
- Parse into a hierarchy of contexts. Contexts are connected through the parent variable.
- :param cli: command definition
- :param prog_name: the program that is running
- :param args: full list of args typed before the incomplete arg
- :return: the final context/command parsed
- """
- ctx = cli.make_context(prog_name, args, resilient_parsing=True)
- args_remaining = ctx.protected_args + ctx.args
- while ctx is not None and args_remaining:
- if isinstance(ctx.command, MultiCommand):
- cmd = ctx.command.get_command(ctx, args_remaining[0])
- if cmd is None:
- return None
- ctx = cmd.make_context(args_remaining[0], args_remaining[1:], parent=ctx, resilient_parsing=True)
- args_remaining = ctx.protected_args + ctx.args
- else:
- ctx = ctx.parent
-
- return ctx
-
-
-def start_of_option(param_str):
- """
- :param param_str: param_str to check
- :return: whether or not this is the start of an option declaration (i.e. starts "-" or "--")
- """
- return param_str and param_str[:1] == '-'
-
-
-def is_incomplete_option(all_args, cmd_param):
- """
- :param all_args: the full original list of args supplied
- :param cmd_param: the current command paramter
- :return: whether or not the last option declaration (i.e. starts "-" or "--") is incomplete and
- corresponds to this cmd_param. In other words whether this cmd_param option can still accept
- values
- """
- if cmd_param.is_flag:
- return False
- last_option = None
- for index, arg_str in enumerate(reversed([arg for arg in all_args if arg != WORDBREAK])):
- if index + 1 > cmd_param.nargs:
- break
- if start_of_option(arg_str):
- last_option = arg_str
-
- return bool(last_option and last_option in cmd_param.opts)
-
-
-def is_incomplete_argument(current_params, cmd_param):
- """
- :param current_params: the current params and values for this argument as already entered
- :param cmd_param: the current command parameter
- :return: whether or not the last argument is incomplete and corresponds to this cmd_param. In
- other words whether or not the this cmd_param argument can still accept values
- """
- current_param_values = current_params[cmd_param.name]
- if current_param_values is None:
- return True
- if cmd_param.nargs == -1:
- return True
- if isinstance(current_param_values, collections.abc.Iterable) \
- and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs:
- return True
- return False
-
-
-def get_user_autocompletions(args, incomplete, cmd, cmd_param, override):
- """
- :param args: full list of args typed before the incomplete arg
- :param incomplete: the incomplete text of the arg to autocomplete
- :param cmd_param: command definition
- :param override: a callable (cmd_param, args, incomplete) that will be
- called to override default completion based on parameter type. Should raise
- 'CompleteUnhandled' if it could not find a completion.
- :return: all the possible user-specified completions for the param
- """
-
- # Use the type specific default completions unless it was overridden
- try:
- return override(cmd=cmd,
- cmd_param=cmd_param,
- args=args,
- incomplete=incomplete)
- except CompleteUnhandled:
- return get_param_type_completion(cmd_param.type, incomplete) or []
-
-
-def get_choices(cli, prog_name, args, incomplete, override):
- """
- :param cli: command definition
- :param prog_name: the program that is running
- :param args: full list of args typed before the incomplete arg
- :param incomplete: the incomplete text of the arg to autocomplete
- :param override: a callable (cmd_param, args, incomplete) that will be
- called to override default completion based on parameter type. Should raise
- 'CompleteUnhandled' if it could not find a completion.
- :return: all the possible completions for the incomplete
- """
- all_args = copy.deepcopy(args)
-
- ctx = resolve_ctx(cli, prog_name, args)
- if ctx is None:
- return
-
- # In newer versions of bash long opts with '='s are partitioned, but it's easier to parse
- # without the '='
- if start_of_option(incomplete) and WORDBREAK in incomplete:
- partition_incomplete = incomplete.partition(WORDBREAK)
- all_args.append(partition_incomplete[0])
- incomplete = partition_incomplete[2]
- elif incomplete == WORDBREAK:
- incomplete = ''
-
- choices = []
- found_param = False
- if start_of_option(incomplete):
- # completions for options
- for param in ctx.command.params:
- if isinstance(param, Option):
- choices.extend([param_opt + " " for param_opt in param.opts + param.secondary_opts
- if param_opt not in all_args or param.multiple])
- found_param = True
- if not found_param:
- # completion for option values by choices
- for cmd_param in ctx.command.params:
- if isinstance(cmd_param, Option) and is_incomplete_option(all_args, cmd_param):
- choices.extend(get_user_autocompletions(all_args, incomplete, ctx.command, cmd_param, override))
- found_param = True
- break
- if not found_param:
- # completion for argument values by choices
- for cmd_param in ctx.command.params:
- if isinstance(cmd_param, Argument) and is_incomplete_argument(ctx.params, cmd_param):
- choices.extend(get_user_autocompletions(all_args, incomplete, ctx.command, cmd_param, override))
- found_param = True
- break
-
- if not found_param and isinstance(ctx.command, MultiCommand):
- # completion for any subcommands
- choices.extend([cmd + " " for cmd in ctx.command.list_commands(ctx)
- if not ctx.command.get_command(ctx, cmd).hidden])
-
- if not start_of_option(incomplete) and ctx.parent is not None \
- and isinstance(ctx.parent.command, MultiCommand) and ctx.parent.command.chain:
- # completion for chained commands
- visible_commands = [cmd for cmd in ctx.parent.command.list_commands(ctx.parent)
- if not ctx.parent.command.get_command(ctx.parent, cmd).hidden]
- remaining_comands = set(visible_commands) - set(ctx.parent.protected_args)
- choices.extend([cmd + " " for cmd in remaining_comands])
-
- for item in choices:
- if item.startswith(incomplete):
- yield item
-
-
-def do_complete(cli, prog_name, override):
- cwords = split_arg_string(os.environ['COMP_WORDS'])
- cword = int(os.environ['COMP_CWORD'])
- args = cwords[1:cword]
- try:
- incomplete = cwords[cword]
- except IndexError:
- incomplete = ''
-
- for item in get_choices(cli, prog_name, args, incomplete, override):
- click.echo(item)
-
-
-# Main function called from main.py at startup here
-#
-def main_bashcomplete(cmd, prog_name, override):
- """Internal handler for the bash completion support."""
-
- if '_BST_COMPLETION' in os.environ:
- do_complete(cmd, prog_name, override)
- return True
-
- return False
diff --git a/buildstream/_frontend/linuxapp.py b/buildstream/_frontend/linuxapp.py
deleted file mode 100644
index 0444dc7b4..000000000
--- a/buildstream/_frontend/linuxapp.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Copyright (C) 2018 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>
-import os
-import click
-
-from .app import App
-
-
-# This trick is currently only supported on some terminals,
-# avoid using it where it can cause garbage to be printed
-# to the terminal.
-#
-def _osc_777_supported():
-
- term = os.environ.get('TERM')
-
- if term and (term.startswith('xterm') or term.startswith('vte')):
-
- # Since vte version 4600, upstream silently ignores
- # the OSC 777 without printing garbage to the terminal.
- #
- # For distros like Fedora who have patched vte, this
- # will trigger a desktop notification and bring attention
- # to the terminal.
- #
- vte_version = os.environ.get('VTE_VERSION')
- try:
- vte_version_int = int(vte_version)
- except (ValueError, TypeError):
- return False
-
- if vte_version_int >= 4600:
- return True
-
- return False
-
-
-# A linux specific App implementation
-#
-class LinuxApp(App):
-
- def notify(self, title, text):
-
- # Currently we only try this notification method
- # of sending an escape sequence to the terminal
- #
- if _osc_777_supported():
- click.echo("\033]777;notify;{};{}\007".format(title, text), err=True)
diff --git a/buildstream/_frontend/profile.py b/buildstream/_frontend/profile.py
deleted file mode 100644
index dda0f7ffe..000000000
--- a/buildstream/_frontend/profile.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# Copyright (C) 2018 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>
-import re
-import copy
-import click
-
-
-# Profile()
-#
-# A class for formatting text with ansi color codes
-#
-# Kwargs:
-# The same keyword arguments which can be used with click.style()
-#
-class Profile():
- def __init__(self, **kwargs):
- self._kwargs = dict(kwargs)
-
- # fmt()
- #
- # Format some text with ansi color codes
- #
- # Args:
- # text (str): The text to format
- #
- # Kwargs:
- # Keyword arguments to apply on top of the base click.style()
- # arguments
- #
- def fmt(self, text, **kwargs):
- kwargs = dict(kwargs)
- fmtargs = copy.copy(self._kwargs)
- fmtargs.update(kwargs)
- return click.style(text, **fmtargs)
-
- # fmt_subst()
- #
- # Substitute a variable of the %{varname} form, formatting
- # only the substituted text with the given click.style() configurations
- #
- # Args:
- # text (str): The text to format, with possible variables
- # varname (str): The variable name to substitute
- # value (str): The value to substitute the variable with
- #
- # Kwargs:
- # Keyword arguments to apply on top of the base click.style()
- # arguments
- #
- def fmt_subst(self, text, varname, value, **kwargs):
-
- def subst_callback(match):
- # Extract and format the "{(varname)...}" portion of the match
- inner_token = match.group(1)
- formatted = inner_token.format(**{varname: value})
-
- # Colorize after the pythonic format formatting, which may have padding
- return self.fmt(formatted, **kwargs)
-
- # Lazy regex, after our word, match anything that does not have '%'
- return re.sub(r"%(\{(" + varname + r")[^%]*\})", subst_callback, text)
diff --git a/buildstream/_frontend/status.py b/buildstream/_frontend/status.py
deleted file mode 100644
index 91f47221a..000000000
--- a/buildstream/_frontend/status.py
+++ /dev/null
@@ -1,523 +0,0 @@
-#
-# Copyright (C) 2018 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>
-import os
-import sys
-import curses
-import click
-
-# Import a widget internal for formatting time codes
-from .widget import TimeCode
-from .._scheduler import ElementJob
-
-
-# Status()
-#
-# A widget for formatting overall status.
-#
-# Note that the render() and clear() methods in this class are
-# simply noops in the case that the application is not connected
-# to a terminal, or if the terminal does not support ANSI escape codes.
-#
-# Args:
-# context (Context): The Context
-# content_profile (Profile): Formatting profile for content text
-# format_profile (Profile): Formatting profile for formatting text
-# success_profile (Profile): Formatting profile for success text
-# error_profile (Profile): Formatting profile for error text
-# stream (Stream): The Stream
-# colors (bool): Whether to print the ANSI color codes in the output
-#
-class Status():
-
- # Table of the terminal capabilities we require and use
- _TERM_CAPABILITIES = {
- 'move_up': 'cuu1',
- 'move_x': 'hpa',
- 'clear_eol': 'el'
- }
-
- def __init__(self, context,
- content_profile, format_profile,
- success_profile, error_profile,
- stream, colors=False):
-
- self._context = context
- self._content_profile = content_profile
- self._format_profile = format_profile
- self._success_profile = success_profile
- self._error_profile = error_profile
- self._stream = stream
- self._jobs = []
- self._last_lines = 0 # Number of status lines we last printed to console
- self._spacing = 1
- self._colors = colors
- self._header = _StatusHeader(context,
- content_profile, format_profile,
- success_profile, error_profile,
- stream)
-
- self._term_width, _ = click.get_terminal_size()
- self._alloc_lines = 0
- self._alloc_columns = None
- self._line_length = 0
- self._need_alloc = True
- self._term_caps = self._init_terminal()
-
- # add_job()
- #
- # Adds a job to track in the status area
- #
- # Args:
- # element (Element): The element of the job to track
- # action_name (str): The action name for this job
- #
- def add_job(self, job):
- elapsed = self._stream.elapsed_time
- job = _StatusJob(self._context, job, self._content_profile, self._format_profile, elapsed)
- self._jobs.append(job)
- self._need_alloc = True
-
- # remove_job()
- #
- # Removes a job currently being tracked in the status area
- #
- # Args:
- # element (Element): The element of the job to track
- # action_name (str): The action name for this job
- #
- def remove_job(self, job):
- action_name = job.action_name
- if not isinstance(job, ElementJob):
- element = None
- else:
- element = job.element
-
- self._jobs = [
- job for job in self._jobs
- if not (job.element is element and
- job.action_name == action_name)
- ]
- self._need_alloc = True
-
- # clear()
- #
- # Clear the status area, it is necessary to call
- # this before printing anything to the console if
- # a status area is in use.
- #
- # To print some logging to the output and then restore
- # the status, use the following:
- #
- # status.clear()
- # ... print something to console ...
- # status.render()
- #
- def clear(self):
-
- if not self._term_caps:
- return
-
- for _ in range(self._last_lines):
- self._move_up()
- self._clear_line()
- self._last_lines = 0
-
- # render()
- #
- # Render the status area.
- #
- # If you are not printing a line in addition to rendering
- # the status area, for instance in a timeout, then it is
- # not necessary to call clear().
- def render(self):
-
- if not self._term_caps:
- return
-
- elapsed = self._stream.elapsed_time
-
- self.clear()
- self._check_term_width()
- self._allocate()
-
- # Nothing to render, early return
- if self._alloc_lines == 0:
- return
-
- # Before rendering the actual lines, we need to add some line
- # feeds for the amount of lines we intend to print first, and
- # move cursor position back to the first line
- for _ in range(self._alloc_lines + self._header.lines):
- click.echo('', err=True)
- for _ in range(self._alloc_lines + self._header.lines):
- self._move_up()
-
- # Render the one line header
- text = self._header.render(self._term_width, elapsed)
- click.echo(text, color=self._colors, err=True)
-
- # Now we have the number of columns, and an allocation for
- # alignment of each column
- n_columns = len(self._alloc_columns)
- for line in self._job_lines(n_columns):
- text = ''
- for job in line:
- column = line.index(job)
- text += job.render(self._alloc_columns[column] - job.size, elapsed)
-
- # Add spacing between columns
- if column < (n_columns - 1):
- text += ' ' * self._spacing
-
- # Print the line
- click.echo(text, color=self._colors, err=True)
-
- # Track what we printed last, for the next clear
- self._last_lines = self._alloc_lines + self._header.lines
-
- ###################################################
- # Private Methods #
- ###################################################
-
- # _init_terminal()
- #
- # Initialize the terminal and return the resolved terminal
- # capabilities dictionary.
- #
- # Returns:
- # (dict|None): The resolved terminal capabilities dictionary,
- # or None if the terminal does not support all
- # of the required capabilities.
- #
- def _init_terminal(self):
-
- # We need both output streams to be connected to a terminal
- if not (sys.stdout.isatty() and sys.stderr.isatty()):
- return None
-
- # Initialized terminal, curses might decide it doesnt
- # support this terminal
- try:
- curses.setupterm(os.environ.get('TERM', 'dumb'))
- except curses.error:
- return None
-
- term_caps = {}
-
- # Resolve the string capabilities we need for the capability
- # names we need.
- #
- for capname, capval in self._TERM_CAPABILITIES.items():
- code = curses.tigetstr(capval)
-
- # If any of the required capabilities resolve empty strings or None,
- # then we don't have the capabilities we need for a status bar on
- # this terminal.
- if not code:
- return None
-
- # Decode sequences as latin1, as they are always 8-bit bytes,
- # so when b'\xff' is returned, this must be decoded to u'\xff'.
- #
- # This technique is employed by the python blessings library
- # as well, and should provide better compatibility with most
- # terminals.
- #
- term_caps[capname] = code.decode('latin1')
-
- return term_caps
-
- def _check_term_width(self):
- term_width, _ = click.get_terminal_size()
- if self._term_width != term_width:
- self._term_width = term_width
- self._need_alloc = True
-
- def _move_up(self):
- assert self._term_caps is not None
-
- # Explicitly move to beginning of line, fixes things up
- # when there was a ^C or ^Z printed to the terminal.
- move_x = curses.tparm(self._term_caps['move_x'].encode('latin1'), 0)
- move_x = move_x.decode('latin1')
-
- move_up = curses.tparm(self._term_caps['move_up'].encode('latin1'))
- move_up = move_up.decode('latin1')
-
- click.echo(move_x + move_up, nl=False, err=True)
-
- def _clear_line(self):
- assert self._term_caps is not None
-
- clear_eol = curses.tparm(self._term_caps['clear_eol'].encode('latin1'))
- clear_eol = clear_eol.decode('latin1')
- click.echo(clear_eol, nl=False, err=True)
-
- def _allocate(self):
- if not self._need_alloc:
- return
-
- # State when there is no jobs to display
- alloc_lines = 0
- alloc_columns = []
- line_length = 0
-
- # Test for the widest width which fits columnized jobs
- for columns in reversed(range(len(self._jobs))):
- alloc_lines, alloc_columns = self._allocate_columns(columns + 1)
-
- # If the sum of column widths with spacing in between
- # fits into the terminal width, this is a good allocation.
- line_length = sum(alloc_columns) + (columns * self._spacing)
- if line_length < self._term_width:
- break
-
- self._alloc_lines = alloc_lines
- self._alloc_columns = alloc_columns
- self._line_length = line_length
- self._need_alloc = False
-
- def _job_lines(self, columns):
- for i in range(0, len(self._jobs), columns):
- yield self._jobs[i:i + columns]
-
- # Returns an array of integers representing the maximum
- # length in characters for each column, given the current
- # list of jobs to render.
- #
- def _allocate_columns(self, columns):
- column_widths = [0 for _ in range(columns)]
- lines = 0
- for line in self._job_lines(columns):
- line_len = len(line)
- lines += 1
- for col in range(columns):
- if col < line_len:
- job = line[col]
- column_widths[col] = max(column_widths[col], job.size)
-
- return lines, column_widths
-
-
-# _StatusHeader()
-#
-# A delegate object for rendering the header part of the Status() widget
-#
-# Args:
-# context (Context): The Context
-# content_profile (Profile): Formatting profile for content text
-# format_profile (Profile): Formatting profile for formatting text
-# success_profile (Profile): Formatting profile for success text
-# error_profile (Profile): Formatting profile for error text
-# stream (Stream): The Stream
-#
-class _StatusHeader():
-
- def __init__(self, context,
- content_profile, format_profile,
- success_profile, error_profile,
- stream):
-
- #
- # Public members
- #
- self.lines = 3
-
- #
- # Private members
- #
- self._content_profile = content_profile
- self._format_profile = format_profile
- self._success_profile = success_profile
- self._error_profile = error_profile
- self._stream = stream
- self._time_code = TimeCode(context, content_profile, format_profile)
- self._context = context
-
- def render(self, line_length, elapsed):
- project = self._context.get_toplevel_project()
- line_length = max(line_length, 80)
-
- #
- # Line 1: Session time, project name, session / total elements
- #
- # ========= 00:00:00 project-name (143/387) =========
- #
- session = str(len(self._stream.session_elements))
- total = str(len(self._stream.total_elements))
-
- size = 0
- text = ''
- size += len(total) + len(session) + 4 # Size for (N/N) with a leading space
- size += 8 # Size of time code
- size += len(project.name) + 1
- text += self._time_code.render_time(elapsed)
- text += ' ' + self._content_profile.fmt(project.name)
- text += ' ' + self._format_profile.fmt('(') + \
- self._content_profile.fmt(session) + \
- self._format_profile.fmt('/') + \
- self._content_profile.fmt(total) + \
- self._format_profile.fmt(')')
-
- line1 = self._centered(text, size, line_length, '=')
-
- #
- # Line 2: Dynamic list of queue status reports
- #
- # (Fetched:0 117 0)→ (Built:4 0 0)
- #
- size = 0
- text = ''
-
- # Format and calculate size for each queue progress
- for queue in self._stream.queues:
-
- # Add spacing
- if self._stream.queues.index(queue) > 0:
- size += 2
- text += self._format_profile.fmt('→ ')
-
- queue_text, queue_size = self._render_queue(queue)
- size += queue_size
- text += queue_text
-
- line2 = self._centered(text, size, line_length, ' ')
-
- #
- # Line 3: Cache usage percentage report
- #
- # ~~~~~~ cache: 69% ~~~~~~
- #
- usage = self._context.get_cache_usage()
- usage_percent = '{}%'.format(usage.used_percent)
-
- size = 21
- size += len(usage_percent)
- if usage.used_percent >= 95:
- formatted_usage_percent = self._error_profile.fmt(usage_percent)
- elif usage.used_percent >= 80:
- formatted_usage_percent = self._content_profile.fmt(usage_percent)
- else:
- formatted_usage_percent = self._success_profile.fmt(usage_percent)
-
- text = self._format_profile.fmt("~~~~~~ ") + \
- self._content_profile.fmt('cache') + \
- self._format_profile.fmt(': ') + \
- formatted_usage_percent + \
- self._format_profile.fmt(' ~~~~~~')
- line3 = self._centered(text, size, line_length, ' ')
-
- return line1 + '\n' + line2 + '\n' + line3
-
- ###################################################
- # Private Methods #
- ###################################################
- def _render_queue(self, queue):
- processed = str(len(queue.processed_elements))
- skipped = str(len(queue.skipped_elements))
- failed = str(len(queue.failed_elements))
-
- size = 5 # Space for the formatting '[', ':', ' ', ' ' and ']'
- size += len(queue.complete_name)
- size += len(processed) + len(skipped) + len(failed)
- text = self._format_profile.fmt("(") + \
- self._content_profile.fmt(queue.complete_name) + \
- self._format_profile.fmt(":") + \
- self._success_profile.fmt(processed) + ' ' + \
- self._content_profile.fmt(skipped) + ' ' + \
- self._error_profile.fmt(failed) + \
- self._format_profile.fmt(")")
-
- return (text, size)
-
- def _centered(self, text, size, line_length, fill):
- remaining = line_length - size
- remaining -= 2
-
- final_text = self._format_profile.fmt(fill * (remaining // 2)) + ' '
- final_text += text
- final_text += ' ' + self._format_profile.fmt(fill * (remaining // 2))
-
- return final_text
-
-
-# _StatusJob()
-#
-# A delegate object for rendering a job in the status area
-#
-# Args:
-# context (Context): The Context
-# job (Job): The job being processed
-# content_profile (Profile): Formatting profile for content text
-# format_profile (Profile): Formatting profile for formatting text
-# elapsed (datetime): The offset into the session when this job is created
-#
-class _StatusJob():
-
- def __init__(self, context, job, content_profile, format_profile, elapsed):
- action_name = job.action_name
- if not isinstance(job, ElementJob):
- element = None
- else:
- element = job.element
-
- #
- # Public members
- #
- self.element = element # The Element
- self.action_name = action_name # The action name
- self.size = None # The number of characters required to render
- self.full_name = element._get_full_name() if element else action_name
-
- #
- # Private members
- #
- self._offset = elapsed
- self._content_profile = content_profile
- self._format_profile = format_profile
- self._time_code = TimeCode(context, content_profile, format_profile)
-
- # Calculate the size needed to display
- self.size = 10 # Size of time code with brackets
- self.size += len(action_name)
- self.size += len(self.full_name)
- self.size += 3 # '[' + ':' + ']'
-
- # render()
- #
- # Render the Job, return a rendered string
- #
- # Args:
- # padding (int): Amount of padding to print in order to align with columns
- # elapsed (datetime): The session elapsed time offset
- #
- def render(self, padding, elapsed):
- text = self._format_profile.fmt('[') + \
- self._time_code.render_time(elapsed - self._offset) + \
- self._format_profile.fmt(']')
-
- # Add padding after the display name, before terminating ']'
- name = self.full_name + (' ' * padding)
- text += self._format_profile.fmt('[') + \
- self._content_profile.fmt(self.action_name) + \
- self._format_profile.fmt(':') + \
- self._content_profile.fmt(name) + \
- self._format_profile.fmt(']')
-
- return text
diff --git a/buildstream/_frontend/widget.py b/buildstream/_frontend/widget.py
deleted file mode 100644
index cfe3a06e9..000000000
--- a/buildstream/_frontend/widget.py
+++ /dev/null
@@ -1,806 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-import datetime
-import os
-from collections import defaultdict, OrderedDict
-from contextlib import ExitStack
-from mmap import mmap
-import re
-import textwrap
-from ruamel import yaml
-import click
-
-from .profile import Profile
-from .. import Element, Consistency, Scope
-from .. import _yaml
-from .. import __version__ as bst_version
-from .._exceptions import ImplError
-from .._message import MessageType
-from ..plugin import Plugin
-
-
-# These messages are printed a bit differently
-ERROR_MESSAGES = [MessageType.FAIL, MessageType.ERROR, MessageType.BUG]
-
-
-# Widget()
-#
-# Args:
-# content_profile (Profile): The profile to use for rendering content
-# format_profile (Profile): The profile to use for rendering formatting
-#
-# An abstract class for printing output columns in our text UI.
-#
-class Widget():
-
- def __init__(self, context, content_profile, format_profile):
-
- # The context
- self.context = context
-
- # The content profile
- self.content_profile = content_profile
-
- # The formatting profile
- self.format_profile = format_profile
-
- # render()
- #
- # Renders a string to be printed in the UI
- #
- # Args:
- # message (Message): A message to print
- #
- # Returns:
- # (str): The string this widget prints for the given message
- #
- def render(self, message):
- raise ImplError("{} does not implement render()".format(type(self).__name__))
-
-
-# Used to add spacing between columns
-class Space(Widget):
-
- def render(self, message):
- return ' '
-
-
-# Used to add fixed text between columns
-class FixedText(Widget):
-
- def __init__(self, context, text, content_profile, format_profile):
- super(FixedText, self).__init__(context, content_profile, format_profile)
- self.text = text
-
- def render(self, message):
- return self.format_profile.fmt(self.text)
-
-
-# Used to add the wallclock time this message was created at
-class WallclockTime(Widget):
- def __init__(self, context, content_profile, format_profile, output_format=False):
- self._output_format = output_format
- super(WallclockTime, self).__init__(context, content_profile, format_profile)
-
- def render(self, message):
-
- fields = [self.content_profile.fmt("{:02d}".format(x)) for x in
- [message.creation_time.hour,
- message.creation_time.minute,
- message.creation_time.second,
- ]
- ]
- text = self.format_profile.fmt(":").join(fields)
-
- if self._output_format == 'us':
- text += self.content_profile.fmt(".{:06d}".format(message.creation_time.microsecond))
-
- return text
-
-
-# A widget for rendering the debugging column
-class Debug(Widget):
-
- def render(self, message):
- unique_id = 0 if message.unique_id is None else message.unique_id
-
- text = self.format_profile.fmt('pid:')
- text += self.content_profile.fmt("{: <5}".format(message.pid))
- text += self.format_profile.fmt(" id:")
- text += self.content_profile.fmt("{:0>3}".format(unique_id))
-
- return text
-
-
-# A widget for rendering the time codes
-class TimeCode(Widget):
- def __init__(self, context, content_profile, format_profile, microseconds=False):
- self._microseconds = microseconds
- super(TimeCode, self).__init__(context, content_profile, format_profile)
-
- def render(self, message):
- return self.render_time(message.elapsed)
-
- def render_time(self, elapsed):
- if elapsed is None:
- fields = [
- self.content_profile.fmt('--')
- for i in range(3)
- ]
- else:
- hours, remainder = divmod(int(elapsed.total_seconds()), 60 * 60)
- minutes, seconds = divmod(remainder, 60)
- fields = [
- self.content_profile.fmt("{0:02d}".format(field))
- for field in [hours, minutes, seconds]
- ]
-
- text = self.format_profile.fmt(':').join(fields)
-
- if self._microseconds:
- if elapsed is not None:
- text += self.content_profile.fmt(".{0:06d}".format(elapsed.microseconds))
- else:
- text += self.content_profile.fmt(".------")
- return text
-
-
-# A widget for rendering the MessageType
-class TypeName(Widget):
-
- _action_colors = {
- MessageType.DEBUG: "cyan",
- MessageType.STATUS: "cyan",
- MessageType.INFO: "magenta",
- MessageType.WARN: "yellow",
- MessageType.START: "blue",
- MessageType.SUCCESS: "green",
- MessageType.FAIL: "red",
- MessageType.SKIPPED: "yellow",
- MessageType.ERROR: "red",
- MessageType.BUG: "red",
- }
-
- def render(self, message):
- return self.content_profile.fmt("{: <7}"
- .format(message.message_type.upper()),
- bold=True, dim=True,
- fg=self._action_colors[message.message_type])
-
-
-# A widget for displaying the Element name
-class ElementName(Widget):
-
- def render(self, message):
- action_name = message.action_name
- element_id = message.task_id or message.unique_id
- if element_id is not None:
- plugin = Plugin._lookup(element_id)
- name = plugin._get_full_name()
- name = '{: <30}'.format(name)
- else:
- name = 'core activity'
- name = '{: <30}'.format(name)
-
- if not action_name:
- action_name = "Main"
-
- return self.content_profile.fmt("{: >8}".format(action_name.lower())) + \
- self.format_profile.fmt(':') + self.content_profile.fmt(name)
-
-
-# A widget for displaying the primary message text
-class MessageText(Widget):
-
- def render(self, message):
- return message.message
-
-
-# A widget for formatting the element cache key
-class CacheKey(Widget):
-
- def __init__(self, context, content_profile, format_profile, err_profile):
- super(CacheKey, self).__init__(context, content_profile, format_profile)
-
- self._err_profile = err_profile
- self._key_length = context.log_key_length
-
- def render(self, message):
-
- element_id = message.task_id or message.unique_id
- if not self._key_length:
- return ""
-
- if element_id is None:
- return ' ' * self._key_length
-
- missing = False
- key = ' ' * self._key_length
- plugin = Plugin._lookup(element_id)
- if isinstance(plugin, Element):
- _, key, missing = plugin._get_display_key()
-
- if message.message_type in ERROR_MESSAGES:
- text = self._err_profile.fmt(key)
- else:
- text = self.content_profile.fmt(key, dim=missing)
-
- return text
-
-
-# A widget for formatting the log file
-class LogFile(Widget):
-
- def __init__(self, context, content_profile, format_profile, err_profile):
- super(LogFile, self).__init__(context, content_profile, format_profile)
-
- self._err_profile = err_profile
- self._logdir = context.logdir
-
- def render(self, message, abbrev=True):
-
- if message.logfile and message.scheduler:
- logfile = message.logfile
-
- if abbrev and self._logdir != "" and logfile.startswith(self._logdir):
- logfile = logfile[len(self._logdir):]
- logfile = logfile.lstrip(os.sep)
-
- if message.message_type in ERROR_MESSAGES:
- text = self._err_profile.fmt(logfile)
- else:
- text = self.content_profile.fmt(logfile, dim=True)
- else:
- text = ''
-
- return text
-
-
-# START and SUCCESS messages are expected to have no useful
-# information in the message text, so we display the logfile name for
-# these messages, and the message text for other types.
-#
-class MessageOrLogFile(Widget):
- def __init__(self, context, content_profile, format_profile, err_profile):
- super(MessageOrLogFile, self).__init__(context, content_profile, format_profile)
- self._message_widget = MessageText(context, content_profile, format_profile)
- self._logfile_widget = LogFile(context, content_profile, format_profile, err_profile)
-
- def render(self, message):
- # Show the log file only in the main start/success messages
- if message.logfile and message.scheduler and \
- message.message_type in [MessageType.START, MessageType.SUCCESS]:
- text = self._logfile_widget.render(message)
- else:
- text = self._message_widget.render(message)
- return text
-
-
-# LogLine
-#
-# A widget for formatting a log line
-#
-# Args:
-# context (Context): The Context
-# content_profile (Profile): Formatting profile for content text
-# format_profile (Profile): Formatting profile for formatting text
-# success_profile (Profile): Formatting profile for success text
-# error_profile (Profile): Formatting profile for error text
-# detail_profile (Profile): Formatting profile for detail text
-# indent (int): Number of spaces to use for general indentation
-#
-class LogLine(Widget):
-
- def __init__(self, context,
- content_profile,
- format_profile,
- success_profile,
- err_profile,
- detail_profile,
- indent=4):
- super(LogLine, self).__init__(context, content_profile, format_profile)
-
- self._columns = []
- self._failure_messages = defaultdict(list)
- self._success_profile = success_profile
- self._err_profile = err_profile
- self._detail_profile = detail_profile
- self._indent = ' ' * indent
- self._log_lines = context.log_error_lines
- self._message_lines = context.log_message_lines
- self._resolved_keys = None
-
- self._space_widget = Space(context, content_profile, format_profile)
- self._logfile_widget = LogFile(context, content_profile, format_profile, err_profile)
-
- if context.log_debug:
- self._columns.extend([
- Debug(context, content_profile, format_profile)
- ])
-
- self.logfile_variable_names = {
- "elapsed": TimeCode(context, content_profile, format_profile, microseconds=False),
- "elapsed-us": TimeCode(context, content_profile, format_profile, microseconds=True),
- "wallclock": WallclockTime(context, content_profile, format_profile),
- "wallclock-us": WallclockTime(context, content_profile, format_profile, output_format='us'),
- "key": CacheKey(context, content_profile, format_profile, err_profile),
- "element": ElementName(context, content_profile, format_profile),
- "action": TypeName(context, content_profile, format_profile),
- "message": MessageOrLogFile(context, content_profile, format_profile, err_profile)
- }
- logfile_tokens = self._parse_logfile_format(context.log_message_format, content_profile, format_profile)
- self._columns.extend(logfile_tokens)
-
- # show_pipeline()
- #
- # Display a list of elements in the specified format.
- #
- # The formatting string is the one currently documented in `bst show`, this
- # is used in pipeline session headings and also to implement `bst show`.
- #
- # Args:
- # dependencies (list of Element): A list of Element objects
- # format_: A formatting string, as specified by `bst show`
- #
- # Returns:
- # (str): The formatted list of elements
- #
- def show_pipeline(self, dependencies, format_):
- report = ''
- p = Profile()
-
- for element in dependencies:
- line = format_
-
- full_key, cache_key, dim_keys = element._get_display_key()
-
- line = p.fmt_subst(line, 'name', element._get_full_name(), fg='blue', bold=True)
- line = p.fmt_subst(line, 'key', cache_key, fg='yellow', dim=dim_keys)
- line = p.fmt_subst(line, 'full-key', full_key, fg='yellow', dim=dim_keys)
-
- consistency = element._get_consistency()
- if consistency == Consistency.INCONSISTENT:
- line = p.fmt_subst(line, 'state', "no reference", fg='red')
- else:
- if element._cached_failure():
- line = p.fmt_subst(line, 'state', "failed", fg='red')
- elif element._cached_success():
- line = p.fmt_subst(line, 'state', "cached", fg='magenta')
- elif consistency == Consistency.RESOLVED and not element._source_cached():
- line = p.fmt_subst(line, 'state', "fetch needed", fg='red')
- elif element._buildable():
- line = p.fmt_subst(line, 'state', "buildable", fg='green')
- else:
- line = p.fmt_subst(line, 'state', "waiting", fg='blue')
-
- # Element configuration
- if "%{config" in format_:
- config = _yaml.node_sanitize(element._Element__config)
- line = p.fmt_subst(
- line, 'config',
- yaml.round_trip_dump(config, default_flow_style=False, allow_unicode=True))
-
- # Variables
- if "%{vars" in format_:
- variables = _yaml.node_sanitize(element._Element__variables.flat)
- line = p.fmt_subst(
- line, 'vars',
- yaml.round_trip_dump(variables, default_flow_style=False, allow_unicode=True))
-
- # Environment
- if "%{env" in format_:
- environment = _yaml.node_sanitize(element._Element__environment)
- line = p.fmt_subst(
- line, 'env',
- yaml.round_trip_dump(environment, default_flow_style=False, allow_unicode=True))
-
- # Public
- if "%{public" in format_:
- environment = _yaml.node_sanitize(element._Element__public)
- line = p.fmt_subst(
- line, 'public',
- yaml.round_trip_dump(environment, default_flow_style=False, allow_unicode=True))
-
- # Workspaced
- if "%{workspaced" in format_:
- line = p.fmt_subst(
- line, 'workspaced',
- '(workspaced)' if element._get_workspace() else '', fg='yellow')
-
- # Workspace-dirs
- if "%{workspace-dirs" in format_:
- workspace = element._get_workspace()
- if workspace is not None:
- path = workspace.get_absolute_path()
- if path.startswith("~/"):
- path = os.path.join(os.getenv('HOME', '/root'), path[2:])
- line = p.fmt_subst(line, 'workspace-dirs', "Workspace: {}".format(path))
- else:
- line = p.fmt_subst(
- line, 'workspace-dirs', '')
-
- # Dependencies
- if "%{deps" in format_:
- deps = [e.name for e in element.dependencies(Scope.ALL, recurse=False)]
- line = p.fmt_subst(
- line, 'deps',
- yaml.safe_dump(deps, default_style=None).rstrip('\n'))
-
- # Build Dependencies
- if "%{build-deps" in format_:
- build_deps = [e.name for e in element.dependencies(Scope.BUILD, recurse=False)]
- line = p.fmt_subst(
- line, 'build-deps',
- yaml.safe_dump(build_deps, default_style=False).rstrip('\n'))
-
- # Runtime Dependencies
- if "%{runtime-deps" in format_:
- runtime_deps = [e.name for e in element.dependencies(Scope.RUN, recurse=False)]
- line = p.fmt_subst(
- line, 'runtime-deps',
- yaml.safe_dump(runtime_deps, default_style=False).rstrip('\n'))
-
- report += line + '\n'
-
- return report.rstrip('\n')
-
- # print_heading()
- #
- # A message to be printed at program startup, indicating
- # some things about user configuration and BuildStream version
- # and so on.
- #
- # Args:
- # project (Project): The toplevel project we were invoked from
- # stream (Stream): The stream
- # log_file (file): An optional file handle for additional logging
- # styling (bool): Whether to enable ansi escape codes in the output
- #
- def print_heading(self, project, stream, *, log_file, styling=False):
- context = self.context
- starttime = datetime.datetime.now()
- text = ''
-
- self._resolved_keys = {element: element._get_cache_key() for element in stream.session_elements}
-
- # Main invocation context
- text += '\n'
- text += self.content_profile.fmt("BuildStream Version {}\n".format(bst_version), bold=True)
- values = OrderedDict()
- values["Session Start"] = starttime.strftime('%A, %d-%m-%Y at %H:%M:%S')
- values["Project"] = "{} ({})".format(project.name, project.directory)
- values["Targets"] = ", ".join([t.name for t in stream.targets])
- values["Cache Usage"] = str(context.get_cache_usage())
- text += self._format_values(values)
-
- # User configurations
- text += '\n'
- text += self.content_profile.fmt("User Configuration\n", bold=True)
- values = OrderedDict()
- values["Configuration File"] = \
- "Default Configuration" if not context.config_origin else context.config_origin
- values["Cache Directory"] = context.cachedir
- values["Log Files"] = context.logdir
- values["Source Mirrors"] = context.sourcedir
- values["Build Area"] = context.builddir
- values["Strict Build Plan"] = "Yes" if context.get_strict() else "No"
- values["Maximum Fetch Tasks"] = context.sched_fetchers
- values["Maximum Build Tasks"] = context.sched_builders
- values["Maximum Push Tasks"] = context.sched_pushers
- values["Maximum Network Retries"] = context.sched_network_retries
- text += self._format_values(values)
- text += '\n'
-
- # Project Options
- values = OrderedDict()
- project.options.printable_variables(values)
- if values:
- text += self.content_profile.fmt("Project Options\n", bold=True)
- text += self._format_values(values)
- text += '\n'
-
- # Plugins
- text += self._format_plugins(project.first_pass_config.element_factory.loaded_dependencies,
- project.first_pass_config.source_factory.loaded_dependencies)
- if project.config.element_factory and project.config.source_factory:
- text += self._format_plugins(project.config.element_factory.loaded_dependencies,
- project.config.source_factory.loaded_dependencies)
-
- # Pipeline state
- text += self.content_profile.fmt("Pipeline\n", bold=True)
- text += self.show_pipeline(stream.total_elements, context.log_element_format)
- text += '\n'
-
- # Separator line before following output
- text += self.format_profile.fmt("=" * 79 + '\n')
-
- click.echo(text, color=styling, nl=False, err=True)
- if log_file:
- click.echo(text, file=log_file, color=False, nl=False)
-
- # print_summary()
- #
- # Print a summary of activities at the end of a session
- #
- # Args:
- # stream (Stream): The Stream
- # log_file (file): An optional file handle for additional logging
- # styling (bool): Whether to enable ansi escape codes in the output
- #
- def print_summary(self, stream, log_file, styling=False):
-
- # Early silent return if there are no queues, can happen
- # only in the case that the stream early returned due to
- # an inconsistent pipeline state.
- if not stream.queues:
- return
-
- text = ''
-
- assert self._resolved_keys is not None
- elements = sorted(e for (e, k) in self._resolved_keys.items() if k != e._get_cache_key())
- if elements:
- text += self.content_profile.fmt("Resolved key Summary\n", bold=True)
- text += self.show_pipeline(elements, self.context.log_element_format)
- text += "\n\n"
-
- if self._failure_messages:
- values = OrderedDict()
-
- for element, messages in sorted(self._failure_messages.items(), key=lambda x: x[0].name):
- for queue in stream.queues:
- if any(el.name == element.name for el in queue.failed_elements):
- values[element.name] = ''.join(self._render(v) for v in messages)
- if values:
- text += self.content_profile.fmt("Failure Summary\n", bold=True)
- text += self._format_values(values, style_value=False)
-
- text += self.content_profile.fmt("Pipeline Summary\n", bold=True)
- values = OrderedDict()
-
- values['Total'] = self.content_profile.fmt(str(len(stream.total_elements)))
- values['Session'] = self.content_profile.fmt(str(len(stream.session_elements)))
-
- processed_maxlen = 1
- skipped_maxlen = 1
- failed_maxlen = 1
- for queue in stream.queues:
- processed_maxlen = max(len(str(len(queue.processed_elements))), processed_maxlen)
- skipped_maxlen = max(len(str(len(queue.skipped_elements))), skipped_maxlen)
- failed_maxlen = max(len(str(len(queue.failed_elements))), failed_maxlen)
-
- for queue in stream.queues:
- processed = str(len(queue.processed_elements))
- skipped = str(len(queue.skipped_elements))
- failed = str(len(queue.failed_elements))
-
- processed_align = ' ' * (processed_maxlen - len(processed))
- skipped_align = ' ' * (skipped_maxlen - len(skipped))
- failed_align = ' ' * (failed_maxlen - len(failed))
-
- status_text = self.content_profile.fmt("processed ") + \
- self._success_profile.fmt(processed) + \
- self.format_profile.fmt(', ') + processed_align
-
- status_text += self.content_profile.fmt("skipped ") + \
- self.content_profile.fmt(skipped) + \
- self.format_profile.fmt(', ') + skipped_align
-
- status_text += self.content_profile.fmt("failed ") + \
- self._err_profile.fmt(failed) + ' ' + failed_align
- values["{} Queue".format(queue.action_name)] = status_text
-
- text += self._format_values(values, style_value=False)
-
- click.echo(text, color=styling, nl=False, err=True)
- if log_file:
- click.echo(text, file=log_file, color=False, nl=False)
-
- ###################################################
- # Widget Abstract Methods #
- ###################################################
-
- def render(self, message):
-
- # Track logfiles for later use
- element_id = message.task_id or message.unique_id
- if message.message_type in ERROR_MESSAGES and element_id is not None:
- plugin = Plugin._lookup(element_id)
- self._failure_messages[plugin].append(message)
-
- return self._render(message)
-
- ###################################################
- # Private Methods #
- ###################################################
- def _parse_logfile_format(self, format_string, content_profile, format_profile):
- logfile_tokens = []
- while format_string:
- if format_string.startswith("%%"):
- logfile_tokens.append(FixedText(self.context, "%", content_profile, format_profile))
- format_string = format_string[2:]
- continue
- m = re.search(r"^%\{([^\}]+)\}", format_string)
- if m is not None:
- variable = m.group(1)
- format_string = format_string[m.end(0):]
- if variable not in self.logfile_variable_names:
- raise Exception("'{0}' is not a valid log variable name.".format(variable))
- logfile_tokens.append(self.logfile_variable_names[variable])
- else:
- m = re.search("^[^%]+", format_string)
- if m is not None:
- text = FixedText(self.context, m.group(0), content_profile, format_profile)
- format_string = format_string[m.end(0):]
- logfile_tokens.append(text)
- else:
- # No idea what to do now
- raise Exception("'{0}' could not be parsed into a valid logging format.".format(format_string))
- return logfile_tokens
-
- def _render(self, message):
-
- # Render the column widgets first
- text = ''
- for widget in self._columns:
- text += widget.render(message)
-
- text += '\n'
-
- extra_nl = False
-
- # Now add some custom things
- if message.detail:
-
- # Identify frontend messages, we never abbreviate these
- frontend_message = not (message.task_id or message.unique_id)
-
- # Split and truncate message detail down to message_lines lines
- lines = message.detail.splitlines(True)
-
- n_lines = len(lines)
- abbrev = False
- if message.message_type not in ERROR_MESSAGES \
- and not frontend_message and n_lines > self._message_lines:
- lines = lines[0:self._message_lines]
- if self._message_lines > 0:
- abbrev = True
- else:
- lines[n_lines - 1] = lines[n_lines - 1].rstrip('\n')
-
- detail = self._indent + self._indent.join(lines)
-
- text += '\n'
- if message.message_type in ERROR_MESSAGES:
- text += self._err_profile.fmt(detail, bold=True)
- else:
- text += self._detail_profile.fmt(detail)
-
- if abbrev:
- text += self._indent + \
- self.content_profile.fmt('Message contains {} additional lines'
- .format(n_lines - self._message_lines), dim=True)
- text += '\n'
-
- extra_nl = True
-
- if message.scheduler and message.message_type == MessageType.FAIL:
- text += '\n'
-
- if self.context is not None and not self.context.log_verbose:
- text += self._indent + self._err_profile.fmt("Log file: ")
- text += self._indent + self._logfile_widget.render(message) + '\n'
- elif self._log_lines > 0:
- text += self._indent + self._err_profile.fmt("Printing the last {} lines from log file:"
- .format(self._log_lines)) + '\n'
- text += self._indent + self._logfile_widget.render(message, abbrev=False) + '\n'
- text += self._indent + self._err_profile.fmt("=" * 70) + '\n'
-
- log_content = self._read_last_lines(message.logfile)
- log_content = textwrap.indent(log_content, self._indent)
- text += self._detail_profile.fmt(log_content)
- text += '\n'
- text += self._indent + self._err_profile.fmt("=" * 70) + '\n'
- extra_nl = True
-
- if extra_nl:
- text += '\n'
-
- return text
-
- def _read_last_lines(self, logfile):
- with ExitStack() as stack:
- # mmap handles low-level memory details, allowing for
- # faster searches
- f = stack.enter_context(open(logfile, 'r+'))
- log = stack.enter_context(mmap(f.fileno(), os.path.getsize(f.name)))
-
- count = 0
- end = log.size() - 1
-
- while count < self._log_lines and end >= 0:
- location = log.rfind(b'\n', 0, end)
- count += 1
-
- # If location is -1 (none found), this will print the
- # first character because of the later +1
- end = location
-
- # end+1 is correct whether or not a newline was found at
- # that location. If end is -1 (seek before beginning of file)
- # then we get the first characther. If end is a newline position,
- # we discard it and only want to print the beginning of the next
- # line.
- lines = log[(end + 1):].splitlines()
- return '\n'.join([line.decode('utf-8') for line in lines]).rstrip()
-
- def _format_plugins(self, element_plugins, source_plugins):
- text = ""
-
- if not (element_plugins or source_plugins):
- return text
-
- text += self.content_profile.fmt("Loaded Plugins\n", bold=True)
-
- if element_plugins:
- text += self.format_profile.fmt(" Element Plugins\n")
- for plugin in element_plugins:
- text += self.content_profile.fmt(" - {}\n".format(plugin))
-
- if source_plugins:
- text += self.format_profile.fmt(" Source Plugins\n")
- for plugin in source_plugins:
- text += self.content_profile.fmt(" - {}\n".format(plugin))
-
- text += '\n'
-
- return text
-
- # _format_values()
- #
- # Formats an indented dictionary of titles / values, ensuring
- # the values are aligned.
- #
- # Args:
- # values: A dictionary, usually an OrderedDict()
- # style_value: Whether to use the content profile for the values
- #
- # Returns:
- # (str): The formatted values
- #
- def _format_values(self, values, style_value=True):
- text = ''
- max_key_len = 0
- for key, value in values.items():
- max_key_len = max(len(key), max_key_len)
-
- for key, value in values.items():
- if isinstance(value, str) and '\n' in value:
- text += self.format_profile.fmt(" {}:\n".format(key))
- text += textwrap.indent(value, self._indent)
- continue
-
- text += self.format_profile.fmt(" {}: {}".format(key, ' ' * (max_key_len - len(key))))
- if style_value:
- text += self.content_profile.fmt(str(value))
- else:
- text += str(value)
- text += '\n'
-
- return text
diff --git a/buildstream/_fuse/__init__.py b/buildstream/_fuse/__init__.py
deleted file mode 100644
index a5e882634..000000000
--- a/buildstream/_fuse/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .hardlinks import SafeHardlinks
diff --git a/buildstream/_fuse/fuse.py b/buildstream/_fuse/fuse.py
deleted file mode 100644
index 4ff6b9903..000000000
--- a/buildstream/_fuse/fuse.py
+++ /dev/null
@@ -1,1006 +0,0 @@
-# This is an embedded copy of fuse.py taken from the following upstream commit:
-#
-# https://github.com/terencehonles/fusepy/commit/0eafeb557e0e70926ed9450008ef17057d302391
-#
-# Our local modifications are recorded in the Git history of this repo.
-
-# Copyright (c) 2012 Terence Honles <terence@honles.com> (maintainer)
-# Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com> (author)
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-# pylint: skip-file
-
-from __future__ import print_function, absolute_import, division
-
-from ctypes import *
-from ctypes.util import find_library
-from errno import *
-from os import strerror
-from platform import machine, system
-from signal import signal, SIGINT, SIG_DFL
-from stat import S_IFDIR
-from traceback import print_exc
-
-import logging
-
-try:
- from functools import partial
-except ImportError:
- # http://docs.python.org/library/functools.html#functools.partial
- def partial(func, *args, **keywords):
- def newfunc(*fargs, **fkeywords):
- newkeywords = keywords.copy()
- newkeywords.update(fkeywords)
- return func(*(args + fargs), **newkeywords)
-
- newfunc.func = func
- newfunc.args = args
- newfunc.keywords = keywords
- return newfunc
-
-try:
- basestring
-except NameError:
- basestring = str
-
-class c_timespec(Structure):
- _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)]
-
-class c_utimbuf(Structure):
- _fields_ = [('actime', c_timespec), ('modtime', c_timespec)]
-
-class c_stat(Structure):
- pass # Platform dependent
-
-_system = system()
-_machine = machine()
-
-if _system == 'Darwin':
- _libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL) # libfuse dependency
- _libfuse_path = (find_library('fuse4x') or find_library('osxfuse') or
- find_library('fuse'))
-else:
- _libfuse_path = find_library('fuse')
-
-if not _libfuse_path:
- raise EnvironmentError('Unable to find libfuse')
-else:
- _libfuse = CDLL(_libfuse_path)
-
-if _system == 'Darwin' and hasattr(_libfuse, 'macfuse_version'):
- _system = 'Darwin-MacFuse'
-
-
-if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'):
- ENOTSUP = 45
- c_dev_t = c_int32
- c_fsblkcnt_t = c_ulong
- c_fsfilcnt_t = c_ulong
- c_gid_t = c_uint32
- c_mode_t = c_uint16
- c_off_t = c_int64
- c_pid_t = c_int32
- c_uid_t = c_uint32
- setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
- c_size_t, c_int, c_uint32)
- getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
- c_size_t, c_uint32)
- if _system == 'Darwin':
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('st_mode', c_mode_t),
- ('st_nlink', c_uint16),
- ('st_ino', c_uint64),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('st_rdev', c_dev_t),
- ('st_atimespec', c_timespec),
- ('st_mtimespec', c_timespec),
- ('st_ctimespec', c_timespec),
- ('st_birthtimespec', c_timespec),
- ('st_size', c_off_t),
- ('st_blocks', c_int64),
- ('st_blksize', c_int32),
- ('st_flags', c_int32),
- ('st_gen', c_int32),
- ('st_lspare', c_int32),
- ('st_qspare', c_int64)]
- else:
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('st_ino', c_uint32),
- ('st_mode', c_mode_t),
- ('st_nlink', c_uint16),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('st_rdev', c_dev_t),
- ('st_atimespec', c_timespec),
- ('st_mtimespec', c_timespec),
- ('st_ctimespec', c_timespec),
- ('st_size', c_off_t),
- ('st_blocks', c_int64),
- ('st_blksize', c_int32)]
-elif _system == 'Linux':
- ENOTSUP = 95
- c_dev_t = c_ulonglong
- c_fsblkcnt_t = c_ulonglong
- c_fsfilcnt_t = c_ulonglong
- c_gid_t = c_uint
- c_mode_t = c_uint
- c_off_t = c_longlong
- c_pid_t = c_int
- c_uid_t = c_uint
- setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
- c_size_t, c_int)
-
- getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
- c_size_t)
-
- if _machine == 'x86_64':
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('st_ino', c_ulong),
- ('st_nlink', c_ulong),
- ('st_mode', c_mode_t),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('__pad0', c_int),
- ('st_rdev', c_dev_t),
- ('st_size', c_off_t),
- ('st_blksize', c_long),
- ('st_blocks', c_long),
- ('st_atimespec', c_timespec),
- ('st_mtimespec', c_timespec),
- ('st_ctimespec', c_timespec)]
- elif _machine == 'mips':
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('__pad1_1', c_ulong),
- ('__pad1_2', c_ulong),
- ('__pad1_3', c_ulong),
- ('st_ino', c_ulong),
- ('st_mode', c_mode_t),
- ('st_nlink', c_ulong),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('st_rdev', c_dev_t),
- ('__pad2_1', c_ulong),
- ('__pad2_2', c_ulong),
- ('st_size', c_off_t),
- ('__pad3', c_ulong),
- ('st_atimespec', c_timespec),
- ('__pad4', c_ulong),
- ('st_mtimespec', c_timespec),
- ('__pad5', c_ulong),
- ('st_ctimespec', c_timespec),
- ('__pad6', c_ulong),
- ('st_blksize', c_long),
- ('st_blocks', c_long),
- ('__pad7_1', c_ulong),
- ('__pad7_2', c_ulong),
- ('__pad7_3', c_ulong),
- ('__pad7_4', c_ulong),
- ('__pad7_5', c_ulong),
- ('__pad7_6', c_ulong),
- ('__pad7_7', c_ulong),
- ('__pad7_8', c_ulong),
- ('__pad7_9', c_ulong),
- ('__pad7_10', c_ulong),
- ('__pad7_11', c_ulong),
- ('__pad7_12', c_ulong),
- ('__pad7_13', c_ulong),
- ('__pad7_14', c_ulong)]
- elif _machine == 'ppc':
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('st_ino', c_ulonglong),
- ('st_mode', c_mode_t),
- ('st_nlink', c_uint),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('st_rdev', c_dev_t),
- ('__pad2', c_ushort),
- ('st_size', c_off_t),
- ('st_blksize', c_long),
- ('st_blocks', c_longlong),
- ('st_atimespec', c_timespec),
- ('st_mtimespec', c_timespec),
- ('st_ctimespec', c_timespec)]
- elif _machine == 'ppc64' or _machine == 'ppc64le':
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('st_ino', c_ulong),
- ('st_nlink', c_ulong),
- ('st_mode', c_mode_t),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('__pad', c_uint),
- ('st_rdev', c_dev_t),
- ('st_size', c_off_t),
- ('st_blksize', c_long),
- ('st_blocks', c_long),
- ('st_atimespec', c_timespec),
- ('st_mtimespec', c_timespec),
- ('st_ctimespec', c_timespec)]
- elif _machine == 'aarch64':
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('st_ino', c_ulong),
- ('st_mode', c_mode_t),
- ('st_nlink', c_uint),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('st_rdev', c_dev_t),
- ('__pad1', c_ulong),
- ('st_size', c_off_t),
- ('st_blksize', c_int),
- ('__pad2', c_int),
- ('st_blocks', c_long),
- ('st_atimespec', c_timespec),
- ('st_mtimespec', c_timespec),
- ('st_ctimespec', c_timespec)]
- else:
- # i686, use as fallback for everything else
- c_stat._fields_ = [
- ('st_dev', c_dev_t),
- ('__pad1', c_ushort),
- ('__st_ino', c_ulong),
- ('st_mode', c_mode_t),
- ('st_nlink', c_uint),
- ('st_uid', c_uid_t),
- ('st_gid', c_gid_t),
- ('st_rdev', c_dev_t),
- ('__pad2', c_ushort),
- ('st_size', c_off_t),
- ('st_blksize', c_long),
- ('st_blocks', c_longlong),
- ('st_atimespec', c_timespec),
- ('st_mtimespec', c_timespec),
- ('st_ctimespec', c_timespec),
- ('st_ino', c_ulonglong)]
-else:
- raise NotImplementedError('{} is not supported.'.format(_system))
-
-
-class c_statvfs(Structure):
- _fields_ = [
- ('f_bsize', c_ulong),
- ('f_frsize', c_ulong),
- ('f_blocks', c_fsblkcnt_t),
- ('f_bfree', c_fsblkcnt_t),
- ('f_bavail', c_fsblkcnt_t),
- ('f_files', c_fsfilcnt_t),
- ('f_ffree', c_fsfilcnt_t),
- ('f_favail', c_fsfilcnt_t),
- ('f_fsid', c_ulong),
- #('unused', c_int),
- ('f_flag', c_ulong),
- ('f_namemax', c_ulong)]
-
-if _system == 'FreeBSD':
- c_fsblkcnt_t = c_uint64
- c_fsfilcnt_t = c_uint64
- setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
- c_size_t, c_int)
-
- getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
- c_size_t)
-
- class c_statvfs(Structure):
- _fields_ = [
- ('f_bavail', c_fsblkcnt_t),
- ('f_bfree', c_fsblkcnt_t),
- ('f_blocks', c_fsblkcnt_t),
- ('f_favail', c_fsfilcnt_t),
- ('f_ffree', c_fsfilcnt_t),
- ('f_files', c_fsfilcnt_t),
- ('f_bsize', c_ulong),
- ('f_flag', c_ulong),
- ('f_frsize', c_ulong)]
-
-class fuse_file_info(Structure):
- _fields_ = [
- ('flags', c_int),
- ('fh_old', c_ulong),
- ('writepage', c_int),
- ('direct_io', c_uint, 1),
- ('keep_cache', c_uint, 1),
- ('flush', c_uint, 1),
- ('padding', c_uint, 29),
- ('fh', c_uint64),
- ('lock_owner', c_uint64)]
-
-class fuse_context(Structure):
- _fields_ = [
- ('fuse', c_voidp),
- ('uid', c_uid_t),
- ('gid', c_gid_t),
- ('pid', c_pid_t),
- ('private_data', c_voidp)]
-
-_libfuse.fuse_get_context.restype = POINTER(fuse_context)
-
-
-class fuse_operations(Structure):
- _fields_ = [
- ('getattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat))),
- ('readlink', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
- ('getdir', c_voidp), # Deprecated, use readdir
- ('mknod', CFUNCTYPE(c_int, c_char_p, c_mode_t, c_dev_t)),
- ('mkdir', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
- ('unlink', CFUNCTYPE(c_int, c_char_p)),
- ('rmdir', CFUNCTYPE(c_int, c_char_p)),
- ('symlink', CFUNCTYPE(c_int, c_char_p, c_char_p)),
- ('rename', CFUNCTYPE(c_int, c_char_p, c_char_p)),
- ('link', CFUNCTYPE(c_int, c_char_p, c_char_p)),
- ('chmod', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
- ('chown', CFUNCTYPE(c_int, c_char_p, c_uid_t, c_gid_t)),
- ('truncate', CFUNCTYPE(c_int, c_char_p, c_off_t)),
- ('utime', c_voidp), # Deprecated, use utimens
- ('open', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
-
- ('read', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t,
- c_off_t, POINTER(fuse_file_info))),
-
- ('write', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t,
- c_off_t, POINTER(fuse_file_info))),
-
- ('statfs', CFUNCTYPE(c_int, c_char_p, POINTER(c_statvfs))),
- ('flush', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
- ('release', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
- ('fsync', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))),
- ('setxattr', setxattr_t),
- ('getxattr', getxattr_t),
- ('listxattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
- ('removexattr', CFUNCTYPE(c_int, c_char_p, c_char_p)),
- ('opendir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
-
- ('readdir', CFUNCTYPE(c_int, c_char_p, c_voidp,
- CFUNCTYPE(c_int, c_voidp, c_char_p,
- POINTER(c_stat), c_off_t),
- c_off_t, POINTER(fuse_file_info))),
-
- ('releasedir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
-
- ('fsyncdir', CFUNCTYPE(c_int, c_char_p, c_int,
- POINTER(fuse_file_info))),
-
- ('init', CFUNCTYPE(c_voidp, c_voidp)),
- ('destroy', CFUNCTYPE(c_voidp, c_voidp)),
- ('access', CFUNCTYPE(c_int, c_char_p, c_int)),
-
- ('create', CFUNCTYPE(c_int, c_char_p, c_mode_t,
- POINTER(fuse_file_info))),
-
- ('ftruncate', CFUNCTYPE(c_int, c_char_p, c_off_t,
- POINTER(fuse_file_info))),
-
- ('fgetattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat),
- POINTER(fuse_file_info))),
-
- ('lock', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info),
- c_int, c_voidp)),
-
- ('utimens', CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))),
- ('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong))),
- ('flag_nullpath_ok', c_uint, 1),
- ('flag_nopath', c_uint, 1),
- ('flag_utime_omit_ok', c_uint, 1),
- ('flag_reserved', c_uint, 29),
- ]
-
-
-def time_of_timespec(ts):
- return ts.tv_sec + ts.tv_nsec / 10 ** 9
-
-def set_st_attrs(st, attrs):
- for key, val in attrs.items():
- if key in ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime'):
- timespec = getattr(st, key + 'spec', None)
- if timespec is None:
- continue
- timespec.tv_sec = int(val)
- timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9)
- elif hasattr(st, key):
- setattr(st, key, val)
-
-
-def fuse_get_context():
- 'Returns a (uid, gid, pid) tuple'
-
- ctxp = _libfuse.fuse_get_context()
- ctx = ctxp.contents
- return ctx.uid, ctx.gid, ctx.pid
-
-
-class FuseOSError(OSError):
- def __init__(self, errno):
- super(FuseOSError, self).__init__(errno, strerror(errno))
-
-
-class FUSE(object):
- '''
- This class is the lower level interface and should not be subclassed under
- normal use. Its methods are called by fuse.
-
- Assumes API version 2.6 or later.
- '''
-
- OPTIONS = (
- ('foreground', '-f'),
- ('debug', '-d'),
- ('nothreads', '-s'),
- )
-
- def __init__(self, operations, mountpoint, raw_fi=False, encoding='utf-8',
- **kwargs):
-
- '''
- Setting raw_fi to True will cause FUSE to pass the fuse_file_info
- class as is to Operations, instead of just the fh field.
-
- This gives you access to direct_io, keep_cache, etc.
- '''
-
- self.operations = operations
- self.raw_fi = raw_fi
- self.encoding = encoding
-
- args = ['fuse']
-
- args.extend(flag for arg, flag in self.OPTIONS
- if kwargs.pop(arg, False))
-
- kwargs.setdefault('fsname', operations.__class__.__name__)
- args.append('-o')
- args.append(','.join(self._normalize_fuse_options(**kwargs)))
- args.append(mountpoint)
-
- args = [arg.encode(encoding) for arg in args]
- argv = (c_char_p * len(args))(*args)
-
- fuse_ops = fuse_operations()
- for ent in fuse_operations._fields_:
- name, prototype = ent[:2]
-
- val = getattr(operations, name, None)
- if val is None:
- continue
-
- # Function pointer members are tested for using the
- # getattr(operations, name) above but are dynamically
- # invoked using self.operations(name)
- if hasattr(prototype, 'argtypes'):
- val = prototype(partial(self._wrapper, getattr(self, name)))
-
- setattr(fuse_ops, name, val)
-
- try:
- old_handler = signal(SIGINT, SIG_DFL)
- except ValueError:
- old_handler = SIG_DFL
-
- err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops),
- sizeof(fuse_ops), None)
-
- try:
- signal(SIGINT, old_handler)
- except ValueError:
- pass
-
- del self.operations # Invoke the destructor
- if err:
- raise RuntimeError(err)
-
- @staticmethod
- def _normalize_fuse_options(**kargs):
- for key, value in kargs.items():
- if isinstance(value, bool):
- if value is True: yield key
- else:
- yield '{}={}'.format(key, value)
-
- @staticmethod
- def _wrapper(func, *args, **kwargs):
- 'Decorator for the methods that follow'
-
- try:
- return func(*args, **kwargs) or 0
- except OSError as e:
- return -(e.errno or EFAULT)
- except:
- print_exc()
- return -EFAULT
-
- def _decode_optional_path(self, path):
- # NB: this method is intended for fuse operations that
- # allow the path argument to be NULL,
- # *not* as a generic path decoding method
- if path is None:
- return None
- return path.decode(self.encoding)
-
- def getattr(self, path, buf):
- return self.fgetattr(path, buf, None)
-
- def readlink(self, path, buf, bufsize):
- ret = self.operations('readlink', path.decode(self.encoding)) \
- .encode(self.encoding)
-
- # copies a string into the given buffer
- # (null terminated and truncated if necessary)
- data = create_string_buffer(ret[:bufsize - 1])
- memmove(buf, data, len(data))
- return 0
-
- def mknod(self, path, mode, dev):
- return self.operations('mknod', path.decode(self.encoding), mode, dev)
-
- def mkdir(self, path, mode):
- return self.operations('mkdir', path.decode(self.encoding), mode)
-
- def unlink(self, path):
- return self.operations('unlink', path.decode(self.encoding))
-
- def rmdir(self, path):
- return self.operations('rmdir', path.decode(self.encoding))
-
- def symlink(self, source, target):
- 'creates a symlink `target -> source` (e.g. ln -s source target)'
-
- return self.operations('symlink', target.decode(self.encoding),
- source.decode(self.encoding))
-
- def rename(self, old, new):
- return self.operations('rename', old.decode(self.encoding),
- new.decode(self.encoding))
-
- def link(self, source, target):
- 'creates a hard link `target -> source` (e.g. ln source target)'
-
- return self.operations('link', target.decode(self.encoding),
- source.decode(self.encoding))
-
- def chmod(self, path, mode):
- return self.operations('chmod', path.decode(self.encoding), mode)
-
- def chown(self, path, uid, gid):
- # Check if any of the arguments is a -1 that has overflowed
- if c_uid_t(uid + 1).value == 0:
- uid = -1
- if c_gid_t(gid + 1).value == 0:
- gid = -1
-
- return self.operations('chown', path.decode(self.encoding), uid, gid)
-
- def truncate(self, path, length):
- return self.operations('truncate', path.decode(self.encoding), length)
-
- def open(self, path, fip):
- fi = fip.contents
- if self.raw_fi:
- return self.operations('open', path.decode(self.encoding), fi)
- else:
- fi.fh = self.operations('open', path.decode(self.encoding),
- fi.flags)
-
- return 0
-
- def read(self, path, buf, size, offset, fip):
- if self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- ret = self.operations('read', self._decode_optional_path(path), size,
- offset, fh)
-
- if not ret: return 0
-
- retsize = len(ret)
- assert retsize <= size, \
- 'actual amount read {:d} greater than expected {:d}'.format(retsize, size)
-
- data = create_string_buffer(ret, retsize)
- memmove(buf, data, retsize)
- return retsize
-
- def write(self, path, buf, size, offset, fip):
- data = string_at(buf, size)
-
- if self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- return self.operations('write', self._decode_optional_path(path), data,
- offset, fh)
-
- def statfs(self, path, buf):
- stv = buf.contents
- attrs = self.operations('statfs', path.decode(self.encoding))
- for key, val in attrs.items():
- if hasattr(stv, key):
- setattr(stv, key, val)
-
- return 0
-
- def flush(self, path, fip):
- if self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- return self.operations('flush', self._decode_optional_path(path), fh)
-
- def release(self, path, fip):
- if self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- return self.operations('release', self._decode_optional_path(path), fh)
-
- def fsync(self, path, datasync, fip):
- if self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- return self.operations('fsync', self._decode_optional_path(path), datasync,
- fh)
-
- def setxattr(self, path, name, value, size, options, *args):
- return self.operations('setxattr', path.decode(self.encoding),
- name.decode(self.encoding),
- string_at(value, size), options, *args)
-
- def getxattr(self, path, name, value, size, *args):
- ret = self.operations('getxattr', path.decode(self.encoding),
- name.decode(self.encoding), *args)
-
- retsize = len(ret)
- # allow size queries
- if not value: return retsize
-
- # do not truncate
- if retsize > size: return -ERANGE
-
- buf = create_string_buffer(ret, retsize) # Does not add trailing 0
- memmove(value, buf, retsize)
-
- return retsize
-
- def listxattr(self, path, namebuf, size):
- attrs = self.operations('listxattr', path.decode(self.encoding)) or ''
- ret = '\x00'.join(attrs).encode(self.encoding)
- if len(ret) > 0:
- ret += '\x00'.encode(self.encoding)
-
- retsize = len(ret)
- # allow size queries
- if not namebuf: return retsize
-
- # do not truncate
- if retsize > size: return -ERANGE
-
- buf = create_string_buffer(ret, retsize)
- memmove(namebuf, buf, retsize)
-
- return retsize
-
- def removexattr(self, path, name):
- return self.operations('removexattr', path.decode(self.encoding),
- name.decode(self.encoding))
-
- def opendir(self, path, fip):
- # Ignore raw_fi
- fip.contents.fh = self.operations('opendir',
- path.decode(self.encoding))
-
- return 0
-
- def readdir(self, path, buf, filler, offset, fip):
- # Ignore raw_fi
- for item in self.operations('readdir', self._decode_optional_path(path),
- fip.contents.fh):
-
- if isinstance(item, basestring):
- name, st, offset = item, None, 0
- else:
- name, attrs, offset = item
- if attrs:
- st = c_stat()
- set_st_attrs(st, attrs)
- else:
- st = None
-
- if filler(buf, name.encode(self.encoding), st, offset) != 0:
- break
-
- return 0
-
- def releasedir(self, path, fip):
- # Ignore raw_fi
- return self.operations('releasedir', self._decode_optional_path(path),
- fip.contents.fh)
-
- def fsyncdir(self, path, datasync, fip):
- # Ignore raw_fi
- return self.operations('fsyncdir', self._decode_optional_path(path),
- datasync, fip.contents.fh)
-
- def init(self, conn):
- return self.operations('init', '/')
-
- def destroy(self, private_data):
- return self.operations('destroy', '/')
-
- def access(self, path, amode):
- return self.operations('access', path.decode(self.encoding), amode)
-
- def create(self, path, mode, fip):
- fi = fip.contents
- path = path.decode(self.encoding)
-
- if self.raw_fi:
- return self.operations('create', path, mode, fi)
- else:
- # This line is different from upstream to fix issues
- # reading file opened with O_CREAT|O_RDWR.
- # See issue #143.
- fi.fh = self.operations('create', path, mode, fi.flags)
- # END OF MODIFICATION
- return 0
-
- def ftruncate(self, path, length, fip):
- if self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- return self.operations('truncate', self._decode_optional_path(path),
- length, fh)
-
- def fgetattr(self, path, buf, fip):
- memset(buf, 0, sizeof(c_stat))
-
- st = buf.contents
- if not fip:
- fh = fip
- elif self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- attrs = self.operations('getattr', self._decode_optional_path(path), fh)
- set_st_attrs(st, attrs)
- return 0
-
- def lock(self, path, fip, cmd, lock):
- if self.raw_fi:
- fh = fip.contents
- else:
- fh = fip.contents.fh
-
- return self.operations('lock', self._decode_optional_path(path), fh, cmd,
- lock)
-
- def utimens(self, path, buf):
- if buf:
- atime = time_of_timespec(buf.contents.actime)
- mtime = time_of_timespec(buf.contents.modtime)
- times = (atime, mtime)
- else:
- times = None
-
- return self.operations('utimens', path.decode(self.encoding), times)
-
- def bmap(self, path, blocksize, idx):
- return self.operations('bmap', path.decode(self.encoding), blocksize,
- idx)
-
-
-class Operations(object):
- '''
- This class should be subclassed and passed as an argument to FUSE on
- initialization. All operations should raise a FuseOSError exception on
- error.
-
- When in doubt of what an operation should do, check the FUSE header file
- or the corresponding system call man page.
- '''
-
- def __call__(self, op, *args):
- if not hasattr(self, op):
- raise FuseOSError(EFAULT)
- return getattr(self, op)(*args)
-
- def access(self, path, amode):
- return 0
-
- bmap = None
-
- def chmod(self, path, mode):
- raise FuseOSError(EROFS)
-
- def chown(self, path, uid, gid):
- raise FuseOSError(EROFS)
-
- def create(self, path, mode, fi=None):
- '''
- When raw_fi is False (default case), fi is None and create should
- return a numerical file handle.
-
- When raw_fi is True the file handle should be set directly by create
- and return 0.
- '''
-
- raise FuseOSError(EROFS)
-
- def destroy(self, path):
- 'Called on filesystem destruction. Path is always /'
-
- pass
-
- def flush(self, path, fh):
- return 0
-
- def fsync(self, path, datasync, fh):
- return 0
-
- def fsyncdir(self, path, datasync, fh):
- return 0
-
- def getattr(self, path, fh=None):
- '''
- Returns a dictionary with keys identical to the stat C structure of
- stat(2).
-
- st_atime, st_mtime and st_ctime should be floats.
-
- NOTE: There is an incombatibility between Linux and Mac OS X
- concerning st_nlink of directories. Mac OS X counts all files inside
- the directory, while Linux counts only the subdirectories.
- '''
-
- if path != '/':
- raise FuseOSError(ENOENT)
- return dict(st_mode=(S_IFDIR | 0o755), st_nlink=2)
-
- def getxattr(self, path, name, position=0):
- raise FuseOSError(ENOTSUP)
-
- def init(self, path):
- '''
- Called on filesystem initialization. (Path is always /)
-
- Use it instead of __init__ if you start threads on initialization.
- '''
-
- pass
-
- def link(self, target, source):
- 'creates a hard link `target -> source` (e.g. ln source target)'
-
- raise FuseOSError(EROFS)
-
- def listxattr(self, path):
- return []
-
- lock = None
-
- def mkdir(self, path, mode):
- raise FuseOSError(EROFS)
-
- def mknod(self, path, mode, dev):
- raise FuseOSError(EROFS)
-
- def open(self, path, flags):
- '''
- When raw_fi is False (default case), open should return a numerical
- file handle.
-
- When raw_fi is True the signature of open becomes:
- open(self, path, fi)
-
- and the file handle should be set directly.
- '''
-
- return 0
-
- def opendir(self, path):
- 'Returns a numerical file handle.'
-
- return 0
-
- def read(self, path, size, offset, fh):
- 'Returns a string containing the data requested.'
-
- raise FuseOSError(EIO)
-
- def readdir(self, path, fh):
- '''
- Can return either a list of names, or a list of (name, attrs, offset)
- tuples. attrs is a dict as in getattr.
- '''
-
- return ['.', '..']
-
- def readlink(self, path):
- raise FuseOSError(ENOENT)
-
- def release(self, path, fh):
- return 0
-
- def releasedir(self, path, fh):
- return 0
-
- def removexattr(self, path, name):
- raise FuseOSError(ENOTSUP)
-
- def rename(self, old, new):
- raise FuseOSError(EROFS)
-
- def rmdir(self, path):
- raise FuseOSError(EROFS)
-
- def setxattr(self, path, name, value, options, position=0):
- raise FuseOSError(ENOTSUP)
-
- def statfs(self, path):
- '''
- Returns a dictionary with keys identical to the statvfs C structure of
- statvfs(3).
-
- On Mac OS X f_bsize and f_frsize must be a power of 2
- (minimum 512).
- '''
-
- return {}
-
- def symlink(self, target, source):
- 'creates a symlink `target -> source` (e.g. ln -s source target)'
-
- raise FuseOSError(EROFS)
-
- def truncate(self, path, length, fh=None):
- raise FuseOSError(EROFS)
-
- def unlink(self, path):
- raise FuseOSError(EROFS)
-
- def utimens(self, path, times=None):
- 'Times is a (atime, mtime) tuple. If None use current time.'
-
- return 0
-
- def write(self, path, data, offset, fh):
- raise FuseOSError(EROFS)
-
-
-class LoggingMixIn:
- log = logging.getLogger('fuse.log-mixin')
-
- def __call__(self, op, path, *args):
- self.log.debug('-> %s %s %s', op, path, repr(args))
- ret = '[Unhandled Exception]'
- try:
- ret = getattr(self, op)(path, *args)
- return ret
- except OSError as e:
- ret = str(e)
- raise
- finally:
- self.log.debug('<- %s %s', op, repr(ret))
diff --git a/buildstream/_fuse/hardlinks.py b/buildstream/_fuse/hardlinks.py
deleted file mode 100644
index ff2e81eea..000000000
--- a/buildstream/_fuse/hardlinks.py
+++ /dev/null
@@ -1,218 +0,0 @@
-#
-# Copyright (C) 2016 Stavros Korokithakis
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-#
-# The filesystem operations implementation here is based
-# on some example code written by Stavros Korokithakis.
-
-import errno
-import os
-import shutil
-import stat
-import tempfile
-
-from .fuse import FuseOSError, Operations
-
-from .mount import Mount
-
-
-# SafeHardlinks()
-#
-# A FUSE mount which implements a copy on write hardlink experience.
-#
-# Args:
-# root (str): The underlying filesystem path to mirror
-# tmp (str): A directory on the same filesystem for creating temp files
-#
-class SafeHardlinks(Mount):
-
- def __init__(self, directory, tempdir, fuse_mount_options=None):
- self.directory = directory
- self.tempdir = tempdir
- if fuse_mount_options is None:
- fuse_mount_options = {}
- super().__init__(fuse_mount_options=fuse_mount_options)
-
- def create_operations(self):
- return SafeHardlinkOps(self.directory, self.tempdir)
-
-
-# SafeHardlinkOps()
-#
-# The actual FUSE Operations implementation below.
-#
-class SafeHardlinkOps(Operations):
-
- def __init__(self, root, tmp):
- self.root = root
- self.tmp = tmp
-
- def _full_path(self, partial):
- if partial.startswith("/"):
- partial = partial[1:]
- path = os.path.join(self.root, partial)
- return path
-
- def _ensure_copy(self, full_path):
- try:
- # Follow symbolic links manually here
- real_path = os.path.realpath(full_path)
- file_stat = os.stat(real_path)
-
- # Dont bother with files that cannot be hardlinked, oddly it
- # directories actually usually have st_nlink > 1 so just avoid
- # that.
- #
- # We already wont get symlinks here, and stat will throw
- # the FileNotFoundError below if a followed symlink did not exist.
- #
- if not stat.S_ISDIR(file_stat.st_mode) and file_stat.st_nlink > 1:
- with tempfile.TemporaryDirectory(dir=self.tmp) as tempdir:
- basename = os.path.basename(real_path)
- temp_path = os.path.join(tempdir, basename)
-
- # First copy, then unlink origin and rename
- shutil.copy2(real_path, temp_path)
- os.unlink(real_path)
- os.rename(temp_path, real_path)
-
- except FileNotFoundError:
- # This doesnt exist yet, assume we're about to create it
- # so it's not a problem.
- pass
-
- ###########################################################
- # Fuse Methods #
- ###########################################################
- def access(self, path, mode):
- full_path = self._full_path(path)
- if not os.access(full_path, mode):
- raise FuseOSError(errno.EACCES)
-
- def chmod(self, path, mode):
- full_path = self._full_path(path)
-
- # Ensure copies on chmod
- self._ensure_copy(full_path)
- return os.chmod(full_path, mode)
-
- def chown(self, path, uid, gid):
- full_path = self._full_path(path)
-
- # Ensure copies on chown
- self._ensure_copy(full_path)
- return os.chown(full_path, uid, gid)
-
- def getattr(self, path, fh=None):
- full_path = self._full_path(path)
- st = os.lstat(full_path)
- return dict((key, getattr(st, key)) for key in (
- 'st_atime', 'st_ctime', 'st_gid', 'st_mode',
- 'st_mtime', 'st_nlink', 'st_size', 'st_uid', 'st_rdev'))
-
- def readdir(self, path, fh):
- full_path = self._full_path(path)
-
- dirents = ['.', '..']
- if os.path.isdir(full_path):
- dirents.extend(os.listdir(full_path))
- for r in dirents:
- yield r
-
- def readlink(self, path):
- pathname = os.readlink(self._full_path(path))
- if pathname.startswith("/"):
- # Path name is absolute, sanitize it.
- return os.path.relpath(pathname, self.root)
- else:
- return pathname
-
- def mknod(self, path, mode, dev):
- return os.mknod(self._full_path(path), mode, dev)
-
- def rmdir(self, path):
- full_path = self._full_path(path)
- return os.rmdir(full_path)
-
- def mkdir(self, path, mode):
- return os.mkdir(self._full_path(path), mode)
-
- def statfs(self, path):
- full_path = self._full_path(path)
- stv = os.statvfs(full_path)
- return dict((key, getattr(stv, key)) for key in (
- 'f_bavail', 'f_bfree', 'f_blocks', 'f_bsize', 'f_favail',
- 'f_ffree', 'f_files', 'f_flag', 'f_frsize', 'f_namemax'))
-
- def unlink(self, path):
- return os.unlink(self._full_path(path))
-
- def symlink(self, name, target):
- return os.symlink(target, self._full_path(name))
-
- def rename(self, old, new):
- return os.rename(self._full_path(old), self._full_path(new))
-
- def link(self, target, name):
-
- # When creating a hard link here, should we ensure the original
- # file is not a hardlink itself first ?
- #
- return os.link(self._full_path(name), self._full_path(target))
-
- def utimens(self, path, times=None):
- return os.utime(self._full_path(path), times)
-
- def open(self, path, flags):
- full_path = self._full_path(path)
-
- # If we're opening for writing, ensure it's a copy first
- if flags & os.O_WRONLY or flags & os.O_RDWR:
- self._ensure_copy(full_path)
-
- return os.open(full_path, flags)
-
- def create(self, path, mode, flags):
- full_path = self._full_path(path)
-
- # If it already exists, ensure it's a copy first
- self._ensure_copy(full_path)
- return os.open(full_path, flags, mode)
-
- def read(self, path, length, offset, fh):
- os.lseek(fh, offset, os.SEEK_SET)
- return os.read(fh, length)
-
- def write(self, path, buf, offset, fh):
- os.lseek(fh, offset, os.SEEK_SET)
- return os.write(fh, buf)
-
- def truncate(self, path, length, fh=None):
- full_path = self._full_path(path)
- with open(full_path, 'r+') as f:
- f.truncate(length)
-
- def flush(self, path, fh):
- return os.fsync(fh)
-
- def release(self, path, fh):
- return os.close(fh)
-
- def fsync(self, path, fdatasync, fh):
- return self.flush(path, fh)
diff --git a/buildstream/_fuse/mount.py b/buildstream/_fuse/mount.py
deleted file mode 100644
index e31684100..000000000
--- a/buildstream/_fuse/mount.py
+++ /dev/null
@@ -1,196 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-import os
-import signal
-import time
-import sys
-
-from contextlib import contextmanager
-from multiprocessing import Process
-from .fuse import FUSE
-
-from .._exceptions import ImplError
-from .. import _signals
-
-
-# Just a custom exception to raise here, for identifying possible
-# bugs with a fuse layer implementation
-#
-class FuseMountError(Exception):
- pass
-
-
-# This is a convenience class which takes care of synchronizing the
-# startup of FUSE and shutting it down.
-#
-# The implementations / subclasses should:
-#
-# - Overload the instance initializer to add any parameters
-# needed for their fuse Operations implementation
-#
-# - Implement create_operations() to create the Operations
-# instance on behalf of the superclass, using any additional
-# parameters collected in the initializer.
-#
-# Mount objects can be treated as contextmanagers, the volume
-# will be mounted during the context.
-#
-# UGLY CODE NOTE:
-#
-# This is a horrible little piece of code. The problem we face
-# here is that the highlevel libfuse API has fuse_main(), which
-# will either block in the foreground, or become a full daemon.
-#
-# With the daemon approach, we know that the fuse is mounted right
-# away when fuse_main() returns, then the daemon will go and handle
-# requests on its own, but then we have no way to shut down the
-# daemon.
-#
-# With the blocking approach, we still have it as a child process
-# so we can tell it to gracefully terminate; but it's impossible
-# to know when the mount is done, there is no callback for that
-#
-# The solution we use here without digging too deep into the
-# low level fuse API, is to fork a child process which will
-# fun the fuse loop in foreground, and we block the parent
-# process until the volume is mounted with a busy loop with timeouts.
-#
-class Mount():
-
- # These are not really class data, they are
- # just here for the sake of having None setup instead
- # of missing attributes, since we do not provide any
- # initializer and leave the initializer to the subclass.
- #
- __mountpoint = None
- __operations = None
- __process = None
-
- ################################################
- # User Facing API #
- ################################################
-
- def __init__(self, fuse_mount_options=None):
- self._fuse_mount_options = {} if fuse_mount_options is None else fuse_mount_options
-
- # mount():
- #
- # User facing API for mounting a fuse subclass implementation
- #
- # Args:
- # (str): Location to mount this fuse fs
- #
- def mount(self, mountpoint):
-
- assert self.__process is None
-
- self.__mountpoint = mountpoint
- self.__process = Process(target=self.__run_fuse)
-
- # Ensure the child fork() does not inherit our signal handlers, if the
- # child wants to handle a signal then it will first set its own
- # handler, and then unblock it.
- with _signals.blocked([signal.SIGTERM, signal.SIGTSTP, signal.SIGINT], ignore=False):
- self.__process.start()
-
- # This is horrible, we're going to wait until mountpoint is mounted and that's it.
- while not os.path.ismount(mountpoint):
- time.sleep(1 / 100)
-
- # unmount():
- #
- # User facing API for unmounting a fuse subclass implementation
- #
- def unmount(self):
-
- # Terminate child process and join
- if self.__process is not None:
- self.__process.terminate()
- self.__process.join()
-
- # Report an error if ever the underlying operations crashed for some reason.
- if self.__process.exitcode != 0:
- raise FuseMountError("{} reported exit code {} when unmounting"
- .format(type(self).__name__, self.__process.exitcode))
-
- self.__mountpoint = None
- self.__process = None
-
- # mounted():
- #
- # A context manager to run a code block with this fuse Mount
- # mounted, this will take care of automatically unmounting
- # in the case that the calling process is terminated.
- #
- # Args:
- # (str): Location to mount this fuse fs
- #
- @contextmanager
- def mounted(self, mountpoint):
-
- self.mount(mountpoint)
- try:
- with _signals.terminator(self.unmount):
- yield
- finally:
- self.unmount()
-
- ################################################
- # Abstract Methods #
- ################################################
-
- # create_operations():
- #
- # Create an Operations class (from fusepy) and return it
- #
- # Returns:
- # (Operations): A FUSE Operations implementation
- def create_operations(self):
- raise ImplError("Mount subclass '{}' did not implement create_operations()"
- .format(type(self).__name__))
-
- ################################################
- # Child Process #
- ################################################
- def __run_fuse(self):
-
- # First become session leader while signals are still blocked
- #
- # Then reset the SIGTERM handler to the default and finally
- # unblock SIGTERM.
- #
- os.setsid()
- signal.signal(signal.SIGTERM, signal.SIG_DFL)
- signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGTERM])
-
- # Ask the subclass to give us an Operations object
- #
- self.__operations = self.create_operations() # pylint: disable=assignment-from-no-return
-
- # Run fuse in foreground in this child process, internally libfuse
- # will handle SIGTERM and gracefully exit its own little main loop.
- #
- FUSE(self.__operations, self.__mountpoint, nothreads=True, foreground=True, nonempty=True,
- **self._fuse_mount_options)
-
- # Explicit 0 exit code, if the operations crashed for some reason, the exit
- # code will not be 0, and we want to know about it.
- #
- sys.exit(0)
diff --git a/buildstream/_gitsourcebase.py b/buildstream/_gitsourcebase.py
deleted file mode 100644
index 7d07c56cb..000000000
--- a/buildstream/_gitsourcebase.py
+++ /dev/null
@@ -1,683 +0,0 @@
-#
-# 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>
-# Chandan Singh <csingh43@bloomberg.net>
-
-"""Abstract base class for source implementations that work with a Git repository"""
-
-import os
-import re
-import shutil
-from collections.abc import Mapping
-from io import StringIO
-from tempfile import TemporaryFile
-
-from configparser import RawConfigParser
-
-from .source import Source, SourceError, SourceFetcher
-from .types import Consistency, CoreWarnings
-from . import utils
-from .utils import move_atomic, DirectoryExistsError
-
-GIT_MODULES = '.gitmodules'
-
-# Warnings
-WARN_INCONSISTENT_SUBMODULE = "inconsistent-submodule"
-WARN_UNLISTED_SUBMODULE = "unlisted-submodule"
-WARN_INVALID_SUBMODULE = "invalid-submodule"
-
-
-# Because of handling of submodules, we maintain a _GitMirror
-# for the primary git source and also for each submodule it
-# might have at a given time
-#
-class _GitMirror(SourceFetcher):
-
- def __init__(self, source, path, url, ref, *, primary=False, tags=[]):
-
- super().__init__()
- self.source = source
- self.path = path
- self.url = url
- self.ref = ref
- self.tags = tags
- self.primary = primary
- self.mirror = os.path.join(source.get_mirror_directory(), utils.url_directory_name(url))
- self.mark_download_url(url)
-
- # Ensures that the mirror exists
- def ensure(self, alias_override=None):
-
- # Unfortunately, git does not know how to only clone just a specific ref,
- # so we have to download all of those gigs even if we only need a couple
- # of bytes.
- if not os.path.exists(self.mirror):
-
- # Do the initial clone in a tmpdir just because we want an atomic move
- # after a long standing clone which could fail overtime, for now do
- # this directly in our git directory, eliminating the chances that the
- # system configured tmpdir is not on the same partition.
- #
- with self.source.tempdir() as tmpdir:
- url = self.source.translate_url(self.url, alias_override=alias_override,
- primary=self.primary)
- self.source.call([self.source.host_git, 'clone', '--mirror', '-n', url, tmpdir],
- fail="Failed to clone git repository {}".format(url),
- fail_temporarily=True)
-
- try:
- move_atomic(tmpdir, self.mirror)
- except DirectoryExistsError:
- # Another process was quicker to download this repository.
- # Let's discard our own
- self.source.status("{}: Discarding duplicate clone of {}"
- .format(self.source, url))
- except OSError as e:
- raise SourceError("{}: Failed to move cloned git repository {} from '{}' to '{}': {}"
- .format(self.source, url, tmpdir, self.mirror, e)) from e
-
- def _fetch(self, alias_override=None):
- url = self.source.translate_url(self.url,
- alias_override=alias_override,
- primary=self.primary)
-
- if alias_override:
- remote_name = utils.url_directory_name(alias_override)
- _, remotes = self.source.check_output(
- [self.source.host_git, 'remote'],
- fail="Failed to retrieve list of remotes in {}".format(self.mirror),
- cwd=self.mirror
- )
- if remote_name not in remotes:
- self.source.call(
- [self.source.host_git, 'remote', 'add', remote_name, url],
- fail="Failed to add remote {} with url {}".format(remote_name, url),
- cwd=self.mirror
- )
- else:
- remote_name = "origin"
-
- self.source.call([self.source.host_git, 'fetch', remote_name, '--prune',
- '+refs/heads/*:refs/heads/*', '+refs/tags/*:refs/tags/*'],
- fail="Failed to fetch from remote git repository: {}".format(url),
- fail_temporarily=True,
- cwd=self.mirror)
-
- def fetch(self, alias_override=None):
- # Resolve the URL for the message
- resolved_url = self.source.translate_url(self.url,
- alias_override=alias_override,
- primary=self.primary)
-
- with self.source.timed_activity("Fetching from {}"
- .format(resolved_url),
- silent_nested=True):
- self.ensure(alias_override)
- if not self.has_ref():
- self._fetch(alias_override)
- self.assert_ref()
-
- def has_ref(self):
- if not self.ref:
- return False
-
- # If the mirror doesnt exist, we also dont have the ref
- if not os.path.exists(self.mirror):
- return False
-
- # Check if the ref is really there
- rc = self.source.call([self.source.host_git, 'cat-file', '-t', self.ref], cwd=self.mirror)
- return rc == 0
-
- def assert_ref(self):
- if not self.has_ref():
- raise SourceError("{}: expected ref '{}' was not found in git repository: '{}'"
- .format(self.source, self.ref, self.url))
-
- def latest_commit_with_tags(self, tracking, track_tags=False):
- _, output = self.source.check_output(
- [self.source.host_git, 'rev-parse', tracking],
- fail="Unable to find commit for specified branch name '{}'".format(tracking),
- cwd=self.mirror)
- ref = output.rstrip('\n')
-
- if self.source.ref_format == 'git-describe':
- # Prefix the ref with the closest tag, if available,
- # to make the ref human readable
- exit_code, output = self.source.check_output(
- [self.source.host_git, 'describe', '--tags', '--abbrev=40', '--long', ref],
- cwd=self.mirror)
- if exit_code == 0:
- ref = output.rstrip('\n')
-
- if not track_tags:
- return ref, []
-
- tags = set()
- for options in [[], ['--first-parent'], ['--tags'], ['--tags', '--first-parent']]:
- exit_code, output = self.source.check_output(
- [self.source.host_git, 'describe', '--abbrev=0', ref, *options],
- cwd=self.mirror)
- if exit_code == 0:
- tag = output.strip()
- _, commit_ref = self.source.check_output(
- [self.source.host_git, 'rev-parse', tag + '^{commit}'],
- fail="Unable to resolve tag '{}'".format(tag),
- cwd=self.mirror)
- exit_code = self.source.call(
- [self.source.host_git, 'cat-file', 'tag', tag],
- cwd=self.mirror)
- annotated = (exit_code == 0)
-
- tags.add((tag, commit_ref.strip(), annotated))
-
- return ref, list(tags)
-
- def stage(self, directory):
- fullpath = os.path.join(directory, self.path)
-
- # Using --shared here avoids copying the objects into the checkout, in any
- # case we're just checking out a specific commit and then removing the .git/
- # directory.
- self.source.call([self.source.host_git, 'clone', '--no-checkout', '--shared', self.mirror, fullpath],
- fail="Failed to create git mirror {} in directory: {}".format(self.mirror, fullpath),
- fail_temporarily=True)
-
- self.source.call([self.source.host_git, 'checkout', '--force', self.ref],
- fail="Failed to checkout git ref {}".format(self.ref),
- cwd=fullpath)
-
- # Remove .git dir
- shutil.rmtree(os.path.join(fullpath, ".git"))
-
- self._rebuild_git(fullpath)
-
- def init_workspace(self, directory):
- fullpath = os.path.join(directory, self.path)
- url = self.source.translate_url(self.url)
-
- self.source.call([self.source.host_git, 'clone', '--no-checkout', self.mirror, fullpath],
- fail="Failed to clone git mirror {} in directory: {}".format(self.mirror, fullpath),
- fail_temporarily=True)
-
- self.source.call([self.source.host_git, 'remote', 'set-url', 'origin', url],
- fail='Failed to add remote origin "{}"'.format(url),
- cwd=fullpath)
-
- self.source.call([self.source.host_git, 'checkout', '--force', self.ref],
- fail="Failed to checkout git ref {}".format(self.ref),
- cwd=fullpath)
-
- # List the submodules (path/url tuples) present at the given ref of this repo
- def submodule_list(self):
- modules = "{}:{}".format(self.ref, GIT_MODULES)
- exit_code, output = self.source.check_output(
- [self.source.host_git, 'show', modules], cwd=self.mirror)
-
- # If git show reports error code 128 here, we take it to mean there is
- # no .gitmodules file to display for the given revision.
- if exit_code == 128:
- return
- elif exit_code != 0:
- raise SourceError(
- "{plugin}: Failed to show gitmodules at ref {ref}".format(
- plugin=self, ref=self.ref))
-
- content = '\n'.join([l.strip() for l in output.splitlines()])
-
- io = StringIO(content)
- parser = RawConfigParser()
- parser.read_file(io)
-
- for section in parser.sections():
- # validate section name against the 'submodule "foo"' pattern
- if re.match(r'submodule "(.*)"', section):
- path = parser.get(section, 'path')
- url = parser.get(section, 'url')
-
- yield (path, url)
-
- # Fetch the ref which this mirror requires its submodule to have,
- # at the given ref of this mirror.
- def submodule_ref(self, submodule, ref=None):
- if not ref:
- ref = self.ref
-
- # list objects in the parent repo tree to find the commit
- # object that corresponds to the submodule
- _, output = self.source.check_output([self.source.host_git, 'ls-tree', ref, submodule],
- fail="ls-tree failed for commit {} and submodule: {}".format(
- ref, submodule),
- cwd=self.mirror)
-
- # read the commit hash from the output
- fields = output.split()
- if len(fields) >= 2 and fields[1] == 'commit':
- submodule_commit = output.split()[2]
-
- # fail if the commit hash is invalid
- if len(submodule_commit) != 40:
- raise SourceError("{}: Error reading commit information for submodule '{}'"
- .format(self.source, submodule))
-
- return submodule_commit
-
- else:
- detail = "The submodule '{}' is defined either in the BuildStream source\n".format(submodule) + \
- "definition, or in a .gitmodules file. But the submodule was never added to the\n" + \
- "underlying git repository with `git submodule add`."
-
- self.source.warn("{}: Ignoring inconsistent submodule '{}'"
- .format(self.source, submodule), detail=detail,
- warning_token=WARN_INCONSISTENT_SUBMODULE)
-
- return None
-
- def _rebuild_git(self, fullpath):
- if not self.tags:
- return
-
- with self.source.tempdir() as tmpdir:
- included = set()
- shallow = set()
- for _, commit_ref, _ in self.tags:
-
- if commit_ref == self.ref:
- # rev-list does not work in case of same rev
- shallow.add(self.ref)
- else:
- _, out = self.source.check_output([self.source.host_git, 'rev-list',
- '--ancestry-path', '--boundary',
- '{}..{}'.format(commit_ref, self.ref)],
- fail="Failed to get git history {}..{} in directory: {}"
- .format(commit_ref, self.ref, fullpath),
- fail_temporarily=True,
- cwd=self.mirror)
- self.source.warn("refs {}..{}: {}".format(commit_ref, self.ref, out.splitlines()))
- for line in out.splitlines():
- rev = line.lstrip('-')
- if line[0] == '-':
- shallow.add(rev)
- else:
- included.add(rev)
-
- shallow -= included
- included |= shallow
-
- self.source.call([self.source.host_git, 'init'],
- fail="Cannot initialize git repository: {}".format(fullpath),
- cwd=fullpath)
-
- for rev in included:
- with TemporaryFile(dir=tmpdir) as commit_file:
- self.source.call([self.source.host_git, 'cat-file', 'commit', rev],
- stdout=commit_file,
- fail="Failed to get commit {}".format(rev),
- cwd=self.mirror)
- commit_file.seek(0, 0)
- self.source.call([self.source.host_git, 'hash-object', '-w', '-t', 'commit', '--stdin'],
- stdin=commit_file,
- fail="Failed to add commit object {}".format(rev),
- cwd=fullpath)
-
- with open(os.path.join(fullpath, '.git', 'shallow'), 'w') as shallow_file:
- for rev in shallow:
- shallow_file.write('{}\n'.format(rev))
-
- for tag, commit_ref, annotated in self.tags:
- if annotated:
- with TemporaryFile(dir=tmpdir) as tag_file:
- tag_data = 'object {}\ntype commit\ntag {}\n'.format(commit_ref, tag)
- tag_file.write(tag_data.encode('ascii'))
- tag_file.seek(0, 0)
- _, tag_ref = self.source.check_output(
- [self.source.host_git, 'hash-object', '-w', '-t',
- 'tag', '--stdin'],
- stdin=tag_file,
- fail="Failed to add tag object {}".format(tag),
- cwd=fullpath)
-
- self.source.call([self.source.host_git, 'tag', tag, tag_ref.strip()],
- fail="Failed to tag: {}".format(tag),
- cwd=fullpath)
- else:
- self.source.call([self.source.host_git, 'tag', tag, commit_ref],
- fail="Failed to tag: {}".format(tag),
- cwd=fullpath)
-
- with open(os.path.join(fullpath, '.git', 'HEAD'), 'w') as head:
- self.source.call([self.source.host_git, 'rev-parse', self.ref],
- stdout=head,
- fail="Failed to parse commit {}".format(self.ref),
- cwd=self.mirror)
-
-
-class _GitSourceBase(Source):
- # pylint: disable=attribute-defined-outside-init
-
- # The GitMirror class which this plugin uses. This may be
- # overridden in derived plugins as long as the replacement class
- # follows the same interface used by the _GitMirror class
- BST_MIRROR_CLASS = _GitMirror
-
- def configure(self, node):
- ref = self.node_get_member(node, str, 'ref', None)
-
- config_keys = ['url', 'track', 'ref', 'submodules',
- 'checkout-submodules', 'ref-format',
- 'track-tags', 'tags']
- self.node_validate(node, config_keys + Source.COMMON_CONFIG_KEYS)
-
- tags_node = self.node_get_member(node, list, 'tags', [])
- for tag_node in tags_node:
- self.node_validate(tag_node, ['tag', 'commit', 'annotated'])
-
- tags = self._load_tags(node)
- self.track_tags = self.node_get_member(node, bool, 'track-tags', False)
-
- self.original_url = self.node_get_member(node, str, 'url')
- self.mirror = self.BST_MIRROR_CLASS(self, '', self.original_url, ref, tags=tags, primary=True)
- self.tracking = self.node_get_member(node, str, 'track', None)
-
- self.ref_format = self.node_get_member(node, str, 'ref-format', 'sha1')
- if self.ref_format not in ['sha1', 'git-describe']:
- provenance = self.node_provenance(node, member_name='ref-format')
- raise SourceError("{}: Unexpected value for ref-format: {}".format(provenance, self.ref_format))
-
- # At this point we now know if the source has a ref and/or a track.
- # If it is missing both then we will be unable to track or build.
- if self.mirror.ref is None and self.tracking is None:
- raise SourceError("{}: Git sources require a ref and/or track".format(self),
- reason="missing-track-and-ref")
-
- self.checkout_submodules = self.node_get_member(node, bool, 'checkout-submodules', True)
- self.submodules = []
-
- # Parse a dict of submodule overrides, stored in the submodule_overrides
- # and submodule_checkout_overrides dictionaries.
- self.submodule_overrides = {}
- self.submodule_checkout_overrides = {}
- modules = self.node_get_member(node, Mapping, 'submodules', {})
- for path, _ in self.node_items(modules):
- submodule = self.node_get_member(modules, Mapping, path)
- url = self.node_get_member(submodule, str, 'url', None)
-
- # Make sure to mark all URLs that are specified in the configuration
- if url:
- self.mark_download_url(url, primary=False)
-
- self.submodule_overrides[path] = url
- if 'checkout' in submodule:
- checkout = self.node_get_member(submodule, bool, 'checkout')
- self.submodule_checkout_overrides[path] = checkout
-
- self.mark_download_url(self.original_url)
-
- def preflight(self):
- # Check if git is installed, get the binary at the same time
- self.host_git = utils.get_host_tool('git')
-
- def get_unique_key(self):
- # Here we want to encode the local name of the repository and
- # the ref, if the user changes the alias to fetch the same sources
- # from another location, it should not affect the cache key.
- key = [self.original_url, self.mirror.ref]
- if self.mirror.tags:
- tags = {tag: (commit, annotated) for tag, commit, annotated in self.mirror.tags}
- key.append({'tags': tags})
-
- # Only modify the cache key with checkout_submodules if it's something
- # other than the default behaviour.
- if self.checkout_submodules is False:
- key.append({"checkout_submodules": self.checkout_submodules})
-
- # We want the cache key to change if the source was
- # configured differently, and submodules count.
- if self.submodule_overrides:
- key.append(self.submodule_overrides)
-
- if self.submodule_checkout_overrides:
- key.append({"submodule_checkout_overrides": self.submodule_checkout_overrides})
-
- return key
-
- def get_consistency(self):
- if self._have_all_refs():
- return Consistency.CACHED
- elif self.mirror.ref is not None:
- return Consistency.RESOLVED
- return Consistency.INCONSISTENT
-
- def load_ref(self, node):
- self.mirror.ref = self.node_get_member(node, str, 'ref', None)
- self.mirror.tags = self._load_tags(node)
-
- def get_ref(self):
- return self.mirror.ref, self.mirror.tags
-
- def set_ref(self, ref_data, node):
- if not ref_data:
- self.mirror.ref = None
- if 'ref' in node:
- del node['ref']
- self.mirror.tags = []
- if 'tags' in node:
- del node['tags']
- else:
- ref, tags = ref_data
- node['ref'] = self.mirror.ref = ref
- self.mirror.tags = tags
- if tags:
- node['tags'] = []
- for tag, commit_ref, annotated in tags:
- data = {'tag': tag,
- 'commit': commit_ref,
- 'annotated': annotated}
- node['tags'].append(data)
- else:
- if 'tags' in node:
- del node['tags']
-
- def track(self):
-
- # If self.tracking is not specified it's not an error, just silently return
- if not self.tracking:
- # Is there a better way to check if a ref is given.
- if self.mirror.ref is None:
- detail = 'Without a tracking branch ref can not be updated. Please ' + \
- 'provide a ref or a track.'
- raise SourceError("{}: No track or ref".format(self),
- detail=detail, reason="track-attempt-no-track")
- return None
-
- # Resolve the URL for the message
- resolved_url = self.translate_url(self.mirror.url)
- with self.timed_activity("Tracking {} from {}"
- .format(self.tracking, resolved_url),
- silent_nested=True):
- self.mirror.ensure()
- self.mirror._fetch()
-
- # Update self.mirror.ref and node.ref from the self.tracking branch
- ret = self.mirror.latest_commit_with_tags(self.tracking, self.track_tags)
-
- return ret
-
- def init_workspace(self, directory):
- # XXX: may wish to refactor this as some code dupe with stage()
- self._refresh_submodules()
-
- with self.timed_activity('Setting up workspace "{}"'.format(directory), silent_nested=True):
- self.mirror.init_workspace(directory)
- for mirror in self.submodules:
- mirror.init_workspace(directory)
-
- def stage(self, directory):
-
- # Need to refresh submodule list here again, because
- # it's possible that we did not load in the main process
- # with submodules present (source needed fetching) and
- # we may not know about the submodule yet come time to build.
- #
- self._refresh_submodules()
-
- # Stage the main repo in the specified directory
- #
- with self.timed_activity("Staging {}".format(self.mirror.url), silent_nested=True):
- self.mirror.stage(directory)
- for mirror in self.submodules:
- mirror.stage(directory)
-
- def get_source_fetchers(self):
- yield self.mirror
- self._refresh_submodules()
- for submodule in self.submodules:
- yield submodule
-
- def validate_cache(self):
- discovered_submodules = {}
- unlisted_submodules = []
- invalid_submodules = []
-
- for path, url in self.mirror.submodule_list():
- discovered_submodules[path] = url
- if self._ignore_submodule(path):
- continue
-
- override_url = self.submodule_overrides.get(path)
- if not override_url:
- unlisted_submodules.append((path, url))
-
- # Warn about submodules which are explicitly configured but do not exist
- for path, url in self.submodule_overrides.items():
- if path not in discovered_submodules:
- invalid_submodules.append((path, url))
-
- if invalid_submodules:
- detail = []
- for path, url in invalid_submodules:
- detail.append(" Submodule URL '{}' at path '{}'".format(url, path))
-
- self.warn("{}: Invalid submodules specified".format(self),
- warning_token=WARN_INVALID_SUBMODULE,
- detail="The following submodules are specified in the source "
- "description but do not exist according to the repository\n\n" +
- "\n".join(detail))
-
- # Warn about submodules which exist but have not been explicitly configured
- if unlisted_submodules:
- detail = []
- for path, url in unlisted_submodules:
- detail.append(" Submodule URL '{}' at path '{}'".format(url, path))
-
- self.warn("{}: Unlisted submodules exist".format(self),
- warning_token=WARN_UNLISTED_SUBMODULE,
- detail="The following submodules exist but are not specified " +
- "in the source description\n\n" +
- "\n".join(detail))
-
- # Assert that the ref exists in the track tag/branch, if track has been specified.
- ref_in_track = False
- if self.tracking:
- _, branch = self.check_output([self.host_git, 'branch', '--list', self.tracking,
- '--contains', self.mirror.ref],
- cwd=self.mirror.mirror)
- if branch:
- ref_in_track = True
- else:
- _, tag = self.check_output([self.host_git, 'tag', '--list', self.tracking,
- '--contains', self.mirror.ref],
- cwd=self.mirror.mirror)
- if tag:
- ref_in_track = True
-
- if not ref_in_track:
- detail = "The ref provided for the element does not exist locally " + \
- "in the provided track branch / tag '{}'.\n".format(self.tracking) + \
- "You may wish to track the element to update the ref from '{}' ".format(self.tracking) + \
- "with `bst source track`,\n" + \
- "or examine the upstream at '{}' for the specific ref.".format(self.mirror.url)
-
- self.warn("{}: expected ref '{}' was not found in given track '{}' for staged repository: '{}'\n"
- .format(self, self.mirror.ref, self.tracking, self.mirror.url),
- detail=detail, warning_token=CoreWarnings.REF_NOT_IN_TRACK)
-
- ###########################################################
- # Local Functions #
- ###########################################################
-
- def _have_all_refs(self):
- if not self.mirror.has_ref():
- return False
-
- self._refresh_submodules()
- for mirror in self.submodules:
- if not os.path.exists(mirror.mirror):
- return False
- if not mirror.has_ref():
- return False
-
- return True
-
- # Refreshes the BST_MIRROR_CLASS objects for submodules
- #
- # Assumes that we have our mirror and we have the ref which we point to
- #
- def _refresh_submodules(self):
- self.mirror.ensure()
- submodules = []
-
- for path, url in self.mirror.submodule_list():
-
- # Completely ignore submodules which are disabled for checkout
- if self._ignore_submodule(path):
- continue
-
- # Allow configuration to override the upstream
- # location of the submodules.
- override_url = self.submodule_overrides.get(path)
- if override_url:
- url = override_url
-
- ref = self.mirror.submodule_ref(path)
- if ref is not None:
- mirror = self.BST_MIRROR_CLASS(self, path, url, ref)
- submodules.append(mirror)
-
- self.submodules = submodules
-
- def _load_tags(self, node):
- tags = []
- tags_node = self.node_get_member(node, list, 'tags', [])
- for tag_node in tags_node:
- tag = self.node_get_member(tag_node, str, 'tag')
- commit_ref = self.node_get_member(tag_node, str, 'commit')
- annotated = self.node_get_member(tag_node, bool, 'annotated')
- tags.append((tag, commit_ref, annotated))
- return tags
-
- # Checks whether the plugin configuration has explicitly
- # configured this submodule to be ignored
- def _ignore_submodule(self, path):
- try:
- checkout = self.submodule_checkout_overrides[path]
- except KeyError:
- checkout = self.checkout_submodules
-
- return not checkout
diff --git a/buildstream/_includes.py b/buildstream/_includes.py
deleted file mode 100644
index f792b7716..000000000
--- a/buildstream/_includes.py
+++ /dev/null
@@ -1,145 +0,0 @@
-import os
-from . import _yaml
-from ._exceptions import LoadError, LoadErrorReason
-
-
-# Includes()
-#
-# This takes care of processing include directives "(@)".
-#
-# Args:
-# loader (Loader): The Loader object
-# copy_tree (bool): Whether to make a copy, of tree in
-# provenance. Should be true if intended to be
-# serialized.
-class Includes:
-
- def __init__(self, loader, *, copy_tree=False):
- self._loader = loader
- self._loaded = {}
- self._copy_tree = copy_tree
-
- # process()
- #
- # Process recursively include directives in a YAML node.
- #
- # Args:
- # node (dict): A YAML node
- # included (set): Fail for recursion if trying to load any files in this set
- # current_loader (Loader): Use alternative loader (for junction files)
- # only_local (bool): Whether to ignore junction files
- def process(self, node, *,
- included=set(),
- current_loader=None,
- only_local=False):
- if current_loader is None:
- current_loader = self._loader
-
- includes = _yaml.node_get(node, None, '(@)', default_value=None)
- if isinstance(includes, str):
- includes = [includes]
-
- if not isinstance(includes, list) and includes is not None:
- provenance = _yaml.node_get_provenance(node, key='(@)')
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: {} must either be list or str".format(provenance, includes))
-
- include_provenance = None
- if includes:
- include_provenance = _yaml.node_get_provenance(node, key='(@)')
- _yaml.node_del(node, '(@)')
-
- for include in reversed(includes):
- if only_local and ':' in include:
- continue
- try:
- include_node, file_path, sub_loader = self._include_file(include,
- current_loader)
- except LoadError as e:
- if e.reason == LoadErrorReason.MISSING_FILE:
- message = "{}: Include block references a file that could not be found: '{}'.".format(
- include_provenance, include)
- raise LoadError(LoadErrorReason.MISSING_FILE, message) from e
- elif e.reason == LoadErrorReason.LOADING_DIRECTORY:
- message = "{}: Include block references a directory instead of a file: '{}'.".format(
- include_provenance, include)
- raise LoadError(LoadErrorReason.LOADING_DIRECTORY, message) from e
- else:
- raise
-
- if file_path in included:
- raise LoadError(LoadErrorReason.RECURSIVE_INCLUDE,
- "{}: trying to recursively include {}". format(include_provenance,
- file_path))
- # Because the included node will be modified, we need
- # to copy it so that we do not modify the toplevel
- # node of the provenance.
- include_node = _yaml.node_copy(include_node)
-
- try:
- included.add(file_path)
- self.process(include_node, included=included,
- current_loader=sub_loader,
- only_local=only_local)
- finally:
- included.remove(file_path)
-
- _yaml.composite_and_move(node, include_node)
-
- for _, value in _yaml.node_items(node):
- self._process_value(value,
- included=included,
- current_loader=current_loader,
- only_local=only_local)
-
- # _include_file()
- #
- # Load include YAML file from with a loader.
- #
- # Args:
- # include (str): file path relative to loader's project directory.
- # Can be prefixed with junctio name.
- # loader (Loader): Loader for the current project.
- def _include_file(self, include, loader):
- shortname = include
- if ':' in include:
- junction, include = include.split(':', 1)
- junction_loader = loader._get_loader(junction, fetch_subprojects=True)
- current_loader = junction_loader
- else:
- current_loader = loader
- project = current_loader.project
- directory = project.directory
- file_path = os.path.join(directory, include)
- key = (current_loader, file_path)
- if key not in self._loaded:
- self._loaded[key] = _yaml.load(file_path,
- shortname=shortname,
- project=project,
- copy_tree=self._copy_tree)
- return self._loaded[key], file_path, current_loader
-
- # _process_value()
- #
- # Select processing for value that could be a list or a dictionary.
- #
- # Args:
- # value: Value to process. Can be a list or a dictionary.
- # included (set): Fail for recursion if trying to load any files in this set
- # current_loader (Loader): Use alternative loader (for junction files)
- # only_local (bool): Whether to ignore junction files
- def _process_value(self, value, *,
- included=set(),
- current_loader=None,
- only_local=False):
- if _yaml.is_node(value):
- self.process(value,
- included=included,
- current_loader=current_loader,
- only_local=only_local)
- elif isinstance(value, list):
- for v in value:
- self._process_value(v,
- included=included,
- current_loader=current_loader,
- only_local=only_local)
diff --git a/buildstream/_loader/__init__.py b/buildstream/_loader/__init__.py
deleted file mode 100644
index a2c31796e..000000000
--- a/buildstream/_loader/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Copyright (C) 2018 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>
-
-from .metasource import MetaSource
-from .metaelement import MetaElement
-from .loader import Loader
diff --git a/buildstream/_loader/loadelement.py b/buildstream/_loader/loadelement.py
deleted file mode 100644
index 684c32554..000000000
--- a/buildstream/_loader/loadelement.py
+++ /dev/null
@@ -1,181 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-# System imports
-from itertools import count
-
-from pyroaring import BitMap, FrozenBitMap # pylint: disable=no-name-in-module
-
-# BuildStream toplevel imports
-from .. import _yaml
-
-# Local package imports
-from .types import Symbol, Dependency
-
-
-# LoadElement():
-#
-# A transient object breaking down what is loaded allowing us to
-# do complex operations in multiple passes.
-#
-# Args:
-# node (dict): A YAML loaded dictionary
-# name (str): The element name
-# loader (Loader): The Loader object for this element
-#
-class LoadElement():
- # Dependency():
- #
- # A link from a LoadElement to its dependencies.
- #
- # Keeps a link to one of the current Element's dependencies, together with
- # its dependency type.
- #
- # Args:
- # element (LoadElement): a LoadElement on which there is a dependency
- # dep_type (str): the type of dependency this dependency link is
- class Dependency:
- def __init__(self, element, dep_type):
- self.element = element
- self.dep_type = dep_type
-
- _counter = count()
-
- def __init__(self, node, filename, loader):
-
- #
- # Public members
- #
- self.node = node # The YAML node
- self.name = filename # The element name
- self.full_name = None # The element full name (with associated junction)
- self.deps = None # The list of Dependency objects
- self.node_id = next(self._counter)
-
- #
- # Private members
- #
- self._loader = loader # The Loader object
- self._dep_cache = None # The dependency cache, to speed up depends()
-
- #
- # Initialization
- #
- if loader.project.junction:
- # dependency is in subproject, qualify name
- self.full_name = '{}:{}'.format(loader.project.junction.name, self.name)
- else:
- # dependency is in top-level project
- self.full_name = self.name
-
- # Ensure the root node is valid
- _yaml.node_validate(self.node, [
- 'kind', 'depends', 'sources', 'sandbox',
- 'variables', 'environment', 'environment-nocache',
- 'config', 'public', 'description',
- 'build-depends', 'runtime-depends',
- ])
-
- self.dependencies = []
-
- @property
- def junction(self):
- return self._loader.project.junction
-
- # depends():
- #
- # Checks if this element depends on another element, directly
- # or indirectly.
- #
- # Args:
- # other (LoadElement): Another LoadElement
- #
- # Returns:
- # (bool): True if this LoadElement depends on 'other'
- #
- def depends(self, other):
- self._ensure_depends_cache()
- return other.node_id in self._dep_cache
-
- ###########################################
- # Private Methods #
- ###########################################
- def _ensure_depends_cache(self):
-
- if self._dep_cache:
- return
-
- self._dep_cache = BitMap()
-
- for dep in self.dependencies:
- elt = dep.element
-
- # Ensure the cache of the element we depend on
- elt._ensure_depends_cache()
-
- # We depend on this element
- self._dep_cache.add(elt.node_id)
-
- # And we depend on everything this element depends on
- self._dep_cache.update(elt._dep_cache)
-
- self._dep_cache = FrozenBitMap(self._dep_cache)
-
-
-# _extract_depends_from_node():
-#
-# Creates an array of Dependency objects from a given dict node 'node',
-# allows both strings and dicts for expressing the dependency and
-# throws a comprehensive LoadError in the case that the node is malformed.
-#
-# After extracting depends, the symbol is deleted from the node
-#
-# Args:
-# node (dict): A YAML loaded dictionary
-#
-# Returns:
-# (list): a list of Dependency objects
-#
-def _extract_depends_from_node(node, *, key=None):
- if key is None:
- build_depends = _extract_depends_from_node(node, key=Symbol.BUILD_DEPENDS)
- runtime_depends = _extract_depends_from_node(node, key=Symbol.RUNTIME_DEPENDS)
- depends = _extract_depends_from_node(node, key=Symbol.DEPENDS)
- return build_depends + runtime_depends + depends
- elif key == Symbol.BUILD_DEPENDS:
- default_dep_type = Symbol.BUILD
- elif key == Symbol.RUNTIME_DEPENDS:
- default_dep_type = Symbol.RUNTIME
- elif key == Symbol.DEPENDS:
- default_dep_type = None
- else:
- assert False, "Unexpected value of key '{}'".format(key)
-
- depends = _yaml.node_get(node, list, key, default_value=[])
- output_deps = []
-
- for index, dep in enumerate(depends):
- dep_provenance = _yaml.node_get_provenance(node, key=key, indices=[index])
- dependency = Dependency(dep, dep_provenance, default_dep_type=default_dep_type)
- output_deps.append(dependency)
-
- # Now delete the field, we dont want it anymore
- _yaml.node_del(node, key, safe=True)
-
- return output_deps
diff --git a/buildstream/_loader/loader.py b/buildstream/_loader/loader.py
deleted file mode 100644
index 261ec40e4..000000000
--- a/buildstream/_loader/loader.py
+++ /dev/null
@@ -1,710 +0,0 @@
-#
-# Copyright (C) 2018 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>
-
-import os
-from functools import cmp_to_key
-from collections.abc import Mapping
-
-from .._exceptions import LoadError, LoadErrorReason
-from .. import Consistency
-from .. import _yaml
-from ..element import Element
-from .._profile import Topics, PROFILER
-from .._includes import Includes
-
-from .types import Symbol
-from .loadelement import LoadElement, _extract_depends_from_node
-from .metaelement import MetaElement
-from .metasource import MetaSource
-from ..types import CoreWarnings
-from .._message import Message, MessageType
-
-
-# Loader():
-#
-# The Loader class does the heavy lifting of parsing target
-# bst files and ultimately transforming them into a list of MetaElements
-# with their own MetaSources, ready for instantiation by the core.
-#
-# Args:
-# context (Context): The Context object
-# project (Project): The toplevel Project object
-# parent (Loader): A parent Loader object, in the case this is a junctioned Loader
-#
-class Loader():
-
- def __init__(self, context, project, *, parent=None):
-
- # Ensure we have an absolute path for the base directory
- basedir = project.element_path
- if not os.path.isabs(basedir):
- basedir = os.path.abspath(basedir)
-
- #
- # Public members
- #
- self.project = project # The associated Project
-
- #
- # Private members
- #
- self._context = context
- self._options = project.options # Project options (OptionPool)
- self._basedir = basedir # Base project directory
- self._first_pass_options = project.first_pass_config.options # Project options (OptionPool)
- self._parent = parent # The parent loader
-
- self._meta_elements = {} # Dict of resolved meta elements by name
- self._elements = {} # Dict of elements
- self._loaders = {} # Dict of junction loaders
-
- self._includes = Includes(self, copy_tree=True)
-
- # load():
- #
- # Loads the project based on the parameters given to the constructor
- #
- # Args:
- # rewritable (bool): Whether the loaded files should be rewritable
- # this is a bit more expensive due to deep copies
- # ticker (callable): An optional function for tracking load progress
- # targets (list of str): Target, element-path relative bst filenames in the project
- # fetch_subprojects (bool): Whether to fetch subprojects while loading
- #
- # Raises: LoadError
- #
- # Returns: The toplevel LoadElement
- def load(self, targets, rewritable=False, ticker=None, fetch_subprojects=False):
-
- for filename in targets:
- if os.path.isabs(filename):
- # XXX Should this just be an assertion ?
- # Expect that the caller gives us the right thing at least ?
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "Target '{}' was not specified as a relative "
- "path to the base project directory: {}"
- .format(filename, self._basedir))
-
- self._warn_invalid_elements(targets)
-
- # First pass, recursively load files and populate our table of LoadElements
- #
- target_elements = []
-
- for target in targets:
- with PROFILER.profile(Topics.LOAD_PROJECT, target):
- _junction, name, loader = self._parse_name(target, rewritable, ticker,
- fetch_subprojects=fetch_subprojects)
- element = loader._load_file(name, rewritable, ticker, fetch_subprojects)
- target_elements.append(element)
-
- #
- # Now that we've resolve the dependencies, scan them for circular dependencies
- #
-
- # Set up a dummy element that depends on all top-level targets
- # to resolve potential circular dependencies between them
- dummy_target = LoadElement(_yaml.new_empty_node(), "", self)
- dummy_target.dependencies.extend(
- LoadElement.Dependency(element, Symbol.RUNTIME)
- for element in target_elements
- )
-
- with PROFILER.profile(Topics.CIRCULAR_CHECK, "_".join(targets)):
- self._check_circular_deps(dummy_target)
-
- ret = []
- #
- # Sort direct dependencies of elements by their dependency ordering
- #
- for element in target_elements:
- loader = element._loader
- with PROFILER.profile(Topics.SORT_DEPENDENCIES, element.name):
- loader._sort_dependencies(element)
-
- # Finally, wrap what we have into LoadElements and return the target
- #
- ret.append(loader._collect_element(element))
-
- self._clean_caches()
-
- return ret
-
- # clean_caches()
- #
- # Clean internal loader caches, recursively
- #
- # When loading the elements, the loaders use caches in order to not load the
- # same element twice. These are kept after loading and prevent garbage
- # collection. Cleaning them explicitely is required.
- #
- def _clean_caches(self):
- for loader in self._loaders.values():
- # value may be None with nested junctions without overrides
- if loader is not None:
- loader._clean_caches()
-
- self._meta_elements = {}
- self._elements = {}
-
- ###########################################
- # Private Methods #
- ###########################################
-
- # _load_file():
- #
- # Recursively load bst files
- #
- # Args:
- # filename (str): The element-path relative bst file
- # rewritable (bool): Whether we should load in round trippable mode
- # ticker (callable): A callback to report loaded filenames to the frontend
- # fetch_subprojects (bool): Whether to fetch subprojects while loading
- # provenance (Provenance): The location from where the file was referred to, or None
- #
- # Returns:
- # (LoadElement): A loaded LoadElement
- #
- def _load_file(self, filename, rewritable, ticker, fetch_subprojects, provenance=None):
-
- # Silently ignore already loaded files
- if filename in self._elements:
- return self._elements[filename]
-
- # Call the ticker
- if ticker:
- ticker(filename)
-
- # Load the data and process any conditional statements therein
- fullpath = os.path.join(self._basedir, filename)
- try:
- node = _yaml.load(fullpath, shortname=filename, copy_tree=rewritable,
- project=self.project)
- except LoadError as e:
- if e.reason == LoadErrorReason.MISSING_FILE:
-
- if self.project.junction:
- message = "Could not find element '{}' in project referred to by junction element '{}'" \
- .format(filename, self.project.junction.name)
- else:
- message = "Could not find element '{}' in elements directory '{}'".format(filename, self._basedir)
-
- if provenance:
- message = "{}: {}".format(provenance, message)
-
- # If we can't find the file, try to suggest plausible
- # alternatives by stripping the element-path from the given
- # filename, and verifying that it exists.
- detail = None
- elements_dir = os.path.relpath(self._basedir, self.project.directory)
- element_relpath = os.path.relpath(filename, elements_dir)
- if filename.startswith(elements_dir) and os.path.exists(os.path.join(self._basedir, element_relpath)):
- detail = "Did you mean '{}'?".format(element_relpath)
-
- raise LoadError(LoadErrorReason.MISSING_FILE,
- message, detail=detail) from e
-
- elif e.reason == LoadErrorReason.LOADING_DIRECTORY:
- # If a <directory>.bst file exists in the element path,
- # let's suggest this as a plausible alternative.
- message = str(e)
- if provenance:
- message = "{}: {}".format(provenance, message)
- detail = None
- if os.path.exists(os.path.join(self._basedir, filename + '.bst')):
- element_name = filename + '.bst'
- detail = "Did you mean '{}'?\n".format(element_name)
- raise LoadError(LoadErrorReason.LOADING_DIRECTORY,
- message, detail=detail) from e
- else:
- raise
- kind = _yaml.node_get(node, str, Symbol.KIND)
- if kind == "junction":
- self._first_pass_options.process_node(node)
- else:
- self.project.ensure_fully_loaded()
-
- self._includes.process(node)
-
- self._options.process_node(node)
-
- element = LoadElement(node, filename, self)
-
- self._elements[filename] = element
-
- dependencies = _extract_depends_from_node(node)
-
- # Load all dependency files for the new LoadElement
- for dep in dependencies:
- if dep.junction:
- self._load_file(dep.junction, rewritable, ticker, fetch_subprojects, dep.provenance)
- loader = self._get_loader(dep.junction, rewritable=rewritable, ticker=ticker,
- fetch_subprojects=fetch_subprojects, provenance=dep.provenance)
- else:
- loader = self
-
- dep_element = loader._load_file(dep.name, rewritable, ticker,
- fetch_subprojects, dep.provenance)
-
- if _yaml.node_get(dep_element.node, str, Symbol.KIND) == 'junction':
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Cannot depend on junction"
- .format(dep.provenance))
-
- element.dependencies.append(LoadElement.Dependency(dep_element, dep.dep_type))
-
- deps_names = [dep.name for dep in dependencies]
- self._warn_invalid_elements(deps_names)
-
- return element
-
- # _check_circular_deps():
- #
- # Detect circular dependencies on LoadElements with
- # dependencies already resolved.
- #
- # Args:
- # element (str): The element to check
- #
- # Raises:
- # (LoadError): In case there was a circular dependency error
- #
- def _check_circular_deps(self, element, check_elements=None, validated=None, sequence=None):
-
- if check_elements is None:
- check_elements = set()
- if validated is None:
- validated = set()
- if sequence is None:
- sequence = []
-
- # Skip already validated branches
- if element in validated:
- return
-
- if element in check_elements:
- # Create `chain`, the loop of element dependencies from this
- # element back to itself, by trimming everything before this
- # element from the sequence under consideration.
- chain = sequence[sequence.index(element.full_name):]
- chain.append(element.full_name)
- raise LoadError(LoadErrorReason.CIRCULAR_DEPENDENCY,
- ("Circular dependency detected at element: {}\n" +
- "Dependency chain: {}")
- .format(element.full_name, " -> ".join(chain)))
-
- # Push / Check each dependency / Pop
- check_elements.add(element)
- sequence.append(element.full_name)
- for dep in element.dependencies:
- dep.element._loader._check_circular_deps(dep.element, check_elements, validated, sequence)
- check_elements.remove(element)
- sequence.pop()
-
- # Eliminate duplicate paths
- validated.add(element)
-
- # _sort_dependencies():
- #
- # Sort dependencies of each element by their dependencies,
- # so that direct dependencies which depend on other direct
- # dependencies (directly or indirectly) appear later in the
- # list.
- #
- # This avoids the need for performing multiple topological
- # sorts throughout the build process.
- #
- # Args:
- # element (LoadElement): The element to sort
- #
- def _sort_dependencies(self, element, visited=None):
- if visited is None:
- visited = set()
-
- if element in visited:
- return
-
- for dep in element.dependencies:
- dep.element._loader._sort_dependencies(dep.element, visited=visited)
-
- def dependency_cmp(dep_a, dep_b):
- element_a = dep_a.element
- element_b = dep_b.element
-
- # Sort on inter element dependency first
- if element_a.depends(element_b):
- return 1
- elif element_b.depends(element_a):
- return -1
-
- # If there are no inter element dependencies, place
- # runtime only dependencies last
- if dep_a.dep_type != dep_b.dep_type:
- if dep_a.dep_type == Symbol.RUNTIME:
- return 1
- elif dep_b.dep_type == Symbol.RUNTIME:
- return -1
-
- # All things being equal, string comparison.
- if element_a.name > element_b.name:
- return 1
- elif element_a.name < element_b.name:
- return -1
-
- # Sort local elements before junction elements
- # and use string comparison between junction elements
- if element_a.junction and element_b.junction:
- if element_a.junction > element_b.junction:
- return 1
- elif element_a.junction < element_b.junction:
- return -1
- elif element_a.junction:
- return -1
- elif element_b.junction:
- return 1
-
- # This wont ever happen
- return 0
-
- # Now dependency sort, we ensure that if any direct dependency
- # directly or indirectly depends on another direct dependency,
- # it is found later in the list.
- element.dependencies.sort(key=cmp_to_key(dependency_cmp))
-
- visited.add(element)
-
- # _collect_element()
- #
- # Collect the toplevel elements we have
- #
- # Args:
- # element (LoadElement): The element for which to load a MetaElement
- #
- # Returns:
- # (MetaElement): A recursively loaded MetaElement
- #
- def _collect_element(self, element):
- # Return the already built one, if we already built it
- meta_element = self._meta_elements.get(element.name)
- if meta_element:
- return meta_element
-
- node = element.node
- elt_provenance = _yaml.node_get_provenance(node)
- meta_sources = []
-
- sources = _yaml.node_get(node, list, Symbol.SOURCES, default_value=[])
- element_kind = _yaml.node_get(node, str, Symbol.KIND)
-
- # Safe loop calling into _yaml.node_get() for each element ensures
- # we have good error reporting
- for i in range(len(sources)):
- source = _yaml.node_get(node, Mapping, Symbol.SOURCES, indices=[i])
- kind = _yaml.node_get(source, str, Symbol.KIND)
- _yaml.node_del(source, Symbol.KIND)
-
- # Directory is optional
- directory = _yaml.node_get(source, str, Symbol.DIRECTORY, default_value=None)
- if directory:
- _yaml.node_del(source, Symbol.DIRECTORY)
-
- index = sources.index(source)
- meta_source = MetaSource(element.name, index, element_kind, kind, source, directory)
- meta_sources.append(meta_source)
-
- meta_element = MetaElement(self.project, element.name, element_kind,
- elt_provenance, meta_sources,
- _yaml.node_get(node, Mapping, Symbol.CONFIG, default_value={}),
- _yaml.node_get(node, Mapping, Symbol.VARIABLES, default_value={}),
- _yaml.node_get(node, Mapping, Symbol.ENVIRONMENT, default_value={}),
- _yaml.node_get(node, list, Symbol.ENV_NOCACHE, default_value=[]),
- _yaml.node_get(node, Mapping, Symbol.PUBLIC, default_value={}),
- _yaml.node_get(node, Mapping, Symbol.SANDBOX, default_value={}),
- element_kind == 'junction')
-
- # Cache it now, make sure it's already there before recursing
- self._meta_elements[element.name] = meta_element
-
- # Descend
- for dep in element.dependencies:
- loader = dep.element._loader
- meta_dep = loader._collect_element(dep.element)
- if dep.dep_type != 'runtime':
- meta_element.build_dependencies.append(meta_dep)
- if dep.dep_type != 'build':
- meta_element.dependencies.append(meta_dep)
-
- return meta_element
-
- # _get_loader():
- #
- # Return loader for specified junction
- #
- # Args:
- # filename (str): Junction name
- # fetch_subprojects (bool): Whether to fetch subprojects while loading
- #
- # Raises: LoadError
- #
- # Returns: A Loader or None if specified junction does not exist
- def _get_loader(self, filename, *, rewritable=False, ticker=None, level=0,
- fetch_subprojects=False, provenance=None):
-
- provenance_str = ""
- if provenance is not None:
- provenance_str = "{}: ".format(provenance)
-
- # return previously determined result
- if filename in self._loaders:
- loader = self._loaders[filename]
-
- if loader is None:
- # do not allow junctions with the same name in different
- # subprojects
- raise LoadError(LoadErrorReason.CONFLICTING_JUNCTION,
- "{}Conflicting junction {} in subprojects, define junction in {}"
- .format(provenance_str, filename, self.project.name))
-
- return loader
-
- if self._parent:
- # junctions in the parent take precedence over junctions defined
- # in subprojects
- loader = self._parent._get_loader(filename, rewritable=rewritable, ticker=ticker,
- level=level + 1, fetch_subprojects=fetch_subprojects,
- provenance=provenance)
- if loader:
- self._loaders[filename] = loader
- return loader
-
- try:
- self._load_file(filename, rewritable, ticker, fetch_subprojects)
- except LoadError as e:
- if e.reason != LoadErrorReason.MISSING_FILE:
- # other load error
- raise
-
- if level == 0:
- # junction element not found in this or ancestor projects
- raise
- else:
- # mark junction as not available to allow detection of
- # conflicting junctions in subprojects
- self._loaders[filename] = None
- return None
-
- # meta junction element
- meta_element = self._collect_element(self._elements[filename])
- if meta_element.kind != 'junction':
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}{}: Expected junction but element kind is {}".format(
- provenance_str, filename, meta_element.kind))
-
- element = Element._new_from_meta(meta_element)
- element._preflight()
-
- # If this junction element points to a sub-sub-project, we need to
- # find loader for that project.
- if element.target:
- subproject_loader = self._get_loader(element.target_junction, rewritable=rewritable, ticker=ticker,
- level=level, fetch_subprojects=fetch_subprojects,
- provenance=provenance)
- loader = subproject_loader._get_loader(element.target_element, rewritable=rewritable, ticker=ticker,
- level=level, fetch_subprojects=fetch_subprojects,
- provenance=provenance)
- self._loaders[filename] = loader
- return loader
-
- sources = list(element.sources())
- if not element._source_cached():
- for idx, source in enumerate(sources):
- # Handle the case where a subproject needs to be fetched
- #
- if source.get_consistency() == Consistency.RESOLVED:
- if fetch_subprojects:
- if ticker:
- ticker(filename, 'Fetching subproject from {} source'.format(source.get_kind()))
- source._fetch(sources[0:idx])
- else:
- detail = "Try fetching the project with `bst source fetch {}`".format(filename)
- raise LoadError(LoadErrorReason.SUBPROJECT_FETCH_NEEDED,
- "{}Subproject fetch needed for junction: {}".format(provenance_str, filename),
- detail=detail)
-
- # Handle the case where a subproject has no ref
- #
- elif source.get_consistency() == Consistency.INCONSISTENT:
- detail = "Try tracking the junction element with `bst source track {}`".format(filename)
- raise LoadError(LoadErrorReason.SUBPROJECT_INCONSISTENT,
- "{}Subproject has no ref for junction: {}".format(provenance_str, filename),
- detail=detail)
-
- workspace = element._get_workspace()
- if workspace:
- # If a workspace is open, load it from there instead
- basedir = workspace.get_absolute_path()
- elif len(sources) == 1 and sources[0]._get_local_path():
- # Optimization for junctions with a single local source
- basedir = sources[0]._get_local_path()
- else:
- # Stage sources
- element._set_required()
- basedir = os.path.join(self.project.directory, ".bst", "staged-junctions",
- filename, element._get_cache_key())
- if not os.path.exists(basedir):
- os.makedirs(basedir, exist_ok=True)
- element._stage_sources_at(basedir, mount_workspaces=False)
-
- # Load the project
- project_dir = os.path.join(basedir, element.path)
- try:
- from .._project import Project # pylint: disable=cyclic-import
- project = Project(project_dir, self._context, junction=element,
- parent_loader=self, search_for_project=False)
- except LoadError as e:
- if e.reason == LoadErrorReason.MISSING_PROJECT_CONF:
- message = (
- provenance_str + "Could not find the project.conf file in the project "
- "referred to by junction element '{}'.".format(element.name)
- )
- if element.path:
- message += " Was expecting it at path '{}' in the junction's source.".format(element.path)
- raise LoadError(reason=LoadErrorReason.INVALID_JUNCTION,
- message=message) from e
- else:
- raise
-
- loader = project.loader
- self._loaders[filename] = loader
-
- return loader
-
- # _parse_name():
- #
- # Get junction and base name of element along with loader for the sub-project
- #
- # Args:
- # name (str): Name of target
- # rewritable (bool): Whether the loaded files should be rewritable
- # this is a bit more expensive due to deep copies
- # ticker (callable): An optional function for tracking load progress
- # fetch_subprojects (bool): Whether to fetch subprojects while loading
- #
- # Returns:
- # (tuple): - (str): name of the junction element
- # - (str): name of the element
- # - (Loader): loader for sub-project
- #
- def _parse_name(self, name, rewritable, ticker, fetch_subprojects=False):
- # We allow to split only once since deep junctions names are forbidden.
- # Users who want to refer to elements in sub-sub-projects are required
- # to create junctions on the top level project.
- junction_path = name.rsplit(':', 1)
- if len(junction_path) == 1:
- return None, junction_path[-1], self
- else:
- self._load_file(junction_path[-2], rewritable, ticker, fetch_subprojects)
- loader = self._get_loader(junction_path[-2], rewritable=rewritable, ticker=ticker,
- fetch_subprojects=fetch_subprojects)
- return junction_path[-2], junction_path[-1], loader
-
- # Print a warning message, checks warning_token against project configuration
- #
- # Args:
- # brief (str): The brief message
- # warning_token (str): An optional configurable warning assosciated with this warning,
- # this will cause PluginError to be raised if this warning is configured as fatal.
- # (*Since 1.4*)
- #
- # Raises:
- # (:class:`.LoadError`): When warning_token is considered fatal by the project configuration
- #
- def _warn(self, brief, *, warning_token=None):
- if warning_token:
- if self.project._warning_is_fatal(warning_token):
- raise LoadError(warning_token, brief)
-
- message = Message(None, MessageType.WARN, brief)
- self._context.message(message)
-
- # Print warning messages if any of the specified elements have invalid names.
- #
- # Valid filenames should end with ".bst" extension.
- #
- # Args:
- # elements (list): List of element names
- #
- # Raises:
- # (:class:`.LoadError`): When warning_token is considered fatal by the project configuration
- #
- def _warn_invalid_elements(self, elements):
-
- # invalid_elements
- #
- # A dict that maps warning types to the matching elements.
- invalid_elements = {
- CoreWarnings.BAD_ELEMENT_SUFFIX: [],
- CoreWarnings.BAD_CHARACTERS_IN_NAME: [],
- }
-
- for filename in elements:
- if not filename.endswith(".bst"):
- invalid_elements[CoreWarnings.BAD_ELEMENT_SUFFIX].append(filename)
- if not self._valid_chars_name(filename):
- invalid_elements[CoreWarnings.BAD_CHARACTERS_IN_NAME].append(filename)
-
- if invalid_elements[CoreWarnings.BAD_ELEMENT_SUFFIX]:
- self._warn("Target elements '{}' do not have expected file extension `.bst` "
- "Improperly named elements will not be discoverable by commands"
- .format(invalid_elements[CoreWarnings.BAD_ELEMENT_SUFFIX]),
- warning_token=CoreWarnings.BAD_ELEMENT_SUFFIX)
- if invalid_elements[CoreWarnings.BAD_CHARACTERS_IN_NAME]:
- self._warn("Target elements '{}' have invalid characerts in their name."
- .format(invalid_elements[CoreWarnings.BAD_CHARACTERS_IN_NAME]),
- warning_token=CoreWarnings.BAD_CHARACTERS_IN_NAME)
-
- # Check if given filename containers valid characters.
- #
- # Args:
- # name (str): Name of the file
- #
- # Returns:
- # (bool): True if all characters are valid, False otherwise.
- #
- def _valid_chars_name(self, name):
- for char in name:
- char_val = ord(char)
-
- # 0-31 are control chars, 127 is DEL, and >127 means non-ASCII
- if char_val <= 31 or char_val >= 127:
- return False
-
- # Disallow characters that are invalid on Windows. The list can be
- # found at https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
- #
- # Note that although : (colon) is not allowed, we do not raise
- # warnings because of that, since we use it as a separator for
- # junctioned elements.
- #
- # We also do not raise warnings on slashes since they are used as
- # path separators.
- if char in r'<>"|?*':
- return False
-
- return True
diff --git a/buildstream/_loader/metaelement.py b/buildstream/_loader/metaelement.py
deleted file mode 100644
index 45eb6f4d0..000000000
--- a/buildstream/_loader/metaelement.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-from .. import _yaml
-
-
-class MetaElement():
-
- # MetaElement()
- #
- # An abstract object holding data suitable for constructing an Element
- #
- # Args:
- # project: The project that contains the element
- # name: The resolved element name
- # kind: The element kind
- # provenance: The provenance of the element
- # sources: An array of MetaSource objects
- # config: The configuration data for the element
- # variables: The variables declared or overridden on this element
- # environment: The environment variables declared or overridden on this element
- # env_nocache: List of environment vars which should not be considered in cache keys
- # public: Public domain data dictionary
- # sandbox: Configuration specific to the sandbox environment
- # first_pass: The element is to be loaded with first pass configuration (junction)
- #
- def __init__(self, project, name, kind=None, provenance=None, sources=None, config=None,
- variables=None, environment=None, env_nocache=None, public=None,
- sandbox=None, first_pass=False):
- self.project = project
- self.name = name
- self.kind = kind
- self.provenance = provenance
- self.sources = sources
- self.config = config or _yaml.new_empty_node()
- self.variables = variables or _yaml.new_empty_node()
- self.environment = environment or _yaml.new_empty_node()
- self.env_nocache = env_nocache or []
- self.public = public or _yaml.new_empty_node()
- self.sandbox = sandbox or _yaml.new_empty_node()
- self.build_dependencies = []
- self.dependencies = []
- self.first_pass = first_pass
- self.is_junction = kind == "junction"
diff --git a/buildstream/_loader/metasource.py b/buildstream/_loader/metasource.py
deleted file mode 100644
index da2c0e292..000000000
--- a/buildstream/_loader/metasource.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-
-class MetaSource():
-
- # MetaSource()
- #
- # An abstract object holding data suitable for constructing a Source
- #
- # Args:
- # element_name: The name of the owning element
- # element_index: The index of the source in the owning element's source list
- # element_kind: The kind of the owning element
- # kind: The kind of the source
- # config: The configuration data for the source
- # first_pass: This source will be used with first project pass configuration (used for junctions).
- #
- def __init__(self, element_name, element_index, element_kind, kind, config, directory):
- self.element_name = element_name
- self.element_index = element_index
- self.element_kind = element_kind
- self.kind = kind
- self.config = config
- self.directory = directory
- self.first_pass = False
diff --git a/buildstream/_loader/types.py b/buildstream/_loader/types.py
deleted file mode 100644
index f9dd38ca0..000000000
--- a/buildstream/_loader/types.py
+++ /dev/null
@@ -1,112 +0,0 @@
-#
-# Copyright (C) 2018 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>
-
-from .._exceptions import LoadError, LoadErrorReason
-from .. import _yaml
-
-
-# Symbol():
-#
-# A simple object to denote the symbols we load with from YAML
-#
-class Symbol():
- FILENAME = "filename"
- KIND = "kind"
- DEPENDS = "depends"
- BUILD_DEPENDS = "build-depends"
- RUNTIME_DEPENDS = "runtime-depends"
- SOURCES = "sources"
- CONFIG = "config"
- VARIABLES = "variables"
- ENVIRONMENT = "environment"
- ENV_NOCACHE = "environment-nocache"
- PUBLIC = "public"
- TYPE = "type"
- BUILD = "build"
- RUNTIME = "runtime"
- ALL = "all"
- DIRECTORY = "directory"
- JUNCTION = "junction"
- SANDBOX = "sandbox"
-
-
-# Dependency()
-#
-# A simple object describing a dependency
-#
-# Args:
-# name (str): The element name
-# dep_type (str): The type of dependency, can be
-# Symbol.ALL, Symbol.BUILD, or Symbol.RUNTIME
-# junction (str): The element name of the junction, or None
-# provenance (Provenance): The YAML node provenance of where this
-# dependency was declared
-#
-class Dependency():
- def __init__(self, dep, provenance, default_dep_type=None):
- self.provenance = provenance
-
- if isinstance(dep, str):
- self.name = dep
- self.dep_type = default_dep_type
- self.junction = None
-
- elif _yaml.is_node(dep):
- if default_dep_type:
- _yaml.node_validate(dep, ['filename', 'junction'])
- dep_type = default_dep_type
- else:
- _yaml.node_validate(dep, ['filename', 'type', 'junction'])
-
- # Make type optional, for this we set it to None
- dep_type = _yaml.node_get(dep, str, Symbol.TYPE, default_value=None)
- if dep_type is None or dep_type == Symbol.ALL:
- dep_type = None
- elif dep_type not in [Symbol.BUILD, Symbol.RUNTIME]:
- provenance = _yaml.node_get_provenance(dep, key=Symbol.TYPE)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Dependency type '{}' is not 'build', 'runtime' or 'all'"
- .format(provenance, dep_type))
-
- self.name = _yaml.node_get(dep, str, Symbol.FILENAME)
- self.dep_type = dep_type
- self.junction = _yaml.node_get(dep, str, Symbol.JUNCTION, default_value=None)
-
- else:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Dependency is not specified as a string or a dictionary".format(provenance))
-
- # `:` characters are not allowed in filename if a junction was
- # explicitly specified
- if self.junction and ':' in self.name:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Dependency {} contains `:` in its name. "
- "`:` characters are not allowed in filename when "
- "junction attribute is specified.".format(self.provenance, self.name))
-
- # Name of the element should never contain more than one `:` characters
- if self.name.count(':') > 1:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Dependency {} contains multiple `:` in its name. "
- "Recursive lookups for cross-junction elements is not "
- "allowed.".format(self.provenance, self.name))
-
- # Attempt to split name if no junction was specified explicitly
- if not self.junction and self.name.count(':') == 1:
- self.junction, self.name = self.name.split(':')
diff --git a/buildstream/_message.py b/buildstream/_message.py
deleted file mode 100644
index c2cdb8277..000000000
--- a/buildstream/_message.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-import datetime
-import os
-
-
-# Types of status messages.
-#
-class MessageType():
- DEBUG = "debug" # Debugging message
- STATUS = "status" # Status message, verbose details
- INFO = "info" # Informative messages
- WARN = "warning" # Warning messages
- ERROR = "error" # Error messages
- BUG = "bug" # An unhandled exception was raised in a plugin
- LOG = "log" # Messages for log files _only_, never in the frontend
-
- # Timed Messages: SUCCESS and FAIL have duration timestamps
- START = "start" # Status start message
- SUCCESS = "success" # Successful status complete message
- FAIL = "failure" # Failing status complete message
- SKIPPED = "skipped"
-
-
-# Messages which should be reported regardless of whether
-# they are currently silenced or not
-unconditional_messages = [
- MessageType.INFO,
- MessageType.WARN,
- MessageType.FAIL,
- MessageType.ERROR,
- MessageType.BUG
-]
-
-
-# Message object
-#
-class Message():
-
- def __init__(self, unique_id, message_type, message,
- task_id=None,
- detail=None,
- action_name=None,
- elapsed=None,
- depth=None,
- logfile=None,
- sandbox=None,
- scheduler=False):
- self.message_type = message_type # Message type
- self.message = message # The message string
- self.detail = detail # An additional detail string
- self.action_name = action_name # Name of the task queue (fetch, refresh, build, etc)
- self.elapsed = elapsed # The elapsed time, in timed messages
- self.depth = depth # The depth of a timed message
- self.logfile = logfile # The log file path where commands took place
- self.sandbox = sandbox # The error that caused this message used a sandbox
- self.pid = os.getpid() # The process pid
- self.unique_id = unique_id # The plugin object ID issueing the message
- self.task_id = task_id # The plugin object ID of the task
- self.scheduler = scheduler # Whether this is a scheduler level message
- self.creation_time = datetime.datetime.now()
- if message_type in (MessageType.SUCCESS, MessageType.FAIL):
- assert elapsed is not None
diff --git a/buildstream/_options/__init__.py b/buildstream/_options/__init__.py
deleted file mode 100644
index 70bbe35aa..000000000
--- a/buildstream/_options/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .optionpool import OptionPool
diff --git a/buildstream/_options/option.py b/buildstream/_options/option.py
deleted file mode 100644
index ffdb4d272..000000000
--- a/buildstream/_options/option.py
+++ /dev/null
@@ -1,112 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .. import _yaml
-
-
-# Shared symbols for validation purposes
-#
-OPTION_SYMBOLS = [
- 'type',
- 'description',
- 'variable'
-]
-
-
-# Option()
-#
-# An abstract class representing a project option.
-#
-# Concrete classes must be created to handle option types,
-# the loaded project options is a collection of typed Option
-# instances.
-#
-class Option():
-
- # Subclasses use this to specify the type name used
- # for the yaml format and error messages
- OPTION_TYPE = None
-
- def __init__(self, name, definition, pool):
- self.name = name
- self.description = None
- self.variable = None
- self.value = None
- self.pool = pool
- self.load(definition)
-
- # load()
- #
- # Loads the option attributes from the descriptions
- # in the project.conf
- #
- # Args:
- # node (dict): The loaded YAML dictionary describing
- # the option
- def load(self, node):
- self.description = _yaml.node_get(node, str, 'description')
- self.variable = _yaml.node_get(node, str, 'variable', default_value=None)
-
- # Assert valid symbol name for variable name
- if self.variable is not None:
- p = _yaml.node_get_provenance(node, 'variable')
- _yaml.assert_symbol_name(p, self.variable, 'variable name')
-
- # load_value()
- #
- # Loads the value of the option in string form.
- #
- # Args:
- # node (Mapping): The YAML loaded key/value dictionary
- # to load the value from
- # transform (callbable): Transform function for variable substitution
- #
- def load_value(self, node, *, transform=None):
- pass # pragma: nocover
-
- # set_value()
- #
- # Sets the value of an option from a string passed
- # to buildstream on the command line
- #
- # Args:
- # value (str): The value in string form
- #
- def set_value(self, value):
- pass # pragma: nocover
-
- # get_value()
- #
- # Gets the value of an option in string form, this
- # is for the purpose of exporting option values to
- # variables which must be in string form.
- #
- # Returns:
- # (str): The value in string form
- #
- def get_value(self):
- pass # pragma: nocover
-
- # resolve()
- #
- # Called on each option once, after all configuration
- # and cli options have been passed.
- #
- def resolve(self):
- pass # pragma: nocover
diff --git a/buildstream/_options/optionarch.py b/buildstream/_options/optionarch.py
deleted file mode 100644
index 0e2963c84..000000000
--- a/buildstream/_options/optionarch.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .. import _yaml
-from .._exceptions import LoadError, LoadErrorReason, PlatformError
-from .._platform import Platform
-from .optionenum import OptionEnum
-
-
-# OptionArch
-#
-# An enumeration project option which does not allow
-# definition of a default value, but instead tries to set
-# the default value to the machine architecture introspected
-# using `uname`
-#
-# Note that when using OptionArch in a project, it will automatically
-# bail out of the host machine `uname` reports a machine architecture
-# not supported by the project, in the case that no option was
-# specifically specified
-#
-class OptionArch(OptionEnum):
-
- OPTION_TYPE = 'arch'
-
- def load(self, node):
- super(OptionArch, self).load(node, allow_default_definition=False)
-
- def load_default_value(self, node):
- arch = Platform.get_host_arch()
-
- default_value = None
-
- for index, value in enumerate(self.values):
- try:
- canonical_value = Platform.canonicalize_arch(value)
- if default_value is None and canonical_value == arch:
- default_value = value
- # Do not terminate the loop early to ensure we validate
- # all values in the list.
- except PlatformError as e:
- provenance = _yaml.node_get_provenance(node, key='values', indices=[index])
- prefix = ""
- if provenance:
- prefix = "{}: ".format(provenance)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}Invalid value for {} option '{}': {}"
- .format(prefix, self.OPTION_TYPE, self.name, e))
-
- if default_value is None:
- # Host architecture is not supported by the project.
- # Do not raise an error here as the user may override it.
- # If the user does not override it, an error will be raised
- # by resolve()/validate().
- default_value = arch
-
- return default_value
-
- def resolve(self):
-
- # Validate that the default machine arch reported by uname() is
- # explicitly supported by the project, only if it was not
- # overridden by user configuration or cli.
- #
- # If the value is specified on the cli or user configuration,
- # then it will already be valid.
- #
- self.validate(self.value)
diff --git a/buildstream/_options/optionbool.py b/buildstream/_options/optionbool.py
deleted file mode 100644
index 867de22df..000000000
--- a/buildstream/_options/optionbool.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .. import _yaml
-from .._exceptions import LoadError, LoadErrorReason
-from .option import Option, OPTION_SYMBOLS
-
-
-# OptionBool
-#
-# A boolean project option
-#
-class OptionBool(Option):
-
- OPTION_TYPE = 'bool'
-
- def load(self, node):
-
- super(OptionBool, self).load(node)
- _yaml.node_validate(node, OPTION_SYMBOLS + ['default'])
- self.value = _yaml.node_get(node, bool, 'default')
-
- def load_value(self, node, *, transform=None):
- if transform:
- self.set_value(transform(_yaml.node_get(node, str, self.name)))
- else:
- self.value = _yaml.node_get(node, bool, self.name)
-
- def set_value(self, value):
- if value in ('True', 'true'):
- self.value = True
- elif value in ('False', 'false'):
- self.value = False
- else:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "Invalid value for boolean option {}: {}".format(self.name, value))
-
- def get_value(self):
- if self.value:
- return "1"
- else:
- return "0"
diff --git a/buildstream/_options/optioneltmask.py b/buildstream/_options/optioneltmask.py
deleted file mode 100644
index 09c2ce8c2..000000000
--- a/buildstream/_options/optioneltmask.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .. import utils
-from .optionflags import OptionFlags
-
-
-# OptionEltMask
-#
-# A flags option which automatically only allows element
-# names as values.
-#
-class OptionEltMask(OptionFlags):
-
- OPTION_TYPE = 'element-mask'
-
- def load(self, node):
- # Ask the parent constructor to disallow value definitions,
- # we define those automatically only.
- super(OptionEltMask, self).load(node, allow_value_definitions=False)
-
- # Here we want all valid elements as possible values,
- # but we'll settle for just the relative filenames
- # of files ending with ".bst" in the project element directory
- def load_valid_values(self, node):
- values = []
- for filename in utils.list_relative_paths(self.pool.element_path):
- if filename.endswith('.bst'):
- values.append(filename)
- return values
diff --git a/buildstream/_options/optionenum.py b/buildstream/_options/optionenum.py
deleted file mode 100644
index 095b9c356..000000000
--- a/buildstream/_options/optionenum.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .. import _yaml
-from .._exceptions import LoadError, LoadErrorReason
-from .option import Option, OPTION_SYMBOLS
-
-
-# OptionEnum
-#
-# An enumeration project option
-#
-class OptionEnum(Option):
-
- OPTION_TYPE = 'enum'
-
- def load(self, node, allow_default_definition=True):
- super(OptionEnum, self).load(node)
-
- valid_symbols = OPTION_SYMBOLS + ['values']
- if allow_default_definition:
- valid_symbols += ['default']
-
- _yaml.node_validate(node, valid_symbols)
-
- self.values = _yaml.node_get(node, list, 'values', default_value=[])
- if not self.values:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: No values specified for {} option '{}'"
- .format(_yaml.node_get_provenance(node), self.OPTION_TYPE, self.name))
-
- # Allow subclass to define the default value
- self.value = self.load_default_value(node)
-
- def load_value(self, node, *, transform=None):
- self.value = _yaml.node_get(node, str, self.name)
- if transform:
- self.value = transform(self.value)
- self.validate(self.value, _yaml.node_get_provenance(node, self.name))
-
- def set_value(self, value):
- self.validate(value)
- self.value = value
-
- def get_value(self):
- return self.value
-
- def validate(self, value, provenance=None):
- if value not in self.values:
- prefix = ""
- if provenance:
- prefix = "{}: ".format(provenance)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}Invalid value for {} option '{}': {}\n"
- .format(prefix, self.OPTION_TYPE, self.name, value) +
- "Valid values: {}".format(", ".join(self.values)))
-
- def load_default_value(self, node):
- value = _yaml.node_get(node, str, 'default')
- self.validate(value, _yaml.node_get_provenance(node, 'default'))
- return value
diff --git a/buildstream/_options/optionflags.py b/buildstream/_options/optionflags.py
deleted file mode 100644
index 0271208d9..000000000
--- a/buildstream/_options/optionflags.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .. import _yaml
-from .._exceptions import LoadError, LoadErrorReason
-from .option import Option, OPTION_SYMBOLS
-
-
-# OptionFlags
-#
-# A flags project option
-#
-class OptionFlags(Option):
-
- OPTION_TYPE = 'flags'
-
- def load(self, node, allow_value_definitions=True):
- super(OptionFlags, self).load(node)
-
- valid_symbols = OPTION_SYMBOLS + ['default']
- if allow_value_definitions:
- valid_symbols += ['values']
-
- _yaml.node_validate(node, valid_symbols)
-
- # Allow subclass to define the valid values
- self.values = self.load_valid_values(node)
- if not self.values:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: No values specified for {} option '{}'"
- .format(_yaml.node_get_provenance(node), self.OPTION_TYPE, self.name))
-
- self.value = _yaml.node_get(node, list, 'default', default_value=[])
- self.validate(self.value, _yaml.node_get_provenance(node, 'default'))
-
- def load_value(self, node, *, transform=None):
- self.value = _yaml.node_get(node, list, self.name)
- if transform:
- self.value = [transform(x) for x in self.value]
- self.value = sorted(self.value)
- self.validate(self.value, _yaml.node_get_provenance(node, self.name))
-
- def set_value(self, value):
- # Strip out all whitespace, allowing: "value1, value2 , value3"
- stripped = "".join(value.split())
-
- # Get the comma separated values
- list_value = stripped.split(',')
-
- self.validate(list_value)
- self.value = sorted(list_value)
-
- def get_value(self):
- return ",".join(self.value)
-
- def validate(self, value, provenance=None):
- for flag in value:
- if flag not in self.values:
- prefix = ""
- if provenance:
- prefix = "{}: ".format(provenance)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}Invalid value for flags option '{}': {}\n"
- .format(prefix, self.name, value) +
- "Valid values: {}".format(", ".join(self.values)))
-
- def load_valid_values(self, node):
- # Allow the more descriptive error to raise when no values
- # exist rather than bailing out here (by specifying default_value)
- return _yaml.node_get(node, list, 'values', default_value=[])
diff --git a/buildstream/_options/optionos.py b/buildstream/_options/optionos.py
deleted file mode 100644
index 2d46b70ba..000000000
--- a/buildstream/_options/optionos.py
+++ /dev/null
@@ -1,41 +0,0 @@
-
-#
-# 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:
-# Raoul Hidalgo Charman <raoul.hidalgocharman@codethink.co.uk>
-
-import platform
-from .optionenum import OptionEnum
-
-
-# OptionOS
-#
-class OptionOS(OptionEnum):
-
- OPTION_TYPE = 'os'
-
- def load(self, node):
- super(OptionOS, self).load(node, allow_default_definition=False)
-
- def load_default_value(self, node):
- return platform.uname().system
-
- def resolve(self):
-
- # Validate that the default OS reported by uname() is explicitly
- # supported by the project, if not overridden by user config or cli.
- self.validate(self.value)
diff --git a/buildstream/_options/optionpool.py b/buildstream/_options/optionpool.py
deleted file mode 100644
index de3af3e15..000000000
--- a/buildstream/_options/optionpool.py
+++ /dev/null
@@ -1,295 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-#
-
-import jinja2
-
-from .. import _yaml
-from .._exceptions import LoadError, LoadErrorReason
-from .optionbool import OptionBool
-from .optionenum import OptionEnum
-from .optionflags import OptionFlags
-from .optioneltmask import OptionEltMask
-from .optionarch import OptionArch
-from .optionos import OptionOS
-
-
-_OPTION_TYPES = {
- OptionBool.OPTION_TYPE: OptionBool,
- OptionEnum.OPTION_TYPE: OptionEnum,
- OptionFlags.OPTION_TYPE: OptionFlags,
- OptionEltMask.OPTION_TYPE: OptionEltMask,
- OptionArch.OPTION_TYPE: OptionArch,
- OptionOS.OPTION_TYPE: OptionOS,
-}
-
-
-class OptionPool():
-
- def __init__(self, element_path):
- # We hold on to the element path for the sake of OptionEltMask
- self.element_path = element_path
-
- #
- # Private members
- #
- self._options = {} # The Options
- self._variables = None # The Options resolved into typed variables
-
- # jinja2 environment, with default globals cleared out of the way
- self._environment = jinja2.Environment(undefined=jinja2.StrictUndefined)
- self._environment.globals = []
-
- # load()
- #
- # Loads the options described in the project.conf
- #
- # Args:
- # node (dict): The loaded YAML options
- #
- def load(self, options):
-
- for option_name, option_definition in _yaml.node_items(options):
-
- # Assert that the option name is a valid symbol
- p = _yaml.node_get_provenance(options, option_name)
- _yaml.assert_symbol_name(p, option_name, "option name", allow_dashes=False)
-
- opt_type_name = _yaml.node_get(option_definition, str, 'type')
- try:
- opt_type = _OPTION_TYPES[opt_type_name]
- except KeyError:
- p = _yaml.node_get_provenance(option_definition, 'type')
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Invalid option type '{}'".format(p, opt_type_name))
-
- option = opt_type(option_name, option_definition, self)
- self._options[option_name] = option
-
- # load_yaml_values()
- #
- # Loads the option values specified in a key/value
- # dictionary loaded from YAML
- #
- # Args:
- # node (dict): The loaded YAML options
- #
- def load_yaml_values(self, node, *, transform=None):
- for option_name in _yaml.node_keys(node):
- try:
- option = self._options[option_name]
- except KeyError as e:
- p = _yaml.node_get_provenance(node, option_name)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Unknown option '{}' specified"
- .format(p, option_name)) from e
- option.load_value(node, transform=transform)
-
- # load_cli_values()
- #
- # Loads the option values specified in a list of tuples
- # collected from the command line
- #
- # Args:
- # cli_options (list): A list of (str, str) tuples
- # ignore_unknown (bool): Whether to silently ignore unknown options.
- #
- def load_cli_values(self, cli_options, *, ignore_unknown=False):
- for option_name, option_value in cli_options:
- try:
- option = self._options[option_name]
- except KeyError as e:
- if not ignore_unknown:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "Unknown option '{}' specified on the command line"
- .format(option_name)) from e
- else:
- option.set_value(option_value)
-
- # resolve()
- #
- # Resolves the loaded options, this is just a step which must be
- # performed after loading all options and their values, and before
- # ever trying to evaluate an expression
- #
- def resolve(self):
- self._variables = {}
- for option_name, option in self._options.items():
- # Delegate one more method for options to
- # do some last minute validation once any
- # overrides have been performed.
- #
- option.resolve()
-
- self._variables[option_name] = option.value
-
- # export_variables()
- #
- # Exports the option values which are declared
- # to be exported, to the passed dictionary.
- #
- # Variable values are exported in string form
- #
- # Args:
- # variables (dict): A variables dictionary
- #
- def export_variables(self, variables):
- for _, option in self._options.items():
- if option.variable:
- _yaml.node_set(variables, option.variable, option.get_value())
-
- # printable_variables()
- #
- # Exports all option names and string values
- # to the passed dictionary in alphabetical order.
- #
- # Args:
- # variables (dict): A variables dictionary
- #
- def printable_variables(self, variables):
- for key in sorted(self._options):
- variables[key] = self._options[key].get_value()
-
- # process_node()
- #
- # Args:
- # node (node): A YAML Loaded dictionary
- #
- def process_node(self, node):
-
- # A conditional will result in composition, which can
- # in turn add new conditionals to the root.
- #
- # Keep processing conditionals on the root node until
- # all directly nested conditionals are resolved.
- #
- while self._process_one_node(node):
- pass
-
- # Now recurse into nested dictionaries and lists
- # and process any indirectly nested conditionals.
- #
- for _, value in _yaml.node_items(node):
- if _yaml.is_node(value):
- self.process_node(value)
- elif isinstance(value, list):
- self._process_list(value)
-
- #######################################################
- # Private Methods #
- #######################################################
-
- # _evaluate()
- #
- # Evaluates a jinja2 style expression with the loaded options in context.
- #
- # Args:
- # expression (str): The jinja2 style expression
- #
- # Returns:
- # (bool): Whether the expression resolved to a truthy value or a falsy one.
- #
- # Raises:
- # LoadError: If the expression failed to resolve for any reason
- #
- def _evaluate(self, expression):
-
- #
- # Variables must be resolved at this point.
- #
- try:
- template_string = "{{% if {} %}} True {{% else %}} False {{% endif %}}".format(expression)
- template = self._environment.from_string(template_string)
- context = template.new_context(self._variables, shared=True)
- result = template.root_render_func(context)
- evaluated = jinja2.utils.concat(result)
- val = evaluated.strip()
-
- if val == "True":
- return True
- elif val == "False":
- return False
- else: # pragma: nocover
- raise LoadError(LoadErrorReason.EXPRESSION_FAILED,
- "Failed to evaluate expression: {}".format(expression))
- except jinja2.exceptions.TemplateError as e:
- raise LoadError(LoadErrorReason.EXPRESSION_FAILED,
- "Failed to evaluate expression ({}): {}".format(expression, e))
-
- # Recursion assistent for lists, in case there
- # are lists of lists.
- #
- def _process_list(self, values):
- for value in values:
- if _yaml.is_node(value):
- self.process_node(value)
- elif isinstance(value, list):
- self._process_list(value)
-
- # Process a single conditional, resulting in composition
- # at the root level on the passed node
- #
- # Return true if a conditional was processed.
- #
- def _process_one_node(self, node):
- conditions = _yaml.node_get(node, list, '(?)', default_value=None)
- assertion = _yaml.node_get(node, str, '(!)', default_value=None)
-
- # Process assersions first, we want to abort on the first encountered
- # assertion in a given dictionary, and not lose an assertion due to
- # it being overwritten by a later assertion which might also trigger.
- if assertion is not None:
- p = _yaml.node_get_provenance(node, '(!)')
- raise LoadError(LoadErrorReason.USER_ASSERTION,
- "{}: {}".format(p, assertion.strip()))
-
- if conditions is not None:
-
- # Collect provenance first, we need to delete the (?) key
- # before any composition occurs.
- provenance = [
- _yaml.node_get_provenance(node, '(?)', indices=[i])
- for i in range(len(conditions))
- ]
- _yaml.node_del(node, '(?)')
-
- for condition, p in zip(conditions, provenance):
- tuples = list(_yaml.node_items(condition))
- if len(tuples) > 1:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Conditional statement has more than one key".format(p))
-
- expression, value = tuples[0]
- try:
- apply_fragment = self._evaluate(expression)
- except LoadError as e:
- # Prepend the provenance of the error
- raise LoadError(e.reason, "{}: {}".format(p, e)) from e
-
- if not _yaml.is_node(value):
- raise LoadError(LoadErrorReason.ILLEGAL_COMPOSITE,
- "{}: Only values of type 'dict' can be composed.".format(p))
-
- # Apply the yaml fragment if its condition evaluates to true
- if apply_fragment:
- _yaml.composite(node, value)
-
- return True
-
- return False
diff --git a/buildstream/_pipeline.py b/buildstream/_pipeline.py
deleted file mode 100644
index c176b82f6..000000000
--- a/buildstream/_pipeline.py
+++ /dev/null
@@ -1,516 +0,0 @@
-#
-# Copyright (C) 2016-2018 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-import os
-import itertools
-from operator import itemgetter
-from collections import OrderedDict
-
-from pyroaring import BitMap # pylint: disable=no-name-in-module
-
-from ._exceptions import PipelineError
-from ._message import Message, MessageType
-from ._profile import Topics, PROFILER
-from . import Scope, Consistency
-from ._project import ProjectRefStorage
-
-
-# PipelineSelection()
-#
-# Defines the kind of pipeline selection to make when the pipeline
-# is provided a list of targets, for whichever purpose.
-#
-# These values correspond to the CLI `--deps` arguments for convenience.
-#
-class PipelineSelection():
-
- # Select only the target elements in the associated targets
- NONE = 'none'
-
- # As NONE, but redirect elements that are capable of it
- REDIRECT = 'redirect'
-
- # Select elements which must be built for the associated targets to be built
- PLAN = 'plan'
-
- # All dependencies of all targets, including the targets
- ALL = 'all'
-
- # All direct build dependencies and their recursive runtime dependencies,
- # excluding the targets
- BUILD = 'build'
-
- # All direct runtime dependencies and their recursive runtime dependencies,
- # including the targets
- RUN = 'run'
-
-
-# Pipeline()
-#
-# Args:
-# project (Project): The Project object
-# context (Context): The Context object
-# artifacts (Context): The ArtifactCache object
-#
-class Pipeline():
-
- def __init__(self, context, project, artifacts):
-
- self._context = context # The Context
- self._project = project # The toplevel project
-
- #
- # Private members
- #
- self._artifacts = artifacts
-
- # load()
- #
- # Loads elements from target names.
- #
- # This function is called with a list of lists, such that multiple
- # target groups may be specified. Element names specified in `targets`
- # are allowed to be redundant.
- #
- # Args:
- # target_groups (list of lists): Groups of toplevel targets to load
- # fetch_subprojects (bool): Whether we should fetch subprojects as a part of the
- # loading process, if they are not yet locally cached
- # rewritable (bool): Whether the loaded files should be rewritable
- # this is a bit more expensive due to deep copies
- #
- # Returns:
- # (tuple of lists): A tuple of grouped Element objects corresponding to target_groups
- #
- def load(self, target_groups, *,
- fetch_subprojects=True,
- rewritable=False):
-
- # First concatenate all the lists for the loader's sake
- targets = list(itertools.chain(*target_groups))
-
- with PROFILER.profile(Topics.LOAD_PIPELINE, "_".join(t.replace(os.sep, "-") for t in targets)):
- elements = self._project.load_elements(targets,
- rewritable=rewritable,
- fetch_subprojects=fetch_subprojects)
-
- # Now create element groups to match the input target groups
- elt_iter = iter(elements)
- element_groups = [
- [next(elt_iter) for i in range(len(group))]
- for group in target_groups
- ]
-
- return tuple(element_groups)
-
- # resolve_elements()
- #
- # Resolve element state and cache keys.
- #
- # Args:
- # targets (list of Element): The list of toplevel element targets
- #
- def resolve_elements(self, targets):
- with self._context.timed_activity("Resolving cached state", silent_nested=True):
- for element in self.dependencies(targets, Scope.ALL):
-
- # Preflight
- element._preflight()
-
- # Determine initial element state.
- element._update_state()
-
- # dependencies()
- #
- # Generator function to iterate over elements and optionally
- # also iterate over sources.
- #
- # Args:
- # targets (list of Element): The target Elements to loop over
- # scope (Scope): The scope to iterate over
- # recurse (bool): Whether to recurse into dependencies
- #
- def dependencies(self, targets, scope, *, recurse=True):
- # Keep track of 'visited' in this scope, so that all targets
- # share the same context.
- visited = (BitMap(), BitMap())
-
- for target in targets:
- for element in target.dependencies(scope, recurse=recurse, visited=visited):
- yield element
-
- # plan()
- #
- # Generator function to iterate over only the elements
- # which are required to build the pipeline target, omitting
- # cached elements. The elements are yielded in a depth sorted
- # ordering for optimal build plans
- #
- # Args:
- # elements (list of Element): List of target elements to plan
- #
- # Returns:
- # (list of Element): A depth sorted list of the build plan
- #
- def plan(self, elements):
- # Keep locally cached elements in the plan if remote artifact cache is used
- # to allow pulling artifact with strict cache key, if available.
- plan_cached = not self._context.get_strict() and self._artifacts.has_fetch_remotes()
-
- return _Planner().plan(elements, plan_cached)
-
- # get_selection()
- #
- # Gets a full list of elements based on a toplevel
- # list of element targets
- #
- # Args:
- # targets (list of Element): The target Elements
- # mode (PipelineSelection): The PipelineSelection mode
- #
- # Various commands define a --deps option to specify what elements to
- # use in the result, this function reports a list that is appropriate for
- # the selected option.
- #
- def get_selection(self, targets, mode, *, silent=True):
-
- elements = None
- if mode == PipelineSelection.NONE:
- elements = targets
- elif mode == PipelineSelection.REDIRECT:
- # Redirect and log if permitted
- elements = []
- for t in targets:
- new_elm = t._get_source_element()
- if new_elm != t and not silent:
- self._message(MessageType.INFO, "Element '{}' redirected to '{}'"
- .format(t.name, new_elm.name))
- if new_elm not in elements:
- elements.append(new_elm)
- elif mode == PipelineSelection.PLAN:
- elements = self.plan(targets)
- else:
- if mode == PipelineSelection.ALL:
- scope = Scope.ALL
- elif mode == PipelineSelection.BUILD:
- scope = Scope.BUILD
- elif mode == PipelineSelection.RUN:
- scope = Scope.RUN
-
- elements = list(self.dependencies(targets, scope))
-
- return elements
-
- # except_elements():
- #
- # Return what we are left with after the intersection between
- # excepted and target elements and their unique dependencies is
- # gone.
- #
- # Args:
- # targets (list of Element): List of toplevel targetted elements
- # elements (list of Element): The list to remove elements from
- # except_targets (list of Element): List of toplevel except targets
- #
- # Returns:
- # (list of Element): The elements list with the intersected
- # exceptions removed
- #
- def except_elements(self, targets, elements, except_targets):
- if not except_targets:
- return elements
-
- targeted = list(self.dependencies(targets, Scope.ALL))
- visited = []
-
- def find_intersection(element):
- if element in visited:
- return
- visited.append(element)
-
- # Intersection elements are those that are also in
- # 'targeted', as long as we don't recurse into them.
- if element in targeted:
- yield element
- else:
- for dep in element.dependencies(Scope.ALL, recurse=False):
- yield from find_intersection(dep)
-
- # Build a list of 'intersection' elements, i.e. the set of
- # elements that lie on the border closest to excepted elements
- # between excepted and target elements.
- intersection = list(itertools.chain.from_iterable(
- find_intersection(element) for element in except_targets
- ))
-
- # Now use this set of elements to traverse the targeted
- # elements, except 'intersection' elements and their unique
- # dependencies.
- queue = []
- visited = []
-
- queue.extend(targets)
- while queue:
- element = queue.pop()
- if element in visited or element in intersection:
- continue
- visited.append(element)
-
- queue.extend(element.dependencies(Scope.ALL, recurse=False))
-
- # That looks like a lot, but overall we only traverse (part
- # of) the graph twice. This could be reduced to once if we
- # kept track of parent elements, but is probably not
- # significant.
-
- # Ensure that we return elements in the same order they were
- # in before.
- return [element for element in elements if element in visited]
-
- # targets_include()
- #
- # Checks whether the given targets are, or depend on some elements
- #
- # Args:
- # targets (list of Element): A list of targets
- # elements (list of Element): List of elements to check
- #
- # Returns:
- # (bool): True if all of `elements` are the `targets`, or are
- # somehow depended on by `targets`.
- #
- def targets_include(self, targets, elements):
- target_element_set = set(self.dependencies(targets, Scope.ALL))
- element_set = set(elements)
- return element_set.issubset(target_element_set)
-
- # subtract_elements()
- #
- # Subtract a subset of elements
- #
- # Args:
- # elements (list of Element): The element list
- # subtract (list of Element): List of elements to subtract from elements
- #
- # Returns:
- # (list): The original elements list, with elements in subtract removed
- #
- def subtract_elements(self, elements, subtract):
- subtract_set = set(subtract)
- return [
- e for e in elements
- if e not in subtract_set
- ]
-
- # track_cross_junction_filter()
- #
- # Filters out elements which are across junction boundaries,
- # otherwise asserts that there are no such elements.
- #
- # This is currently assumed to be only relevant for element
- # lists targetted at tracking.
- #
- # Args:
- # project (Project): Project used for cross_junction filtering.
- # All elements are expected to belong to that project.
- # elements (list of Element): The list of elements to filter
- # cross_junction_requested (bool): Whether the user requested
- # cross junction tracking
- #
- # Returns:
- # (list of Element): The filtered or asserted result
- #
- def track_cross_junction_filter(self, project, elements, cross_junction_requested):
- # Filter out cross junctioned elements
- if not cross_junction_requested:
- elements = self._filter_cross_junctions(project, elements)
- self._assert_junction_tracking(elements)
-
- return elements
-
- # assert_consistent()
- #
- # Asserts that the given list of elements are in a consistent state, that
- # is to say that all sources are consistent and can at least be fetched.
- #
- # Consequently it also means that cache keys can be resolved.
- #
- def assert_consistent(self, elements):
- inconsistent = []
- inconsistent_workspaced = []
- with self._context.timed_activity("Checking sources"):
- for element in elements:
- if element._get_consistency() == Consistency.INCONSISTENT:
- if element._get_workspace():
- inconsistent_workspaced.append(element)
- else:
- inconsistent.append(element)
-
- if inconsistent:
- detail = "Exact versions are missing for the following elements:\n\n"
- for element in inconsistent:
- detail += " Element: {} is inconsistent\n".format(element._get_full_name())
- for source in element.sources():
- if source._get_consistency() == Consistency.INCONSISTENT:
- detail += " {} is missing ref\n".format(source)
- detail += '\n'
- detail += "Try tracking these elements first with `bst source track`\n"
-
- raise PipelineError("Inconsistent pipeline", detail=detail, reason="inconsistent-pipeline")
-
- if inconsistent_workspaced:
- detail = "Some workspaces do not exist but are not closed\n" + \
- "Try closing them with `bst workspace close`\n\n"
- for element in inconsistent_workspaced:
- detail += " " + element._get_full_name() + "\n"
- raise PipelineError("Inconsistent pipeline", detail=detail, reason="inconsistent-pipeline-workspaced")
-
- # assert_sources_cached()
- #
- # Asserts that sources for the given list of elements are cached.
- #
- # Args:
- # elements (list): The list of elements
- #
- def assert_sources_cached(self, elements):
- uncached = []
- with self._context.timed_activity("Checking sources"):
- for element in elements:
- if element._get_consistency() < Consistency.CACHED and \
- not element._source_cached():
- uncached.append(element)
-
- if uncached:
- detail = "Sources are not cached for the following elements:\n\n"
- for element in uncached:
- detail += " Following sources for element: {} are not cached:\n".format(element._get_full_name())
- for source in element.sources():
- if source._get_consistency() < Consistency.CACHED:
- detail += " {}\n".format(source)
- detail += '\n'
- detail += "Try fetching these elements first with `bst source fetch`,\n" + \
- "or run this command with `--fetch` option\n"
-
- raise PipelineError("Uncached sources", detail=detail, reason="uncached-sources")
-
- #############################################################
- # Private Methods #
- #############################################################
-
- # _filter_cross_junction()
- #
- # Filters out cross junction elements from the elements
- #
- # Args:
- # project (Project): The project on which elements are allowed
- # elements (list of Element): The list of elements to be tracked
- #
- # Returns:
- # (list): A filtered list of `elements` which does
- # not contain any cross junction elements.
- #
- def _filter_cross_junctions(self, project, elements):
- return [
- element for element in elements
- if element._get_project() is project
- ]
-
- # _assert_junction_tracking()
- #
- # Raises an error if tracking is attempted on junctioned elements and
- # a project.refs file is not enabled for the toplevel project.
- #
- # Args:
- # elements (list of Element): The list of elements to be tracked
- #
- def _assert_junction_tracking(self, elements):
-
- # We can track anything if the toplevel project uses project.refs
- #
- if self._project.ref_storage == ProjectRefStorage.PROJECT_REFS:
- return
-
- # Ideally, we would want to report every cross junction element but not
- # their dependencies, unless those cross junction elements dependencies
- # were also explicitly requested on the command line.
- #
- # But this is too hard, lets shoot for a simple error.
- for element in elements:
- element_project = element._get_project()
- if element_project is not self._project:
- detail = "Requested to track sources across junction boundaries\n" + \
- "in a project which does not use project.refs ref-storage."
-
- raise PipelineError("Untrackable sources", detail=detail, reason="untrackable-sources")
-
- # _message()
- #
- # Local message propagator
- #
- def _message(self, message_type, message, **kwargs):
- args = dict(kwargs)
- self._context.message(
- Message(None, message_type, message, **args))
-
-
-# _Planner()
-#
-# An internal object used for constructing build plan
-# from a given resolved toplevel element, while considering what
-# parts need to be built depending on build only dependencies
-# being cached, and depth sorting for more efficient processing.
-#
-class _Planner():
- def __init__(self):
- self.depth_map = OrderedDict()
- self.visiting_elements = set()
-
- # Here we want to traverse the same element more than once when
- # it is reachable from multiple places, with the interest of finding
- # the deepest occurance of every element
- def plan_element(self, element, depth):
- if element in self.visiting_elements:
- # circular dependency, already being processed
- return
-
- prev_depth = self.depth_map.get(element)
- if prev_depth is not None and prev_depth >= depth:
- # element and dependencies already processed at equal or greater depth
- return
-
- self.visiting_elements.add(element)
- for dep in element.dependencies(Scope.RUN, recurse=False):
- self.plan_element(dep, depth)
-
- # Dont try to plan builds of elements that are cached already
- if not element._cached_success():
- for dep in element.dependencies(Scope.BUILD, recurse=False):
- self.plan_element(dep, depth + 1)
-
- self.depth_map[element] = depth
- self.visiting_elements.remove(element)
-
- def plan(self, roots, plan_cached):
- for root in roots:
- self.plan_element(root, 0)
-
- depth_sorted = sorted(self.depth_map.items(), key=itemgetter(1), reverse=True)
- return [item[0] for item in depth_sorted if plan_cached or not item[0]._cached_success()]
diff --git a/buildstream/_platform/__init__.py b/buildstream/_platform/__init__.py
deleted file mode 100644
index 29a29894b..000000000
--- a/buildstream/_platform/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# 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:
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-from .platform import Platform
diff --git a/buildstream/_platform/darwin.py b/buildstream/_platform/darwin.py
deleted file mode 100644
index 8e08685ec..000000000
--- a/buildstream/_platform/darwin.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Copyright (C) 2017 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/>.
-
-import os
-
-from ..sandbox import SandboxDummy
-
-from .platform import Platform
-
-
-class Darwin(Platform):
-
- # This value comes from OPEN_MAX in syslimits.h
- OPEN_MAX = 10240
-
- def create_sandbox(self, *args, **kwargs):
- kwargs['dummy_reason'] = \
- "OSXFUSE is not supported and there are no supported sandbox " + \
- "technologies for MacOS at this time"
- return SandboxDummy(*args, **kwargs)
-
- def check_sandbox_config(self, config):
- # Accept all sandbox configs as it's irrelevant with the dummy sandbox (no Sandbox.run).
- return True
-
- def get_cpu_count(self, cap=None):
- cpu_count = os.cpu_count()
- if cap is None:
- return cpu_count
- else:
- return min(cpu_count, cap)
-
- def set_resource_limits(self, soft_limit=OPEN_MAX, hard_limit=None):
- super().set_resource_limits(soft_limit)
diff --git a/buildstream/_platform/linux.py b/buildstream/_platform/linux.py
deleted file mode 100644
index e4ce02572..000000000
--- a/buildstream/_platform/linux.py
+++ /dev/null
@@ -1,150 +0,0 @@
-#
-# 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:
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-import os
-import subprocess
-
-from .. import _site
-from .. import utils
-from ..sandbox import SandboxDummy
-
-from .platform import Platform
-from .._exceptions import PlatformError
-
-
-class Linux(Platform):
-
- def __init__(self):
-
- super().__init__()
-
- self._uid = os.geteuid()
- self._gid = os.getegid()
-
- self._have_fuse = os.path.exists("/dev/fuse")
-
- bwrap_version = _site.get_bwrap_version()
-
- if bwrap_version is None:
- self._bwrap_exists = False
- self._have_good_bwrap = False
- self._die_with_parent_available = False
- self._json_status_available = False
- else:
- self._bwrap_exists = True
- self._have_good_bwrap = (0, 1, 2) <= bwrap_version
- self._die_with_parent_available = (0, 1, 8) <= bwrap_version
- self._json_status_available = (0, 3, 2) <= bwrap_version
-
- self._local_sandbox_available = self._have_fuse and self._have_good_bwrap
-
- if self._local_sandbox_available:
- self._user_ns_available = self._check_user_ns_available()
- else:
- self._user_ns_available = False
-
- # Set linux32 option
- self._linux32 = False
-
- def create_sandbox(self, *args, **kwargs):
- if not self._local_sandbox_available:
- return self._create_dummy_sandbox(*args, **kwargs)
- else:
- return self._create_bwrap_sandbox(*args, **kwargs)
-
- def check_sandbox_config(self, config):
- if not self._local_sandbox_available:
- # Accept all sandbox configs as it's irrelevant with the dummy sandbox (no Sandbox.run).
- return True
-
- if self._user_ns_available:
- # User namespace support allows arbitrary build UID/GID settings.
- pass
- elif (config.build_uid != self._uid or config.build_gid != self._gid):
- # Without user namespace support, the UID/GID in the sandbox
- # will match the host UID/GID.
- return False
-
- # We can't do builds for another host or architecture except x86-32 on
- # x86-64
- host_os = self.get_host_os()
- host_arch = self.get_host_arch()
- if config.build_os != host_os:
- raise PlatformError("Configured and host OS don't match.")
- elif config.build_arch != host_arch:
- # We can use linux32 for building 32bit on 64bit machines
- if (host_os == "Linux" and
- ((config.build_arch == "x86-32" and host_arch == "x86-64") or
- (config.build_arch == "aarch32" and host_arch == "aarch64"))):
- # check linux32 is available
- try:
- utils.get_host_tool('linux32')
- self._linux32 = True
- except utils.ProgramNotFoundError:
- pass
- else:
- raise PlatformError("Configured architecture and host architecture don't match.")
-
- return True
-
- ################################################
- # Private Methods #
- ################################################
-
- def _create_dummy_sandbox(self, *args, **kwargs):
- reasons = []
- if not self._have_fuse:
- reasons.append("FUSE is unavailable")
- if not self._have_good_bwrap:
- if self._bwrap_exists:
- reasons.append("`bwrap` is too old (bst needs at least 0.1.2)")
- else:
- reasons.append("`bwrap` executable not found")
-
- kwargs['dummy_reason'] = " and ".join(reasons)
- return SandboxDummy(*args, **kwargs)
-
- def _create_bwrap_sandbox(self, *args, **kwargs):
- from ..sandbox._sandboxbwrap import SandboxBwrap
- # Inform the bubblewrap sandbox as to whether it can use user namespaces or not
- kwargs['user_ns_available'] = self._user_ns_available
- kwargs['die_with_parent_available'] = self._die_with_parent_available
- kwargs['json_status_available'] = self._json_status_available
- kwargs['linux32'] = self._linux32
- return SandboxBwrap(*args, **kwargs)
-
- def _check_user_ns_available(self):
- # Here, lets check if bwrap is able to create user namespaces,
- # issue a warning if it's not available, and save the state
- # locally so that we can inform the sandbox to not try it
- # later on.
- bwrap = utils.get_host_tool('bwrap')
- whoami = utils.get_host_tool('whoami')
- try:
- output = subprocess.check_output([
- bwrap,
- '--ro-bind', '/', '/',
- '--unshare-user',
- '--uid', '0', '--gid', '0',
- whoami,
- ], universal_newlines=True).strip()
- except subprocess.CalledProcessError:
- output = ''
-
- return output == 'root'
diff --git a/buildstream/_platform/platform.py b/buildstream/_platform/platform.py
deleted file mode 100644
index dba60ddca..000000000
--- a/buildstream/_platform/platform.py
+++ /dev/null
@@ -1,164 +0,0 @@
-#
-# 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:
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-import os
-import platform
-import sys
-import resource
-
-from .._exceptions import PlatformError, ImplError
-
-
-class Platform():
- _instance = None
-
- # Platform()
- #
- # A class to manage platform-specific details. Currently holds the
- # sandbox factory as well as platform helpers.
- #
- def __init__(self):
- self.set_resource_limits()
-
- @classmethod
- def _create_instance(cls):
- # Meant for testing purposes and therefore hidden in the
- # deepest corners of the source code. Try not to abuse this,
- # please?
- if os.getenv('BST_FORCE_BACKEND'):
- backend = os.getenv('BST_FORCE_BACKEND')
- elif sys.platform.startswith('linux'):
- backend = 'linux'
- elif sys.platform.startswith('darwin'):
- backend = 'darwin'
- else:
- backend = 'unix'
-
- if backend == 'linux':
- from .linux import Linux as PlatformImpl # pylint: disable=cyclic-import
- elif backend == 'darwin':
- from .darwin import Darwin as PlatformImpl # pylint: disable=cyclic-import
- elif backend == 'unix':
- from .unix import Unix as PlatformImpl # pylint: disable=cyclic-import
- else:
- raise PlatformError("No such platform: '{}'".format(backend))
-
- cls._instance = PlatformImpl()
-
- @classmethod
- def get_platform(cls):
- if not cls._instance:
- cls._create_instance()
- return cls._instance
-
- def get_cpu_count(self, cap=None):
- cpu_count = len(os.sched_getaffinity(0))
- if cap is None:
- return cpu_count
- else:
- return min(cpu_count, cap)
-
- @staticmethod
- def get_host_os():
- return platform.uname().system
-
- # canonicalize_arch():
- #
- # This returns the canonical, OS-independent architecture name
- # or raises a PlatformError if the architecture is unknown.
- #
- @staticmethod
- def canonicalize_arch(arch):
- # Note that these are all expected to be lowercase, as we want a
- # case-insensitive lookup. Windows can report its arch in ALLCAPS.
- aliases = {
- "aarch32": "aarch32",
- "aarch64": "aarch64",
- "aarch64-be": "aarch64-be",
- "amd64": "x86-64",
- "arm": "aarch32",
- "armv8l": "aarch64",
- "armv8b": "aarch64-be",
- "i386": "x86-32",
- "i486": "x86-32",
- "i586": "x86-32",
- "i686": "x86-32",
- "power-isa-be": "power-isa-be",
- "power-isa-le": "power-isa-le",
- "ppc64": "power-isa-be",
- "ppc64le": "power-isa-le",
- "sparc": "sparc-v9",
- "sparc64": "sparc-v9",
- "sparc-v9": "sparc-v9",
- "x86-32": "x86-32",
- "x86-64": "x86-64"
- }
-
- try:
- return aliases[arch.replace('_', '-').lower()]
- except KeyError:
- raise PlatformError("Unknown architecture: {}".format(arch))
-
- # get_host_arch():
- #
- # This returns the architecture of the host machine. The possible values
- # map from uname -m in order to be a OS independent list.
- #
- # Returns:
- # (string): String representing the architecture
- @staticmethod
- def get_host_arch():
- # get the hardware identifier from uname
- uname_machine = platform.uname().machine
- return Platform.canonicalize_arch(uname_machine)
-
- ##################################################################
- # Sandbox functions #
- ##################################################################
-
- # create_sandbox():
- #
- # Create a build sandbox suitable for the environment
- #
- # Args:
- # args (dict): The arguments to pass to the sandbox constructor
- # kwargs (file): The keyword arguments to pass to the sandbox constructor
- #
- # Returns:
- # (Sandbox) A sandbox
- #
- def create_sandbox(self, *args, **kwargs):
- raise ImplError("Platform {platform} does not implement create_sandbox()"
- .format(platform=type(self).__name__))
-
- def check_sandbox_config(self, config):
- raise ImplError("Platform {platform} does not implement check_sandbox_config()"
- .format(platform=type(self).__name__))
-
- def set_resource_limits(self, soft_limit=None, hard_limit=None):
- # Need to set resources for _frontend/app.py as this is dependent on the platform
- # SafeHardlinks FUSE needs to hold file descriptors for all processes in the sandbox.
- # Avoid hitting the limit too quickly.
- limits = resource.getrlimit(resource.RLIMIT_NOFILE)
- if limits[0] != limits[1]:
- if soft_limit is None:
- soft_limit = limits[1]
- if hard_limit is None:
- hard_limit = limits[1]
- resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit))
diff --git a/buildstream/_platform/unix.py b/buildstream/_platform/unix.py
deleted file mode 100644
index d04b0712c..000000000
--- a/buildstream/_platform/unix.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# 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:
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-import os
-
-from .._exceptions import PlatformError
-
-from .platform import Platform
-
-
-class Unix(Platform):
-
- def __init__(self):
-
- super().__init__()
-
- self._uid = os.geteuid()
- self._gid = os.getegid()
-
- # Not necessarily 100% reliable, but we want to fail early.
- if self._uid != 0:
- raise PlatformError("Root privileges are required to run without bubblewrap.")
-
- def create_sandbox(self, *args, **kwargs):
- from ..sandbox._sandboxchroot import SandboxChroot
- return SandboxChroot(*args, **kwargs)
-
- def check_sandbox_config(self, config):
- # With the chroot sandbox, the UID/GID in the sandbox
- # will match the host UID/GID (typically 0/0).
- if config.build_uid != self._uid or config.build_gid != self._gid:
- return False
-
- # Check host os and architecture match
- if config.build_os != self.get_host_os():
- raise PlatformError("Configured and host OS don't match.")
- elif config.build_arch != self.get_host_arch():
- raise PlatformError("Configured and host architecture don't match.")
-
- return True
diff --git a/buildstream/_plugincontext.py b/buildstream/_plugincontext.py
deleted file mode 100644
index 7a5407cf6..000000000
--- a/buildstream/_plugincontext.py
+++ /dev/null
@@ -1,239 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-import os
-import inspect
-
-from ._exceptions import PluginError, LoadError, LoadErrorReason
-from . import utils
-from . import _yaml
-
-
-# A Context for loading plugin types
-#
-# Args:
-# plugin_base (PluginBase): The main PluginBase object to work with
-# base_type (type): A base object type for this context
-# site_plugin_path (str): Path to where buildstream keeps plugins
-# plugin_origins (list): Data used to search for plugins
-#
-# Since multiple pipelines can be processed recursively
-# within the same interpretor, it's important that we have
-# one context associated to the processing of a given pipeline,
-# this way sources and element types which are particular to
-# a given BuildStream project are isolated to their respective
-# Pipelines.
-#
-class PluginContext():
-
- def __init__(self, plugin_base, base_type, site_plugin_path, *,
- plugin_origins=None, dependencies=None,
- format_versions={}):
-
- # The plugin kinds which were loaded
- self.loaded_dependencies = []
-
- #
- # Private members
- #
- self._dependencies = dependencies
- self._base_type = base_type # The base class plugins derive from
- self._types = {} # Plugin type lookup table by kind
- self._plugin_origins = plugin_origins or []
-
- # The PluginSource object
- self._plugin_base = plugin_base
- self._site_source = plugin_base.make_plugin_source(searchpath=site_plugin_path)
- self._alternate_sources = {}
- self._format_versions = format_versions
-
- # lookup():
- #
- # Fetches a type loaded from a plugin in this plugin context
- #
- # Args:
- # kind (str): The kind of Plugin to create
- #
- # Returns: the type associated with the given kind
- #
- # Raises: PluginError
- #
- def lookup(self, kind):
- return self._ensure_plugin(kind)
-
- def _get_local_plugin_source(self, path):
- if ('local', path) not in self._alternate_sources:
- # key by a tuple to avoid collision
- source = self._plugin_base.make_plugin_source(searchpath=[path])
- # Ensure that sources never get garbage collected,
- # as they'll take the plugins with them.
- self._alternate_sources[('local', path)] = source
- else:
- source = self._alternate_sources[('local', path)]
- return source
-
- def _get_pip_plugin_source(self, package_name, kind):
- defaults = None
- if ('pip', package_name) not in self._alternate_sources:
- import pkg_resources
- # key by a tuple to avoid collision
- try:
- package = pkg_resources.get_entry_info(package_name,
- 'buildstream.plugins',
- kind)
- except pkg_resources.DistributionNotFound as e:
- raise PluginError("Failed to load {} plugin '{}': {}"
- .format(self._base_type.__name__, kind, e)) from e
-
- if package is None:
- raise PluginError("Pip package {} does not contain a plugin named '{}'"
- .format(package_name, kind))
-
- location = package.dist.get_resource_filename(
- pkg_resources._manager,
- package.module_name.replace('.', os.sep) + '.py'
- )
-
- # Also load the defaults - required since setuptools
- # may need to extract the file.
- try:
- defaults = package.dist.get_resource_filename(
- pkg_resources._manager,
- package.module_name.replace('.', os.sep) + '.yaml'
- )
- except KeyError:
- # The plugin didn't have an accompanying YAML file
- defaults = None
-
- source = self._plugin_base.make_plugin_source(searchpath=[os.path.dirname(location)])
- self._alternate_sources[('pip', package_name)] = source
-
- else:
- source = self._alternate_sources[('pip', package_name)]
-
- return source, defaults
-
- def _ensure_plugin(self, kind):
-
- if kind not in self._types:
- # Check whether the plugin is specified in plugins
- source = None
- defaults = None
- loaded_dependency = False
-
- for origin in self._plugin_origins:
- if kind not in _yaml.node_get(origin, list, 'plugins'):
- continue
-
- if _yaml.node_get(origin, str, 'origin') == 'local':
- local_path = _yaml.node_get(origin, str, 'path')
- source = self._get_local_plugin_source(local_path)
- elif _yaml.node_get(origin, str, 'origin') == 'pip':
- package_name = _yaml.node_get(origin, str, 'package-name')
- source, defaults = self._get_pip_plugin_source(package_name, kind)
- else:
- raise PluginError("Failed to load plugin '{}': "
- "Unexpected plugin origin '{}'"
- .format(kind, _yaml.node_get(origin, str, 'origin')))
- loaded_dependency = True
- break
-
- # Fall back to getting the source from site
- if not source:
- if kind not in self._site_source.list_plugins():
- raise PluginError("No {} type registered for kind '{}'"
- .format(self._base_type.__name__, kind))
-
- source = self._site_source
-
- self._types[kind] = self._load_plugin(source, kind, defaults)
- if loaded_dependency:
- self.loaded_dependencies.append(kind)
-
- return self._types[kind]
-
- def _load_plugin(self, source, kind, defaults):
-
- try:
- plugin = source.load_plugin(kind)
-
- if not defaults:
- plugin_file = inspect.getfile(plugin)
- plugin_dir = os.path.dirname(plugin_file)
- plugin_conf_name = "{}.yaml".format(kind)
- defaults = os.path.join(plugin_dir, plugin_conf_name)
-
- except ImportError as e:
- raise PluginError("Failed to load {} plugin '{}': {}"
- .format(self._base_type.__name__, kind, e)) from e
-
- try:
- plugin_type = plugin.setup()
- except AttributeError as e:
- raise PluginError("{} plugin '{}' did not provide a setup() function"
- .format(self._base_type.__name__, kind)) from e
- except TypeError as e:
- raise PluginError("setup symbol in {} plugin '{}' is not a function"
- .format(self._base_type.__name__, kind)) from e
-
- self._assert_plugin(kind, plugin_type)
- self._assert_version(kind, plugin_type)
- return (plugin_type, defaults)
-
- def _assert_plugin(self, kind, plugin_type):
- if kind in self._types:
- raise PluginError("Tried to register {} plugin for existing kind '{}' "
- "(already registered {})"
- .format(self._base_type.__name__, kind, self._types[kind].__name__))
- try:
- if not issubclass(plugin_type, self._base_type):
- raise PluginError("{} plugin '{}' returned type '{}', which is not a subclass of {}"
- .format(self._base_type.__name__, kind,
- plugin_type.__name__,
- self._base_type.__name__))
- except TypeError as e:
- raise PluginError("{} plugin '{}' returned something that is not a type (expected subclass of {})"
- .format(self._base_type.__name__, kind,
- self._base_type.__name__)) from e
-
- def _assert_version(self, kind, plugin_type):
-
- # Now assert BuildStream version
- bst_major, bst_minor = utils.get_bst_version()
-
- if bst_major < plugin_type.BST_REQUIRED_VERSION_MAJOR or \
- (bst_major == plugin_type.BST_REQUIRED_VERSION_MAJOR and
- bst_minor < plugin_type.BST_REQUIRED_VERSION_MINOR):
- raise PluginError("BuildStream {}.{} is too old for {} plugin '{}' (requires {}.{})"
- .format(
- bst_major, bst_minor,
- self._base_type.__name__, kind,
- plugin_type.BST_REQUIRED_VERSION_MAJOR,
- plugin_type.BST_REQUIRED_VERSION_MINOR))
-
- # _assert_plugin_format()
- #
- # Helper to raise a PluginError if the loaded plugin is of a lesser version then
- # the required version for this plugin
- #
- def _assert_plugin_format(self, plugin, version):
- if plugin.BST_FORMAT_VERSION < version:
- raise LoadError(LoadErrorReason.UNSUPPORTED_PLUGIN,
- "{}: Format version {} is too old for requested version {}"
- .format(plugin, plugin.BST_FORMAT_VERSION, version))
diff --git a/buildstream/_profile.py b/buildstream/_profile.py
deleted file mode 100644
index b17215d0e..000000000
--- a/buildstream/_profile.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#
-# Copyright (C) 2017 Codethink Limited
-# Copyright (C) 2019 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>
-# James Ennis <james.ennis@codethink.co.uk>
-# Benjamin Schubert <bschubert15@bloomberg.net>
-
-
-import contextlib
-import cProfile
-import pstats
-import os
-import datetime
-import time
-
-
-# Use the topic values here to decide what to profile
-# by setting them in the BST_PROFILE environment variable.
-#
-# Multiple topics can be set with the ':' separator.
-#
-# E.g.:
-#
-# BST_PROFILE=circ-dep-check:sort-deps bst <command> <args>
-#
-# The special 'all' value will enable all profiles.
-class Topics():
- CIRCULAR_CHECK = 'circ-dep-check'
- SORT_DEPENDENCIES = 'sort-deps'
- LOAD_CONTEXT = 'load-context'
- LOAD_PROJECT = 'load-project'
- LOAD_PIPELINE = 'load-pipeline'
- LOAD_SELECTION = 'load-selection'
- SCHEDULER = 'scheduler'
- ALL = 'all'
-
-
-class _Profile:
- def __init__(self, key, message):
- self.profiler = cProfile.Profile()
- self._additional_pstats_files = []
-
- self.key = key
- self.message = message
-
- self.start_time = time.time()
- filename_template = os.path.join(
- os.getcwd(),
- "profile-{}-{}".format(
- datetime.datetime.fromtimestamp(self.start_time).strftime("%Y%m%dT%H%M%S"),
- self.key.replace("/", "-").replace(".", "-")
- )
- )
- self.log_filename = "{}.log".format(filename_template)
- self.cprofile_filename = "{}.cprofile".format(filename_template)
-
- def __enter__(self):
- self.start()
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.stop()
- self.save()
-
- def merge(self, profile):
- self._additional_pstats_files.append(profile.cprofile_filename)
-
- def start(self):
- self.profiler.enable()
-
- def stop(self):
- self.profiler.disable()
-
- def save(self):
- heading = "\n".join([
- "-" * 64,
- "Profile for key: {}".format(self.key),
- "Started at: {}".format(self.start_time),
- "\n\t{}".format(self.message) if self.message else "",
- "-" * 64,
- "" # for a final new line
- ])
-
- with open(self.log_filename, "a") as fp:
- stats = pstats.Stats(self.profiler, *self._additional_pstats_files, stream=fp)
-
- # Create the log file
- fp.write(heading)
- stats.sort_stats("cumulative")
- stats.print_stats()
-
- # Dump the cprofile
- stats.dump_stats(self.cprofile_filename)
-
-
-class _Profiler:
- def __init__(self, settings):
- self.active_topics = set()
- self.enabled_topics = set()
- self._active_profilers = []
-
- if settings:
- self.enabled_topics = {
- topic
- for topic in settings.split(":")
- }
-
- @contextlib.contextmanager
- def profile(self, topic, key, message=None):
- if not self._is_profile_enabled(topic):
- yield
- return
-
- if self._active_profilers:
- # we are in a nested profiler, stop the parent
- self._active_profilers[-1].stop()
-
- key = "{}-{}".format(topic, key)
-
- assert key not in self.active_topics
- self.active_topics.add(key)
-
- profiler = _Profile(key, message)
- self._active_profilers.append(profiler)
-
- with profiler:
- yield
-
- self.active_topics.remove(key)
-
- # Remove the last profiler from the list
- self._active_profilers.pop()
-
- if self._active_profilers:
- # We were in a previous profiler, add the previous results to it
- # and reenable it.
- parent_profiler = self._active_profilers[-1]
- parent_profiler.merge(profiler)
- parent_profiler.start()
-
- def _is_profile_enabled(self, topic):
- return topic in self.enabled_topics or Topics.ALL in self.enabled_topics
-
-
-# Export a profiler to be used by BuildStream
-PROFILER = _Profiler(os.getenv("BST_PROFILE"))
diff --git a/buildstream/_project.py b/buildstream/_project.py
deleted file mode 100644
index c40321c66..000000000
--- a/buildstream/_project.py
+++ /dev/null
@@ -1,975 +0,0 @@
-#
-# Copyright (C) 2016-2018 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>
-# Tiago Gomes <tiago.gomes@codethink.co.uk>
-
-import os
-import sys
-from collections import OrderedDict
-from collections.abc import Mapping
-from pathlib import Path
-from pluginbase import PluginBase
-from . import utils
-from . import _cachekey
-from . import _site
-from . import _yaml
-from ._artifactelement import ArtifactElement
-from ._profile import Topics, PROFILER
-from ._exceptions import LoadError, LoadErrorReason
-from ._options import OptionPool
-from ._artifactcache import ArtifactCache
-from ._sourcecache import SourceCache
-from .sandbox import SandboxRemote
-from ._elementfactory import ElementFactory
-from ._sourcefactory import SourceFactory
-from .types import CoreWarnings
-from ._projectrefs import ProjectRefs, ProjectRefStorage
-from ._versions import BST_FORMAT_VERSION
-from ._loader import Loader
-from .element import Element
-from ._message import Message, MessageType
-from ._includes import Includes
-from ._platform import Platform
-from ._workspaces import WORKSPACE_PROJECT_FILE
-
-
-# Project Configuration file
-_PROJECT_CONF_FILE = 'project.conf'
-
-
-# HostMount()
-#
-# A simple object describing the behavior of
-# a host mount.
-#
-class HostMount():
-
- def __init__(self, path, host_path=None, optional=False):
-
- # Support environment variable expansion in host mounts
- path = os.path.expandvars(path)
- if host_path is not None:
- host_path = os.path.expandvars(host_path)
-
- self.path = path # Path inside the sandbox
- self.host_path = host_path # Path on the host
- self.optional = optional # Optional mounts do not incur warnings or errors
-
- if self.host_path is None:
- self.host_path = self.path
-
-
-# Represents project configuration that can have different values for junctions.
-class ProjectConfig:
- def __init__(self):
- self.element_factory = None
- self.source_factory = None
- self.options = None # OptionPool
- self.base_variables = {} # The base set of variables
- self.element_overrides = {} # Element specific configurations
- self.source_overrides = {} # Source specific configurations
- self.mirrors = OrderedDict() # contains dicts of alias-mappings to URIs.
- self.default_mirror = None # The name of the preferred mirror.
- self._aliases = {} # Aliases dictionary
-
-
-# Project()
-#
-# The Project Configuration
-#
-class Project():
-
- def __init__(self, directory, context, *, junction=None, cli_options=None,
- default_mirror=None, parent_loader=None,
- search_for_project=True):
-
- # The project name
- self.name = None
-
- self._context = context # The invocation Context, a private member
-
- if search_for_project:
- self.directory, self._invoked_from_workspace_element = self._find_project_dir(directory)
- else:
- self.directory = directory
- self._invoked_from_workspace_element = None
-
- self._absolute_directory_path = Path(self.directory).resolve()
-
- # Absolute path to where elements are loaded from within the project
- self.element_path = None
-
- # Default target elements
- self._default_targets = None
-
- # ProjectRefs for the main refs and also for junctions
- self.refs = ProjectRefs(self.directory, 'project.refs')
- self.junction_refs = ProjectRefs(self.directory, 'junction.refs')
-
- self.config = ProjectConfig()
- self.first_pass_config = ProjectConfig()
-
- self.junction = junction # The junction Element object, if this is a subproject
-
- self.ref_storage = None # ProjectRefStorage setting
- self.base_environment = {} # The base set of environment variables
- self.base_env_nocache = None # The base nocache mask (list) for the environment
-
- #
- # Private Members
- #
-
- self._default_mirror = default_mirror # The name of the preferred mirror.
-
- self._cli_options = cli_options
- self._cache_key = None
-
- self._fatal_warnings = [] # A list of warnings which should trigger an error
-
- self._shell_command = [] # The default interactive shell command
- self._shell_environment = {} # Statically set environment vars
- self._shell_host_files = [] # A list of HostMount objects
-
- self.artifact_cache_specs = None
- self.source_cache_specs = None
- self.remote_execution_specs = None
- self._sandbox = None
- self._splits = None
-
- self._context.add_project(self)
-
- self._partially_loaded = False
- self._fully_loaded = False
- self._project_includes = None
-
- with PROFILER.profile(Topics.LOAD_PROJECT, self.directory.replace(os.sep, '-')):
- self._load(parent_loader=parent_loader)
-
- self._partially_loaded = True
-
- @property
- def options(self):
- return self.config.options
-
- @property
- def base_variables(self):
- return self.config.base_variables
-
- @property
- def element_overrides(self):
- return self.config.element_overrides
-
- @property
- def source_overrides(self):
- return self.config.source_overrides
-
- # translate_url():
- #
- # Translates the given url which may be specified with an alias
- # into a fully qualified url.
- #
- # Args:
- # url (str): A url, which may be using an alias
- # first_pass (bool): Whether to use first pass configuration (for junctions)
- #
- # Returns:
- # str: The fully qualified url, with aliases resolved
- #
- # This method is provided for :class:`.Source` objects to resolve
- # fully qualified urls based on the shorthand which is allowed
- # to be specified in the YAML
- def translate_url(self, url, *, first_pass=False):
- if first_pass:
- config = self.first_pass_config
- else:
- config = self.config
-
- if url and utils._ALIAS_SEPARATOR in url:
- url_alias, url_body = url.split(utils._ALIAS_SEPARATOR, 1)
- alias_url = _yaml.node_get(config._aliases, str, url_alias, default_value=None)
- if alias_url:
- url = alias_url + url_body
-
- return url
-
- # get_shell_config()
- #
- # Gets the project specified shell configuration
- #
- # Returns:
- # (list): The shell command
- # (dict): The shell environment
- # (list): The list of HostMount objects
- #
- def get_shell_config(self):
- return (self._shell_command, self._shell_environment, self._shell_host_files)
-
- # get_cache_key():
- #
- # Returns the cache key, calculating it if necessary
- #
- # Returns:
- # (str): A hex digest cache key for the Context
- #
- def get_cache_key(self):
- if self._cache_key is None:
-
- # Anything that alters the build goes into the unique key
- # (currently nothing here)
- self._cache_key = _cachekey.generate_key(_yaml.new_empty_node())
-
- return self._cache_key
-
- # get_path_from_node()
- #
- # Fetches the project path from a dictionary node and validates it
- #
- # Paths are asserted to never lead to a directory outside of the project
- # directory. In addition, paths can not point to symbolic links, fifos,
- # sockets and block/character devices.
- #
- # The `check_is_file` and `check_is_dir` parameters can be used to
- # perform additional validations on the path. Note that an exception
- # will always be raised if both parameters are set to ``True``.
- #
- # Args:
- # node (dict): A dictionary loaded from YAML
- # key (str): The key whose value contains a path to validate
- # check_is_file (bool): If ``True`` an error will also be raised
- # if path does not point to a regular file.
- # Defaults to ``False``
- # check_is_dir (bool): If ``True`` an error will be also raised
- # if path does not point to a directory.
- # Defaults to ``False``
- # Returns:
- # (str): The project path
- #
- # Raises:
- # (LoadError): In case that the project path is not valid or does not
- # exist
- #
- def get_path_from_node(self, node, key, *,
- check_is_file=False, check_is_dir=False):
- path_str = _yaml.node_get(node, str, key)
- path = Path(path_str)
- full_path = self._absolute_directory_path / path
-
- provenance = _yaml.node_get_provenance(node, key=key)
-
- if full_path.is_symlink():
- raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
- "{}: Specified path '{}' must not point to "
- "symbolic links "
- .format(provenance, path_str))
-
- if path.parts and path.parts[0] == '..':
- raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
- "{}: Specified path '{}' first component must "
- "not be '..'"
- .format(provenance, path_str))
-
- try:
- if sys.version_info[0] == 3 and sys.version_info[1] < 6:
- full_resolved_path = full_path.resolve()
- else:
- full_resolved_path = full_path.resolve(strict=True) # pylint: disable=unexpected-keyword-arg
- except FileNotFoundError:
- raise LoadError(LoadErrorReason.MISSING_FILE,
- "{}: Specified path '{}' does not exist"
- .format(provenance, path_str))
-
- is_inside = self._absolute_directory_path in full_resolved_path.parents or (
- full_resolved_path == self._absolute_directory_path)
-
- if not is_inside:
- raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
- "{}: Specified path '{}' must not lead outside of the "
- "project directory"
- .format(provenance, path_str))
-
- if path.is_absolute():
- raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
- "{}: Absolute path: '{}' invalid.\n"
- "Please specify a path relative to the project's root."
- .format(provenance, path))
-
- if full_resolved_path.is_socket() or (
- full_resolved_path.is_fifo() or
- full_resolved_path.is_block_device()):
- raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
- "{}: Specified path '{}' points to an unsupported "
- "file kind"
- .format(provenance, path_str))
-
- if check_is_file and not full_resolved_path.is_file():
- raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
- "{}: Specified path '{}' is not a regular file"
- .format(provenance, path_str))
-
- if check_is_dir and not full_resolved_path.is_dir():
- raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
- "{}: Specified path '{}' is not a directory"
- .format(provenance, path_str))
-
- return path_str
-
- def _validate_node(self, node):
- _yaml.node_validate(node, [
- 'format-version',
- 'element-path', 'variables',
- 'environment', 'environment-nocache',
- 'split-rules', 'elements', 'plugins',
- 'aliases', 'name', 'defaults',
- 'artifacts', 'options',
- 'fail-on-overlap', 'shell', 'fatal-warnings',
- 'ref-storage', 'sandbox', 'mirrors', 'remote-execution',
- 'sources', 'source-caches', '(@)'
- ])
-
- # create_element()
- #
- # Instantiate and return an element
- #
- # Args:
- # meta (MetaElement): The loaded MetaElement
- # first_pass (bool): Whether to use first pass configuration (for junctions)
- #
- # Returns:
- # (Element): A newly created Element object of the appropriate kind
- #
- def create_element(self, meta, *, first_pass=False):
- if first_pass:
- return self.first_pass_config.element_factory.create(self._context, self, meta)
- else:
- return self.config.element_factory.create(self._context, self, meta)
-
- # create_artifact_element()
- #
- # Instantiate and return an ArtifactElement
- #
- # Args:
- # ref (str): A string of the artifact ref
- #
- # Returns:
- # (ArtifactElement): A newly created ArtifactElement object of the appropriate kind
- #
- def create_artifact_element(self, ref):
- return ArtifactElement(self._context, ref)
-
- # create_source()
- #
- # Instantiate and return a Source
- #
- # Args:
- # meta (MetaSource): The loaded MetaSource
- # first_pass (bool): Whether to use first pass configuration (for junctions)
- #
- # Returns:
- # (Source): A newly created Source object of the appropriate kind
- #
- def create_source(self, meta, *, first_pass=False):
- if first_pass:
- return self.first_pass_config.source_factory.create(self._context, self, meta)
- else:
- return self.config.source_factory.create(self._context, self, meta)
-
- # get_alias_uri()
- #
- # Returns the URI for a given alias, if it exists
- #
- # Args:
- # alias (str): The alias.
- # first_pass (bool): Whether to use first pass configuration (for junctions)
- #
- # Returns:
- # str: The URI for the given alias; or None: if there is no URI for
- # that alias.
- def get_alias_uri(self, alias, *, first_pass=False):
- if first_pass:
- config = self.first_pass_config
- else:
- config = self.config
-
- return _yaml.node_get(config._aliases, str, alias, default_value=None)
-
- # get_alias_uris()
- #
- # Args:
- # alias (str): The alias.
- # first_pass (bool): Whether to use first pass configuration (for junctions)
- #
- # Returns a list of every URI to replace an alias with
- def get_alias_uris(self, alias, *, first_pass=False):
- if first_pass:
- config = self.first_pass_config
- else:
- config = self.config
-
- if not alias or alias not in config._aliases:
- return [None]
-
- mirror_list = []
- for key, alias_mapping in config.mirrors.items():
- if alias in alias_mapping:
- if key == config.default_mirror:
- mirror_list = alias_mapping[alias] + mirror_list
- else:
- mirror_list += alias_mapping[alias]
- mirror_list.append(_yaml.node_get(config._aliases, str, alias))
- return mirror_list
-
- # load_elements()
- #
- # Loads elements from target names.
- #
- # Args:
- # targets (list): Target names
- # rewritable (bool): Whether the loaded files should be rewritable
- # this is a bit more expensive due to deep copies
- # fetch_subprojects (bool): Whether we should fetch subprojects as a part of the
- # loading process, if they are not yet locally cached
- #
- # Returns:
- # (list): A list of loaded Element
- #
- def load_elements(self, targets, *,
- rewritable=False, fetch_subprojects=False):
- with self._context.timed_activity("Loading elements", silent_nested=True):
- meta_elements = self.loader.load(targets, rewritable=rewritable,
- ticker=None,
- fetch_subprojects=fetch_subprojects)
-
- with self._context.timed_activity("Resolving elements"):
- elements = [
- Element._new_from_meta(meta)
- for meta in meta_elements
- ]
-
- Element._clear_meta_elements_cache()
-
- # Now warn about any redundant source references which may have
- # been discovered in the resolve() phase.
- redundant_refs = Element._get_redundant_source_refs()
- if redundant_refs:
- detail = "The following inline specified source references will be ignored:\n\n"
- lines = [
- "{}:{}".format(source._get_provenance(), ref)
- for source, ref in redundant_refs
- ]
- detail += "\n".join(lines)
- self._context.message(
- Message(None, MessageType.WARN, "Ignoring redundant source references", detail=detail))
-
- return elements
-
- # ensure_fully_loaded()
- #
- # Ensure project has finished loading. At first initialization, a
- # project can only load junction elements. Other elements require
- # project to be fully loaded.
- #
- def ensure_fully_loaded(self):
- if self._fully_loaded:
- return
- assert self._partially_loaded
- self._fully_loaded = True
-
- if self.junction:
- self.junction._get_project().ensure_fully_loaded()
-
- self._load_second_pass()
-
- # invoked_from_workspace_element()
- #
- # Returns the element whose workspace was used to invoke buildstream
- # if buildstream was invoked from an external workspace
- #
- def invoked_from_workspace_element(self):
- return self._invoked_from_workspace_element
-
- # cleanup()
- #
- # Cleans up resources used loading elements
- #
- def cleanup(self):
- # Reset the element loader state
- Element._reset_load_state()
-
- # get_default_target()
- #
- # Attempts to interpret which element the user intended to run a command on.
- # This is for commands that only accept a single target element and thus,
- # this only uses the workspace element (if invoked from workspace directory)
- # and does not use the project default targets.
- #
- def get_default_target(self):
- return self._invoked_from_workspace_element
-
- # get_default_targets()
- #
- # Attempts to interpret which elements the user intended to run a command on.
- # This is for commands that accept multiple target elements.
- #
- def get_default_targets(self):
-
- # If _invoked_from_workspace_element has a value,
- # a workspace element was found before a project config
- # Therefore the workspace does not contain a project
- if self._invoked_from_workspace_element:
- return (self._invoked_from_workspace_element,)
-
- # Default targets from project configuration
- if self._default_targets:
- return tuple(self._default_targets)
-
- # If default targets are not configured, default to all project elements
- default_targets = []
- for root, dirs, files in os.walk(self.element_path):
- # Do not recurse down the ".bst" directory which is where we stage
- # junctions and other BuildStream internals.
- if ".bst" in dirs:
- dirs.remove(".bst")
- for file in files:
- if file.endswith(".bst"):
- rel_dir = os.path.relpath(root, self.element_path)
- rel_file = os.path.join(rel_dir, file).lstrip("./")
- default_targets.append(rel_file)
-
- return tuple(default_targets)
-
- # _load():
- #
- # Loads the project configuration file in the project
- # directory process the first pass.
- #
- # Raises: LoadError if there was a problem with the project.conf
- #
- def _load(self, parent_loader=None):
-
- # Load builtin default
- projectfile = os.path.join(self.directory, _PROJECT_CONF_FILE)
- self._default_config_node = _yaml.load(_site.default_project_config)
-
- # Load project local config and override the builtin
- try:
- self._project_conf = _yaml.load(projectfile)
- except LoadError as e:
- # Raise a more specific error here
- if e.reason == LoadErrorReason.MISSING_FILE:
- raise LoadError(LoadErrorReason.MISSING_PROJECT_CONF, str(e)) from e
- else:
- raise
-
- pre_config_node = _yaml.node_copy(self._default_config_node)
- _yaml.composite(pre_config_node, self._project_conf)
-
- # Assert project's format version early, before validating toplevel keys
- format_version = _yaml.node_get(pre_config_node, int, 'format-version')
- if BST_FORMAT_VERSION < format_version:
- major, minor = utils.get_bst_version()
- raise LoadError(
- LoadErrorReason.UNSUPPORTED_PROJECT,
- "Project requested format version {}, but BuildStream {}.{} only supports up until format version {}"
- .format(format_version, major, minor, BST_FORMAT_VERSION))
-
- self._validate_node(pre_config_node)
-
- # The project name, element path and option declarations
- # are constant and cannot be overridden by option conditional statements
- self.name = _yaml.node_get(self._project_conf, str, 'name')
-
- # Validate that project name is a valid symbol name
- _yaml.assert_symbol_name(_yaml.node_get_provenance(pre_config_node, 'name'),
- self.name, "project name")
-
- self.element_path = os.path.join(
- self.directory,
- self.get_path_from_node(pre_config_node, 'element-path',
- check_is_dir=True)
- )
-
- self.config.options = OptionPool(self.element_path)
- self.first_pass_config.options = OptionPool(self.element_path)
-
- defaults = _yaml.node_get(pre_config_node, Mapping, 'defaults')
- _yaml.node_validate(defaults, ['targets'])
- self._default_targets = _yaml.node_get(defaults, list, "targets")
-
- # Fatal warnings
- self._fatal_warnings = _yaml.node_get(pre_config_node, list, 'fatal-warnings', default_value=[])
-
- self.loader = Loader(self._context, self,
- parent=parent_loader)
-
- self._project_includes = Includes(self.loader, copy_tree=False)
-
- project_conf_first_pass = _yaml.node_copy(self._project_conf)
- self._project_includes.process(project_conf_first_pass, only_local=True)
- config_no_include = _yaml.node_copy(self._default_config_node)
- _yaml.composite(config_no_include, project_conf_first_pass)
-
- self._load_pass(config_no_include, self.first_pass_config,
- ignore_unknown=True)
-
- # Use separate file for storing source references
- self.ref_storage = _yaml.node_get(pre_config_node, str, 'ref-storage')
- if self.ref_storage not in [ProjectRefStorage.INLINE, ProjectRefStorage.PROJECT_REFS]:
- p = _yaml.node_get_provenance(pre_config_node, 'ref-storage')
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Invalid value '{}' specified for ref-storage"
- .format(p, self.ref_storage))
-
- if self.ref_storage == ProjectRefStorage.PROJECT_REFS:
- self.junction_refs.load(self.first_pass_config.options)
-
- # _load_second_pass()
- #
- # Process the second pass of loading the project configuration.
- #
- def _load_second_pass(self):
- project_conf_second_pass = _yaml.node_copy(self._project_conf)
- self._project_includes.process(project_conf_second_pass)
- config = _yaml.node_copy(self._default_config_node)
- _yaml.composite(config, project_conf_second_pass)
-
- self._load_pass(config, self.config)
-
- self._validate_node(config)
-
- #
- # Now all YAML composition is done, from here on we just load
- # the values from our loaded configuration dictionary.
- #
-
- # Load artifacts pull/push configuration for this project
- project_specs = ArtifactCache.specs_from_config_node(config, self.directory)
- override_specs = ArtifactCache.specs_from_config_node(
- self._context.get_overrides(self.name), self.directory)
-
- self.artifact_cache_specs = override_specs + project_specs
-
- if self.junction:
- parent = self.junction._get_project()
- self.artifact_cache_specs = parent.artifact_cache_specs + self.artifact_cache_specs
-
- # Load source caches with pull/push config
- self.source_cache_specs = SourceCache.specs_from_config_node(config, self.directory)
-
- # Load remote-execution configuration for this project
- project_specs = SandboxRemote.specs_from_config_node(config, self.directory)
- override_specs = SandboxRemote.specs_from_config_node(
- self._context.get_overrides(self.name), self.directory)
-
- if override_specs is not None:
- self.remote_execution_specs = override_specs
- elif project_specs is not None:
- self.remote_execution_specs = project_specs
- else:
- self.remote_execution_specs = self._context.remote_execution_specs
-
- # Load sandbox environment variables
- self.base_environment = _yaml.node_get(config, Mapping, 'environment')
- self.base_env_nocache = _yaml.node_get(config, list, 'environment-nocache')
-
- # Load sandbox configuration
- self._sandbox = _yaml.node_get(config, Mapping, 'sandbox')
-
- # Load project split rules
- self._splits = _yaml.node_get(config, Mapping, 'split-rules')
-
- # Support backwards compatibility for fail-on-overlap
- fail_on_overlap = _yaml.node_get(config, bool, 'fail-on-overlap', default_value=None)
-
- if (CoreWarnings.OVERLAPS not in self._fatal_warnings) and fail_on_overlap:
- self._fatal_warnings.append(CoreWarnings.OVERLAPS)
-
- # Deprecation check
- if fail_on_overlap is not None:
- self._context.message(
- Message(
- None,
- MessageType.WARN,
- "Use of fail-on-overlap within project.conf " +
- "is deprecated. Consider using fatal-warnings instead."
- )
- )
-
- # Load project.refs if it exists, this may be ignored.
- if self.ref_storage == ProjectRefStorage.PROJECT_REFS:
- self.refs.load(self.options)
-
- # Parse shell options
- shell_options = _yaml.node_get(config, Mapping, 'shell')
- _yaml.node_validate(shell_options, ['command', 'environment', 'host-files'])
- self._shell_command = _yaml.node_get(shell_options, list, 'command')
-
- # Perform environment expansion right away
- shell_environment = _yaml.node_get(shell_options, Mapping, 'environment', default_value={})
- for key in _yaml.node_keys(shell_environment):
- value = _yaml.node_get(shell_environment, str, key)
- self._shell_environment[key] = os.path.expandvars(value)
-
- # Host files is parsed as a list for convenience
- host_files = _yaml.node_get(shell_options, list, 'host-files', default_value=[])
- for host_file in host_files:
- if isinstance(host_file, str):
- mount = HostMount(host_file)
- else:
- # Some validation
- index = host_files.index(host_file)
- host_file_desc = _yaml.node_get(shell_options, Mapping, 'host-files', indices=[index])
- _yaml.node_validate(host_file_desc, ['path', 'host_path', 'optional'])
-
- # Parse the host mount
- path = _yaml.node_get(host_file_desc, str, 'path')
- host_path = _yaml.node_get(host_file_desc, str, 'host_path', default_value=None)
- optional = _yaml.node_get(host_file_desc, bool, 'optional', default_value=False)
- mount = HostMount(path, host_path, optional)
-
- self._shell_host_files.append(mount)
-
- # _load_pass():
- #
- # Loads parts of the project configuration that are different
- # for first and second pass configurations.
- #
- # Args:
- # config (dict) - YaML node of the configuration file.
- # output (ProjectConfig) - ProjectConfig to load configuration onto.
- # ignore_unknown (bool) - Whether option loader shoud ignore unknown options.
- #
- def _load_pass(self, config, output, *,
- ignore_unknown=False):
-
- # Element and Source type configurations will be composited later onto
- # element/source types, so we delete it from here and run our final
- # assertion after.
- output.element_overrides = _yaml.node_get(config, Mapping, 'elements', default_value={})
- output.source_overrides = _yaml.node_get(config, Mapping, 'sources', default_value={})
- _yaml.node_del(config, 'elements', safe=True)
- _yaml.node_del(config, 'sources', safe=True)
- _yaml.node_final_assertions(config)
-
- self._load_plugin_factories(config, output)
-
- # Load project options
- options_node = _yaml.node_get(config, Mapping, 'options', default_value={})
- output.options.load(options_node)
- if self.junction:
- # load before user configuration
- output.options.load_yaml_values(self.junction.options, transform=self.junction._subst_string)
-
- # Collect option values specified in the user configuration
- overrides = self._context.get_overrides(self.name)
- override_options = _yaml.node_get(overrides, Mapping, 'options', default_value={})
- output.options.load_yaml_values(override_options)
- if self._cli_options:
- output.options.load_cli_values(self._cli_options, ignore_unknown=ignore_unknown)
-
- # We're done modifying options, now we can use them for substitutions
- output.options.resolve()
-
- #
- # Now resolve any conditionals in the remaining configuration,
- # any conditionals specified for project option declarations,
- # or conditionally specifying the project name; will be ignored.
- #
- # Don't forget to also resolve options in the element and source overrides.
- output.options.process_node(config)
- output.options.process_node(output.element_overrides)
- output.options.process_node(output.source_overrides)
-
- # Load base variables
- output.base_variables = _yaml.node_get(config, Mapping, 'variables')
-
- # Add the project name as a default variable
- _yaml.node_set(output.base_variables, 'project-name', self.name)
-
- # Extend variables with automatic variables and option exports
- # Initialize it as a string as all variables are processed as strings.
- # Based on some testing (mainly on AWS), maximum effective
- # max-jobs value seems to be around 8-10 if we have enough cores
- # users should set values based on workload and build infrastructure
- platform = Platform.get_platform()
- _yaml.node_set(output.base_variables, 'max-jobs', str(platform.get_cpu_count(8)))
-
- # Export options into variables, if that was requested
- output.options.export_variables(output.base_variables)
-
- # Override default_mirror if not set by command-line
- output.default_mirror = self._default_mirror or _yaml.node_get(overrides, str,
- 'default-mirror', default_value=None)
-
- mirrors = _yaml.node_get(config, list, 'mirrors', default_value=[])
- for mirror in mirrors:
- allowed_mirror_fields = [
- 'name', 'aliases'
- ]
- _yaml.node_validate(mirror, allowed_mirror_fields)
- mirror_name = _yaml.node_get(mirror, str, 'name')
- alias_mappings = {}
- for alias_mapping, uris in _yaml.node_items(_yaml.node_get(mirror, Mapping, 'aliases')):
- assert isinstance(uris, list)
- alias_mappings[alias_mapping] = list(uris)
- output.mirrors[mirror_name] = alias_mappings
- if not output.default_mirror:
- output.default_mirror = mirror_name
-
- # Source url aliases
- output._aliases = _yaml.node_get(config, Mapping, 'aliases', default_value={})
-
- # _find_project_dir()
- #
- # Returns path of the project directory, if a configuration file is found
- # in given directory or any of its parent directories.
- #
- # Args:
- # directory (str) - directory from where the command was invoked
- #
- # Raises:
- # LoadError if project.conf is not found
- #
- # Returns:
- # (str) - the directory that contains the project, and
- # (str) - the name of the element required to find the project, or None
- #
- def _find_project_dir(self, directory):
- workspace_element = None
- config_filenames = [_PROJECT_CONF_FILE, WORKSPACE_PROJECT_FILE]
- found_directory, filename = utils._search_upward_for_files(
- directory, config_filenames
- )
- if filename == _PROJECT_CONF_FILE:
- project_directory = found_directory
- elif filename == WORKSPACE_PROJECT_FILE:
- workspace_project_cache = self._context.get_workspace_project_cache()
- workspace_project = workspace_project_cache.get(found_directory)
- if workspace_project:
- project_directory = workspace_project.get_default_project_path()
- workspace_element = workspace_project.get_default_element()
- else:
- raise LoadError(
- LoadErrorReason.MISSING_PROJECT_CONF,
- "None of {names} found in '{path}' or any of its parent directories"
- .format(names=config_filenames, path=directory))
-
- return project_directory, workspace_element
-
- def _load_plugin_factories(self, config, output):
- plugin_source_origins = [] # Origins of custom sources
- plugin_element_origins = [] # Origins of custom elements
-
- # Plugin origins and versions
- origins = _yaml.node_get(config, list, 'plugins', default_value=[])
- source_format_versions = {}
- element_format_versions = {}
- for origin in origins:
- allowed_origin_fields = [
- 'origin', 'sources', 'elements',
- 'package-name', 'path',
- ]
- allowed_origins = ['core', 'local', 'pip']
- _yaml.node_validate(origin, allowed_origin_fields)
-
- origin_value = _yaml.node_get(origin, str, 'origin')
- if origin_value not in allowed_origins:
- raise LoadError(
- LoadErrorReason.INVALID_YAML,
- "Origin '{}' is not one of the allowed types"
- .format(origin_value))
-
- # Store source versions for checking later
- source_versions = _yaml.node_get(origin, Mapping, 'sources', default_value={})
- for key in _yaml.node_keys(source_versions):
- if key in source_format_versions:
- raise LoadError(
- LoadErrorReason.INVALID_YAML,
- "Duplicate listing of source '{}'".format(key))
- source_format_versions[key] = _yaml.node_get(source_versions, int, key)
-
- # Store element versions for checking later
- element_versions = _yaml.node_get(origin, Mapping, 'elements', default_value={})
- for key in _yaml.node_keys(element_versions):
- if key in element_format_versions:
- raise LoadError(
- LoadErrorReason.INVALID_YAML,
- "Duplicate listing of element '{}'".format(key))
- element_format_versions[key] = _yaml.node_get(element_versions, int, key)
-
- # Store the origins if they're not 'core'.
- # core elements are loaded by default, so storing is unnecessary.
- if _yaml.node_get(origin, str, 'origin') != 'core':
- self._store_origin(origin, 'sources', plugin_source_origins)
- self._store_origin(origin, 'elements', plugin_element_origins)
-
- pluginbase = PluginBase(package='buildstream.plugins')
- output.element_factory = ElementFactory(pluginbase,
- plugin_origins=plugin_element_origins,
- format_versions=element_format_versions)
- output.source_factory = SourceFactory(pluginbase,
- plugin_origins=plugin_source_origins,
- format_versions=source_format_versions)
-
- # _store_origin()
- #
- # Helper function to store plugin origins
- #
- # Args:
- # origin (node) - a node indicating the origin of a group of
- # plugins.
- # plugin_group (str) - The name of the type of plugin that is being
- # loaded
- # destination (list) - A list of nodes to store the origins in
- #
- # Raises:
- # LoadError if 'origin' is an unexpected value
- def _store_origin(self, origin, plugin_group, destination):
- expected_groups = ['sources', 'elements']
- if plugin_group not in expected_groups:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "Unexpected plugin group: {}, expecting {}"
- .format(plugin_group, expected_groups))
- node_keys = [key for key in _yaml.node_keys(origin)]
- if plugin_group in node_keys:
- origin_node = _yaml.node_copy(origin)
- plugins = _yaml.node_get(origin, Mapping, plugin_group, default_value={})
- _yaml.node_set(origin_node, 'plugins', [k for k in _yaml.node_keys(plugins)])
- for group in expected_groups:
- if group in origin_node:
- _yaml.node_del(origin_node, group)
-
- if _yaml.node_get(origin_node, str, 'origin') == 'local':
- path = self.get_path_from_node(origin, 'path',
- check_is_dir=True)
- # paths are passed in relative to the project, but must be absolute
- _yaml.node_set(origin_node, 'path', os.path.join(self.directory, path))
- destination.append(origin_node)
-
- # _warning_is_fatal():
- #
- # Returns true if the warning in question should be considered fatal based on
- # the project configuration.
- #
- # Args:
- # warning_str (str): The warning configuration string to check against
- #
- # Returns:
- # (bool): True if the warning should be considered fatal and cause an error.
- #
- def _warning_is_fatal(self, warning_str):
- return warning_str in self._fatal_warnings
diff --git a/buildstream/_projectrefs.py b/buildstream/_projectrefs.py
deleted file mode 100644
index 09205a7c3..000000000
--- a/buildstream/_projectrefs.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#
-# Copyright (C) 2018 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>
-import os
-
-from . import _yaml
-from ._exceptions import LoadError, LoadErrorReason
-
-
-# ProjectRefStorage()
-#
-# Indicates the type of ref storage
-class ProjectRefStorage():
-
- # Source references are stored inline
- #
- INLINE = 'inline'
-
- # Source references are stored in a central project.refs file
- #
- PROJECT_REFS = 'project.refs'
-
-
-# ProjectRefs()
-#
-# The project.refs file management
-#
-# Args:
-# directory (str): The project directory
-# base_name (str): The project.refs basename
-#
-class ProjectRefs():
-
- def __init__(self, directory, base_name):
- directory = os.path.abspath(directory)
- self._fullpath = os.path.join(directory, base_name)
- self._base_name = base_name
- self._toplevel_node = None
- self._toplevel_save = None
-
- # load()
- #
- # Load the project.refs file
- #
- # Args:
- # options (OptionPool): To resolve conditional statements
- #
- def load(self, options):
- try:
- self._toplevel_node = _yaml.load(self._fullpath, shortname=self._base_name, copy_tree=True)
- provenance = _yaml.node_get_provenance(self._toplevel_node)
- self._toplevel_save = provenance.toplevel
-
- # Process any project options immediately
- options.process_node(self._toplevel_node)
-
- # Run any final assertions on the project.refs, just incase there
- # are list composition directives or anything left unprocessed.
- _yaml.node_final_assertions(self._toplevel_node)
-
- except LoadError as e:
- if e.reason != LoadErrorReason.MISSING_FILE:
- raise
-
- # Ignore failure if the file doesnt exist, it'll be created and
- # for now just assumed to be empty
- self._toplevel_node = _yaml.new_synthetic_file(self._fullpath)
- self._toplevel_save = self._toplevel_node
-
- _yaml.node_validate(self._toplevel_node, ['projects'])
-
- # Ensure we create our toplevel entry point on the fly here
- for node in [self._toplevel_node, self._toplevel_save]:
- if 'projects' not in node:
- _yaml.node_set(node, 'projects', _yaml.new_empty_node(ref_node=node))
-
- # lookup_ref()
- #
- # Fetch the ref node for a given Source. If the ref node does not
- # exist and `write` is specified, it will be automatically created.
- #
- # Args:
- # project (str): The project to lookup
- # element (str): The element name to lookup
- # source_index (int): The index of the Source in the specified element
- # write (bool): Whether we want to read the node or write to it
- #
- # Returns:
- # (node): The YAML dictionary where the ref is stored
- #
- def lookup_ref(self, project, element, source_index, *, write=False):
-
- node = self._lookup(self._toplevel_node, project, element, source_index)
-
- if write:
-
- # If we couldnt find the orignal, create a new one.
- #
- if node is None:
- node = self._lookup(self._toplevel_save, project, element, source_index, ensure=True)
-
- return node
-
- # _lookup()
- #
- # Looks up a ref node in the project.refs file, creates one if ensure is True.
- #
- def _lookup(self, toplevel, project, element, source_index, *, ensure=False):
- # Fetch the project
- try:
- projects = _yaml.node_get(toplevel, dict, 'projects')
- project_node = _yaml.node_get(projects, dict, project)
- except LoadError:
- if not ensure:
- return None
- project_node = _yaml.new_empty_node(ref_node=projects)
- _yaml.node_set(projects, project, project_node)
-
- # Fetch the element
- try:
- element_list = _yaml.node_get(project_node, list, element)
- except LoadError:
- if not ensure:
- return None
- element_list = []
- _yaml.node_set(project_node, element, element_list)
-
- # Fetch the source index
- try:
- node = element_list[source_index]
- except IndexError:
- if not ensure:
- return None
-
- # Pad the list with empty newly created dictionaries
- _yaml.node_extend_list(project_node, element, source_index + 1, {})
-
- node = _yaml.node_get(project_node, dict, element, indices=[source_index])
-
- return node
diff --git a/buildstream/_protos/__init__.py b/buildstream/_protos/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/build/__init__.py b/buildstream/_protos/build/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/build/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/build/bazel/__init__.py b/buildstream/_protos/build/bazel/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/build/bazel/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/build/bazel/remote/__init__.py b/buildstream/_protos/build/bazel/remote/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/build/bazel/remote/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/build/bazel/remote/execution/__init__.py b/buildstream/_protos/build/bazel/remote/execution/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/build/bazel/remote/execution/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/build/bazel/remote/execution/v2/__init__.py b/buildstream/_protos/build/bazel/remote/execution/v2/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/build/bazel/remote/execution/v2/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution.proto b/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution.proto
deleted file mode 100644
index 7edbce3bc..000000000
--- a/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution.proto
+++ /dev/null
@@ -1,1331 +0,0 @@
-// Copyright 2018 The Bazel Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package build.bazel.remote.execution.v2;
-
-import "build/bazel/semver/semver.proto";
-import "google/api/annotations.proto";
-import "google/longrunning/operations.proto";
-import "google/protobuf/duration.proto";
-import "google/protobuf/timestamp.proto";
-import "google/rpc/status.proto";
-
-option csharp_namespace = "Build.Bazel.Remote.Execution.V2";
-option go_package = "remoteexecution";
-option java_multiple_files = true;
-option java_outer_classname = "RemoteExecutionProto";
-option java_package = "build.bazel.remote.execution.v2";
-option objc_class_prefix = "REX";
-
-
-// The Remote Execution API is used to execute an
-// [Action][build.bazel.remote.execution.v2.Action] on the remote
-// workers.
-//
-// As with other services in the Remote Execution API, any call may return an
-// error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
-// information about when the client should retry the request; clients SHOULD
-// respect the information provided.
-service Execution {
- // Execute an action remotely.
- //
- // In order to execute an action, the client must first upload all of the
- // inputs, the
- // [Command][build.bazel.remote.execution.v2.Command] to run, and the
- // [Action][build.bazel.remote.execution.v2.Action] into the
- // [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage].
- // It then calls `Execute` with an `action_digest` referring to them. The
- // server will run the action and eventually return the result.
- //
- // The input `Action`'s fields MUST meet the various canonicalization
- // requirements specified in the documentation for their types so that it has
- // the same digest as other logically equivalent `Action`s. The server MAY
- // enforce the requirements and return errors if a non-canonical input is
- // received. It MAY also proceed without verifying some or all of the
- // requirements, such as for performance reasons. If the server does not
- // verify the requirement, then it will treat the `Action` as distinct from
- // another logically equivalent action if they hash differently.
- //
- // Returns a stream of
- // [google.longrunning.Operation][google.longrunning.Operation] messages
- // describing the resulting execution, with eventual `response`
- // [ExecuteResponse][build.bazel.remote.execution.v2.ExecuteResponse]. The
- // `metadata` on the operation is of type
- // [ExecuteOperationMetadata][build.bazel.remote.execution.v2.ExecuteOperationMetadata].
- //
- // If the client remains connected after the first response is returned after
- // the server, then updates are streamed as if the client had called
- // [WaitExecution][build.bazel.remote.execution.v2.Execution.WaitExecution]
- // until the execution completes or the request reaches an error. The
- // operation can also be queried using [Operations
- // API][google.longrunning.Operations.GetOperation].
- //
- // The server NEED NOT implement other methods or functionality of the
- // Operations API.
- //
- // Errors discovered during creation of the `Operation` will be reported
- // as gRPC Status errors, while errors that occurred while running the
- // action will be reported in the `status` field of the `ExecuteResponse`. The
- // server MUST NOT set the `error` field of the `Operation` proto.
- // The possible errors include:
- // * `INVALID_ARGUMENT`: One or more arguments are invalid.
- // * `FAILED_PRECONDITION`: One or more errors occurred in setting up the
- // action requested, such as a missing input or command or no worker being
- // available. The client may be able to fix the errors and retry.
- // * `RESOURCE_EXHAUSTED`: There is insufficient quota of some resource to run
- // the action.
- // * `UNAVAILABLE`: Due to a transient condition, such as all workers being
- // occupied (and the server does not support a queue), the action could not
- // be started. The client should retry.
- // * `INTERNAL`: An internal error occurred in the execution engine or the
- // worker.
- // * `DEADLINE_EXCEEDED`: The execution timed out.
- //
- // In the case of a missing input or command, the server SHOULD additionally
- // send a [PreconditionFailure][google.rpc.PreconditionFailure] error detail
- // where, for each requested blob not present in the CAS, there is a
- // `Violation` with a `type` of `MISSING` and a `subject` of
- // `"blobs/{hash}/{size}"` indicating the digest of the missing blob.
- rpc Execute(ExecuteRequest) returns (stream google.longrunning.Operation) {
- option (google.api.http) = { post: "/v2/{instance_name=**}/actions:execute" body: "*" };
- }
-
- // Wait for an execution operation to complete. When the client initially
- // makes the request, the server immediately responds with the current status
- // of the execution. The server will leave the request stream open until the
- // operation completes, and then respond with the completed operation. The
- // server MAY choose to stream additional updates as execution progresses,
- // such as to provide an update as to the state of the execution.
- rpc WaitExecution(WaitExecutionRequest) returns (stream google.longrunning.Operation) {
- option (google.api.http) = { post: "/v2/{name=operations/**}:waitExecution" body: "*" };
- }
-}
-
-// The action cache API is used to query whether a given action has already been
-// performed and, if so, retrieve its result. Unlike the
-// [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage],
-// which addresses blobs by their own content, the action cache addresses the
-// [ActionResult][build.bazel.remote.execution.v2.ActionResult] by a
-// digest of the encoded [Action][build.bazel.remote.execution.v2.Action]
-// which produced them.
-//
-// The lifetime of entries in the action cache is implementation-specific, but
-// the server SHOULD assume that more recently used entries are more likely to
-// be used again. Additionally, action cache implementations SHOULD ensure that
-// any blobs referenced in the
-// [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage]
-// are still valid when returning a result.
-//
-// As with other services in the Remote Execution API, any call may return an
-// error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
-// information about when the client should retry the request; clients SHOULD
-// respect the information provided.
-service ActionCache {
- // Retrieve a cached execution result.
- //
- // Errors:
- // * `NOT_FOUND`: The requested `ActionResult` is not in the cache.
- rpc GetActionResult(GetActionResultRequest) returns (ActionResult) {
- option (google.api.http) = { get: "/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}" };
- }
-
- // Upload a new execution result.
- //
- // This method is intended for servers which implement the distributed cache
- // independently of the
- // [Execution][build.bazel.remote.execution.v2.Execution] API. As a
- // result, it is OPTIONAL for servers to implement.
- //
- // In order to allow the server to perform access control based on the type of
- // action, and to assist with client debugging, the client MUST first upload
- // the [Action][build.bazel.remote.execution.v2.Execution] that produced the
- // result, along with its
- // [Command][build.bazel.remote.execution.v2.Command], into the
- // `ContentAddressableStorage`.
- //
- // Errors:
- // * `NOT_IMPLEMENTED`: This method is not supported by the server.
- // * `RESOURCE_EXHAUSTED`: There is insufficient storage space to add the
- // entry to the cache.
- rpc UpdateActionResult(UpdateActionResultRequest) returns (ActionResult) {
- option (google.api.http) = { put: "/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}" body: "action_result" };
- }
-}
-
-// The CAS (content-addressable storage) is used to store the inputs to and
-// outputs from the execution service. Each piece of content is addressed by the
-// digest of its binary data.
-//
-// Most of the binary data stored in the CAS is opaque to the execution engine,
-// and is only used as a communication medium. In order to build an
-// [Action][build.bazel.remote.execution.v2.Action],
-// however, the client will need to also upload the
-// [Command][build.bazel.remote.execution.v2.Command] and input root
-// [Directory][build.bazel.remote.execution.v2.Directory] for the Action.
-// The Command and Directory messages must be marshalled to wire format and then
-// uploaded under the hash as with any other piece of content. In practice, the
-// input root directory is likely to refer to other Directories in its
-// hierarchy, which must also each be uploaded on their own.
-//
-// For small file uploads the client should group them together and call
-// [BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs]
-// on chunks of no more than 10 MiB. For large uploads, the client must use the
-// [Write method][google.bytestream.ByteStream.Write] of the ByteStream API. The
-// `resource_name` is `{instance_name}/uploads/{uuid}/blobs/{hash}/{size}`,
-// where `instance_name` is as described in the next paragraph, `uuid` is a
-// version 4 UUID generated by the client, and `hash` and `size` are the
-// [Digest][build.bazel.remote.execution.v2.Digest] of the blob. The
-// `uuid` is used only to avoid collisions when multiple clients try to upload
-// the same file (or the same client tries to upload the file multiple times at
-// once on different threads), so the client MAY reuse the `uuid` for uploading
-// different blobs. The `resource_name` may optionally have a trailing filename
-// (or other metadata) for a client to use if it is storing URLs, as in
-// `{instance}/uploads/{uuid}/blobs/{hash}/{size}/foo/bar/baz.cc`. Anything
-// after the `size` is ignored.
-//
-// A single server MAY support multiple instances of the execution system, each
-// with their own workers, storage, cache, etc. The exact relationship between
-// instances is up to the server. If the server does, then the `instance_name`
-// is an identifier, possibly containing multiple path segments, used to
-// distinguish between the various instances on the server, in a manner defined
-// by the server. For servers which do not support multiple instances, then the
-// `instance_name` is the empty path and the leading slash is omitted, so that
-// the `resource_name` becomes `uploads/{uuid}/blobs/{hash}/{size}`.
-//
-// When attempting an upload, if another client has already completed the upload
-// (which may occur in the middle of a single upload if another client uploads
-// the same blob concurrently), the request will terminate immediately with
-// a response whose `committed_size` is the full size of the uploaded file
-// (regardless of how much data was transmitted by the client). If the client
-// completes the upload but the
-// [Digest][build.bazel.remote.execution.v2.Digest] does not match, an
-// `INVALID_ARGUMENT` error will be returned. In either case, the client should
-// not attempt to retry the upload.
-//
-// For downloading blobs, the client must use the
-// [Read method][google.bytestream.ByteStream.Read] of the ByteStream API, with
-// a `resource_name` of `"{instance_name}/blobs/{hash}/{size}"`, where
-// `instance_name` is the instance name (see above), and `hash` and `size` are
-// the [Digest][build.bazel.remote.execution.v2.Digest] of the blob.
-//
-// The lifetime of entries in the CAS is implementation specific, but it SHOULD
-// be long enough to allow for newly-added and recently looked-up entries to be
-// used in subsequent calls (e.g. to
-// [Execute][build.bazel.remote.execution.v2.Execution.Execute]).
-//
-// As with other services in the Remote Execution API, any call may return an
-// error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
-// information about when the client should retry the request; clients SHOULD
-// respect the information provided.
-service ContentAddressableStorage {
- // Determine if blobs are present in the CAS.
- //
- // Clients can use this API before uploading blobs to determine which ones are
- // already present in the CAS and do not need to be uploaded again.
- //
- // There are no method-specific errors.
- rpc FindMissingBlobs(FindMissingBlobsRequest) returns (FindMissingBlobsResponse) {
- option (google.api.http) = { post: "/v2/{instance_name=**}/blobs:findMissing" body: "*" };
- }
-
- // Upload many blobs at once.
- //
- // The server may enforce a limit of the combined total size of blobs
- // to be uploaded using this API. This limit may be obtained using the
- // [Capabilities][build.bazel.remote.execution.v2.Capabilities] API.
- // Requests exceeding the limit should either be split into smaller
- // chunks or uploaded using the
- // [ByteStream API][google.bytestream.ByteStream], as appropriate.
- //
- // This request is equivalent to calling a Bytestream `Write` request
- // on each individual blob, in parallel. The requests may succeed or fail
- // independently.
- //
- // Errors:
- // * `INVALID_ARGUMENT`: The client attempted to upload more than the
- // server supported limit.
- //
- // Individual requests may return the following errors, additionally:
- // * `RESOURCE_EXHAUSTED`: There is insufficient disk quota to store the blob.
- // * `INVALID_ARGUMENT`: The
- // [Digest][build.bazel.remote.execution.v2.Digest] does not match the
- // provided data.
- rpc BatchUpdateBlobs(BatchUpdateBlobsRequest) returns (BatchUpdateBlobsResponse) {
- option (google.api.http) = { post: "/v2/{instance_name=**}/blobs:batchUpdate" body: "*" };
- }
-
- // Download many blobs at once.
- //
- // The server may enforce a limit of the combined total size of blobs
- // to be downloaded using this API. This limit may be obtained using the
- // [Capabilities][build.bazel.remote.execution.v2.Capabilities] API.
- // Requests exceeding the limit should either be split into smaller
- // chunks or downloaded using the
- // [ByteStream API][google.bytestream.ByteStream], as appropriate.
- //
- // This request is equivalent to calling a Bytestream `Read` request
- // on each individual blob, in parallel. The requests may succeed or fail
- // independently.
- //
- // Errors:
- // * `INVALID_ARGUMENT`: The client attempted to read more than the
- // server supported limit.
- //
- // Every error on individual read will be returned in the corresponding digest
- // status.
- rpc BatchReadBlobs(BatchReadBlobsRequest) returns (BatchReadBlobsResponse) {
- option (google.api.http) = { post: "/v2/{instance_name=**}/blobs:batchRead" body: "*" };
- }
-
- // Fetch the entire directory tree rooted at a node.
- //
- // This request must be targeted at a
- // [Directory][build.bazel.remote.execution.v2.Directory] stored in the
- // [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage]
- // (CAS). The server will enumerate the `Directory` tree recursively and
- // return every node descended from the root.
- //
- // The GetTreeRequest.page_token parameter can be used to skip ahead in
- // the stream (e.g. when retrying a partially completed and aborted request),
- // by setting it to a value taken from GetTreeResponse.next_page_token of the
- // last successfully processed GetTreeResponse).
- //
- // The exact traversal order is unspecified and, unless retrieving subsequent
- // pages from an earlier request, is not guaranteed to be stable across
- // multiple invocations of `GetTree`.
- //
- // If part of the tree is missing from the CAS, the server will return the
- // portion present and omit the rest.
- //
- // * `NOT_FOUND`: The requested tree root is not present in the CAS.
- rpc GetTree(GetTreeRequest) returns (stream GetTreeResponse) {
- option (google.api.http) = { get: "/v2/{instance_name=**}/blobs/{root_digest.hash}/{root_digest.size_bytes}:getTree" };
- }
-}
-
-// The Capabilities service may be used by remote execution clients to query
-// various server properties, in order to self-configure or return meaningful
-// error messages.
-//
-// The query may include a particular `instance_name`, in which case the values
-// returned will pertain to that instance.
-service Capabilities {
- // GetCapabilities returns the server capabilities configuration.
- rpc GetCapabilities(GetCapabilitiesRequest) returns (ServerCapabilities) {
- option (google.api.http) = {
- get: "/v2/{instance_name=**}/capabilities"
- };
- }
-}
-
-// An `Action` captures all the information about an execution which is required
-// to reproduce it.
-//
-// `Action`s are the core component of the [Execution] service. A single
-// `Action` represents a repeatable action that can be performed by the
-// execution service. `Action`s can be succinctly identified by the digest of
-// their wire format encoding and, once an `Action` has been executed, will be
-// cached in the action cache. Future requests can then use the cached result
-// rather than needing to run afresh.
-//
-// When a server completes execution of an
-// [Action][build.bazel.remote.execution.v2.Action], it MAY choose to
-// cache the [result][build.bazel.remote.execution.v2.ActionResult] in
-// the [ActionCache][build.bazel.remote.execution.v2.ActionCache] unless
-// `do_not_cache` is `true`. Clients SHOULD expect the server to do so. By
-// default, future calls to
-// [Execute][build.bazel.remote.execution.v2.Execution.Execute] the same
-// `Action` will also serve their results from the cache. Clients must take care
-// to understand the caching behaviour. Ideally, all `Action`s will be
-// reproducible so that serving a result from cache is always desirable and
-// correct.
-message Action {
- // The digest of the [Command][build.bazel.remote.execution.v2.Command]
- // to run, which MUST be present in the
- // [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage].
- Digest command_digest = 1;
-
- // The digest of the root
- // [Directory][build.bazel.remote.execution.v2.Directory] for the input
- // files. The files in the directory tree are available in the correct
- // location on the build machine before the command is executed. The root
- // directory, as well as every subdirectory and content blob referred to, MUST
- // be in the
- // [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage].
- Digest input_root_digest = 2;
-
- reserved 3 to 5; // Used for fields moved to [Command][build.bazel.remote.execution.v2.Command].
-
- // A timeout after which the execution should be killed. If the timeout is
- // absent, then the client is specifying that the execution should continue
- // as long as the server will let it. The server SHOULD impose a timeout if
- // the client does not specify one, however, if the client does specify a
- // timeout that is longer than the server's maximum timeout, the server MUST
- // reject the request.
- //
- // The timeout is a part of the
- // [Action][build.bazel.remote.execution.v2.Action] message, and
- // therefore two `Actions` with different timeouts are different, even if they
- // are otherwise identical. This is because, if they were not, running an
- // `Action` with a lower timeout than is required might result in a cache hit
- // from an execution run with a longer timeout, hiding the fact that the
- // timeout is too short. By encoding it directly in the `Action`, a lower
- // timeout will result in a cache miss and the execution timeout will fail
- // immediately, rather than whenever the cache entry gets evicted.
- google.protobuf.Duration timeout = 6;
-
- // If true, then the `Action`'s result cannot be cached.
- bool do_not_cache = 7;
-}
-
-// A `Command` is the actual command executed by a worker running an
-// [Action][build.bazel.remote.execution.v2.Action] and specifications of its
-// environment.
-//
-// Except as otherwise required, the environment (such as which system
-// libraries or binaries are available, and what filesystems are mounted where)
-// is defined by and specific to the implementation of the remote execution API.
-message Command {
- // An `EnvironmentVariable` is one variable to set in the running program's
- // environment.
- message EnvironmentVariable {
- // The variable name.
- string name = 1;
-
- // The variable value.
- string value = 2;
- }
-
- // The arguments to the command. The first argument must be the path to the
- // executable, which must be either a relative path, in which case it is
- // evaluated with respect to the input root, or an absolute path.
- repeated string arguments = 1;
-
- // The environment variables to set when running the program. The worker may
- // provide its own default environment variables; these defaults can be
- // overridden using this field. Additional variables can also be specified.
- //
- // In order to ensure that equivalent `Command`s always hash to the same
- // value, the environment variables MUST be lexicographically sorted by name.
- // Sorting of strings is done by code point, equivalently, by the UTF-8 bytes.
- repeated EnvironmentVariable environment_variables = 2;
-
- // A list of the output files that the client expects to retrieve from the
- // action. Only the listed files, as well as directories listed in
- // `output_directories`, will be returned to the client as output.
- // Other files that may be created during command execution are discarded.
- //
- // The paths are relative to the working directory of the action execution.
- // The paths are specified using a single forward slash (`/`) as a path
- // separator, even if the execution platform natively uses a different
- // separator. The path MUST NOT include a trailing slash, nor a leading slash,
- // being a relative path.
- //
- // In order to ensure consistent hashing of the same Action, the output paths
- // MUST be sorted lexicographically by code point (or, equivalently, by UTF-8
- // bytes).
- //
- // An output file cannot be duplicated, be a parent of another output file, be
- // a child of a listed output directory, or have the same path as any of the
- // listed output directories.
- repeated string output_files = 3;
-
- // A list of the output directories that the client expects to retrieve from
- // the action. Only the contents of the indicated directories (recursively
- // including the contents of their subdirectories) will be
- // returned, as well as files listed in `output_files`. Other files that may
- // be created during command execution are discarded.
- //
- // The paths are relative to the working directory of the action execution.
- // The paths are specified using a single forward slash (`/`) as a path
- // separator, even if the execution platform natively uses a different
- // separator. The path MUST NOT include a trailing slash, nor a leading slash,
- // being a relative path. The special value of empty string is allowed,
- // although not recommended, and can be used to capture the entire working
- // directory tree, including inputs.
- //
- // In order to ensure consistent hashing of the same Action, the output paths
- // MUST be sorted lexicographically by code point (or, equivalently, by UTF-8
- // bytes).
- //
- // An output directory cannot be duplicated, be a parent of another output
- // directory, be a parent of a listed output file, or have the same path as
- // any of the listed output files.
- repeated string output_directories = 4;
-
- // The platform requirements for the execution environment. The server MAY
- // choose to execute the action on any worker satisfying the requirements, so
- // the client SHOULD ensure that running the action on any such worker will
- // have the same result.
- Platform platform = 5;
-
- // The working directory, relative to the input root, for the command to run
- // in. It must be a directory which exists in the input tree. If it is left
- // empty, then the action is run in the input root.
- string working_directory = 6;
-}
-
-// A `Platform` is a set of requirements, such as hardware, operating system, or
-// compiler toolchain, for an
-// [Action][build.bazel.remote.execution.v2.Action]'s execution
-// environment. A `Platform` is represented as a series of key-value pairs
-// representing the properties that are required of the platform.
-message Platform {
- // A single property for the environment. The server is responsible for
- // specifying the property `name`s that it accepts. If an unknown `name` is
- // provided in the requirements for an
- // [Action][build.bazel.remote.execution.v2.Action], the server SHOULD
- // reject the execution request. If permitted by the server, the same `name`
- // may occur multiple times.
- //
- // The server is also responsible for specifying the interpretation of
- // property `value`s. For instance, a property describing how much RAM must be
- // available may be interpreted as allowing a worker with 16GB to fulfill a
- // request for 8GB, while a property describing the OS environment on which
- // the action must be performed may require an exact match with the worker's
- // OS.
- //
- // The server MAY use the `value` of one or more properties to determine how
- // it sets up the execution environment, such as by making specific system
- // files available to the worker.
- message Property {
- // The property name.
- string name = 1;
-
- // The property value.
- string value = 2;
- }
-
- // The properties that make up this platform. In order to ensure that
- // equivalent `Platform`s always hash to the same value, the properties MUST
- // be lexicographically sorted by name, and then by value. Sorting of strings
- // is done by code point, equivalently, by the UTF-8 bytes.
- repeated Property properties = 1;
-}
-
-// A `Directory` represents a directory node in a file tree, containing zero or
-// more children [FileNodes][build.bazel.remote.execution.v2.FileNode],
-// [DirectoryNodes][build.bazel.remote.execution.v2.DirectoryNode] and
-// [SymlinkNodes][build.bazel.remote.execution.v2.SymlinkNode].
-// Each `Node` contains its name in the directory, either the digest of its
-// content (either a file blob or a `Directory` proto) or a symlink target, as
-// well as possibly some metadata about the file or directory.
-//
-// In order to ensure that two equivalent directory trees hash to the same
-// value, the following restrictions MUST be obeyed when constructing a
-// a `Directory`:
-// - Every child in the directory must have a path of exactly one segment.
-// Multiple levels of directory hierarchy may not be collapsed.
-// - Each child in the directory must have a unique path segment (file name).
-// - The files, directories and symlinks in the directory must each be sorted
-// in lexicographical order by path. The path strings must be sorted by code
-// point, equivalently, by UTF-8 bytes.
-//
-// A `Directory` that obeys the restrictions is said to be in canonical form.
-//
-// As an example, the following could be used for a file named `bar` and a
-// directory named `foo` with an executable file named `baz` (hashes shortened
-// for readability):
-//
-// ```json
-// // (Directory proto)
-// {
-// files: [
-// {
-// name: "bar",
-// digest: {
-// hash: "4a73bc9d03...",
-// size: 65534
-// }
-// }
-// ],
-// directories: [
-// {
-// name: "foo",
-// digest: {
-// hash: "4cf2eda940...",
-// size: 43
-// }
-// }
-// ]
-// }
-//
-// // (Directory proto with hash "4cf2eda940..." and size 43)
-// {
-// files: [
-// {
-// name: "baz",
-// digest: {
-// hash: "b2c941073e...",
-// size: 1294,
-// },
-// is_executable: true
-// }
-// ]
-// }
-// ```
-message Directory {
- // The files in the directory.
- repeated FileNode files = 1;
-
- // The subdirectories in the directory.
- repeated DirectoryNode directories = 2;
-
- // The symlinks in the directory.
- repeated SymlinkNode symlinks = 3;
-}
-
-// A `FileNode` represents a single file and associated metadata.
-message FileNode {
- // The name of the file.
- string name = 1;
-
- // The digest of the file's content.
- Digest digest = 2;
-
- reserved 3; // Reserved to ensure wire-compatibility with `OutputFile`.
-
- // True if file is executable, false otherwise.
- bool is_executable = 4;
-}
-
-// A `DirectoryNode` represents a child of a
-// [Directory][build.bazel.remote.execution.v2.Directory] which is itself
-// a `Directory` and its associated metadata.
-message DirectoryNode {
- // The name of the directory.
- string name = 1;
-
- // The digest of the
- // [Directory][build.bazel.remote.execution.v2.Directory] object
- // represented. See [Digest][build.bazel.remote.execution.v2.Digest]
- // for information about how to take the digest of a proto message.
- Digest digest = 2;
-}
-
-// A `SymlinkNode` represents a symbolic link.
-message SymlinkNode {
- // The name of the symlink.
- string name = 1;
-
- // The target path of the symlink. The path separator is a forward slash `/`.
- // The target path can be relative to the parent directory of the symlink or
- // it can be an absolute path starting with `/`. Support for absolute paths
- // can be checked using the [Capabilities][build.bazel.remote.execution.v2.Capabilities]
- // API. The canonical form forbids the substrings `/./` and `//` in the target
- // path. `..` components are allowed anywhere in the target path.
- string target = 2;
-}
-
-// A content digest. A digest for a given blob consists of the size of the blob
-// and its hash. The hash algorithm to use is defined by the server, but servers
-// SHOULD use SHA-256.
-//
-// The size is considered to be an integral part of the digest and cannot be
-// separated. That is, even if the `hash` field is correctly specified but
-// `size_bytes` is not, the server MUST reject the request.
-//
-// The reason for including the size in the digest is as follows: in a great
-// many cases, the server needs to know the size of the blob it is about to work
-// with prior to starting an operation with it, such as flattening Merkle tree
-// structures or streaming it to a worker. Technically, the server could
-// implement a separate metadata store, but this results in a significantly more
-// complicated implementation as opposed to having the client specify the size
-// up-front (or storing the size along with the digest in every message where
-// digests are embedded). This does mean that the API leaks some implementation
-// details of (what we consider to be) a reasonable server implementation, but
-// we consider this to be a worthwhile tradeoff.
-//
-// When a `Digest` is used to refer to a proto message, it always refers to the
-// message in binary encoded form. To ensure consistent hashing, clients and
-// servers MUST ensure that they serialize messages according to the following
-// rules, even if there are alternate valid encodings for the same message.
-// - Fields are serialized in tag order.
-// - There are no unknown fields.
-// - There are no duplicate fields.
-// - Fields are serialized according to the default semantics for their type.
-//
-// Most protocol buffer implementations will always follow these rules when
-// serializing, but care should be taken to avoid shortcuts. For instance,
-// concatenating two messages to merge them may produce duplicate fields.
-message Digest {
- // The hash. In the case of SHA-256, it will always be a lowercase hex string
- // exactly 64 characters long.
- string hash = 1;
-
- // The size of the blob, in bytes.
- int64 size_bytes = 2;
-}
-
-// ExecutedActionMetadata contains details about a completed execution.
-message ExecutedActionMetadata {
- // The name of the worker which ran the execution.
- string worker = 1;
-
- // When was the action added to the queue.
- google.protobuf.Timestamp queued_timestamp = 2;
-
- // When the worker received the action.
- google.protobuf.Timestamp worker_start_timestamp = 3;
-
- // When the worker completed the action, including all stages.
- google.protobuf.Timestamp worker_completed_timestamp = 4;
-
- // When the worker started fetching action inputs.
- google.protobuf.Timestamp input_fetch_start_timestamp = 5;
-
- // When the worker finished fetching action inputs.
- google.protobuf.Timestamp input_fetch_completed_timestamp = 6;
-
- // When the worker started executing the action command.
- google.protobuf.Timestamp execution_start_timestamp = 7;
-
- // When the worker completed executing the action command.
- google.protobuf.Timestamp execution_completed_timestamp = 8;
-
- // When the worker started uploading action outputs.
- google.protobuf.Timestamp output_upload_start_timestamp = 9;
-
- // When the worker finished uploading action outputs.
- google.protobuf.Timestamp output_upload_completed_timestamp = 10;
-}
-
-// An ActionResult represents the result of an
-// [Action][build.bazel.remote.execution.v2.Action] being run.
-message ActionResult {
- reserved 1; // Reserved for use as the resource name.
-
- // The output files of the action. For each output file requested in the
- // `output_files` field of the Action, if the corresponding file existed after
- // the action completed, a single entry will be present in the output list.
- //
- // If the action does not produce the requested output, or produces a
- // directory where a regular file is expected or vice versa, then that output
- // will be omitted from the list. The server is free to arrange the output
- // list as desired; clients MUST NOT assume that the output list is sorted.
- repeated OutputFile output_files = 2;
-
- // The output directories of the action. For each output directory requested
- // in the `output_directories` field of the Action, if the corresponding
- // directory existed after the action completed, a single entry will be
- // present in the output list, which will contain the digest of a
- // [Tree][build.bazel.remote.execution.v2.Tree] message containing the
- // directory tree, and the path equal exactly to the corresponding Action
- // output_directories member.
- //
- // As an example, suppose the Action had an output directory `a/b/dir` and the
- // execution produced the following contents in `a/b/dir`: a file named `bar`
- // and a directory named `foo` with an executable file named `baz`. Then,
- // output_directory will contain (hashes shortened for readability):
- //
- // ```json
- // // OutputDirectory proto:
- // {
- // path: "a/b/dir"
- // tree_digest: {
- // hash: "4a73bc9d03...",
- // size: 55
- // }
- // }
- // // Tree proto with hash "4a73bc9d03..." and size 55:
- // {
- // root: {
- // files: [
- // {
- // name: "bar",
- // digest: {
- // hash: "4a73bc9d03...",
- // size: 65534
- // }
- // }
- // ],
- // directories: [
- // {
- // name: "foo",
- // digest: {
- // hash: "4cf2eda940...",
- // size: 43
- // }
- // }
- // ]
- // }
- // children : {
- // // (Directory proto with hash "4cf2eda940..." and size 43)
- // files: [
- // {
- // name: "baz",
- // digest: {
- // hash: "b2c941073e...",
- // size: 1294,
- // },
- // is_executable: true
- // }
- // ]
- // }
- // }
- // ```
- repeated OutputDirectory output_directories = 3;
-
- // The exit code of the command.
- int32 exit_code = 4;
-
- // The standard output buffer of the action. The server will determine, based
- // on the size of the buffer, whether to return it in raw form or to return
- // a digest in `stdout_digest` that points to the buffer. If neither is set,
- // then the buffer is empty. The client SHOULD NOT assume it will get one of
- // the raw buffer or a digest on any given request and should be prepared to
- // handle either.
- bytes stdout_raw = 5;
-
- // The digest for a blob containing the standard output of the action, which
- // can be retrieved from the
- // [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage].
- // See `stdout_raw` for when this will be set.
- Digest stdout_digest = 6;
-
- // The standard error buffer of the action. The server will determine, based
- // on the size of the buffer, whether to return it in raw form or to return
- // a digest in `stderr_digest` that points to the buffer. If neither is set,
- // then the buffer is empty. The client SHOULD NOT assume it will get one of
- // the raw buffer or a digest on any given request and should be prepared to
- // handle either.
- bytes stderr_raw = 7;
-
- // The digest for a blob containing the standard error of the action, which
- // can be retrieved from the
- // [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage].
- // See `stderr_raw` for when this will be set.
- Digest stderr_digest = 8;
-
- // The details of the execution that originally produced this result.
- ExecutedActionMetadata execution_metadata = 9;
-}
-
-// An `OutputFile` is similar to a
-// [FileNode][build.bazel.remote.execution.v2.FileNode], but it is used as an
-// output in an `ActionResult`. It allows a full file path rather than
-// only a name.
-//
-// `OutputFile` is binary-compatible with `FileNode`.
-message OutputFile {
- // The full path of the file relative to the input root, including the
- // filename. The path separator is a forward slash `/`. Since this is a
- // relative path, it MUST NOT begin with a leading forward slash.
- string path = 1;
-
- // The digest of the file's content.
- Digest digest = 2;
-
- reserved 3; // Used for a removed field in an earlier version of the API.
-
- // True if file is executable, false otherwise.
- bool is_executable = 4;
-}
-
-// A `Tree` contains all the
-// [Directory][build.bazel.remote.execution.v2.Directory] protos in a
-// single directory Merkle tree, compressed into one message.
-message Tree {
- // The root directory in the tree.
- Directory root = 1;
-
- // All the child directories: the directories referred to by the root and,
- // recursively, all its children. In order to reconstruct the directory tree,
- // the client must take the digests of each of the child directories and then
- // build up a tree starting from the `root`.
- repeated Directory children = 2;
-}
-
-// An `OutputDirectory` is the output in an `ActionResult` corresponding to a
-// directory's full contents rather than a single file.
-message OutputDirectory {
- // The full path of the directory relative to the working directory. The path
- // separator is a forward slash `/`. Since this is a relative path, it MUST
- // NOT begin with a leading forward slash. The empty string value is allowed,
- // and it denotes the entire working directory.
- string path = 1;
-
- reserved 2; // Used for a removed field in an earlier version of the API.
-
- // The digest of the encoded
- // [Tree][build.bazel.remote.execution.v2.Tree] proto containing the
- // directory's contents.
- Digest tree_digest = 3;
-}
-
-// An `ExecutionPolicy` can be used to control the scheduling of the action.
-message ExecutionPolicy {
- // The priority (relative importance) of this action. Generally, a lower value
- // means that the action should be run sooner than actions having a greater
- // priority value, but the interpretation of a given value is server-
- // dependent. A priority of 0 means the *default* priority. Priorities may be
- // positive or negative, and such actions should run later or sooner than
- // actions having the default priority, respectively. The particular semantics
- // of this field is up to the server. In particular, every server will have
- // their own supported range of priorities, and will decide how these map into
- // scheduling policy.
- int32 priority = 1;
-}
-
-// A `ResultsCachePolicy` is used for fine-grained control over how action
-// outputs are stored in the CAS and Action Cache.
-message ResultsCachePolicy {
- // The priority (relative importance) of this content in the overall cache.
- // Generally, a lower value means a longer retention time or other advantage,
- // but the interpretation of a given value is server-dependent. A priority of
- // 0 means a *default* value, decided by the server.
- //
- // The particular semantics of this field is up to the server. In particular,
- // every server will have their own supported range of priorities, and will
- // decide how these map into retention/eviction policy.
- int32 priority = 1;
-}
-
-// A request message for
-// [Execution.Execute][build.bazel.remote.execution.v2.Execution.Execute].
-message ExecuteRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // If true, the action will be executed anew even if its result was already
- // present in the cache. If false, the result may be served from the
- // [ActionCache][build.bazel.remote.execution.v2.ActionCache].
- bool skip_cache_lookup = 3;
-
- reserved 2, 4, 5; // Used for removed fields in an earlier version of the API.
-
- // The digest of the [Action][build.bazel.remote.execution.v2.Action] to
- // execute.
- Digest action_digest = 6;
-
- // An optional policy for execution of the action.
- // The server will have a default policy if this is not provided.
- ExecutionPolicy execution_policy = 7;
-
- // An optional policy for the results of this execution in the remote cache.
- // The server will have a default policy if this is not provided.
- // This may be applied to both the ActionResult and the associated blobs.
- ResultsCachePolicy results_cache_policy = 8;
-}
-
-// A `LogFile` is a log stored in the CAS.
-message LogFile {
- // The digest of the log contents.
- Digest digest = 1;
-
- // This is a hint as to the purpose of the log, and is set to true if the log
- // is human-readable text that can be usefully displayed to a user, and false
- // otherwise. For instance, if a command-line client wishes to print the
- // server logs to the terminal for a failed action, this allows it to avoid
- // displaying a binary file.
- bool human_readable = 2;
-}
-
-// The response message for
-// [Execution.Execute][build.bazel.remote.execution.v2.Execution.Execute],
-// which will be contained in the [response
-// field][google.longrunning.Operation.response] of the
-// [Operation][google.longrunning.Operation].
-message ExecuteResponse {
- // The result of the action.
- ActionResult result = 1;
-
- // True if the result was served from cache, false if it was executed.
- bool cached_result = 2;
-
- // If the status has a code other than `OK`, it indicates that the action did
- // not finish execution. For example, if the operation times out during
- // execution, the status will have a `DEADLINE_EXCEEDED` code. Servers MUST
- // use this field for errors in execution, rather than the error field on the
- // `Operation` object.
- //
- // If the status code is other than `OK`, then the result MUST NOT be cached.
- // For an error status, the `result` field is optional; the server may
- // populate the output-, stdout-, and stderr-related fields if it has any
- // information available, such as the stdout and stderr of a timed-out action.
- google.rpc.Status status = 3;
-
- // An optional list of additional log outputs the server wishes to provide. A
- // server can use this to return execution-specific logs however it wishes.
- // This is intended primarily to make it easier for users to debug issues that
- // may be outside of the actual job execution, such as by identifying the
- // worker executing the action or by providing logs from the worker's setup
- // phase. The keys SHOULD be human readable so that a client can display them
- // to a user.
- map<string, LogFile> server_logs = 4;
-}
-
-// Metadata about an ongoing
-// [execution][build.bazel.remote.execution.v2.Execution.Execute], which
-// will be contained in the [metadata
-// field][google.longrunning.Operation.response] of the
-// [Operation][google.longrunning.Operation].
-message ExecuteOperationMetadata {
- // The current stage of execution.
- enum Stage {
- UNKNOWN = 0;
-
- // Checking the result against the cache.
- CACHE_CHECK = 1;
-
- // Currently idle, awaiting a free machine to execute.
- QUEUED = 2;
-
- // Currently being executed by a worker.
- EXECUTING = 3;
-
- // Finished execution.
- COMPLETED = 4;
- }
-
- Stage stage = 1;
-
- // The digest of the [Action][build.bazel.remote.execution.v2.Action]
- // being executed.
- Digest action_digest = 2;
-
- // If set, the client can use this name with
- // [ByteStream.Read][google.bytestream.ByteStream.Read] to stream the
- // standard output.
- string stdout_stream_name = 3;
-
- // If set, the client can use this name with
- // [ByteStream.Read][google.bytestream.ByteStream.Read] to stream the
- // standard error.
- string stderr_stream_name = 4;
-}
-
-// A request message for
-// [WaitExecution][build.bazel.remote.execution.v2.Execution.WaitExecution].
-message WaitExecutionRequest {
- // The name of the [Operation][google.longrunning.operations.v1.Operation]
- // returned by [Execute][build.bazel.remote.execution.v2.Execution.Execute].
- string name = 1;
-}
-
-// A request message for
-// [ActionCache.GetActionResult][build.bazel.remote.execution.v2.ActionCache.GetActionResult].
-message GetActionResultRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // The digest of the [Action][build.bazel.remote.execution.v2.Action]
- // whose result is requested.
- Digest action_digest = 2;
-}
-
-// A request message for
-// [ActionCache.UpdateActionResult][build.bazel.remote.execution.v2.ActionCache.UpdateActionResult].
-message UpdateActionResultRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // The digest of the [Action][build.bazel.remote.execution.v2.Action]
- // whose result is being uploaded.
- Digest action_digest = 2;
-
- // The [ActionResult][build.bazel.remote.execution.v2.ActionResult]
- // to store in the cache.
- ActionResult action_result = 3;
-
- // An optional policy for the results of this execution in the remote cache.
- // The server will have a default policy if this is not provided.
- // This may be applied to both the ActionResult and the associated blobs.
- ResultsCachePolicy results_cache_policy = 4;
-}
-
-// A request message for
-// [ContentAddressableStorage.FindMissingBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.FindMissingBlobs].
-message FindMissingBlobsRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // A list of the blobs to check.
- repeated Digest blob_digests = 2;
-}
-
-// A response message for
-// [ContentAddressableStorage.FindMissingBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.FindMissingBlobs].
-message FindMissingBlobsResponse {
- // A list of the blobs requested *not* present in the storage.
- repeated Digest missing_blob_digests = 2;
-}
-
-// A request message for
-// [ContentAddressableStorage.BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs].
-message BatchUpdateBlobsRequest {
- // A request corresponding to a single blob that the client wants to upload.
- message Request {
- // The digest of the blob. This MUST be the digest of `data`.
- Digest digest = 1;
-
- // The raw binary data.
- bytes data = 2;
- }
-
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // The individual upload requests.
- repeated Request requests = 2;
-}
-
-// A response message for
-// [ContentAddressableStorage.BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs].
-message BatchUpdateBlobsResponse {
- // A response corresponding to a single blob that the client tried to upload.
- message Response {
- // The blob digest to which this response corresponds.
- Digest digest = 1;
-
- // The result of attempting to upload that blob.
- google.rpc.Status status = 2;
- }
-
- // The responses to the requests.
- repeated Response responses = 1;
-}
-
-// A request message for
-// [ContentAddressableStorage.BatchReadBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchReadBlobs].
-message BatchReadBlobsRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // The individual blob digests.
- repeated Digest digests = 2;
-}
-
-// A response message for
-// [ContentAddressableStorage.BatchReadBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchReadBlobs].
-message BatchReadBlobsResponse {
- // A response corresponding to a single blob that the client tried to upload.
- message Response {
- // The digest to which this response corresponds.
- Digest digest = 1;
-
- // The raw binary data.
- bytes data = 2;
-
- // The result of attempting to download that blob.
- google.rpc.Status status = 3;
- }
-
- // The responses to the requests.
- repeated Response responses = 1;
-}
-
-// A request message for
-// [ContentAddressableStorage.GetTree][build.bazel.remote.execution.v2.ContentAddressableStorage.GetTree].
-message GetTreeRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // The digest of the root, which must be an encoded
- // [Directory][build.bazel.remote.execution.v2.Directory] message
- // stored in the
- // [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage].
- Digest root_digest = 2;
-
- // A maximum page size to request. If present, the server will request no more
- // than this many items. Regardless of whether a page size is specified, the
- // server may place its own limit on the number of items to be returned and
- // require the client to retrieve more items using a subsequent request.
- int32 page_size = 3;
-
- // A page token, which must be a value received in a previous
- // [GetTreeResponse][build.bazel.remote.execution.v2.GetTreeResponse].
- // If present, the server will use it to return the following page of results.
- string page_token = 4;
-}
-
-// A response message for
-// [ContentAddressableStorage.GetTree][build.bazel.remote.execution.v2.ContentAddressableStorage.GetTree].
-message GetTreeResponse {
- // The directories descended from the requested root.
- repeated Directory directories = 1;
-
- // If present, signifies that there are more results which the client can
- // retrieve by passing this as the page_token in a subsequent
- // [request][build.bazel.remote.execution.v2.GetTreeRequest].
- // If empty, signifies that this is the last page of results.
- string next_page_token = 2;
-}
-
-// A request message for
-// [Capabilities.GetCapabilities][google.devtools.remoteexecution.v2.Capabilities.GetCapabilities].
-message GetCapabilitiesRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-}
-
-// A response message for
-// [Capabilities.GetCapabilities][google.devtools.remoteexecution.v2.Capabilities.GetCapabilities].
-message ServerCapabilities {
- // Capabilities of the remote cache system.
- CacheCapabilities cache_capabilities = 1;
-
- // Capabilities of the remote execution system.
- ExecutionCapabilities execution_capabilities = 2;
-
- // Earliest RE API version supported, including deprecated versions.
- build.bazel.semver.SemVer deprecated_api_version = 3;
-
- // Earliest non-deprecated RE API version supported.
- build.bazel.semver.SemVer low_api_version = 4;
-
- // Latest RE API version supported.
- build.bazel.semver.SemVer high_api_version = 5;
-}
-
-// The digest function used for converting values into keys for CAS and Action
-// Cache.
-enum DigestFunction {
- UNKNOWN = 0;
- SHA256 = 1;
- SHA1 = 2;
- MD5 = 3;
-}
-
-// Describes the server/instance capabilities for updating the action cache.
-message ActionCacheUpdateCapabilities {
- bool update_enabled = 1;
-}
-
-// Allowed values for priority in
-// [ResultsCachePolicy][google.devtools.remoteexecution.v2.ResultsCachePolicy]
-// Used for querying both cache and execution valid priority ranges.
-message PriorityCapabilities {
- // Supported range of priorities, including boundaries.
- message PriorityRange {
- int32 min_priority = 1;
- int32 max_priority = 2;
- }
- repeated PriorityRange priorities = 1;
-}
-
-// Capabilities of the remote cache system.
-message CacheCapabilities {
- // Describes how the server treats absolute symlink targets.
- enum SymlinkAbsolutePathStrategy {
- UNKNOWN = 0;
-
- // Server will return an INVALID_ARGUMENT on input symlinks with absolute targets.
- // If an action tries to create an output symlink with an absolute target, a
- // FAILED_PRECONDITION will be returned.
- DISALLOWED = 1;
-
- // Server will allow symlink targets to escape the input root tree, possibly
- // resulting in non-hermetic builds.
- ALLOWED = 2;
- }
-
- // All the digest functions supported by the remote cache.
- // Remote cache may support multiple digest functions simultaneously.
- repeated DigestFunction digest_function = 1;
-
- // Capabilities for updating the action cache.
- ActionCacheUpdateCapabilities action_cache_update_capabilities = 2;
-
- // Supported cache priority range for both CAS and ActionCache.
- PriorityCapabilities cache_priority_capabilities = 3;
-
- // Maximum total size of blobs to be uploaded/downloaded using
- // batch methods. A value of 0 means no limit is set, although
- // in practice there will always be a message size limitation
- // of the protocol in use, e.g. GRPC.
- int64 max_batch_total_size_bytes = 4;
-
- // Whether absolute symlink targets are supported.
- SymlinkAbsolutePathStrategy symlink_absolute_path_strategy = 5;
-}
-
-// Capabilities of the remote execution system.
-message ExecutionCapabilities {
- // Remote execution may only support a single digest function.
- DigestFunction digest_function = 1;
-
- // Whether remote execution is enabled for the particular server/instance.
- bool exec_enabled = 2;
-
- // Supported execution priority range.
- PriorityCapabilities execution_priority_capabilities = 3;
-}
-
-// Details for the tool used to call the API.
-message ToolDetails {
- // Name of the tool, e.g. bazel.
- string tool_name = 1;
-
- // Version of the tool used for the request, e.g. 5.0.3.
- string tool_version = 2;
-}
-
-// An optional Metadata to attach to any RPC request to tell the server about an
-// external context of the request. The server may use this for logging or other
-// purposes. To use it, the client attaches the header to the call using the
-// canonical proto serialization:
-// name: build.bazel.remote.execution.v2.requestmetadata-bin
-// contents: the base64 encoded binary RequestMetadata message.
-message RequestMetadata {
- // The details for the tool invoking the requests.
- ToolDetails tool_details = 1;
-
- // An identifier that ties multiple requests to the same action.
- // For example, multiple requests to the CAS, Action Cache, and Execution
- // API are used in order to compile foo.cc.
- string action_id = 2;
-
- // An identifier that ties multiple actions together to a final result.
- // For example, multiple actions are required to build and run foo_test.
- string tool_invocation_id = 3;
-
- // An identifier to tie multiple tool invocations together. For example,
- // runs of foo_test, bar_test and baz_test on a post-submit of a given patch.
- string correlated_invocations_id = 4;
-}
diff --git a/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2.py b/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2.py
deleted file mode 100644
index 46d59b184..000000000
--- a/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2.py
+++ /dev/null
@@ -1,2660 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: build/bazel/remote/execution/v2/remote_execution.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf.internal import enum_type_wrapper
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-from buildstream._protos.build.bazel.semver import semver_pb2 as build_dot_bazel_dot_semver_dot_semver__pb2
-from buildstream._protos.google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2
-from buildstream._protos.google.longrunning import operations_pb2 as google_dot_longrunning_dot_operations__pb2
-from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2
-from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
-from buildstream._protos.google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='build/bazel/remote/execution/v2/remote_execution.proto',
- package='build.bazel.remote.execution.v2',
- syntax='proto3',
- serialized_pb=_b('\n6build/bazel/remote/execution/v2/remote_execution.proto\x12\x1f\x62uild.bazel.remote.execution.v2\x1a\x1f\x62uild/bazel/semver/semver.proto\x1a\x1cgoogle/api/annotations.proto\x1a#google/longrunning/operations.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x17google/rpc/status.proto\"\xd5\x01\n\x06\x41\x63tion\x12?\n\x0e\x63ommand_digest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x42\n\x11input_root_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12*\n\x07timeout\x18\x06 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x14\n\x0c\x64o_not_cache\x18\x07 \x01(\x08J\x04\x08\x03\x10\x06\"\xb7\x02\n\x07\x43ommand\x12\x11\n\targuments\x18\x01 \x03(\t\x12[\n\x15\x65nvironment_variables\x18\x02 \x03(\x0b\x32<.build.bazel.remote.execution.v2.Command.EnvironmentVariable\x12\x14\n\x0coutput_files\x18\x03 \x03(\t\x12\x1a\n\x12output_directories\x18\x04 \x03(\t\x12;\n\x08platform\x18\x05 \x01(\x0b\x32).build.bazel.remote.execution.v2.Platform\x12\x19\n\x11working_directory\x18\x06 \x01(\t\x1a\x32\n\x13\x45nvironmentVariable\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"{\n\x08Platform\x12\x46\n\nproperties\x18\x01 \x03(\x0b\x32\x32.build.bazel.remote.execution.v2.Platform.Property\x1a\'\n\x08Property\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\xca\x01\n\tDirectory\x12\x38\n\x05\x66iles\x18\x01 \x03(\x0b\x32).build.bazel.remote.execution.v2.FileNode\x12\x43\n\x0b\x64irectories\x18\x02 \x03(\x0b\x32..build.bazel.remote.execution.v2.DirectoryNode\x12>\n\x08symlinks\x18\x03 \x03(\x0b\x32,.build.bazel.remote.execution.v2.SymlinkNode\"n\n\x08\x46ileNode\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x06\x64igest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x15\n\ris_executable\x18\x04 \x01(\x08J\x04\x08\x03\x10\x04\"V\n\rDirectoryNode\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x06\x64igest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"+\n\x0bSymlinkNode\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06target\x18\x02 \x01(\t\"*\n\x06\x44igest\x12\x0c\n\x04hash\x18\x01 \x01(\t\x12\x12\n\nsize_bytes\x18\x02 \x01(\x03\"\xec\x04\n\x16\x45xecutedActionMetadata\x12\x0e\n\x06worker\x18\x01 \x01(\t\x12\x34\n\x10queued_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12:\n\x16worker_start_timestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12>\n\x1aworker_completed_timestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12?\n\x1binput_fetch_start_timestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x43\n\x1finput_fetch_completed_timestamp\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12=\n\x19\x65xecution_start_timestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x41\n\x1d\x65xecution_completed_timestamp\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x41\n\x1doutput_upload_start_timestamp\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x45\n!output_upload_completed_timestamp\x18\n \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb5\x03\n\x0c\x41\x63tionResult\x12\x41\n\x0coutput_files\x18\x02 \x03(\x0b\x32+.build.bazel.remote.execution.v2.OutputFile\x12L\n\x12output_directories\x18\x03 \x03(\x0b\x32\x30.build.bazel.remote.execution.v2.OutputDirectory\x12\x11\n\texit_code\x18\x04 \x01(\x05\x12\x12\n\nstdout_raw\x18\x05 \x01(\x0c\x12>\n\rstdout_digest\x18\x06 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x12\n\nstderr_raw\x18\x07 \x01(\x0c\x12>\n\rstderr_digest\x18\x08 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12S\n\x12\x65xecution_metadata\x18\t \x01(\x0b\x32\x37.build.bazel.remote.execution.v2.ExecutedActionMetadataJ\x04\x08\x01\x10\x02\"p\n\nOutputFile\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x37\n\x06\x64igest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x15\n\ris_executable\x18\x04 \x01(\x08J\x04\x08\x03\x10\x04\"~\n\x04Tree\x12\x38\n\x04root\x18\x01 \x01(\x0b\x32*.build.bazel.remote.execution.v2.Directory\x12<\n\x08\x63hildren\x18\x02 \x03(\x0b\x32*.build.bazel.remote.execution.v2.Directory\"c\n\x0fOutputDirectory\x12\x0c\n\x04path\x18\x01 \x01(\t\x12<\n\x0btree_digest\x18\x03 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.DigestJ\x04\x08\x02\x10\x03\"#\n\x0f\x45xecutionPolicy\x12\x10\n\x08priority\x18\x01 \x01(\x05\"&\n\x12ResultsCachePolicy\x12\x10\n\x08priority\x18\x01 \x01(\x05\"\xb3\x02\n\x0e\x45xecuteRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x19\n\x11skip_cache_lookup\x18\x03 \x01(\x08\x12>\n\raction_digest\x18\x06 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12J\n\x10\x65xecution_policy\x18\x07 \x01(\x0b\x32\x30.build.bazel.remote.execution.v2.ExecutionPolicy\x12Q\n\x14results_cache_policy\x18\x08 \x01(\x0b\x32\x33.build.bazel.remote.execution.v2.ResultsCachePolicyJ\x04\x08\x02\x10\x03J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06\"Z\n\x07LogFile\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x16\n\x0ehuman_readable\x18\x02 \x01(\x08\"\xbf\x02\n\x0f\x45xecuteResponse\x12=\n\x06result\x18\x01 \x01(\x0b\x32-.build.bazel.remote.execution.v2.ActionResult\x12\x15\n\rcached_result\x18\x02 \x01(\x08\x12\"\n\x06status\x18\x03 \x01(\x0b\x32\x12.google.rpc.Status\x12U\n\x0bserver_logs\x18\x04 \x03(\x0b\x32@.build.bazel.remote.execution.v2.ExecuteResponse.ServerLogsEntry\x1a[\n\x0fServerLogsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x37\n\x05value\x18\x02 \x01(\x0b\x32(.build.bazel.remote.execution.v2.LogFile:\x02\x38\x01\"\xb3\x02\n\x18\x45xecuteOperationMetadata\x12N\n\x05stage\x18\x01 \x01(\x0e\x32?.build.bazel.remote.execution.v2.ExecuteOperationMetadata.Stage\x12>\n\raction_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x1a\n\x12stdout_stream_name\x18\x03 \x01(\t\x12\x1a\n\x12stderr_stream_name\x18\x04 \x01(\t\"O\n\x05Stage\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0f\n\x0b\x43\x41\x43HE_CHECK\x10\x01\x12\n\n\x06QUEUED\x10\x02\x12\r\n\tEXECUTING\x10\x03\x12\r\n\tCOMPLETED\x10\x04\"$\n\x14WaitExecutionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"o\n\x16GetActionResultRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12>\n\raction_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"\x8b\x02\n\x19UpdateActionResultRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12>\n\raction_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x44\n\raction_result\x18\x03 \x01(\x0b\x32-.build.bazel.remote.execution.v2.ActionResult\x12Q\n\x14results_cache_policy\x18\x04 \x01(\x0b\x32\x33.build.bazel.remote.execution.v2.ResultsCachePolicy\"o\n\x17\x46indMissingBlobsRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12=\n\x0c\x62lob_digests\x18\x02 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"a\n\x18\x46indMissingBlobsResponse\x12\x45\n\x14missing_blob_digests\x18\x02 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"\xd6\x01\n\x17\x42\x61tchUpdateBlobsRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12R\n\x08requests\x18\x02 \x03(\x0b\x32@.build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.Request\x1aP\n\x07Request\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\xda\x01\n\x18\x42\x61tchUpdateBlobsResponse\x12U\n\tresponses\x18\x01 \x03(\x0b\x32\x42.build.bazel.remote.execution.v2.BatchUpdateBlobsResponse.Response\x1ag\n\x08Response\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\"\n\x06status\x18\x02 \x01(\x0b\x32\x12.google.rpc.Status\"h\n\x15\x42\x61tchReadBlobsRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x38\n\x07\x64igests\x18\x02 \x03(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"\xe4\x01\n\x16\x42\x61tchReadBlobsResponse\x12S\n\tresponses\x18\x01 \x03(\x0b\x32@.build.bazel.remote.execution.v2.BatchReadBlobsResponse.Response\x1au\n\x08Response\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\"\n\x06status\x18\x03 \x01(\x0b\x32\x12.google.rpc.Status\"\x8c\x01\n\x0eGetTreeRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12<\n\x0broot_digest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x11\n\tpage_size\x18\x03 \x01(\x05\x12\x12\n\npage_token\x18\x04 \x01(\t\"k\n\x0fGetTreeResponse\x12?\n\x0b\x64irectories\x18\x01 \x03(\x0b\x32*.build.bazel.remote.execution.v2.Directory\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"/\n\x16GetCapabilitiesRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\"\xe3\x02\n\x12ServerCapabilities\x12N\n\x12\x63\x61\x63he_capabilities\x18\x01 \x01(\x0b\x32\x32.build.bazel.remote.execution.v2.CacheCapabilities\x12V\n\x16\x65xecution_capabilities\x18\x02 \x01(\x0b\x32\x36.build.bazel.remote.execution.v2.ExecutionCapabilities\x12:\n\x16\x64\x65precated_api_version\x18\x03 \x01(\x0b\x32\x1a.build.bazel.semver.SemVer\x12\x33\n\x0flow_api_version\x18\x04 \x01(\x0b\x32\x1a.build.bazel.semver.SemVer\x12\x34\n\x10high_api_version\x18\x05 \x01(\x0b\x32\x1a.build.bazel.semver.SemVer\"7\n\x1d\x41\x63tionCacheUpdateCapabilities\x12\x16\n\x0eupdate_enabled\x18\x01 \x01(\x08\"\xac\x01\n\x14PriorityCapabilities\x12W\n\npriorities\x18\x01 \x03(\x0b\x32\x43.build.bazel.remote.execution.v2.PriorityCapabilities.PriorityRange\x1a;\n\rPriorityRange\x12\x14\n\x0cmin_priority\x18\x01 \x01(\x05\x12\x14\n\x0cmax_priority\x18\x02 \x01(\x05\"\x88\x04\n\x11\x43\x61\x63heCapabilities\x12H\n\x0f\x64igest_function\x18\x01 \x03(\x0e\x32/.build.bazel.remote.execution.v2.DigestFunction\x12h\n action_cache_update_capabilities\x18\x02 \x01(\x0b\x32>.build.bazel.remote.execution.v2.ActionCacheUpdateCapabilities\x12Z\n\x1b\x63\x61\x63he_priority_capabilities\x18\x03 \x01(\x0b\x32\x35.build.bazel.remote.execution.v2.PriorityCapabilities\x12\"\n\x1amax_batch_total_size_bytes\x18\x04 \x01(\x03\x12v\n\x1esymlink_absolute_path_strategy\x18\x05 \x01(\x0e\x32N.build.bazel.remote.execution.v2.CacheCapabilities.SymlinkAbsolutePathStrategy\"G\n\x1bSymlinkAbsolutePathStrategy\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0e\n\nDISALLOWED\x10\x01\x12\x0b\n\x07\x41LLOWED\x10\x02\"\xd7\x01\n\x15\x45xecutionCapabilities\x12H\n\x0f\x64igest_function\x18\x01 \x01(\x0e\x32/.build.bazel.remote.execution.v2.DigestFunction\x12\x14\n\x0c\x65xec_enabled\x18\x02 \x01(\x08\x12^\n\x1f\x65xecution_priority_capabilities\x18\x03 \x01(\x0b\x32\x35.build.bazel.remote.execution.v2.PriorityCapabilities\"6\n\x0bToolDetails\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x14\n\x0ctool_version\x18\x02 \x01(\t\"\xa7\x01\n\x0fRequestMetadata\x12\x42\n\x0ctool_details\x18\x01 \x01(\x0b\x32,.build.bazel.remote.execution.v2.ToolDetails\x12\x11\n\taction_id\x18\x02 \x01(\t\x12\x1a\n\x12tool_invocation_id\x18\x03 \x01(\t\x12!\n\x19\x63orrelated_invocations_id\x18\x04 \x01(\t*<\n\x0e\x44igestFunction\x12\x0b\n\x07UNKNOWN\x10\x00\x12\n\n\x06SHA256\x10\x01\x12\x08\n\x04SHA1\x10\x02\x12\x07\n\x03MD5\x10\x03\x32\xb9\x02\n\tExecution\x12\x8e\x01\n\x07\x45xecute\x12/.build.bazel.remote.execution.v2.ExecuteRequest\x1a\x1d.google.longrunning.Operation\"1\x82\xd3\xe4\x93\x02+\"&/v2/{instance_name=**}/actions:execute:\x01*0\x01\x12\x9a\x01\n\rWaitExecution\x12\x35.build.bazel.remote.execution.v2.WaitExecutionRequest\x1a\x1d.google.longrunning.Operation\"1\x82\xd3\xe4\x93\x02+\"&/v2/{name=operations/**}:waitExecution:\x01*0\x01\x32\xd6\x03\n\x0b\x41\x63tionCache\x12\xd7\x01\n\x0fGetActionResult\x12\x37.build.bazel.remote.execution.v2.GetActionResultRequest\x1a-.build.bazel.remote.execution.v2.ActionResult\"\\\x82\xd3\xe4\x93\x02V\x12T/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}\x12\xec\x01\n\x12UpdateActionResult\x12:.build.bazel.remote.execution.v2.UpdateActionResultRequest\x1a-.build.bazel.remote.execution.v2.ActionResult\"k\x82\xd3\xe4\x93\x02\x65\x1aT/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}:\raction_result2\x9b\x06\n\x19\x43ontentAddressableStorage\x12\xbc\x01\n\x10\x46indMissingBlobs\x12\x38.build.bazel.remote.execution.v2.FindMissingBlobsRequest\x1a\x39.build.bazel.remote.execution.v2.FindMissingBlobsResponse\"3\x82\xd3\xe4\x93\x02-\"(/v2/{instance_name=**}/blobs:findMissing:\x01*\x12\xbc\x01\n\x10\x42\x61tchUpdateBlobs\x12\x38.build.bazel.remote.execution.v2.BatchUpdateBlobsRequest\x1a\x39.build.bazel.remote.execution.v2.BatchUpdateBlobsResponse\"3\x82\xd3\xe4\x93\x02-\"(/v2/{instance_name=**}/blobs:batchUpdate:\x01*\x12\xb4\x01\n\x0e\x42\x61tchReadBlobs\x12\x36.build.bazel.remote.execution.v2.BatchReadBlobsRequest\x1a\x37.build.bazel.remote.execution.v2.BatchReadBlobsResponse\"1\x82\xd3\xe4\x93\x02+\"&/v2/{instance_name=**}/blobs:batchRead:\x01*\x12\xc8\x01\n\x07GetTree\x12/.build.bazel.remote.execution.v2.GetTreeRequest\x1a\x30.build.bazel.remote.execution.v2.GetTreeResponse\"X\x82\xd3\xe4\x93\x02R\x12P/v2/{instance_name=**}/blobs/{root_digest.hash}/{root_digest.size_bytes}:getTree0\x01\x32\xbd\x01\n\x0c\x43\x61pabilities\x12\xac\x01\n\x0fGetCapabilities\x12\x37.build.bazel.remote.execution.v2.GetCapabilitiesRequest\x1a\x33.build.bazel.remote.execution.v2.ServerCapabilities\"+\x82\xd3\xe4\x93\x02%\x12#/v2/{instance_name=**}/capabilitiesBr\n\x1f\x62uild.bazel.remote.execution.v2B\x14RemoteExecutionProtoP\x01Z\x0fremoteexecution\xa2\x02\x03REX\xaa\x02\x1f\x42uild.Bazel.Remote.Execution.V2b\x06proto3')
- ,
- dependencies=[build_dot_bazel_dot_semver_dot_semver__pb2.DESCRIPTOR,google_dot_api_dot_annotations__pb2.DESCRIPTOR,google_dot_longrunning_dot_operations__pb2.DESCRIPTOR,google_dot_protobuf_dot_duration__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,google_dot_rpc_dot_status__pb2.DESCRIPTOR,])
-
-_DIGESTFUNCTION = _descriptor.EnumDescriptor(
- name='DigestFunction',
- full_name='build.bazel.remote.execution.v2.DigestFunction',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='UNKNOWN', index=0, number=0,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='SHA256', index=1, number=1,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='SHA1', index=2, number=2,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='MD5', index=3, number=3,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=7213,
- serialized_end=7273,
-)
-_sym_db.RegisterEnumDescriptor(_DIGESTFUNCTION)
-
-DigestFunction = enum_type_wrapper.EnumTypeWrapper(_DIGESTFUNCTION)
-UNKNOWN = 0
-SHA256 = 1
-SHA1 = 2
-MD5 = 3
-
-
-_EXECUTEOPERATIONMETADATA_STAGE = _descriptor.EnumDescriptor(
- name='Stage',
- full_name='build.bazel.remote.execution.v2.ExecuteOperationMetadata.Stage',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='UNKNOWN', index=0, number=0,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='CACHE_CHECK', index=1, number=1,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='QUEUED', index=2, number=2,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='EXECUTING', index=3, number=3,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='COMPLETED', index=4, number=4,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=3866,
- serialized_end=3945,
-)
-_sym_db.RegisterEnumDescriptor(_EXECUTEOPERATIONMETADATA_STAGE)
-
-_CACHECAPABILITIES_SYMLINKABSOLUTEPATHSTRATEGY = _descriptor.EnumDescriptor(
- name='SymlinkAbsolutePathStrategy',
- full_name='build.bazel.remote.execution.v2.CacheCapabilities.SymlinkAbsolutePathStrategy',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='UNKNOWN', index=0, number=0,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='DISALLOWED', index=1, number=1,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='ALLOWED', index=2, number=2,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=6696,
- serialized_end=6767,
-)
-_sym_db.RegisterEnumDescriptor(_CACHECAPABILITIES_SYMLINKABSOLUTEPATHSTRATEGY)
-
-
-_ACTION = _descriptor.Descriptor(
- name='Action',
- full_name='build.bazel.remote.execution.v2.Action',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='command_digest', full_name='build.bazel.remote.execution.v2.Action.command_digest', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='input_root_digest', full_name='build.bazel.remote.execution.v2.Action.input_root_digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='timeout', full_name='build.bazel.remote.execution.v2.Action.timeout', index=2,
- number=6, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='do_not_cache', full_name='build.bazel.remote.execution.v2.Action.do_not_cache', index=3,
- number=7, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=282,
- serialized_end=495,
-)
-
-
-_COMMAND_ENVIRONMENTVARIABLE = _descriptor.Descriptor(
- name='EnvironmentVariable',
- full_name='build.bazel.remote.execution.v2.Command.EnvironmentVariable',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='build.bazel.remote.execution.v2.Command.EnvironmentVariable.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='value', full_name='build.bazel.remote.execution.v2.Command.EnvironmentVariable.value', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=759,
- serialized_end=809,
-)
-
-_COMMAND = _descriptor.Descriptor(
- name='Command',
- full_name='build.bazel.remote.execution.v2.Command',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='arguments', full_name='build.bazel.remote.execution.v2.Command.arguments', index=0,
- number=1, type=9, cpp_type=9, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='environment_variables', full_name='build.bazel.remote.execution.v2.Command.environment_variables', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='output_files', full_name='build.bazel.remote.execution.v2.Command.output_files', index=2,
- number=3, type=9, cpp_type=9, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='output_directories', full_name='build.bazel.remote.execution.v2.Command.output_directories', index=3,
- number=4, type=9, cpp_type=9, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='platform', full_name='build.bazel.remote.execution.v2.Command.platform', index=4,
- number=5, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='working_directory', full_name='build.bazel.remote.execution.v2.Command.working_directory', index=5,
- number=6, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_COMMAND_ENVIRONMENTVARIABLE, ],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=498,
- serialized_end=809,
-)
-
-
-_PLATFORM_PROPERTY = _descriptor.Descriptor(
- name='Property',
- full_name='build.bazel.remote.execution.v2.Platform.Property',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='build.bazel.remote.execution.v2.Platform.Property.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='value', full_name='build.bazel.remote.execution.v2.Platform.Property.value', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=895,
- serialized_end=934,
-)
-
-_PLATFORM = _descriptor.Descriptor(
- name='Platform',
- full_name='build.bazel.remote.execution.v2.Platform',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='properties', full_name='build.bazel.remote.execution.v2.Platform.properties', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_PLATFORM_PROPERTY, ],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=811,
- serialized_end=934,
-)
-
-
-_DIRECTORY = _descriptor.Descriptor(
- name='Directory',
- full_name='build.bazel.remote.execution.v2.Directory',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='files', full_name='build.bazel.remote.execution.v2.Directory.files', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='directories', full_name='build.bazel.remote.execution.v2.Directory.directories', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='symlinks', full_name='build.bazel.remote.execution.v2.Directory.symlinks', index=2,
- number=3, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=937,
- serialized_end=1139,
-)
-
-
-_FILENODE = _descriptor.Descriptor(
- name='FileNode',
- full_name='build.bazel.remote.execution.v2.FileNode',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='build.bazel.remote.execution.v2.FileNode.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='digest', full_name='build.bazel.remote.execution.v2.FileNode.digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='is_executable', full_name='build.bazel.remote.execution.v2.FileNode.is_executable', index=2,
- number=4, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1141,
- serialized_end=1251,
-)
-
-
-_DIRECTORYNODE = _descriptor.Descriptor(
- name='DirectoryNode',
- full_name='build.bazel.remote.execution.v2.DirectoryNode',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='build.bazel.remote.execution.v2.DirectoryNode.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='digest', full_name='build.bazel.remote.execution.v2.DirectoryNode.digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1253,
- serialized_end=1339,
-)
-
-
-_SYMLINKNODE = _descriptor.Descriptor(
- name='SymlinkNode',
- full_name='build.bazel.remote.execution.v2.SymlinkNode',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='build.bazel.remote.execution.v2.SymlinkNode.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='target', full_name='build.bazel.remote.execution.v2.SymlinkNode.target', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1341,
- serialized_end=1384,
-)
-
-
-_DIGEST = _descriptor.Descriptor(
- name='Digest',
- full_name='build.bazel.remote.execution.v2.Digest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='hash', full_name='build.bazel.remote.execution.v2.Digest.hash', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='size_bytes', full_name='build.bazel.remote.execution.v2.Digest.size_bytes', index=1,
- number=2, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1386,
- serialized_end=1428,
-)
-
-
-_EXECUTEDACTIONMETADATA = _descriptor.Descriptor(
- name='ExecutedActionMetadata',
- full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='worker', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.worker', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='queued_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.queued_timestamp', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='worker_start_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.worker_start_timestamp', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='worker_completed_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.worker_completed_timestamp', index=3,
- number=4, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='input_fetch_start_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.input_fetch_start_timestamp', index=4,
- number=5, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='input_fetch_completed_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.input_fetch_completed_timestamp', index=5,
- number=6, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='execution_start_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.execution_start_timestamp', index=6,
- number=7, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='execution_completed_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.execution_completed_timestamp', index=7,
- number=8, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='output_upload_start_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.output_upload_start_timestamp', index=8,
- number=9, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='output_upload_completed_timestamp', full_name='build.bazel.remote.execution.v2.ExecutedActionMetadata.output_upload_completed_timestamp', index=9,
- number=10, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1431,
- serialized_end=2051,
-)
-
-
-_ACTIONRESULT = _descriptor.Descriptor(
- name='ActionResult',
- full_name='build.bazel.remote.execution.v2.ActionResult',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='output_files', full_name='build.bazel.remote.execution.v2.ActionResult.output_files', index=0,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='output_directories', full_name='build.bazel.remote.execution.v2.ActionResult.output_directories', index=1,
- number=3, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='exit_code', full_name='build.bazel.remote.execution.v2.ActionResult.exit_code', index=2,
- number=4, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='stdout_raw', full_name='build.bazel.remote.execution.v2.ActionResult.stdout_raw', index=3,
- number=5, type=12, cpp_type=9, label=1,
- has_default_value=False, default_value=_b(""),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='stdout_digest', full_name='build.bazel.remote.execution.v2.ActionResult.stdout_digest', index=4,
- number=6, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='stderr_raw', full_name='build.bazel.remote.execution.v2.ActionResult.stderr_raw', index=5,
- number=7, type=12, cpp_type=9, label=1,
- has_default_value=False, default_value=_b(""),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='stderr_digest', full_name='build.bazel.remote.execution.v2.ActionResult.stderr_digest', index=6,
- number=8, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='execution_metadata', full_name='build.bazel.remote.execution.v2.ActionResult.execution_metadata', index=7,
- number=9, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2054,
- serialized_end=2491,
-)
-
-
-_OUTPUTFILE = _descriptor.Descriptor(
- name='OutputFile',
- full_name='build.bazel.remote.execution.v2.OutputFile',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='path', full_name='build.bazel.remote.execution.v2.OutputFile.path', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='digest', full_name='build.bazel.remote.execution.v2.OutputFile.digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='is_executable', full_name='build.bazel.remote.execution.v2.OutputFile.is_executable', index=2,
- number=4, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2493,
- serialized_end=2605,
-)
-
-
-_TREE = _descriptor.Descriptor(
- name='Tree',
- full_name='build.bazel.remote.execution.v2.Tree',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='root', full_name='build.bazel.remote.execution.v2.Tree.root', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='children', full_name='build.bazel.remote.execution.v2.Tree.children', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2607,
- serialized_end=2733,
-)
-
-
-_OUTPUTDIRECTORY = _descriptor.Descriptor(
- name='OutputDirectory',
- full_name='build.bazel.remote.execution.v2.OutputDirectory',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='path', full_name='build.bazel.remote.execution.v2.OutputDirectory.path', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='tree_digest', full_name='build.bazel.remote.execution.v2.OutputDirectory.tree_digest', index=1,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2735,
- serialized_end=2834,
-)
-
-
-_EXECUTIONPOLICY = _descriptor.Descriptor(
- name='ExecutionPolicy',
- full_name='build.bazel.remote.execution.v2.ExecutionPolicy',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='priority', full_name='build.bazel.remote.execution.v2.ExecutionPolicy.priority', index=0,
- number=1, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2836,
- serialized_end=2871,
-)
-
-
-_RESULTSCACHEPOLICY = _descriptor.Descriptor(
- name='ResultsCachePolicy',
- full_name='build.bazel.remote.execution.v2.ResultsCachePolicy',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='priority', full_name='build.bazel.remote.execution.v2.ResultsCachePolicy.priority', index=0,
- number=1, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2873,
- serialized_end=2911,
-)
-
-
-_EXECUTEREQUEST = _descriptor.Descriptor(
- name='ExecuteRequest',
- full_name='build.bazel.remote.execution.v2.ExecuteRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.ExecuteRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='skip_cache_lookup', full_name='build.bazel.remote.execution.v2.ExecuteRequest.skip_cache_lookup', index=1,
- number=3, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='action_digest', full_name='build.bazel.remote.execution.v2.ExecuteRequest.action_digest', index=2,
- number=6, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='execution_policy', full_name='build.bazel.remote.execution.v2.ExecuteRequest.execution_policy', index=3,
- number=7, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='results_cache_policy', full_name='build.bazel.remote.execution.v2.ExecuteRequest.results_cache_policy', index=4,
- number=8, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2914,
- serialized_end=3221,
-)
-
-
-_LOGFILE = _descriptor.Descriptor(
- name='LogFile',
- full_name='build.bazel.remote.execution.v2.LogFile',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='digest', full_name='build.bazel.remote.execution.v2.LogFile.digest', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='human_readable', full_name='build.bazel.remote.execution.v2.LogFile.human_readable', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3223,
- serialized_end=3313,
-)
-
-
-_EXECUTERESPONSE_SERVERLOGSENTRY = _descriptor.Descriptor(
- name='ServerLogsEntry',
- full_name='build.bazel.remote.execution.v2.ExecuteResponse.ServerLogsEntry',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='build.bazel.remote.execution.v2.ExecuteResponse.ServerLogsEntry.key', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='value', full_name='build.bazel.remote.execution.v2.ExecuteResponse.ServerLogsEntry.value', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')),
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3544,
- serialized_end=3635,
-)
-
-_EXECUTERESPONSE = _descriptor.Descriptor(
- name='ExecuteResponse',
- full_name='build.bazel.remote.execution.v2.ExecuteResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='result', full_name='build.bazel.remote.execution.v2.ExecuteResponse.result', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='cached_result', full_name='build.bazel.remote.execution.v2.ExecuteResponse.cached_result', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='status', full_name='build.bazel.remote.execution.v2.ExecuteResponse.status', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='server_logs', full_name='build.bazel.remote.execution.v2.ExecuteResponse.server_logs', index=3,
- number=4, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_EXECUTERESPONSE_SERVERLOGSENTRY, ],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3316,
- serialized_end=3635,
-)
-
-
-_EXECUTEOPERATIONMETADATA = _descriptor.Descriptor(
- name='ExecuteOperationMetadata',
- full_name='build.bazel.remote.execution.v2.ExecuteOperationMetadata',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='stage', full_name='build.bazel.remote.execution.v2.ExecuteOperationMetadata.stage', index=0,
- number=1, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='action_digest', full_name='build.bazel.remote.execution.v2.ExecuteOperationMetadata.action_digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='stdout_stream_name', full_name='build.bazel.remote.execution.v2.ExecuteOperationMetadata.stdout_stream_name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='stderr_stream_name', full_name='build.bazel.remote.execution.v2.ExecuteOperationMetadata.stderr_stream_name', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- _EXECUTEOPERATIONMETADATA_STAGE,
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3638,
- serialized_end=3945,
-)
-
-
-_WAITEXECUTIONREQUEST = _descriptor.Descriptor(
- name='WaitExecutionRequest',
- full_name='build.bazel.remote.execution.v2.WaitExecutionRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='build.bazel.remote.execution.v2.WaitExecutionRequest.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3947,
- serialized_end=3983,
-)
-
-
-_GETACTIONRESULTREQUEST = _descriptor.Descriptor(
- name='GetActionResultRequest',
- full_name='build.bazel.remote.execution.v2.GetActionResultRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.GetActionResultRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='action_digest', full_name='build.bazel.remote.execution.v2.GetActionResultRequest.action_digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3985,
- serialized_end=4096,
-)
-
-
-_UPDATEACTIONRESULTREQUEST = _descriptor.Descriptor(
- name='UpdateActionResultRequest',
- full_name='build.bazel.remote.execution.v2.UpdateActionResultRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.UpdateActionResultRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='action_digest', full_name='build.bazel.remote.execution.v2.UpdateActionResultRequest.action_digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='action_result', full_name='build.bazel.remote.execution.v2.UpdateActionResultRequest.action_result', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='results_cache_policy', full_name='build.bazel.remote.execution.v2.UpdateActionResultRequest.results_cache_policy', index=3,
- number=4, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=4099,
- serialized_end=4366,
-)
-
-
-_FINDMISSINGBLOBSREQUEST = _descriptor.Descriptor(
- name='FindMissingBlobsRequest',
- full_name='build.bazel.remote.execution.v2.FindMissingBlobsRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.FindMissingBlobsRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='blob_digests', full_name='build.bazel.remote.execution.v2.FindMissingBlobsRequest.blob_digests', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=4368,
- serialized_end=4479,
-)
-
-
-_FINDMISSINGBLOBSRESPONSE = _descriptor.Descriptor(
- name='FindMissingBlobsResponse',
- full_name='build.bazel.remote.execution.v2.FindMissingBlobsResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='missing_blob_digests', full_name='build.bazel.remote.execution.v2.FindMissingBlobsResponse.missing_blob_digests', index=0,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=4481,
- serialized_end=4578,
-)
-
-
-_BATCHUPDATEBLOBSREQUEST_REQUEST = _descriptor.Descriptor(
- name='Request',
- full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.Request',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='digest', full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.Request.digest', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='data', full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.Request.data', index=1,
- number=2, type=12, cpp_type=9, label=1,
- has_default_value=False, default_value=_b(""),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=4715,
- serialized_end=4795,
-)
-
-_BATCHUPDATEBLOBSREQUEST = _descriptor.Descriptor(
- name='BatchUpdateBlobsRequest',
- full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='requests', full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.requests', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_BATCHUPDATEBLOBSREQUEST_REQUEST, ],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=4581,
- serialized_end=4795,
-)
-
-
-_BATCHUPDATEBLOBSRESPONSE_RESPONSE = _descriptor.Descriptor(
- name='Response',
- full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsResponse.Response',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='digest', full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsResponse.Response.digest', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='status', full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsResponse.Response.status', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=4913,
- serialized_end=5016,
-)
-
-_BATCHUPDATEBLOBSRESPONSE = _descriptor.Descriptor(
- name='BatchUpdateBlobsResponse',
- full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='responses', full_name='build.bazel.remote.execution.v2.BatchUpdateBlobsResponse.responses', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_BATCHUPDATEBLOBSRESPONSE_RESPONSE, ],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=4798,
- serialized_end=5016,
-)
-
-
-_BATCHREADBLOBSREQUEST = _descriptor.Descriptor(
- name='BatchReadBlobsRequest',
- full_name='build.bazel.remote.execution.v2.BatchReadBlobsRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.BatchReadBlobsRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='digests', full_name='build.bazel.remote.execution.v2.BatchReadBlobsRequest.digests', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=5018,
- serialized_end=5122,
-)
-
-
-_BATCHREADBLOBSRESPONSE_RESPONSE = _descriptor.Descriptor(
- name='Response',
- full_name='build.bazel.remote.execution.v2.BatchReadBlobsResponse.Response',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='digest', full_name='build.bazel.remote.execution.v2.BatchReadBlobsResponse.Response.digest', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='data', full_name='build.bazel.remote.execution.v2.BatchReadBlobsResponse.Response.data', index=1,
- number=2, type=12, cpp_type=9, label=1,
- has_default_value=False, default_value=_b(""),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='status', full_name='build.bazel.remote.execution.v2.BatchReadBlobsResponse.Response.status', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=5236,
- serialized_end=5353,
-)
-
-_BATCHREADBLOBSRESPONSE = _descriptor.Descriptor(
- name='BatchReadBlobsResponse',
- full_name='build.bazel.remote.execution.v2.BatchReadBlobsResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='responses', full_name='build.bazel.remote.execution.v2.BatchReadBlobsResponse.responses', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_BATCHREADBLOBSRESPONSE_RESPONSE, ],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=5125,
- serialized_end=5353,
-)
-
-
-_GETTREEREQUEST = _descriptor.Descriptor(
- name='GetTreeRequest',
- full_name='build.bazel.remote.execution.v2.GetTreeRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.GetTreeRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='root_digest', full_name='build.bazel.remote.execution.v2.GetTreeRequest.root_digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='page_size', full_name='build.bazel.remote.execution.v2.GetTreeRequest.page_size', index=2,
- number=3, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='page_token', full_name='build.bazel.remote.execution.v2.GetTreeRequest.page_token', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=5356,
- serialized_end=5496,
-)
-
-
-_GETTREERESPONSE = _descriptor.Descriptor(
- name='GetTreeResponse',
- full_name='build.bazel.remote.execution.v2.GetTreeResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='directories', full_name='build.bazel.remote.execution.v2.GetTreeResponse.directories', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='next_page_token', full_name='build.bazel.remote.execution.v2.GetTreeResponse.next_page_token', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=5498,
- serialized_end=5605,
-)
-
-
-_GETCAPABILITIESREQUEST = _descriptor.Descriptor(
- name='GetCapabilitiesRequest',
- full_name='build.bazel.remote.execution.v2.GetCapabilitiesRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='build.bazel.remote.execution.v2.GetCapabilitiesRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=5607,
- serialized_end=5654,
-)
-
-
-_SERVERCAPABILITIES = _descriptor.Descriptor(
- name='ServerCapabilities',
- full_name='build.bazel.remote.execution.v2.ServerCapabilities',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='cache_capabilities', full_name='build.bazel.remote.execution.v2.ServerCapabilities.cache_capabilities', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='execution_capabilities', full_name='build.bazel.remote.execution.v2.ServerCapabilities.execution_capabilities', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='deprecated_api_version', full_name='build.bazel.remote.execution.v2.ServerCapabilities.deprecated_api_version', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='low_api_version', full_name='build.bazel.remote.execution.v2.ServerCapabilities.low_api_version', index=3,
- number=4, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='high_api_version', full_name='build.bazel.remote.execution.v2.ServerCapabilities.high_api_version', index=4,
- number=5, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=5657,
- serialized_end=6012,
-)
-
-
-_ACTIONCACHEUPDATECAPABILITIES = _descriptor.Descriptor(
- name='ActionCacheUpdateCapabilities',
- full_name='build.bazel.remote.execution.v2.ActionCacheUpdateCapabilities',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='update_enabled', full_name='build.bazel.remote.execution.v2.ActionCacheUpdateCapabilities.update_enabled', index=0,
- number=1, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=6014,
- serialized_end=6069,
-)
-
-
-_PRIORITYCAPABILITIES_PRIORITYRANGE = _descriptor.Descriptor(
- name='PriorityRange',
- full_name='build.bazel.remote.execution.v2.PriorityCapabilities.PriorityRange',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='min_priority', full_name='build.bazel.remote.execution.v2.PriorityCapabilities.PriorityRange.min_priority', index=0,
- number=1, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='max_priority', full_name='build.bazel.remote.execution.v2.PriorityCapabilities.PriorityRange.max_priority', index=1,
- number=2, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=6185,
- serialized_end=6244,
-)
-
-_PRIORITYCAPABILITIES = _descriptor.Descriptor(
- name='PriorityCapabilities',
- full_name='build.bazel.remote.execution.v2.PriorityCapabilities',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='priorities', full_name='build.bazel.remote.execution.v2.PriorityCapabilities.priorities', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_PRIORITYCAPABILITIES_PRIORITYRANGE, ],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=6072,
- serialized_end=6244,
-)
-
-
-_CACHECAPABILITIES = _descriptor.Descriptor(
- name='CacheCapabilities',
- full_name='build.bazel.remote.execution.v2.CacheCapabilities',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='digest_function', full_name='build.bazel.remote.execution.v2.CacheCapabilities.digest_function', index=0,
- number=1, type=14, cpp_type=8, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='action_cache_update_capabilities', full_name='build.bazel.remote.execution.v2.CacheCapabilities.action_cache_update_capabilities', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='cache_priority_capabilities', full_name='build.bazel.remote.execution.v2.CacheCapabilities.cache_priority_capabilities', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='max_batch_total_size_bytes', full_name='build.bazel.remote.execution.v2.CacheCapabilities.max_batch_total_size_bytes', index=3,
- number=4, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='symlink_absolute_path_strategy', full_name='build.bazel.remote.execution.v2.CacheCapabilities.symlink_absolute_path_strategy', index=4,
- number=5, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- _CACHECAPABILITIES_SYMLINKABSOLUTEPATHSTRATEGY,
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=6247,
- serialized_end=6767,
-)
-
-
-_EXECUTIONCAPABILITIES = _descriptor.Descriptor(
- name='ExecutionCapabilities',
- full_name='build.bazel.remote.execution.v2.ExecutionCapabilities',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='digest_function', full_name='build.bazel.remote.execution.v2.ExecutionCapabilities.digest_function', index=0,
- number=1, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='exec_enabled', full_name='build.bazel.remote.execution.v2.ExecutionCapabilities.exec_enabled', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='execution_priority_capabilities', full_name='build.bazel.remote.execution.v2.ExecutionCapabilities.execution_priority_capabilities', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=6770,
- serialized_end=6985,
-)
-
-
-_TOOLDETAILS = _descriptor.Descriptor(
- name='ToolDetails',
- full_name='build.bazel.remote.execution.v2.ToolDetails',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='tool_name', full_name='build.bazel.remote.execution.v2.ToolDetails.tool_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='tool_version', full_name='build.bazel.remote.execution.v2.ToolDetails.tool_version', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=6987,
- serialized_end=7041,
-)
-
-
-_REQUESTMETADATA = _descriptor.Descriptor(
- name='RequestMetadata',
- full_name='build.bazel.remote.execution.v2.RequestMetadata',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='tool_details', full_name='build.bazel.remote.execution.v2.RequestMetadata.tool_details', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='action_id', full_name='build.bazel.remote.execution.v2.RequestMetadata.action_id', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='tool_invocation_id', full_name='build.bazel.remote.execution.v2.RequestMetadata.tool_invocation_id', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='correlated_invocations_id', full_name='build.bazel.remote.execution.v2.RequestMetadata.correlated_invocations_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=7044,
- serialized_end=7211,
-)
-
-_ACTION.fields_by_name['command_digest'].message_type = _DIGEST
-_ACTION.fields_by_name['input_root_digest'].message_type = _DIGEST
-_ACTION.fields_by_name['timeout'].message_type = google_dot_protobuf_dot_duration__pb2._DURATION
-_COMMAND_ENVIRONMENTVARIABLE.containing_type = _COMMAND
-_COMMAND.fields_by_name['environment_variables'].message_type = _COMMAND_ENVIRONMENTVARIABLE
-_COMMAND.fields_by_name['platform'].message_type = _PLATFORM
-_PLATFORM_PROPERTY.containing_type = _PLATFORM
-_PLATFORM.fields_by_name['properties'].message_type = _PLATFORM_PROPERTY
-_DIRECTORY.fields_by_name['files'].message_type = _FILENODE
-_DIRECTORY.fields_by_name['directories'].message_type = _DIRECTORYNODE
-_DIRECTORY.fields_by_name['symlinks'].message_type = _SYMLINKNODE
-_FILENODE.fields_by_name['digest'].message_type = _DIGEST
-_DIRECTORYNODE.fields_by_name['digest'].message_type = _DIGEST
-_EXECUTEDACTIONMETADATA.fields_by_name['queued_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['worker_start_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['worker_completed_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['input_fetch_start_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['input_fetch_completed_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['execution_start_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['execution_completed_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['output_upload_start_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_EXECUTEDACTIONMETADATA.fields_by_name['output_upload_completed_timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_ACTIONRESULT.fields_by_name['output_files'].message_type = _OUTPUTFILE
-_ACTIONRESULT.fields_by_name['output_directories'].message_type = _OUTPUTDIRECTORY
-_ACTIONRESULT.fields_by_name['stdout_digest'].message_type = _DIGEST
-_ACTIONRESULT.fields_by_name['stderr_digest'].message_type = _DIGEST
-_ACTIONRESULT.fields_by_name['execution_metadata'].message_type = _EXECUTEDACTIONMETADATA
-_OUTPUTFILE.fields_by_name['digest'].message_type = _DIGEST
-_TREE.fields_by_name['root'].message_type = _DIRECTORY
-_TREE.fields_by_name['children'].message_type = _DIRECTORY
-_OUTPUTDIRECTORY.fields_by_name['tree_digest'].message_type = _DIGEST
-_EXECUTEREQUEST.fields_by_name['action_digest'].message_type = _DIGEST
-_EXECUTEREQUEST.fields_by_name['execution_policy'].message_type = _EXECUTIONPOLICY
-_EXECUTEREQUEST.fields_by_name['results_cache_policy'].message_type = _RESULTSCACHEPOLICY
-_LOGFILE.fields_by_name['digest'].message_type = _DIGEST
-_EXECUTERESPONSE_SERVERLOGSENTRY.fields_by_name['value'].message_type = _LOGFILE
-_EXECUTERESPONSE_SERVERLOGSENTRY.containing_type = _EXECUTERESPONSE
-_EXECUTERESPONSE.fields_by_name['result'].message_type = _ACTIONRESULT
-_EXECUTERESPONSE.fields_by_name['status'].message_type = google_dot_rpc_dot_status__pb2._STATUS
-_EXECUTERESPONSE.fields_by_name['server_logs'].message_type = _EXECUTERESPONSE_SERVERLOGSENTRY
-_EXECUTEOPERATIONMETADATA.fields_by_name['stage'].enum_type = _EXECUTEOPERATIONMETADATA_STAGE
-_EXECUTEOPERATIONMETADATA.fields_by_name['action_digest'].message_type = _DIGEST
-_EXECUTEOPERATIONMETADATA_STAGE.containing_type = _EXECUTEOPERATIONMETADATA
-_GETACTIONRESULTREQUEST.fields_by_name['action_digest'].message_type = _DIGEST
-_UPDATEACTIONRESULTREQUEST.fields_by_name['action_digest'].message_type = _DIGEST
-_UPDATEACTIONRESULTREQUEST.fields_by_name['action_result'].message_type = _ACTIONRESULT
-_UPDATEACTIONRESULTREQUEST.fields_by_name['results_cache_policy'].message_type = _RESULTSCACHEPOLICY
-_FINDMISSINGBLOBSREQUEST.fields_by_name['blob_digests'].message_type = _DIGEST
-_FINDMISSINGBLOBSRESPONSE.fields_by_name['missing_blob_digests'].message_type = _DIGEST
-_BATCHUPDATEBLOBSREQUEST_REQUEST.fields_by_name['digest'].message_type = _DIGEST
-_BATCHUPDATEBLOBSREQUEST_REQUEST.containing_type = _BATCHUPDATEBLOBSREQUEST
-_BATCHUPDATEBLOBSREQUEST.fields_by_name['requests'].message_type = _BATCHUPDATEBLOBSREQUEST_REQUEST
-_BATCHUPDATEBLOBSRESPONSE_RESPONSE.fields_by_name['digest'].message_type = _DIGEST
-_BATCHUPDATEBLOBSRESPONSE_RESPONSE.fields_by_name['status'].message_type = google_dot_rpc_dot_status__pb2._STATUS
-_BATCHUPDATEBLOBSRESPONSE_RESPONSE.containing_type = _BATCHUPDATEBLOBSRESPONSE
-_BATCHUPDATEBLOBSRESPONSE.fields_by_name['responses'].message_type = _BATCHUPDATEBLOBSRESPONSE_RESPONSE
-_BATCHREADBLOBSREQUEST.fields_by_name['digests'].message_type = _DIGEST
-_BATCHREADBLOBSRESPONSE_RESPONSE.fields_by_name['digest'].message_type = _DIGEST
-_BATCHREADBLOBSRESPONSE_RESPONSE.fields_by_name['status'].message_type = google_dot_rpc_dot_status__pb2._STATUS
-_BATCHREADBLOBSRESPONSE_RESPONSE.containing_type = _BATCHREADBLOBSRESPONSE
-_BATCHREADBLOBSRESPONSE.fields_by_name['responses'].message_type = _BATCHREADBLOBSRESPONSE_RESPONSE
-_GETTREEREQUEST.fields_by_name['root_digest'].message_type = _DIGEST
-_GETTREERESPONSE.fields_by_name['directories'].message_type = _DIRECTORY
-_SERVERCAPABILITIES.fields_by_name['cache_capabilities'].message_type = _CACHECAPABILITIES
-_SERVERCAPABILITIES.fields_by_name['execution_capabilities'].message_type = _EXECUTIONCAPABILITIES
-_SERVERCAPABILITIES.fields_by_name['deprecated_api_version'].message_type = build_dot_bazel_dot_semver_dot_semver__pb2._SEMVER
-_SERVERCAPABILITIES.fields_by_name['low_api_version'].message_type = build_dot_bazel_dot_semver_dot_semver__pb2._SEMVER
-_SERVERCAPABILITIES.fields_by_name['high_api_version'].message_type = build_dot_bazel_dot_semver_dot_semver__pb2._SEMVER
-_PRIORITYCAPABILITIES_PRIORITYRANGE.containing_type = _PRIORITYCAPABILITIES
-_PRIORITYCAPABILITIES.fields_by_name['priorities'].message_type = _PRIORITYCAPABILITIES_PRIORITYRANGE
-_CACHECAPABILITIES.fields_by_name['digest_function'].enum_type = _DIGESTFUNCTION
-_CACHECAPABILITIES.fields_by_name['action_cache_update_capabilities'].message_type = _ACTIONCACHEUPDATECAPABILITIES
-_CACHECAPABILITIES.fields_by_name['cache_priority_capabilities'].message_type = _PRIORITYCAPABILITIES
-_CACHECAPABILITIES.fields_by_name['symlink_absolute_path_strategy'].enum_type = _CACHECAPABILITIES_SYMLINKABSOLUTEPATHSTRATEGY
-_CACHECAPABILITIES_SYMLINKABSOLUTEPATHSTRATEGY.containing_type = _CACHECAPABILITIES
-_EXECUTIONCAPABILITIES.fields_by_name['digest_function'].enum_type = _DIGESTFUNCTION
-_EXECUTIONCAPABILITIES.fields_by_name['execution_priority_capabilities'].message_type = _PRIORITYCAPABILITIES
-_REQUESTMETADATA.fields_by_name['tool_details'].message_type = _TOOLDETAILS
-DESCRIPTOR.message_types_by_name['Action'] = _ACTION
-DESCRIPTOR.message_types_by_name['Command'] = _COMMAND
-DESCRIPTOR.message_types_by_name['Platform'] = _PLATFORM
-DESCRIPTOR.message_types_by_name['Directory'] = _DIRECTORY
-DESCRIPTOR.message_types_by_name['FileNode'] = _FILENODE
-DESCRIPTOR.message_types_by_name['DirectoryNode'] = _DIRECTORYNODE
-DESCRIPTOR.message_types_by_name['SymlinkNode'] = _SYMLINKNODE
-DESCRIPTOR.message_types_by_name['Digest'] = _DIGEST
-DESCRIPTOR.message_types_by_name['ExecutedActionMetadata'] = _EXECUTEDACTIONMETADATA
-DESCRIPTOR.message_types_by_name['ActionResult'] = _ACTIONRESULT
-DESCRIPTOR.message_types_by_name['OutputFile'] = _OUTPUTFILE
-DESCRIPTOR.message_types_by_name['Tree'] = _TREE
-DESCRIPTOR.message_types_by_name['OutputDirectory'] = _OUTPUTDIRECTORY
-DESCRIPTOR.message_types_by_name['ExecutionPolicy'] = _EXECUTIONPOLICY
-DESCRIPTOR.message_types_by_name['ResultsCachePolicy'] = _RESULTSCACHEPOLICY
-DESCRIPTOR.message_types_by_name['ExecuteRequest'] = _EXECUTEREQUEST
-DESCRIPTOR.message_types_by_name['LogFile'] = _LOGFILE
-DESCRIPTOR.message_types_by_name['ExecuteResponse'] = _EXECUTERESPONSE
-DESCRIPTOR.message_types_by_name['ExecuteOperationMetadata'] = _EXECUTEOPERATIONMETADATA
-DESCRIPTOR.message_types_by_name['WaitExecutionRequest'] = _WAITEXECUTIONREQUEST
-DESCRIPTOR.message_types_by_name['GetActionResultRequest'] = _GETACTIONRESULTREQUEST
-DESCRIPTOR.message_types_by_name['UpdateActionResultRequest'] = _UPDATEACTIONRESULTREQUEST
-DESCRIPTOR.message_types_by_name['FindMissingBlobsRequest'] = _FINDMISSINGBLOBSREQUEST
-DESCRIPTOR.message_types_by_name['FindMissingBlobsResponse'] = _FINDMISSINGBLOBSRESPONSE
-DESCRIPTOR.message_types_by_name['BatchUpdateBlobsRequest'] = _BATCHUPDATEBLOBSREQUEST
-DESCRIPTOR.message_types_by_name['BatchUpdateBlobsResponse'] = _BATCHUPDATEBLOBSRESPONSE
-DESCRIPTOR.message_types_by_name['BatchReadBlobsRequest'] = _BATCHREADBLOBSREQUEST
-DESCRIPTOR.message_types_by_name['BatchReadBlobsResponse'] = _BATCHREADBLOBSRESPONSE
-DESCRIPTOR.message_types_by_name['GetTreeRequest'] = _GETTREEREQUEST
-DESCRIPTOR.message_types_by_name['GetTreeResponse'] = _GETTREERESPONSE
-DESCRIPTOR.message_types_by_name['GetCapabilitiesRequest'] = _GETCAPABILITIESREQUEST
-DESCRIPTOR.message_types_by_name['ServerCapabilities'] = _SERVERCAPABILITIES
-DESCRIPTOR.message_types_by_name['ActionCacheUpdateCapabilities'] = _ACTIONCACHEUPDATECAPABILITIES
-DESCRIPTOR.message_types_by_name['PriorityCapabilities'] = _PRIORITYCAPABILITIES
-DESCRIPTOR.message_types_by_name['CacheCapabilities'] = _CACHECAPABILITIES
-DESCRIPTOR.message_types_by_name['ExecutionCapabilities'] = _EXECUTIONCAPABILITIES
-DESCRIPTOR.message_types_by_name['ToolDetails'] = _TOOLDETAILS
-DESCRIPTOR.message_types_by_name['RequestMetadata'] = _REQUESTMETADATA
-DESCRIPTOR.enum_types_by_name['DigestFunction'] = _DIGESTFUNCTION
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-Action = _reflection.GeneratedProtocolMessageType('Action', (_message.Message,), dict(
- DESCRIPTOR = _ACTION,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Action)
- ))
-_sym_db.RegisterMessage(Action)
-
-Command = _reflection.GeneratedProtocolMessageType('Command', (_message.Message,), dict(
-
- EnvironmentVariable = _reflection.GeneratedProtocolMessageType('EnvironmentVariable', (_message.Message,), dict(
- DESCRIPTOR = _COMMAND_ENVIRONMENTVARIABLE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Command.EnvironmentVariable)
- ))
- ,
- DESCRIPTOR = _COMMAND,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Command)
- ))
-_sym_db.RegisterMessage(Command)
-_sym_db.RegisterMessage(Command.EnvironmentVariable)
-
-Platform = _reflection.GeneratedProtocolMessageType('Platform', (_message.Message,), dict(
-
- Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict(
- DESCRIPTOR = _PLATFORM_PROPERTY,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Platform.Property)
- ))
- ,
- DESCRIPTOR = _PLATFORM,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Platform)
- ))
-_sym_db.RegisterMessage(Platform)
-_sym_db.RegisterMessage(Platform.Property)
-
-Directory = _reflection.GeneratedProtocolMessageType('Directory', (_message.Message,), dict(
- DESCRIPTOR = _DIRECTORY,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Directory)
- ))
-_sym_db.RegisterMessage(Directory)
-
-FileNode = _reflection.GeneratedProtocolMessageType('FileNode', (_message.Message,), dict(
- DESCRIPTOR = _FILENODE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.FileNode)
- ))
-_sym_db.RegisterMessage(FileNode)
-
-DirectoryNode = _reflection.GeneratedProtocolMessageType('DirectoryNode', (_message.Message,), dict(
- DESCRIPTOR = _DIRECTORYNODE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.DirectoryNode)
- ))
-_sym_db.RegisterMessage(DirectoryNode)
-
-SymlinkNode = _reflection.GeneratedProtocolMessageType('SymlinkNode', (_message.Message,), dict(
- DESCRIPTOR = _SYMLINKNODE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.SymlinkNode)
- ))
-_sym_db.RegisterMessage(SymlinkNode)
-
-Digest = _reflection.GeneratedProtocolMessageType('Digest', (_message.Message,), dict(
- DESCRIPTOR = _DIGEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Digest)
- ))
-_sym_db.RegisterMessage(Digest)
-
-ExecutedActionMetadata = _reflection.GeneratedProtocolMessageType('ExecutedActionMetadata', (_message.Message,), dict(
- DESCRIPTOR = _EXECUTEDACTIONMETADATA,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ExecutedActionMetadata)
- ))
-_sym_db.RegisterMessage(ExecutedActionMetadata)
-
-ActionResult = _reflection.GeneratedProtocolMessageType('ActionResult', (_message.Message,), dict(
- DESCRIPTOR = _ACTIONRESULT,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ActionResult)
- ))
-_sym_db.RegisterMessage(ActionResult)
-
-OutputFile = _reflection.GeneratedProtocolMessageType('OutputFile', (_message.Message,), dict(
- DESCRIPTOR = _OUTPUTFILE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.OutputFile)
- ))
-_sym_db.RegisterMessage(OutputFile)
-
-Tree = _reflection.GeneratedProtocolMessageType('Tree', (_message.Message,), dict(
- DESCRIPTOR = _TREE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.Tree)
- ))
-_sym_db.RegisterMessage(Tree)
-
-OutputDirectory = _reflection.GeneratedProtocolMessageType('OutputDirectory', (_message.Message,), dict(
- DESCRIPTOR = _OUTPUTDIRECTORY,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.OutputDirectory)
- ))
-_sym_db.RegisterMessage(OutputDirectory)
-
-ExecutionPolicy = _reflection.GeneratedProtocolMessageType('ExecutionPolicy', (_message.Message,), dict(
- DESCRIPTOR = _EXECUTIONPOLICY,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ExecutionPolicy)
- ))
-_sym_db.RegisterMessage(ExecutionPolicy)
-
-ResultsCachePolicy = _reflection.GeneratedProtocolMessageType('ResultsCachePolicy', (_message.Message,), dict(
- DESCRIPTOR = _RESULTSCACHEPOLICY,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ResultsCachePolicy)
- ))
-_sym_db.RegisterMessage(ResultsCachePolicy)
-
-ExecuteRequest = _reflection.GeneratedProtocolMessageType('ExecuteRequest', (_message.Message,), dict(
- DESCRIPTOR = _EXECUTEREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ExecuteRequest)
- ))
-_sym_db.RegisterMessage(ExecuteRequest)
-
-LogFile = _reflection.GeneratedProtocolMessageType('LogFile', (_message.Message,), dict(
- DESCRIPTOR = _LOGFILE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.LogFile)
- ))
-_sym_db.RegisterMessage(LogFile)
-
-ExecuteResponse = _reflection.GeneratedProtocolMessageType('ExecuteResponse', (_message.Message,), dict(
-
- ServerLogsEntry = _reflection.GeneratedProtocolMessageType('ServerLogsEntry', (_message.Message,), dict(
- DESCRIPTOR = _EXECUTERESPONSE_SERVERLOGSENTRY,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ExecuteResponse.ServerLogsEntry)
- ))
- ,
- DESCRIPTOR = _EXECUTERESPONSE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ExecuteResponse)
- ))
-_sym_db.RegisterMessage(ExecuteResponse)
-_sym_db.RegisterMessage(ExecuteResponse.ServerLogsEntry)
-
-ExecuteOperationMetadata = _reflection.GeneratedProtocolMessageType('ExecuteOperationMetadata', (_message.Message,), dict(
- DESCRIPTOR = _EXECUTEOPERATIONMETADATA,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ExecuteOperationMetadata)
- ))
-_sym_db.RegisterMessage(ExecuteOperationMetadata)
-
-WaitExecutionRequest = _reflection.GeneratedProtocolMessageType('WaitExecutionRequest', (_message.Message,), dict(
- DESCRIPTOR = _WAITEXECUTIONREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.WaitExecutionRequest)
- ))
-_sym_db.RegisterMessage(WaitExecutionRequest)
-
-GetActionResultRequest = _reflection.GeneratedProtocolMessageType('GetActionResultRequest', (_message.Message,), dict(
- DESCRIPTOR = _GETACTIONRESULTREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.GetActionResultRequest)
- ))
-_sym_db.RegisterMessage(GetActionResultRequest)
-
-UpdateActionResultRequest = _reflection.GeneratedProtocolMessageType('UpdateActionResultRequest', (_message.Message,), dict(
- DESCRIPTOR = _UPDATEACTIONRESULTREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.UpdateActionResultRequest)
- ))
-_sym_db.RegisterMessage(UpdateActionResultRequest)
-
-FindMissingBlobsRequest = _reflection.GeneratedProtocolMessageType('FindMissingBlobsRequest', (_message.Message,), dict(
- DESCRIPTOR = _FINDMISSINGBLOBSREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.FindMissingBlobsRequest)
- ))
-_sym_db.RegisterMessage(FindMissingBlobsRequest)
-
-FindMissingBlobsResponse = _reflection.GeneratedProtocolMessageType('FindMissingBlobsResponse', (_message.Message,), dict(
- DESCRIPTOR = _FINDMISSINGBLOBSRESPONSE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.FindMissingBlobsResponse)
- ))
-_sym_db.RegisterMessage(FindMissingBlobsResponse)
-
-BatchUpdateBlobsRequest = _reflection.GeneratedProtocolMessageType('BatchUpdateBlobsRequest', (_message.Message,), dict(
-
- Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict(
- DESCRIPTOR = _BATCHUPDATEBLOBSREQUEST_REQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.BatchUpdateBlobsRequest.Request)
- ))
- ,
- DESCRIPTOR = _BATCHUPDATEBLOBSREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.BatchUpdateBlobsRequest)
- ))
-_sym_db.RegisterMessage(BatchUpdateBlobsRequest)
-_sym_db.RegisterMessage(BatchUpdateBlobsRequest.Request)
-
-BatchUpdateBlobsResponse = _reflection.GeneratedProtocolMessageType('BatchUpdateBlobsResponse', (_message.Message,), dict(
-
- Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict(
- DESCRIPTOR = _BATCHUPDATEBLOBSRESPONSE_RESPONSE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.BatchUpdateBlobsResponse.Response)
- ))
- ,
- DESCRIPTOR = _BATCHUPDATEBLOBSRESPONSE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.BatchUpdateBlobsResponse)
- ))
-_sym_db.RegisterMessage(BatchUpdateBlobsResponse)
-_sym_db.RegisterMessage(BatchUpdateBlobsResponse.Response)
-
-BatchReadBlobsRequest = _reflection.GeneratedProtocolMessageType('BatchReadBlobsRequest', (_message.Message,), dict(
- DESCRIPTOR = _BATCHREADBLOBSREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.BatchReadBlobsRequest)
- ))
-_sym_db.RegisterMessage(BatchReadBlobsRequest)
-
-BatchReadBlobsResponse = _reflection.GeneratedProtocolMessageType('BatchReadBlobsResponse', (_message.Message,), dict(
-
- Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict(
- DESCRIPTOR = _BATCHREADBLOBSRESPONSE_RESPONSE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.BatchReadBlobsResponse.Response)
- ))
- ,
- DESCRIPTOR = _BATCHREADBLOBSRESPONSE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.BatchReadBlobsResponse)
- ))
-_sym_db.RegisterMessage(BatchReadBlobsResponse)
-_sym_db.RegisterMessage(BatchReadBlobsResponse.Response)
-
-GetTreeRequest = _reflection.GeneratedProtocolMessageType('GetTreeRequest', (_message.Message,), dict(
- DESCRIPTOR = _GETTREEREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.GetTreeRequest)
- ))
-_sym_db.RegisterMessage(GetTreeRequest)
-
-GetTreeResponse = _reflection.GeneratedProtocolMessageType('GetTreeResponse', (_message.Message,), dict(
- DESCRIPTOR = _GETTREERESPONSE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.GetTreeResponse)
- ))
-_sym_db.RegisterMessage(GetTreeResponse)
-
-GetCapabilitiesRequest = _reflection.GeneratedProtocolMessageType('GetCapabilitiesRequest', (_message.Message,), dict(
- DESCRIPTOR = _GETCAPABILITIESREQUEST,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.GetCapabilitiesRequest)
- ))
-_sym_db.RegisterMessage(GetCapabilitiesRequest)
-
-ServerCapabilities = _reflection.GeneratedProtocolMessageType('ServerCapabilities', (_message.Message,), dict(
- DESCRIPTOR = _SERVERCAPABILITIES,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ServerCapabilities)
- ))
-_sym_db.RegisterMessage(ServerCapabilities)
-
-ActionCacheUpdateCapabilities = _reflection.GeneratedProtocolMessageType('ActionCacheUpdateCapabilities', (_message.Message,), dict(
- DESCRIPTOR = _ACTIONCACHEUPDATECAPABILITIES,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ActionCacheUpdateCapabilities)
- ))
-_sym_db.RegisterMessage(ActionCacheUpdateCapabilities)
-
-PriorityCapabilities = _reflection.GeneratedProtocolMessageType('PriorityCapabilities', (_message.Message,), dict(
-
- PriorityRange = _reflection.GeneratedProtocolMessageType('PriorityRange', (_message.Message,), dict(
- DESCRIPTOR = _PRIORITYCAPABILITIES_PRIORITYRANGE,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.PriorityCapabilities.PriorityRange)
- ))
- ,
- DESCRIPTOR = _PRIORITYCAPABILITIES,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.PriorityCapabilities)
- ))
-_sym_db.RegisterMessage(PriorityCapabilities)
-_sym_db.RegisterMessage(PriorityCapabilities.PriorityRange)
-
-CacheCapabilities = _reflection.GeneratedProtocolMessageType('CacheCapabilities', (_message.Message,), dict(
- DESCRIPTOR = _CACHECAPABILITIES,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.CacheCapabilities)
- ))
-_sym_db.RegisterMessage(CacheCapabilities)
-
-ExecutionCapabilities = _reflection.GeneratedProtocolMessageType('ExecutionCapabilities', (_message.Message,), dict(
- DESCRIPTOR = _EXECUTIONCAPABILITIES,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ExecutionCapabilities)
- ))
-_sym_db.RegisterMessage(ExecutionCapabilities)
-
-ToolDetails = _reflection.GeneratedProtocolMessageType('ToolDetails', (_message.Message,), dict(
- DESCRIPTOR = _TOOLDETAILS,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.ToolDetails)
- ))
-_sym_db.RegisterMessage(ToolDetails)
-
-RequestMetadata = _reflection.GeneratedProtocolMessageType('RequestMetadata', (_message.Message,), dict(
- DESCRIPTOR = _REQUESTMETADATA,
- __module__ = 'build.bazel.remote.execution.v2.remote_execution_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.remote.execution.v2.RequestMetadata)
- ))
-_sym_db.RegisterMessage(RequestMetadata)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\037build.bazel.remote.execution.v2B\024RemoteExecutionProtoP\001Z\017remoteexecution\242\002\003REX\252\002\037Build.Bazel.Remote.Execution.V2'))
-_EXECUTERESPONSE_SERVERLOGSENTRY.has_options = True
-_EXECUTERESPONSE_SERVERLOGSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001'))
-
-_EXECUTION = _descriptor.ServiceDescriptor(
- name='Execution',
- full_name='build.bazel.remote.execution.v2.Execution',
- file=DESCRIPTOR,
- index=0,
- options=None,
- serialized_start=7276,
- serialized_end=7589,
- methods=[
- _descriptor.MethodDescriptor(
- name='Execute',
- full_name='build.bazel.remote.execution.v2.Execution.Execute',
- index=0,
- containing_service=None,
- input_type=_EXECUTEREQUEST,
- output_type=google_dot_longrunning_dot_operations__pb2._OPERATION,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002+\"&/v2/{instance_name=**}/actions:execute:\001*')),
- ),
- _descriptor.MethodDescriptor(
- name='WaitExecution',
- full_name='build.bazel.remote.execution.v2.Execution.WaitExecution',
- index=1,
- containing_service=None,
- input_type=_WAITEXECUTIONREQUEST,
- output_type=google_dot_longrunning_dot_operations__pb2._OPERATION,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002+\"&/v2/{name=operations/**}:waitExecution:\001*')),
- ),
-])
-_sym_db.RegisterServiceDescriptor(_EXECUTION)
-
-DESCRIPTOR.services_by_name['Execution'] = _EXECUTION
-
-
-_ACTIONCACHE = _descriptor.ServiceDescriptor(
- name='ActionCache',
- full_name='build.bazel.remote.execution.v2.ActionCache',
- file=DESCRIPTOR,
- index=1,
- options=None,
- serialized_start=7592,
- serialized_end=8062,
- methods=[
- _descriptor.MethodDescriptor(
- name='GetActionResult',
- full_name='build.bazel.remote.execution.v2.ActionCache.GetActionResult',
- index=0,
- containing_service=None,
- input_type=_GETACTIONRESULTREQUEST,
- output_type=_ACTIONRESULT,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002V\022T/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}')),
- ),
- _descriptor.MethodDescriptor(
- name='UpdateActionResult',
- full_name='build.bazel.remote.execution.v2.ActionCache.UpdateActionResult',
- index=1,
- containing_service=None,
- input_type=_UPDATEACTIONRESULTREQUEST,
- output_type=_ACTIONRESULT,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002e\032T/v2/{instance_name=**}/actionResults/{action_digest.hash}/{action_digest.size_bytes}:\raction_result')),
- ),
-])
-_sym_db.RegisterServiceDescriptor(_ACTIONCACHE)
-
-DESCRIPTOR.services_by_name['ActionCache'] = _ACTIONCACHE
-
-
-_CONTENTADDRESSABLESTORAGE = _descriptor.ServiceDescriptor(
- name='ContentAddressableStorage',
- full_name='build.bazel.remote.execution.v2.ContentAddressableStorage',
- file=DESCRIPTOR,
- index=2,
- options=None,
- serialized_start=8065,
- serialized_end=8860,
- methods=[
- _descriptor.MethodDescriptor(
- name='FindMissingBlobs',
- full_name='build.bazel.remote.execution.v2.ContentAddressableStorage.FindMissingBlobs',
- index=0,
- containing_service=None,
- input_type=_FINDMISSINGBLOBSREQUEST,
- output_type=_FINDMISSINGBLOBSRESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002-\"(/v2/{instance_name=**}/blobs:findMissing:\001*')),
- ),
- _descriptor.MethodDescriptor(
- name='BatchUpdateBlobs',
- full_name='build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs',
- index=1,
- containing_service=None,
- input_type=_BATCHUPDATEBLOBSREQUEST,
- output_type=_BATCHUPDATEBLOBSRESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002-\"(/v2/{instance_name=**}/blobs:batchUpdate:\001*')),
- ),
- _descriptor.MethodDescriptor(
- name='BatchReadBlobs',
- full_name='build.bazel.remote.execution.v2.ContentAddressableStorage.BatchReadBlobs',
- index=2,
- containing_service=None,
- input_type=_BATCHREADBLOBSREQUEST,
- output_type=_BATCHREADBLOBSRESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002+\"&/v2/{instance_name=**}/blobs:batchRead:\001*')),
- ),
- _descriptor.MethodDescriptor(
- name='GetTree',
- full_name='build.bazel.remote.execution.v2.ContentAddressableStorage.GetTree',
- index=3,
- containing_service=None,
- input_type=_GETTREEREQUEST,
- output_type=_GETTREERESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002R\022P/v2/{instance_name=**}/blobs/{root_digest.hash}/{root_digest.size_bytes}:getTree')),
- ),
-])
-_sym_db.RegisterServiceDescriptor(_CONTENTADDRESSABLESTORAGE)
-
-DESCRIPTOR.services_by_name['ContentAddressableStorage'] = _CONTENTADDRESSABLESTORAGE
-
-
-_CAPABILITIES = _descriptor.ServiceDescriptor(
- name='Capabilities',
- full_name='build.bazel.remote.execution.v2.Capabilities',
- file=DESCRIPTOR,
- index=3,
- options=None,
- serialized_start=8863,
- serialized_end=9052,
- methods=[
- _descriptor.MethodDescriptor(
- name='GetCapabilities',
- full_name='build.bazel.remote.execution.v2.Capabilities.GetCapabilities',
- index=0,
- containing_service=None,
- input_type=_GETCAPABILITIESREQUEST,
- output_type=_SERVERCAPABILITIES,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002%\022#/v2/{instance_name=**}/capabilities')),
- ),
-])
-_sym_db.RegisterServiceDescriptor(_CAPABILITIES)
-
-DESCRIPTOR.services_by_name['Capabilities'] = _CAPABILITIES
-
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py b/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py
deleted file mode 100644
index 3769a680d..000000000
--- a/buildstream/_protos/build/bazel/remote/execution/v2/remote_execution_pb2_grpc.py
+++ /dev/null
@@ -1,593 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
-from buildstream._protos.build.bazel.remote.execution.v2 import remote_execution_pb2 as build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2
-from buildstream._protos.google.longrunning import operations_pb2 as google_dot_longrunning_dot_operations__pb2
-
-
-class ExecutionStub(object):
- """The Remote Execution API is used to execute an
- [Action][build.bazel.remote.execution.v2.Action] on the remote
- workers.
-
- As with other services in the Remote Execution API, any call may return an
- error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
- information about when the client should retry the request; clients SHOULD
- respect the information provided.
- """
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.Execute = channel.unary_stream(
- '/build.bazel.remote.execution.v2.Execution/Execute',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ExecuteRequest.SerializeToString,
- response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString,
- )
- self.WaitExecution = channel.unary_stream(
- '/build.bazel.remote.execution.v2.Execution/WaitExecution',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.WaitExecutionRequest.SerializeToString,
- response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString,
- )
-
-
-class ExecutionServicer(object):
- """The Remote Execution API is used to execute an
- [Action][build.bazel.remote.execution.v2.Action] on the remote
- workers.
-
- As with other services in the Remote Execution API, any call may return an
- error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
- information about when the client should retry the request; clients SHOULD
- respect the information provided.
- """
-
- def Execute(self, request, context):
- """Execute an action remotely.
-
- In order to execute an action, the client must first upload all of the
- inputs, the
- [Command][build.bazel.remote.execution.v2.Command] to run, and the
- [Action][build.bazel.remote.execution.v2.Action] into the
- [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage].
- It then calls `Execute` with an `action_digest` referring to them. The
- server will run the action and eventually return the result.
-
- The input `Action`'s fields MUST meet the various canonicalization
- requirements specified in the documentation for their types so that it has
- the same digest as other logically equivalent `Action`s. The server MAY
- enforce the requirements and return errors if a non-canonical input is
- received. It MAY also proceed without verifying some or all of the
- requirements, such as for performance reasons. If the server does not
- verify the requirement, then it will treat the `Action` as distinct from
- another logically equivalent action if they hash differently.
-
- Returns a stream of
- [google.longrunning.Operation][google.longrunning.Operation] messages
- describing the resulting execution, with eventual `response`
- [ExecuteResponse][build.bazel.remote.execution.v2.ExecuteResponse]. The
- `metadata` on the operation is of type
- [ExecuteOperationMetadata][build.bazel.remote.execution.v2.ExecuteOperationMetadata].
-
- If the client remains connected after the first response is returned after
- the server, then updates are streamed as if the client had called
- [WaitExecution][build.bazel.remote.execution.v2.Execution.WaitExecution]
- until the execution completes or the request reaches an error. The
- operation can also be queried using [Operations
- API][google.longrunning.Operations.GetOperation].
-
- The server NEED NOT implement other methods or functionality of the
- Operations API.
-
- Errors discovered during creation of the `Operation` will be reported
- as gRPC Status errors, while errors that occurred while running the
- action will be reported in the `status` field of the `ExecuteResponse`. The
- server MUST NOT set the `error` field of the `Operation` proto.
- The possible errors include:
- * `INVALID_ARGUMENT`: One or more arguments are invalid.
- * `FAILED_PRECONDITION`: One or more errors occurred in setting up the
- action requested, such as a missing input or command or no worker being
- available. The client may be able to fix the errors and retry.
- * `RESOURCE_EXHAUSTED`: There is insufficient quota of some resource to run
- the action.
- * `UNAVAILABLE`: Due to a transient condition, such as all workers being
- occupied (and the server does not support a queue), the action could not
- be started. The client should retry.
- * `INTERNAL`: An internal error occurred in the execution engine or the
- worker.
- * `DEADLINE_EXCEEDED`: The execution timed out.
-
- In the case of a missing input or command, the server SHOULD additionally
- send a [PreconditionFailure][google.rpc.PreconditionFailure] error detail
- where, for each requested blob not present in the CAS, there is a
- `Violation` with a `type` of `MISSING` and a `subject` of
- `"blobs/{hash}/{size}"` indicating the digest of the missing blob.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def WaitExecution(self, request, context):
- """Wait for an execution operation to complete. When the client initially
- makes the request, the server immediately responds with the current status
- of the execution. The server will leave the request stream open until the
- operation completes, and then respond with the completed operation. The
- server MAY choose to stream additional updates as execution progresses,
- such as to provide an update as to the state of the execution.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_ExecutionServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'Execute': grpc.unary_stream_rpc_method_handler(
- servicer.Execute,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ExecuteRequest.FromString,
- response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString,
- ),
- 'WaitExecution': grpc.unary_stream_rpc_method_handler(
- servicer.WaitExecution,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.WaitExecutionRequest.FromString,
- response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'build.bazel.remote.execution.v2.Execution', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
-
-
-class ActionCacheStub(object):
- """The action cache API is used to query whether a given action has already been
- performed and, if so, retrieve its result. Unlike the
- [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage],
- which addresses blobs by their own content, the action cache addresses the
- [ActionResult][build.bazel.remote.execution.v2.ActionResult] by a
- digest of the encoded [Action][build.bazel.remote.execution.v2.Action]
- which produced them.
-
- The lifetime of entries in the action cache is implementation-specific, but
- the server SHOULD assume that more recently used entries are more likely to
- be used again. Additionally, action cache implementations SHOULD ensure that
- any blobs referenced in the
- [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage]
- are still valid when returning a result.
-
- As with other services in the Remote Execution API, any call may return an
- error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
- information about when the client should retry the request; clients SHOULD
- respect the information provided.
- """
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.GetActionResult = channel.unary_unary(
- '/build.bazel.remote.execution.v2.ActionCache/GetActionResult',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetActionResultRequest.SerializeToString,
- response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.FromString,
- )
- self.UpdateActionResult = channel.unary_unary(
- '/build.bazel.remote.execution.v2.ActionCache/UpdateActionResult',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.UpdateActionResultRequest.SerializeToString,
- response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.FromString,
- )
-
-
-class ActionCacheServicer(object):
- """The action cache API is used to query whether a given action has already been
- performed and, if so, retrieve its result. Unlike the
- [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage],
- which addresses blobs by their own content, the action cache addresses the
- [ActionResult][build.bazel.remote.execution.v2.ActionResult] by a
- digest of the encoded [Action][build.bazel.remote.execution.v2.Action]
- which produced them.
-
- The lifetime of entries in the action cache is implementation-specific, but
- the server SHOULD assume that more recently used entries are more likely to
- be used again. Additionally, action cache implementations SHOULD ensure that
- any blobs referenced in the
- [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage]
- are still valid when returning a result.
-
- As with other services in the Remote Execution API, any call may return an
- error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
- information about when the client should retry the request; clients SHOULD
- respect the information provided.
- """
-
- def GetActionResult(self, request, context):
- """Retrieve a cached execution result.
-
- Errors:
- * `NOT_FOUND`: The requested `ActionResult` is not in the cache.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def UpdateActionResult(self, request, context):
- """Upload a new execution result.
-
- This method is intended for servers which implement the distributed cache
- independently of the
- [Execution][build.bazel.remote.execution.v2.Execution] API. As a
- result, it is OPTIONAL for servers to implement.
-
- In order to allow the server to perform access control based on the type of
- action, and to assist with client debugging, the client MUST first upload
- the [Action][build.bazel.remote.execution.v2.Execution] that produced the
- result, along with its
- [Command][build.bazel.remote.execution.v2.Command], into the
- `ContentAddressableStorage`.
-
- Errors:
- * `NOT_IMPLEMENTED`: This method is not supported by the server.
- * `RESOURCE_EXHAUSTED`: There is insufficient storage space to add the
- entry to the cache.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_ActionCacheServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'GetActionResult': grpc.unary_unary_rpc_method_handler(
- servicer.GetActionResult,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetActionResultRequest.FromString,
- response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.SerializeToString,
- ),
- 'UpdateActionResult': grpc.unary_unary_rpc_method_handler(
- servicer.UpdateActionResult,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.UpdateActionResultRequest.FromString,
- response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ActionResult.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'build.bazel.remote.execution.v2.ActionCache', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
-
-
-class ContentAddressableStorageStub(object):
- """The CAS (content-addressable storage) is used to store the inputs to and
- outputs from the execution service. Each piece of content is addressed by the
- digest of its binary data.
-
- Most of the binary data stored in the CAS is opaque to the execution engine,
- and is only used as a communication medium. In order to build an
- [Action][build.bazel.remote.execution.v2.Action],
- however, the client will need to also upload the
- [Command][build.bazel.remote.execution.v2.Command] and input root
- [Directory][build.bazel.remote.execution.v2.Directory] for the Action.
- The Command and Directory messages must be marshalled to wire format and then
- uploaded under the hash as with any other piece of content. In practice, the
- input root directory is likely to refer to other Directories in its
- hierarchy, which must also each be uploaded on their own.
-
- For small file uploads the client should group them together and call
- [BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs]
- on chunks of no more than 10 MiB. For large uploads, the client must use the
- [Write method][google.bytestream.ByteStream.Write] of the ByteStream API. The
- `resource_name` is `{instance_name}/uploads/{uuid}/blobs/{hash}/{size}`,
- where `instance_name` is as described in the next paragraph, `uuid` is a
- version 4 UUID generated by the client, and `hash` and `size` are the
- [Digest][build.bazel.remote.execution.v2.Digest] of the blob. The
- `uuid` is used only to avoid collisions when multiple clients try to upload
- the same file (or the same client tries to upload the file multiple times at
- once on different threads), so the client MAY reuse the `uuid` for uploading
- different blobs. The `resource_name` may optionally have a trailing filename
- (or other metadata) for a client to use if it is storing URLs, as in
- `{instance}/uploads/{uuid}/blobs/{hash}/{size}/foo/bar/baz.cc`. Anything
- after the `size` is ignored.
-
- A single server MAY support multiple instances of the execution system, each
- with their own workers, storage, cache, etc. The exact relationship between
- instances is up to the server. If the server does, then the `instance_name`
- is an identifier, possibly containing multiple path segments, used to
- distinguish between the various instances on the server, in a manner defined
- by the server. For servers which do not support multiple instances, then the
- `instance_name` is the empty path and the leading slash is omitted, so that
- the `resource_name` becomes `uploads/{uuid}/blobs/{hash}/{size}`.
-
- When attempting an upload, if another client has already completed the upload
- (which may occur in the middle of a single upload if another client uploads
- the same blob concurrently), the request will terminate immediately with
- a response whose `committed_size` is the full size of the uploaded file
- (regardless of how much data was transmitted by the client). If the client
- completes the upload but the
- [Digest][build.bazel.remote.execution.v2.Digest] does not match, an
- `INVALID_ARGUMENT` error will be returned. In either case, the client should
- not attempt to retry the upload.
-
- For downloading blobs, the client must use the
- [Read method][google.bytestream.ByteStream.Read] of the ByteStream API, with
- a `resource_name` of `"{instance_name}/blobs/{hash}/{size}"`, where
- `instance_name` is the instance name (see above), and `hash` and `size` are
- the [Digest][build.bazel.remote.execution.v2.Digest] of the blob.
-
- The lifetime of entries in the CAS is implementation specific, but it SHOULD
- be long enough to allow for newly-added and recently looked-up entries to be
- used in subsequent calls (e.g. to
- [Execute][build.bazel.remote.execution.v2.Execution.Execute]).
-
- As with other services in the Remote Execution API, any call may return an
- error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
- information about when the client should retry the request; clients SHOULD
- respect the information provided.
- """
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.FindMissingBlobs = channel.unary_unary(
- '/build.bazel.remote.execution.v2.ContentAddressableStorage/FindMissingBlobs',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsRequest.SerializeToString,
- response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsResponse.FromString,
- )
- self.BatchUpdateBlobs = channel.unary_unary(
- '/build.bazel.remote.execution.v2.ContentAddressableStorage/BatchUpdateBlobs',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsRequest.SerializeToString,
- response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsResponse.FromString,
- )
- self.BatchReadBlobs = channel.unary_unary(
- '/build.bazel.remote.execution.v2.ContentAddressableStorage/BatchReadBlobs',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsRequest.SerializeToString,
- response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsResponse.FromString,
- )
- self.GetTree = channel.unary_stream(
- '/build.bazel.remote.execution.v2.ContentAddressableStorage/GetTree',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeRequest.SerializeToString,
- response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeResponse.FromString,
- )
-
-
-class ContentAddressableStorageServicer(object):
- """The CAS (content-addressable storage) is used to store the inputs to and
- outputs from the execution service. Each piece of content is addressed by the
- digest of its binary data.
-
- Most of the binary data stored in the CAS is opaque to the execution engine,
- and is only used as a communication medium. In order to build an
- [Action][build.bazel.remote.execution.v2.Action],
- however, the client will need to also upload the
- [Command][build.bazel.remote.execution.v2.Command] and input root
- [Directory][build.bazel.remote.execution.v2.Directory] for the Action.
- The Command and Directory messages must be marshalled to wire format and then
- uploaded under the hash as with any other piece of content. In practice, the
- input root directory is likely to refer to other Directories in its
- hierarchy, which must also each be uploaded on their own.
-
- For small file uploads the client should group them together and call
- [BatchUpdateBlobs][build.bazel.remote.execution.v2.ContentAddressableStorage.BatchUpdateBlobs]
- on chunks of no more than 10 MiB. For large uploads, the client must use the
- [Write method][google.bytestream.ByteStream.Write] of the ByteStream API. The
- `resource_name` is `{instance_name}/uploads/{uuid}/blobs/{hash}/{size}`,
- where `instance_name` is as described in the next paragraph, `uuid` is a
- version 4 UUID generated by the client, and `hash` and `size` are the
- [Digest][build.bazel.remote.execution.v2.Digest] of the blob. The
- `uuid` is used only to avoid collisions when multiple clients try to upload
- the same file (or the same client tries to upload the file multiple times at
- once on different threads), so the client MAY reuse the `uuid` for uploading
- different blobs. The `resource_name` may optionally have a trailing filename
- (or other metadata) for a client to use if it is storing URLs, as in
- `{instance}/uploads/{uuid}/blobs/{hash}/{size}/foo/bar/baz.cc`. Anything
- after the `size` is ignored.
-
- A single server MAY support multiple instances of the execution system, each
- with their own workers, storage, cache, etc. The exact relationship between
- instances is up to the server. If the server does, then the `instance_name`
- is an identifier, possibly containing multiple path segments, used to
- distinguish between the various instances on the server, in a manner defined
- by the server. For servers which do not support multiple instances, then the
- `instance_name` is the empty path and the leading slash is omitted, so that
- the `resource_name` becomes `uploads/{uuid}/blobs/{hash}/{size}`.
-
- When attempting an upload, if another client has already completed the upload
- (which may occur in the middle of a single upload if another client uploads
- the same blob concurrently), the request will terminate immediately with
- a response whose `committed_size` is the full size of the uploaded file
- (regardless of how much data was transmitted by the client). If the client
- completes the upload but the
- [Digest][build.bazel.remote.execution.v2.Digest] does not match, an
- `INVALID_ARGUMENT` error will be returned. In either case, the client should
- not attempt to retry the upload.
-
- For downloading blobs, the client must use the
- [Read method][google.bytestream.ByteStream.Read] of the ByteStream API, with
- a `resource_name` of `"{instance_name}/blobs/{hash}/{size}"`, where
- `instance_name` is the instance name (see above), and `hash` and `size` are
- the [Digest][build.bazel.remote.execution.v2.Digest] of the blob.
-
- The lifetime of entries in the CAS is implementation specific, but it SHOULD
- be long enough to allow for newly-added and recently looked-up entries to be
- used in subsequent calls (e.g. to
- [Execute][build.bazel.remote.execution.v2.Execution.Execute]).
-
- As with other services in the Remote Execution API, any call may return an
- error with a [RetryInfo][google.rpc.RetryInfo] error detail providing
- information about when the client should retry the request; clients SHOULD
- respect the information provided.
- """
-
- def FindMissingBlobs(self, request, context):
- """Determine if blobs are present in the CAS.
-
- Clients can use this API before uploading blobs to determine which ones are
- already present in the CAS and do not need to be uploaded again.
-
- There are no method-specific errors.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def BatchUpdateBlobs(self, request, context):
- """Upload many blobs at once.
-
- The server may enforce a limit of the combined total size of blobs
- to be uploaded using this API. This limit may be obtained using the
- [Capabilities][build.bazel.remote.execution.v2.Capabilities] API.
- Requests exceeding the limit should either be split into smaller
- chunks or uploaded using the
- [ByteStream API][google.bytestream.ByteStream], as appropriate.
-
- This request is equivalent to calling a Bytestream `Write` request
- on each individual blob, in parallel. The requests may succeed or fail
- independently.
-
- Errors:
- * `INVALID_ARGUMENT`: The client attempted to upload more than the
- server supported limit.
-
- Individual requests may return the following errors, additionally:
- * `RESOURCE_EXHAUSTED`: There is insufficient disk quota to store the blob.
- * `INVALID_ARGUMENT`: The
- [Digest][build.bazel.remote.execution.v2.Digest] does not match the
- provided data.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def BatchReadBlobs(self, request, context):
- """Download many blobs at once.
-
- The server may enforce a limit of the combined total size of blobs
- to be downloaded using this API. This limit may be obtained using the
- [Capabilities][build.bazel.remote.execution.v2.Capabilities] API.
- Requests exceeding the limit should either be split into smaller
- chunks or downloaded using the
- [ByteStream API][google.bytestream.ByteStream], as appropriate.
-
- This request is equivalent to calling a Bytestream `Read` request
- on each individual blob, in parallel. The requests may succeed or fail
- independently.
-
- Errors:
- * `INVALID_ARGUMENT`: The client attempted to read more than the
- server supported limit.
-
- Every error on individual read will be returned in the corresponding digest
- status.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def GetTree(self, request, context):
- """Fetch the entire directory tree rooted at a node.
-
- This request must be targeted at a
- [Directory][build.bazel.remote.execution.v2.Directory] stored in the
- [ContentAddressableStorage][build.bazel.remote.execution.v2.ContentAddressableStorage]
- (CAS). The server will enumerate the `Directory` tree recursively and
- return every node descended from the root.
-
- The GetTreeRequest.page_token parameter can be used to skip ahead in
- the stream (e.g. when retrying a partially completed and aborted request),
- by setting it to a value taken from GetTreeResponse.next_page_token of the
- last successfully processed GetTreeResponse).
-
- The exact traversal order is unspecified and, unless retrieving subsequent
- pages from an earlier request, is not guaranteed to be stable across
- multiple invocations of `GetTree`.
-
- If part of the tree is missing from the CAS, the server will return the
- portion present and omit the rest.
-
- * `NOT_FOUND`: The requested tree root is not present in the CAS.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_ContentAddressableStorageServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'FindMissingBlobs': grpc.unary_unary_rpc_method_handler(
- servicer.FindMissingBlobs,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsRequest.FromString,
- response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.FindMissingBlobsResponse.SerializeToString,
- ),
- 'BatchUpdateBlobs': grpc.unary_unary_rpc_method_handler(
- servicer.BatchUpdateBlobs,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsRequest.FromString,
- response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchUpdateBlobsResponse.SerializeToString,
- ),
- 'BatchReadBlobs': grpc.unary_unary_rpc_method_handler(
- servicer.BatchReadBlobs,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsRequest.FromString,
- response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.BatchReadBlobsResponse.SerializeToString,
- ),
- 'GetTree': grpc.unary_stream_rpc_method_handler(
- servicer.GetTree,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeRequest.FromString,
- response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetTreeResponse.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'build.bazel.remote.execution.v2.ContentAddressableStorage', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
-
-
-class CapabilitiesStub(object):
- """The Capabilities service may be used by remote execution clients to query
- various server properties, in order to self-configure or return meaningful
- error messages.
-
- The query may include a particular `instance_name`, in which case the values
- returned will pertain to that instance.
- """
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.GetCapabilities = channel.unary_unary(
- '/build.bazel.remote.execution.v2.Capabilities/GetCapabilities',
- request_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetCapabilitiesRequest.SerializeToString,
- response_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ServerCapabilities.FromString,
- )
-
-
-class CapabilitiesServicer(object):
- """The Capabilities service may be used by remote execution clients to query
- various server properties, in order to self-configure or return meaningful
- error messages.
-
- The query may include a particular `instance_name`, in which case the values
- returned will pertain to that instance.
- """
-
- def GetCapabilities(self, request, context):
- """GetCapabilities returns the server capabilities configuration.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_CapabilitiesServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'GetCapabilities': grpc.unary_unary_rpc_method_handler(
- servicer.GetCapabilities,
- request_deserializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.GetCapabilitiesRequest.FromString,
- response_serializer=build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.ServerCapabilities.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'build.bazel.remote.execution.v2.Capabilities', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
diff --git a/buildstream/_protos/build/bazel/semver/__init__.py b/buildstream/_protos/build/bazel/semver/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/build/bazel/semver/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/build/bazel/semver/semver.proto b/buildstream/_protos/build/bazel/semver/semver.proto
deleted file mode 100644
index 2caf76bcc..000000000
--- a/buildstream/_protos/build/bazel/semver/semver.proto
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2018 The Bazel Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package build.bazel.semver;
-
-message SemVer {
- int32 major = 1;
- int32 minor = 2;
- int32 patch = 3;
- string prerelease = 4;
-}
diff --git a/buildstream/_protos/build/bazel/semver/semver_pb2.py b/buildstream/_protos/build/bazel/semver/semver_pb2.py
deleted file mode 100644
index a36cf722a..000000000
--- a/buildstream/_protos/build/bazel/semver/semver_pb2.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: build/bazel/semver/semver.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='build/bazel/semver/semver.proto',
- package='build.bazel.semver',
- syntax='proto3',
- serialized_pb=_b('\n\x1f\x62uild/bazel/semver/semver.proto\x12\x12\x62uild.bazel.semver\"I\n\x06SemVer\x12\r\n\x05major\x18\x01 \x01(\x05\x12\r\n\x05minor\x18\x02 \x01(\x05\x12\r\n\x05patch\x18\x03 \x01(\x05\x12\x12\n\nprerelease\x18\x04 \x01(\tb\x06proto3')
-)
-
-
-
-
-_SEMVER = _descriptor.Descriptor(
- name='SemVer',
- full_name='build.bazel.semver.SemVer',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='major', full_name='build.bazel.semver.SemVer.major', index=0,
- number=1, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='minor', full_name='build.bazel.semver.SemVer.minor', index=1,
- number=2, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='patch', full_name='build.bazel.semver.SemVer.patch', index=2,
- number=3, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='prerelease', full_name='build.bazel.semver.SemVer.prerelease', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=55,
- serialized_end=128,
-)
-
-DESCRIPTOR.message_types_by_name['SemVer'] = _SEMVER
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-SemVer = _reflection.GeneratedProtocolMessageType('SemVer', (_message.Message,), dict(
- DESCRIPTOR = _SEMVER,
- __module__ = 'build.bazel.semver.semver_pb2'
- # @@protoc_insertion_point(class_scope:build.bazel.semver.SemVer)
- ))
-_sym_db.RegisterMessage(SemVer)
-
-
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/build/bazel/semver/semver_pb2_grpc.py b/buildstream/_protos/build/bazel/semver/semver_pb2_grpc.py
deleted file mode 100644
index a89435267..000000000
--- a/buildstream/_protos/build/bazel/semver/semver_pb2_grpc.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
diff --git a/buildstream/_protos/buildstream/__init__.py b/buildstream/_protos/buildstream/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/buildstream/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/buildstream/v2/__init__.py b/buildstream/_protos/buildstream/v2/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/buildstream/v2/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/buildstream/v2/artifact.proto b/buildstream/_protos/buildstream/v2/artifact.proto
deleted file mode 100644
index 56ddbca6b..000000000
--- a/buildstream/_protos/buildstream/v2/artifact.proto
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2019 Bloomberg Finance LP
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// Authors
-// Raoul Hidalgo Charman <raoul.hidalgo.charman@gmail.com>
-
-syntax = "proto3";
-
-package buildstream.v2;
-
-import "build/bazel/remote/execution/v2/remote_execution.proto";
-import "google/api/annotations.proto";
-
-service ArtifactService {
- // Retrieves an Artifact message
- //
- // Errors:
- // * `NOT_FOUND`: Artifact not found on server
- rpc GetArtifact(GetArtifactRequest) returns (Artifact) {}
-
- // Sets an Artifact message
- //
- // Errors:
- // * `FAILED_PRECONDITION`: Files specified in upload aren't present in CAS
- rpc UpdateArtifact(UpdateArtifactRequest) returns (Artifact) {}
-}
-
-message Artifact {
- // This version number must always be present and can be used to
- // further indicate presence or absence of parts of the proto at a
- // later date. It only needs incrementing if a change to what is
- // *mandatory* changes.
- int32 version = 1;
- // Core metadata
- bool build_success = 2;
- string build_error = 3; // optional
- string build_error_details = 4;
- string strong_key = 5;
- string weak_key = 6;
- bool was_workspaced = 7;
- // digest of a Directory
- build.bazel.remote.execution.v2.Digest files = 8;
-
- // Information about the build dependencies
- message Dependency {
- string element_name = 1;
- string cache_key = 2;
- bool was_workspaced = 3;
- };
- repeated Dependency build_deps = 9;
-
- // The public data is a yaml file which is stored into the CAS
- // Digest is of a directory
- build.bazel.remote.execution.v2.Digest public_data = 10;
-
- // The logs are stored in the CAS
- message LogFile {
- string name = 1;
- // digest of a file
- build.bazel.remote.execution.v2.Digest digest = 2;
- };
- repeated LogFile logs = 11; // Zero or more log files here
-
- // digest of a directory
- build.bazel.remote.execution.v2.Digest buildtree = 12; // optional
-}
-
-message GetArtifactRequest {
- string instance_name = 1;
- string cache_key = 2;
-}
-
-message UpdateArtifactRequest {
- string instance_name = 1;
- string cache_key = 2;
- Artifact artifact = 3;
-}
diff --git a/buildstream/_protos/buildstream/v2/artifact_pb2.py b/buildstream/_protos/buildstream/v2/artifact_pb2.py
deleted file mode 100644
index c56d1ae8a..000000000
--- a/buildstream/_protos/buildstream/v2/artifact_pb2.py
+++ /dev/null
@@ -1,387 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: buildstream/v2/artifact.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-from buildstream._protos.build.bazel.remote.execution.v2 import remote_execution_pb2 as build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2
-from buildstream._protos.google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='buildstream/v2/artifact.proto',
- package='buildstream.v2',
- syntax='proto3',
- serialized_options=None,
- serialized_pb=_b('\n\x1d\x62uildstream/v2/artifact.proto\x12\x0e\x62uildstream.v2\x1a\x36\x62uild/bazel/remote/execution/v2/remote_execution.proto\x1a\x1cgoogle/api/annotations.proto\"\xde\x04\n\x08\x41rtifact\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x15\n\rbuild_success\x18\x02 \x01(\x08\x12\x13\n\x0b\x62uild_error\x18\x03 \x01(\t\x12\x1b\n\x13\x62uild_error_details\x18\x04 \x01(\t\x12\x12\n\nstrong_key\x18\x05 \x01(\t\x12\x10\n\x08weak_key\x18\x06 \x01(\t\x12\x16\n\x0ewas_workspaced\x18\x07 \x01(\x08\x12\x36\n\x05\x66iles\x18\x08 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12\x37\n\nbuild_deps\x18\t \x03(\x0b\x32#.buildstream.v2.Artifact.Dependency\x12<\n\x0bpublic_data\x18\n \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x12.\n\x04logs\x18\x0b \x03(\x0b\x32 .buildstream.v2.Artifact.LogFile\x12:\n\tbuildtree\x18\x0c \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\x1aM\n\nDependency\x12\x14\n\x0c\x65lement_name\x18\x01 \x01(\t\x12\x11\n\tcache_key\x18\x02 \x01(\t\x12\x16\n\x0ewas_workspaced\x18\x03 \x01(\x08\x1aP\n\x07LogFile\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x06\x64igest\x18\x02 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\">\n\x12GetArtifactRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x11\n\tcache_key\x18\x02 \x01(\t\"m\n\x15UpdateArtifactRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x11\n\tcache_key\x18\x02 \x01(\t\x12*\n\x08\x61rtifact\x18\x03 \x01(\x0b\x32\x18.buildstream.v2.Artifact2\xb5\x01\n\x0f\x41rtifactService\x12M\n\x0bGetArtifact\x12\".buildstream.v2.GetArtifactRequest\x1a\x18.buildstream.v2.Artifact\"\x00\x12S\n\x0eUpdateArtifact\x12%.buildstream.v2.UpdateArtifactRequest\x1a\x18.buildstream.v2.Artifact\"\x00\x62\x06proto3')
- ,
- dependencies=[build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.DESCRIPTOR,google_dot_api_dot_annotations__pb2.DESCRIPTOR,])
-
-
-
-
-_ARTIFACT_DEPENDENCY = _descriptor.Descriptor(
- name='Dependency',
- full_name='buildstream.v2.Artifact.Dependency',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='element_name', full_name='buildstream.v2.Artifact.Dependency.element_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='cache_key', full_name='buildstream.v2.Artifact.Dependency.cache_key', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='was_workspaced', full_name='buildstream.v2.Artifact.Dependency.was_workspaced', index=2,
- number=3, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=583,
- serialized_end=660,
-)
-
-_ARTIFACT_LOGFILE = _descriptor.Descriptor(
- name='LogFile',
- full_name='buildstream.v2.Artifact.LogFile',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='buildstream.v2.Artifact.LogFile.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='digest', full_name='buildstream.v2.Artifact.LogFile.digest', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=662,
- serialized_end=742,
-)
-
-_ARTIFACT = _descriptor.Descriptor(
- name='Artifact',
- full_name='buildstream.v2.Artifact',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='version', full_name='buildstream.v2.Artifact.version', index=0,
- number=1, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='build_success', full_name='buildstream.v2.Artifact.build_success', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='build_error', full_name='buildstream.v2.Artifact.build_error', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='build_error_details', full_name='buildstream.v2.Artifact.build_error_details', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='strong_key', full_name='buildstream.v2.Artifact.strong_key', index=4,
- number=5, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='weak_key', full_name='buildstream.v2.Artifact.weak_key', index=5,
- number=6, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='was_workspaced', full_name='buildstream.v2.Artifact.was_workspaced', index=6,
- number=7, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='files', full_name='buildstream.v2.Artifact.files', index=7,
- number=8, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='build_deps', full_name='buildstream.v2.Artifact.build_deps', index=8,
- number=9, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='public_data', full_name='buildstream.v2.Artifact.public_data', index=9,
- number=10, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='logs', full_name='buildstream.v2.Artifact.logs', index=10,
- number=11, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='buildtree', full_name='buildstream.v2.Artifact.buildtree', index=11,
- number=12, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_ARTIFACT_DEPENDENCY, _ARTIFACT_LOGFILE, ],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=136,
- serialized_end=742,
-)
-
-
-_GETARTIFACTREQUEST = _descriptor.Descriptor(
- name='GetArtifactRequest',
- full_name='buildstream.v2.GetArtifactRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='buildstream.v2.GetArtifactRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='cache_key', full_name='buildstream.v2.GetArtifactRequest.cache_key', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=744,
- serialized_end=806,
-)
-
-
-_UPDATEARTIFACTREQUEST = _descriptor.Descriptor(
- name='UpdateArtifactRequest',
- full_name='buildstream.v2.UpdateArtifactRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='buildstream.v2.UpdateArtifactRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='cache_key', full_name='buildstream.v2.UpdateArtifactRequest.cache_key', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='artifact', full_name='buildstream.v2.UpdateArtifactRequest.artifact', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=808,
- serialized_end=917,
-)
-
-_ARTIFACT_DEPENDENCY.containing_type = _ARTIFACT
-_ARTIFACT_LOGFILE.fields_by_name['digest'].message_type = build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2._DIGEST
-_ARTIFACT_LOGFILE.containing_type = _ARTIFACT
-_ARTIFACT.fields_by_name['files'].message_type = build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2._DIGEST
-_ARTIFACT.fields_by_name['build_deps'].message_type = _ARTIFACT_DEPENDENCY
-_ARTIFACT.fields_by_name['public_data'].message_type = build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2._DIGEST
-_ARTIFACT.fields_by_name['logs'].message_type = _ARTIFACT_LOGFILE
-_ARTIFACT.fields_by_name['buildtree'].message_type = build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2._DIGEST
-_UPDATEARTIFACTREQUEST.fields_by_name['artifact'].message_type = _ARTIFACT
-DESCRIPTOR.message_types_by_name['Artifact'] = _ARTIFACT
-DESCRIPTOR.message_types_by_name['GetArtifactRequest'] = _GETARTIFACTREQUEST
-DESCRIPTOR.message_types_by_name['UpdateArtifactRequest'] = _UPDATEARTIFACTREQUEST
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-Artifact = _reflection.GeneratedProtocolMessageType('Artifact', (_message.Message,), dict(
-
- Dependency = _reflection.GeneratedProtocolMessageType('Dependency', (_message.Message,), dict(
- DESCRIPTOR = _ARTIFACT_DEPENDENCY,
- __module__ = 'buildstream.v2.artifact_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.Artifact.Dependency)
- ))
- ,
-
- LogFile = _reflection.GeneratedProtocolMessageType('LogFile', (_message.Message,), dict(
- DESCRIPTOR = _ARTIFACT_LOGFILE,
- __module__ = 'buildstream.v2.artifact_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.Artifact.LogFile)
- ))
- ,
- DESCRIPTOR = _ARTIFACT,
- __module__ = 'buildstream.v2.artifact_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.Artifact)
- ))
-_sym_db.RegisterMessage(Artifact)
-_sym_db.RegisterMessage(Artifact.Dependency)
-_sym_db.RegisterMessage(Artifact.LogFile)
-
-GetArtifactRequest = _reflection.GeneratedProtocolMessageType('GetArtifactRequest', (_message.Message,), dict(
- DESCRIPTOR = _GETARTIFACTREQUEST,
- __module__ = 'buildstream.v2.artifact_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.GetArtifactRequest)
- ))
-_sym_db.RegisterMessage(GetArtifactRequest)
-
-UpdateArtifactRequest = _reflection.GeneratedProtocolMessageType('UpdateArtifactRequest', (_message.Message,), dict(
- DESCRIPTOR = _UPDATEARTIFACTREQUEST,
- __module__ = 'buildstream.v2.artifact_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.UpdateArtifactRequest)
- ))
-_sym_db.RegisterMessage(UpdateArtifactRequest)
-
-
-
-_ARTIFACTSERVICE = _descriptor.ServiceDescriptor(
- name='ArtifactService',
- full_name='buildstream.v2.ArtifactService',
- file=DESCRIPTOR,
- index=0,
- serialized_options=None,
- serialized_start=920,
- serialized_end=1101,
- methods=[
- _descriptor.MethodDescriptor(
- name='GetArtifact',
- full_name='buildstream.v2.ArtifactService.GetArtifact',
- index=0,
- containing_service=None,
- input_type=_GETARTIFACTREQUEST,
- output_type=_ARTIFACT,
- serialized_options=None,
- ),
- _descriptor.MethodDescriptor(
- name='UpdateArtifact',
- full_name='buildstream.v2.ArtifactService.UpdateArtifact',
- index=1,
- containing_service=None,
- input_type=_UPDATEARTIFACTREQUEST,
- output_type=_ARTIFACT,
- serialized_options=None,
- ),
-])
-_sym_db.RegisterServiceDescriptor(_ARTIFACTSERVICE)
-
-DESCRIPTOR.services_by_name['ArtifactService'] = _ARTIFACTSERVICE
-
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/buildstream/v2/artifact_pb2_grpc.py b/buildstream/_protos/buildstream/v2/artifact_pb2_grpc.py
deleted file mode 100644
index d355146af..000000000
--- a/buildstream/_protos/buildstream/v2/artifact_pb2_grpc.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
-from buildstream._protos.buildstream.v2 import artifact_pb2 as buildstream_dot_v2_dot_artifact__pb2
-
-
-class ArtifactServiceStub(object):
- # missing associated documentation comment in .proto file
- pass
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.GetArtifact = channel.unary_unary(
- '/buildstream.v2.ArtifactService/GetArtifact',
- request_serializer=buildstream_dot_v2_dot_artifact__pb2.GetArtifactRequest.SerializeToString,
- response_deserializer=buildstream_dot_v2_dot_artifact__pb2.Artifact.FromString,
- )
- self.UpdateArtifact = channel.unary_unary(
- '/buildstream.v2.ArtifactService/UpdateArtifact',
- request_serializer=buildstream_dot_v2_dot_artifact__pb2.UpdateArtifactRequest.SerializeToString,
- response_deserializer=buildstream_dot_v2_dot_artifact__pb2.Artifact.FromString,
- )
-
-
-class ArtifactServiceServicer(object):
- # missing associated documentation comment in .proto file
- pass
-
- def GetArtifact(self, request, context):
- """Retrieves an Artifact message
-
- Errors:
- * `NOT_FOUND`: Artifact not found on server
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def UpdateArtifact(self, request, context):
- """Sets an Artifact message
-
- Errors:
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_ArtifactServiceServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'GetArtifact': grpc.unary_unary_rpc_method_handler(
- servicer.GetArtifact,
- request_deserializer=buildstream_dot_v2_dot_artifact__pb2.GetArtifactRequest.FromString,
- response_serializer=buildstream_dot_v2_dot_artifact__pb2.Artifact.SerializeToString,
- ),
- 'UpdateArtifact': grpc.unary_unary_rpc_method_handler(
- servicer.UpdateArtifact,
- request_deserializer=buildstream_dot_v2_dot_artifact__pb2.UpdateArtifactRequest.FromString,
- response_serializer=buildstream_dot_v2_dot_artifact__pb2.Artifact.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'buildstream.v2.ArtifactService', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
diff --git a/buildstream/_protos/buildstream/v2/buildstream.proto b/buildstream/_protos/buildstream/v2/buildstream.proto
deleted file mode 100644
index f283d6f3f..000000000
--- a/buildstream/_protos/buildstream/v2/buildstream.proto
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2018 Codethink Limited
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package buildstream.v2;
-
-import "build/bazel/remote/execution/v2/remote_execution.proto";
-import "google/api/annotations.proto";
-
-service ReferenceStorage {
- // Retrieve a CAS [Directory][build.bazel.remote.execution.v2.Directory]
- // digest by name.
- //
- // Errors:
- // * `NOT_FOUND`: The requested reference is not in the cache.
- rpc GetReference(GetReferenceRequest) returns (GetReferenceResponse) {
- option (google.api.http) = { get: "/v2/{instance_name=**}/buildstream/refs/{key}" };
- }
-
- // Associate a name with a CAS [Directory][build.bazel.remote.execution.v2.Directory]
- // digest.
- //
- // Errors:
- // * `RESOURCE_EXHAUSTED`: There is insufficient storage space to add the
- // entry to the cache.
- rpc UpdateReference(UpdateReferenceRequest) returns (UpdateReferenceResponse) {
- option (google.api.http) = { put: "/v2/{instance_name=**}/buildstream/refs/{key}" body: "digest" };
- }
-
- rpc Status(StatusRequest) returns (StatusResponse) {
- option (google.api.http) = { put: "/v2/{instance_name=**}/buildstream/refs:status" };
- }
-}
-
-message GetReferenceRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // The name of the reference.
- string key = 2;
-}
-
-message GetReferenceResponse {
- // The digest of the CAS [Directory][build.bazel.remote.execution.v2.Directory].
- build.bazel.remote.execution.v2.Digest digest = 1;
-}
-
-message UpdateReferenceRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-
- // The name of the reference.
- repeated string keys = 2;
-
- // The digest of the CAS [Directory][build.bazel.remote.execution.v2.Directory]
- // to store in the cache.
- build.bazel.remote.execution.v2.Digest digest = 3;
-}
-
-message UpdateReferenceResponse {
-}
-
-message StatusRequest {
- // The instance of the execution system to operate against. A server may
- // support multiple instances of the execution system (with their own workers,
- // storage, caches, etc.). The server MAY require use of this field to select
- // between them in an implementation-defined fashion, otherwise it can be
- // omitted.
- string instance_name = 1;
-}
-
-message StatusResponse {
- // Whether reference updates are allowed for the connected client.
- bool allow_updates = 1;
-}
diff --git a/buildstream/_protos/buildstream/v2/buildstream_pb2.py b/buildstream/_protos/buildstream/v2/buildstream_pb2.py
deleted file mode 100644
index 57fdae49d..000000000
--- a/buildstream/_protos/buildstream/v2/buildstream_pb2.py
+++ /dev/null
@@ -1,325 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: buildstream/v2/buildstream.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-from buildstream._protos.build.bazel.remote.execution.v2 import remote_execution_pb2 as build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2
-from buildstream._protos.google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='buildstream/v2/buildstream.proto',
- package='buildstream.v2',
- syntax='proto3',
- serialized_pb=_b('\n buildstream/v2/buildstream.proto\x12\x0e\x62uildstream.v2\x1a\x36\x62uild/bazel/remote/execution/v2/remote_execution.proto\x1a\x1cgoogle/api/annotations.proto\"9\n\x13GetReferenceRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\"O\n\x14GetReferenceResponse\x12\x37\n\x06\x64igest\x18\x01 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"v\n\x16UpdateReferenceRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\x12\x0c\n\x04keys\x18\x02 \x03(\t\x12\x37\n\x06\x64igest\x18\x03 \x01(\x0b\x32\'.build.bazel.remote.execution.v2.Digest\"\x19\n\x17UpdateReferenceResponse\"&\n\rStatusRequest\x12\x15\n\rinstance_name\x18\x01 \x01(\t\"\'\n\x0eStatusResponse\x12\x15\n\rallow_updates\x18\x01 \x01(\x08\x32\xca\x03\n\x10ReferenceStorage\x12\x90\x01\n\x0cGetReference\x12#.buildstream.v2.GetReferenceRequest\x1a$.buildstream.v2.GetReferenceResponse\"5\x82\xd3\xe4\x93\x02/\x12-/v2/{instance_name=**}/buildstream/refs/{key}\x12\xa1\x01\n\x0fUpdateReference\x12&.buildstream.v2.UpdateReferenceRequest\x1a\'.buildstream.v2.UpdateReferenceResponse\"=\x82\xd3\xe4\x93\x02\x37\x1a-/v2/{instance_name=**}/buildstream/refs/{key}:\x06\x64igest\x12\x7f\n\x06Status\x12\x1d.buildstream.v2.StatusRequest\x1a\x1e.buildstream.v2.StatusResponse\"6\x82\xd3\xe4\x93\x02\x30\x1a./v2/{instance_name=**}/buildstream/refs:statusb\x06proto3')
- ,
- dependencies=[build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2.DESCRIPTOR,google_dot_api_dot_annotations__pb2.DESCRIPTOR,])
-
-
-
-
-_GETREFERENCEREQUEST = _descriptor.Descriptor(
- name='GetReferenceRequest',
- full_name='buildstream.v2.GetReferenceRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='buildstream.v2.GetReferenceRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='buildstream.v2.GetReferenceRequest.key', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=138,
- serialized_end=195,
-)
-
-
-_GETREFERENCERESPONSE = _descriptor.Descriptor(
- name='GetReferenceResponse',
- full_name='buildstream.v2.GetReferenceResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='digest', full_name='buildstream.v2.GetReferenceResponse.digest', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=197,
- serialized_end=276,
-)
-
-
-_UPDATEREFERENCEREQUEST = _descriptor.Descriptor(
- name='UpdateReferenceRequest',
- full_name='buildstream.v2.UpdateReferenceRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='buildstream.v2.UpdateReferenceRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='keys', full_name='buildstream.v2.UpdateReferenceRequest.keys', index=1,
- number=2, type=9, cpp_type=9, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='digest', full_name='buildstream.v2.UpdateReferenceRequest.digest', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=278,
- serialized_end=396,
-)
-
-
-_UPDATEREFERENCERESPONSE = _descriptor.Descriptor(
- name='UpdateReferenceResponse',
- full_name='buildstream.v2.UpdateReferenceResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=398,
- serialized_end=423,
-)
-
-
-_STATUSREQUEST = _descriptor.Descriptor(
- name='StatusRequest',
- full_name='buildstream.v2.StatusRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='instance_name', full_name='buildstream.v2.StatusRequest.instance_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=425,
- serialized_end=463,
-)
-
-
-_STATUSRESPONSE = _descriptor.Descriptor(
- name='StatusResponse',
- full_name='buildstream.v2.StatusResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='allow_updates', full_name='buildstream.v2.StatusResponse.allow_updates', index=0,
- number=1, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=465,
- serialized_end=504,
-)
-
-_GETREFERENCERESPONSE.fields_by_name['digest'].message_type = build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2._DIGEST
-_UPDATEREFERENCEREQUEST.fields_by_name['digest'].message_type = build_dot_bazel_dot_remote_dot_execution_dot_v2_dot_remote__execution__pb2._DIGEST
-DESCRIPTOR.message_types_by_name['GetReferenceRequest'] = _GETREFERENCEREQUEST
-DESCRIPTOR.message_types_by_name['GetReferenceResponse'] = _GETREFERENCERESPONSE
-DESCRIPTOR.message_types_by_name['UpdateReferenceRequest'] = _UPDATEREFERENCEREQUEST
-DESCRIPTOR.message_types_by_name['UpdateReferenceResponse'] = _UPDATEREFERENCERESPONSE
-DESCRIPTOR.message_types_by_name['StatusRequest'] = _STATUSREQUEST
-DESCRIPTOR.message_types_by_name['StatusResponse'] = _STATUSRESPONSE
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-GetReferenceRequest = _reflection.GeneratedProtocolMessageType('GetReferenceRequest', (_message.Message,), dict(
- DESCRIPTOR = _GETREFERENCEREQUEST,
- __module__ = 'buildstream.v2.buildstream_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.GetReferenceRequest)
- ))
-_sym_db.RegisterMessage(GetReferenceRequest)
-
-GetReferenceResponse = _reflection.GeneratedProtocolMessageType('GetReferenceResponse', (_message.Message,), dict(
- DESCRIPTOR = _GETREFERENCERESPONSE,
- __module__ = 'buildstream.v2.buildstream_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.GetReferenceResponse)
- ))
-_sym_db.RegisterMessage(GetReferenceResponse)
-
-UpdateReferenceRequest = _reflection.GeneratedProtocolMessageType('UpdateReferenceRequest', (_message.Message,), dict(
- DESCRIPTOR = _UPDATEREFERENCEREQUEST,
- __module__ = 'buildstream.v2.buildstream_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.UpdateReferenceRequest)
- ))
-_sym_db.RegisterMessage(UpdateReferenceRequest)
-
-UpdateReferenceResponse = _reflection.GeneratedProtocolMessageType('UpdateReferenceResponse', (_message.Message,), dict(
- DESCRIPTOR = _UPDATEREFERENCERESPONSE,
- __module__ = 'buildstream.v2.buildstream_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.UpdateReferenceResponse)
- ))
-_sym_db.RegisterMessage(UpdateReferenceResponse)
-
-StatusRequest = _reflection.GeneratedProtocolMessageType('StatusRequest', (_message.Message,), dict(
- DESCRIPTOR = _STATUSREQUEST,
- __module__ = 'buildstream.v2.buildstream_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.StatusRequest)
- ))
-_sym_db.RegisterMessage(StatusRequest)
-
-StatusResponse = _reflection.GeneratedProtocolMessageType('StatusResponse', (_message.Message,), dict(
- DESCRIPTOR = _STATUSRESPONSE,
- __module__ = 'buildstream.v2.buildstream_pb2'
- # @@protoc_insertion_point(class_scope:buildstream.v2.StatusResponse)
- ))
-_sym_db.RegisterMessage(StatusResponse)
-
-
-
-_REFERENCESTORAGE = _descriptor.ServiceDescriptor(
- name='ReferenceStorage',
- full_name='buildstream.v2.ReferenceStorage',
- file=DESCRIPTOR,
- index=0,
- options=None,
- serialized_start=507,
- serialized_end=965,
- methods=[
- _descriptor.MethodDescriptor(
- name='GetReference',
- full_name='buildstream.v2.ReferenceStorage.GetReference',
- index=0,
- containing_service=None,
- input_type=_GETREFERENCEREQUEST,
- output_type=_GETREFERENCERESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002/\022-/v2/{instance_name=**}/buildstream/refs/{key}')),
- ),
- _descriptor.MethodDescriptor(
- name='UpdateReference',
- full_name='buildstream.v2.ReferenceStorage.UpdateReference',
- index=1,
- containing_service=None,
- input_type=_UPDATEREFERENCEREQUEST,
- output_type=_UPDATEREFERENCERESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\0027\032-/v2/{instance_name=**}/buildstream/refs/{key}:\006digest')),
- ),
- _descriptor.MethodDescriptor(
- name='Status',
- full_name='buildstream.v2.ReferenceStorage.Status',
- index=2,
- containing_service=None,
- input_type=_STATUSREQUEST,
- output_type=_STATUSRESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\0020\032./v2/{instance_name=**}/buildstream/refs:status')),
- ),
-])
-_sym_db.RegisterServiceDescriptor(_REFERENCESTORAGE)
-
-DESCRIPTOR.services_by_name['ReferenceStorage'] = _REFERENCESTORAGE
-
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/buildstream/v2/buildstream_pb2_grpc.py b/buildstream/_protos/buildstream/v2/buildstream_pb2_grpc.py
deleted file mode 100644
index b3e653493..000000000
--- a/buildstream/_protos/buildstream/v2/buildstream_pb2_grpc.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
-from buildstream._protos.buildstream.v2 import buildstream_pb2 as buildstream_dot_v2_dot_buildstream__pb2
-
-
-class ReferenceStorageStub(object):
- # missing associated documentation comment in .proto file
- pass
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.GetReference = channel.unary_unary(
- '/buildstream.v2.ReferenceStorage/GetReference',
- request_serializer=buildstream_dot_v2_dot_buildstream__pb2.GetReferenceRequest.SerializeToString,
- response_deserializer=buildstream_dot_v2_dot_buildstream__pb2.GetReferenceResponse.FromString,
- )
- self.UpdateReference = channel.unary_unary(
- '/buildstream.v2.ReferenceStorage/UpdateReference',
- request_serializer=buildstream_dot_v2_dot_buildstream__pb2.UpdateReferenceRequest.SerializeToString,
- response_deserializer=buildstream_dot_v2_dot_buildstream__pb2.UpdateReferenceResponse.FromString,
- )
- self.Status = channel.unary_unary(
- '/buildstream.v2.ReferenceStorage/Status',
- request_serializer=buildstream_dot_v2_dot_buildstream__pb2.StatusRequest.SerializeToString,
- response_deserializer=buildstream_dot_v2_dot_buildstream__pb2.StatusResponse.FromString,
- )
-
-
-class ReferenceStorageServicer(object):
- # missing associated documentation comment in .proto file
- pass
-
- def GetReference(self, request, context):
- """Retrieve a CAS [Directory][build.bazel.remote.execution.v2.Directory]
- digest by name.
-
- Errors:
- * `NOT_FOUND`: The requested reference is not in the cache.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def UpdateReference(self, request, context):
- """Associate a name with a CAS [Directory][build.bazel.remote.execution.v2.Directory]
- digest.
-
- Errors:
- * `RESOURCE_EXHAUSTED`: There is insufficient storage space to add the
- entry to the cache.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def Status(self, request, context):
- # missing associated documentation comment in .proto file
- pass
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_ReferenceStorageServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'GetReference': grpc.unary_unary_rpc_method_handler(
- servicer.GetReference,
- request_deserializer=buildstream_dot_v2_dot_buildstream__pb2.GetReferenceRequest.FromString,
- response_serializer=buildstream_dot_v2_dot_buildstream__pb2.GetReferenceResponse.SerializeToString,
- ),
- 'UpdateReference': grpc.unary_unary_rpc_method_handler(
- servicer.UpdateReference,
- request_deserializer=buildstream_dot_v2_dot_buildstream__pb2.UpdateReferenceRequest.FromString,
- response_serializer=buildstream_dot_v2_dot_buildstream__pb2.UpdateReferenceResponse.SerializeToString,
- ),
- 'Status': grpc.unary_unary_rpc_method_handler(
- servicer.Status,
- request_deserializer=buildstream_dot_v2_dot_buildstream__pb2.StatusRequest.FromString,
- response_serializer=buildstream_dot_v2_dot_buildstream__pb2.StatusResponse.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'buildstream.v2.ReferenceStorage', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
diff --git a/buildstream/_protos/google/__init__.py b/buildstream/_protos/google/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/google/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/google/api/__init__.py b/buildstream/_protos/google/api/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/google/api/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/google/api/annotations.proto b/buildstream/_protos/google/api/annotations.proto
deleted file mode 100644
index 85c361b47..000000000
--- a/buildstream/_protos/google/api/annotations.proto
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2015, Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package google.api;
-
-import "google/api/http.proto";
-import "google/protobuf/descriptor.proto";
-
-option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
-option java_multiple_files = true;
-option java_outer_classname = "AnnotationsProto";
-option java_package = "com.google.api";
-option objc_class_prefix = "GAPI";
-
-extend google.protobuf.MethodOptions {
- // See `HttpRule`.
- HttpRule http = 72295728;
-}
diff --git a/buildstream/_protos/google/api/annotations_pb2.py b/buildstream/_protos/google/api/annotations_pb2.py
deleted file mode 100644
index 092c46de7..000000000
--- a/buildstream/_protos/google/api/annotations_pb2.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: google/api/annotations.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-from buildstream._protos.google.api import http_pb2 as google_dot_api_dot_http__pb2
-from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='google/api/annotations.proto',
- package='google.api',
- syntax='proto3',
- serialized_pb=_b('\n\x1cgoogle/api/annotations.proto\x12\ngoogle.api\x1a\x15google/api/http.proto\x1a google/protobuf/descriptor.proto:E\n\x04http\x12\x1e.google.protobuf.MethodOptions\x18\xb0\xca\xbc\" \x01(\x0b\x32\x14.google.api.HttpRuleBn\n\x0e\x63om.google.apiB\x10\x41nnotationsProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3')
- ,
- dependencies=[google_dot_api_dot_http__pb2.DESCRIPTOR,google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR,])
-
-
-HTTP_FIELD_NUMBER = 72295728
-http = _descriptor.FieldDescriptor(
- name='http', full_name='google.api.http', index=0,
- number=72295728, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=True, extension_scope=None,
- options=None, file=DESCRIPTOR)
-
-DESCRIPTOR.extensions_by_name['http'] = http
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-http.message_type = google_dot_api_dot_http__pb2._HTTPRULE
-google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(http)
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\016com.google.apiB\020AnnotationsProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI'))
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/google/api/annotations_pb2_grpc.py b/buildstream/_protos/google/api/annotations_pb2_grpc.py
deleted file mode 100644
index a89435267..000000000
--- a/buildstream/_protos/google/api/annotations_pb2_grpc.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
diff --git a/buildstream/_protos/google/api/http.proto b/buildstream/_protos/google/api/http.proto
deleted file mode 100644
index 78d515d4b..000000000
--- a/buildstream/_protos/google/api/http.proto
+++ /dev/null
@@ -1,313 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package google.api;
-
-option cc_enable_arenas = true;
-option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
-option java_multiple_files = true;
-option java_outer_classname = "HttpProto";
-option java_package = "com.google.api";
-option objc_class_prefix = "GAPI";
-
-
-// Defines the HTTP configuration for an API service. It contains a list of
-// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method
-// to one or more HTTP REST API methods.
-message Http {
- // A list of HTTP configuration rules that apply to individual API methods.
- //
- // **NOTE:** All service configuration rules follow "last one wins" order.
- repeated HttpRule rules = 1;
-
- // When set to true, URL path parmeters will be fully URI-decoded except in
- // cases of single segment matches in reserved expansion, where "%2F" will be
- // left encoded.
- //
- // The default behavior is to not decode RFC 6570 reserved characters in multi
- // segment matches.
- bool fully_decode_reserved_expansion = 2;
-}
-
-// `HttpRule` defines the mapping of an RPC method to one or more HTTP
-// REST API methods. The mapping specifies how different portions of the RPC
-// request message are mapped to URL path, URL query parameters, and
-// HTTP request body. The mapping is typically specified as an
-// `google.api.http` annotation on the RPC method,
-// see "google/api/annotations.proto" for details.
-//
-// The mapping consists of a field specifying the path template and
-// method kind. The path template can refer to fields in the request
-// message, as in the example below which describes a REST GET
-// operation on a resource collection of messages:
-//
-//
-// service Messaging {
-// rpc GetMessage(GetMessageRequest) returns (Message) {
-// option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}";
-// }
-// }
-// message GetMessageRequest {
-// message SubMessage {
-// string subfield = 1;
-// }
-// string message_id = 1; // mapped to the URL
-// SubMessage sub = 2; // `sub.subfield` is url-mapped
-// }
-// message Message {
-// string text = 1; // content of the resource
-// }
-//
-// The same http annotation can alternatively be expressed inside the
-// `GRPC API Configuration` YAML file.
-//
-// http:
-// rules:
-// - selector: <proto_package_name>.Messaging.GetMessage
-// get: /v1/messages/{message_id}/{sub.subfield}
-//
-// This definition enables an automatic, bidrectional mapping of HTTP
-// JSON to RPC. Example:
-//
-// HTTP | RPC
-// -----|-----
-// `GET /v1/messages/123456/foo` | `GetMessage(message_id: "123456" sub: SubMessage(subfield: "foo"))`
-//
-// In general, not only fields but also field paths can be referenced
-// from a path pattern. Fields mapped to the path pattern cannot be
-// repeated and must have a primitive (non-message) type.
-//
-// Any fields in the request message which are not bound by the path
-// pattern automatically become (optional) HTTP query
-// parameters. Assume the following definition of the request message:
-//
-//
-// service Messaging {
-// rpc GetMessage(GetMessageRequest) returns (Message) {
-// option (google.api.http).get = "/v1/messages/{message_id}";
-// }
-// }
-// message GetMessageRequest {
-// message SubMessage {
-// string subfield = 1;
-// }
-// string message_id = 1; // mapped to the URL
-// int64 revision = 2; // becomes a parameter
-// SubMessage sub = 3; // `sub.subfield` becomes a parameter
-// }
-//
-//
-// This enables a HTTP JSON to RPC mapping as below:
-//
-// HTTP | RPC
-// -----|-----
-// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo"))`
-//
-// Note that fields which are mapped to HTTP parameters must have a
-// primitive type or a repeated primitive type. Message types are not
-// allowed. In the case of a repeated type, the parameter can be
-// repeated in the URL, as in `...?param=A&param=B`.
-//
-// For HTTP method kinds which allow a request body, the `body` field
-// specifies the mapping. Consider a REST update method on the
-// message resource collection:
-//
-//
-// service Messaging {
-// rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
-// option (google.api.http) = {
-// put: "/v1/messages/{message_id}"
-// body: "message"
-// };
-// }
-// }
-// message UpdateMessageRequest {
-// string message_id = 1; // mapped to the URL
-// Message message = 2; // mapped to the body
-// }
-//
-//
-// The following HTTP JSON to RPC mapping is enabled, where the
-// representation of the JSON in the request body is determined by
-// protos JSON encoding:
-//
-// HTTP | RPC
-// -----|-----
-// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" message { text: "Hi!" })`
-//
-// The special name `*` can be used in the body mapping to define that
-// every field not bound by the path template should be mapped to the
-// request body. This enables the following alternative definition of
-// the update method:
-//
-// service Messaging {
-// rpc UpdateMessage(Message) returns (Message) {
-// option (google.api.http) = {
-// put: "/v1/messages/{message_id}"
-// body: "*"
-// };
-// }
-// }
-// message Message {
-// string message_id = 1;
-// string text = 2;
-// }
-//
-//
-// The following HTTP JSON to RPC mapping is enabled:
-//
-// HTTP | RPC
-// -----|-----
-// `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" text: "Hi!")`
-//
-// Note that when using `*` in the body mapping, it is not possible to
-// have HTTP parameters, as all fields not bound by the path end in
-// the body. This makes this option more rarely used in practice of
-// defining REST APIs. The common usage of `*` is in custom methods
-// which don't use the URL at all for transferring data.
-//
-// It is possible to define multiple HTTP methods for one RPC by using
-// the `additional_bindings` option. Example:
-//
-// service Messaging {
-// rpc GetMessage(GetMessageRequest) returns (Message) {
-// option (google.api.http) = {
-// get: "/v1/messages/{message_id}"
-// additional_bindings {
-// get: "/v1/users/{user_id}/messages/{message_id}"
-// }
-// };
-// }
-// }
-// message GetMessageRequest {
-// string message_id = 1;
-// string user_id = 2;
-// }
-//
-//
-// This enables the following two alternative HTTP JSON to RPC
-// mappings:
-//
-// HTTP | RPC
-// -----|-----
-// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")`
-// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: "123456")`
-//
-// # Rules for HTTP mapping
-//
-// The rules for mapping HTTP path, query parameters, and body fields
-// to the request message are as follows:
-//
-// 1. The `body` field specifies either `*` or a field path, or is
-// omitted. If omitted, it indicates there is no HTTP request body.
-// 2. Leaf fields (recursive expansion of nested messages in the
-// request) can be classified into three types:
-// (a) Matched in the URL template.
-// (b) Covered by body (if body is `*`, everything except (a) fields;
-// else everything under the body field)
-// (c) All other fields.
-// 3. URL query parameters found in the HTTP request are mapped to (c) fields.
-// 4. Any body sent with an HTTP request can contain only (b) fields.
-//
-// The syntax of the path template is as follows:
-//
-// Template = "/" Segments [ Verb ] ;
-// Segments = Segment { "/" Segment } ;
-// Segment = "*" | "**" | LITERAL | Variable ;
-// Variable = "{" FieldPath [ "=" Segments ] "}" ;
-// FieldPath = IDENT { "." IDENT } ;
-// Verb = ":" LITERAL ;
-//
-// The syntax `*` matches a single path segment. The syntax `**` matches zero
-// or more path segments, which must be the last part of the path except the
-// `Verb`. The syntax `LITERAL` matches literal text in the path.
-//
-// The syntax `Variable` matches part of the URL path as specified by its
-// template. A variable template must not contain other variables. If a variable
-// matches a single path segment, its template may be omitted, e.g. `{var}`
-// is equivalent to `{var=*}`.
-//
-// If a variable contains exactly one path segment, such as `"{var}"` or
-// `"{var=*}"`, when such a variable is expanded into a URL path, all characters
-// except `[-_.~0-9a-zA-Z]` are percent-encoded. Such variables show up in the
-// Discovery Document as `{var}`.
-//
-// If a variable contains one or more path segments, such as `"{var=foo/*}"`
-// or `"{var=**}"`, when such a variable is expanded into a URL path, all
-// characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. Such variables
-// show up in the Discovery Document as `{+var}`.
-//
-// NOTE: While the single segment variable matches the semantics of
-// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2
-// Simple String Expansion, the multi segment variable **does not** match
-// RFC 6570 Reserved Expansion. The reason is that the Reserved Expansion
-// does not expand special characters like `?` and `#`, which would lead
-// to invalid URLs.
-//
-// NOTE: the field paths in variables and in the `body` must not refer to
-// repeated fields or map fields.
-message HttpRule {
- // Selects methods to which this rule applies.
- //
- // Refer to [selector][google.api.DocumentationRule.selector] for syntax details.
- string selector = 1;
-
- // Determines the URL pattern is matched by this rules. This pattern can be
- // used with any of the {get|put|post|delete|patch} methods. A custom method
- // can be defined using the 'custom' field.
- oneof pattern {
- // Used for listing and getting information about resources.
- string get = 2;
-
- // Used for updating a resource.
- string put = 3;
-
- // Used for creating a resource.
- string post = 4;
-
- // Used for deleting a resource.
- string delete = 5;
-
- // Used for updating a resource.
- string patch = 6;
-
- // The custom pattern is used for specifying an HTTP method that is not
- // included in the `pattern` field, such as HEAD, or "*" to leave the
- // HTTP method unspecified for this rule. The wild-card rule is useful
- // for services that provide content to Web (HTML) clients.
- CustomHttpPattern custom = 8;
- }
-
- // The name of the request field whose value is mapped to the HTTP body, or
- // `*` for mapping all fields not captured by the path pattern to the HTTP
- // body. NOTE: the referred field must not be a repeated field and must be
- // present at the top-level of request message type.
- string body = 7;
-
- // Additional HTTP bindings for the selector. Nested bindings must
- // not contain an `additional_bindings` field themselves (that is,
- // the nesting may only be one level deep).
- repeated HttpRule additional_bindings = 11;
-}
-
-// A custom pattern is used for defining custom HTTP verb.
-message CustomHttpPattern {
- // The name of this custom HTTP verb.
- string kind = 1;
-
- // The path matched by this custom verb.
- string path = 2;
-}
diff --git a/buildstream/_protos/google/api/http_pb2.py b/buildstream/_protos/google/api/http_pb2.py
deleted file mode 100644
index aad9ddb97..000000000
--- a/buildstream/_protos/google/api/http_pb2.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: google/api/http.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='google/api/http.proto',
- package='google.api',
- syntax='proto3',
- serialized_pb=_b('\n\x15google/api/http.proto\x12\ngoogle.api\"T\n\x04Http\x12#\n\x05rules\x18\x01 \x03(\x0b\x32\x14.google.api.HttpRule\x12\'\n\x1f\x66ully_decode_reserved_expansion\x18\x02 \x01(\x08\"\xea\x01\n\x08HttpRule\x12\x10\n\x08selector\x18\x01 \x01(\t\x12\r\n\x03get\x18\x02 \x01(\tH\x00\x12\r\n\x03put\x18\x03 \x01(\tH\x00\x12\x0e\n\x04post\x18\x04 \x01(\tH\x00\x12\x10\n\x06\x64\x65lete\x18\x05 \x01(\tH\x00\x12\x0f\n\x05patch\x18\x06 \x01(\tH\x00\x12/\n\x06\x63ustom\x18\x08 \x01(\x0b\x32\x1d.google.api.CustomHttpPatternH\x00\x12\x0c\n\x04\x62ody\x18\x07 \x01(\t\x12\x31\n\x13\x61\x64\x64itional_bindings\x18\x0b \x03(\x0b\x32\x14.google.api.HttpRuleB\t\n\x07pattern\"/\n\x11\x43ustomHttpPattern\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\tBj\n\x0e\x63om.google.apiB\tHttpProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xf8\x01\x01\xa2\x02\x04GAPIb\x06proto3')
-)
-
-
-
-
-_HTTP = _descriptor.Descriptor(
- name='Http',
- full_name='google.api.Http',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='rules', full_name='google.api.Http.rules', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='fully_decode_reserved_expansion', full_name='google.api.Http.fully_decode_reserved_expansion', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=37,
- serialized_end=121,
-)
-
-
-_HTTPRULE = _descriptor.Descriptor(
- name='HttpRule',
- full_name='google.api.HttpRule',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='selector', full_name='google.api.HttpRule.selector', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='get', full_name='google.api.HttpRule.get', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='put', full_name='google.api.HttpRule.put', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='post', full_name='google.api.HttpRule.post', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='delete', full_name='google.api.HttpRule.delete', index=4,
- number=5, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='patch', full_name='google.api.HttpRule.patch', index=5,
- number=6, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='custom', full_name='google.api.HttpRule.custom', index=6,
- number=8, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='body', full_name='google.api.HttpRule.body', index=7,
- number=7, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='additional_bindings', full_name='google.api.HttpRule.additional_bindings', index=8,
- number=11, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- _descriptor.OneofDescriptor(
- name='pattern', full_name='google.api.HttpRule.pattern',
- index=0, containing_type=None, fields=[]),
- ],
- serialized_start=124,
- serialized_end=358,
-)
-
-
-_CUSTOMHTTPPATTERN = _descriptor.Descriptor(
- name='CustomHttpPattern',
- full_name='google.api.CustomHttpPattern',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='kind', full_name='google.api.CustomHttpPattern.kind', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='path', full_name='google.api.CustomHttpPattern.path', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=360,
- serialized_end=407,
-)
-
-_HTTP.fields_by_name['rules'].message_type = _HTTPRULE
-_HTTPRULE.fields_by_name['custom'].message_type = _CUSTOMHTTPPATTERN
-_HTTPRULE.fields_by_name['additional_bindings'].message_type = _HTTPRULE
-_HTTPRULE.oneofs_by_name['pattern'].fields.append(
- _HTTPRULE.fields_by_name['get'])
-_HTTPRULE.fields_by_name['get'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
-_HTTPRULE.oneofs_by_name['pattern'].fields.append(
- _HTTPRULE.fields_by_name['put'])
-_HTTPRULE.fields_by_name['put'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
-_HTTPRULE.oneofs_by_name['pattern'].fields.append(
- _HTTPRULE.fields_by_name['post'])
-_HTTPRULE.fields_by_name['post'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
-_HTTPRULE.oneofs_by_name['pattern'].fields.append(
- _HTTPRULE.fields_by_name['delete'])
-_HTTPRULE.fields_by_name['delete'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
-_HTTPRULE.oneofs_by_name['pattern'].fields.append(
- _HTTPRULE.fields_by_name['patch'])
-_HTTPRULE.fields_by_name['patch'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
-_HTTPRULE.oneofs_by_name['pattern'].fields.append(
- _HTTPRULE.fields_by_name['custom'])
-_HTTPRULE.fields_by_name['custom'].containing_oneof = _HTTPRULE.oneofs_by_name['pattern']
-DESCRIPTOR.message_types_by_name['Http'] = _HTTP
-DESCRIPTOR.message_types_by_name['HttpRule'] = _HTTPRULE
-DESCRIPTOR.message_types_by_name['CustomHttpPattern'] = _CUSTOMHTTPPATTERN
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-Http = _reflection.GeneratedProtocolMessageType('Http', (_message.Message,), dict(
- DESCRIPTOR = _HTTP,
- __module__ = 'google.api.http_pb2'
- # @@protoc_insertion_point(class_scope:google.api.Http)
- ))
-_sym_db.RegisterMessage(Http)
-
-HttpRule = _reflection.GeneratedProtocolMessageType('HttpRule', (_message.Message,), dict(
- DESCRIPTOR = _HTTPRULE,
- __module__ = 'google.api.http_pb2'
- # @@protoc_insertion_point(class_scope:google.api.HttpRule)
- ))
-_sym_db.RegisterMessage(HttpRule)
-
-CustomHttpPattern = _reflection.GeneratedProtocolMessageType('CustomHttpPattern', (_message.Message,), dict(
- DESCRIPTOR = _CUSTOMHTTPPATTERN,
- __module__ = 'google.api.http_pb2'
- # @@protoc_insertion_point(class_scope:google.api.CustomHttpPattern)
- ))
-_sym_db.RegisterMessage(CustomHttpPattern)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\016com.google.apiB\tHttpProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\370\001\001\242\002\004GAPI'))
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/google/api/http_pb2_grpc.py b/buildstream/_protos/google/api/http_pb2_grpc.py
deleted file mode 100644
index a89435267..000000000
--- a/buildstream/_protos/google/api/http_pb2_grpc.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
diff --git a/buildstream/_protos/google/bytestream/__init__.py b/buildstream/_protos/google/bytestream/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/google/bytestream/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/google/bytestream/bytestream.proto b/buildstream/_protos/google/bytestream/bytestream.proto
deleted file mode 100644
index 85e386fc2..000000000
--- a/buildstream/_protos/google/bytestream/bytestream.proto
+++ /dev/null
@@ -1,181 +0,0 @@
-// Copyright 2016 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package google.bytestream;
-
-import "google/api/annotations.proto";
-import "google/protobuf/wrappers.proto";
-
-option go_package = "google.golang.org/genproto/googleapis/bytestream;bytestream";
-option java_outer_classname = "ByteStreamProto";
-option java_package = "com.google.bytestream";
-
-
-// #### Introduction
-//
-// The Byte Stream API enables a client to read and write a stream of bytes to
-// and from a resource. Resources have names, and these names are supplied in
-// the API calls below to identify the resource that is being read from or
-// written to.
-//
-// All implementations of the Byte Stream API export the interface defined here:
-//
-// * `Read()`: Reads the contents of a resource.
-//
-// * `Write()`: Writes the contents of a resource. The client can call `Write()`
-// multiple times with the same resource and can check the status of the write
-// by calling `QueryWriteStatus()`.
-//
-// #### Service parameters and metadata
-//
-// The ByteStream API provides no direct way to access/modify any metadata
-// associated with the resource.
-//
-// #### Errors
-//
-// The errors returned by the service are in the Google canonical error space.
-service ByteStream {
- // `Read()` is used to retrieve the contents of a resource as a sequence
- // of bytes. The bytes are returned in a sequence of responses, and the
- // responses are delivered as the results of a server-side streaming RPC.
- rpc Read(ReadRequest) returns (stream ReadResponse);
-
- // `Write()` is used to send the contents of a resource as a sequence of
- // bytes. The bytes are sent in a sequence of request protos of a client-side
- // streaming RPC.
- //
- // A `Write()` action is resumable. If there is an error or the connection is
- // broken during the `Write()`, the client should check the status of the
- // `Write()` by calling `QueryWriteStatus()` and continue writing from the
- // returned `committed_size`. This may be less than the amount of data the
- // client previously sent.
- //
- // Calling `Write()` on a resource name that was previously written and
- // finalized could cause an error, depending on whether the underlying service
- // allows over-writing of previously written resources.
- //
- // When the client closes the request channel, the service will respond with
- // a `WriteResponse`. The service will not view the resource as `complete`
- // until the client has sent a `WriteRequest` with `finish_write` set to
- // `true`. Sending any requests on a stream after sending a request with
- // `finish_write` set to `true` will cause an error. The client **should**
- // check the `WriteResponse` it receives to determine how much data the
- // service was able to commit and whether the service views the resource as
- // `complete` or not.
- rpc Write(stream WriteRequest) returns (WriteResponse);
-
- // `QueryWriteStatus()` is used to find the `committed_size` for a resource
- // that is being written, which can then be used as the `write_offset` for
- // the next `Write()` call.
- //
- // If the resource does not exist (i.e., the resource has been deleted, or the
- // first `Write()` has not yet reached the service), this method returns the
- // error `NOT_FOUND`.
- //
- // The client **may** call `QueryWriteStatus()` at any time to determine how
- // much data has been processed for this resource. This is useful if the
- // client is buffering data and needs to know which data can be safely
- // evicted. For any sequence of `QueryWriteStatus()` calls for a given
- // resource name, the sequence of returned `committed_size` values will be
- // non-decreasing.
- rpc QueryWriteStatus(QueryWriteStatusRequest) returns (QueryWriteStatusResponse);
-}
-
-// Request object for ByteStream.Read.
-message ReadRequest {
- // The name of the resource to read.
- string resource_name = 1;
-
- // The offset for the first byte to return in the read, relative to the start
- // of the resource.
- //
- // A `read_offset` that is negative or greater than the size of the resource
- // will cause an `OUT_OF_RANGE` error.
- int64 read_offset = 2;
-
- // The maximum number of `data` bytes the server is allowed to return in the
- // sum of all `ReadResponse` messages. A `read_limit` of zero indicates that
- // there is no limit, and a negative `read_limit` will cause an error.
- //
- // If the stream returns fewer bytes than allowed by the `read_limit` and no
- // error occurred, the stream includes all data from the `read_offset` to the
- // end of the resource.
- int64 read_limit = 3;
-}
-
-// Response object for ByteStream.Read.
-message ReadResponse {
- // A portion of the data for the resource. The service **may** leave `data`
- // empty for any given `ReadResponse`. This enables the service to inform the
- // client that the request is still live while it is running an operation to
- // generate more data.
- bytes data = 10;
-}
-
-// Request object for ByteStream.Write.
-message WriteRequest {
- // The name of the resource to write. This **must** be set on the first
- // `WriteRequest` of each `Write()` action. If it is set on subsequent calls,
- // it **must** match the value of the first request.
- string resource_name = 1;
-
- // The offset from the beginning of the resource at which the data should be
- // written. It is required on all `WriteRequest`s.
- //
- // In the first `WriteRequest` of a `Write()` action, it indicates
- // the initial offset for the `Write()` call. The value **must** be equal to
- // the `committed_size` that a call to `QueryWriteStatus()` would return.
- //
- // On subsequent calls, this value **must** be set and **must** be equal to
- // the sum of the first `write_offset` and the sizes of all `data` bundles
- // sent previously on this stream.
- //
- // An incorrect value will cause an error.
- int64 write_offset = 2;
-
- // If `true`, this indicates that the write is complete. Sending any
- // `WriteRequest`s subsequent to one in which `finish_write` is `true` will
- // cause an error.
- bool finish_write = 3;
-
- // A portion of the data for the resource. The client **may** leave `data`
- // empty for any given `WriteRequest`. This enables the client to inform the
- // service that the request is still live while it is running an operation to
- // generate more data.
- bytes data = 10;
-}
-
-// Response object for ByteStream.Write.
-message WriteResponse {
- // The number of bytes that have been processed for the given resource.
- int64 committed_size = 1;
-}
-
-// Request object for ByteStream.QueryWriteStatus.
-message QueryWriteStatusRequest {
- // The name of the resource whose write status is being requested.
- string resource_name = 1;
-}
-
-// Response object for ByteStream.QueryWriteStatus.
-message QueryWriteStatusResponse {
- // The number of bytes that have been processed for the given resource.
- int64 committed_size = 1;
-
- // `complete` is `true` only if the client has sent a `WriteRequest` with
- // `finish_write` set to true, and the server has processed that request.
- bool complete = 2;
-}
diff --git a/buildstream/_protos/google/bytestream/bytestream_pb2.py b/buildstream/_protos/google/bytestream/bytestream_pb2.py
deleted file mode 100644
index c8487d6a0..000000000
--- a/buildstream/_protos/google/bytestream/bytestream_pb2.py
+++ /dev/null
@@ -1,353 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: google/bytestream/bytestream.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-from buildstream._protos.google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2
-from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='google/bytestream/bytestream.proto',
- package='google.bytestream',
- syntax='proto3',
- serialized_pb=_b('\n\"google/bytestream/bytestream.proto\x12\x11google.bytestream\x1a\x1cgoogle/api/annotations.proto\x1a\x1egoogle/protobuf/wrappers.proto\"M\n\x0bReadRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\x12\x13\n\x0bread_offset\x18\x02 \x01(\x03\x12\x12\n\nread_limit\x18\x03 \x01(\x03\"\x1c\n\x0cReadResponse\x12\x0c\n\x04\x64\x61ta\x18\n \x01(\x0c\"_\n\x0cWriteRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\x12\x14\n\x0cwrite_offset\x18\x02 \x01(\x03\x12\x14\n\x0c\x66inish_write\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\n \x01(\x0c\"\'\n\rWriteResponse\x12\x16\n\x0e\x63ommitted_size\x18\x01 \x01(\x03\"0\n\x17QueryWriteStatusRequest\x12\x15\n\rresource_name\x18\x01 \x01(\t\"D\n\x18QueryWriteStatusResponse\x12\x16\n\x0e\x63ommitted_size\x18\x01 \x01(\x03\x12\x10\n\x08\x63omplete\x18\x02 \x01(\x08\x32\x92\x02\n\nByteStream\x12I\n\x04Read\x12\x1e.google.bytestream.ReadRequest\x1a\x1f.google.bytestream.ReadResponse0\x01\x12L\n\x05Write\x12\x1f.google.bytestream.WriteRequest\x1a .google.bytestream.WriteResponse(\x01\x12k\n\x10QueryWriteStatus\x12*.google.bytestream.QueryWriteStatusRequest\x1a+.google.bytestream.QueryWriteStatusResponseBe\n\x15\x63om.google.bytestreamB\x0f\x42yteStreamProtoZ;google.golang.org/genproto/googleapis/bytestream;bytestreamb\x06proto3')
- ,
- dependencies=[google_dot_api_dot_annotations__pb2.DESCRIPTOR,google_dot_protobuf_dot_wrappers__pb2.DESCRIPTOR,])
-
-
-
-
-_READREQUEST = _descriptor.Descriptor(
- name='ReadRequest',
- full_name='google.bytestream.ReadRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='resource_name', full_name='google.bytestream.ReadRequest.resource_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='read_offset', full_name='google.bytestream.ReadRequest.read_offset', index=1,
- number=2, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='read_limit', full_name='google.bytestream.ReadRequest.read_limit', index=2,
- number=3, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=119,
- serialized_end=196,
-)
-
-
-_READRESPONSE = _descriptor.Descriptor(
- name='ReadResponse',
- full_name='google.bytestream.ReadResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='data', full_name='google.bytestream.ReadResponse.data', index=0,
- number=10, type=12, cpp_type=9, label=1,
- has_default_value=False, default_value=_b(""),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=198,
- serialized_end=226,
-)
-
-
-_WRITEREQUEST = _descriptor.Descriptor(
- name='WriteRequest',
- full_name='google.bytestream.WriteRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='resource_name', full_name='google.bytestream.WriteRequest.resource_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='write_offset', full_name='google.bytestream.WriteRequest.write_offset', index=1,
- number=2, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='finish_write', full_name='google.bytestream.WriteRequest.finish_write', index=2,
- number=3, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='data', full_name='google.bytestream.WriteRequest.data', index=3,
- number=10, type=12, cpp_type=9, label=1,
- has_default_value=False, default_value=_b(""),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=228,
- serialized_end=323,
-)
-
-
-_WRITERESPONSE = _descriptor.Descriptor(
- name='WriteResponse',
- full_name='google.bytestream.WriteResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='committed_size', full_name='google.bytestream.WriteResponse.committed_size', index=0,
- number=1, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=325,
- serialized_end=364,
-)
-
-
-_QUERYWRITESTATUSREQUEST = _descriptor.Descriptor(
- name='QueryWriteStatusRequest',
- full_name='google.bytestream.QueryWriteStatusRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='resource_name', full_name='google.bytestream.QueryWriteStatusRequest.resource_name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=366,
- serialized_end=414,
-)
-
-
-_QUERYWRITESTATUSRESPONSE = _descriptor.Descriptor(
- name='QueryWriteStatusResponse',
- full_name='google.bytestream.QueryWriteStatusResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='committed_size', full_name='google.bytestream.QueryWriteStatusResponse.committed_size', index=0,
- number=1, type=3, cpp_type=2, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='complete', full_name='google.bytestream.QueryWriteStatusResponse.complete', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=416,
- serialized_end=484,
-)
-
-DESCRIPTOR.message_types_by_name['ReadRequest'] = _READREQUEST
-DESCRIPTOR.message_types_by_name['ReadResponse'] = _READRESPONSE
-DESCRIPTOR.message_types_by_name['WriteRequest'] = _WRITEREQUEST
-DESCRIPTOR.message_types_by_name['WriteResponse'] = _WRITERESPONSE
-DESCRIPTOR.message_types_by_name['QueryWriteStatusRequest'] = _QUERYWRITESTATUSREQUEST
-DESCRIPTOR.message_types_by_name['QueryWriteStatusResponse'] = _QUERYWRITESTATUSRESPONSE
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-ReadRequest = _reflection.GeneratedProtocolMessageType('ReadRequest', (_message.Message,), dict(
- DESCRIPTOR = _READREQUEST,
- __module__ = 'google.bytestream.bytestream_pb2'
- # @@protoc_insertion_point(class_scope:google.bytestream.ReadRequest)
- ))
-_sym_db.RegisterMessage(ReadRequest)
-
-ReadResponse = _reflection.GeneratedProtocolMessageType('ReadResponse', (_message.Message,), dict(
- DESCRIPTOR = _READRESPONSE,
- __module__ = 'google.bytestream.bytestream_pb2'
- # @@protoc_insertion_point(class_scope:google.bytestream.ReadResponse)
- ))
-_sym_db.RegisterMessage(ReadResponse)
-
-WriteRequest = _reflection.GeneratedProtocolMessageType('WriteRequest', (_message.Message,), dict(
- DESCRIPTOR = _WRITEREQUEST,
- __module__ = 'google.bytestream.bytestream_pb2'
- # @@protoc_insertion_point(class_scope:google.bytestream.WriteRequest)
- ))
-_sym_db.RegisterMessage(WriteRequest)
-
-WriteResponse = _reflection.GeneratedProtocolMessageType('WriteResponse', (_message.Message,), dict(
- DESCRIPTOR = _WRITERESPONSE,
- __module__ = 'google.bytestream.bytestream_pb2'
- # @@protoc_insertion_point(class_scope:google.bytestream.WriteResponse)
- ))
-_sym_db.RegisterMessage(WriteResponse)
-
-QueryWriteStatusRequest = _reflection.GeneratedProtocolMessageType('QueryWriteStatusRequest', (_message.Message,), dict(
- DESCRIPTOR = _QUERYWRITESTATUSREQUEST,
- __module__ = 'google.bytestream.bytestream_pb2'
- # @@protoc_insertion_point(class_scope:google.bytestream.QueryWriteStatusRequest)
- ))
-_sym_db.RegisterMessage(QueryWriteStatusRequest)
-
-QueryWriteStatusResponse = _reflection.GeneratedProtocolMessageType('QueryWriteStatusResponse', (_message.Message,), dict(
- DESCRIPTOR = _QUERYWRITESTATUSRESPONSE,
- __module__ = 'google.bytestream.bytestream_pb2'
- # @@protoc_insertion_point(class_scope:google.bytestream.QueryWriteStatusResponse)
- ))
-_sym_db.RegisterMessage(QueryWriteStatusResponse)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\025com.google.bytestreamB\017ByteStreamProtoZ;google.golang.org/genproto/googleapis/bytestream;bytestream'))
-
-_BYTESTREAM = _descriptor.ServiceDescriptor(
- name='ByteStream',
- full_name='google.bytestream.ByteStream',
- file=DESCRIPTOR,
- index=0,
- options=None,
- serialized_start=487,
- serialized_end=761,
- methods=[
- _descriptor.MethodDescriptor(
- name='Read',
- full_name='google.bytestream.ByteStream.Read',
- index=0,
- containing_service=None,
- input_type=_READREQUEST,
- output_type=_READRESPONSE,
- options=None,
- ),
- _descriptor.MethodDescriptor(
- name='Write',
- full_name='google.bytestream.ByteStream.Write',
- index=1,
- containing_service=None,
- input_type=_WRITEREQUEST,
- output_type=_WRITERESPONSE,
- options=None,
- ),
- _descriptor.MethodDescriptor(
- name='QueryWriteStatus',
- full_name='google.bytestream.ByteStream.QueryWriteStatus',
- index=2,
- containing_service=None,
- input_type=_QUERYWRITESTATUSREQUEST,
- output_type=_QUERYWRITESTATUSRESPONSE,
- options=None,
- ),
-])
-_sym_db.RegisterServiceDescriptor(_BYTESTREAM)
-
-DESCRIPTOR.services_by_name['ByteStream'] = _BYTESTREAM
-
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/google/bytestream/bytestream_pb2_grpc.py b/buildstream/_protos/google/bytestream/bytestream_pb2_grpc.py
deleted file mode 100644
index ef993e040..000000000
--- a/buildstream/_protos/google/bytestream/bytestream_pb2_grpc.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
-from buildstream._protos.google.bytestream import bytestream_pb2 as google_dot_bytestream_dot_bytestream__pb2
-
-
-class ByteStreamStub(object):
- """#### Introduction
-
- The Byte Stream API enables a client to read and write a stream of bytes to
- and from a resource. Resources have names, and these names are supplied in
- the API calls below to identify the resource that is being read from or
- written to.
-
- All implementations of the Byte Stream API export the interface defined here:
-
- * `Read()`: Reads the contents of a resource.
-
- * `Write()`: Writes the contents of a resource. The client can call `Write()`
- multiple times with the same resource and can check the status of the write
- by calling `QueryWriteStatus()`.
-
- #### Service parameters and metadata
-
- The ByteStream API provides no direct way to access/modify any metadata
- associated with the resource.
-
- #### Errors
-
- The errors returned by the service are in the Google canonical error space.
- """
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.Read = channel.unary_stream(
- '/google.bytestream.ByteStream/Read',
- request_serializer=google_dot_bytestream_dot_bytestream__pb2.ReadRequest.SerializeToString,
- response_deserializer=google_dot_bytestream_dot_bytestream__pb2.ReadResponse.FromString,
- )
- self.Write = channel.stream_unary(
- '/google.bytestream.ByteStream/Write',
- request_serializer=google_dot_bytestream_dot_bytestream__pb2.WriteRequest.SerializeToString,
- response_deserializer=google_dot_bytestream_dot_bytestream__pb2.WriteResponse.FromString,
- )
- self.QueryWriteStatus = channel.unary_unary(
- '/google.bytestream.ByteStream/QueryWriteStatus',
- request_serializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusRequest.SerializeToString,
- response_deserializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusResponse.FromString,
- )
-
-
-class ByteStreamServicer(object):
- """#### Introduction
-
- The Byte Stream API enables a client to read and write a stream of bytes to
- and from a resource. Resources have names, and these names are supplied in
- the API calls below to identify the resource that is being read from or
- written to.
-
- All implementations of the Byte Stream API export the interface defined here:
-
- * `Read()`: Reads the contents of a resource.
-
- * `Write()`: Writes the contents of a resource. The client can call `Write()`
- multiple times with the same resource and can check the status of the write
- by calling `QueryWriteStatus()`.
-
- #### Service parameters and metadata
-
- The ByteStream API provides no direct way to access/modify any metadata
- associated with the resource.
-
- #### Errors
-
- The errors returned by the service are in the Google canonical error space.
- """
-
- def Read(self, request, context):
- """`Read()` is used to retrieve the contents of a resource as a sequence
- of bytes. The bytes are returned in a sequence of responses, and the
- responses are delivered as the results of a server-side streaming RPC.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def Write(self, request_iterator, context):
- """`Write()` is used to send the contents of a resource as a sequence of
- bytes. The bytes are sent in a sequence of request protos of a client-side
- streaming RPC.
-
- A `Write()` action is resumable. If there is an error or the connection is
- broken during the `Write()`, the client should check the status of the
- `Write()` by calling `QueryWriteStatus()` and continue writing from the
- returned `committed_size`. This may be less than the amount of data the
- client previously sent.
-
- Calling `Write()` on a resource name that was previously written and
- finalized could cause an error, depending on whether the underlying service
- allows over-writing of previously written resources.
-
- When the client closes the request channel, the service will respond with
- a `WriteResponse`. The service will not view the resource as `complete`
- until the client has sent a `WriteRequest` with `finish_write` set to
- `true`. Sending any requests on a stream after sending a request with
- `finish_write` set to `true` will cause an error. The client **should**
- check the `WriteResponse` it receives to determine how much data the
- service was able to commit and whether the service views the resource as
- `complete` or not.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def QueryWriteStatus(self, request, context):
- """`QueryWriteStatus()` is used to find the `committed_size` for a resource
- that is being written, which can then be used as the `write_offset` for
- the next `Write()` call.
-
- If the resource does not exist (i.e., the resource has been deleted, or the
- first `Write()` has not yet reached the service), this method returns the
- error `NOT_FOUND`.
-
- The client **may** call `QueryWriteStatus()` at any time to determine how
- much data has been processed for this resource. This is useful if the
- client is buffering data and needs to know which data can be safely
- evicted. For any sequence of `QueryWriteStatus()` calls for a given
- resource name, the sequence of returned `committed_size` values will be
- non-decreasing.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_ByteStreamServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'Read': grpc.unary_stream_rpc_method_handler(
- servicer.Read,
- request_deserializer=google_dot_bytestream_dot_bytestream__pb2.ReadRequest.FromString,
- response_serializer=google_dot_bytestream_dot_bytestream__pb2.ReadResponse.SerializeToString,
- ),
- 'Write': grpc.stream_unary_rpc_method_handler(
- servicer.Write,
- request_deserializer=google_dot_bytestream_dot_bytestream__pb2.WriteRequest.FromString,
- response_serializer=google_dot_bytestream_dot_bytestream__pb2.WriteResponse.SerializeToString,
- ),
- 'QueryWriteStatus': grpc.unary_unary_rpc_method_handler(
- servicer.QueryWriteStatus,
- request_deserializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusRequest.FromString,
- response_serializer=google_dot_bytestream_dot_bytestream__pb2.QueryWriteStatusResponse.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'google.bytestream.ByteStream', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
diff --git a/buildstream/_protos/google/longrunning/__init__.py b/buildstream/_protos/google/longrunning/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/google/longrunning/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/google/longrunning/operations.proto b/buildstream/_protos/google/longrunning/operations.proto
deleted file mode 100644
index 76fef29c3..000000000
--- a/buildstream/_protos/google/longrunning/operations.proto
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2016 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package google.longrunning;
-
-import "google/api/annotations.proto";
-import "google/protobuf/any.proto";
-import "google/protobuf/empty.proto";
-import "google/rpc/status.proto";
-
-option csharp_namespace = "Google.LongRunning";
-option go_package = "google.golang.org/genproto/googleapis/longrunning;longrunning";
-option java_multiple_files = true;
-option java_outer_classname = "OperationsProto";
-option java_package = "com.google.longrunning";
-option php_namespace = "Google\\LongRunning";
-
-
-// Manages long-running operations with an API service.
-//
-// When an API method normally takes long time to complete, it can be designed
-// to return [Operation][google.longrunning.Operation] to the client, and the client can use this
-// interface to receive the real response asynchronously by polling the
-// operation resource, or pass the operation resource to another API (such as
-// Google Cloud Pub/Sub API) to receive the response. Any API service that
-// returns long-running operations should implement the `Operations` interface
-// so developers can have a consistent client experience.
-service Operations {
- // Lists operations that match the specified filter in the request. If the
- // server doesn't support this method, it returns `UNIMPLEMENTED`.
- //
- // NOTE: the `name` binding below allows API services to override the binding
- // to use different resource name schemes, such as `users/*/operations`.
- rpc ListOperations(ListOperationsRequest) returns (ListOperationsResponse) {
- option (google.api.http) = { get: "/v1/{name=operations}" };
- }
-
- // Gets the latest state of a long-running operation. Clients can use this
- // method to poll the operation result at intervals as recommended by the API
- // service.
- rpc GetOperation(GetOperationRequest) returns (Operation) {
- option (google.api.http) = { get: "/v1/{name=operations/**}" };
- }
-
- // Deletes a long-running operation. This method indicates that the client is
- // no longer interested in the operation result. It does not cancel the
- // operation. If the server doesn't support this method, it returns
- // `google.rpc.Code.UNIMPLEMENTED`.
- rpc DeleteOperation(DeleteOperationRequest) returns (google.protobuf.Empty) {
- option (google.api.http) = { delete: "/v1/{name=operations/**}" };
- }
-
- // Starts asynchronous cancellation on a long-running operation. The server
- // makes a best effort to cancel the operation, but success is not
- // guaranteed. If the server doesn't support this method, it returns
- // `google.rpc.Code.UNIMPLEMENTED`. Clients can use
- // [Operations.GetOperation][google.longrunning.Operations.GetOperation] or
- // other methods to check whether the cancellation succeeded or whether the
- // operation completed despite cancellation. On successful cancellation,
- // the operation is not deleted; instead, it becomes an operation with
- // an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1,
- // corresponding to `Code.CANCELLED`.
- rpc CancelOperation(CancelOperationRequest) returns (google.protobuf.Empty) {
- option (google.api.http) = { post: "/v1/{name=operations/**}:cancel" body: "*" };
- }
-}
-
-// This resource represents a long-running operation that is the result of a
-// network API call.
-message Operation {
- // The server-assigned name, which is only unique within the same service that
- // originally returns it. If you use the default HTTP mapping, the
- // `name` should have the format of `operations/some/unique/name`.
- string name = 1;
-
- // Service-specific metadata associated with the operation. It typically
- // contains progress information and common metadata such as create time.
- // Some services might not provide such metadata. Any method that returns a
- // long-running operation should document the metadata type, if any.
- google.protobuf.Any metadata = 2;
-
- // If the value is `false`, it means the operation is still in progress.
- // If true, the operation is completed, and either `error` or `response` is
- // available.
- bool done = 3;
-
- // The operation result, which can be either an `error` or a valid `response`.
- // If `done` == `false`, neither `error` nor `response` is set.
- // If `done` == `true`, exactly one of `error` or `response` is set.
- oneof result {
- // The error result of the operation in case of failure or cancellation.
- google.rpc.Status error = 4;
-
- // The normal response of the operation in case of success. If the original
- // method returns no data on success, such as `Delete`, the response is
- // `google.protobuf.Empty`. If the original method is standard
- // `Get`/`Create`/`Update`, the response should be the resource. For other
- // methods, the response should have the type `XxxResponse`, where `Xxx`
- // is the original method name. For example, if the original method name
- // is `TakeSnapshot()`, the inferred response type is
- // `TakeSnapshotResponse`.
- google.protobuf.Any response = 5;
- }
-}
-
-// The request message for [Operations.GetOperation][google.longrunning.Operations.GetOperation].
-message GetOperationRequest {
- // The name of the operation resource.
- string name = 1;
-}
-
-// The request message for [Operations.ListOperations][google.longrunning.Operations.ListOperations].
-message ListOperationsRequest {
- // The name of the operation collection.
- string name = 4;
-
- // The standard list filter.
- string filter = 1;
-
- // The standard list page size.
- int32 page_size = 2;
-
- // The standard list page token.
- string page_token = 3;
-}
-
-// The response message for [Operations.ListOperations][google.longrunning.Operations.ListOperations].
-message ListOperationsResponse {
- // A list of operations that matches the specified filter in the request.
- repeated Operation operations = 1;
-
- // The standard List next-page token.
- string next_page_token = 2;
-}
-
-// The request message for [Operations.CancelOperation][google.longrunning.Operations.CancelOperation].
-message CancelOperationRequest {
- // The name of the operation resource to be cancelled.
- string name = 1;
-}
-
-// The request message for [Operations.DeleteOperation][google.longrunning.Operations.DeleteOperation].
-message DeleteOperationRequest {
- // The name of the operation resource to be deleted.
- string name = 1;
-}
-
diff --git a/buildstream/_protos/google/longrunning/operations_pb2.py b/buildstream/_protos/google/longrunning/operations_pb2.py
deleted file mode 100644
index 9fd89937f..000000000
--- a/buildstream/_protos/google/longrunning/operations_pb2.py
+++ /dev/null
@@ -1,391 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: google/longrunning/operations.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-from buildstream._protos.google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2
-from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2
-from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
-from buildstream._protos.google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='google/longrunning/operations.proto',
- package='google.longrunning',
- syntax='proto3',
- serialized_pb=_b('\n#google/longrunning/operations.proto\x12\x12google.longrunning\x1a\x1cgoogle/api/annotations.proto\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x17google/rpc/status.proto\"\xa8\x01\n\tOperation\x12\x0c\n\x04name\x18\x01 \x01(\t\x12&\n\x08metadata\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\x12#\n\x05\x65rror\x18\x04 \x01(\x0b\x32\x12.google.rpc.StatusH\x00\x12(\n\x08response\x18\x05 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x42\x08\n\x06result\"#\n\x13GetOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\\\n\x15ListOperationsRequest\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"d\n\x16ListOperationsResponse\x12\x31\n\noperations\x18\x01 \x03(\x0b\x32\x1d.google.longrunning.Operation\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"&\n\x16\x43\x61ncelOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"&\n\x16\x44\x65leteOperationRequest\x12\x0c\n\x04name\x18\x01 \x01(\t2\x8c\x04\n\nOperations\x12\x86\x01\n\x0eListOperations\x12).google.longrunning.ListOperationsRequest\x1a*.google.longrunning.ListOperationsResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\x12\x15/v1/{name=operations}\x12x\n\x0cGetOperation\x12\'.google.longrunning.GetOperationRequest\x1a\x1d.google.longrunning.Operation\" \x82\xd3\xe4\x93\x02\x1a\x12\x18/v1/{name=operations/**}\x12w\n\x0f\x44\x65leteOperation\x12*.google.longrunning.DeleteOperationRequest\x1a\x16.google.protobuf.Empty\" \x82\xd3\xe4\x93\x02\x1a*\x18/v1/{name=operations/**}\x12\x81\x01\n\x0f\x43\x61ncelOperation\x12*.google.longrunning.CancelOperationRequest\x1a\x16.google.protobuf.Empty\"*\x82\xd3\xe4\x93\x02$\"\x1f/v1/{name=operations/**}:cancel:\x01*B\x94\x01\n\x16\x63om.google.longrunningB\x0fOperationsProtoP\x01Z=google.golang.org/genproto/googleapis/longrunning;longrunning\xaa\x02\x12Google.LongRunning\xca\x02\x12Google\\LongRunningb\x06proto3')
- ,
- dependencies=[google_dot_api_dot_annotations__pb2.DESCRIPTOR,google_dot_protobuf_dot_any__pb2.DESCRIPTOR,google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_rpc_dot_status__pb2.DESCRIPTOR,])
-
-
-
-
-_OPERATION = _descriptor.Descriptor(
- name='Operation',
- full_name='google.longrunning.Operation',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='google.longrunning.Operation.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='metadata', full_name='google.longrunning.Operation.metadata', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='done', full_name='google.longrunning.Operation.done', index=2,
- number=3, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='error', full_name='google.longrunning.Operation.error', index=3,
- number=4, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='response', full_name='google.longrunning.Operation.response', index=4,
- number=5, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- _descriptor.OneofDescriptor(
- name='result', full_name='google.longrunning.Operation.result',
- index=0, containing_type=None, fields=[]),
- ],
- serialized_start=171,
- serialized_end=339,
-)
-
-
-_GETOPERATIONREQUEST = _descriptor.Descriptor(
- name='GetOperationRequest',
- full_name='google.longrunning.GetOperationRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='google.longrunning.GetOperationRequest.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=341,
- serialized_end=376,
-)
-
-
-_LISTOPERATIONSREQUEST = _descriptor.Descriptor(
- name='ListOperationsRequest',
- full_name='google.longrunning.ListOperationsRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='google.longrunning.ListOperationsRequest.name', index=0,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='filter', full_name='google.longrunning.ListOperationsRequest.filter', index=1,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='page_size', full_name='google.longrunning.ListOperationsRequest.page_size', index=2,
- number=2, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='page_token', full_name='google.longrunning.ListOperationsRequest.page_token', index=3,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=378,
- serialized_end=470,
-)
-
-
-_LISTOPERATIONSRESPONSE = _descriptor.Descriptor(
- name='ListOperationsResponse',
- full_name='google.longrunning.ListOperationsResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='operations', full_name='google.longrunning.ListOperationsResponse.operations', index=0,
- number=1, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='next_page_token', full_name='google.longrunning.ListOperationsResponse.next_page_token', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=472,
- serialized_end=572,
-)
-
-
-_CANCELOPERATIONREQUEST = _descriptor.Descriptor(
- name='CancelOperationRequest',
- full_name='google.longrunning.CancelOperationRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='google.longrunning.CancelOperationRequest.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=574,
- serialized_end=612,
-)
-
-
-_DELETEOPERATIONREQUEST = _descriptor.Descriptor(
- name='DeleteOperationRequest',
- full_name='google.longrunning.DeleteOperationRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='name', full_name='google.longrunning.DeleteOperationRequest.name', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=614,
- serialized_end=652,
-)
-
-_OPERATION.fields_by_name['metadata'].message_type = google_dot_protobuf_dot_any__pb2._ANY
-_OPERATION.fields_by_name['error'].message_type = google_dot_rpc_dot_status__pb2._STATUS
-_OPERATION.fields_by_name['response'].message_type = google_dot_protobuf_dot_any__pb2._ANY
-_OPERATION.oneofs_by_name['result'].fields.append(
- _OPERATION.fields_by_name['error'])
-_OPERATION.fields_by_name['error'].containing_oneof = _OPERATION.oneofs_by_name['result']
-_OPERATION.oneofs_by_name['result'].fields.append(
- _OPERATION.fields_by_name['response'])
-_OPERATION.fields_by_name['response'].containing_oneof = _OPERATION.oneofs_by_name['result']
-_LISTOPERATIONSRESPONSE.fields_by_name['operations'].message_type = _OPERATION
-DESCRIPTOR.message_types_by_name['Operation'] = _OPERATION
-DESCRIPTOR.message_types_by_name['GetOperationRequest'] = _GETOPERATIONREQUEST
-DESCRIPTOR.message_types_by_name['ListOperationsRequest'] = _LISTOPERATIONSREQUEST
-DESCRIPTOR.message_types_by_name['ListOperationsResponse'] = _LISTOPERATIONSRESPONSE
-DESCRIPTOR.message_types_by_name['CancelOperationRequest'] = _CANCELOPERATIONREQUEST
-DESCRIPTOR.message_types_by_name['DeleteOperationRequest'] = _DELETEOPERATIONREQUEST
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-Operation = _reflection.GeneratedProtocolMessageType('Operation', (_message.Message,), dict(
- DESCRIPTOR = _OPERATION,
- __module__ = 'google.longrunning.operations_pb2'
- # @@protoc_insertion_point(class_scope:google.longrunning.Operation)
- ))
-_sym_db.RegisterMessage(Operation)
-
-GetOperationRequest = _reflection.GeneratedProtocolMessageType('GetOperationRequest', (_message.Message,), dict(
- DESCRIPTOR = _GETOPERATIONREQUEST,
- __module__ = 'google.longrunning.operations_pb2'
- # @@protoc_insertion_point(class_scope:google.longrunning.GetOperationRequest)
- ))
-_sym_db.RegisterMessage(GetOperationRequest)
-
-ListOperationsRequest = _reflection.GeneratedProtocolMessageType('ListOperationsRequest', (_message.Message,), dict(
- DESCRIPTOR = _LISTOPERATIONSREQUEST,
- __module__ = 'google.longrunning.operations_pb2'
- # @@protoc_insertion_point(class_scope:google.longrunning.ListOperationsRequest)
- ))
-_sym_db.RegisterMessage(ListOperationsRequest)
-
-ListOperationsResponse = _reflection.GeneratedProtocolMessageType('ListOperationsResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTOPERATIONSRESPONSE,
- __module__ = 'google.longrunning.operations_pb2'
- # @@protoc_insertion_point(class_scope:google.longrunning.ListOperationsResponse)
- ))
-_sym_db.RegisterMessage(ListOperationsResponse)
-
-CancelOperationRequest = _reflection.GeneratedProtocolMessageType('CancelOperationRequest', (_message.Message,), dict(
- DESCRIPTOR = _CANCELOPERATIONREQUEST,
- __module__ = 'google.longrunning.operations_pb2'
- # @@protoc_insertion_point(class_scope:google.longrunning.CancelOperationRequest)
- ))
-_sym_db.RegisterMessage(CancelOperationRequest)
-
-DeleteOperationRequest = _reflection.GeneratedProtocolMessageType('DeleteOperationRequest', (_message.Message,), dict(
- DESCRIPTOR = _DELETEOPERATIONREQUEST,
- __module__ = 'google.longrunning.operations_pb2'
- # @@protoc_insertion_point(class_scope:google.longrunning.DeleteOperationRequest)
- ))
-_sym_db.RegisterMessage(DeleteOperationRequest)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\026com.google.longrunningB\017OperationsProtoP\001Z=google.golang.org/genproto/googleapis/longrunning;longrunning\252\002\022Google.LongRunning\312\002\022Google\\LongRunning'))
-
-_OPERATIONS = _descriptor.ServiceDescriptor(
- name='Operations',
- full_name='google.longrunning.Operations',
- file=DESCRIPTOR,
- index=0,
- options=None,
- serialized_start=655,
- serialized_end=1179,
- methods=[
- _descriptor.MethodDescriptor(
- name='ListOperations',
- full_name='google.longrunning.Operations.ListOperations',
- index=0,
- containing_service=None,
- input_type=_LISTOPERATIONSREQUEST,
- output_type=_LISTOPERATIONSRESPONSE,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\027\022\025/v1/{name=operations}')),
- ),
- _descriptor.MethodDescriptor(
- name='GetOperation',
- full_name='google.longrunning.Operations.GetOperation',
- index=1,
- containing_service=None,
- input_type=_GETOPERATIONREQUEST,
- output_type=_OPERATION,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\032\022\030/v1/{name=operations/**}')),
- ),
- _descriptor.MethodDescriptor(
- name='DeleteOperation',
- full_name='google.longrunning.Operations.DeleteOperation',
- index=2,
- containing_service=None,
- input_type=_DELETEOPERATIONREQUEST,
- output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002\032*\030/v1/{name=operations/**}')),
- ),
- _descriptor.MethodDescriptor(
- name='CancelOperation',
- full_name='google.longrunning.Operations.CancelOperation',
- index=3,
- containing_service=None,
- input_type=_CANCELOPERATIONREQUEST,
- output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
- options=_descriptor._ParseOptions(descriptor_pb2.MethodOptions(), _b('\202\323\344\223\002$\"\037/v1/{name=operations/**}:cancel:\001*')),
- ),
-])
-_sym_db.RegisterServiceDescriptor(_OPERATIONS)
-
-DESCRIPTOR.services_by_name['Operations'] = _OPERATIONS
-
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/google/longrunning/operations_pb2_grpc.py b/buildstream/_protos/google/longrunning/operations_pb2_grpc.py
deleted file mode 100644
index 8f89862e7..000000000
--- a/buildstream/_protos/google/longrunning/operations_pb2_grpc.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
-from buildstream._protos.google.longrunning import operations_pb2 as google_dot_longrunning_dot_operations__pb2
-from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
-
-
-class OperationsStub(object):
- """Manages long-running operations with an API service.
-
- When an API method normally takes long time to complete, it can be designed
- to return [Operation][google.longrunning.Operation] to the client, and the client can use this
- interface to receive the real response asynchronously by polling the
- operation resource, or pass the operation resource to another API (such as
- Google Cloud Pub/Sub API) to receive the response. Any API service that
- returns long-running operations should implement the `Operations` interface
- so developers can have a consistent client experience.
- """
-
- def __init__(self, channel):
- """Constructor.
-
- Args:
- channel: A grpc.Channel.
- """
- self.ListOperations = channel.unary_unary(
- '/google.longrunning.Operations/ListOperations',
- request_serializer=google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.SerializeToString,
- response_deserializer=google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.FromString,
- )
- self.GetOperation = channel.unary_unary(
- '/google.longrunning.Operations/GetOperation',
- request_serializer=google_dot_longrunning_dot_operations__pb2.GetOperationRequest.SerializeToString,
- response_deserializer=google_dot_longrunning_dot_operations__pb2.Operation.FromString,
- )
- self.DeleteOperation = channel.unary_unary(
- '/google.longrunning.Operations/DeleteOperation',
- request_serializer=google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.SerializeToString,
- response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString,
- )
- self.CancelOperation = channel.unary_unary(
- '/google.longrunning.Operations/CancelOperation',
- request_serializer=google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.SerializeToString,
- response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString,
- )
-
-
-class OperationsServicer(object):
- """Manages long-running operations with an API service.
-
- When an API method normally takes long time to complete, it can be designed
- to return [Operation][google.longrunning.Operation] to the client, and the client can use this
- interface to receive the real response asynchronously by polling the
- operation resource, or pass the operation resource to another API (such as
- Google Cloud Pub/Sub API) to receive the response. Any API service that
- returns long-running operations should implement the `Operations` interface
- so developers can have a consistent client experience.
- """
-
- def ListOperations(self, request, context):
- """Lists operations that match the specified filter in the request. If the
- server doesn't support this method, it returns `UNIMPLEMENTED`.
-
- NOTE: the `name` binding below allows API services to override the binding
- to use different resource name schemes, such as `users/*/operations`.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def GetOperation(self, request, context):
- """Gets the latest state of a long-running operation. Clients can use this
- method to poll the operation result at intervals as recommended by the API
- service.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def DeleteOperation(self, request, context):
- """Deletes a long-running operation. This method indicates that the client is
- no longer interested in the operation result. It does not cancel the
- operation. If the server doesn't support this method, it returns
- `google.rpc.Code.UNIMPLEMENTED`.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
- def CancelOperation(self, request, context):
- """Starts asynchronous cancellation on a long-running operation. The server
- makes a best effort to cancel the operation, but success is not
- guaranteed. If the server doesn't support this method, it returns
- `google.rpc.Code.UNIMPLEMENTED`. Clients can use
- [Operations.GetOperation][google.longrunning.Operations.GetOperation] or
- other methods to check whether the cancellation succeeded or whether the
- operation completed despite cancellation. On successful cancellation,
- the operation is not deleted; instead, it becomes an operation with
- an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1,
- corresponding to `Code.CANCELLED`.
- """
- context.set_code(grpc.StatusCode.UNIMPLEMENTED)
- context.set_details('Method not implemented!')
- raise NotImplementedError('Method not implemented!')
-
-
-def add_OperationsServicer_to_server(servicer, server):
- rpc_method_handlers = {
- 'ListOperations': grpc.unary_unary_rpc_method_handler(
- servicer.ListOperations,
- request_deserializer=google_dot_longrunning_dot_operations__pb2.ListOperationsRequest.FromString,
- response_serializer=google_dot_longrunning_dot_operations__pb2.ListOperationsResponse.SerializeToString,
- ),
- 'GetOperation': grpc.unary_unary_rpc_method_handler(
- servicer.GetOperation,
- request_deserializer=google_dot_longrunning_dot_operations__pb2.GetOperationRequest.FromString,
- response_serializer=google_dot_longrunning_dot_operations__pb2.Operation.SerializeToString,
- ),
- 'DeleteOperation': grpc.unary_unary_rpc_method_handler(
- servicer.DeleteOperation,
- request_deserializer=google_dot_longrunning_dot_operations__pb2.DeleteOperationRequest.FromString,
- response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString,
- ),
- 'CancelOperation': grpc.unary_unary_rpc_method_handler(
- servicer.CancelOperation,
- request_deserializer=google_dot_longrunning_dot_operations__pb2.CancelOperationRequest.FromString,
- response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString,
- ),
- }
- generic_handler = grpc.method_handlers_generic_handler(
- 'google.longrunning.Operations', rpc_method_handlers)
- server.add_generic_rpc_handlers((generic_handler,))
diff --git a/buildstream/_protos/google/rpc/__init__.py b/buildstream/_protos/google/rpc/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/_protos/google/rpc/__init__.py
+++ /dev/null
diff --git a/buildstream/_protos/google/rpc/code.proto b/buildstream/_protos/google/rpc/code.proto
deleted file mode 100644
index 74e2c5c9a..000000000
--- a/buildstream/_protos/google/rpc/code.proto
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package google.rpc;
-
-option go_package = "google.golang.org/genproto/googleapis/rpc/code;code";
-option java_multiple_files = true;
-option java_outer_classname = "CodeProto";
-option java_package = "com.google.rpc";
-option objc_class_prefix = "RPC";
-
-
-// The canonical error codes for Google APIs.
-//
-//
-// Sometimes multiple error codes may apply. Services should return
-// the most specific error code that applies. For example, prefer
-// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply.
-// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`.
-enum Code {
- // Not an error; returned on success
- //
- // HTTP Mapping: 200 OK
- OK = 0;
-
- // The operation was cancelled, typically by the caller.
- //
- // HTTP Mapping: 499 Client Closed Request
- CANCELLED = 1;
-
- // Unknown error. For example, this error may be returned when
- // a `Status` value received from another address space belongs to
- // an error space that is not known in this address space. Also
- // errors raised by APIs that do not return enough error information
- // may be converted to this error.
- //
- // HTTP Mapping: 500 Internal Server Error
- UNKNOWN = 2;
-
- // The client specified an invalid argument. Note that this differs
- // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments
- // that are problematic regardless of the state of the system
- // (e.g., a malformed file name).
- //
- // HTTP Mapping: 400 Bad Request
- INVALID_ARGUMENT = 3;
-
- // The deadline expired before the operation could complete. For operations
- // that change the state of the system, this error may be returned
- // even if the operation has completed successfully. For example, a
- // successful response from a server could have been delayed long
- // enough for the deadline to expire.
- //
- // HTTP Mapping: 504 Gateway Timeout
- DEADLINE_EXCEEDED = 4;
-
- // Some requested entity (e.g., file or directory) was not found.
- //
- // Note to server developers: if a request is denied for an entire class
- // of users, such as gradual feature rollout or undocumented whitelist,
- // `NOT_FOUND` may be used. If a request is denied for some users within
- // a class of users, such as user-based access control, `PERMISSION_DENIED`
- // must be used.
- //
- // HTTP Mapping: 404 Not Found
- NOT_FOUND = 5;
-
- // The entity that a client attempted to create (e.g., file or directory)
- // already exists.
- //
- // HTTP Mapping: 409 Conflict
- ALREADY_EXISTS = 6;
-
- // The caller does not have permission to execute the specified
- // operation. `PERMISSION_DENIED` must not be used for rejections
- // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`
- // instead for those errors). `PERMISSION_DENIED` must not be
- // used if the caller can not be identified (use `UNAUTHENTICATED`
- // instead for those errors). This error code does not imply the
- // request is valid or the requested entity exists or satisfies
- // other pre-conditions.
- //
- // HTTP Mapping: 403 Forbidden
- PERMISSION_DENIED = 7;
-
- // The request does not have valid authentication credentials for the
- // operation.
- //
- // HTTP Mapping: 401 Unauthorized
- UNAUTHENTICATED = 16;
-
- // Some resource has been exhausted, perhaps a per-user quota, or
- // perhaps the entire file system is out of space.
- //
- // HTTP Mapping: 429 Too Many Requests
- RESOURCE_EXHAUSTED = 8;
-
- // The operation was rejected because the system is not in a state
- // required for the operation's execution. For example, the directory
- // to be deleted is non-empty, an rmdir operation is applied to
- // a non-directory, etc.
- //
- // Service implementors can use the following guidelines to decide
- // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:
- // (a) Use `UNAVAILABLE` if the client can retry just the failing call.
- // (b) Use `ABORTED` if the client should retry at a higher level
- // (e.g., when a client-specified test-and-set fails, indicating the
- // client should restart a read-modify-write sequence).
- // (c) Use `FAILED_PRECONDITION` if the client should not retry until
- // the system state has been explicitly fixed. E.g., if an "rmdir"
- // fails because the directory is non-empty, `FAILED_PRECONDITION`
- // should be returned since the client should not retry unless
- // the files are deleted from the directory.
- //
- // HTTP Mapping: 400 Bad Request
- FAILED_PRECONDITION = 9;
-
- // The operation was aborted, typically due to a concurrency issue such as
- // a sequencer check failure or transaction abort.
- //
- // See the guidelines above for deciding between `FAILED_PRECONDITION`,
- // `ABORTED`, and `UNAVAILABLE`.
- //
- // HTTP Mapping: 409 Conflict
- ABORTED = 10;
-
- // The operation was attempted past the valid range. E.g., seeking or
- // reading past end-of-file.
- //
- // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may
- // be fixed if the system state changes. For example, a 32-bit file
- // system will generate `INVALID_ARGUMENT` if asked to read at an
- // offset that is not in the range [0,2^32-1], but it will generate
- // `OUT_OF_RANGE` if asked to read from an offset past the current
- // file size.
- //
- // There is a fair bit of overlap between `FAILED_PRECONDITION` and
- // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific
- // error) when it applies so that callers who are iterating through
- // a space can easily look for an `OUT_OF_RANGE` error to detect when
- // they are done.
- //
- // HTTP Mapping: 400 Bad Request
- OUT_OF_RANGE = 11;
-
- // The operation is not implemented or is not supported/enabled in this
- // service.
- //
- // HTTP Mapping: 501 Not Implemented
- UNIMPLEMENTED = 12;
-
- // Internal errors. This means that some invariants expected by the
- // underlying system have been broken. This error code is reserved
- // for serious errors.
- //
- // HTTP Mapping: 500 Internal Server Error
- INTERNAL = 13;
-
- // The service is currently unavailable. This is most likely a
- // transient condition, which can be corrected by retrying with
- // a backoff.
- //
- // See the guidelines above for deciding between `FAILED_PRECONDITION`,
- // `ABORTED`, and `UNAVAILABLE`.
- //
- // HTTP Mapping: 503 Service Unavailable
- UNAVAILABLE = 14;
-
- // Unrecoverable data loss or corruption.
- //
- // HTTP Mapping: 500 Internal Server Error
- DATA_LOSS = 15;
-} \ No newline at end of file
diff --git a/buildstream/_protos/google/rpc/code_pb2.py b/buildstream/_protos/google/rpc/code_pb2.py
deleted file mode 100644
index e06dea194..000000000
--- a/buildstream/_protos/google/rpc/code_pb2.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: google/rpc/code.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf.internal import enum_type_wrapper
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='google/rpc/code.proto',
- package='google.rpc',
- syntax='proto3',
- serialized_options=_b('\n\016com.google.rpcB\tCodeProtoP\001Z3google.golang.org/genproto/googleapis/rpc/code;code\242\002\003RPC'),
- serialized_pb=_b('\n\x15google/rpc/code.proto\x12\ngoogle.rpc*\xb7\x02\n\x04\x43ode\x12\x06\n\x02OK\x10\x00\x12\r\n\tCANCELLED\x10\x01\x12\x0b\n\x07UNKNOWN\x10\x02\x12\x14\n\x10INVALID_ARGUMENT\x10\x03\x12\x15\n\x11\x44\x45\x41\x44LINE_EXCEEDED\x10\x04\x12\r\n\tNOT_FOUND\x10\x05\x12\x12\n\x0e\x41LREADY_EXISTS\x10\x06\x12\x15\n\x11PERMISSION_DENIED\x10\x07\x12\x13\n\x0fUNAUTHENTICATED\x10\x10\x12\x16\n\x12RESOURCE_EXHAUSTED\x10\x08\x12\x17\n\x13\x46\x41ILED_PRECONDITION\x10\t\x12\x0b\n\x07\x41\x42ORTED\x10\n\x12\x10\n\x0cOUT_OF_RANGE\x10\x0b\x12\x11\n\rUNIMPLEMENTED\x10\x0c\x12\x0c\n\x08INTERNAL\x10\r\x12\x0f\n\x0bUNAVAILABLE\x10\x0e\x12\r\n\tDATA_LOSS\x10\x0f\x42X\n\x0e\x63om.google.rpcB\tCodeProtoP\x01Z3google.golang.org/genproto/googleapis/rpc/code;code\xa2\x02\x03RPCb\x06proto3')
-)
-
-_CODE = _descriptor.EnumDescriptor(
- name='Code',
- full_name='google.rpc.Code',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='OK', index=0, number=0,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='CANCELLED', index=1, number=1,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='UNKNOWN', index=2, number=2,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='INVALID_ARGUMENT', index=3, number=3,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='DEADLINE_EXCEEDED', index=4, number=4,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='NOT_FOUND', index=5, number=5,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='ALREADY_EXISTS', index=6, number=6,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='PERMISSION_DENIED', index=7, number=7,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='UNAUTHENTICATED', index=8, number=16,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='RESOURCE_EXHAUSTED', index=9, number=8,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='FAILED_PRECONDITION', index=10, number=9,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='ABORTED', index=11, number=10,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='OUT_OF_RANGE', index=12, number=11,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='UNIMPLEMENTED', index=13, number=12,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='INTERNAL', index=14, number=13,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='UNAVAILABLE', index=15, number=14,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='DATA_LOSS', index=16, number=15,
- serialized_options=None,
- type=None),
- ],
- containing_type=None,
- serialized_options=None,
- serialized_start=38,
- serialized_end=349,
-)
-_sym_db.RegisterEnumDescriptor(_CODE)
-
-Code = enum_type_wrapper.EnumTypeWrapper(_CODE)
-OK = 0
-CANCELLED = 1
-UNKNOWN = 2
-INVALID_ARGUMENT = 3
-DEADLINE_EXCEEDED = 4
-NOT_FOUND = 5
-ALREADY_EXISTS = 6
-PERMISSION_DENIED = 7
-UNAUTHENTICATED = 16
-RESOURCE_EXHAUSTED = 8
-FAILED_PRECONDITION = 9
-ABORTED = 10
-OUT_OF_RANGE = 11
-UNIMPLEMENTED = 12
-INTERNAL = 13
-UNAVAILABLE = 14
-DATA_LOSS = 15
-
-
-DESCRIPTOR.enum_types_by_name['Code'] = _CODE
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-
-DESCRIPTOR._options = None
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/google/rpc/code_pb2_grpc.py b/buildstream/_protos/google/rpc/code_pb2_grpc.py
deleted file mode 100644
index a89435267..000000000
--- a/buildstream/_protos/google/rpc/code_pb2_grpc.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
diff --git a/buildstream/_protos/google/rpc/status.proto b/buildstream/_protos/google/rpc/status.proto
deleted file mode 100644
index 0839ee966..000000000
--- a/buildstream/_protos/google/rpc/status.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package google.rpc;
-
-import "google/protobuf/any.proto";
-
-option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";
-option java_multiple_files = true;
-option java_outer_classname = "StatusProto";
-option java_package = "com.google.rpc";
-option objc_class_prefix = "RPC";
-
-
-// The `Status` type defines a logical error model that is suitable for different
-// programming environments, including REST APIs and RPC APIs. It is used by
-// [gRPC](https://github.com/grpc). The error model is designed to be:
-//
-// - Simple to use and understand for most users
-// - Flexible enough to meet unexpected needs
-//
-// # Overview
-//
-// The `Status` message contains three pieces of data: error code, error message,
-// and error details. The error code should be an enum value of
-// [google.rpc.Code][google.rpc.Code], but it may accept additional error codes if needed. The
-// error message should be a developer-facing English message that helps
-// developers *understand* and *resolve* the error. If a localized user-facing
-// error message is needed, put the localized message in the error details or
-// localize it in the client. The optional error details may contain arbitrary
-// information about the error. There is a predefined set of error detail types
-// in the package `google.rpc` that can be used for common error conditions.
-//
-// # Language mapping
-//
-// The `Status` message is the logical representation of the error model, but it
-// is not necessarily the actual wire format. When the `Status` message is
-// exposed in different client libraries and different wire protocols, it can be
-// mapped differently. For example, it will likely be mapped to some exceptions
-// in Java, but more likely mapped to some error codes in C.
-//
-// # Other uses
-//
-// The error model and the `Status` message can be used in a variety of
-// environments, either with or without APIs, to provide a
-// consistent developer experience across different environments.
-//
-// Example uses of this error model include:
-//
-// - Partial errors. If a service needs to return partial errors to the client,
-// it may embed the `Status` in the normal response to indicate the partial
-// errors.
-//
-// - Workflow errors. A typical workflow has multiple steps. Each step may
-// have a `Status` message for error reporting.
-//
-// - Batch operations. If a client uses batch request and batch response, the
-// `Status` message should be used directly inside batch response, one for
-// each error sub-response.
-//
-// - Asynchronous operations. If an API call embeds asynchronous operation
-// results in its response, the status of those operations should be
-// represented directly using the `Status` message.
-//
-// - Logging. If some API errors are stored in logs, the message `Status` could
-// be used directly after any stripping needed for security/privacy reasons.
-message Status {
- // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
- int32 code = 1;
-
- // A developer-facing error message, which should be in English. Any
- // user-facing error message should be localized and sent in the
- // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
- string message = 2;
-
- // A list of messages that carry the error details. There is a common set of
- // message types for APIs to use.
- repeated google.protobuf.Any details = 3;
-}
diff --git a/buildstream/_protos/google/rpc/status_pb2.py b/buildstream/_protos/google/rpc/status_pb2.py
deleted file mode 100644
index 6c4772311..000000000
--- a/buildstream/_protos/google/rpc/status_pb2.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: google/rpc/status.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='google/rpc/status.proto',
- package='google.rpc',
- syntax='proto3',
- serialized_pb=_b('\n\x17google/rpc/status.proto\x12\ngoogle.rpc\x1a\x19google/protobuf/any.proto\"N\n\x06Status\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\x12%\n\x07\x64\x65tails\x18\x03 \x03(\x0b\x32\x14.google.protobuf.AnyB^\n\x0e\x63om.google.rpcB\x0bStatusProtoP\x01Z7google.golang.org/genproto/googleapis/rpc/status;status\xa2\x02\x03RPCb\x06proto3')
- ,
- dependencies=[google_dot_protobuf_dot_any__pb2.DESCRIPTOR,])
-
-
-
-
-_STATUS = _descriptor.Descriptor(
- name='Status',
- full_name='google.rpc.Status',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='code', full_name='google.rpc.Status.code', index=0,
- number=1, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='message', full_name='google.rpc.Status.message', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='details', full_name='google.rpc.Status.details', index=2,
- number=3, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=66,
- serialized_end=144,
-)
-
-_STATUS.fields_by_name['details'].message_type = google_dot_protobuf_dot_any__pb2._ANY
-DESCRIPTOR.message_types_by_name['Status'] = _STATUS
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-Status = _reflection.GeneratedProtocolMessageType('Status', (_message.Message,), dict(
- DESCRIPTOR = _STATUS,
- __module__ = 'google.rpc.status_pb2'
- # @@protoc_insertion_point(class_scope:google.rpc.Status)
- ))
-_sym_db.RegisterMessage(Status)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\016com.google.rpcB\013StatusProtoP\001Z7google.golang.org/genproto/googleapis/rpc/status;status\242\002\003RPC'))
-# @@protoc_insertion_point(module_scope)
diff --git a/buildstream/_protos/google/rpc/status_pb2_grpc.py b/buildstream/_protos/google/rpc/status_pb2_grpc.py
deleted file mode 100644
index a89435267..000000000
--- a/buildstream/_protos/google/rpc/status_pb2_grpc.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
diff --git a/buildstream/_scheduler/__init__.py b/buildstream/_scheduler/__init__.py
deleted file mode 100644
index d2f458fa5..000000000
--- a/buildstream/_scheduler/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-from .queues import Queue, QueueStatus
-
-from .queues.fetchqueue import FetchQueue
-from .queues.sourcepushqueue import SourcePushQueue
-from .queues.trackqueue import TrackQueue
-from .queues.buildqueue import BuildQueue
-from .queues.artifactpushqueue import ArtifactPushQueue
-from .queues.pullqueue import PullQueue
-
-from .scheduler import Scheduler, SchedStatus
-from .jobs import ElementJob, JobStatus
diff --git a/buildstream/_scheduler/jobs/__init__.py b/buildstream/_scheduler/jobs/__init__.py
deleted file mode 100644
index 3e213171a..000000000
--- a/buildstream/_scheduler/jobs/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2018 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 Maat <tristan.maat@codethink.co.uk>
-
-from .elementjob import ElementJob
-from .cachesizejob import CacheSizeJob
-from .cleanupjob import CleanupJob
-from .job import JobStatus
diff --git a/buildstream/_scheduler/jobs/cachesizejob.py b/buildstream/_scheduler/jobs/cachesizejob.py
deleted file mode 100644
index 5f27b7fc1..000000000
--- a/buildstream/_scheduler/jobs/cachesizejob.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright (C) 2018 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/>.
-#
-# Author:
-# Tristan Daniël Maat <tristan.maat@codethink.co.uk>
-#
-from .job import Job, JobStatus
-
-
-class CacheSizeJob(Job):
- def __init__(self, *args, complete_cb, **kwargs):
- super().__init__(*args, **kwargs)
- self._complete_cb = complete_cb
-
- context = self._scheduler.context
- self._casquota = context.get_casquota()
-
- def child_process(self):
- return self._casquota.compute_cache_size()
-
- def parent_complete(self, status, result):
- if status == JobStatus.OK:
- self._casquota.set_cache_size(result)
-
- if self._complete_cb:
- self._complete_cb(status, result)
-
- def child_process_data(self):
- return {}
diff --git a/buildstream/_scheduler/jobs/cleanupjob.py b/buildstream/_scheduler/jobs/cleanupjob.py
deleted file mode 100644
index 4764b30b3..000000000
--- a/buildstream/_scheduler/jobs/cleanupjob.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2018 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/>.
-#
-# Author:
-# Tristan Daniël Maat <tristan.maat@codethink.co.uk>
-#
-from .job import Job, JobStatus
-
-
-class CleanupJob(Job):
- def __init__(self, *args, complete_cb, **kwargs):
- super().__init__(*args, **kwargs)
- self._complete_cb = complete_cb
-
- context = self._scheduler.context
- self._casquota = context.get_casquota()
-
- def child_process(self):
- def progress():
- self.send_message('update-cache-size',
- self._casquota.get_cache_size())
- return self._casquota.clean(progress)
-
- def handle_message(self, message_type, message):
- # Update the cache size in the main process as we go,
- # this provides better feedback in the UI.
- if message_type == 'update-cache-size':
- self._casquota.set_cache_size(message, write_to_disk=False)
- return True
-
- return False
-
- def parent_complete(self, status, result):
- if status == JobStatus.OK:
- self._casquota.set_cache_size(result, write_to_disk=False)
-
- if self._complete_cb:
- self._complete_cb(status, result)
diff --git a/buildstream/_scheduler/jobs/elementjob.py b/buildstream/_scheduler/jobs/elementjob.py
deleted file mode 100644
index fb5d38e11..000000000
--- a/buildstream/_scheduler/jobs/elementjob.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright (C) 2018 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/>.
-#
-# Author:
-# Tristan Daniël Maat <tristan.maat@codethink.co.uk>
-#
-from ruamel import yaml
-
-from ..._message import Message, MessageType
-
-from .job import Job
-
-
-# ElementJob()
-#
-# A job to run an element's commands. When this job is spawned
-# `action_cb` will be called, and when it completes `complete_cb` will
-# be called.
-#
-# Args:
-# scheduler (Scheduler): The scheduler
-# action_name (str): The queue action name
-# max_retries (int): The maximum number of retries
-# action_cb (callable): The function to execute on the child
-# complete_cb (callable): The function to execute when the job completes
-# element (Element): The element to work on
-# kwargs: Remaining Job() constructor arguments
-#
-# Here is the calling signature of the action_cb:
-#
-# action_cb():
-#
-# This function will be called in the child task
-#
-# Args:
-# element (Element): The element passed to the Job() constructor
-#
-# Returns:
-# (object): Any abstract simple python object, including a string, int,
-# bool, list or dict, this must be a simple serializable object.
-#
-# Here is the calling signature of the complete_cb:
-#
-# complete_cb():
-#
-# This function will be called when the child task completes
-#
-# Args:
-# job (Job): The job object which completed
-# element (Element): The element passed to the Job() constructor
-# status (JobStatus): The status of whether the workload raised an exception
-# result (object): The deserialized object returned by the `action_cb`, or None
-# if `success` is False
-#
-class ElementJob(Job):
- def __init__(self, *args, element, queue, action_cb, complete_cb, **kwargs):
- super().__init__(*args, **kwargs)
- self.queue = queue
- self._element = element
- self._action_cb = action_cb # The action callable function
- self._complete_cb = complete_cb # The complete callable function
-
- # Set the task wide ID for logging purposes
- self.set_task_id(element._unique_id)
-
- @property
- def element(self):
- return self._element
-
- def child_process(self):
-
- # Print the element's environment at the beginning of any element's log file.
- #
- # This should probably be omitted for non-build tasks but it's harmless here
- elt_env = self._element.get_environment()
- env_dump = yaml.round_trip_dump(elt_env, default_flow_style=False, allow_unicode=True)
- self.message(MessageType.LOG,
- "Build environment for element {}".format(self._element.name),
- detail=env_dump)
-
- # Run the action
- return self._action_cb(self._element)
-
- def parent_complete(self, status, result):
- self._complete_cb(self, self._element, status, self._result)
-
- def message(self, message_type, message, **kwargs):
- args = dict(kwargs)
- args['scheduler'] = True
- self._scheduler.context.message(
- Message(self._element._unique_id,
- message_type,
- message,
- **args))
-
- def child_process_data(self):
- data = {}
-
- workspace = self._element._get_workspace()
- if workspace is not None:
- data['workspace'] = workspace.to_dict()
-
- return data
diff --git a/buildstream/_scheduler/jobs/job.py b/buildstream/_scheduler/jobs/job.py
deleted file mode 100644
index dd91d1634..000000000
--- a/buildstream/_scheduler/jobs/job.py
+++ /dev/null
@@ -1,682 +0,0 @@
-#
-# Copyright (C) 2018 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-# System imports
-import os
-import sys
-import signal
-import datetime
-import traceback
-import asyncio
-import multiprocessing
-
-# BuildStream toplevel imports
-from ..._exceptions import ImplError, BstError, set_last_task_error, SkipJob
-from ..._message import Message, MessageType, unconditional_messages
-from ... import _signals, utils
-
-# Return code values shutdown of job handling child processes
-#
-RC_OK = 0
-RC_FAIL = 1
-RC_PERM_FAIL = 2
-RC_SKIPPED = 3
-
-
-# JobStatus:
-#
-# The job completion status, passed back through the
-# complete callbacks.
-#
-class JobStatus():
- # Job succeeded
- OK = 0
-
- # A temporary BstError was raised
- FAIL = 1
-
- # A SkipJob was raised
- SKIPPED = 3
-
-
-# Used to distinguish between status messages and return values
-class _Envelope():
- def __init__(self, message_type, message):
- self.message_type = message_type
- self.message = message
-
-
-# Process class that doesn't call waitpid on its own.
-# This prevents conflicts with the asyncio child watcher.
-class Process(multiprocessing.Process):
- # pylint: disable=attribute-defined-outside-init
- def start(self):
- self._popen = self._Popen(self)
- self._sentinel = self._popen.sentinel
-
-
-# Job()
-#
-# The Job object represents a parallel task, when calling Job.spawn(),
-# the given `Job.child_process()` will be called in parallel to the
-# calling process, and `Job.parent_complete()` will be called with the
-# action result in the calling process when the job completes.
-#
-# Args:
-# scheduler (Scheduler): The scheduler
-# action_name (str): The queue action name
-# logfile (str): A template string that points to the logfile
-# that should be used - should contain {pid}.
-# max_retries (int): The maximum number of retries
-#
-class Job():
-
- def __init__(self, scheduler, action_name, logfile, *, max_retries=0):
-
- #
- # Public members
- #
- self.action_name = action_name # The action name for the Queue
- self.child_data = None # Data to be sent to the main process
-
- #
- # Private members
- #
- self._scheduler = scheduler # The scheduler
- self._queue = None # A message passing queue
- self._process = None # The Process object
- self._watcher = None # Child process watcher
- self._listening = False # Whether the parent is currently listening
- self._suspended = False # Whether this job is currently suspended
- self._max_retries = max_retries # Maximum number of automatic retries
- self._result = None # Return value of child action in the parent
- self._tries = 0 # Try count, for retryable jobs
- self._terminated = False # Whether this job has been explicitly terminated
-
- self._logfile = logfile
- self._task_id = None
-
- # spawn()
- #
- # Spawns the job.
- #
- def spawn(self):
-
- self._queue = multiprocessing.Queue()
-
- self._tries += 1
- self._parent_start_listening()
-
- # Spawn the process
- self._process = Process(target=self._child_action, args=[self._queue])
-
- # Block signals which are handled in the main process such that
- # the child process does not inherit the parent's state, but the main
- # process will be notified of any signal after we launch the child.
- #
- with _signals.blocked([signal.SIGINT, signal.SIGTSTP, signal.SIGTERM], ignore=False):
- self._process.start()
-
- # Wait for the child task to complete.
- #
- # This is a tricky part of python which doesnt seem to
- # make it to the online docs:
- #
- # o asyncio.get_child_watcher() will return a SafeChildWatcher() instance
- # which is the default type of watcher, and the instance belongs to the
- # "event loop policy" in use (so there is only one in the main process).
- #
- # o SafeChildWatcher() will register a SIGCHLD handler with the asyncio
- # loop, and will selectively reap any child pids which have been
- # terminated.
- #
- # o At registration time, the process will immediately be checked with
- # `os.waitpid()` and will be reaped immediately, before add_child_handler()
- # returns.
- #
- # The self._parent_child_completed callback passed here will normally
- # be called after the child task has been reaped with `os.waitpid()`, in
- # an event loop callback. Otherwise, if the job completes too fast, then
- # the callback is called immediately.
- #
- self._watcher = asyncio.get_child_watcher()
- self._watcher.add_child_handler(self._process.pid, self._parent_child_completed)
-
- # terminate()
- #
- # Politely request that an ongoing job terminate soon.
- #
- # This will send a SIGTERM signal to the Job process.
- #
- def terminate(self):
-
- # First resume the job if it's suspended
- self.resume(silent=True)
-
- self.message(MessageType.STATUS, "{} terminating".format(self.action_name))
-
- # Make sure there is no garbage on the queue
- self._parent_stop_listening()
-
- # Terminate the process using multiprocessing API pathway
- self._process.terminate()
-
- self._terminated = True
-
- # get_terminated()
- #
- # Check if a job has been terminated.
- #
- # Returns:
- # (bool): True in the main process if Job.terminate() was called.
- #
- def get_terminated(self):
- return self._terminated
-
- # terminate_wait()
- #
- # Wait for terminated jobs to complete
- #
- # Args:
- # timeout (float): Seconds to wait
- #
- # Returns:
- # (bool): True if the process terminated cleanly, otherwise False
- #
- def terminate_wait(self, timeout):
-
- # Join the child process after sending SIGTERM
- self._process.join(timeout)
- return self._process.exitcode is not None
-
- # kill()
- #
- # Forcefully kill the process, and any children it might have.
- #
- def kill(self):
- # Force kill
- self.message(MessageType.WARN,
- "{} did not terminate gracefully, killing".format(self.action_name))
- utils._kill_process_tree(self._process.pid)
-
- # suspend()
- #
- # Suspend this job.
- #
- def suspend(self):
- if not self._suspended:
- self.message(MessageType.STATUS,
- "{} suspending".format(self.action_name))
-
- try:
- # Use SIGTSTP so that child processes may handle and propagate
- # it to processes they spawn that become session leaders
- os.kill(self._process.pid, signal.SIGTSTP)
-
- # For some reason we receive exactly one suspend event for every
- # SIGTSTP we send to the child fork(), even though the child forks
- # are setsid(). We keep a count of these so we can ignore them
- # in our event loop suspend_event()
- self._scheduler.internal_stops += 1
- self._suspended = True
- except ProcessLookupError:
- # ignore, process has already exited
- pass
-
- # resume()
- #
- # Resume this suspended job.
- #
- def resume(self, silent=False):
- if self._suspended:
- if not silent and not self._scheduler.terminated:
- self.message(MessageType.STATUS,
- "{} resuming".format(self.action_name))
-
- os.kill(self._process.pid, signal.SIGCONT)
- self._suspended = False
-
- # set_task_id()
- #
- # This is called by Job subclasses to set a plugin ID
- # associated with the task at large (if any element is related
- # to the task).
- #
- # The task ID helps keep messages in the frontend coherent
- # in the case that multiple plugins log in the context of
- # a single task (e.g. running integration commands should appear
- # in the frontend for the element being built, not the element
- # running the integration commands).
- #
- # Args:
- # task_id (int): The plugin identifier for this task
- #
- def set_task_id(self, task_id):
- self._task_id = task_id
-
- # send_message()
- #
- # To be called from inside Job.child_process() implementations
- # to send messages to the main process during processing.
- #
- # These messages will be processed by the class's Job.handle_message()
- # implementation.
- #
- def send_message(self, message_type, message):
- self._queue.put(_Envelope(message_type, message))
-
- #######################################################
- # Abstract Methods #
- #######################################################
-
- # handle_message()
- #
- # Handle a custom message. This will be called in the main process in
- # response to any messages sent to the main proces using the
- # Job.send_message() API from inside a Job.child_process() implementation
- #
- # Args:
- # message_type (str): A string to identify the message type
- # message (any): A simple serializable object
- #
- # Returns:
- # (bool): Should return a truthy value if message_type is handled.
- #
- def handle_message(self, message_type, message):
- return False
-
- # parent_complete()
- #
- # This will be executed after the job finishes, and is expected to
- # pass the result to the main thread.
- #
- # Args:
- # status (JobStatus): The job exit status
- # result (any): The result returned by child_process().
- #
- def parent_complete(self, status, result):
- raise ImplError("Job '{kind}' does not implement parent_complete()"
- .format(kind=type(self).__name__))
-
- # child_process()
- #
- # This will be executed after fork(), and is intended to perform
- # the job's task.
- #
- # Returns:
- # (any): A (simple!) object to be returned to the main thread
- # as the result.
- #
- def child_process(self):
- raise ImplError("Job '{kind}' does not implement child_process()"
- .format(kind=type(self).__name__))
-
- # message():
- #
- # Logs a message, this will be logged in the task's logfile and
- # conditionally also be sent to the frontend.
- #
- # Args:
- # message_type (MessageType): The type of message to send
- # message (str): The message
- # kwargs: Remaining Message() constructor arguments
- #
- def message(self, message_type, message, **kwargs):
- args = dict(kwargs)
- args['scheduler'] = True
- self._scheduler.context.message(Message(None, message_type, message, **args))
-
- # child_process_data()
- #
- # Abstract method to retrieve additional data that should be
- # returned to the parent process. Note that the job result is
- # retrieved independently.
- #
- # Values can later be retrieved in Job.child_data.
- #
- # Returns:
- # (dict) A dict containing values to be reported to the main process
- #
- def child_process_data(self):
- return {}
-
- #######################################################
- # Local Private Methods #
- #######################################################
- #
- # Methods prefixed with the word 'child' take place in the child process
- #
- # Methods prefixed with the word 'parent' take place in the parent process
- #
- # Other methods can be called in both child or parent processes
- #
- #######################################################
-
- # _child_action()
- #
- # Perform the action in the child process, this calls the action_cb.
- #
- # Args:
- # queue (multiprocessing.Queue): The message queue for IPC
- #
- def _child_action(self, queue):
-
- # This avoids some SIGTSTP signals from grandchildren
- # getting propagated up to the master process
- os.setsid()
-
- # First set back to the default signal handlers for the signals
- # we handle, and then clear their blocked state.
- #
- signal_list = [signal.SIGTSTP, signal.SIGTERM]
- for sig in signal_list:
- signal.signal(sig, signal.SIG_DFL)
- signal.pthread_sigmask(signal.SIG_UNBLOCK, signal_list)
-
- # Assign the queue we passed across the process boundaries
- #
- # Set the global message handler in this child
- # process to forward messages to the parent process
- self._queue = queue
- self._scheduler.context.set_message_handler(self._child_message_handler)
-
- starttime = datetime.datetime.now()
- stopped_time = None
-
- def stop_time():
- nonlocal stopped_time
- stopped_time = datetime.datetime.now()
-
- def resume_time():
- nonlocal stopped_time
- nonlocal starttime
- starttime += (datetime.datetime.now() - stopped_time)
-
- # Time, log and and run the action function
- #
- with _signals.suspendable(stop_time, resume_time), \
- self._scheduler.context.recorded_messages(self._logfile) as filename:
-
- self.message(MessageType.START, self.action_name, logfile=filename)
-
- try:
- # Try the task action
- result = self.child_process() # pylint: disable=assignment-from-no-return
- except SkipJob as e:
- elapsed = datetime.datetime.now() - starttime
- self.message(MessageType.SKIPPED, str(e),
- elapsed=elapsed, logfile=filename)
-
- # Alert parent of skip by return code
- self._child_shutdown(RC_SKIPPED)
- except BstError as e:
- elapsed = datetime.datetime.now() - starttime
- retry_flag = e.temporary
-
- if retry_flag and (self._tries <= self._max_retries):
- self.message(MessageType.FAIL,
- "Try #{} failed, retrying".format(self._tries),
- elapsed=elapsed, logfile=filename)
- else:
- self.message(MessageType.FAIL, str(e),
- elapsed=elapsed, detail=e.detail,
- logfile=filename, sandbox=e.sandbox)
-
- self._queue.put(_Envelope('child_data', self.child_process_data()))
-
- # Report the exception to the parent (for internal testing purposes)
- self._child_send_error(e)
-
- # Set return code based on whether or not the error was temporary.
- #
- self._child_shutdown(RC_FAIL if retry_flag else RC_PERM_FAIL)
-
- except Exception as e: # pylint: disable=broad-except
-
- # If an unhandled (not normalized to BstError) occurs, that's a bug,
- # send the traceback and formatted exception back to the frontend
- # and print it to the log file.
- #
- elapsed = datetime.datetime.now() - starttime
- detail = "An unhandled exception occured:\n\n{}".format(traceback.format_exc())
-
- self.message(MessageType.BUG, self.action_name,
- elapsed=elapsed, detail=detail,
- logfile=filename)
- # Unhandled exceptions should permenantly fail
- self._child_shutdown(RC_PERM_FAIL)
-
- else:
- # No exception occurred in the action
- self._queue.put(_Envelope('child_data', self.child_process_data()))
- self._child_send_result(result)
-
- elapsed = datetime.datetime.now() - starttime
- self.message(MessageType.SUCCESS, self.action_name, elapsed=elapsed,
- logfile=filename)
-
- # Shutdown needs to stay outside of the above context manager,
- # make sure we dont try to handle SIGTERM while the process
- # is already busy in sys.exit()
- self._child_shutdown(RC_OK)
-
- # _child_send_error()
- #
- # Sends an error to the main process through the message queue
- #
- # Args:
- # e (Exception): The error to send
- #
- def _child_send_error(self, e):
- domain = None
- reason = None
-
- if isinstance(e, BstError):
- domain = e.domain
- reason = e.reason
-
- envelope = _Envelope('error', {
- 'domain': domain,
- 'reason': reason
- })
- self._queue.put(envelope)
-
- # _child_send_result()
- #
- # Sends the serialized result to the main process through the message queue
- #
- # Args:
- # result (object): A simple serializable object, or None
- #
- # Note: If None is passed here, nothing needs to be sent, the
- # result member in the parent process will simply remain None.
- #
- def _child_send_result(self, result):
- if result is not None:
- envelope = _Envelope('result', result)
- self._queue.put(envelope)
-
- # _child_shutdown()
- #
- # Shuts down the child process by cleaning up and exiting the process
- #
- # Args:
- # exit_code (int): The exit code to exit with
- #
- def _child_shutdown(self, exit_code):
- self._queue.close()
- sys.exit(exit_code)
-
- # _child_message_handler()
- #
- # A Context delegate for handling messages, this replaces the
- # frontend's main message handler in the context of a child task
- # and performs local logging to the local log file before sending
- # the message back to the parent process for further propagation.
- #
- # Args:
- # message (Message): The message to log
- # context (Context): The context object delegating this message
- #
- def _child_message_handler(self, message, context):
-
- message.action_name = self.action_name
- message.task_id = self._task_id
-
- # Send to frontend if appropriate
- if context.silent_messages() and (message.message_type not in unconditional_messages):
- return
-
- if message.message_type == MessageType.LOG:
- return
-
- self._queue.put(_Envelope('message', message))
-
- # _parent_shutdown()
- #
- # Shuts down the Job on the parent side by reading any remaining
- # messages on the message queue and cleaning up any resources.
- #
- def _parent_shutdown(self):
- # Make sure we've read everything we need and then stop listening
- self._parent_process_queue()
- self._parent_stop_listening()
-
- # _parent_child_completed()
- #
- # Called in the main process courtesy of asyncio's ChildWatcher.add_child_handler()
- #
- # Args:
- # pid (int): The PID of the child which completed
- # returncode (int): The return code of the child process
- #
- def _parent_child_completed(self, pid, returncode):
- self._parent_shutdown()
-
- # We don't want to retry if we got OK or a permanent fail.
- retry_flag = returncode == RC_FAIL
-
- if retry_flag and (self._tries <= self._max_retries) and not self._scheduler.terminated:
- self.spawn()
- return
-
- # Resolve the outward facing overall job completion status
- #
- if returncode == RC_OK:
- status = JobStatus.OK
- elif returncode == RC_SKIPPED:
- status = JobStatus.SKIPPED
- elif returncode in (RC_FAIL, RC_PERM_FAIL):
- status = JobStatus.FAIL
- else:
- status = JobStatus.FAIL
-
- self.parent_complete(status, self._result)
- self._scheduler.job_completed(self, status)
-
- # Force the deletion of the queue and process objects to try and clean up FDs
- self._queue = self._process = None
-
- # _parent_process_envelope()
- #
- # Processes a message Envelope deserialized form the message queue.
- #
- # this will have the side effect of assigning some local state
- # on the Job in the parent process for later inspection when the
- # child process completes.
- #
- # Args:
- # envelope (Envelope): The message envelope
- #
- def _parent_process_envelope(self, envelope):
- if not self._listening:
- return
-
- if envelope.message_type == 'message':
- # Propagate received messages from children
- # back through the context.
- self._scheduler.context.message(envelope.message)
- elif envelope.message_type == 'error':
- # For regression tests only, save the last error domain / reason
- # reported from a child task in the main process, this global state
- # is currently managed in _exceptions.py
- set_last_task_error(envelope.message['domain'],
- envelope.message['reason'])
- elif envelope.message_type == 'result':
- assert self._result is None
- self._result = envelope.message
- elif envelope.message_type == 'child_data':
- # If we retry a job, we assign a new value to this
- self.child_data = envelope.message
-
- # Try Job subclass specific messages now
- elif not self.handle_message(envelope.message_type,
- envelope.message):
- assert 0, "Unhandled message type '{}': {}" \
- .format(envelope.message_type, envelope.message)
-
- # _parent_process_queue()
- #
- # Reads back message envelopes from the message queue
- # in the parent process.
- #
- def _parent_process_queue(self):
- while not self._queue.empty():
- envelope = self._queue.get_nowait()
- self._parent_process_envelope(envelope)
-
- # _parent_recv()
- #
- # A callback to handle I/O events from the message
- # queue file descriptor in the main process message loop
- #
- def _parent_recv(self, *args):
- self._parent_process_queue()
-
- # _parent_start_listening()
- #
- # Starts listening on the message queue
- #
- def _parent_start_listening(self):
- # Warning: Platform specific code up ahead
- #
- # The multiprocessing.Queue object does not tell us how
- # to receive io events in the receiving process, so we
- # need to sneak in and get its file descriptor.
- #
- # The _reader member of the Queue is currently private
- # but well known, perhaps it will become public:
- #
- # http://bugs.python.org/issue3831
- #
- if not self._listening:
- self._scheduler.loop.add_reader(
- self._queue._reader.fileno(), self._parent_recv)
- self._listening = True
-
- # _parent_stop_listening()
- #
- # Stops listening on the message queue
- #
- def _parent_stop_listening(self):
- if self._listening:
- self._scheduler.loop.remove_reader(self._queue._reader.fileno())
- self._listening = False
diff --git a/buildstream/_scheduler/queues/__init__.py b/buildstream/_scheduler/queues/__init__.py
deleted file mode 100644
index 3b2293919..000000000
--- a/buildstream/_scheduler/queues/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .queue import Queue, QueueStatus
diff --git a/buildstream/_scheduler/queues/artifactpushqueue.py b/buildstream/_scheduler/queues/artifactpushqueue.py
deleted file mode 100644
index b861d4fc7..000000000
--- a/buildstream/_scheduler/queues/artifactpushqueue.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Copyright (C) 2016 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-# Local imports
-from . import Queue, QueueStatus
-from ..resources import ResourceType
-from ..._exceptions import SkipJob
-
-
-# A queue which pushes element artifacts
-#
-class ArtifactPushQueue(Queue):
-
- action_name = "Push"
- complete_name = "Pushed"
- resources = [ResourceType.UPLOAD]
-
- def process(self, element):
- # returns whether an artifact was uploaded or not
- if not element._push():
- raise SkipJob(self.action_name)
-
- def status(self, element):
- if element._skip_push():
- return QueueStatus.SKIP
-
- return QueueStatus.READY
diff --git a/buildstream/_scheduler/queues/buildqueue.py b/buildstream/_scheduler/queues/buildqueue.py
deleted file mode 100644
index aa489f381..000000000
--- a/buildstream/_scheduler/queues/buildqueue.py
+++ /dev/null
@@ -1,117 +0,0 @@
-#
-# Copyright (C) 2016 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-from datetime import timedelta
-
-from . import Queue, QueueStatus
-from ..jobs import ElementJob, JobStatus
-from ..resources import ResourceType
-from ..._message import MessageType
-
-
-# A queue which assembles elements
-#
-class BuildQueue(Queue):
-
- action_name = "Build"
- complete_name = "Built"
- resources = [ResourceType.PROCESS, ResourceType.CACHE]
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._tried = set()
-
- def enqueue(self, elts):
- to_queue = []
-
- for element in elts:
- if not element._cached_failure() or element in self._tried:
- to_queue.append(element)
- continue
-
- # XXX: Fix this, See https://mail.gnome.org/archives/buildstream-list/2018-September/msg00029.html
- # Bypass queue processing entirely the first time it's tried.
- self._tried.add(element)
- _, description, detail = element._get_build_result()
- logfile = element._get_build_log()
- self._message(element, MessageType.FAIL, description,
- detail=detail, action_name=self.action_name,
- elapsed=timedelta(seconds=0),
- logfile=logfile)
- job = ElementJob(self._scheduler, self.action_name,
- logfile, element=element, queue=self,
- action_cb=self.process,
- complete_cb=self._job_done,
- max_retries=self._max_retries)
- self._done_queue.append(element)
- self.failed_elements.append(element)
- self._scheduler._job_complete_callback(job, False)
-
- return super().enqueue(to_queue)
-
- def process(self, element):
- return element._assemble()
-
- def status(self, element):
- if not element._is_required():
- # Artifact is not currently required but it may be requested later.
- # Keep it in the queue.
- return QueueStatus.WAIT
-
- if element._cached_success():
- return QueueStatus.SKIP
-
- if not element._buildable():
- return QueueStatus.WAIT
-
- return QueueStatus.READY
-
- def _check_cache_size(self, job, element, artifact_size):
-
- # After completing a build job, add the artifact size
- # as returned from Element._assemble() to the estimated
- # artifact cache size
- #
- context = self._scheduler.context
- artifacts = context.artifactcache
-
- artifacts.add_artifact_size(artifact_size)
-
- # If the estimated size outgrows the quota, ask the scheduler
- # to queue a job to actually check the real cache size.
- #
- if artifacts.full():
- self._scheduler.check_cache_size()
-
- def done(self, job, element, result, status):
-
- # Inform element in main process that assembly is done
- element._assemble_done()
-
- # This has to be done after _assemble_done, such that the
- # element may register its cache key as required
- #
- # FIXME: Element._assemble() does not report both the failure state and the
- # size of the newly cached failed artifact, so we can only adjust the
- # artifact cache size for a successful build even though we know a
- # failed build also grows the artifact cache size.
- #
- if status == JobStatus.OK:
- self._check_cache_size(job, element, result)
diff --git a/buildstream/_scheduler/queues/fetchqueue.py b/buildstream/_scheduler/queues/fetchqueue.py
deleted file mode 100644
index 9edeebb1d..000000000
--- a/buildstream/_scheduler/queues/fetchqueue.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#
-# Copyright (C) 2016 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-# BuildStream toplevel imports
-from ... import Consistency
-
-# Local imports
-from . import Queue, QueueStatus
-from ..resources import ResourceType
-from ..jobs import JobStatus
-
-
-# A queue which fetches element sources
-#
-class FetchQueue(Queue):
-
- action_name = "Fetch"
- complete_name = "Fetched"
- resources = [ResourceType.DOWNLOAD]
-
- def __init__(self, scheduler, skip_cached=False, fetch_original=False):
- super().__init__(scheduler)
-
- self._skip_cached = skip_cached
- self._fetch_original = fetch_original
-
- def process(self, element):
- element._fetch(fetch_original=self._fetch_original)
-
- def status(self, element):
- if not element._is_required():
- # Artifact is not currently required but it may be requested later.
- # Keep it in the queue.
- return QueueStatus.WAIT
-
- # Optionally skip elements that are already in the artifact cache
- if self._skip_cached:
- if not element._can_query_cache():
- return QueueStatus.WAIT
-
- if element._cached():
- return QueueStatus.SKIP
-
- # This will automatically skip elements which
- # have no sources.
-
- if not element._should_fetch(self._fetch_original):
- return QueueStatus.SKIP
-
- return QueueStatus.READY
-
- def done(self, _, element, result, status):
-
- if status == JobStatus.FAIL:
- return
-
- element._fetch_done()
-
- # Successful fetch, we must be CACHED or in the sourcecache
- if self._fetch_original:
- assert element._get_consistency() == Consistency.CACHED
- else:
- assert element._source_cached()
diff --git a/buildstream/_scheduler/queues/pullqueue.py b/buildstream/_scheduler/queues/pullqueue.py
deleted file mode 100644
index 013ee6489..000000000
--- a/buildstream/_scheduler/queues/pullqueue.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Copyright (C) 2016 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-# Local imports
-from . import Queue, QueueStatus
-from ..resources import ResourceType
-from ..jobs import JobStatus
-from ..._exceptions import SkipJob
-
-
-# A queue which pulls element artifacts
-#
-class PullQueue(Queue):
-
- action_name = "Pull"
- complete_name = "Pulled"
- resources = [ResourceType.DOWNLOAD, ResourceType.CACHE]
-
- def process(self, element):
- # returns whether an artifact was downloaded or not
- if not element._pull():
- raise SkipJob(self.action_name)
-
- def status(self, element):
- if not element._is_required():
- # Artifact is not currently required but it may be requested later.
- # Keep it in the queue.
- return QueueStatus.WAIT
-
- if not element._can_query_cache():
- return QueueStatus.WAIT
-
- if element._pull_pending():
- return QueueStatus.READY
- else:
- return QueueStatus.SKIP
-
- def done(self, _, element, result, status):
-
- if status == JobStatus.FAIL:
- return
-
- element._pull_done()
-
- # Build jobs will check the "approximate" size first. Since we
- # do not get an artifact size from pull jobs, we have to
- # actually check the cache size.
- if status == JobStatus.OK:
- self._scheduler.check_cache_size()
diff --git a/buildstream/_scheduler/queues/queue.py b/buildstream/_scheduler/queues/queue.py
deleted file mode 100644
index 1efcffc16..000000000
--- a/buildstream/_scheduler/queues/queue.py
+++ /dev/null
@@ -1,328 +0,0 @@
-#
-# Copyright (C) 2016 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-# System imports
-import os
-from collections import deque
-from enum import Enum
-import traceback
-
-# Local imports
-from ..jobs import ElementJob, JobStatus
-from ..resources import ResourceType
-
-# BuildStream toplevel imports
-from ..._exceptions import BstError, set_last_task_error
-from ..._message import Message, MessageType
-
-
-# Queue status for a given element
-#
-#
-class QueueStatus(Enum):
- # The element is waiting for dependencies.
- WAIT = 1
-
- # The element can skip this queue.
- SKIP = 2
-
- # The element is ready for processing in this queue.
- READY = 3
-
-
-# Queue()
-#
-# Args:
-# scheduler (Scheduler): The Scheduler
-#
-class Queue():
-
- # These should be overridden on class data of of concrete Queue implementations
- action_name = None
- complete_name = None
- resources = [] # Resources this queues' jobs want
-
- def __init__(self, scheduler):
-
- #
- # Public members
- #
- self.failed_elements = [] # List of failed elements, for the frontend
- self.processed_elements = [] # List of processed elements, for the frontend
- self.skipped_elements = [] # List of skipped elements, for the frontend
-
- #
- # Private members
- #
- self._scheduler = scheduler
- self._resources = scheduler.resources # Shared resource pool
- self._wait_queue = deque() # Ready / Waiting elements
- self._done_queue = deque() # Processed / Skipped elements
- self._max_retries = 0
-
- # Assert the subclass has setup class data
- assert self.action_name is not None
- assert self.complete_name is not None
-
- if ResourceType.UPLOAD in self.resources or ResourceType.DOWNLOAD in self.resources:
- self._max_retries = scheduler.context.sched_network_retries
-
- #####################################################
- # Abstract Methods for Queue implementations #
- #####################################################
-
- # process()
- #
- # Abstract method for processing an element
- #
- # Args:
- # element (Element): An element to process
- #
- # Returns:
- # (any): An optional something to be returned
- # for every element successfully processed
- #
- #
- def process(self, element):
- pass
-
- # status()
- #
- # Abstract method for reporting the status of an element.
- #
- # Args:
- # element (Element): An element to process
- #
- # Returns:
- # (QueueStatus): The element status
- #
- def status(self, element):
- return QueueStatus.READY
-
- # done()
- #
- # Abstract method for handling a successful job completion.
- #
- # Args:
- # job (Job): The job which completed processing
- # element (Element): The element which completed processing
- # result (any): The return value of the process() implementation
- # status (JobStatus): The return status of the Job
- #
- def done(self, job, element, result, status):
- pass
-
- #####################################################
- # Scheduler / Pipeline facing APIs #
- #####################################################
-
- # enqueue()
- #
- # Enqueues some elements
- #
- # Args:
- # elts (list): A list of Elements
- #
- def enqueue(self, elts):
- if not elts:
- return
-
- # Place skipped elements on the done queue right away.
- #
- # The remaining ready and waiting elements must remain in the
- # same queue, and ready status must be determined at the moment
- # which the scheduler is asking for the next job.
- #
- skip = [elt for elt in elts if self.status(elt) == QueueStatus.SKIP]
- wait = [elt for elt in elts if elt not in skip]
-
- self.skipped_elements.extend(skip) # Public record of skipped elements
- self._done_queue.extend(skip) # Elements to be processed
- self._wait_queue.extend(wait) # Elements eligible to be dequeued
-
- # dequeue()
- #
- # A generator which dequeues the elements which
- # are ready to exit the queue.
- #
- # Yields:
- # (Element): Elements being dequeued
- #
- def dequeue(self):
- while self._done_queue:
- yield self._done_queue.popleft()
-
- # dequeue_ready()
- #
- # Reports whether any elements can be promoted to other queues
- #
- # Returns:
- # (bool): Whether there are elements ready
- #
- def dequeue_ready(self):
- return any(self._done_queue)
-
- # harvest_jobs()
- #
- # Process elements in the queue, moving elements which were enqueued
- # into the dequeue pool, and creating as many jobs for which resources
- # can be reserved.
- #
- # Returns:
- # ([Job]): A list of jobs which can be run now
- #
- def harvest_jobs(self):
- unready = []
- ready = []
-
- while self._wait_queue:
- if not self._resources.reserve(self.resources, peek=True):
- break
-
- element = self._wait_queue.popleft()
- status = self.status(element)
-
- if status == QueueStatus.WAIT:
- unready.append(element)
- elif status == QueueStatus.SKIP:
- self._done_queue.append(element)
- self.skipped_elements.append(element)
- else:
- reserved = self._resources.reserve(self.resources)
- assert reserved
- ready.append(element)
-
- self._wait_queue.extendleft(unready)
-
- return [
- ElementJob(self._scheduler, self.action_name,
- self._element_log_path(element),
- element=element, queue=self,
- action_cb=self.process,
- complete_cb=self._job_done,
- max_retries=self._max_retries)
- for element in ready
- ]
-
- #####################################################
- # Private Methods #
- #####################################################
-
- # _update_workspaces()
- #
- # Updates and possibly saves the workspaces in the
- # main data model in the main process after a job completes.
- #
- # Args:
- # element (Element): The element which completed
- # job (Job): The job which completed
- #
- def _update_workspaces(self, element, job):
- workspace_dict = None
- if job.child_data:
- workspace_dict = job.child_data.get('workspace', None)
-
- # Handle any workspace modifications now
- #
- if workspace_dict:
- context = element._get_context()
- workspaces = context.get_workspaces()
- if workspaces.update_workspace(element._get_full_name(), workspace_dict):
- try:
- workspaces.save_config()
- except BstError as e:
- self._message(element, MessageType.ERROR, "Error saving workspaces", detail=str(e))
- except Exception as e: # pylint: disable=broad-except
- self._message(element, MessageType.BUG,
- "Unhandled exception while saving workspaces",
- detail=traceback.format_exc())
-
- # _job_done()
- #
- # A callback reported by the Job() when a job completes
- #
- # This will call the Queue implementation specific Queue.done()
- # implementation and trigger the scheduler to reschedule.
- #
- # See the Job object for an explanation of the call signature
- #
- def _job_done(self, job, element, status, result):
-
- # Now release the resources we reserved
- #
- self._resources.release(self.resources)
-
- # Update values that need to be synchronized in the main task
- # before calling any queue implementation
- self._update_workspaces(element, job)
-
- # Give the result of the job to the Queue implementor,
- # and determine if it should be considered as processed
- # or skipped.
- try:
- self.done(job, element, result, status)
- except BstError as e:
-
- # Report error and mark as failed
- #
- self._message(element, MessageType.ERROR, "Post processing error", detail=str(e))
- self.failed_elements.append(element)
-
- # Treat this as a task error as it's related to a task
- # even though it did not occur in the task context
- #
- # This just allows us stronger testing capability
- #
- set_last_task_error(e.domain, e.reason)
-
- except Exception as e: # pylint: disable=broad-except
-
- # Report unhandled exceptions and mark as failed
- #
- self._message(element, MessageType.BUG,
- "Unhandled exception in post processing",
- detail=traceback.format_exc())
- self.failed_elements.append(element)
- else:
- # All elements get placed on the done queue for later processing.
- self._done_queue.append(element)
-
- # These lists are for bookkeeping purposes for the UI and logging.
- if status == JobStatus.SKIPPED or job.get_terminated():
- self.skipped_elements.append(element)
- elif status == JobStatus.OK:
- self.processed_elements.append(element)
- else:
- self.failed_elements.append(element)
-
- # Convenience wrapper for Queue implementations to send
- # a message for the element they are processing
- def _message(self, element, message_type, brief, **kwargs):
- context = element._get_context()
- message = Message(element._unique_id, message_type, brief, **kwargs)
- context.message(message)
-
- def _element_log_path(self, element):
- project = element._get_project()
- key = element._get_display_key()[1]
- action = self.action_name.lower()
- logfile = "{key}-{action}".format(key=key, action=action)
-
- return os.path.join(project.name, element.normal_name, logfile)
diff --git a/buildstream/_scheduler/queues/sourcepushqueue.py b/buildstream/_scheduler/queues/sourcepushqueue.py
deleted file mode 100644
index c38460e6a..000000000
--- a/buildstream/_scheduler/queues/sourcepushqueue.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Copyright (C) 2019 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:
-# Raoul Hidalgo Charman <raoul.hidalgocharman@codethink.co.uk>
-
-from . import Queue, QueueStatus
-from ..resources import ResourceType
-from ..._exceptions import SkipJob
-
-
-# A queue which pushes staged sources
-#
-class SourcePushQueue(Queue):
-
- action_name = "Src-push"
- complete_name = "Sources pushed"
- resources = [ResourceType.UPLOAD]
-
- def process(self, element):
- # Returns whether a source was pushed or not
- if not element._source_push():
- raise SkipJob(self.action_name)
-
- def status(self, element):
- if element._skip_source_push():
- return QueueStatus.SKIP
-
- return QueueStatus.READY
diff --git a/buildstream/_scheduler/queues/trackqueue.py b/buildstream/_scheduler/queues/trackqueue.py
deleted file mode 100644
index 72a79a532..000000000
--- a/buildstream/_scheduler/queues/trackqueue.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Copyright (C) 2016 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-# BuildStream toplevel imports
-from ...plugin import Plugin
-
-# Local imports
-from . import Queue, QueueStatus
-from ..resources import ResourceType
-from ..jobs import JobStatus
-
-
-# A queue which tracks sources
-#
-class TrackQueue(Queue):
-
- action_name = "Track"
- complete_name = "Tracked"
- resources = [ResourceType.DOWNLOAD]
-
- def process(self, element):
- return element._track()
-
- def status(self, element):
- # We can skip elements entirely if they have no sources.
- if not list(element.sources()):
-
- # But we still have to mark them as tracked
- element._tracking_done()
- return QueueStatus.SKIP
-
- return QueueStatus.READY
-
- def done(self, _, element, result, status):
-
- if status == JobStatus.FAIL:
- return
-
- # Set the new refs in the main process one by one as they complete,
- # writing to bst files this time
- for unique_id, new_ref in result:
- source = Plugin._lookup(unique_id)
- source._set_ref(new_ref, save=True)
-
- element._tracking_done()
diff --git a/buildstream/_scheduler/resources.py b/buildstream/_scheduler/resources.py
deleted file mode 100644
index 73bf66b4a..000000000
--- a/buildstream/_scheduler/resources.py
+++ /dev/null
@@ -1,166 +0,0 @@
-class ResourceType():
- CACHE = 0
- DOWNLOAD = 1
- PROCESS = 2
- UPLOAD = 3
-
-
-class Resources():
- def __init__(self, num_builders, num_fetchers, num_pushers):
- self._max_resources = {
- ResourceType.CACHE: 0,
- ResourceType.DOWNLOAD: num_fetchers,
- ResourceType.PROCESS: num_builders,
- ResourceType.UPLOAD: num_pushers
- }
-
- # Resources jobs are currently using.
- self._used_resources = {
- ResourceType.CACHE: 0,
- ResourceType.DOWNLOAD: 0,
- ResourceType.PROCESS: 0,
- ResourceType.UPLOAD: 0
- }
-
- # Resources jobs currently want exclusive access to. The set
- # of jobs that have asked for exclusive access is the value -
- # this is so that we can avoid scheduling any other jobs until
- # *all* exclusive jobs that "register interest" have finished
- # - which avoids starving them of scheduling time.
- self._exclusive_resources = {
- ResourceType.CACHE: set(),
- ResourceType.DOWNLOAD: set(),
- ResourceType.PROCESS: set(),
- ResourceType.UPLOAD: set()
- }
-
- # reserve()
- #
- # Reserves a set of resources
- #
- # Args:
- # resources (set): A set of ResourceTypes
- # exclusive (set): Another set of ResourceTypes
- # peek (bool): Whether to only peek at whether the resource is available
- #
- # Returns:
- # (bool): True if the resources could be reserved
- #
- def reserve(self, resources, exclusive=None, *, peek=False):
- if exclusive is None:
- exclusive = set()
-
- resources = set(resources)
- exclusive = set(exclusive)
-
- # First, we check if the job wants to access a resource that
- # another job wants exclusive access to. If so, it cannot be
- # scheduled.
- #
- # Note that if *both* jobs want this exclusively, we don't
- # fail yet.
- #
- # FIXME: I *think* we can deadlock if two jobs want disjoint
- # sets of exclusive and non-exclusive resources. This
- # is currently not possible, but may be worth thinking
- # about.
- #
- for resource in resources - exclusive:
-
- # If our job wants this resource exclusively, we never
- # check this, so we can get away with not (temporarily)
- # removing it from the set.
- if self._exclusive_resources[resource]:
- return False
-
- # Now we check if anything is currently using any resources
- # this job wants exclusively. If so, the job cannot be
- # scheduled.
- #
- # Since jobs that use a resource exclusively are also using
- # it, this means only one exclusive job can ever be scheduled
- # at a time, despite being allowed to be part of the exclusive
- # set.
- #
- for resource in exclusive:
- if self._used_resources[resource] != 0:
- return False
-
- # Finally, we check if we have enough of each resource
- # available. If we don't have enough, the job cannot be
- # scheduled.
- for resource in resources:
- if (self._max_resources[resource] > 0 and
- self._used_resources[resource] >= self._max_resources[resource]):
- return False
-
- # Now we register the fact that our job is using the resources
- # it asked for, and tell the scheduler that it is allowed to
- # continue.
- if not peek:
- for resource in resources:
- self._used_resources[resource] += 1
-
- return True
-
- # release()
- #
- # Release resources previously reserved with Resources.reserve()
- #
- # Args:
- # resources (set): A set of resources to release
- #
- def release(self, resources):
- for resource in resources:
- assert self._used_resources[resource] > 0, "Scheduler resource imbalance"
- self._used_resources[resource] -= 1
-
- # register_exclusive_interest()
- #
- # Inform the resources pool that `source` has an interest in
- # reserving this resource exclusively.
- #
- # The source parameter is used to identify the caller, it
- # must be ensured to be unique for the time that the
- # interest is registered.
- #
- # This function may be called multiple times, and subsequent
- # calls will simply have no effect until clear_exclusive_interest()
- # is used to clear the interest.
- #
- # This must be called in advance of reserve()
- #
- # Args:
- # resources (set): Set of resources to reserve exclusively
- # source (any): Source identifier, to be used again when unregistering
- # the interest.
- #
- def register_exclusive_interest(self, resources, source):
-
- # The very first thing we do is to register any exclusive
- # resources this job may want. Even if the job is not yet
- # allowed to run (because another job is holding the resource
- # it wants), we can still set this - it just means that any
- # job *currently* using these resources has to finish first,
- # and no new jobs wanting these can be launched (except other
- # exclusive-access jobs).
- #
- for resource in resources:
- self._exclusive_resources[resource].add(source)
-
- # unregister_exclusive_interest()
- #
- # Clear the exclusive interest in these resources.
- #
- # This should be called by the given source which registered
- # an exclusive interest.
- #
- # Args:
- # resources (set): Set of resources to reserve exclusively
- # source (str): Source identifier, to be used again when unregistering
- # the interest.
- #
- def unregister_exclusive_interest(self, resources, source):
-
- for resource in resources:
- self._exclusive_resources[resource].discard(source)
diff --git a/buildstream/_scheduler/scheduler.py b/buildstream/_scheduler/scheduler.py
deleted file mode 100644
index 50ad7f07a..000000000
--- a/buildstream/_scheduler/scheduler.py
+++ /dev/null
@@ -1,602 +0,0 @@
-#
-# Copyright (C) 2016 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-# System imports
-import os
-import asyncio
-from itertools import chain
-import signal
-import datetime
-from contextlib import contextmanager
-
-# Local imports
-from .resources import Resources, ResourceType
-from .jobs import JobStatus, CacheSizeJob, CleanupJob
-from .._profile import Topics, PROFILER
-
-
-# A decent return code for Scheduler.run()
-class SchedStatus():
- SUCCESS = 0
- ERROR = -1
- TERMINATED = 1
-
-
-# Some action names for the internal jobs we launch
-#
-_ACTION_NAME_CLEANUP = 'clean'
-_ACTION_NAME_CACHE_SIZE = 'size'
-
-
-# Scheduler()
-#
-# The scheduler operates on a list queues, each of which is meant to accomplish
-# a specific task. Elements enter the first queue when Scheduler.run() is called
-# and into the next queue when complete. Scheduler.run() returns when all of the
-# elements have been traversed or when an error occurs.
-#
-# Using the scheduler is a matter of:
-# a.) Deriving the Queue class and implementing its abstract methods
-# b.) Instantiating a Scheduler with one or more queues
-# c.) Calling Scheduler.run(elements) with a list of elements
-# d.) Fetching results from your queues
-#
-# Args:
-# context: The Context in the parent scheduling process
-# start_time: The time at which the session started
-# interrupt_callback: A callback to handle ^C
-# ticker_callback: A callback call once per second
-# job_start_callback: A callback call when each job starts
-# job_complete_callback: A callback call when each job completes
-#
-class Scheduler():
-
- def __init__(self, context,
- start_time,
- interrupt_callback=None,
- ticker_callback=None,
- job_start_callback=None,
- job_complete_callback=None):
-
- #
- # Public members
- #
- self.queues = None # Exposed for the frontend to print summaries
- self.context = context # The Context object shared with Queues
- self.terminated = False # Whether the scheduler was asked to terminate or has terminated
- self.suspended = False # Whether the scheduler is currently suspended
-
- # These are shared with the Job, but should probably be removed or made private in some way.
- self.loop = None # Shared for Job access to observe the message queue
- self.internal_stops = 0 # Amount of SIGSTP signals we've introduced, this is shared with job.py
-
- #
- # Private members
- #
- self._active_jobs = [] # Jobs currently being run in the scheduler
- self._starttime = start_time # Initial application start time
- self._suspendtime = None # Session time compensation for suspended state
- self._queue_jobs = True # Whether we should continue to queue jobs
-
- # State of cache management related jobs
- self._cache_size_scheduled = False # Whether we have a cache size job scheduled
- self._cache_size_running = None # A running CacheSizeJob, or None
- self._cleanup_scheduled = False # Whether we have a cleanup job scheduled
- self._cleanup_running = None # A running CleanupJob, or None
-
- # Callbacks to report back to the Scheduler owner
- self._interrupt_callback = interrupt_callback
- self._ticker_callback = ticker_callback
- self._job_start_callback = job_start_callback
- self._job_complete_callback = job_complete_callback
-
- # Whether our exclusive jobs, like 'cleanup' are currently already
- # waiting or active.
- #
- # This is just a bit quicker than scanning the wait queue and active
- # queue and comparing job action names.
- #
- self._exclusive_waiting = set()
- self._exclusive_active = set()
-
- self.resources = Resources(context.sched_builders,
- context.sched_fetchers,
- context.sched_pushers)
-
- # run()
- #
- # Args:
- # queues (list): A list of Queue objects
- #
- # Returns:
- # (timedelta): The amount of time since the start of the session,
- # discounting any time spent while jobs were suspended
- # (SchedStatus): How the scheduling terminated
- #
- # Elements in the 'plan' will be processed by each
- # queue in order. Processing will complete when all
- # elements have been processed by each queue or when
- # an error arises
- #
- def run(self, queues):
-
- # Hold on to the queues to process
- self.queues = queues
-
- # Ensure that we have a fresh new event loop, in case we want
- # to run another test in this thread.
- self.loop = asyncio.new_event_loop()
- asyncio.set_event_loop(self.loop)
-
- # Add timeouts
- if self._ticker_callback:
- self.loop.call_later(1, self._tick)
-
- # Handle unix signals while running
- self._connect_signals()
-
- # Check if we need to start with some cache maintenance
- self._check_cache_management()
-
- # Start the profiler
- with PROFILER.profile(Topics.SCHEDULER, "_".join(queue.action_name for queue in self.queues)):
- # Run the queues
- self._sched()
- self.loop.run_forever()
- self.loop.close()
-
- # Stop handling unix signals
- self._disconnect_signals()
-
- failed = any(any(queue.failed_elements) for queue in self.queues)
- self.loop = None
-
- if failed:
- status = SchedStatus.ERROR
- elif self.terminated:
- status = SchedStatus.TERMINATED
- else:
- status = SchedStatus.SUCCESS
-
- return self.elapsed_time(), status
-
- # terminate_jobs()
- #
- # Forcefully terminates all ongoing jobs.
- #
- # For this to be effective, one needs to return to
- # the scheduler loop first and allow the scheduler
- # to complete gracefully.
- #
- # NOTE: This will block SIGINT so that graceful process
- # termination is not interrupted, and SIGINT will
- # remain blocked after Scheduler.run() returns.
- #
- def terminate_jobs(self):
-
- # Set this right away, the frontend will check this
- # attribute to decide whether or not to print status info
- # etc and the following code block will trigger some callbacks.
- self.terminated = True
- self.loop.call_soon(self._terminate_jobs_real)
-
- # Block this until we're finished terminating jobs,
- # this will remain blocked forever.
- signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGINT])
-
- # jobs_suspended()
- #
- # A context manager for running with jobs suspended
- #
- @contextmanager
- def jobs_suspended(self):
- self._disconnect_signals()
- self._suspend_jobs()
-
- yield
-
- self._resume_jobs()
- self._connect_signals()
-
- # stop_queueing()
- #
- # Stop queueing additional jobs, causes Scheduler.run()
- # to return once all currently processing jobs are finished.
- #
- def stop_queueing(self):
- self._queue_jobs = False
-
- # elapsed_time()
- #
- # Fetches the current session elapsed time
- #
- # Returns:
- # (timedelta): The amount of time since the start of the session,
- # discounting any time spent while jobs were suspended.
- #
- def elapsed_time(self):
- timenow = datetime.datetime.now()
- starttime = self._starttime
- if not starttime:
- starttime = timenow
- return timenow - starttime
-
- # job_completed():
- #
- # Called when a Job completes
- #
- # Args:
- # queue (Queue): The Queue holding a complete job
- # job (Job): The completed Job
- # status (JobStatus): The status of the completed job
- #
- def job_completed(self, job, status):
-
- # Remove from the active jobs list
- self._active_jobs.remove(job)
-
- # Scheduler owner facing callback
- self._job_complete_callback(job, status)
-
- # Now check for more jobs
- self._sched()
-
- # check_cache_size():
- #
- # Queues a cache size calculation job, after the cache
- # size is calculated, a cleanup job will be run automatically
- # if needed.
- #
- def check_cache_size(self):
-
- # Here we assume we are called in response to a job
- # completion callback, or before entering the scheduler.
- #
- # As such there is no need to call `_sched()` from here,
- # and we prefer to run it once at the last moment.
- #
- self._cache_size_scheduled = True
-
- #######################################################
- # Local Private Methods #
- #######################################################
-
- # _check_cache_management()
- #
- # Run an initial check if we need to lock the cache
- # resource and check the size and possibly launch
- # a cleanup.
- #
- # Sessions which do not add to the cache are not affected.
- #
- def _check_cache_management(self):
-
- # Only trigger the check for a scheduler run which has
- # queues which require the CACHE resource.
- if not any(q for q in self.queues
- if ResourceType.CACHE in q.resources):
- return
-
- # If the estimated size outgrows the quota, queue a job to
- # actually check the real cache size initially, this one
- # should have exclusive access to the cache to ensure nothing
- # starts while we are checking the cache.
- #
- artifacts = self.context.artifactcache
- if artifacts.full():
- self._sched_cache_size_job(exclusive=True)
-
- # _spawn_job()
- #
- # Spanws a job
- #
- # Args:
- # job (Job): The job to spawn
- #
- def _spawn_job(self, job):
- self._active_jobs.append(job)
- if self._job_start_callback:
- self._job_start_callback(job)
- job.spawn()
-
- # Callback for the cache size job
- def _cache_size_job_complete(self, status, cache_size):
-
- # Deallocate cache size job resources
- self._cache_size_running = None
- self.resources.release([ResourceType.CACHE, ResourceType.PROCESS])
-
- # Unregister the exclusive interest if there was any
- self.resources.unregister_exclusive_interest(
- [ResourceType.CACHE], 'cache-size'
- )
-
- # Schedule a cleanup job if we've hit the threshold
- if status != JobStatus.OK:
- return
-
- context = self.context
- artifacts = context.artifactcache
-
- if artifacts.full():
- self._cleanup_scheduled = True
-
- # Callback for the cleanup job
- def _cleanup_job_complete(self, status, cache_size):
-
- # Deallocate cleanup job resources
- self._cleanup_running = None
- self.resources.release([ResourceType.CACHE, ResourceType.PROCESS])
-
- # Unregister the exclusive interest when we're done with it
- if not self._cleanup_scheduled:
- self.resources.unregister_exclusive_interest(
- [ResourceType.CACHE], 'cache-cleanup'
- )
-
- # _sched_cleanup_job()
- #
- # Runs a cleanup job if one is scheduled to run now and
- # sufficient recources are available.
- #
- def _sched_cleanup_job(self):
-
- if self._cleanup_scheduled and self._cleanup_running is None:
-
- # Ensure we have an exclusive interest in the resources
- self.resources.register_exclusive_interest(
- [ResourceType.CACHE], 'cache-cleanup'
- )
-
- if self.resources.reserve([ResourceType.CACHE, ResourceType.PROCESS],
- [ResourceType.CACHE]):
-
- # Update state and launch
- self._cleanup_scheduled = False
- self._cleanup_running = \
- CleanupJob(self, _ACTION_NAME_CLEANUP, 'cleanup/cleanup',
- complete_cb=self._cleanup_job_complete)
- self._spawn_job(self._cleanup_running)
-
- # _sched_cache_size_job()
- #
- # Runs a cache size job if one is scheduled to run now and
- # sufficient recources are available.
- #
- # Args:
- # exclusive (bool): Run a cache size job immediately and
- # hold the ResourceType.CACHE resource
- # exclusively (used at startup).
- #
- def _sched_cache_size_job(self, *, exclusive=False):
-
- # The exclusive argument is not intended (or safe) for arbitrary use.
- if exclusive:
- assert not self._cache_size_scheduled
- assert not self._cache_size_running
- assert not self._active_jobs
- self._cache_size_scheduled = True
-
- if self._cache_size_scheduled and not self._cache_size_running:
-
- # Handle the exclusive launch
- exclusive_resources = set()
- if exclusive:
- exclusive_resources.add(ResourceType.CACHE)
- self.resources.register_exclusive_interest(
- exclusive_resources, 'cache-size'
- )
-
- # Reserve the resources (with the possible exclusive cache resource)
- if self.resources.reserve([ResourceType.CACHE, ResourceType.PROCESS],
- exclusive_resources):
-
- # Update state and launch
- self._cache_size_scheduled = False
- self._cache_size_running = \
- CacheSizeJob(self, _ACTION_NAME_CACHE_SIZE,
- 'cache_size/cache_size',
- complete_cb=self._cache_size_job_complete)
- self._spawn_job(self._cache_size_running)
-
- # _sched_queue_jobs()
- #
- # Ask the queues what jobs they want to schedule and schedule
- # them. This is done here so we can ask for new jobs when jobs
- # from previous queues become available.
- #
- # This will process the Queues, pull elements through the Queues
- # and process anything that is ready.
- #
- def _sched_queue_jobs(self):
- ready = []
- process_queues = True
-
- while self._queue_jobs and process_queues:
-
- # Pull elements forward through queues
- elements = []
- for queue in self.queues:
- queue.enqueue(elements)
- elements = list(queue.dequeue())
-
- # Kickoff whatever processes can be processed at this time
- #
- # We start by queuing from the last queue first, because
- # we want to give priority to queues later in the
- # scheduling process in the case that multiple queues
- # share the same token type.
- #
- # This avoids starvation situations where we dont move on
- # to fetch tasks for elements which failed to pull, and
- # thus need all the pulls to complete before ever starting
- # a build
- ready.extend(chain.from_iterable(
- q.harvest_jobs() for q in reversed(self.queues)
- ))
-
- # harvest_jobs() may have decided to skip some jobs, making
- # them eligible for promotion to the next queue as a side effect.
- #
- # If that happens, do another round.
- process_queues = any(q.dequeue_ready() for q in self.queues)
-
- # Spawn the jobs
- #
- for job in ready:
- self._spawn_job(job)
-
- # _sched()
- #
- # Run any jobs which are ready to run, or quit the main loop
- # when nothing is running or is ready to run.
- #
- # This is the main driving function of the scheduler, it is called
- # initially when we enter Scheduler.run(), and at the end of whenever
- # any job completes, after any bussiness logic has occurred and before
- # going back to sleep.
- #
- def _sched(self):
-
- if not self.terminated:
-
- #
- # Try the cache management jobs
- #
- self._sched_cleanup_job()
- self._sched_cache_size_job()
-
- #
- # Run as many jobs as the queues can handle for the
- # available resources
- #
- self._sched_queue_jobs()
-
- #
- # If nothing is ticking then bail out
- #
- if not self._active_jobs:
- self.loop.stop()
-
- # _suspend_jobs()
- #
- # Suspend all ongoing jobs.
- #
- def _suspend_jobs(self):
- if not self.suspended:
- self._suspendtime = datetime.datetime.now()
- self.suspended = True
- for job in self._active_jobs:
- job.suspend()
-
- # _resume_jobs()
- #
- # Resume suspended jobs.
- #
- def _resume_jobs(self):
- if self.suspended:
- for job in self._active_jobs:
- job.resume()
- self.suspended = False
- self._starttime += (datetime.datetime.now() - self._suspendtime)
- self._suspendtime = None
-
- # _interrupt_event():
- #
- # A loop registered event callback for keyboard interrupts
- #
- def _interrupt_event(self):
-
- # FIXME: This should not be needed, but for some reason we receive an
- # additional SIGINT event when the user hits ^C a second time
- # to inform us that they really intend to terminate; even though
- # we have disconnected our handlers at this time.
- #
- if self.terminated:
- return
-
- # Leave this to the frontend to decide, if no
- # interrrupt callback was specified, then just terminate.
- if self._interrupt_callback:
- self._interrupt_callback()
- else:
- # Default without a frontend is just terminate
- self.terminate_jobs()
-
- # _terminate_event():
- #
- # A loop registered event callback for SIGTERM
- #
- def _terminate_event(self):
- self.terminate_jobs()
-
- # _suspend_event():
- #
- # A loop registered event callback for SIGTSTP
- #
- def _suspend_event(self):
-
- # Ignore the feedback signals from Job.suspend()
- if self.internal_stops:
- self.internal_stops -= 1
- return
-
- # No need to care if jobs were suspended or not, we _only_ handle this
- # while we know jobs are not suspended.
- self._suspend_jobs()
- os.kill(os.getpid(), signal.SIGSTOP)
- self._resume_jobs()
-
- # _connect_signals():
- #
- # Connects our signal handler event callbacks to the mainloop
- #
- def _connect_signals(self):
- self.loop.add_signal_handler(signal.SIGINT, self._interrupt_event)
- self.loop.add_signal_handler(signal.SIGTERM, self._terminate_event)
- self.loop.add_signal_handler(signal.SIGTSTP, self._suspend_event)
-
- def _disconnect_signals(self):
- self.loop.remove_signal_handler(signal.SIGINT)
- self.loop.remove_signal_handler(signal.SIGTSTP)
- self.loop.remove_signal_handler(signal.SIGTERM)
-
- def _terminate_jobs_real(self):
- # 20 seconds is a long time, it can take a while and sometimes
- # we still fail, need to look deeper into this again.
- wait_start = datetime.datetime.now()
- wait_limit = 20.0
-
- # First tell all jobs to terminate
- for job in self._active_jobs:
- job.terminate()
-
- # Now wait for them to really terminate
- for job in self._active_jobs:
- elapsed = datetime.datetime.now() - wait_start
- timeout = max(wait_limit - elapsed.total_seconds(), 0.0)
- if not job.terminate_wait(timeout):
- job.kill()
-
- # Regular timeout for driving status in the UI
- def _tick(self):
- elapsed = self.elapsed_time()
- self._ticker_callback(elapsed)
- self.loop.call_later(1, self._tick)
diff --git a/buildstream/_signals.py b/buildstream/_signals.py
deleted file mode 100644
index 41b100f93..000000000
--- a/buildstream/_signals.py
+++ /dev/null
@@ -1,203 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-import os
-import signal
-import sys
-import threading
-import traceback
-from contextlib import contextmanager, ExitStack
-from collections import deque
-
-
-# Global per process state for handling of sigterm/sigtstp/sigcont,
-# note that it is expected that this only ever be used by processes
-# the scheduler forks off, not the main process
-terminator_stack = deque()
-suspendable_stack = deque()
-
-
-# Per process SIGTERM handler
-def terminator_handler(signal_, frame):
- while terminator_stack:
- terminator_ = terminator_stack.pop()
- try:
- terminator_()
- except: # noqa pylint: disable=bare-except
- # Ensure we print something if there's an exception raised when
- # processing the handlers. Note that the default exception
- # handler won't be called because we os._exit next, so we must
- # catch all possible exceptions with the unqualified 'except'
- # clause.
- traceback.print_exc(file=sys.stderr)
- print('Error encountered in BuildStream while processing custom SIGTERM handler:',
- terminator_,
- file=sys.stderr)
-
- # Use special exit here, terminate immediately, recommended
- # for precisely this situation where child forks are teminated.
- os._exit(-1)
-
-
-# terminator()
-#
-# A context manager for interruptable tasks, this guarantees
-# that while the code block is running, the supplied function
-# will be called upon process termination.
-#
-# Note that after handlers are called, the termination will be handled by
-# terminating immediately with os._exit(). This means that SystemExit will not
-# be raised and 'finally' clauses will not be executed.
-#
-# Args:
-# terminate_func (callable): A function to call when aborting
-# the nested code block.
-#
-@contextmanager
-def terminator(terminate_func):
- global terminator_stack # pylint: disable=global-statement
-
- # Signal handling only works in the main thread
- if threading.current_thread() != threading.main_thread():
- yield
- return
-
- outermost = bool(not terminator_stack)
-
- terminator_stack.append(terminate_func)
- if outermost:
- original_handler = signal.signal(signal.SIGTERM, terminator_handler)
-
- try:
- yield
- finally:
- if outermost:
- signal.signal(signal.SIGTERM, original_handler)
- terminator_stack.pop()
-
-
-# Just a simple object for holding on to two callbacks
-class Suspender():
- def __init__(self, suspend_callback, resume_callback):
- self.suspend = suspend_callback
- self.resume = resume_callback
-
-
-# Per process SIGTSTP handler
-def suspend_handler(sig, frame):
-
- # Suspend callbacks from innermost frame first
- for suspender in reversed(suspendable_stack):
- suspender.suspend()
-
- # Use SIGSTOP directly now on self, dont introduce more SIGTSTP
- #
- # Here the process sleeps until SIGCONT, which we simply
- # dont handle. We know we'll pickup execution right here
- # when we wake up.
- os.kill(os.getpid(), signal.SIGSTOP)
-
- # Resume callbacks from outermost frame inwards
- for suspender in suspendable_stack:
- suspender.resume()
-
-
-# suspendable()
-#
-# A context manager for handling process suspending and resumeing
-#
-# Args:
-# suspend_callback (callable): A function to call as process suspend time.
-# resume_callback (callable): A function to call as process resume time.
-#
-# This must be used in code blocks which spawn processes that become
-# their own session leader. In these cases, SIGSTOP and SIGCONT need
-# to be propagated to the child process group.
-#
-# This context manager can also be used recursively, so multiple
-# things can happen at suspend/resume time (such as tracking timers
-# and ensuring durations do not count suspended time).
-#
-@contextmanager
-def suspendable(suspend_callback, resume_callback):
- global suspendable_stack # pylint: disable=global-statement
-
- outermost = bool(not suspendable_stack)
- suspender = Suspender(suspend_callback, resume_callback)
- suspendable_stack.append(suspender)
-
- if outermost:
- original_stop = signal.signal(signal.SIGTSTP, suspend_handler)
-
- try:
- yield
- finally:
- if outermost:
- signal.signal(signal.SIGTSTP, original_stop)
-
- suspendable_stack.pop()
-
-
-# blocked()
-#
-# A context manager for running a code block with blocked signals
-#
-# Args:
-# signals (list): A list of unix signals to block
-# ignore (bool): Whether to ignore entirely the signals which were
-# received and pending while the process had blocked them
-#
-@contextmanager
-def blocked(signal_list, ignore=True):
-
- with ExitStack() as stack:
-
- # Optionally add the ignored() context manager to this context
- if ignore:
- stack.enter_context(ignored(signal_list))
-
- # Set and save the sigprocmask
- blocked_signals = signal.pthread_sigmask(signal.SIG_BLOCK, signal_list)
-
- try:
- yield
- finally:
- # If we have discarded the signals completely, this line will cause
- # the discard_handler() to trigger for each signal in the list
- signal.pthread_sigmask(signal.SIG_SETMASK, blocked_signals)
-
-
-# ignored()
-#
-# A context manager for running a code block with ignored signals
-#
-# Args:
-# signals (list): A list of unix signals to ignore
-#
-@contextmanager
-def ignored(signal_list):
-
- orig_handlers = {}
- for sig in signal_list:
- orig_handlers[sig] = signal.signal(sig, signal.SIG_IGN)
-
- try:
- yield
- finally:
- for sig in signal_list:
- signal.signal(sig, orig_handlers[sig])
diff --git a/buildstream/_site.py b/buildstream/_site.py
deleted file mode 100644
index 8940fa34a..000000000
--- a/buildstream/_site.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-import os
-import shutil
-import subprocess
-
-#
-# Private module declaring some info about where the buildstream
-# is installed so we can lookup package relative resources easily
-#
-
-# The package root, wherever we are running the package from
-root = os.path.dirname(os.path.abspath(__file__))
-
-# The Element plugin directory
-element_plugins = os.path.join(root, 'plugins', 'elements')
-
-# The Source plugin directory
-source_plugins = os.path.join(root, 'plugins', 'sources')
-
-# Default user configuration
-default_user_config = os.path.join(root, 'data', 'userconfig.yaml')
-
-# Default project configuration
-default_project_config = os.path.join(root, 'data', 'projectconfig.yaml')
-
-# Script template to call module building scripts
-build_all_template = os.path.join(root, 'data', 'build-all.sh.in')
-
-# Module building script template
-build_module_template = os.path.join(root, 'data', 'build-module.sh.in')
-
-
-def get_bwrap_version():
- # Get the current bwrap version
- #
- # returns None if no bwrap was found
- # otherwise returns a tuple of 3 int: major, minor, patch
- bwrap_path = shutil.which('bwrap')
-
- if not bwrap_path:
- return None
-
- cmd = [bwrap_path, "--version"]
- try:
- version = str(subprocess.check_output(cmd).split()[1], "utf-8")
- except subprocess.CalledProcessError:
- return None
-
- return tuple(int(x) for x in version.split("."))
diff --git a/buildstream/_sourcecache.py b/buildstream/_sourcecache.py
deleted file mode 100644
index 1d3342a75..000000000
--- a/buildstream/_sourcecache.py
+++ /dev/null
@@ -1,249 +0,0 @@
-#
-# Copyright (C) 2019 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:
-# Raoul Hidalgo Charman <raoul.hidalgocharman@codethink.co.uk>
-#
-import os
-
-from ._cas import CASRemoteSpec
-from .storage._casbaseddirectory import CasBasedDirectory
-from ._basecache import BaseCache
-from ._exceptions import CASError, CASCacheError, SourceCacheError
-from . import utils
-
-
-# Holds configuration for a remote used for the source cache.
-#
-# Args:
-# url (str): Location of the remote source cache
-# push (bool): Whether we should attempt to push sources to this cache,
-# in addition to pulling from it.
-# instance-name (str): Name if any, of instance of server
-#
-class SourceCacheSpec(CASRemoteSpec):
- pass
-
-
-# Class that keeps config of remotes and deals with caching of sources.
-#
-# Args:
-# context (Context): The Buildstream context
-#
-class SourceCache(BaseCache):
-
- spec_class = SourceCacheSpec
- spec_name = "source_cache_specs"
- spec_error = SourceCacheError
- config_node_name = "source-caches"
-
- def __init__(self, context):
- super().__init__(context)
-
- self._required_sources = set()
-
- self.casquota.add_remove_callbacks(self.unrequired_sources, self.cas.remove)
- self.casquota.add_list_refs_callback(self.list_sources)
-
- # mark_required_sources()
- #
- # Mark sources that are required by the current run.
- #
- # Sources that are in this list will not be removed during the current
- # pipeline.
- #
- # Args:
- # sources (iterable): An iterable over sources that are required
- #
- def mark_required_sources(self, sources):
- sources = list(sources) # in case it's a generator
-
- self._required_sources.update(sources)
-
- # update mtimes just in case
- for source in sources:
- ref = source._get_source_name()
- try:
- self.cas.update_mtime(ref)
- except CASCacheError:
- pass
-
- # required_sources()
- #
- # Yields the keys of all sources marked as required by the current build
- # plan
- #
- # Returns:
- # iterable (str): iterable over the required source refs
- #
- def required_sources(self):
- for source in self._required_sources:
- yield source._get_source_name()
-
- # unrequired_sources()
- #
- # Yields the refs of all sources not required by the current build plan
- #
- # Returns:
- # iter (str): iterable over unrequired source keys
- #
- def unrequired_sources(self):
- required_source_names = set(map(
- lambda x: x._get_source_name(), self._required_sources))
- for (mtime, source) in self._list_refs_mtimes(
- os.path.join(self.cas.casdir, 'refs', 'heads'),
- glob_expr="@sources/*"):
- if source not in required_source_names:
- yield (mtime, source)
-
- # list_sources()
- #
- # Get list of all sources in the `cas/refs/heads/@sources/` folder
- #
- # Returns:
- # ([str]): iterable over all source refs
- #
- def list_sources(self):
- return [ref for _, ref in self._list_refs_mtimes(
- os.path.join(self.cas.casdir, 'refs', 'heads'),
- glob_expr="@sources/*")]
-
- # contains()
- #
- # Given a source, gets the ref name and checks whether the local CAS
- # contains it.
- #
- # Args:
- # source (Source): Source to check
- #
- # Returns:
- # (bool): whether the CAS contains this source or not
- #
- def contains(self, source):
- ref = source._get_source_name()
- return self.cas.contains(ref)
-
- # commit()
- #
- # Given a source along with previous sources, it stages and commits these
- # to the local CAS. This is done due to some types of sources being
- # dependent on previous sources, such as the patch source.
- #
- # Args:
- # source: last source
- # previous_sources: rest of the sources.
- def commit(self, source, previous_sources):
- ref = source._get_source_name()
-
- # Use tmpdir for now
- vdir = CasBasedDirectory(self.cas)
- for previous_source in previous_sources:
- vdir.import_files(self.export(previous_source))
-
- with utils._tempdir(dir=self.context.tmpdir, prefix='staging-temp') as tmpdir:
- if not vdir.is_empty():
- vdir.export_files(tmpdir)
- source._stage(tmpdir)
- vdir.import_files(tmpdir, can_link=True)
-
- self.cas.set_ref(ref, vdir._get_digest())
-
- # export()
- #
- # Exports a source in the CAS to a virtual directory
- #
- # Args:
- # source (Source): source we want to export
- #
- # Returns:
- # CASBasedDirectory
- def export(self, source):
- ref = source._get_source_name()
-
- try:
- digest = self.cas.resolve_ref(ref)
- except CASCacheError as e:
- raise SourceCacheError("Error exporting source: {}".format(e))
-
- return CasBasedDirectory(self.cas, digest=digest)
-
- # pull()
- #
- # Attempts to pull sources from configure remote source caches.
- #
- # Args:
- # source (Source): The source we want to fetch
- # progress (callable|None): The progress callback
- #
- # Returns:
- # (bool): True if pull successful, False if not
- def pull(self, source):
- ref = source._get_source_name()
-
- project = source._get_project()
-
- display_key = source._get_brief_display_key()
-
- for remote in self._remotes[project]:
- try:
- source.status("Pulling source {} <- {}".format(display_key, remote.spec.url))
-
- if self.cas.pull(ref, remote):
- source.info("Pulled source {} <- {}".format(display_key, remote.spec.url))
- # no need to pull from additional remotes
- return True
- else:
- source.info("Remote ({}) does not have source {} cached".format(
- remote.spec.url, display_key))
- except CASError as e:
- raise SourceCacheError("Failed to pull source {}: {}".format(
- display_key, e)) from e
- return False
-
- # push()
- #
- # Push a source to configured remote source caches
- #
- # Args:
- # source (Source): source to push
- #
- # Returns:
- # (Bool): whether it pushed to a remote source cache
- #
- def push(self, source):
- ref = source._get_source_name()
- project = source._get_project()
-
- # find configured push remotes for this source
- if self._has_push_remotes:
- push_remotes = [r for r in self._remotes[project] if r.spec.push]
- else:
- push_remotes = []
-
- pushed = False
-
- display_key = source._get_brief_display_key()
- for remote in push_remotes:
- remote.init()
- source.status("Pushing source {} -> {}".format(display_key, remote.spec.url))
- if self.cas.push([ref], remote):
- source.info("Pushed source {} -> {}".format(display_key, remote.spec.url))
- pushed = True
- else:
- source.info("Remote ({}) already has source {} cached"
- .format(remote.spec.url, display_key))
-
- return pushed
diff --git a/buildstream/_sourcefactory.py b/buildstream/_sourcefactory.py
deleted file mode 100644
index 1d959a140..000000000
--- a/buildstream/_sourcefactory.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-from . import _site
-from ._plugincontext import PluginContext
-from .source import Source
-
-
-# A SourceFactory creates Source instances
-# in the context of a given factory
-#
-# Args:
-# plugin_base (PluginBase): The main PluginBase object to work with
-# plugin_origins (list): Data used to search for external Source plugins
-#
-class SourceFactory(PluginContext):
-
- def __init__(self, plugin_base, *,
- format_versions={},
- plugin_origins=None):
-
- super().__init__(plugin_base, Source, [_site.source_plugins],
- format_versions=format_versions,
- plugin_origins=plugin_origins)
-
- # create():
- #
- # Create a Source object, the pipeline uses this to create Source
- # objects on demand for a given pipeline.
- #
- # Args:
- # context (object): The Context object for processing
- # project (object): The project object
- # meta (object): The loaded MetaSource
- #
- # Returns:
- # A newly created Source object of the appropriate kind
- #
- # Raises:
- # PluginError (if the kind lookup failed)
- # LoadError (if the source itself took issue with the config)
- #
- def create(self, context, project, meta):
- source_type, _ = self.lookup(meta.kind)
- source = source_type(context, project, meta)
- version = self._format_versions.get(meta.kind, 0)
- self._assert_plugin_format(source, version)
- return source
diff --git a/buildstream/_stream.py b/buildstream/_stream.py
deleted file mode 100644
index 2343c553c..000000000
--- a/buildstream/_stream.py
+++ /dev/null
@@ -1,1512 +0,0 @@
-#
-# Copyright (C) 2018 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>
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-import itertools
-import functools
-import os
-import sys
-import stat
-import shlex
-import shutil
-import tarfile
-import tempfile
-from contextlib import contextmanager, suppress
-from fnmatch import fnmatch
-
-from ._artifactelement import verify_artifact_ref
-from ._exceptions import StreamError, ImplError, BstError, ArtifactElementError, ArtifactError
-from ._message import Message, MessageType
-from ._scheduler import Scheduler, SchedStatus, TrackQueue, FetchQueue, \
- SourcePushQueue, BuildQueue, PullQueue, ArtifactPushQueue
-from ._pipeline import Pipeline, PipelineSelection
-from ._profile import Topics, PROFILER
-from .types import _KeyStrength
-from . import utils, _yaml, _site
-from . import Scope, Consistency
-
-
-# Stream()
-#
-# This is the main, toplevel calling interface in BuildStream core.
-#
-# Args:
-# context (Context): The Context object
-# project (Project): The Project object
-# session_start (datetime): The time when the session started
-# session_start_callback (callable): A callback to invoke when the session starts
-# interrupt_callback (callable): A callback to invoke when we get interrupted
-# ticker_callback (callable): Invoked every second while running the scheduler
-# job_start_callback (callable): Called when a job starts
-# job_complete_callback (callable): Called when a job completes
-#
-class Stream():
-
- def __init__(self, context, project, session_start, *,
- session_start_callback=None,
- interrupt_callback=None,
- ticker_callback=None,
- job_start_callback=None,
- job_complete_callback=None):
-
- #
- # Public members
- #
- self.targets = [] # Resolved target elements
- self.session_elements = [] # List of elements being processed this session
- self.total_elements = [] # Total list of elements based on targets
- self.queues = [] # Queue objects
-
- #
- # Private members
- #
- self._artifacts = context.artifactcache
- self._sourcecache = context.sourcecache
- self._context = context
- self._project = project
- self._pipeline = Pipeline(context, project, self._artifacts)
- self._scheduler = Scheduler(context, session_start,
- interrupt_callback=interrupt_callback,
- ticker_callback=ticker_callback,
- job_start_callback=job_start_callback,
- job_complete_callback=job_complete_callback)
- self._first_non_track_queue = None
- self._session_start_callback = session_start_callback
-
- # cleanup()
- #
- # Cleans up application state
- #
- def cleanup(self):
- if self._project:
- self._project.cleanup()
-
- # load_selection()
- #
- # An all purpose method for loading a selection of elements, this
- # is primarily useful for the frontend to implement `bst show`
- # and `bst shell`.
- #
- # Args:
- # targets (list of str): Targets to pull
- # selection (PipelineSelection): The selection mode for the specified targets
- # except_targets (list of str): Specified targets to except from fetching
- # use_artifact_config (bool): If artifact remote configs should be loaded
- #
- # Returns:
- # (list of Element): The selected elements
- def load_selection(self, targets, *,
- selection=PipelineSelection.NONE,
- except_targets=(),
- use_artifact_config=False,
- load_refs=False):
- with PROFILER.profile(Topics.LOAD_SELECTION, "_".join(t.replace(os.sep, "-") for t in targets)):
- target_objects, _ = self._load(targets, (),
- selection=selection,
- except_targets=except_targets,
- fetch_subprojects=False,
- use_artifact_config=use_artifact_config,
- load_refs=load_refs)
-
- return target_objects
-
- # shell()
- #
- # Run a shell
- #
- # Args:
- # element (Element): An Element object to run the shell for
- # scope (Scope): The scope for the shell (Scope.BUILD or Scope.RUN)
- # prompt (str): The prompt to display in the shell
- # directory (str): A directory where an existing prestaged sysroot is expected, or None
- # mounts (list of HostMount): Additional directories to mount into the sandbox
- # isolate (bool): Whether to isolate the environment like we do in builds
- # command (list): An argv to launch in the sandbox, or None
- # usebuildtree (str): Whether to use a buildtree as the source, given cli option
- #
- # Returns:
- # (int): The exit code of the launched shell
- #
- def shell(self, element, scope, prompt, *,
- directory=None,
- mounts=None,
- isolate=False,
- command=None,
- usebuildtree=None):
-
- # Assert we have everything we need built, unless the directory is specified
- # in which case we just blindly trust the directory, using the element
- # definitions to control the execution environment only.
- if directory is None:
- missing_deps = [
- dep._get_full_name()
- for dep in self._pipeline.dependencies([element], scope)
- if not dep._cached()
- ]
- if missing_deps:
- raise StreamError("Elements need to be built or downloaded before staging a shell environment",
- detail="\n".join(missing_deps))
-
- buildtree = False
- # Check if we require a pull queue attempt, with given artifact state and context
- if usebuildtree:
- if not element._cached_buildtree():
- require_buildtree = self._buildtree_pull_required([element])
- # Attempt a pull queue for the given element if remote and context allow it
- if require_buildtree:
- self._message(MessageType.INFO, "Attempting to fetch missing artifact buildtree")
- self._add_queue(PullQueue(self._scheduler))
- self._enqueue_plan(require_buildtree)
- self._run()
- # Now check if the buildtree was successfully fetched
- if element._cached_buildtree():
- buildtree = True
-
- if not buildtree:
- if element._buildtree_exists():
- message = "Buildtree is not cached locally or in available remotes"
- else:
- message = "Artifact was created without buildtree"
-
- if usebuildtree == "always":
- raise StreamError(message)
- else:
- self._message(MessageType.INFO, message + ", shell will be loaded without it")
- else:
- buildtree = True
-
- return element._shell(scope, directory, mounts=mounts, isolate=isolate, prompt=prompt, command=command,
- usebuildtree=buildtree)
-
- # build()
- #
- # Builds (assembles) elements in the pipeline.
- #
- # Args:
- # targets (list of str): Targets to build
- # track_targets (list of str): Specified targets for tracking
- # track_except (list of str): Specified targets to except from tracking
- # track_cross_junctions (bool): Whether tracking should cross junction boundaries
- # ignore_junction_targets (bool): Whether junction targets should be filtered out
- # build_all (bool): Whether to build all elements, or only those
- # which are required to build the target.
- # remote (str): The URL of a specific remote server to push to, or None
- #
- # If `remote` specified as None, then regular configuration will be used
- # to determine where to push artifacts to.
- #
- def build(self, targets, *,
- track_targets=None,
- track_except=None,
- track_cross_junctions=False,
- ignore_junction_targets=False,
- build_all=False,
- remote=None):
-
- if build_all:
- selection = PipelineSelection.ALL
- else:
- selection = PipelineSelection.PLAN
-
- use_config = True
- if remote:
- use_config = False
-
- elements, track_elements = \
- self._load(targets, track_targets,
- selection=selection, track_selection=PipelineSelection.ALL,
- track_except_targets=track_except,
- track_cross_junctions=track_cross_junctions,
- ignore_junction_targets=ignore_junction_targets,
- use_artifact_config=use_config,
- artifact_remote_url=remote,
- use_source_config=True,
- fetch_subprojects=True,
- dynamic_plan=True)
-
- # Remove the tracking elements from the main targets
- elements = self._pipeline.subtract_elements(elements, track_elements)
-
- # Assert that the elements we're not going to track are consistent
- self._pipeline.assert_consistent(elements)
-
- if all(project.remote_execution_specs for project in self._context.get_projects()):
- # Remote execution is configured for all projects.
- # Require artifact files only for target elements and their runtime dependencies.
- self._context.set_artifact_files_optional()
- for element in self.targets:
- element._set_artifact_files_required()
-
- # Now construct the queues
- #
- track_queue = None
- if track_elements:
- track_queue = TrackQueue(self._scheduler)
- self._add_queue(track_queue, track=True)
-
- if self._artifacts.has_fetch_remotes():
- self._add_queue(PullQueue(self._scheduler))
-
- self._add_queue(FetchQueue(self._scheduler, skip_cached=True))
-
- self._add_queue(BuildQueue(self._scheduler))
-
- if self._artifacts.has_push_remotes():
- self._add_queue(ArtifactPushQueue(self._scheduler))
-
- if self._sourcecache.has_push_remotes():
- self._add_queue(SourcePushQueue(self._scheduler))
-
- # Enqueue elements
- #
- if track_elements:
- self._enqueue_plan(track_elements, queue=track_queue)
- self._enqueue_plan(elements)
- self._run()
-
- # fetch()
- #
- # Fetches sources on the pipeline.
- #
- # Args:
- # targets (list of str): Targets to fetch
- # selection (PipelineSelection): The selection mode for the specified targets
- # except_targets (list of str): Specified targets to except from fetching
- # track_targets (bool): Whether to track selected targets in addition to fetching
- # track_cross_junctions (bool): Whether tracking should cross junction boundaries
- # remote (str|None): The URL of a specific remote server to pull from.
- #
- def fetch(self, targets, *,
- selection=PipelineSelection.PLAN,
- except_targets=None,
- track_targets=False,
- track_cross_junctions=False,
- remote=None):
-
- if track_targets:
- track_targets = targets
- track_selection = selection
- track_except_targets = except_targets
- else:
- track_targets = ()
- track_selection = PipelineSelection.NONE
- track_except_targets = ()
-
- use_source_config = True
- if remote:
- use_source_config = False
-
- elements, track_elements = \
- self._load(targets, track_targets,
- selection=selection, track_selection=track_selection,
- except_targets=except_targets,
- track_except_targets=track_except_targets,
- track_cross_junctions=track_cross_junctions,
- fetch_subprojects=True,
- use_source_config=use_source_config,
- source_remote_url=remote)
-
- # Delegated to a shared fetch method
- self._fetch(elements, track_elements=track_elements)
-
- # track()
- #
- # Tracks all the sources of the selected elements.
- #
- # Args:
- # targets (list of str): Targets to track
- # selection (PipelineSelection): The selection mode for the specified targets
- # except_targets (list of str): Specified targets to except from tracking
- # cross_junctions (bool): Whether tracking should cross junction boundaries
- #
- # If no error is encountered while tracking, then the project files
- # are rewritten inline.
- #
- def track(self, targets, *,
- selection=PipelineSelection.REDIRECT,
- except_targets=None,
- cross_junctions=False):
-
- # We pass no target to build. Only to track. Passing build targets
- # would fully load project configuration which might not be
- # possible before tracking is done.
- _, elements = \
- self._load([], targets,
- selection=selection, track_selection=selection,
- except_targets=except_targets,
- track_except_targets=except_targets,
- track_cross_junctions=cross_junctions,
- fetch_subprojects=True)
-
- track_queue = TrackQueue(self._scheduler)
- self._add_queue(track_queue, track=True)
- self._enqueue_plan(elements, queue=track_queue)
- self._run()
-
- # pull()
- #
- # Pulls artifacts from remote artifact server(s)
- #
- # Args:
- # targets (list of str): Targets to pull
- # selection (PipelineSelection): The selection mode for the specified targets
- # ignore_junction_targets (bool): Whether junction targets should be filtered out
- # remote (str): The URL of a specific remote server to pull from, or None
- #
- # If `remote` specified as None, then regular configuration will be used
- # to determine where to pull artifacts from.
- #
- def pull(self, targets, *,
- selection=PipelineSelection.NONE,
- ignore_junction_targets=False,
- remote=None):
-
- use_config = True
- if remote:
- use_config = False
-
- elements, _ = self._load(targets, (),
- selection=selection,
- ignore_junction_targets=ignore_junction_targets,
- use_artifact_config=use_config,
- artifact_remote_url=remote,
- fetch_subprojects=True)
-
- if not self._artifacts.has_fetch_remotes():
- raise StreamError("No artifact caches available for pulling artifacts")
-
- self._pipeline.assert_consistent(elements)
- self._add_queue(PullQueue(self._scheduler))
- self._enqueue_plan(elements)
- self._run()
-
- # push()
- #
- # Pulls artifacts to remote artifact server(s)
- #
- # Args:
- # targets (list of str): Targets to push
- # selection (PipelineSelection): The selection mode for the specified targets
- # ignore_junction_targets (bool): Whether junction targets should be filtered out
- # remote (str): The URL of a specific remote server to push to, or None
- #
- # If `remote` specified as None, then regular configuration will be used
- # to determine where to push artifacts to.
- #
- # If any of the given targets are missing their expected buildtree artifact,
- # a pull queue will be created if user context and available remotes allow for
- # attempting to fetch them.
- #
- def push(self, targets, *,
- selection=PipelineSelection.NONE,
- ignore_junction_targets=False,
- remote=None):
-
- use_config = True
- if remote:
- use_config = False
-
- elements, _ = self._load(targets, (),
- selection=selection,
- ignore_junction_targets=ignore_junction_targets,
- use_artifact_config=use_config,
- artifact_remote_url=remote,
- fetch_subprojects=True)
-
- if not self._artifacts.has_push_remotes():
- raise StreamError("No artifact caches available for pushing artifacts")
-
- self._pipeline.assert_consistent(elements)
-
- # Check if we require a pull queue, with given artifact state and context
- require_buildtrees = self._buildtree_pull_required(elements)
- if require_buildtrees:
- self._message(MessageType.INFO, "Attempting to fetch missing artifact buildtrees")
- self._add_queue(PullQueue(self._scheduler))
- self._enqueue_plan(require_buildtrees)
- else:
- # FIXME: This hack should be removed as a result of refactoring
- # Element._update_state()
- #
- # This workaround marks all dependencies of all selected elements as
- # "pulled" before trying to push.
- #
- # Instead of lying to the elements and telling them they have already
- # been pulled, we should have something more consistent with how other
- # state bits are handled; and explicitly tell the elements that they
- # need to be pulled with something like Element._schedule_pull().
- #
- for element in elements:
- element._pull_done()
-
- push_queue = ArtifactPushQueue(self._scheduler)
- self._add_queue(push_queue)
- self._enqueue_plan(elements, queue=push_queue)
- self._run()
-
- # checkout()
- #
- # Checkout target artifact to the specified location
- #
- # Args:
- # target (str): Target to checkout
- # location (str): Location to checkout the artifact to
- # force (bool): Whether files can be overwritten if necessary
- # scope (str): The scope of dependencies to checkout
- # integrate (bool): Whether to run integration commands
- # hardlinks (bool): Whether checking out files hardlinked to
- # their artifacts is acceptable
- # tar (bool): If true, a tarball from the artifact contents will
- # be created, otherwise the file tree of the artifact
- # will be placed at the given location. If true and
- # location is '-', the tarball will be dumped on the
- # standard output.
- #
- def checkout(self, target, *,
- location=None,
- force=False,
- scope=Scope.RUN,
- integrate=True,
- hardlinks=False,
- tar=False):
-
- # We only have one target in a checkout command
- elements, _ = self._load((target,), (), fetch_subprojects=True)
- target = elements[0]
-
- self._check_location_writable(location, force=force, tar=tar)
-
- # Stage deps into a temporary sandbox first
- try:
- with target._prepare_sandbox(scope=scope, directory=None,
- integrate=integrate) as sandbox:
-
- # Copy or move the sandbox to the target directory
- sandbox_vroot = sandbox.get_virtual_directory()
-
- if not tar:
- with target.timed_activity("Checking out files in '{}'"
- .format(location)):
- try:
- if hardlinks:
- self._checkout_hardlinks(sandbox_vroot, location)
- else:
- sandbox_vroot.export_files(location)
- except OSError as e:
- raise StreamError("Failed to checkout files: '{}'"
- .format(e)) from e
- else:
- if location == '-':
- with target.timed_activity("Creating tarball"):
- # Save the stdout FD to restore later
- saved_fd = os.dup(sys.stdout.fileno())
- try:
- with os.fdopen(sys.stdout.fileno(), 'wb') as fo:
- with tarfile.open(fileobj=fo, mode="w|") as tf:
- sandbox_vroot.export_to_tar(tf, '.')
- finally:
- # No matter what, restore stdout for further use
- os.dup2(saved_fd, sys.stdout.fileno())
- os.close(saved_fd)
- else:
- with target.timed_activity("Creating tarball '{}'"
- .format(location)):
- with tarfile.open(location, "w:") as tf:
- sandbox_vroot.export_to_tar(tf, '.')
-
- except BstError as e:
- raise StreamError("Error while staging dependencies into a sandbox"
- ": '{}'".format(e), detail=e.detail, reason=e.reason) from e
-
- # artifact_log()
- #
- # Show the full log of an artifact
- #
- # Args:
- # targets (str): Targets to view the logs of
- #
- # Returns:
- # logsdir (list): A list of CasBasedDirectory objects containing artifact logs
- #
- def artifact_log(self, targets):
- # Return list of Element and/or ArtifactElement objects
- target_objects = self.load_selection(targets, selection=PipelineSelection.NONE, load_refs=True)
-
- logsdirs = []
- for obj in target_objects:
- ref = obj.get_artifact_name()
- if not obj._cached():
- self._message(MessageType.WARN, "{} is not cached".format(ref))
- continue
- elif not obj._cached_logs():
- self._message(MessageType.WARN, "{} is cached without log files".format(ref))
- continue
-
- logsdirs.append(self._artifacts.get_artifact_logs(ref))
-
- return logsdirs
-
- # artifact_delete()
- #
- # Remove artifacts from the local cache
- #
- # Args:
- # targets (str): Targets to remove
- # no_prune (bool): Whether to prune the unreachable refs, default False
- #
- def artifact_delete(self, targets, no_prune):
- # Return list of Element and/or ArtifactElement objects
- target_objects = self.load_selection(targets, selection=PipelineSelection.NONE, load_refs=True)
-
- # Some of the targets may refer to the same key, so first obtain a
- # set of the refs to be removed.
- remove_refs = set()
- for obj in target_objects:
- for key_strength in [_KeyStrength.STRONG, _KeyStrength.WEAK]:
- key = obj._get_cache_key(strength=key_strength)
- remove_refs.add(obj.get_artifact_name(key=key))
-
- ref_removed = False
- for ref in remove_refs:
- try:
- self._artifacts.remove(ref, defer_prune=True)
- except ArtifactError as e:
- self._message(MessageType.WARN, str(e))
- continue
-
- self._message(MessageType.INFO, "Removed: {}".format(ref))
- ref_removed = True
-
- # Prune the artifact cache
- if ref_removed and not no_prune:
- with self._context.timed_activity("Pruning artifact cache"):
- self._artifacts.prune()
-
- if not ref_removed:
- self._message(MessageType.INFO, "No artifacts were removed")
-
- # source_checkout()
- #
- # Checkout sources of the target element to the specified location
- #
- # Args:
- # target (str): The target element whose sources to checkout
- # location (str): Location to checkout the sources to
- # deps (str): The dependencies to checkout
- # fetch (bool): Whether to fetch missing sources
- # except_targets (list): List of targets to except from staging
- #
- def source_checkout(self, target, *,
- location=None,
- force=False,
- deps='none',
- fetch=False,
- except_targets=(),
- tar=False,
- include_build_scripts=False):
-
- self._check_location_writable(location, force=force, tar=tar)
-
- elements, _ = self._load((target,), (),
- selection=deps,
- except_targets=except_targets,
- fetch_subprojects=True)
-
- # Assert all sources are cached in the source dir
- if fetch:
- self._fetch(elements, fetch_original=True)
- self._pipeline.assert_sources_cached(elements)
-
- # Stage all sources determined by scope
- try:
- self._source_checkout(elements, location, force, deps,
- fetch, tar, include_build_scripts)
- except BstError as e:
- raise StreamError("Error while writing sources"
- ": '{}'".format(e), detail=e.detail, reason=e.reason) from e
-
- # workspace_open
- #
- # Open a project workspace
- #
- # Args:
- # targets (list): List of target elements to open workspaces for
- # no_checkout (bool): Whether to skip checking out the source
- # track_first (bool): Whether to track and fetch first
- # force (bool): Whether to ignore contents in an existing directory
- # custom_dir (str): Custom location to create a workspace or false to use default location.
- #
- def workspace_open(self, targets, *,
- no_checkout,
- track_first,
- force,
- custom_dir):
- # This function is a little funny but it is trying to be as atomic as possible.
-
- if track_first:
- track_targets = targets
- else:
- track_targets = ()
-
- elements, track_elements = self._load(targets, track_targets,
- selection=PipelineSelection.REDIRECT,
- track_selection=PipelineSelection.REDIRECT)
-
- workspaces = self._context.get_workspaces()
-
- # If we're going to checkout, we need at least a fetch,
- # if we were asked to track first, we're going to fetch anyway.
- #
- if not no_checkout or track_first:
- track_elements = []
- if track_first:
- track_elements = elements
- self._fetch(elements, track_elements=track_elements, fetch_original=True)
-
- expanded_directories = []
- # To try to be more atomic, loop through the elements and raise any errors we can early
- for target in elements:
-
- if not list(target.sources()):
- build_depends = [x.name for x in target.dependencies(Scope.BUILD, recurse=False)]
- if not build_depends:
- raise StreamError("The element {} has no sources".format(target.name))
- detail = "Try opening a workspace on one of its dependencies instead:\n"
- detail += " \n".join(build_depends)
- raise StreamError("The element {} has no sources".format(target.name), detail=detail)
-
- # Check for workspace config
- workspace = workspaces.get_workspace(target._get_full_name())
- if workspace and not force:
- raise StreamError("Element '{}' already has workspace defined at: {}"
- .format(target.name, workspace.get_absolute_path()))
-
- target_consistency = target._get_consistency()
- if not no_checkout and target_consistency < Consistency.CACHED and \
- target_consistency._source_cached():
- raise StreamError("Could not stage uncached source. For {} ".format(target.name) +
- "Use `--track` to track and " +
- "fetch the latest version of the " +
- "source.")
-
- if not custom_dir:
- directory = os.path.abspath(os.path.join(self._context.workspacedir, target.name))
- if directory[-4:] == '.bst':
- directory = directory[:-4]
- expanded_directories.append(directory)
-
- if custom_dir:
- if len(elements) != 1:
- raise StreamError("Exactly one element can be given if --directory is used",
- reason='directory-with-multiple-elements')
- directory = os.path.abspath(custom_dir)
- expanded_directories = [directory, ]
- else:
- # If this fails it is a bug in what ever calls this, usually cli.py and so can not be tested for via the
- # run bst test mechanism.
- assert len(elements) == len(expanded_directories)
-
- for target, directory in zip(elements, expanded_directories):
- if os.path.exists(directory):
- if not os.path.isdir(directory):
- raise StreamError("For element '{}', Directory path is not a directory: {}"
- .format(target.name, directory), reason='bad-directory')
-
- if not (no_checkout or force) and os.listdir(directory):
- raise StreamError("For element '{}', Directory path is not empty: {}"
- .format(target.name, directory), reason='bad-directory')
-
- # So far this function has tried to catch as many issues as possible with out making any changes
- # Now it dose the bits that can not be made atomic.
- targetGenerator = zip(elements, expanded_directories)
- for target, directory in targetGenerator:
- self._message(MessageType.INFO, "Creating workspace for element {}"
- .format(target.name))
-
- workspace = workspaces.get_workspace(target._get_full_name())
- if workspace:
- workspaces.delete_workspace(target._get_full_name())
- workspaces.save_config()
- shutil.rmtree(directory)
- try:
- os.makedirs(directory, exist_ok=True)
- except OSError as e:
- todo_elements = " ".join([str(target.name) for target, directory_dict in targetGenerator])
- if todo_elements:
- # This output should make creating the remaining workspaces as easy as possible.
- todo_elements = "\nDid not try to create workspaces for " + todo_elements
- raise StreamError("Failed to create workspace directory: {}".format(e) + todo_elements) from e
-
- workspaces.create_workspace(target, directory, checkout=not no_checkout)
- self._message(MessageType.INFO, "Created a workspace for element: {}"
- .format(target._get_full_name()))
-
- # workspace_close
- #
- # Close a project workspace
- #
- # Args:
- # element_name (str): The element name to close the workspace for
- # remove_dir (bool): Whether to remove the associated directory
- #
- def workspace_close(self, element_name, *, remove_dir):
- workspaces = self._context.get_workspaces()
- workspace = workspaces.get_workspace(element_name)
-
- # Remove workspace directory if prompted
- if remove_dir:
- with self._context.timed_activity("Removing workspace directory {}"
- .format(workspace.get_absolute_path())):
- try:
- shutil.rmtree(workspace.get_absolute_path())
- except OSError as e:
- raise StreamError("Could not remove '{}': {}"
- .format(workspace.get_absolute_path(), e)) from e
-
- # Delete the workspace and save the configuration
- workspaces.delete_workspace(element_name)
- workspaces.save_config()
- self._message(MessageType.INFO, "Closed workspace for {}".format(element_name))
-
- # workspace_reset
- #
- # Reset a workspace to its original state, discarding any user
- # changes.
- #
- # Args:
- # targets (list of str): The target elements to reset the workspace for
- # soft (bool): Only reset workspace state
- # track_first (bool): Whether to also track the sources first
- #
- def workspace_reset(self, targets, *, soft, track_first):
-
- if track_first:
- track_targets = targets
- else:
- track_targets = ()
-
- elements, track_elements = self._load(targets, track_targets,
- selection=PipelineSelection.REDIRECT,
- track_selection=PipelineSelection.REDIRECT)
-
- nonexisting = []
- for element in elements:
- if not self.workspace_exists(element.name):
- nonexisting.append(element.name)
- if nonexisting:
- raise StreamError("Workspace does not exist", detail="\n".join(nonexisting))
-
- # Do the tracking first
- if track_first:
- self._fetch(elements, track_elements=track_elements, fetch_original=True)
-
- workspaces = self._context.get_workspaces()
-
- for element in elements:
- workspace = workspaces.get_workspace(element._get_full_name())
- workspace_path = workspace.get_absolute_path()
- if soft:
- workspace.prepared = False
- self._message(MessageType.INFO, "Reset workspace state for {} at: {}"
- .format(element.name, workspace_path))
- continue
-
- with element.timed_activity("Removing workspace directory {}"
- .format(workspace_path)):
- try:
- shutil.rmtree(workspace_path)
- except OSError as e:
- raise StreamError("Could not remove '{}': {}"
- .format(workspace_path, e)) from e
-
- workspaces.delete_workspace(element._get_full_name())
- workspaces.create_workspace(element, workspace_path, checkout=True)
-
- self._message(MessageType.INFO,
- "Reset workspace for {} at: {}".format(element.name,
- workspace_path))
-
- workspaces.save_config()
-
- # workspace_exists
- #
- # Check if a workspace exists
- #
- # Args:
- # element_name (str): The element name to close the workspace for, or None
- #
- # Returns:
- # (bool): True if the workspace exists
- #
- # If None is specified for `element_name`, then this will return
- # True if there are any existing workspaces.
- #
- def workspace_exists(self, element_name=None):
- workspaces = self._context.get_workspaces()
- if element_name:
- workspace = workspaces.get_workspace(element_name)
- if workspace:
- return True
- elif any(workspaces.list()):
- return True
-
- return False
-
- # workspace_is_required()
- #
- # Checks whether the workspace belonging to element_name is required to
- # load the project
- #
- # Args:
- # element_name (str): The element whose workspace may be required
- #
- # Returns:
- # (bool): True if the workspace is required
- def workspace_is_required(self, element_name):
- invoked_elm = self._project.invoked_from_workspace_element()
- return invoked_elm == element_name
-
- # workspace_list
- #
- # Serializes the workspaces and dumps them in YAML to stdout.
- #
- def workspace_list(self):
- workspaces = []
- for element_name, workspace_ in self._context.get_workspaces().list():
- workspace_detail = {
- 'element': element_name,
- 'directory': workspace_.get_absolute_path(),
- }
- workspaces.append(workspace_detail)
-
- _yaml.dump({
- 'workspaces': workspaces
- })
-
- # redirect_element_names()
- #
- # Takes a list of element names and returns a list where elements have been
- # redirected to their source elements if the element file exists, and just
- # the name, if not.
- #
- # Args:
- # elements (list of str): The element names to redirect
- #
- # Returns:
- # (list of str): The element names after redirecting
- #
- def redirect_element_names(self, elements):
- element_dir = self._project.element_path
- load_elements = []
- output_elements = set()
-
- for e in elements:
- element_path = os.path.join(element_dir, e)
- if os.path.exists(element_path):
- load_elements.append(e)
- else:
- output_elements.add(e)
- if load_elements:
- loaded_elements, _ = self._load(load_elements, (),
- selection=PipelineSelection.REDIRECT,
- track_selection=PipelineSelection.REDIRECT)
-
- for e in loaded_elements:
- output_elements.add(e.name)
-
- return list(output_elements)
-
- #############################################################
- # Scheduler API forwarding #
- #############################################################
-
- # running
- #
- # Whether the scheduler is running
- #
- @property
- def running(self):
- return self._scheduler.loop is not None
-
- # suspended
- #
- # Whether the scheduler is currently suspended
- #
- @property
- def suspended(self):
- return self._scheduler.suspended
-
- # terminated
- #
- # Whether the scheduler is currently terminated
- #
- @property
- def terminated(self):
- return self._scheduler.terminated
-
- # elapsed_time
- #
- # Elapsed time since the session start
- #
- @property
- def elapsed_time(self):
- return self._scheduler.elapsed_time()
-
- # terminate()
- #
- # Terminate jobs
- #
- def terminate(self):
- self._scheduler.terminate_jobs()
-
- # quit()
- #
- # Quit the session, this will continue with any ongoing
- # jobs, use Stream.terminate() instead for cancellation
- # of ongoing jobs
- #
- def quit(self):
- self._scheduler.stop_queueing()
-
- # suspend()
- #
- # Context manager to suspend ongoing jobs
- #
- @contextmanager
- def suspend(self):
- with self._scheduler.jobs_suspended():
- yield
-
- #############################################################
- # Private Methods #
- #############################################################
-
- # _load()
- #
- # A convenience method for loading element lists
- #
- # If `targets` is not empty used project configuration will be
- # fully loaded. If `targets` is empty, tracking will still be
- # resolved for elements in `track_targets`, but no build pipeline
- # will be resolved. This is behavior is import for track() to
- # not trigger full loading of project configuration.
- #
- # Args:
- # targets (list of str): Main targets to load
- # track_targets (list of str): Tracking targets
- # selection (PipelineSelection): The selection mode for the specified targets
- # track_selection (PipelineSelection): The selection mode for the specified tracking targets
- # except_targets (list of str): Specified targets to except from fetching
- # track_except_targets (list of str): Specified targets to except from fetching
- # track_cross_junctions (bool): Whether tracking should cross junction boundaries
- # ignore_junction_targets (bool): Whether junction targets should be filtered out
- # use_artifact_config (bool): Whether to initialize artifacts with the config
- # use_source_config (bool): Whether to initialize remote source caches with the config
- # artifact_remote_url (str): A remote url for initializing the artifacts
- # source_remote_url (str): A remote url for initializing source caches
- # fetch_subprojects (bool): Whether to fetch subprojects while loading
- #
- # Returns:
- # (list of Element): The primary element selection
- # (list of Element): The tracking element selection
- #
- def _load(self, targets, track_targets, *,
- selection=PipelineSelection.NONE,
- track_selection=PipelineSelection.NONE,
- except_targets=(),
- track_except_targets=(),
- track_cross_junctions=False,
- ignore_junction_targets=False,
- use_artifact_config=False,
- use_source_config=False,
- artifact_remote_url=None,
- source_remote_url=None,
- fetch_subprojects=False,
- dynamic_plan=False,
- load_refs=False):
-
- # Classify element and artifact strings
- target_elements, target_artifacts = self._classify_artifacts(targets)
-
- if target_artifacts and not load_refs:
- detail = '\n'.join(target_artifacts)
- raise ArtifactElementError("Cannot perform this operation with artifact refs:", detail=detail)
-
- # Load rewritable if we have any tracking selection to make
- rewritable = False
- if track_targets:
- rewritable = True
-
- # Load all target elements
- elements, except_elements, track_elements, track_except_elements = \
- self._pipeline.load([target_elements, except_targets, track_targets, track_except_targets],
- rewritable=rewritable,
- fetch_subprojects=fetch_subprojects)
-
- # Obtain the ArtifactElement objects
- artifacts = [self._project.create_artifact_element(ref) for ref in target_artifacts]
-
- # Optionally filter out junction elements
- if ignore_junction_targets:
- elements = [e for e in elements if e.get_kind() != 'junction']
-
- # Hold on to the targets
- self.targets = elements + artifacts
-
- # Here we should raise an error if the track_elements targets
- # are not dependencies of the primary targets, this is not
- # supported.
- #
- # This can happen with `bst build --track`
- #
- if targets and not self._pipeline.targets_include(elements, track_elements):
- raise StreamError("Specified tracking targets that are not "
- "within the scope of primary targets")
-
- # First take care of marking tracking elements, this must be
- # done before resolving element states.
- #
- assert track_selection != PipelineSelection.PLAN
-
- # Tracked elements are split by owner projects in order to
- # filter cross junctions tracking dependencies on their
- # respective project.
- track_projects = {}
- for element in track_elements:
- project = element._get_project()
- if project not in track_projects:
- track_projects[project] = [element]
- else:
- track_projects[project].append(element)
-
- track_selected = []
-
- for project, project_elements in track_projects.items():
- selected = self._pipeline.get_selection(project_elements, track_selection)
- selected = self._pipeline.track_cross_junction_filter(project,
- selected,
- track_cross_junctions)
- track_selected.extend(selected)
-
- track_selected = self._pipeline.except_elements(track_elements,
- track_selected,
- track_except_elements)
-
- for element in track_selected:
- element._schedule_tracking()
-
- if not targets:
- self._pipeline.resolve_elements(track_selected)
- return [], track_selected
-
- # ArtifactCache.setup_remotes expects all projects to be fully loaded
- for project in self._context.get_projects():
- project.ensure_fully_loaded()
-
- # Connect to remote caches, this needs to be done before resolving element state
- self._artifacts.setup_remotes(use_config=use_artifact_config, remote_url=artifact_remote_url)
- self._sourcecache.setup_remotes(use_config=use_source_config, remote_url=source_remote_url)
-
- # Now move on to loading primary selection.
- #
- self._pipeline.resolve_elements(self.targets)
- selected = self._pipeline.get_selection(self.targets, selection, silent=False)
- selected = self._pipeline.except_elements(self.targets,
- selected,
- except_elements)
-
- # Set the "required" artifacts that should not be removed
- # while this pipeline is active
- #
- # It must include all the artifacts which are required by the
- # final product. Note that this is a superset of the build plan.
- #
- # use partial as we send this to both Artifact and Source caches
- required_elements = functools.partial(self._pipeline.dependencies, elements, Scope.ALL)
- self._artifacts.mark_required_elements(required_elements())
-
- self._sourcecache.mark_required_sources(
- itertools.chain.from_iterable(
- [element.sources() for element in required_elements()]))
-
- if selection == PipelineSelection.PLAN and dynamic_plan:
- # We use a dynamic build plan, only request artifacts of top-level targets,
- # others are requested dynamically as needed.
- # This avoids pulling, fetching, or building unneeded build-only dependencies.
- for element in elements:
- element._set_required()
- else:
- for element in selected:
- element._set_required()
-
- return selected, track_selected
-
- # _message()
- #
- # Local message propagator
- #
- def _message(self, message_type, message, **kwargs):
- args = dict(kwargs)
- self._context.message(
- Message(None, message_type, message, **args))
-
- # _add_queue()
- #
- # Adds a queue to the stream
- #
- # Args:
- # queue (Queue): Queue to add to the pipeline
- # track (bool): Whether this is the tracking queue
- #
- def _add_queue(self, queue, *, track=False):
- self.queues.append(queue)
-
- if not (track or self._first_non_track_queue):
- self._first_non_track_queue = queue
-
- # _enqueue_plan()
- #
- # Enqueues planned elements to the specified queue.
- #
- # Args:
- # plan (list of Element): The list of elements to be enqueued
- # queue (Queue): The target queue, defaults to the first non-track queue
- #
- def _enqueue_plan(self, plan, *, queue=None):
- queue = queue or self._first_non_track_queue
-
- queue.enqueue(plan)
- self.session_elements += plan
-
- # _run()
- #
- # Common function for running the scheduler
- #
- def _run(self):
-
- # Inform the frontend of the full list of elements
- # and the list of elements which will be processed in this run
- #
- self.total_elements = list(self._pipeline.dependencies(self.targets, Scope.ALL))
-
- if self._session_start_callback is not None:
- self._session_start_callback()
-
- _, status = self._scheduler.run(self.queues)
-
- if status == SchedStatus.ERROR:
- raise StreamError()
- elif status == SchedStatus.TERMINATED:
- raise StreamError(terminated=True)
-
- # _fetch()
- #
- # Performs the fetch job, the body of this function is here because
- # it is shared between a few internals.
- #
- # Args:
- # elements (list of Element): Elements to fetch
- # track_elements (list of Element): Elements to track
- # fetch_original (Bool): Whether to fetch original unstaged
- #
- def _fetch(self, elements, *, track_elements=None, fetch_original=False):
-
- if track_elements is None:
- track_elements = []
-
- # Subtract the track elements from the fetch elements, they will be added separately
- fetch_plan = self._pipeline.subtract_elements(elements, track_elements)
-
- # Assert consistency for the fetch elements
- self._pipeline.assert_consistent(fetch_plan)
-
- # Filter out elements with cached sources, only from the fetch plan
- # let the track plan resolve new refs.
- cached = [elt for elt in fetch_plan
- if not elt._should_fetch(fetch_original)]
- fetch_plan = self._pipeline.subtract_elements(fetch_plan, cached)
-
- # Construct queues, enqueue and run
- #
- track_queue = None
- if track_elements:
- track_queue = TrackQueue(self._scheduler)
- self._add_queue(track_queue, track=True)
- self._add_queue(FetchQueue(self._scheduler, fetch_original=fetch_original))
-
- if track_elements:
- self._enqueue_plan(track_elements, queue=track_queue)
-
- self._enqueue_plan(fetch_plan)
- self._run()
-
- # _check_location_writable()
- #
- # Check if given location is writable.
- #
- # Args:
- # location (str): Destination path
- # force (bool): Allow files to be overwritten
- # tar (bool): Whether destination is a tarball
- #
- # Raises:
- # (StreamError): If the destination is not writable
- #
- def _check_location_writable(self, location, force=False, tar=False):
- if not tar:
- try:
- os.makedirs(location, exist_ok=True)
- except OSError as e:
- raise StreamError("Failed to create destination directory: '{}'"
- .format(e)) from e
- if not os.access(location, os.W_OK):
- raise StreamError("Destination directory '{}' not writable"
- .format(location))
- if not force and os.listdir(location):
- raise StreamError("Destination directory '{}' not empty"
- .format(location))
- elif os.path.exists(location) and location != '-':
- if not os.access(location, os.W_OK):
- raise StreamError("Output file '{}' not writable"
- .format(location))
- if not force and os.path.exists(location):
- raise StreamError("Output file '{}' already exists"
- .format(location))
-
- # Helper function for checkout()
- #
- def _checkout_hardlinks(self, sandbox_vroot, directory):
- try:
- utils.safe_remove(directory)
- except OSError as e:
- raise StreamError("Failed to remove checkout directory: {}".format(e)) from e
-
- sandbox_vroot.export_files(directory, can_link=True, can_destroy=True)
-
- # Helper function for source_checkout()
- def _source_checkout(self, elements,
- location=None,
- force=False,
- deps='none',
- fetch=False,
- tar=False,
- include_build_scripts=False):
- location = os.path.abspath(location)
- location_parent = os.path.abspath(os.path.join(location, ".."))
-
- # Stage all our sources in a temporary directory. The this
- # directory can be used to either construct a tarball or moved
- # to the final desired location.
- temp_source_dir = tempfile.TemporaryDirectory(dir=location_parent)
- try:
- self._write_element_sources(temp_source_dir.name, elements)
- if include_build_scripts:
- self._write_build_scripts(temp_source_dir.name, elements)
- if tar:
- self._create_tarball(temp_source_dir.name, location)
- else:
- self._move_directory(temp_source_dir.name, location, force)
- except OSError as e:
- raise StreamError("Failed to checkout sources to {}: {}"
- .format(location, e)) from e
- finally:
- with suppress(FileNotFoundError):
- temp_source_dir.cleanup()
-
- # Move a directory src to dest. This will work across devices and
- # may optionaly overwrite existing files.
- def _move_directory(self, src, dest, force=False):
- def is_empty_dir(path):
- return os.path.isdir(dest) and not os.listdir(dest)
-
- try:
- os.rename(src, dest)
- return
- except OSError:
- pass
-
- if force or is_empty_dir(dest):
- try:
- utils.link_files(src, dest)
- except utils.UtilError as e:
- raise StreamError("Failed to move directory: {}".format(e)) from e
-
- # Write the element build script to the given directory
- def _write_element_script(self, directory, element):
- try:
- element._write_script(directory)
- except ImplError:
- return False
- return True
-
- # Write all source elements to the given directory
- def _write_element_sources(self, directory, elements):
- for element in elements:
- element_source_dir = self._get_element_dirname(directory, element)
- if list(element.sources()):
- os.makedirs(element_source_dir)
- element._stage_sources_at(element_source_dir, mount_workspaces=False)
-
- # Create a tarball from the content of directory
- def _create_tarball(self, directory, tar_name):
- try:
- with utils.save_file_atomic(tar_name, mode='wb') as f:
- # This TarFile does not need to be explicitly closed
- # as the underlying file object will be closed be the
- # save_file_atomic contect manager
- tarball = tarfile.open(fileobj=f, mode='w')
- for item in os.listdir(str(directory)):
- file_to_add = os.path.join(directory, item)
- tarball.add(file_to_add, arcname=item)
- except OSError as e:
- raise StreamError("Failed to create tar archive: {}".format(e)) from e
-
- # Write all the build_scripts for elements in the directory location
- def _write_build_scripts(self, location, elements):
- for element in elements:
- self._write_element_script(location, element)
- self._write_master_build_script(location, elements)
-
- # Write a master build script to the sandbox
- def _write_master_build_script(self, directory, elements):
-
- module_string = ""
- for element in elements:
- module_string += shlex.quote(element.normal_name) + " "
-
- script_path = os.path.join(directory, "build.sh")
-
- with open(_site.build_all_template, "r") as f:
- script_template = f.read()
-
- with utils.save_file_atomic(script_path, "w") as script:
- script.write(script_template.format(modules=module_string))
-
- os.chmod(script_path, stat.S_IEXEC | stat.S_IREAD)
-
- # Collect the sources in the given sandbox into a tarfile
- def _collect_sources(self, directory, tar_name, element_name, compression):
- with self._context.timed_activity("Creating tarball {}".format(tar_name)):
- if compression == "none":
- permissions = "w:"
- else:
- permissions = "w:" + compression
-
- with tarfile.open(tar_name, permissions) as tar:
- tar.add(directory, arcname=element_name)
-
- # _get_element_dirname()
- #
- # Get path to directory for an element based on its normal name.
- #
- # For cross-junction elements, the path will be prefixed with the name
- # of the junction element.
- #
- # Args:
- # directory (str): path to base directory
- # element (Element): the element
- #
- # Returns:
- # (str): Path to directory for this element
- #
- def _get_element_dirname(self, directory, element):
- parts = [element.normal_name]
- while element._get_project() != self._project:
- element = element._get_project().junction
- parts.append(element.normal_name)
-
- return os.path.join(directory, *reversed(parts))
-
- # _buildtree_pull_required()
- #
- # Check if current task, given config, requires element buildtree artifact
- #
- # Args:
- # elements (list): elements to check if buildtrees are required
- #
- # Returns:
- # (list): elements requiring buildtrees
- #
- def _buildtree_pull_required(self, elements):
- required_list = []
-
- # If context is set to not pull buildtrees, or no fetch remotes, return empty list
- if not self._context.pull_buildtrees or not self._artifacts.has_fetch_remotes():
- return required_list
-
- for element in elements:
- # Check if element is partially cached without its buildtree, as the element
- # artifact may not be cached at all
- if element._cached() and not element._cached_buildtree() and element._buildtree_exists():
- required_list.append(element)
-
- return required_list
-
- # _classify_artifacts()
- #
- # Split up a list of targets into element names and artifact refs
- #
- # Args:
- # targets (list): A list of targets
- #
- # Returns:
- # (list): element names present in the targets
- # (list): artifact refs present in the targets
- #
- def _classify_artifacts(self, targets):
- element_targets = []
- artifact_refs = []
- element_globs = []
- artifact_globs = []
-
- for target in targets:
- if target.endswith('.bst'):
- if any(c in "*?[" for c in target):
- element_globs.append(target)
- else:
- element_targets.append(target)
- else:
- if any(c in "*?[" for c in target):
- artifact_globs.append(target)
- else:
- try:
- verify_artifact_ref(target)
- except ArtifactElementError:
- element_targets.append(target)
- continue
- artifact_refs.append(target)
-
- if element_globs:
- for dirpath, _, filenames in os.walk(self._project.element_path):
- for filename in filenames:
- element_path = os.path.join(dirpath, filename)
- length = len(self._project.element_path) + 1
- element_path = element_path[length:] # Strip out the element_path
-
- if any(fnmatch(element_path, glob) for glob in element_globs):
- element_targets.append(element_path)
-
- if artifact_globs:
- for glob in artifact_globs:
- artifact_refs.extend(self._artifacts.list_artifacts(glob=glob))
- if not artifact_refs:
- self._message(MessageType.WARN, "No artifacts found for globs: {}".format(', '.join(artifact_globs)))
-
- return element_targets, artifact_refs
diff --git a/buildstream/_variables.py b/buildstream/_variables.py
deleted file mode 100644
index 74314cf1f..000000000
--- a/buildstream/_variables.py
+++ /dev/null
@@ -1,251 +0,0 @@
-#
-# Copyright (C) 2016 Codethink Limited
-# Copyright (C) 2019 Bloomberg L.P.
-#
-# 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>
-# Daniel Silverstone <daniel.silverstone@codethink.co.uk>
-
-import re
-import sys
-
-from ._exceptions import LoadError, LoadErrorReason
-from . import _yaml
-
-# Variables are allowed to have dashes here
-#
-PARSE_EXPANSION = re.compile(r"\%\{([a-zA-Z][a-zA-Z0-9_-]*)\}")
-
-
-# Throughout this code you will see variables named things like `expstr`.
-# These hold data structures called "expansion strings" and are the parsed
-# form of the strings which are the input to this subsystem. Strings
-# such as "Hello %{name}, how are you?" are parsed into the form:
-# (3, ["Hello ", "name", ", how are you?"])
-# i.e. a tuple of an integer and a list, where the integer is the cached
-# length of the list, and the list consists of one or more strings.
-# Strings in even indices of the list (0, 2, 4, etc) are constants which
-# are copied into the output of the expansion algorithm. Strings in the
-# odd indices (1, 3, 5, etc) are the names of further expansions to make.
-# In the example above, first "Hello " is copied, then "name" is expanded
-# and so must be another named expansion string passed in to the constructor
-# of the Variables class, and whatever is yielded from the expansion of "name"
-# is added to the concatenation for the result. Finally ", how are you?" is
-# copied in and the whole lot concatenated for return.
-#
-# To see how strings are parsed, see `_parse_expstr()` after the class, and
-# to see how expansion strings are expanded, see `_expand_expstr()` after that.
-
-
-# The Variables helper object will resolve the variable references in
-# the given dictionary, expecting that any dictionary values which contain
-# variable references can be resolved from the same dictionary.
-#
-# Each Element creates its own Variables instance to track the configured
-# variable settings for the element.
-#
-# Args:
-# node (dict): A node loaded and composited with yaml tools
-#
-# Raises:
-# LoadError, if unresolved variables, or cycles in resolution, occur.
-#
-class Variables():
-
- def __init__(self, node):
-
- self.original = node
- self._expstr_map = self._resolve(node)
- self.flat = self._flatten()
-
- # subst():
- #
- # Substitutes any variables in 'string' and returns the result.
- #
- # Args:
- # (string): The string to substitute
- #
- # Returns:
- # (string): The new string with any substitutions made
- #
- # Raises:
- # LoadError, if the string contains unresolved variable references.
- #
- def subst(self, string):
- expstr = _parse_expstr(string)
-
- try:
- return _expand_expstr(self._expstr_map, expstr)
- except KeyError:
- unmatched = []
-
- # Look for any unmatched variable names in the expansion string
- for var in expstr[1][1::2]:
- if var not in self._expstr_map:
- unmatched.append(var)
-
- if unmatched:
- message = "Unresolved variable{}: {}".format(
- "s" if len(unmatched) > 1 else "",
- ", ".join(unmatched)
- )
-
- raise LoadError(LoadErrorReason.UNRESOLVED_VARIABLE, message)
- # Otherwise, re-raise the KeyError since it clearly came from some
- # other unknowable cause.
- raise
-
- # Variable resolving code
- #
- # Here we resolve all of our inputs into a dictionary, ready for use
- # in subst()
- def _resolve(self, node):
- # Special case, if notparallel is specified in the variables for this
- # element, then override max-jobs to be 1.
- # Initialize it as a string as all variables are processed as strings.
- #
- if _yaml.node_get(node, bool, 'notparallel', default_value=False):
- _yaml.node_set(node, 'max-jobs', str(1))
-
- ret = {}
- for key, value in _yaml.node_items(node):
- value = _yaml.node_get(node, str, key)
- ret[sys.intern(key)] = _parse_expstr(value)
- return ret
-
- def _check_for_missing(self):
- # First the check for anything unresolvable
- summary = []
- for key, expstr in self._expstr_map.items():
- for var in expstr[1][1::2]:
- if var not in self._expstr_map:
- line = " unresolved variable '{unmatched}' in declaration of '{variable}' at: {provenance}"
- provenance = _yaml.node_get_provenance(self.original, key)
- summary.append(line.format(unmatched=var, variable=key, provenance=provenance))
- if summary:
- raise LoadError(LoadErrorReason.UNRESOLVED_VARIABLE,
- "Failed to resolve one or more variable:\n{}\n".format("\n".join(summary)))
-
- def _check_for_cycles(self):
- # And now the cycle checks
- def cycle_check(expstr, visited, cleared):
- for var in expstr[1][1::2]:
- if var in cleared:
- continue
- if var in visited:
- raise LoadError(LoadErrorReason.RECURSIVE_VARIABLE,
- "{}: ".format(_yaml.node_get_provenance(self.original, var)) +
- ("Variable '{}' expands to contain a reference to itself. " +
- "Perhaps '{}' contains '%{{{}}}").format(var, visited[-1], var))
- visited.append(var)
- cycle_check(self._expstr_map[var], visited, cleared)
- visited.pop()
- cleared.add(var)
-
- cleared = set()
- for key, expstr in self._expstr_map.items():
- if key not in cleared:
- cycle_check(expstr, [key], cleared)
-
- # _flatten():
- #
- # Turn our dictionary of expansion strings into a flattened dict
- # so that we can run expansions faster in the future
- #
- # Raises:
- # LoadError, if the string contains unresolved variable references or
- # if cycles are detected in the variable references
- #
- def _flatten(self):
- flat = {}
- try:
- for key, expstr in self._expstr_map.items():
- if expstr[0] > 1:
- expstr = (1, [sys.intern(_expand_expstr(self._expstr_map, expstr))])
- self._expstr_map[key] = expstr
- flat[key] = expstr[1][0]
- except KeyError:
- self._check_for_missing()
- raise
- except RecursionError:
- self._check_for_cycles()
- raise
- return flat
-
-
-# Cache for the parsed expansion strings. While this is nominally
-# something which might "waste" memory, in reality each of these
-# will live as long as the element which uses it, which is the
-# vast majority of the memory usage across the execution of BuildStream.
-PARSE_CACHE = {
- # Prime the cache with the empty string since otherwise that can
- # cause issues with the parser, complications to which cause slowdown
- "": (1, [""]),
-}
-
-
-# Helper to parse a string into an expansion string tuple, caching
-# the results so that future parse requests don't need to think about
-# the string
-def _parse_expstr(instr):
- try:
- return PARSE_CACHE[instr]
- except KeyError:
- # This use of the regex turns a string like "foo %{bar} baz" into
- # a list ["foo ", "bar", " baz"]
- splits = PARSE_EXPANSION.split(instr)
- # If an expansion ends the string, we get an empty string on the end
- # which we can optimise away, making the expansion routines not need
- # a test for this.
- if splits[-1] == '':
- splits = splits[:-1]
- # Cache an interned copy of this. We intern it to try and reduce the
- # memory impact of the cache. It seems odd to cache the list length
- # but this is measurably cheaper than calculating it each time during
- # string expansion.
- PARSE_CACHE[instr] = (len(splits), [sys.intern(s) for s in splits])
- return PARSE_CACHE[instr]
-
-
-# Helper to expand a given top level expansion string tuple in the context
-# of the given dictionary of expansion strings.
-#
-# Note: Will raise KeyError if any expansion is missing
-def _expand_expstr(content, topvalue):
- # Short-circuit constant strings
- if topvalue[0] == 1:
- return topvalue[1][0]
-
- # Short-circuit strings which are entirely an expansion of another variable
- # e.g. "%{another}"
- if topvalue[0] == 2 and topvalue[1][0] == "":
- return _expand_expstr(content, content[topvalue[1][1]])
-
- # Otherwise process fully...
- def internal_expand(value):
- (expansion_len, expansion_bits) = value
- idx = 0
- while idx < expansion_len:
- # First yield any constant string content
- yield expansion_bits[idx]
- idx += 1
- # Now, if there is an expansion variable left to expand, yield
- # the expansion of that variable too
- if idx < expansion_len:
- yield from internal_expand(content[expansion_bits[idx]])
- idx += 1
-
- return "".join(internal_expand(topvalue))
diff --git a/buildstream/_version.py b/buildstream/_version.py
deleted file mode 100644
index 03f946cb8..000000000
--- a/buildstream/_version.py
+++ /dev/null
@@ -1,522 +0,0 @@
-# pylint: skip-file
-
-# This file helps to compute a version number in source trees obtained from
-# git-archive tarball (such as those provided by githubs download-from-tag
-# feature). Distribution tarballs (built by setup.py sdist) and build
-# directories (produced by setup.py build) will contain a much shorter file
-# that just contains the computed version number.
-
-# This file is released into the public domain. Generated by
-# versioneer-0.18 (https://github.com/warner/python-versioneer)
-
-"""Git implementation of _version.py."""
-
-import errno
-import os
-import re
-import subprocess
-import sys
-
-
-def get_keywords():
- """Get the keywords needed to look up the version information."""
- # these strings will be replaced by git during git-archive.
- # setup.py/versioneer.py will grep for the variable names, so they must
- # each be defined on a line of their own. _version.py will just call
- # get_keywords().
- git_refnames = "$Format:%d$"
- git_full = "$Format:%H$"
- git_date = "$Format:%ci$"
- keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
- return keywords
-
-
-class VersioneerConfig:
- """Container for Versioneer configuration parameters."""
-
-
-def get_config():
- """Create, populate and return the VersioneerConfig() object."""
- # these strings are filled in when 'setup.py versioneer' creates
- # _version.py
- cfg = VersioneerConfig()
- cfg.VCS = "git"
- cfg.style = "pep440"
- cfg.tag_prefix = ""
- cfg.tag_regex = "*.*.*"
- cfg.parentdir_prefix = "BuildStream-"
- cfg.versionfile_source = "buildstream/_version.py"
- cfg.verbose = False
- return cfg
-
-
-class NotThisMethod(Exception):
- """Exception raised if a method is not valid for the current scenario."""
-
-
-LONG_VERSION_PY = {}
-HANDLERS = {}
-
-
-def register_vcs_handler(vcs, method): # decorator
- """Decorator to mark a method as the handler for a particular VCS."""
- def decorate(f):
- """Store f in HANDLERS[vcs][method]."""
- if vcs not in HANDLERS:
- HANDLERS[vcs] = {}
- HANDLERS[vcs][method] = f
- return f
- return decorate
-
-
-def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False,
- env=None):
- """Call the given command(s)."""
- assert isinstance(commands, list)
- p = None
- for c in commands:
- try:
- dispcmd = str([c] + args)
- # remember shell=False, so use git.cmd on windows, not just git
- p = subprocess.Popen([c] + args, cwd=cwd, env=env,
- stdout=subprocess.PIPE,
- stderr=(subprocess.PIPE if hide_stderr
- else None))
- break
- except EnvironmentError:
- e = sys.exc_info()[1]
- if e.errno == errno.ENOENT:
- continue
- if verbose:
- print("unable to run %s" % dispcmd)
- print(e)
- return None, None
- else:
- if verbose:
- print("unable to find command, tried %s" % (commands,))
- return None, None
- stdout = p.communicate()[0].strip()
- if sys.version_info[0] >= 3:
- stdout = stdout.decode()
- if p.returncode != 0:
- if verbose:
- print("unable to run %s (error)" % dispcmd)
- print("stdout was %s" % stdout)
- return None, p.returncode
- return stdout, p.returncode
-
-
-def versions_from_parentdir(parentdir_prefix, root, verbose):
- """Try to determine the version from the parent directory name.
-
- Source tarballs conventionally unpack into a directory that includes both
- the project name and a version string. We will also support searching up
- two directory levels for an appropriately named parent directory
- """
- rootdirs = []
-
- for i in range(3):
- dirname = os.path.basename(root)
- if dirname.startswith(parentdir_prefix):
- return {"version": dirname[len(parentdir_prefix):],
- "full-revisionid": None,
- "dirty": False, "error": None, "date": None}
- else:
- rootdirs.append(root)
- root = os.path.dirname(root) # up a level
-
- if verbose:
- print("Tried directories %s but none started with prefix %s" %
- (str(rootdirs), parentdir_prefix))
- raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
-
-
-@register_vcs_handler("git", "get_keywords")
-def git_get_keywords(versionfile_abs):
- """Extract version information from the given file."""
- # the code embedded in _version.py can just fetch the value of these
- # keywords. When used from setup.py, we don't want to import _version.py,
- # so we do it with a regexp instead. This function is not used from
- # _version.py.
- keywords = {}
- try:
- f = open(versionfile_abs, "r")
- for line in f.readlines():
- if line.strip().startswith("git_refnames ="):
- mo = re.search(r'=\s*"(.*)"', line)
- if mo:
- keywords["refnames"] = mo.group(1)
- if line.strip().startswith("git_full ="):
- mo = re.search(r'=\s*"(.*)"', line)
- if mo:
- keywords["full"] = mo.group(1)
- if line.strip().startswith("git_date ="):
- mo = re.search(r'=\s*"(.*)"', line)
- if mo:
- keywords["date"] = mo.group(1)
- f.close()
- except EnvironmentError:
- pass
- return keywords
-
-
-@register_vcs_handler("git", "keywords")
-def git_versions_from_keywords(keywords, tag_prefix, verbose):
- """Get version information from git keywords."""
- if not keywords:
- raise NotThisMethod("no keywords at all, weird")
- date = keywords.get("date")
- if date is not None:
- # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
- # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
- # -like" string, which we must then edit to make compliant), because
- # it's been around since git-1.5.3, and it's too difficult to
- # discover which version we're using, or to work around using an
- # older one.
- date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
- refnames = keywords["refnames"].strip()
- if refnames.startswith("$Format"):
- if verbose:
- print("keywords are unexpanded, not using")
- raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
- refs = set([r.strip() for r in refnames.strip("()").split(",")])
- # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
- # just "foo-1.0". If we see a "tag: " prefix, prefer those.
- TAG = "tag: "
- tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
- if not tags:
- # Either we're using git < 1.8.3, or there really are no tags. We use
- # a heuristic: assume all version tags have a digit. The old git %d
- # expansion behaves like git log --decorate=short and strips out the
- # refs/heads/ and refs/tags/ prefixes that would let us distinguish
- # between branches and tags. By ignoring refnames without digits, we
- # filter out many common branch names like "release" and
- # "stabilization", as well as "HEAD" and "master".
- tags = set([r for r in refs if re.search(r'\d', r)])
- if verbose:
- print("discarding '%s', no digits" % ",".join(refs - tags))
- if verbose:
- print("likely tags: %s" % ",".join(sorted(tags)))
- for ref in sorted(tags):
- # sorting will prefer e.g. "2.0" over "2.0rc1"
- if ref.startswith(tag_prefix):
- r = ref[len(tag_prefix):]
- if verbose:
- print("picking %s" % r)
- return {"version": r,
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": None,
- "date": date}
- # no suitable tags, so version is "0+unknown", but full hex is still there
- if verbose:
- print("no suitable tags, using unknown + full revision id")
- return {"version": "0+unknown",
- "full-revisionid": keywords["full"].strip(),
- "dirty": False, "error": "no suitable tags", "date": None}
-
-
-@register_vcs_handler("git", "pieces_from_vcs")
-def git_pieces_from_vcs(tag_prefix, tag_regex, root, verbose, run_command=run_command):
- """Get version from 'git describe' in the root of the source tree.
-
- This only gets called if the git-archive 'subst' keywords were *not*
- expanded, and _version.py hasn't already been rewritten with a short
- version string, meaning we're inside a checked out source tree.
- """
- GITS = ["git"]
- if sys.platform == "win32":
- GITS = ["git.cmd", "git.exe"]
-
- out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root,
- hide_stderr=True)
- if rc != 0:
- if verbose:
- print("Directory %s not under git control" % root)
- raise NotThisMethod("'git rev-parse --git-dir' returned error")
-
- # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
- # if there isn't one, this yields HEX[-dirty] (no NUM)
- describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty",
- "--always", "--long",
- "--match", "%s%s" % (tag_prefix, tag_regex)],
- cwd=root)
- # --long was added in git-1.5.5
- if describe_out is None:
- raise NotThisMethod("'git describe' failed")
- describe_out = describe_out.strip()
- full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
- if full_out is None:
- raise NotThisMethod("'git rev-parse' failed")
- full_out = full_out.strip()
-
- pieces = {}
- pieces["long"] = full_out
- pieces["short"] = full_out[:7] # maybe improved later
- pieces["error"] = None
-
- # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
- # TAG might have hyphens.
- git_describe = describe_out
-
- # look for -dirty suffix
- dirty = git_describe.endswith("-dirty")
- pieces["dirty"] = dirty
- if dirty:
- git_describe = git_describe[:git_describe.rindex("-dirty")]
-
- # now we have TAG-NUM-gHEX or HEX
-
- if "-" in git_describe:
- # TAG-NUM-gHEX
- mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
- if not mo:
- # unparseable. Maybe git-describe is misbehaving?
- pieces["error"] = ("unable to parse git-describe output: '%s'"
- % describe_out)
- return pieces
-
- # tag
- full_tag = mo.group(1)
- if not full_tag.startswith(tag_prefix):
- if verbose:
- fmt = "tag '%s' doesn't start with prefix '%s'"
- print(fmt % (full_tag, tag_prefix))
- pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
- % (full_tag, tag_prefix))
- return pieces
- pieces["closest-tag"] = full_tag[len(tag_prefix):]
-
- # distance: number of commits since tag
- pieces["distance"] = int(mo.group(2))
-
- # commit: short hex revision ID
- pieces["short"] = mo.group(3)
-
- else:
- # HEX: no tags
- pieces["closest-tag"] = None
- count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"],
- cwd=root)
- pieces["distance"] = int(count_out) # total number of commits
-
- # commit date: see ISO-8601 comment in git_versions_from_keywords()
- date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"],
- cwd=root)[0].strip()
- pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
-
- return pieces
-
-
-def plus_or_dot(pieces):
- """Return a + if we don't already have one, else return a ."""
- if "+" in pieces.get("closest-tag", ""):
- return "."
- return "+"
-
-
-def render_pep440(pieces):
- """Build up version string, with post-release "local version identifier".
-
- Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
- get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
-
- Exceptions:
- 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
- """
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"] or pieces["dirty"]:
- rendered += plus_or_dot(pieces)
- rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
- if pieces["dirty"]:
- rendered += ".dirty"
- else:
- # exception #1
- rendered = "0+untagged.%d.g%s" % (pieces["distance"],
- pieces["short"])
- if pieces["dirty"]:
- rendered += ".dirty"
- return rendered
-
-
-def render_pep440_pre(pieces):
- """TAG[.post.devDISTANCE] -- No -dirty.
-
- Exceptions:
- 1: no tags. 0.post.devDISTANCE
- """
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"]:
- rendered += ".post.dev%d" % pieces["distance"]
- else:
- # exception #1
- rendered = "0.post.dev%d" % pieces["distance"]
- return rendered
-
-
-def render_pep440_post(pieces):
- """TAG[.postDISTANCE[.dev0]+gHEX] .
-
- The ".dev0" means dirty. Note that .dev0 sorts backwards
- (a dirty tree will appear "older" than the corresponding clean one),
- but you shouldn't be releasing software with -dirty anyways.
-
- Exceptions:
- 1: no tags. 0.postDISTANCE[.dev0]
- """
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"] or pieces["dirty"]:
- rendered += ".post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- rendered += plus_or_dot(pieces)
- rendered += "g%s" % pieces["short"]
- else:
- # exception #1
- rendered = "0.post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- rendered += "+g%s" % pieces["short"]
- return rendered
-
-
-def render_pep440_old(pieces):
- """TAG[.postDISTANCE[.dev0]] .
-
- The ".dev0" means dirty.
-
- Eexceptions:
- 1: no tags. 0.postDISTANCE[.dev0]
- """
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"] or pieces["dirty"]:
- rendered += ".post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- else:
- # exception #1
- rendered = "0.post%d" % pieces["distance"]
- if pieces["dirty"]:
- rendered += ".dev0"
- return rendered
-
-
-def render_git_describe(pieces):
- """TAG[-DISTANCE-gHEX][-dirty].
-
- Like 'git describe --tags --dirty --always'.
-
- Exceptions:
- 1: no tags. HEX[-dirty] (note: no 'g' prefix)
- """
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- if pieces["distance"]:
- rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
- else:
- # exception #1
- rendered = pieces["short"]
- if pieces["dirty"]:
- rendered += "-dirty"
- return rendered
-
-
-def render_git_describe_long(pieces):
- """TAG-DISTANCE-gHEX[-dirty].
-
- Like 'git describe --tags --dirty --always -long'.
- The distance/hash is unconditional.
-
- Exceptions:
- 1: no tags. HEX[-dirty] (note: no 'g' prefix)
- """
- if pieces["closest-tag"]:
- rendered = pieces["closest-tag"]
- rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
- else:
- # exception #1
- rendered = pieces["short"]
- if pieces["dirty"]:
- rendered += "-dirty"
- return rendered
-
-
-def render(pieces, style):
- """Render the given version pieces into the requested style."""
- if pieces["error"]:
- return {"version": "unknown",
- "full-revisionid": pieces.get("long"),
- "dirty": None,
- "error": pieces["error"],
- "date": None}
-
- if not style or style == "default":
- style = "pep440" # the default
-
- if style == "pep440":
- rendered = render_pep440(pieces)
- elif style == "pep440-pre":
- rendered = render_pep440_pre(pieces)
- elif style == "pep440-post":
- rendered = render_pep440_post(pieces)
- elif style == "pep440-old":
- rendered = render_pep440_old(pieces)
- elif style == "git-describe":
- rendered = render_git_describe(pieces)
- elif style == "git-describe-long":
- rendered = render_git_describe_long(pieces)
- else:
- raise ValueError("unknown style '%s'" % style)
-
- return {"version": rendered, "full-revisionid": pieces["long"],
- "dirty": pieces["dirty"], "error": None,
- "date": pieces.get("date")}
-
-
-def get_versions():
- """Get version information or return default if unable to do so."""
- # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
- # __file__, we can work backwards from there to the root. Some
- # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
- # case we can only use expanded keywords.
-
- cfg = get_config()
- verbose = cfg.verbose
-
- try:
- return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
- verbose)
- except NotThisMethod:
- pass
-
- try:
- root = os.path.realpath(__file__)
- # versionfile_source is the relative path from the top of the source
- # tree (where the .git directory might live) to this file. Invert
- # this to find the root from __file__.
- for i in cfg.versionfile_source.split('/'):
- root = os.path.dirname(root)
- except NameError:
- return {"version": "0+unknown", "full-revisionid": None,
- "dirty": None,
- "error": "unable to find root of source tree",
- "date": None}
-
- try:
- pieces = git_pieces_from_vcs(cfg.tag_prefix, cfg.tag_regex, root, verbose)
- return render(pieces, cfg.style)
- except NotThisMethod:
- pass
-
- try:
- if cfg.parentdir_prefix:
- return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
- except NotThisMethod:
- pass
-
- return {"version": "0+unknown", "full-revisionid": None,
- "dirty": None,
- "error": "unable to compute version", "date": None}
diff --git a/buildstream/_versions.py b/buildstream/_versions.py
deleted file mode 100644
index c439f59fb..000000000
--- a/buildstream/_versions.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Copyright (C) 2018 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>
-
-
-# The base BuildStream format version
-#
-# This version is bumped whenever enhancements are made
-# to the `project.conf` format or the core element format.
-#
-BST_FORMAT_VERSION = 24
-
-
-# The base BuildStream artifact version
-#
-# The artifact version changes whenever the cache key
-# calculation algorithm changes in an incompatible way
-# or if buildstream was changed in a way which can cause
-# the same cache key to produce something that is no longer
-# the same.
-BST_CORE_ARTIFACT_VERSION = 8
diff --git a/buildstream/_workspaces.py b/buildstream/_workspaces.py
deleted file mode 100644
index 9fbfb7e63..000000000
--- a/buildstream/_workspaces.py
+++ /dev/null
@@ -1,650 +0,0 @@
-#
-# Copyright (C) 2018 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 Maat <tristan.maat@codethink.co.uk>
-
-import os
-from . import utils
-from . import _yaml
-
-from ._exceptions import LoadError, LoadErrorReason
-
-
-BST_WORKSPACE_FORMAT_VERSION = 3
-BST_WORKSPACE_PROJECT_FORMAT_VERSION = 1
-WORKSPACE_PROJECT_FILE = ".bstproject.yaml"
-
-
-# WorkspaceProject()
-#
-# An object to contain various helper functions and data required for
-# referring from a workspace back to buildstream.
-#
-# Args:
-# directory (str): The directory that the workspace exists in.
-#
-class WorkspaceProject():
- def __init__(self, directory):
- self._projects = []
- self._directory = directory
-
- # get_default_project_path()
- #
- # Retrieves the default path to a project.
- #
- # Returns:
- # (str): The path to a project
- #
- def get_default_project_path(self):
- return self._projects[0]['project-path']
-
- # get_default_element()
- #
- # Retrieves the name of the element that owns this workspace.
- #
- # Returns:
- # (str): The name of an element
- #
- def get_default_element(self):
- return self._projects[0]['element-name']
-
- # to_dict()
- #
- # Turn the members data into a dict for serialization purposes
- #
- # Returns:
- # (dict): A dict representation of the WorkspaceProject
- #
- def to_dict(self):
- ret = {
- 'projects': self._projects,
- 'format-version': BST_WORKSPACE_PROJECT_FORMAT_VERSION,
- }
- return ret
-
- # from_dict()
- #
- # Loads a new WorkspaceProject from a simple dictionary
- #
- # Args:
- # directory (str): The directory that the workspace exists in
- # dictionary (dict): The dict to generate a WorkspaceProject from
- #
- # Returns:
- # (WorkspaceProject): A newly instantiated WorkspaceProject
- #
- @classmethod
- def from_dict(cls, directory, dictionary):
- # Only know how to handle one format-version at the moment.
- format_version = int(dictionary['format-version'])
- assert format_version == BST_WORKSPACE_PROJECT_FORMAT_VERSION, \
- "Format version {} not found in {}".format(BST_WORKSPACE_PROJECT_FORMAT_VERSION, dictionary)
-
- workspace_project = cls(directory)
- for item in dictionary['projects']:
- workspace_project.add_project(item['project-path'], item['element-name'])
-
- return workspace_project
-
- # load()
- #
- # Loads the WorkspaceProject for a given directory.
- #
- # Args:
- # directory (str): The directory
- # Returns:
- # (WorkspaceProject): The created WorkspaceProject, if in a workspace, or
- # (NoneType): None, if the directory is not inside a workspace.
- #
- @classmethod
- def load(cls, directory):
- workspace_file = os.path.join(directory, WORKSPACE_PROJECT_FILE)
- if os.path.exists(workspace_file):
- data_dict = _yaml.node_sanitize(_yaml.roundtrip_load(workspace_file), dict_type=dict)
- return cls.from_dict(directory, data_dict)
- else:
- return None
-
- # write()
- #
- # Writes the WorkspaceProject to disk
- #
- def write(self):
- os.makedirs(self._directory, exist_ok=True)
- _yaml.dump(self.to_dict(), self.get_filename())
-
- # get_filename()
- #
- # Returns the full path to the workspace local project file
- #
- def get_filename(self):
- return os.path.join(self._directory, WORKSPACE_PROJECT_FILE)
-
- # add_project()
- #
- # Adds an entry containing the project's path and element's name.
- #
- # Args:
- # project_path (str): The path to the project that opened the workspace.
- # element_name (str): The name of the element that the workspace belongs to.
- #
- def add_project(self, project_path, element_name):
- assert (project_path and element_name)
- self._projects.append({'project-path': project_path, 'element-name': element_name})
-
-
-# WorkspaceProjectCache()
-#
-# A class to manage workspace project data for multiple workspaces.
-#
-class WorkspaceProjectCache():
- def __init__(self):
- self._projects = {} # Mapping of a workspace directory to its WorkspaceProject
-
- # get()
- #
- # Returns a WorkspaceProject for a given directory, retrieving from the cache if
- # present.
- #
- # Args:
- # directory (str): The directory to search for a WorkspaceProject.
- #
- # Returns:
- # (WorkspaceProject): The WorkspaceProject that was found for that directory.
- # or (NoneType): None, if no WorkspaceProject can be found.
- #
- def get(self, directory):
- try:
- workspace_project = self._projects[directory]
- except KeyError:
- workspace_project = WorkspaceProject.load(directory)
- if workspace_project:
- self._projects[directory] = workspace_project
-
- return workspace_project
-
- # add()
- #
- # Adds the project path and element name to the WorkspaceProject that exists
- # for that directory
- #
- # Args:
- # directory (str): The directory to search for a WorkspaceProject.
- # project_path (str): The path to the project that refers to this workspace
- # element_name (str): The element in the project that was refers to this workspace
- #
- # Returns:
- # (WorkspaceProject): The WorkspaceProject that was found for that directory.
- #
- def add(self, directory, project_path, element_name):
- workspace_project = self.get(directory)
- if not workspace_project:
- workspace_project = WorkspaceProject(directory)
- self._projects[directory] = workspace_project
-
- workspace_project.add_project(project_path, element_name)
- return workspace_project
-
- # remove()
- #
- # Removes the project path and element name from the WorkspaceProject that exists
- # for that directory.
- #
- # NOTE: This currently just deletes the file, but with support for multiple
- # projects opening the same workspace, this will involve decreasing the count
- # and deleting the file if there are no more projects.
- #
- # Args:
- # directory (str): The directory to search for a WorkspaceProject.
- #
- def remove(self, directory):
- workspace_project = self.get(directory)
- if not workspace_project:
- raise LoadError(LoadErrorReason.MISSING_FILE,
- "Failed to find a {} file to remove".format(WORKSPACE_PROJECT_FILE))
- path = workspace_project.get_filename()
- try:
- os.unlink(path)
- except FileNotFoundError:
- pass
-
-
-# Workspace()
-#
-# An object to contain various helper functions and data required for
-# workspaces.
-#
-# last_successful, path and running_files are intended to be public
-# properties, but may be best accessed using this classes' helper
-# methods.
-#
-# Args:
-# toplevel_project (Project): Top project. Will be used for resolving relative workspace paths.
-# path (str): The path that should host this workspace
-# last_successful (str): The key of the last successful build of this workspace
-# running_files (dict): A dict mapping dependency elements to files
-# changed between failed builds. Should be
-# made obsolete with failed build artifacts.
-#
-class Workspace():
- def __init__(self, toplevel_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 {}
-
- self._toplevel_project = toplevel_project
- self._key = None
-
- # to_dict()
- #
- # Convert a list of members which get serialized to a dict for serialization purposes
- #
- # Returns:
- # (dict) A dict representation of the workspace
- #
- def to_dict(self):
- ret = {
- 'prepared': self.prepared,
- 'path': self._path,
- 'running_files': self.running_files
- }
- if self.last_successful is not None:
- ret["last_successful"] = self.last_successful
- return ret
-
- # from_dict():
- #
- # Loads a new workspace from a simple dictionary, the dictionary
- # is expected to be generated from Workspace.to_dict(), or manually
- # when loading from a YAML file.
- #
- # Args:
- # toplevel_project (Project): Top project. Will be used for resolving relative workspace paths.
- # dictionary: A simple dictionary object
- #
- # Returns:
- # (Workspace): A newly instantiated Workspace
- #
- @classmethod
- def from_dict(cls, toplevel_project, dictionary):
-
- # Just pass the dictionary as kwargs
- return cls(toplevel_project, **dictionary)
-
- # differs()
- #
- # Checks if two workspaces are different in any way.
- #
- # Args:
- # other (Workspace): Another workspace instance
- #
- # Returns:
- # True if the workspace differs from 'other', otherwise False
- #
- def differs(self, other):
- return self.to_dict() != other.to_dict()
-
- # invalidate_key()
- #
- # Invalidate the workspace key, forcing a recalculation next time
- # it is accessed.
- #
- def invalidate_key(self):
- self._key = None
-
- # stage()
- #
- # Stage the workspace to the given directory.
- #
- # Args:
- # directory (str) - The directory into which to stage this workspace
- #
- def stage(self, directory):
- fullpath = self.get_absolute_path()
- if os.path.isdir(fullpath):
- utils.copy_files(fullpath, directory)
- else:
- destfile = os.path.join(directory, os.path.basename(self.get_absolute_path()))
- utils.safe_copy(fullpath, destfile)
-
- # add_running_files()
- #
- # Append a list of files to the running_files for the given
- # dependency. Duplicate files will be ignored.
- #
- # Args:
- # dep_name (str) - The dependency name whose files to append to
- # files (str) - A list of files to append
- #
- def add_running_files(self, dep_name, files):
- if dep_name in self.running_files:
- # ruamel.py cannot serialize sets in python3.4
- to_add = set(files) - set(self.running_files[dep_name])
- self.running_files[dep_name].extend(to_add)
- else:
- self.running_files[dep_name] = list(files)
-
- # clear_running_files()
- #
- # Clear all running files associated with this workspace.
- #
- def clear_running_files(self):
- self.running_files = {}
-
- # get_key()
- #
- # Get a unique key for this workspace.
- #
- # Args:
- # recalculate (bool) - Whether to recalculate the key
- #
- # Returns:
- # (str) A unique key for this workspace
- #
- def get_key(self, recalculate=False):
- def unique_key(filename):
- try:
- stat = os.lstat(filename)
- except OSError as e:
- raise LoadError(LoadErrorReason.MISSING_FILE,
- "Failed to stat file in workspace: {}".format(e))
-
- # Use the mtime of any file with sub second precision
- return stat.st_mtime_ns
-
- if recalculate or self._key is None:
- fullpath = self.get_absolute_path()
-
- excluded_files = (WORKSPACE_PROJECT_FILE,)
-
- # Get a list of tuples of the the project relative paths and fullpaths
- if os.path.isdir(fullpath):
- filelist = utils.list_relative_paths(fullpath)
- filelist = [
- (relpath, os.path.join(fullpath, relpath)) for relpath in filelist
- if relpath not in excluded_files
- ]
- else:
- filelist = [(self.get_absolute_path(), fullpath)]
-
- self._key = [(relpath, unique_key(fullpath)) for relpath, fullpath in filelist]
-
- return self._key
-
- # get_absolute_path():
- #
- # Returns: The absolute path of the element's workspace.
- #
- def get_absolute_path(self):
- return os.path.join(self._toplevel_project.directory, self._path)
-
-
-# Workspaces()
-#
-# A class to manage Workspaces for multiple elements.
-#
-# Args:
-# toplevel_project (Project): Top project used to resolve paths.
-# workspace_project_cache (WorkspaceProjectCache): The cache of WorkspaceProjects
-#
-class Workspaces():
- def __init__(self, toplevel_project, workspace_project_cache):
- self._toplevel_project = toplevel_project
- self._bst_directory = os.path.join(toplevel_project.directory, ".bst")
- self._workspaces = self._load_config()
- self._workspace_project_cache = workspace_project_cache
-
- # list()
- #
- # Generator function to enumerate workspaces.
- #
- # Yields:
- # A tuple in the following format: (str, Workspace), where the
- # first element is the name of the workspaced element.
- def list(self):
- for element in self._workspaces.keys():
- yield (element, self._workspaces[element])
-
- # create_workspace()
- #
- # Create a workspace in the given path for the given element, and potentially
- # checks-out the target into it.
- #
- # Args:
- # target (Element) - The element to create a workspace for
- # path (str) - The path in which the workspace should be kept
- # checkout (bool): Whether to check-out the element's sources into the directory
- #
- def create_workspace(self, target, path, *, checkout):
- element_name = target._get_full_name()
- project_dir = self._toplevel_project.directory
- if path.startswith(project_dir):
- workspace_path = os.path.relpath(path, project_dir)
- else:
- workspace_path = path
-
- self._workspaces[element_name] = Workspace(self._toplevel_project, path=workspace_path)
-
- if checkout:
- with target.timed_activity("Staging sources to {}".format(path)):
- target._open_workspace()
-
- workspace_project = self._workspace_project_cache.add(path, project_dir, element_name)
- project_file_path = workspace_project.get_filename()
-
- if os.path.exists(project_file_path):
- target.warn("{} was staged from this element's sources".format(WORKSPACE_PROJECT_FILE))
- workspace_project.write()
-
- self.save_config()
-
- # get_workspace()
- #
- # Get the path of the workspace source associated with the given
- # element's source at the given index
- #
- # Args:
- # element_name (str) - The element name whose workspace to return
- #
- # Returns:
- # (None|Workspace)
- #
- def get_workspace(self, element_name):
- if element_name not in self._workspaces:
- return None
- return self._workspaces[element_name]
-
- # update_workspace()
- #
- # Update the datamodel with a new Workspace instance
- #
- # Args:
- # element_name (str): The name of the element to update a workspace for
- # workspace_dict (Workspace): A serialized workspace dictionary
- #
- # Returns:
- # (bool): Whether the workspace has changed as a result
- #
- def update_workspace(self, element_name, workspace_dict):
- assert element_name in self._workspaces
-
- workspace = Workspace.from_dict(self._toplevel_project, workspace_dict)
- if self._workspaces[element_name].differs(workspace):
- self._workspaces[element_name] = workspace
- return True
-
- return False
-
- # delete_workspace()
- #
- # Remove the workspace from the workspace element. Note that this
- # does *not* remove the workspace from the stored yaml
- # configuration, call save_config() afterwards.
- #
- # Args:
- # element_name (str) - The element name whose workspace to delete
- #
- def delete_workspace(self, element_name):
- workspace = self.get_workspace(element_name)
- del self._workspaces[element_name]
-
- # Remove from the cache if it exists
- try:
- self._workspace_project_cache.remove(workspace.get_absolute_path())
- except LoadError as e:
- # We might be closing a workspace with a deleted directory
- if e.reason == LoadErrorReason.MISSING_FILE:
- pass
- else:
- raise
-
- # save_config()
- #
- # Dump the current workspace element to the project configuration
- # file. This makes any changes performed with delete_workspace or
- # create_workspace permanent
- #
- def save_config(self):
- assert utils._is_main_process()
-
- config = {
- 'format-version': BST_WORKSPACE_FORMAT_VERSION,
- 'workspaces': {
- element: workspace.to_dict()
- for element, workspace in self._workspaces.items()
- }
- }
- os.makedirs(self._bst_directory, exist_ok=True)
- _yaml.dump(config, self._get_filename())
-
- # _load_config()
- #
- # Loads and parses the workspace configuration
- #
- # Returns:
- # (dict) The extracted workspaces
- #
- # Raises: LoadError if there was a problem with the workspace config
- #
- def _load_config(self):
- workspace_file = self._get_filename()
- try:
- node = _yaml.load(workspace_file)
- except LoadError as e:
- if e.reason == LoadErrorReason.MISSING_FILE:
- # Return an empty dict if there was no workspace file
- return {}
-
- raise
-
- return self._parse_workspace_config(node)
-
- # _parse_workspace_config_format()
- #
- # If workspace config is in old-style format, i.e. it is using
- # source-specific workspaces, try to convert it to element-specific
- # workspaces.
- #
- # Args:
- # workspaces (dict): current workspace config, usually output of _load_workspace_config()
- #
- # Returns:
- # (dict) The extracted workspaces
- #
- # Raises: LoadError if there was a problem with the workspace config
- #
- def _parse_workspace_config(self, workspaces):
- try:
- version = _yaml.node_get(workspaces, int, 'format-version', default_value=0)
- except ValueError:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "Format version is not an integer in workspace configuration")
-
- if version == 0:
- # Pre-versioning format can be of two forms
- for element, config in _yaml.node_items(workspaces):
- if _yaml.is_node(config):
- # Get a dict
- config = _yaml.node_sanitize(config, dict_type=dict)
-
- if isinstance(config, str):
- pass
-
- elif isinstance(config, dict):
- sources = list(config.items())
- if len(sources) > 1:
- detail = "There are multiple workspaces open for '{}'.\n" + \
- "This is not supported anymore.\n" + \
- "Please remove this element from '{}'."
- raise LoadError(LoadErrorReason.INVALID_DATA,
- detail.format(element, self._get_filename()))
-
- _yaml.node_set(workspaces, element, sources[0][1])
-
- else:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "Workspace config is in unexpected format.")
-
- res = {
- element: Workspace(self._toplevel_project, path=config)
- for element, config in _yaml.node_items(workspaces)
- }
-
- elif 1 <= version <= BST_WORKSPACE_FORMAT_VERSION:
- workspaces = _yaml.node_get(workspaces, dict, "workspaces",
- default_value=_yaml.new_empty_node())
- res = {element: self._load_workspace(node)
- for element, node in _yaml.node_items(workspaces)}
-
- else:
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "Workspace configuration format version {} not supported."
- "Your version of buildstream may be too old. Max supported version: {}"
- .format(version, BST_WORKSPACE_FORMAT_VERSION))
-
- return res
-
- # _load_workspace():
- #
- # Loads a new workspace from a YAML node
- #
- # Args:
- # node: A YAML dict
- #
- # Returns:
- # (Workspace): A newly instantiated Workspace
- #
- def _load_workspace(self, 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_sanitize(
- _yaml.node_get(node, dict, 'running_files', default_value=None),
- dict_type=dict),
- }
- return Workspace.from_dict(self._toplevel_project, dictionary)
-
- # _get_filename():
- #
- # Get the workspaces.yml file path.
- #
- # Returns:
- # (str): The path to workspaces.yml file.
- def _get_filename(self):
- return os.path.join(self._bst_directory, "workspaces.yml")
diff --git a/buildstream/_yaml.py b/buildstream/_yaml.py
deleted file mode 100644
index cdab4269e..000000000
--- a/buildstream/_yaml.py
+++ /dev/null
@@ -1,1432 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 Bloomberg LLP
-#
-# 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>
-# Daniel Silverstone <daniel.silverstone@codethink.co.uk>
-# James Ennis <james.ennis@codethink.co.uk>
-
-import sys
-import string
-from contextlib import ExitStack
-from collections import OrderedDict, namedtuple
-from collections.abc import Mapping, Sequence
-from copy import deepcopy
-from itertools import count
-
-from ruamel import yaml
-from ._exceptions import LoadError, LoadErrorReason
-
-
-# Without this, pylint complains about all the `type(foo) is blah` checks
-# because it feels isinstance() is more idiomatic. Sadly, it is much slower to
-# do `isinstance(foo, blah)` for reasons I am unable to fathom. As such, we
-# blanket disable the check for this module.
-#
-# pylint: disable=unidiomatic-typecheck
-
-
-# Node()
-#
-# Container for YAML loaded data and its provenance
-#
-# All nodes returned (and all internal lists/strings) have this type (rather
-# than a plain tuple, to distinguish them in things like node_sanitize)
-#
-# Members:
-# value (str/list/dict): The loaded value.
-# file_index (int): Index within _FILE_LIST (a list of loaded file paths).
-# Negative indices indicate synthetic nodes so that
-# they can be referenced.
-# line (int): The line number within the file where the value appears.
-# col (int): The column number within the file where the value appears.
-#
-# For efficiency, each field should be accessed by its integer index:
-# value = Node[0]
-# file_index = Node[1]
-# line = Node[2]
-# column = Node[3]
-#
-class Node(namedtuple('Node', ['value', 'file_index', 'line', 'column'])):
- def __contains__(self, what):
- # Delegate to the inner value, though this will likely not work
- # very well if the node is a list or string, it's unlikely that
- # code which has access to such nodes would do this.
- return what in self[0]
-
-
-# File name handling
-_FILE_LIST = []
-
-
-# Purely synthetic node will have None for the file number, have line number
-# zero, and a negative column number which comes from inverting the next value
-# out of this counter. Synthetic nodes created with a reference node will
-# have a file number from the reference node, some unknown line number, and
-# a negative column number from this counter.
-_SYNTHETIC_COUNTER = count(start=-1, step=-1)
-
-
-# Returned from node_get_provenance
-class ProvenanceInformation:
-
- __slots__ = (
- "filename",
- "shortname",
- "displayname",
- "line",
- "col",
- "toplevel",
- "node",
- "project",
- "is_synthetic",
- )
-
- def __init__(self, nodeish):
- self.node = nodeish
- if (nodeish is None) or (nodeish[1] is None):
- self.filename = ""
- self.shortname = ""
- self.displayname = ""
- self.line = 1
- self.col = 0
- self.toplevel = None
- self.project = None
- else:
- fileinfo = _FILE_LIST[nodeish[1]]
- self.filename = fileinfo[0]
- self.shortname = fileinfo[1]
- self.displayname = fileinfo[2]
- # We add 1 here to convert from computerish to humanish
- self.line = nodeish[2] + 1
- self.col = nodeish[3]
- self.toplevel = fileinfo[3]
- self.project = fileinfo[4]
- self.is_synthetic = (self.filename == '') or (self.col < 0)
-
- # Convert a Provenance to a string for error reporting
- def __str__(self):
- if self.is_synthetic:
- return "{} [synthetic node]".format(self.displayname)
- else:
- return "{} [line {:d} column {:d}]".format(self.displayname, self.line, self.col)
-
-
-# These exceptions are intended to be caught entirely within
-# the BuildStream framework, hence they do not reside in the
-# public exceptions.py
-class CompositeError(Exception):
- def __init__(self, path, message):
- super(CompositeError, self).__init__(message)
- self.path = path
- self.message = message
-
-
-class YAMLLoadError(Exception):
- pass
-
-
-# Representer for YAML events comprising input to the BuildStream format.
-#
-# All streams MUST represent a single document which must be a Mapping.
-# Anything else is considered an error.
-#
-# Mappings must only have string keys, values are always represented as
-# strings if they are scalar, or else as simple dictionaries and lists.
-#
-class Representer:
- __slots__ = (
- "_file_index",
- "state",
- "output",
- "keys",
- )
-
- # Initialise a new representer
- #
- # The file index is used to store into the Node instances so that the
- # provenance of the YAML can be tracked.
- #
- # Args:
- # file_index (int): The index of this YAML file
- def __init__(self, file_index):
- self._file_index = file_index
- self.state = "init"
- self.output = []
- self.keys = []
-
- # Handle a YAML parse event
- #
- # Args:
- # event (YAML Event): The event to be handled
- #
- # Raises:
- # YAMLLoadError: Something went wrong.
- def handle_event(self, event):
- if getattr(event, "anchor", None) is not None:
- raise YAMLLoadError("Anchors are disallowed in BuildStream at line {} column {}"
- .format(event.start_mark.line, event.start_mark.column))
-
- if event.__class__.__name__ == "ScalarEvent":
- if event.tag is not None:
- if not event.tag.startswith("tag:yaml.org,2002:"):
- raise YAMLLoadError(
- "Non-core tag expressed in input. " +
- "This is disallowed in BuildStream. At line {} column {}"
- .format(event.start_mark.line, event.start_mark.column))
-
- handler = "_handle_{}_{}".format(self.state, event.__class__.__name__)
- handler = getattr(self, handler, None)
- if handler is None:
- raise YAMLLoadError(
- "Invalid input detected. No handler for {} in state {} at line {} column {}"
- .format(event, self.state, event.start_mark.line, event.start_mark.column))
-
- self.state = handler(event) # pylint: disable=not-callable
-
- # Get the output of the YAML parse
- #
- # Returns:
- # (Node or None): Return the Node instance of the top level mapping or
- # None if there wasn't one.
- def get_output(self):
- try:
- return self.output[0]
- except IndexError:
- return None
-
- def _handle_init_StreamStartEvent(self, ev):
- return "stream"
-
- def _handle_stream_DocumentStartEvent(self, ev):
- return "doc"
-
- def _handle_doc_MappingStartEvent(self, ev):
- newmap = Node({}, self._file_index, ev.start_mark.line, ev.start_mark.column)
- self.output.append(newmap)
- return "wait_key"
-
- def _handle_wait_key_ScalarEvent(self, ev):
- self.keys.append(ev.value)
- return "wait_value"
-
- def _handle_wait_value_ScalarEvent(self, ev):
- key = self.keys.pop()
- self.output[-1][0][key] = \
- Node(ev.value, self._file_index, ev.start_mark.line, ev.start_mark.column)
- return "wait_key"
-
- def _handle_wait_value_MappingStartEvent(self, ev):
- new_state = self._handle_doc_MappingStartEvent(ev)
- key = self.keys.pop()
- self.output[-2][0][key] = self.output[-1]
- return new_state
-
- def _handle_wait_key_MappingEndEvent(self, ev):
- # We've finished a mapping, so pop it off the output stack
- # unless it's the last one in which case we leave it
- if len(self.output) > 1:
- self.output.pop()
- if type(self.output[-1][0]) is list:
- return "wait_list_item"
- else:
- return "wait_key"
- else:
- return "doc"
-
- def _handle_wait_value_SequenceStartEvent(self, ev):
- self.output.append(Node([], self._file_index, ev.start_mark.line, ev.start_mark.column))
- self.output[-2][0][self.keys[-1]] = self.output[-1]
- return "wait_list_item"
-
- def _handle_wait_list_item_SequenceStartEvent(self, ev):
- self.keys.append(len(self.output[-1][0]))
- self.output.append(Node([], self._file_index, ev.start_mark.line, ev.start_mark.column))
- self.output[-2][0].append(self.output[-1])
- return "wait_list_item"
-
- def _handle_wait_list_item_SequenceEndEvent(self, ev):
- # When ending a sequence, we need to pop a key because we retain the
- # key until the end so that if we need to mutate the underlying entry
- # we can.
- key = self.keys.pop()
- self.output.pop()
- if type(key) is int:
- return "wait_list_item"
- else:
- return "wait_key"
-
- def _handle_wait_list_item_ScalarEvent(self, ev):
- self.output[-1][0].append(
- Node(ev.value, self._file_index, ev.start_mark.line, ev.start_mark.column))
- return "wait_list_item"
-
- def _handle_wait_list_item_MappingStartEvent(self, ev):
- new_state = self._handle_doc_MappingStartEvent(ev)
- self.output[-2][0].append(self.output[-1])
- return new_state
-
- def _handle_doc_DocumentEndEvent(self, ev):
- if len(self.output) != 1:
- raise YAMLLoadError("Zero, or more than one document found in YAML stream")
- return "stream"
-
- def _handle_stream_StreamEndEvent(self, ev):
- return "init"
-
-
-# Loads a dictionary from some YAML
-#
-# Args:
-# filename (str): The YAML file to load
-# shortname (str): The filename in shorthand for error reporting (or None)
-# copy_tree (bool): Whether to make a copy, preserving the original toplevels
-# for later serialization
-# project (Project): The (optional) project to associate the parsed YAML with
-#
-# Returns (dict): A loaded copy of the YAML file with provenance information
-#
-# Raises: LoadError
-#
-def load(filename, shortname=None, copy_tree=False, *, project=None):
- if not shortname:
- shortname = filename
-
- if (project is not None) and (project.junction is not None):
- displayname = "{}:{}".format(project.junction.name, shortname)
- else:
- displayname = shortname
-
- file_number = len(_FILE_LIST)
- _FILE_LIST.append((filename, shortname, displayname, None, project))
-
- try:
- with open(filename) as f:
- contents = f.read()
-
- data = load_data(contents,
- file_index=file_number,
- file_name=filename,
- copy_tree=copy_tree)
-
- return data
- except FileNotFoundError as e:
- raise LoadError(LoadErrorReason.MISSING_FILE,
- "Could not find file at {}".format(filename)) from e
- except IsADirectoryError as e:
- raise LoadError(LoadErrorReason.LOADING_DIRECTORY,
- "{} is a directory. bst command expects a .bst file."
- .format(filename)) from e
- except LoadError as e:
- raise LoadError(e.reason, "{}: {}".format(displayname, e)) from e
-
-
-# Like load(), but doesnt require the data to be in a file
-#
-def load_data(data, file_index=None, file_name=None, copy_tree=False):
-
- try:
- rep = Representer(file_index)
- for event in yaml.parse(data, Loader=yaml.CBaseLoader):
- rep.handle_event(event)
- contents = rep.get_output()
- except YAMLLoadError as e:
- raise LoadError(LoadErrorReason.INVALID_YAML,
- "Malformed YAML:\n\n{}\n\n".format(e)) from e
- except Exception as e:
- raise LoadError(LoadErrorReason.INVALID_YAML,
- "Severely malformed YAML:\n\n{}\n\n".format(e)) from e
-
- if not isinstance(contents, tuple) or not isinstance(contents[0], dict):
- # Special case allowance for None, when the loaded file has only comments in it.
- if contents is None:
- contents = Node({}, file_index, 0, 0)
- else:
- raise LoadError(LoadErrorReason.INVALID_YAML,
- "YAML file has content of type '{}' instead of expected type 'dict': {}"
- .format(type(contents[0]).__name__, file_name))
-
- # Store this away because we'll use it later for "top level" provenance
- if file_index is not None:
- _FILE_LIST[file_index] = (
- _FILE_LIST[file_index][0], # Filename
- _FILE_LIST[file_index][1], # Shortname
- _FILE_LIST[file_index][2], # Displayname
- contents,
- _FILE_LIST[file_index][4], # Project
- )
-
- if copy_tree:
- contents = node_copy(contents)
- return contents
-
-
-# dump()
-#
-# Write a YAML node structure out to disk.
-#
-# This will always call `node_sanitize` on its input, so if you wanted
-# to output something close to what you read in, consider using the
-# `roundtrip_load` and `roundtrip_dump` function pair instead.
-#
-# Args:
-# contents (any): Content to write out
-# filename (str): The (optional) file name to write out to
-def dump(contents, filename=None):
- roundtrip_dump(node_sanitize(contents), file=filename)
-
-
-# node_get_provenance()
-#
-# Gets the provenance for a node
-#
-# Args:
-# node (dict): a dictionary
-# key (str): key in the dictionary
-# indices (list of indexes): Index path, in the case of list values
-#
-# Returns: The Provenance of the dict, member or list element
-#
-def node_get_provenance(node, key=None, indices=None):
- assert is_node(node)
-
- if key is None:
- # Retrieving the provenance for this node directly
- return ProvenanceInformation(node)
-
- if key and not indices:
- return ProvenanceInformation(node[0].get(key))
-
- nodeish = node[0].get(key)
- for idx in indices:
- nodeish = nodeish[0][idx]
-
- return ProvenanceInformation(nodeish)
-
-
-# A sentinel to be used as a default argument for functions that need
-# to distinguish between a kwarg set to None and an unset kwarg.
-_sentinel = object()
-
-
-# node_get()
-#
-# Fetches a value from a dictionary node and checks it for
-# an expected value. Use default_value when parsing a value
-# which is only optionally supplied.
-#
-# Args:
-# node (dict): The dictionary node
-# expected_type (type): The expected type for the value being searched
-# key (str): The key to get a value for in node
-# indices (list of ints): Optionally decend into lists of lists
-# default_value: Optionally return this value if the key is not found
-# allow_none: (bool): Allow None to be a valid value
-#
-# Returns:
-# The value if found in node, otherwise default_value is returned
-#
-# Raises:
-# LoadError, when the value found is not of the expected type
-#
-# Note:
-# Returned strings are stripped of leading and trailing whitespace
-#
-def node_get(node, expected_type, key, indices=None, *, default_value=_sentinel, allow_none=False):
- assert type(node) is Node
-
- if indices is None:
- if default_value is _sentinel:
- value = node[0].get(key, Node(default_value, None, 0, 0))
- else:
- value = node[0].get(key, Node(default_value, None, 0, next(_SYNTHETIC_COUNTER)))
-
- if value[0] is _sentinel:
- provenance = node_get_provenance(node)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Dictionary did not contain expected key '{}'".format(provenance, key))
- else:
- # Implied type check of the element itself
- # No need to synthesise useful node content as we destructure it immediately
- value = Node(node_get(node, list, key), None, 0, 0)
- for index in indices:
- value = value[0][index]
- if type(value) is not Node:
- value = (value,)
-
- # Optionally allow None as a valid value for any type
- if value[0] is None and (allow_none or default_value is None):
- return None
-
- if (expected_type is not None) and (not isinstance(value[0], expected_type)):
- # Attempt basic conversions if possible, typically we want to
- # be able to specify numeric values and convert them to strings,
- # but we dont want to try converting dicts/lists
- try:
- if (expected_type == bool and isinstance(value[0], str)):
- # Dont coerce booleans to string, this makes "False" strings evaluate to True
- # We don't structure into full nodes since there's no need.
- if value[0] in ('True', 'true'):
- value = (True, None, 0, 0)
- elif value[0] in ('False', 'false'):
- value = (False, None, 0, 0)
- else:
- raise ValueError()
- elif not (expected_type == list or
- expected_type == dict or
- isinstance(value[0], (list, dict))):
- value = (expected_type(value[0]), None, 0, 0)
- else:
- raise ValueError()
- except (ValueError, TypeError):
- provenance = node_get_provenance(node, key=key, indices=indices)
- if indices:
- path = [key]
- path.extend("[{:d}]".format(i) for i in indices)
- path = "".join(path)
- else:
- path = key
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Value of '{}' is not of the expected type '{}'"
- .format(provenance, path, expected_type.__name__))
-
- # Now collapse lists, and scalars, to their value, leaving nodes as-is
- if type(value[0]) is not dict:
- value = value[0]
-
- # Trim it at the bud, let all loaded strings from yaml be stripped of whitespace
- if type(value) is str:
- value = value.strip()
-
- elif type(value) is list:
- # Now we create a fresh list which unwraps the str and list types
- # semi-recursively.
- value = __trim_list_provenance(value)
-
- return value
-
-
-def __trim_list_provenance(value):
- ret = []
- for entry in value:
- if type(entry) is not Node:
- entry = (entry, None, 0, 0)
- if type(entry[0]) is list:
- ret.append(__trim_list_provenance(entry[0]))
- elif type(entry[0]) is dict:
- ret.append(entry)
- else:
- ret.append(entry[0])
- return ret
-
-
-# node_set()
-#
-# Set an item within the node. If using `indices` be aware that the entry must
-# already exist, or else a KeyError will be raised. Use `node_extend_list` to
-# create entries before using `node_set`
-#
-# Args:
-# node (tuple): The node
-# key (str): The key name
-# value: The value
-# indices: Any indices to index into the list referenced by key, like in
-# `node_get` (must be a list of integers)
-#
-def node_set(node, key, value, indices=None):
- if indices:
- node = node[0][key]
- key = indices.pop()
- for idx in indices:
- node = node[0][idx]
- if type(value) is Node:
- node[0][key] = value
- else:
- try:
- # Need to do this just in case we're modifying a list
- old_value = node[0][key]
- except KeyError:
- old_value = None
- if old_value is None:
- node[0][key] = Node(value, node[1], node[2], next(_SYNTHETIC_COUNTER))
- else:
- node[0][key] = Node(value, old_value[1], old_value[2], old_value[3])
-
-
-# node_extend_list()
-#
-# Extend a list inside a node to a given length, using the passed
-# default value to fill it out.
-#
-# Valid default values are:
-# Any string
-# An empty dict
-# An empty list
-#
-# Args:
-# node (node): The node
-# key (str): The list name in the node
-# length (int): The length to extend the list to
-# default (any): The default value to extend with.
-def node_extend_list(node, key, length, default):
- assert type(default) is str or default in ([], {})
-
- list_node = node[0].get(key)
- if list_node is None:
- list_node = node[0][key] = Node([], node[1], node[2], next(_SYNTHETIC_COUNTER))
-
- assert type(list_node[0]) is list
-
- the_list = list_node[0]
- def_type = type(default)
-
- file_index = node[1]
- if the_list:
- line_num = the_list[-1][2]
- else:
- line_num = list_node[2]
-
- while length > len(the_list):
- if def_type is str:
- value = default
- elif def_type is list:
- value = []
- else:
- value = {}
-
- line_num += 1
-
- the_list.append(Node(value, file_index, line_num, next(_SYNTHETIC_COUNTER)))
-
-
-# node_items()
-#
-# A convenience generator for iterating over loaded key/value
-# tuples in a dictionary loaded from project YAML.
-#
-# Args:
-# node (dict): The dictionary node
-#
-# Yields:
-# (str): The key name
-# (anything): The value for the key
-#
-def node_items(node):
- if type(node) is not Node:
- node = Node(node, None, 0, 0)
- for key, value in node[0].items():
- if type(value) is not Node:
- value = Node(value, None, 0, 0)
- if type(value[0]) is dict:
- yield (key, value)
- elif type(value[0]) is list:
- yield (key, __trim_list_provenance(value[0]))
- else:
- yield (key, value[0])
-
-
-# node_keys()
-#
-# A convenience generator for iterating over loaded keys
-# in a dictionary loaded from project YAML.
-#
-# Args:
-# node (dict): The dictionary node
-#
-# Yields:
-# (str): The key name
-#
-def node_keys(node):
- if type(node) is not Node:
- node = Node(node, None, 0, 0)
- yield from node[0].keys()
-
-
-# node_del()
-#
-# A convenience generator for iterating over loaded key/value
-# tuples in a dictionary loaded from project YAML.
-#
-# Args:
-# node (dict): The dictionary node
-# key (str): The key we want to remove
-# safe (bool): Whether to raise a KeyError if unable
-#
-def node_del(node, key, safe=False):
- try:
- del node[0][key]
- except KeyError:
- if not safe:
- raise
-
-
-# is_node()
-#
-# A test method which returns whether or not the passed in value
-# is a valid YAML node. It is not valid to call this on a Node
-# object which is not a Mapping.
-#
-# Args:
-# maybenode (any): The object to test for nodeness
-#
-# Returns:
-# (bool): Whether or not maybenode was a Node
-#
-def is_node(maybenode):
- # It's a programming error to give this a Node which isn't a mapping
- # so assert that.
- assert (type(maybenode) is not Node) or (type(maybenode[0]) is dict)
- # Now return the type check
- return type(maybenode) is Node
-
-
-# new_synthetic_file()
-#
-# Create a new synthetic mapping node, with an associated file entry
-# (in _FILE_LIST) such that later tracking can correctly determine which
-# file needs writing to in order to persist the changes.
-#
-# Args:
-# filename (str): The name of the synthetic file to create
-# project (Project): The optional project to associate this synthetic file with
-#
-# Returns:
-# (Node): An empty YAML mapping node, whose provenance is to this new
-# synthetic file
-#
-def new_synthetic_file(filename, project=None):
- file_index = len(_FILE_LIST)
- node = Node({}, file_index, 0, 0)
- _FILE_LIST.append((filename,
- filename,
- "<synthetic {}>".format(filename),
- node,
- project))
- return node
-
-
-# new_empty_node()
-#
-# Args:
-# ref_node (Node): Optional node whose provenance should be referenced
-#
-# Returns
-# (Node): A new empty YAML mapping node
-#
-def new_empty_node(ref_node=None):
- if ref_node is not None:
- return Node({}, ref_node[1], ref_node[2], next(_SYNTHETIC_COUNTER))
- else:
- return Node({}, None, 0, 0)
-
-
-# new_node_from_dict()
-#
-# Args:
-# indict (dict): The input dictionary
-#
-# Returns:
-# (Node): A new synthetic YAML tree which represents this dictionary
-#
-def new_node_from_dict(indict):
- ret = {}
- for k, v in indict.items():
- vtype = type(v)
- if vtype is dict:
- ret[k] = new_node_from_dict(v)
- elif vtype is list:
- ret[k] = __new_node_from_list(v)
- else:
- ret[k] = Node(str(v), None, 0, next(_SYNTHETIC_COUNTER))
- return Node(ret, None, 0, next(_SYNTHETIC_COUNTER))
-
-
-# Internal function to help new_node_from_dict() to handle lists
-def __new_node_from_list(inlist):
- ret = []
- for v in inlist:
- vtype = type(v)
- if vtype is dict:
- ret.append(new_node_from_dict(v))
- elif vtype is list:
- ret.append(__new_node_from_list(v))
- else:
- ret.append(Node(str(v), None, 0, next(_SYNTHETIC_COUNTER)))
- return Node(ret, None, 0, next(_SYNTHETIC_COUNTER))
-
-
-# _is_composite_list
-#
-# Checks if the given node is a Mapping with array composition
-# directives.
-#
-# Args:
-# node (value): Any node
-#
-# Returns:
-# (bool): True if node was a Mapping containing only
-# list composition directives
-#
-# Raises:
-# (LoadError): If node was a mapping and contained a mix of
-# list composition directives and other keys
-#
-def _is_composite_list(node):
-
- if type(node[0]) is dict:
- has_directives = False
- has_keys = False
-
- for key, _ in node_items(node):
- if key in ['(>)', '(<)', '(=)']: # pylint: disable=simplifiable-if-statement
- has_directives = True
- else:
- has_keys = True
-
- if has_keys and has_directives:
- provenance = node_get_provenance(node)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Dictionary contains array composition directives and arbitrary keys"
- .format(provenance))
- return has_directives
-
- return False
-
-
-# _compose_composite_list()
-#
-# Composes a composite list (i.e. a dict with list composition directives)
-# on top of a target list which is a composite list itself.
-#
-# Args:
-# target (Node): A composite list
-# source (Node): A composite list
-#
-def _compose_composite_list(target, source):
- clobber = source[0].get("(=)")
- prefix = source[0].get("(<)")
- suffix = source[0].get("(>)")
- if clobber is not None:
- # We want to clobber the target list
- # which basically means replacing the target list
- # with ourselves
- target[0]["(=)"] = clobber
- if prefix is not None:
- target[0]["(<)"] = prefix
- elif "(<)" in target[0]:
- target[0]["(<)"][0].clear()
- if suffix is not None:
- target[0]["(>)"] = suffix
- elif "(>)" in target[0]:
- target[0]["(>)"][0].clear()
- else:
- # Not clobbering, so prefix the prefix and suffix the suffix
- if prefix is not None:
- if "(<)" in target[0]:
- for v in reversed(prefix[0]):
- target[0]["(<)"][0].insert(0, v)
- else:
- target[0]["(<)"] = prefix
- if suffix is not None:
- if "(>)" in target[0]:
- target[0]["(>)"][0].extend(suffix[0])
- else:
- target[0]["(>)"] = suffix
-
-
-# _compose_list()
-#
-# Compose a composite list (a dict with composition directives) on top of a
-# simple list.
-#
-# Args:
-# target (Node): The target list to be composed into
-# source (Node): The composition list to be composed from
-#
-def _compose_list(target, source):
- clobber = source[0].get("(=)")
- prefix = source[0].get("(<)")
- suffix = source[0].get("(>)")
- if clobber is not None:
- target[0].clear()
- target[0].extend(clobber[0])
- if prefix is not None:
- for v in reversed(prefix[0]):
- target[0].insert(0, v)
- if suffix is not None:
- target[0].extend(suffix[0])
-
-
-# composite_dict()
-#
-# Compose one mapping node onto another
-#
-# Args:
-# target (Node): The target to compose into
-# source (Node): The source to compose from
-# path (list): The path to the current composition node
-#
-# Raises: CompositeError
-#
-def composite_dict(target, source, path=None):
- if path is None:
- path = []
- for k, v in source[0].items():
- path.append(k)
- if type(v[0]) is list:
- # List clobbers anything list-like
- target_value = target[0].get(k)
- if not (target_value is None or
- type(target_value[0]) is list or
- _is_composite_list(target_value)):
- raise CompositeError(path,
- "{}: List cannot overwrite {} at: {}"
- .format(node_get_provenance(source, k),
- k,
- node_get_provenance(target, k)))
- # Looks good, clobber it
- target[0][k] = v
- elif _is_composite_list(v):
- if k not in target[0]:
- # Composite list clobbers empty space
- target[0][k] = v
- elif type(target[0][k][0]) is list:
- # Composite list composes into a list
- _compose_list(target[0][k], v)
- elif _is_composite_list(target[0][k]):
- # Composite list merges into composite list
- _compose_composite_list(target[0][k], v)
- else:
- # Else composing on top of normal dict or a scalar, so raise...
- raise CompositeError(path,
- "{}: Cannot compose lists onto {}".format(
- node_get_provenance(v),
- node_get_provenance(target[0][k])))
- elif type(v[0]) is dict:
- # We're composing a dict into target now
- if k not in target[0]:
- # Target lacks a dict at that point, make a fresh one with
- # the same provenance as the incoming dict
- target[0][k] = Node({}, v[1], v[2], v[3])
- if type(target[0]) is not dict:
- raise CompositeError(path,
- "{}: Cannot compose dictionary onto {}".format(
- node_get_provenance(v),
- node_get_provenance(target[0][k])))
- composite_dict(target[0][k], v, path)
- else:
- target_value = target[0].get(k)
- if target_value is not None and type(target_value[0]) is not str:
- raise CompositeError(path,
- "{}: Cannot compose scalar on non-scalar at {}".format(
- node_get_provenance(v),
- node_get_provenance(target[0][k])))
- target[0][k] = v
- path.pop()
-
-
-# Like composite_dict(), but raises an all purpose LoadError for convenience
-#
-def composite(target, source):
- assert type(source[0]) is dict
- assert type(target[0]) is dict
-
- try:
- composite_dict(target, source)
- except CompositeError as e:
- source_provenance = node_get_provenance(source)
- error_prefix = ""
- if source_provenance:
- error_prefix = "{}: ".format(source_provenance)
- raise LoadError(LoadErrorReason.ILLEGAL_COMPOSITE,
- "{}Failure composing {}: {}"
- .format(error_prefix,
- e.path,
- e.message)) from e
-
-
-# Like composite(target, source), but where target overrides source instead.
-#
-def composite_and_move(target, source):
- composite(source, target)
-
- to_delete = [key for key in target[0].keys() if key not in source[0]]
- for key, value in source[0].items():
- target[0][key] = value
- for key in to_delete:
- del target[0][key]
-
-
-# Types we can short-circuit in node_sanitize for speed.
-__SANITIZE_SHORT_CIRCUIT_TYPES = (int, float, str, bool)
-
-
-# node_sanitize()
-#
-# Returns an alphabetically ordered recursive copy
-# of the source node with internal provenance information stripped.
-#
-# Only dicts are ordered, list elements are left in order.
-#
-def node_sanitize(node, *, dict_type=OrderedDict):
- node_type = type(node)
-
- # If we have an unwrappable node, unwrap it
- if node_type is Node:
- node = node[0]
- node_type = type(node)
-
- # Short-circuit None which occurs ca. twice per element
- if node is None:
- return node
-
- # Next short-circuit integers, floats, strings, booleans, and tuples
- if node_type in __SANITIZE_SHORT_CIRCUIT_TYPES:
- return node
-
- # Now short-circuit lists.
- elif node_type is list:
- return [node_sanitize(elt, dict_type=dict_type) for elt in node]
-
- # Finally dict, and other Mappings need special handling
- elif node_type is dict:
- result = dict_type()
-
- key_list = [key for key, _ in node.items()]
- for key in sorted(key_list):
- result[key] = node_sanitize(node[key], dict_type=dict_type)
-
- return result
-
- # Sometimes we're handed tuples and we can't be sure what they contain
- # so we have to sanitize into them
- elif node_type is tuple:
- return tuple((node_sanitize(v, dict_type=dict_type) for v in node))
-
- # Everything else just gets returned as-is.
- return node
-
-
-# node_validate()
-#
-# Validate the node so as to ensure the user has not specified
-# any keys which are unrecognized by buildstream (usually this
-# means a typo which would otherwise not trigger an error).
-#
-# Args:
-# node (dict): A dictionary loaded from YAML
-# valid_keys (list): A list of valid keys for the specified node
-#
-# Raises:
-# LoadError: In the case that the specified node contained
-# one or more invalid keys
-#
-def node_validate(node, valid_keys):
-
- # Probably the fastest way to do this: https://stackoverflow.com/a/23062482
- valid_keys = set(valid_keys)
- invalid = next((key for key in node[0] if key not in valid_keys), None)
-
- if invalid:
- provenance = node_get_provenance(node, key=invalid)
- raise LoadError(LoadErrorReason.INVALID_DATA,
- "{}: Unexpected key: {}".format(provenance, invalid))
-
-
-# Node copying
-#
-# Unfortunately we copy nodes a *lot* and `isinstance()` is super-slow when
-# things from collections.abc get involved. The result is the following
-# intricate but substantially faster group of tuples and the use of `in`.
-#
-# If any of the {node,list}_copy routines raise a ValueError
-# then it's likely additional types need adding to these tuples.
-
-
-# These types just have their value copied
-__QUICK_TYPES = (str, bool)
-
-# These are the directives used to compose lists, we need this because it's
-# slightly faster during the node_final_assertions checks
-__NODE_ASSERT_COMPOSITION_DIRECTIVES = ('(>)', '(<)', '(=)')
-
-
-# node_copy()
-#
-# Make a deep copy of the given YAML node, preserving provenance.
-#
-# Args:
-# source (Node): The YAML node to copy
-#
-# Returns:
-# (Node): A deep copy of source with provenance preserved.
-#
-def node_copy(source):
- copy = {}
- for key, value in source[0].items():
- value_type = type(value[0])
- if value_type is dict:
- copy[key] = node_copy(value)
- elif value_type is list:
- copy[key] = _list_copy(value)
- elif value_type in __QUICK_TYPES:
- copy[key] = value
- else:
- raise ValueError("Unable to be quick about node_copy of {}".format(value_type))
-
- return Node(copy, source[1], source[2], source[3])
-
-
-# Internal function to help node_copy() but for lists.
-def _list_copy(source):
- copy = []
- for item in source[0]:
- item_type = type(item[0])
- if item_type is dict:
- copy.append(node_copy(item))
- elif item_type is list:
- copy.append(_list_copy(item))
- elif item_type in __QUICK_TYPES:
- copy.append(item)
- else:
- raise ValueError("Unable to be quick about list_copy of {}".format(item_type))
-
- return Node(copy, source[1], source[2], source[3])
-
-
-# node_final_assertions()
-#
-# This must be called on a fully loaded and composited node,
-# after all composition has completed.
-#
-# Args:
-# node (Mapping): The final composited node
-#
-# Raises:
-# (LoadError): If any assertions fail
-#
-def node_final_assertions(node):
- assert type(node) is Node
-
- for key, value in node[0].items():
-
- # Assert that list composition directives dont remain, this
- # indicates that the user intended to override a list which
- # never existed in the underlying data
- #
- if key in __NODE_ASSERT_COMPOSITION_DIRECTIVES:
- provenance = node_get_provenance(node, key)
- raise LoadError(LoadErrorReason.TRAILING_LIST_DIRECTIVE,
- "{}: Attempt to override non-existing list".format(provenance))
-
- value_type = type(value[0])
-
- if value_type is dict:
- node_final_assertions(value)
- elif value_type is list:
- _list_final_assertions(value)
-
-
-# Helper function for node_final_assertions(), but for lists.
-def _list_final_assertions(values):
- for value in values[0]:
- value_type = type(value[0])
-
- if value_type is dict:
- node_final_assertions(value)
- elif value_type is list:
- _list_final_assertions(value)
-
-
-# assert_symbol_name()
-#
-# A helper function to check if a loaded string is a valid symbol
-# name and to raise a consistent LoadError if not. For strings which
-# are required to be symbols.
-#
-# Args:
-# provenance (Provenance): The provenance of the loaded symbol, or None
-# symbol_name (str): The loaded symbol name
-# purpose (str): The purpose of the string, for an error message
-# allow_dashes (bool): Whether dashes are allowed for this symbol
-#
-# Raises:
-# LoadError: If the symbol_name is invalid
-#
-# Note that dashes are generally preferred for variable names and
-# usage in YAML, but things such as option names which will be
-# evaluated with jinja2 cannot use dashes.
-def assert_symbol_name(provenance, symbol_name, purpose, *, allow_dashes=True):
- valid_chars = string.digits + string.ascii_letters + '_'
- if allow_dashes:
- valid_chars += '-'
-
- valid = True
- if not symbol_name:
- valid = False
- elif any(x not in valid_chars for x in symbol_name):
- valid = False
- elif symbol_name[0] in string.digits:
- valid = False
-
- if not valid:
- detail = "Symbol names must contain only alphanumeric characters, " + \
- "may not start with a digit, and may contain underscores"
- if allow_dashes:
- detail += " or dashes"
-
- message = "Invalid symbol name for {}: '{}'".format(purpose, symbol_name)
- if provenance is not None:
- message = "{}: {}".format(provenance, message)
-
- raise LoadError(LoadErrorReason.INVALID_SYMBOL_NAME,
- message, detail=detail)
-
-
-# node_find_target()
-#
-# Searches the given node tree for the given target node.
-#
-# This is typically used when trying to walk a path to a given node
-# for the purpose of then modifying a similar tree of objects elsewhere
-#
-# If the key is provided, then we actually hunt for the node represented by
-# target[key] and return its container, rather than hunting for target directly
-#
-# Args:
-# node (Node): The node at the root of the tree to search
-# target (Node): The node you are looking for in that tree
-# key (str): Optional string key within target node
-#
-# Returns:
-# (list): A path from `node` to `target` or None if `target` is not in the subtree
-def node_find_target(node, target, *, key=None):
- assert type(node) is Node
- assert type(target) is Node
- if key is not None:
- target = target[0][key]
-
- path = []
- if _walk_find_target(node, path, target):
- if key:
- # Remove key from end of path
- path = path[:-1]
- return path
- return None
-
-
-# Helper for node_find_target() which walks a value
-def _walk_find_target(node, path, target):
- if node[1:] == target[1:]:
- return True
- elif type(node[0]) is dict:
- return _walk_dict_node(node, path, target)
- elif type(node[0]) is list:
- return _walk_list_node(node, path, target)
- return False
-
-
-# Helper for node_find_target() which walks a list
-def _walk_list_node(node, path, target):
- for i, v in enumerate(node[0]):
- path.append(i)
- if _walk_find_target(v, path, target):
- return True
- del path[-1]
- return False
-
-
-# Helper for node_find_target() which walks a mapping
-def _walk_dict_node(node, path, target):
- for k, v in node[0].items():
- path.append(k)
- if _walk_find_target(v, path, target):
- return True
- del path[-1]
- return False
-
-
-###############################################################################
-
-# Roundtrip code
-
-# Always represent things consistently:
-
-yaml.RoundTripRepresenter.add_representer(OrderedDict,
- yaml.SafeRepresenter.represent_dict)
-
-# Always parse things consistently
-
-yaml.RoundTripConstructor.add_constructor(u'tag:yaml.org,2002:int',
- yaml.RoundTripConstructor.construct_yaml_str)
-yaml.RoundTripConstructor.add_constructor(u'tag:yaml.org,2002:float',
- yaml.RoundTripConstructor.construct_yaml_str)
-yaml.RoundTripConstructor.add_constructor(u'tag:yaml.org,2002:bool',
- yaml.RoundTripConstructor.construct_yaml_str)
-yaml.RoundTripConstructor.add_constructor(u'tag:yaml.org,2002:null',
- yaml.RoundTripConstructor.construct_yaml_str)
-yaml.RoundTripConstructor.add_constructor(u'tag:yaml.org,2002:timestamp',
- yaml.RoundTripConstructor.construct_yaml_str)
-
-
-# HardlineDumper
-#
-# This is a dumper used during roundtrip_dump which forces every scalar to be
-# a plain string, in order to match the output format to the input format.
-#
-# If you discover something is broken, please add a test case to the roundtrip
-# test in tests/internals/yaml/roundtrip-test.yaml
-#
-class HardlineDumper(yaml.RoundTripDumper):
- def __init__(self, *args, **kwargs):
- yaml.RoundTripDumper.__init__(self, *args, **kwargs)
- # For each of YAML 1.1 and 1.2, force everything to be a plain string
- for version in [(1, 1), (1, 2), None]:
- self.add_version_implicit_resolver(
- version,
- u'tag:yaml.org,2002:str',
- yaml.util.RegExp(r'.*'),
- None)
-
-
-# roundtrip_load()
-#
-# Load a YAML file into memory in a form which allows roundtripping as best
-# as ruamel permits.
-#
-# Note, the returned objects can be treated as Mappings and Lists and Strings
-# but replacing content wholesale with plain dicts and lists may result
-# in a loss of comments and formatting.
-#
-# Args:
-# filename (str): The file to load in
-# allow_missing (bool): Optionally set this to True to allow missing files
-#
-# Returns:
-# (Mapping): The loaded YAML mapping.
-#
-# Raises:
-# (LoadError): If the file is missing, or a directory, this is raised.
-# Also if the YAML is malformed.
-#
-def roundtrip_load(filename, *, allow_missing=False):
- try:
- with open(filename, "r") as fh:
- data = fh.read()
- contents = roundtrip_load_data(data, filename=filename)
- except FileNotFoundError as e:
- if allow_missing:
- # Missing files are always empty dictionaries
- return {}
- else:
- raise LoadError(LoadErrorReason.MISSING_FILE,
- "Could not find file at {}".format(filename)) from e
- except IsADirectoryError as e:
- raise LoadError(LoadErrorReason.LOADING_DIRECTORY,
- "{} is a directory."
- .format(filename)) from e
- return contents
-
-
-# roundtrip_load_data()
-#
-# Parse the given contents as YAML, returning them as a roundtrippable data
-# structure.
-#
-# A lack of content will be returned as an empty mapping.
-#
-# Args:
-# contents (str): The contents to be parsed as YAML
-# filename (str): Optional filename to be used in error reports
-#
-# Returns:
-# (Mapping): The loaded YAML mapping
-#
-# Raises:
-# (LoadError): Raised on invalid YAML, or YAML which parses to something other
-# than a Mapping
-#
-def roundtrip_load_data(contents, *, filename=None):
- try:
- contents = yaml.load(contents, yaml.RoundTripLoader, preserve_quotes=True)
- except (yaml.scanner.ScannerError, yaml.composer.ComposerError, yaml.parser.ParserError) as e:
- raise LoadError(LoadErrorReason.INVALID_YAML,
- "Malformed YAML:\n\n{}\n\n{}\n".format(e.problem, e.problem_mark)) from e
-
- # Special case empty files at this point
- if contents is None:
- # We'll make them empty mappings like the main Node loader
- contents = {}
-
- if not isinstance(contents, Mapping):
- raise LoadError(LoadErrorReason.INVALID_YAML,
- "YAML file has content of type '{}' instead of expected type 'dict': {}"
- .format(type(contents).__name__, filename))
-
- return contents
-
-
-# roundtrip_dump()
-#
-# Dumps the given contents as a YAML file. Ideally the contents came from
-# parsing with `roundtrip_load` or `roundtrip_load_data` so that they will be
-# dumped in the same form as they came from.
-#
-# If `file` is a string, it is the filename to write to, if `file` has a
-# `write` method, it's treated as a stream, otherwise output is to stdout.
-#
-# Args:
-# contents (Mapping or list): The content to write out as YAML.
-# file (any): The file to write to
-#
-def roundtrip_dump(contents, file=None):
- assert type(contents) is not Node
-
- def stringify_dict(thing):
- for k, v in thing.items():
- if type(v) is str:
- pass
- elif isinstance(v, Mapping):
- stringify_dict(v)
- elif isinstance(v, Sequence):
- stringify_list(v)
- else:
- thing[k] = str(v)
-
- def stringify_list(thing):
- for i, v in enumerate(thing):
- if type(v) is str:
- pass
- elif isinstance(v, Mapping):
- stringify_dict(v)
- elif isinstance(v, Sequence):
- stringify_list(v)
- else:
- thing[i] = str(v)
-
- contents = deepcopy(contents)
- stringify_dict(contents)
-
- with ExitStack() as stack:
- if type(file) is str:
- from . import utils
- f = stack.enter_context(utils.save_file_atomic(file, 'w'))
- elif hasattr(file, 'write'):
- f = file
- else:
- f = sys.stdout
- yaml.round_trip_dump(contents, f, Dumper=HardlineDumper)
diff --git a/buildstream/buildelement.py b/buildstream/buildelement.py
deleted file mode 100644
index 158f5fc11..000000000
--- a/buildstream/buildelement.py
+++ /dev/null
@@ -1,299 +0,0 @@
-#
-# 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)
diff --git a/buildstream/data/bst b/buildstream/data/bst
deleted file mode 100644
index e38720f77..000000000
--- a/buildstream/data/bst
+++ /dev/null
@@ -1,21 +0,0 @@
-# BuildStream bash completion scriptlet.
-#
-# On systems which use the bash-completion module for
-# completion discovery with bash, this can be installed at:
-#
-# pkg-config --variable=completionsdir bash-completion
-#
-# If BuildStream is not installed system wide, you can
-# simply source this script to enable completions or append
-# this script to your ~/.bash_completion file.
-#
-_bst_completion() {
- local IFS=$'
-'
- COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \
- COMP_CWORD=$COMP_CWORD \
- _BST_COMPLETION=complete $1 ) )
- return 0
-}
-
-complete -F _bst_completion -o nospace bst;
diff --git a/buildstream/data/build-all.sh.in b/buildstream/data/build-all.sh.in
deleted file mode 100644
index bf5c9f880..000000000
--- a/buildstream/data/build-all.sh.in
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/sh
-#
-# DO NOT EDIT THIS FILE
-#
-# This is a build script generated by
-# [BuildStream](https://wiki.gnome.org/Projects/BuildStream/).
-#
-# Builds all given modules using their respective scripts.
-
-set -eu
-
-echo "Buildstream native bootstrap script"
-
-export PATH='/usr/bin:/usr/sbin/:/sbin:/bin:/tools/bin:/tools/sbin'
-export SRCDIR='./source'
-
-SUCCESS=false
-CURRENT_MODULE='None'
-
-echo 'Setting up build environment...'
-
-except() {{
- if [ "$SUCCESS" = true ]; then
- echo "Done!"
- else
- echo "Error building module ${{CURRENT_MODULE}}."
- fi
-}}
-trap "except" EXIT
-
-for module in {modules}; do
- CURRENT_MODULE="$module"
- "$SRCDIR/build-$module"
-
- if [ -e /sbin/ldconfig ]; then
- /sbin/ldconfig || true;
- fi
-done
-
-SUCCESS=true
diff --git a/buildstream/data/build-module.sh.in b/buildstream/data/build-module.sh.in
deleted file mode 100644
index 6e9ea4552..000000000
--- a/buildstream/data/build-module.sh.in
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/sh
-#
-# DO NOT EDIT THIS FILE
-#
-# This is a build script generated by
-# [BuildStream](https://wiki.gnome.org/Projects/BuildStream/).
-#
-# Builds the module {name}.
-
-set -e
-
-# Prepare the build environment
-echo 'Building {name}'
-
-if [ -d '{build_root}' ]; then
- rm -rf '{build_root}'
-fi
-
-if [ -d '{install_root}' ]; then
- rm -rf '{install_root}'
-fi
-
-mkdir -p '{build_root}'
-mkdir -p '{install_root}'
-
-if [ -d "$SRCDIR/{name}/" ]; then
- cp -a "$SRCDIR/{name}/." '{build_root}'
-fi
-cd '{build_root}'
-
-export PREFIX='{install_root}'
-
-export {variables}
-
-# Build the module
-{commands}
-
-rm -rf '{build_root}'
-
-# Install the module
-echo 'Installing {name}'
-
-(cd '{install_root}'; find . | cpio -umdp /)
diff --git a/buildstream/data/projectconfig.yaml b/buildstream/data/projectconfig.yaml
deleted file mode 100644
index ee4055cf5..000000000
--- a/buildstream/data/projectconfig.yaml
+++ /dev/null
@@ -1,183 +0,0 @@
-# Default BuildStream project configuration.
-
-
-# General configuration defaults
-#
-
-# Require format version 0
-format-version: 0
-
-# Elements are found at the project root
-element-path: .
-
-# Store source references in element files
-ref-storage: inline
-
-# Variable Configuration
-#
-variables:
- # Path configuration, to be used in build instructions.
- prefix: "/usr"
- exec_prefix: "%{prefix}"
- bindir: "%{exec_prefix}/bin"
- sbindir: "%{exec_prefix}/sbin"
- libexecdir: "%{exec_prefix}/libexec"
- datadir: "%{prefix}/share"
- sysconfdir: "/etc"
- sharedstatedir: "%{prefix}/com"
- localstatedir: "/var"
- lib: "lib"
- libdir: "%{prefix}/%{lib}"
- debugdir: "%{libdir}/debug"
- includedir: "%{prefix}/include"
- docdir: "%{datadir}/doc"
- infodir: "%{datadir}/info"
- mandir: "%{datadir}/man"
-
- # Indicates the default build directory where input is
- # normally staged
- build-root: /buildstream/%{project-name}/%{element-name}
-
- # Indicates where the build system should look for configuration files
- conf-root: .
-
- # Indicates the build installation directory in the sandbox
- install-root: /buildstream-install
-
- # You need to override this with the commands specific for your system
- strip-binaries: ""
-
- # Generic implementation for reproducible python builds
- fix-pyc-timestamps: |
-
- find "%{install-root}" -name '*.pyc' -exec \
- dd if=/dev/zero of={} bs=1 count=4 seek=4 conv=notrunc ';'
-
-# Base sandbox environment, can be overridden by plugins
-environment:
- PATH: /usr/bin:/bin:/usr/sbin:/sbin
- SHELL: /bin/sh
- TERM: dumb
- USER: tomjon
- USERNAME: tomjon
- LOGNAME: tomjon
- LC_ALL: C
- HOME: /tmp
- TZ: UTC
-
- # For reproducible builds we use 2011-11-11 as a constant
- SOURCE_DATE_EPOCH: 1320937200
-
-# List of environment variables which should not be taken into
-# account when calculating a cache key for a given element.
-#
-environment-nocache: []
-
-# Configuration for the sandbox other than environment variables
-# should go in 'sandbox'. This just contains the UID and GID that
-# the user in the sandbox will have. Not all sandboxes will support
-# changing the values.
-sandbox:
- build-uid: 0
- build-gid: 0
-
-# Defaults for the 'split-rules' public data found on elements
-# in the 'bst' domain.
-#
-split-rules:
-
- # The runtime domain includes whatever is needed for the
- # built element to run, this includes stripped executables
- # and shared libraries by default.
- runtime:
- - |
- %{bindir}
- - |
- %{bindir}/*
- - |
- %{sbindir}
- - |
- %{sbindir}/*
- - |
- %{libexecdir}
- - |
- %{libexecdir}/*
- - |
- %{libdir}/lib*.so*
-
- # The devel domain includes additional things which
- # you may need for development.
- #
- # By default this includes header files, static libraries
- # and other metadata such as pkgconfig files, m4 macros and
- # libtool archives.
- devel:
- - |
- %{includedir}
- - |
- %{includedir}/**
- - |
- %{libdir}/lib*.a
- - |
- %{libdir}/lib*.la
- - |
- %{libdir}/pkgconfig/*.pc
- - |
- %{datadir}/pkgconfig/*.pc
- - |
- %{datadir}/aclocal/*.m4
-
- # The debug domain includes debugging information stripped
- # away from libraries and executables
- debug:
- - |
- %{debugdir}
- - |
- %{debugdir}/**
-
- # The doc domain includes documentation
- doc:
- - |
- %{docdir}
- - |
- %{docdir}/**
- - |
- %{infodir}
- - |
- %{infodir}/**
- - |
- %{mandir}
- - |
- %{mandir}/**
-
- # The locale domain includes translations etc
- locale:
- - |
- %{datadir}/locale
- - |
- %{datadir}/locale/**
- - |
- %{datadir}/i18n
- - |
- %{datadir}/i18n/**
- - |
- %{datadir}/zoneinfo
- - |
- %{datadir}/zoneinfo/**
-
-
-# Default behavior for `bst shell`
-#
-shell:
-
- # Command to run when `bst shell` does not provide a command
- #
- command: [ 'sh', '-i' ]
-
-# Defaults for bst commands
-#
-defaults:
-
- # Set default target elements to use when none are passed on the command line.
- # If none are configured in the project, default to all project elements.
- targets: []
diff --git a/buildstream/data/userconfig.yaml b/buildstream/data/userconfig.yaml
deleted file mode 100644
index 34fd300d1..000000000
--- a/buildstream/data/userconfig.yaml
+++ /dev/null
@@ -1,113 +0,0 @@
-# Default BuildStream user configuration.
-
-#
-# Work Directories
-#
-#
-# Note that BuildStream forces the XDG Base Directory names
-# into the environment if they are not already set, and allows
-# expansion of '~' and environment variables when specifying
-# paths.
-#
-
-# Location to store sources
-sourcedir: ${XDG_CACHE_HOME}/buildstream/sources
-
-# Root location for other directories in the cache
-cachedir: ${XDG_CACHE_HOME}/buildstream
-
-# Location to store build logs
-logdir: ${XDG_CACHE_HOME}/buildstream/logs
-
-# Default root location for workspaces, blank for no default set.
-workspacedir: .
-
-#
-# Cache
-#
-cache:
- # Size of the artifact cache in bytes - BuildStream will attempt to keep the
- # artifact cache within this size.
- # If the value is suffixed with K, M, G or T, the specified memory size is
- # parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base
- # 1024), respectively.
- # Alternatively, a percentage value may be specified, which is taken relative
- # to the isize of the file system containing the cache.
- quota: infinity
-
- # Whether to pull build trees when downloading element artifacts
- pull-buildtrees: False
-
- # Whether to cache build trees on artifact creation:
- #
- # always - Always cache artifact build tree content
- # auto - Only cache build trees when necessary, e.g., for failed builds
- # never - Never cache artifact build tree content. This is not recommended
- # for normal users as this breaks core functionality such as
- # debugging failed builds and may break additional functionality
- # in future versions.
- #
- cache-buildtrees: auto
-
-
-#
-# Scheduler
-#
-scheduler:
-
- # Maximum number of simultaneous downloading tasks.
- fetchers: 10
-
- # Maximum number of simultaneous build tasks.
- builders: 4
-
- # Maximum number of simultaneous uploading tasks.
- pushers: 4
-
- # Maximum number of retries for network tasks.
- network-retries: 2
-
- # What to do when an element fails, if not running in
- # interactive mode:
- #
- # continue - Continue queueing jobs as much as possible
- # quit - Exit after all ongoing jobs complete
- # terminate - Terminate any ongoing jobs and exit
- #
- on-error: quit
-
-
-#
-# Logging
-#
-logging:
-
- # The abbreviated cache key length to display in the UI
- key-length: 8
-
- # Whether to show extra detailed messages
- verbose: True
-
- # Maximum number of lines to print from the
- # end of a failing build log
- error-lines: 20
-
- # Maximum number of lines to print in a detailed
- # message on the console or in the master log (the full
- # messages are always recorded in the individual build
- # logs)
- message-lines: 20
-
- # Whether to enable debugging messages
- debug: False
-
- # Format string for printing the pipeline at startup, this
- # also determines the default display format for `bst show`
- element-format: |
-
- %{state: >12} %{full-key} %{name} %{workspace-dirs}
-
- # Format string for all log messages.
- message-format: |
-
- [%{elapsed}][%{key}][%{element}] %{action} %{message}
diff --git a/buildstream/element.py b/buildstream/element.py
deleted file mode 100644
index 70158f778..000000000
--- a/buildstream/element.py
+++ /dev/null
@@ -1,3062 +0,0 @@
-#
-# Copyright (C) 2016-2018 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>
-
-"""
-Element - Base element class
-============================
-
-
-.. _core_element_abstract_methods:
-
-Abstract Methods
-----------------
-For loading and configuration purposes, Elements must implement the
-:ref:`Plugin base class abstract methods <core_plugin_abstract_methods>`.
-
-
-.. _core_element_build_phase:
-
-Build Phase
-~~~~~~~~~~~
-The following methods are the foundation of the element's *build
-phase*, they must be implemented by all Element classes, unless
-explicitly stated otherwise.
-
-* :func:`Element.configure_sandbox() <buildstream.element.Element.configure_sandbox>`
-
- Configures the :class:`.Sandbox`. This is called before anything else
-
-* :func:`Element.stage() <buildstream.element.Element.stage>`
-
- Stage dependencies and :class:`Sources <buildstream.source.Source>` into
- the sandbox.
-
-* :func:`Element.prepare() <buildstream.element.Element.prepare>`
-
- Call preparation methods that should only be performed once in the
- lifetime of a build directory (e.g. autotools' ./configure).
-
- **Optional**: If left unimplemented, this step will be skipped.
-
-* :func:`Element.assemble() <buildstream.element.Element.assemble>`
-
- Perform the actual assembly of the element
-
-
-Miscellaneous
-~~~~~~~~~~~~~
-Miscellaneous abstract methods also exist:
-
-* :func:`Element.generate_script() <buildstream.element.Element.generate_script>`
-
- For the purpose of ``bst source checkout --include-build-scripts``, an Element may optionally implement this.
-
-
-Class Reference
----------------
-"""
-
-import os
-import re
-import stat
-import copy
-from collections import OrderedDict
-from collections.abc import Mapping
-import contextlib
-from contextlib import contextmanager
-from functools import partial
-from itertools import chain
-import tempfile
-import string
-
-from pyroaring import BitMap # pylint: disable=no-name-in-module
-
-from . import _yaml
-from ._variables import Variables
-from ._versions import BST_CORE_ARTIFACT_VERSION
-from ._exceptions import BstError, LoadError, LoadErrorReason, ImplError, \
- ErrorDomain, SourceCacheError
-from .utils import UtilError
-from . import utils
-from . import _cachekey
-from . import _signals
-from . import _site
-from ._platform import Platform
-from .plugin import Plugin
-from .sandbox import SandboxFlags, SandboxCommandError
-from .sandbox._config import SandboxConfig
-from .sandbox._sandboxremote import SandboxRemote
-from .types import Consistency, CoreWarnings, Scope, _KeyStrength, _UniquePriorityQueue
-from ._artifact import Artifact
-
-from .storage.directory import Directory
-from .storage._filebaseddirectory import FileBasedDirectory
-from .storage._casbaseddirectory import CasBasedDirectory
-from .storage.directory import VirtualDirectoryError
-
-
-class ElementError(BstError):
- """This exception should be raised by :class:`.Element` implementations
- to report errors to the user.
-
- Args:
- message (str): The error message to report to the user
- detail (str): A possibly multiline, more detailed error message
- reason (str): An optional machine readable reason string, used for test cases
- collect (str): An optional directory containing partial install contents
- temporary (bool): An indicator to whether the error may occur if the operation was run again. (*Since: 1.2*)
- """
- def __init__(self, message, *, detail=None, reason=None, collect=None, temporary=False):
- super().__init__(message, detail=detail, domain=ErrorDomain.ELEMENT, reason=reason, temporary=temporary)
-
- self.collect = collect
-
-
-class Element(Plugin):
- """Element()
-
- Base Element class.
-
- All elements derive from this class, this interface defines how
- the core will be interacting with Elements.
- """
- __defaults = None # The defaults from the yaml file and project
- __instantiated_elements = {} # A hash of Element by MetaElement
- __redundant_source_refs = [] # A list of (source, ref) tuples which were redundantly specified
-
- BST_ARTIFACT_VERSION = 0
- """The element plugin's artifact version
-
- Elements must first set this to 1 if they change their unique key
- structure in a way that would produce a different key for the
- same input, or introduce a change in the build output for the
- same unique key. Further changes of this nature require bumping the
- artifact version.
- """
-
- BST_STRICT_REBUILD = False
- """Whether to rebuild this element in non strict mode if
- any of the dependencies have changed.
- """
-
- BST_FORBID_RDEPENDS = False
- """Whether to raise exceptions if an element has runtime dependencies.
-
- *Since: 1.2*
- """
-
- BST_FORBID_BDEPENDS = False
- """Whether to raise exceptions if an element has build dependencies.
-
- *Since: 1.2*
- """
-
- BST_FORBID_SOURCES = False
- """Whether to raise exceptions if an element has sources.
-
- *Since: 1.2*
- """
-
- BST_VIRTUAL_DIRECTORY = False
- """Whether to raise exceptions if an element uses Sandbox.get_directory
- instead of Sandbox.get_virtual_directory.
-
- *Since: 1.4*
- """
-
- BST_RUN_COMMANDS = True
- """Whether the element may run commands using Sandbox.run.
-
- *Since: 1.4*
- """
-
- def __init__(self, context, project, meta, plugin_conf):
-
- self.__cache_key_dict = None # Dict for cache key calculation
- self.__cache_key = None # Our cached cache key
-
- super().__init__(meta.name, context, project, meta.provenance, "element")
-
- # Ensure the project is fully loaded here rather than later on
- if not meta.is_junction:
- project.ensure_fully_loaded()
-
- self.normal_name = _get_normal_name(self.name)
- """A normalized element name
-
- This is the original element without path separators or
- the extension, it's used mainly for composing log file names
- and creating directory names and such.
- """
-
- self.__runtime_dependencies = [] # Direct runtime dependency Elements
- self.__build_dependencies = [] # Direct build dependency Elements
- self.__reverse_dependencies = set() # Direct reverse dependency Elements
- self.__ready_for_runtime = False # Wether the element has all its dependencies ready and has a cache key
- self.__sources = [] # List of Sources
- self.__weak_cache_key = None # Our cached weak cache key
- self.__strict_cache_key = None # Our cached cache key for strict builds
- self.__artifacts = context.artifactcache # Artifact cache
- self.__sourcecache = context.sourcecache # Source cache
- self.__consistency = Consistency.INCONSISTENT # Cached overall consistency state
- self.__assemble_scheduled = False # Element is scheduled to be assembled
- self.__assemble_done = False # Element is assembled
- self.__tracking_scheduled = False # Sources are scheduled to be tracked
- self.__tracking_done = False # Sources have been tracked
- self.__pull_done = False # Whether pull was attempted
- self.__splits = None # Resolved regex objects for computing split domains
- self.__whitelist_regex = None # Resolved regex object to check if file is allowed to overlap
- self.__staged_sources_directory = None # Location where Element.stage_sources() was called
- self.__tainted = None # Whether the artifact is tainted and should not be shared
- self.__required = False # Whether the artifact is required in the current session
- self.__artifact_files_required = False # Whether artifact files are required in the local cache
- self.__build_result = None # The result of assembling this Element (success, description, detail)
- self._build_log_path = None # The path of the build log for this Element
- self.__artifact = None # Artifact class for direct artifact composite interaction
- self.__strict_artifact = None # Artifact for strict cache key
-
- # the index of the last source in this element that requires previous
- # sources for staging
- self.__last_source_requires_previous_ix = None
-
- self.__batch_prepare_assemble = False # Whether batching across prepare()/assemble() is configured
- self.__batch_prepare_assemble_flags = 0 # Sandbox flags for batching across prepare()/assemble()
- self.__batch_prepare_assemble_collect = None # Collect dir for batching across prepare()/assemble()
-
- # Ensure we have loaded this class's defaults
- self.__init_defaults(project, plugin_conf, meta.kind, meta.is_junction)
-
- # Collect the composited variables and resolve them
- variables = self.__extract_variables(project, meta)
- _yaml.node_set(variables, 'element-name', self.name)
- self.__variables = Variables(variables)
-
- # Collect the composited environment now that we have variables
- unexpanded_env = self.__extract_environment(project, meta)
- self.__environment = self.__expand_environment(unexpanded_env)
-
- # Collect the environment nocache blacklist list
- nocache = self.__extract_env_nocache(project, meta)
- self.__env_nocache = nocache
-
- # Grab public domain data declared for this instance
- unexpanded_public = self.__extract_public(meta)
- self.__public = self.__expand_splits(unexpanded_public)
- self.__dynamic_public = None
-
- # Collect the composited element configuration and
- # ask the element to configure itself.
- self.__config = self.__extract_config(meta)
- self._configure(self.__config)
-
- # Extract remote execution URL
- if meta.is_junction:
- self.__remote_execution_specs = None
- else:
- self.__remote_execution_specs = project.remote_execution_specs
-
- # Extract Sandbox config
- self.__sandbox_config = self.__extract_sandbox_config(project, meta)
-
- self.__sandbox_config_supported = True
- if not self.__use_remote_execution():
- platform = Platform.get_platform()
- if not platform.check_sandbox_config(self.__sandbox_config):
- # Local sandbox does not fully support specified sandbox config.
- # This will taint the artifact, disable pushing.
- self.__sandbox_config_supported = False
-
- def __lt__(self, other):
- return self.name < other.name
-
- #############################################################
- # Abstract Methods #
- #############################################################
- def configure_sandbox(self, sandbox):
- """Configures the the sandbox for execution
-
- Args:
- sandbox (:class:`.Sandbox`): The build sandbox
-
- Raises:
- (:class:`.ElementError`): When the element raises an error
-
- Elements must implement this method to configure the sandbox object
- for execution.
- """
- raise ImplError("element plugin '{kind}' does not implement configure_sandbox()".format(
- kind=self.get_kind()))
-
- def stage(self, sandbox):
- """Stage inputs into the sandbox directories
-
- Args:
- sandbox (:class:`.Sandbox`): The build sandbox
-
- Raises:
- (:class:`.ElementError`): When the element raises an error
-
- Elements must implement this method to populate the sandbox
- directory with data. This is done either by staging :class:`.Source`
- objects, by staging the artifacts of the elements this element depends
- on, or both.
- """
- 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.
-
- *Since: 1.2*
- """
-
- def assemble(self, sandbox):
- """Assemble the output artifact
-
- Args:
- sandbox (:class:`.Sandbox`): The build sandbox
-
- Returns:
- (str): An absolute path within the sandbox to collect the artifact from
-
- Raises:
- (:class:`.ElementError`): When the element raises an error
-
- Elements must implement this method to create an output
- artifact from its sources and dependencies.
- """
- raise ImplError("element plugin '{kind}' does not implement assemble()".format(
- kind=self.get_kind()))
-
- def generate_script(self):
- """Generate a build (sh) script to build this element
-
- Returns:
- (str): A string containing the shell commands required to build the element
-
- BuildStream guarantees the following environment when the
- generated script is run:
-
- - All element variables have been exported.
- - The cwd is `self.get_variable('build-root')/self.normal_name`.
- - $PREFIX is set to `self.get_variable('install-root')`.
- - The directory indicated by $PREFIX is an empty directory.
-
- Files are expected to be installed to $PREFIX.
-
- If the script fails, it is expected to return with an exit
- code != 0.
- """
- raise ImplError("element plugin '{kind}' does not implement write_script()".format(
- kind=self.get_kind()))
-
- #############################################################
- # Public Methods #
- #############################################################
- def sources(self):
- """A generator function to enumerate the element sources
-
- Yields:
- (:class:`.Source`): The sources of this element
- """
- for source in self.__sources:
- yield source
-
- def dependencies(self, scope, *, recurse=True, visited=None):
- """dependencies(scope, *, recurse=True)
-
- A generator function which yields the dependencies of the given element.
-
- If `recurse` is specified (the default), the full dependencies will be listed
- in deterministic staging order, starting with the basemost elements in the
- given `scope`. Otherwise, if `recurse` is not specified then only the direct
- dependencies in the given `scope` will be traversed, and the element itself
- will be omitted.
-
- Args:
- scope (:class:`.Scope`): The scope to iterate in
- recurse (bool): Whether to recurse
-
- Yields:
- (:class:`.Element`): The dependencies in `scope`, in deterministic staging order
- """
- # The format of visited is (BitMap(), BitMap()), with the first BitMap
- # containing element that have been visited for the `Scope.BUILD` case
- # and the second one relating to the `Scope.RUN` case.
- if not recurse:
- if scope in (Scope.BUILD, Scope.ALL):
- yield from self.__build_dependencies
- if scope in (Scope.RUN, Scope.ALL):
- yield from self.__runtime_dependencies
- else:
- def visit(element, scope, visited):
- if scope == Scope.ALL:
- visited[0].add(element._unique_id)
- visited[1].add(element._unique_id)
-
- for dep in chain(element.__build_dependencies, element.__runtime_dependencies):
- if dep._unique_id not in visited[0] and dep._unique_id not in visited[1]:
- yield from visit(dep, Scope.ALL, visited)
-
- yield element
- elif scope == Scope.BUILD:
- visited[0].add(element._unique_id)
-
- for dep in element.__build_dependencies:
- if dep._unique_id not in visited[1]:
- yield from visit(dep, Scope.RUN, visited)
-
- elif scope == Scope.RUN:
- visited[1].add(element._unique_id)
-
- for dep in element.__runtime_dependencies:
- if dep._unique_id not in visited[1]:
- yield from visit(dep, Scope.RUN, visited)
-
- yield element
- else:
- yield element
-
- if visited is None:
- # Visited is of the form (Visited for Scope.BUILD, Visited for Scope.RUN)
- visited = (BitMap(), BitMap())
- else:
- # We have already a visited set passed. we might be able to short-circuit
- if scope in (Scope.BUILD, Scope.ALL) and self._unique_id in visited[0]:
- return
- if scope in (Scope.RUN, Scope.ALL) and self._unique_id in visited[1]:
- return
-
- yield from visit(self, scope, visited)
-
- def search(self, scope, name):
- """Search for a dependency by name
-
- Args:
- scope (:class:`.Scope`): The scope to search
- name (str): The dependency to search for
-
- Returns:
- (:class:`.Element`): The dependency element, or None if not found.
- """
- for dep in self.dependencies(scope):
- if dep.name == name:
- return dep
-
- return None
-
- def node_subst_member(self, node, member_name, default=_yaml._sentinel):
- """Fetch the value of a string node member, substituting any variables
- in the loaded value with the element contextual variables.
-
- Args:
- node (dict): A dictionary loaded from YAML
- member_name (str): The name of the member to fetch
- default (str): A value to return when *member_name* is not specified in *node*
-
- Returns:
- The value of *member_name* in *node*, otherwise *default*
-
- Raises:
- :class:`.LoadError`: When *member_name* is not found and no *default* was provided
-
- This is essentially the same as :func:`~buildstream.plugin.Plugin.node_get_member`
- except that it assumes the expected type is a string and will also perform variable
- substitutions.
-
- **Example:**
-
- .. code:: python
-
- # Expect a string 'name' in 'node', substituting any
- # variables in the returned string
- name = self.node_subst_member(node, 'name')
- """
- value = self.node_get_member(node, str, member_name, default)
- try:
- return self.__variables.subst(value)
- except LoadError as e:
- provenance = _yaml.node_get_provenance(node, key=member_name)
- raise LoadError(e.reason, '{}: {}'.format(provenance, e), detail=e.detail) from e
-
- 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)
- ret = []
- for index, x in enumerate(value):
- try:
- ret.append(self.__variables.subst(x))
- except LoadError as e:
- provenance = _yaml.node_get_provenance(node, key=member_name, indices=[index])
- raise LoadError(e.reason, '{}: {}'.format(provenance, e), detail=e.detail) from e
- return ret
-
- 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.
-
- Args:
- node (dict): A dictionary loaded from YAML
- member_name (str): The name of the member to fetch
- indices (list of int): List of indices to search, in case of nested lists
-
- Returns:
- The value of the list element in *member_name* at the specified *indices*
-
- Raises:
- :class:`.LoadError`
-
- This is essentially the same as :func:`~buildstream.plugin.Plugin.node_get_list_element`
- except that it assumes the expected type is a string and will also perform variable
- substitutions.
-
- **Example:**
-
- .. code:: python
-
- # Fetch the list itself
- strings = self.node_get_member(node, list, 'strings')
-
- # Iterate over the list indices
- for i in range(len(strings)):
-
- # Fetch the strings in this list, substituting content
- # with our element's variables if needed
- string = self.node_subst_list_element(
- node, 'strings', [ i ])
- """
- value = self.node_get_list_element(node, str, member_name, indices)
- try:
- return self.__variables.subst(value)
- except LoadError as e:
- provenance = _yaml.node_get_provenance(node, key=member_name, indices=indices)
- raise LoadError(e.reason, '{}: {}'.format(provenance, e), detail=e.detail) from e
-
- def compute_manifest(self, *, include=None, exclude=None, orphans=True):
- """Compute and return this element's selective manifest
-
- The manifest consists on the list of file paths in the
- artifact. The files in the manifest are selected according to
- `include`, `exclude` and `orphans` parameters. If `include` is
- not specified then all files spoken for by any domain are
- included unless explicitly excluded with an `exclude` domain.
-
- Args:
- include (list): An optional list of domains to include files from
- exclude (list): An optional list of domains to exclude files from
- orphans (bool): Whether to include files not spoken for by split domains
-
- Yields:
- (str): The paths of the files in manifest
- """
- self.__assert_cached()
- return self.__compute_splits(include, exclude, orphans)
-
- def get_artifact_name(self, key=None):
- """Compute and return this element's full artifact name
-
- Generate a full name for an artifact, including the project
- namespace, element name and cache key.
-
- This can also be used as a relative path safely, and
- will normalize parts of the element name such that only
- digits, letters and some select characters are allowed.
-
- Args:
- key (str): The element's cache key. Defaults to None
-
- Returns:
- (str): The relative path for the artifact
- """
- project = self._get_project()
- if key is None:
- key = self._get_cache_key()
-
- assert key is not None
-
- return _compose_artifact_name(project.name, self.normal_name, key)
-
- def stage_artifact(self, sandbox, *, path=None, include=None, exclude=None, orphans=True, update_mtimes=None):
- """Stage this element's output artifact in the sandbox
-
- This will stage the files from the artifact to the sandbox at specified location.
- The files are selected for staging according to the `include`, `exclude` and `orphans`
- parameters; if `include` is not specified then all files spoken for by any domain
- are included unless explicitly excluded with an `exclude` domain.
-
- Args:
- sandbox (:class:`.Sandbox`): The build sandbox
- path (str): An optional sandbox relative path
- include (list): An optional list of domains to include files from
- exclude (list): An optional list of domains to exclude files from
- orphans (bool): Whether to include files not spoken for by split domains
- update_mtimes (list): An optional list of files whose mtimes to set to the current time.
-
- Raises:
- (:class:`.ElementError`): If the element has not yet produced an artifact.
-
- Returns:
- (:class:`~.utils.FileListResult`): The result describing what happened while staging
-
- .. note::
-
- Directories in `dest` are replaced with files from `src`,
- unless the existing directory in `dest` is not empty in
- which case the path will be reported in the return value.
-
- **Example:**
-
- .. code:: python
-
- # Stage the dependencies for a build of 'self'
- for dep in self.dependencies(Scope.BUILD):
- dep.stage_artifact(sandbox)
- """
-
- if not self._cached():
- detail = "No artifacts have been cached yet for that element\n" + \
- "Try building the element first with `bst build`\n"
- raise ElementError("No artifacts to stage",
- detail=detail, reason="uncached-checkout-attempt")
-
- if update_mtimes is None:
- update_mtimes = []
-
- # Time to use the artifact, check once more that it's there
- self.__assert_cached()
-
- with self.timed_activity("Staging {}/{}".format(self.name, self._get_brief_display_key())):
- files_vdir = self.__artifact.get_files()
-
- # Hard link it into the staging area
- #
- vbasedir = sandbox.get_virtual_directory()
- vstagedir = vbasedir \
- if path is None \
- else vbasedir.descend(*path.lstrip(os.sep).split(os.sep))
-
- split_filter = self.__split_filter_func(include, exclude, orphans)
-
- # We must not hardlink files whose mtimes we want to update
- if update_mtimes:
- def link_filter(path):
- return ((split_filter is None or split_filter(path)) and
- path not in update_mtimes)
-
- def copy_filter(path):
- return ((split_filter is None or split_filter(path)) and
- path in update_mtimes)
- else:
- link_filter = split_filter
-
- result = vstagedir.import_files(files_vdir, filter_callback=link_filter,
- report_written=True, can_link=True)
-
- if update_mtimes:
- copy_result = vstagedir.import_files(files_vdir, filter_callback=copy_filter,
- report_written=True, update_mtime=True)
- result = result.combine(copy_result)
-
- return result
-
- def stage_dependency_artifacts(self, sandbox, scope, *, path=None,
- include=None, exclude=None, orphans=True):
- """Stage element dependencies in scope
-
- This is primarily a convenience wrapper around
- :func:`Element.stage_artifact() <buildstream.element.Element.stage_artifact>`
- which takes care of staging all the dependencies in `scope` and issueing the
- appropriate warnings.
-
- Args:
- sandbox (:class:`.Sandbox`): The build sandbox
- scope (:class:`.Scope`): The scope to stage dependencies in
- path (str): An optional sandbox relative path
- include (list): An optional list of domains to include files from
- exclude (list): An optional list of domains to exclude files from
- orphans (bool): Whether to include files not spoken for by split domains
-
- Raises:
- (:class:`.ElementError`): If any of the dependencies in `scope` have not
- yet produced artifacts, or if forbidden overlaps
- occur.
- """
- ignored = {}
- overlaps = OrderedDict()
- files_written = {}
- old_dep_keys = None
- workspace = self._get_workspace()
- context = self._get_context()
-
- if self.__can_build_incrementally() and workspace.last_successful:
-
- # Try to perform an incremental build if the last successful
- # build is still in the artifact cache
- #
- if self.__artifacts.contains(self, workspace.last_successful):
- last_successful = Artifact(self, context, strong_key=workspace.last_successful)
- # Get a dict of dependency strong keys
- old_dep_keys = last_successful.get_metadata_dependencies()
- else:
- # Last successful build is no longer in the artifact cache,
- # so let's reset it and perform a full build now.
- workspace.prepared = False
- workspace.last_successful = None
-
- self.info("Resetting workspace state, last successful build is no longer in the cache")
-
- # In case we are staging in the main process
- if utils._is_main_process():
- context.get_workspaces().save_config()
-
- for dep in self.dependencies(scope):
- # If we are workspaced, and we therefore perform an
- # incremental build, we must ensure that we update the mtimes
- # of any files created by our dependencies since the last
- # successful build.
- to_update = None
- if workspace and old_dep_keys:
- dep.__assert_cached()
-
- if dep.name in old_dep_keys:
- key_new = dep._get_cache_key()
- key_old = old_dep_keys[dep.name]
-
- # We only need to worry about modified and added
- # files, since removed files will be picked up by
- # build systems anyway.
- to_update, _, added = self.__artifacts.diff(dep, key_old, key_new)
- workspace.add_running_files(dep.name, to_update + added)
- to_update.extend(workspace.running_files[dep.name])
-
- # In case we are running `bst shell`, this happens in the
- # main process and we need to update the workspace config
- if utils._is_main_process():
- context.get_workspaces().save_config()
-
- result = dep.stage_artifact(sandbox,
- path=path,
- include=include,
- exclude=exclude,
- orphans=orphans,
- update_mtimes=to_update)
- if result.overwritten:
- for overwrite in result.overwritten:
- # Completely new overwrite
- if overwrite not in overlaps:
- # Find the overwritten element by checking where we've
- # written the element before
- for elm, contents in files_written.items():
- if overwrite in contents:
- overlaps[overwrite] = [elm, dep.name]
- else:
- overlaps[overwrite].append(dep.name)
- files_written[dep.name] = result.files_written
-
- if result.ignored:
- ignored[dep.name] = result.ignored
-
- if overlaps:
- overlap_warning = False
- warning_detail = "Staged files overwrite existing files in staging area:\n"
- for f, elements in overlaps.items():
- overlap_warning_elements = []
- # The bottom item overlaps nothing
- overlapping_elements = elements[1:]
- for elm in overlapping_elements:
- element = self.search(scope, elm)
- if not element.__file_is_whitelisted(f):
- overlap_warning_elements.append(elm)
- overlap_warning = True
-
- warning_detail += _overlap_error_detail(f, overlap_warning_elements, elements)
-
- if overlap_warning:
- self.warn("Non-whitelisted overlaps detected", detail=warning_detail,
- warning_token=CoreWarnings.OVERLAPS)
-
- if ignored:
- detail = "Not staging files which would replace non-empty directories:\n"
- for key, value in ignored.items():
- detail += "\nFrom {}:\n".format(key)
- detail += " " + " ".join(["/" + f + "\n" for f in value])
- self.warn("Ignored files", detail=detail)
-
- def integrate(self, sandbox):
- """Integrate currently staged filesystem against this artifact.
-
- Args:
- sandbox (:class:`.Sandbox`): The build sandbox
-
- This modifies the sysroot staged inside the sandbox so that
- the sysroot is *integrated*. Only an *integrated* sandbox
- may be trusted for running the software therein, as the integration
- commands will create and update important system cache files
- required for running the installed software (such as the ld.so.cache).
- """
- bstdata = self.get_public_data('bst')
- environment = self.get_environment()
-
- if bstdata is not None:
- with sandbox.batch(SandboxFlags.NONE):
- commands = self.node_get_member(bstdata, list, 'integration-commands', [])
- for i in range(len(commands)):
- cmd = self.node_subst_list_element(bstdata, 'integration-commands', [i])
-
- sandbox.run(['sh', '-e', '-c', cmd], 0, env=environment, cwd='/',
- label=cmd)
-
- def stage_sources(self, sandbox, directory):
- """Stage this element's sources to a directory in the sandbox
-
- Args:
- sandbox (:class:`.Sandbox`): The build sandbox
- directory (str): An absolute path within the sandbox to stage the sources at
- """
-
- # Hold on to the location where a plugin decided to stage sources,
- # this will be used to reconstruct the failed sysroot properly
- # after a failed build.
- #
- assert self.__staged_sources_directory is None
- self.__staged_sources_directory = directory
-
- self._stage_sources_in_sandbox(sandbox, directory)
-
- def get_public_data(self, domain):
- """Fetch public data on this element
-
- Args:
- domain (str): A public domain name to fetch data for
-
- Returns:
- (dict): The public data dictionary for the given domain
-
- .. note::
-
- This can only be called the abstract methods which are
- called as a part of the :ref:`build phase <core_element_build_phase>`
- and never before.
- """
- if self.__dynamic_public is None:
- self.__load_public_data()
-
- data = _yaml.node_get(self.__dynamic_public, Mapping, domain, default_value=None)
- if data is not None:
- data = _yaml.node_copy(data)
-
- return data
-
- def set_public_data(self, domain, data):
- """Set public data on this element
-
- Args:
- domain (str): A public domain name to fetch data for
- data (dict): The public data dictionary for the given domain
-
- This allows an element to dynamically mutate public data of
- elements or add new domains as the result of success completion
- of the :func:`Element.assemble() <buildstream.element.Element.assemble>`
- method.
- """
- if self.__dynamic_public is None:
- self.__load_public_data()
-
- if data is not None:
- data = _yaml.node_copy(data)
-
- _yaml.node_set(self.__dynamic_public, domain, data)
-
- def get_environment(self):
- """Fetch the environment suitable for running in the sandbox
-
- Returns:
- (dict): A dictionary of string key/values suitable for passing
- to :func:`Sandbox.run() <buildstream.sandbox.Sandbox.run>`
- """
- return _yaml.node_sanitize(self.__environment)
-
- def get_variable(self, varname):
- """Fetch the value of a variable resolved for this element.
-
- Args:
- varname (str): The name of the variable to fetch
-
- Returns:
- (str): The resolved value for *varname*, or None if no
- variable was declared with the given name.
- """
- return self.__variables.flat.get(varname)
-
- def batch_prepare_assemble(self, flags, *, collect=None):
- """ Configure command batching across prepare() and assemble()
-
- Args:
- flags (:class:`.SandboxFlags`): The sandbox flags for the command batch
- collect (str): An optional directory containing partial install contents
- on command failure.
-
- This may be called in :func:`Element.configure_sandbox() <buildstream.element.Element.configure_sandbox>`
- to enable batching of all sandbox commands issued in prepare() and assemble().
- """
- if self.__batch_prepare_assemble:
- raise ElementError("{}: Command batching for prepare/assemble is already configured".format(self))
-
- self.__batch_prepare_assemble = True
- self.__batch_prepare_assemble_flags = flags
- self.__batch_prepare_assemble_collect = collect
-
- #############################################################
- # Private Methods used in BuildStream #
- #############################################################
-
- # _new_from_meta():
- #
- # Recursively instantiate a new Element instance, its sources
- # and its dependencies from a meta element.
- #
- # Args:
- # meta (MetaElement): The meta element
- #
- # Returns:
- # (Element): A newly created Element instance
- #
- @classmethod
- def _new_from_meta(cls, meta):
-
- if not meta.first_pass:
- meta.project.ensure_fully_loaded()
-
- if meta in cls.__instantiated_elements:
- return cls.__instantiated_elements[meta]
-
- element = meta.project.create_element(meta, first_pass=meta.first_pass)
- cls.__instantiated_elements[meta] = element
-
- # Instantiate sources and generate their keys
- for meta_source in meta.sources:
- meta_source.first_pass = meta.is_junction
- source = meta.project.create_source(meta_source,
- first_pass=meta.first_pass)
-
- redundant_ref = source._load_ref()
- element.__sources.append(source)
-
- # Collect redundant refs which occurred at load time
- if redundant_ref is not None:
- cls.__redundant_source_refs.append((source, redundant_ref))
-
- # Instantiate dependencies
- for meta_dep in meta.dependencies:
- dependency = Element._new_from_meta(meta_dep)
- element.__runtime_dependencies.append(dependency)
- dependency.__reverse_dependencies.add(element)
-
- for meta_dep in meta.build_dependencies:
- dependency = Element._new_from_meta(meta_dep)
- element.__build_dependencies.append(dependency)
- dependency.__reverse_dependencies.add(element)
-
- return element
-
- # _clear_meta_elements_cache()
- #
- # Clear the internal meta elements cache.
- #
- # When loading elements from meta, we cache already instantiated elements
- # in order to not have to load the same elements twice.
- # This clears the cache.
- #
- # It should be called whenever we are done loading all elements in order
- # to save memory.
- #
- @classmethod
- def _clear_meta_elements_cache(cls):
- cls.__instantiated_elements = {}
-
- # _get_redundant_source_refs()
- #
- # Fetches a list of (Source, ref) tuples of all the Sources
- # which were loaded with a ref specified in the element declaration
- # for projects which use project.refs ref-storage.
- #
- # This is used to produce a warning
- @classmethod
- def _get_redundant_source_refs(cls):
- return cls.__redundant_source_refs
-
- # _reset_load_state()
- #
- # This is called by Pipeline.cleanup() and is used to
- # reset the loader state between multiple sessions.
- #
- @classmethod
- def _reset_load_state(cls):
- cls.__instantiated_elements = {}
- cls.__redundant_source_refs = []
-
- # _get_consistency()
- #
- # Returns cached consistency state
- #
- def _get_consistency(self):
- return self.__consistency
-
- # _cached():
- #
- # Returns:
- # (bool): Whether this element is already present in
- # the artifact cache
- #
- def _cached(self):
- if not self.__artifact:
- return False
-
- return self.__artifact.cached()
-
- # _get_build_result():
- #
- # Returns:
- # (bool): Whether the artifact of this element present in the artifact cache is of a success
- # (str): Short description of the result
- # (str): Detailed description of the result
- #
- def _get_build_result(self):
- if self.__build_result is None:
- self.__load_build_result()
-
- return self.__build_result
-
- # __set_build_result():
- #
- # Sets the assembly result
- #
- # Args:
- # success (bool): Whether the result is a success
- # description (str): Short description of the result
- # detail (str): Detailed description of the result
- #
- def __set_build_result(self, success, description, detail=None):
- self.__build_result = (success, description, detail)
-
- # _cached_success():
- #
- # Returns:
- # (bool): Whether this element is already present in
- # the artifact cache and the element assembled successfully
- #
- def _cached_success(self):
- if not self._cached():
- return False
-
- success, _, _ = self._get_build_result()
- return success
-
- # _cached_failure():
- #
- # Returns:
- # (bool): Whether this element is already present in
- # the artifact cache and the element did not assemble successfully
- #
- def _cached_failure(self):
- if not self._cached():
- return False
-
- success, _, _ = self._get_build_result()
- return not success
-
- # _buildable():
- #
- # Returns:
- # (bool): Whether this element can currently be built
- #
- def _buildable(self):
- if self._get_consistency() < Consistency.CACHED and \
- not self._source_cached():
- return False
-
- for dependency in self.dependencies(Scope.BUILD):
- # In non-strict mode an element's strong cache key may not be available yet
- # even though an artifact is available in the local cache. This can happen
- # if the pull job is still pending as the remote cache may have an artifact
- # that matches the strict cache key, which is preferred over a locally
- # cached artifact with a weak cache key match.
- if not dependency._cached_success() or not dependency._get_cache_key(strength=_KeyStrength.STRONG):
- return False
-
- if not self.__assemble_scheduled:
- return False
-
- return True
-
- # _get_cache_key():
- #
- # Returns the cache key
- #
- # Args:
- # strength (_KeyStrength): Either STRONG or WEAK key strength
- #
- # Returns:
- # (str): A hex digest cache key for this Element, or None
- #
- # None is returned if information for the cache key is missing.
- #
- def _get_cache_key(self, strength=_KeyStrength.STRONG):
- if strength == _KeyStrength.STRONG:
- return self.__cache_key
- else:
- return self.__weak_cache_key
-
- # _can_query_cache():
- #
- # Returns whether the cache key required for cache queries is available.
- #
- # Returns:
- # (bool): True if cache can be queried
- #
- def _can_query_cache(self):
- # If build has already been scheduled, we know that the element is
- # not cached and thus can allow cache query even if the strict cache key
- # is not available yet.
- # This special case is required for workspaced elements to prevent
- # them from getting blocked in the pull queue.
- if self.__assemble_scheduled:
- return True
-
- # cache cannot be queried until strict cache key is available
- return self.__strict_cache_key is not None
-
- # _update_state()
- #
- # Keep track of element state. Calculate cache keys if possible and
- # check whether artifacts are cached.
- #
- # This must be called whenever the state of an element may have changed.
- #
- def _update_state(self):
- context = self._get_context()
-
- # Compute and determine consistency of sources
- self.__update_source_state()
-
- if self._get_consistency() == Consistency.INCONSISTENT:
- # Tracking may still be pending
- return
-
- if self._get_workspace() and self.__assemble_scheduled:
- # If we have an active workspace and are going to build, then
- # discard current cache key values as their correct values can only
- # be calculated once the build is complete
- self.__reset_cache_data()
- return
-
- self.__update_cache_keys()
- self.__update_artifact_state()
-
- # Workspaced sources are considered unstable if a build is pending
- # as the build will modify the contents of the workspace.
- # Determine as early as possible if a build is pending to discard
- # unstable cache keys.
- # Also, uncached workspaced elements must be assembled so we can know
- # the cache key.
- if (not self.__assemble_scheduled and not self.__assemble_done and
- self.__artifact and
- (self._is_required() or self._get_workspace()) and
- not self._cached_success() and
- not self._pull_pending()):
- self._schedule_assemble()
- return
-
- if not context.get_strict():
- self.__update_cache_key_non_strict()
-
- if not self.__ready_for_runtime and self.__cache_key is not None:
- self.__ready_for_runtime = all(
- dep.__ready_for_runtime for dep in self.__runtime_dependencies)
-
- # _get_display_key():
- #
- # Returns cache keys for display purposes
- #
- # Returns:
- # (str): A full hex digest cache key for this Element
- # (str): An abbreviated hex digest cache key for this Element
- # (bool): True if key should be shown as dim, False otherwise
- #
- # Question marks are returned if information for the cache key is missing.
- #
- def _get_display_key(self):
- context = self._get_context()
- dim_key = True
-
- cache_key = self._get_cache_key()
-
- if not cache_key:
- cache_key = "{:?<64}".format('')
- elif self._get_cache_key() == self.__strict_cache_key:
- # Strong cache key used in this session matches cache key
- # that would be used in strict build mode
- dim_key = False
-
- length = min(len(cache_key), context.log_key_length)
- return (cache_key, cache_key[0:length], dim_key)
-
- # _get_brief_display_key()
- #
- # Returns an abbreviated cache key for display purposes
- #
- # Returns:
- # (str): An abbreviated hex digest cache key for this Element
- #
- # Question marks are returned if information for the cache key is missing.
- #
- def _get_brief_display_key(self):
- _, display_key, _ = self._get_display_key()
- return display_key
-
- # _preflight():
- #
- # A wrapper for calling the abstract preflight() method on
- # the element and its sources.
- #
- def _preflight(self):
-
- if self.BST_FORBID_RDEPENDS and self.BST_FORBID_BDEPENDS:
- if any(self.dependencies(Scope.RUN, recurse=False)) or any(self.dependencies(Scope.BUILD, recurse=False)):
- raise ElementError("{}: Dependencies are forbidden for '{}' elements"
- .format(self, self.get_kind()), reason="element-forbidden-depends")
-
- if self.BST_FORBID_RDEPENDS:
- if any(self.dependencies(Scope.RUN, recurse=False)):
- raise ElementError("{}: Runtime dependencies are forbidden for '{}' elements"
- .format(self, self.get_kind()), reason="element-forbidden-rdepends")
-
- if self.BST_FORBID_BDEPENDS:
- if any(self.dependencies(Scope.BUILD, recurse=False)):
- raise ElementError("{}: Build dependencies are forbidden for '{}' elements"
- .format(self, self.get_kind()), reason="element-forbidden-bdepends")
-
- if self.BST_FORBID_SOURCES:
- if any(self.sources()):
- raise ElementError("{}: Sources are forbidden for '{}' elements"
- .format(self, self.get_kind()), reason="element-forbidden-sources")
-
- try:
- self.preflight()
- except BstError as e:
- # Prepend provenance to the error
- raise ElementError("{}: {}".format(self, e), reason=e.reason, detail=e.detail) from e
-
- # Ensure that the first source does not need access to previous soruces
- if self.__sources and self.__sources[0]._requires_previous_sources():
- raise ElementError("{}: {} cannot be the first source of an element "
- "as it requires access to previous sources"
- .format(self, self.__sources[0]))
-
- # Preflight the sources
- for source in self.sources():
- source._preflight()
-
- # _schedule_tracking():
- #
- # Force an element state to be inconsistent. Any sources appear to be
- # inconsistent.
- #
- # This is used across the pipeline in sessions where the
- # elements in question are going to be tracked, causing the
- # pipeline to rebuild safely by ensuring cache key recalculation
- # and reinterrogation of element state after tracking of elements
- # succeeds.
- #
- def _schedule_tracking(self):
- self.__tracking_scheduled = True
-
- # _tracking_done():
- #
- # This is called in the main process after the element has been tracked
- #
- def _tracking_done(self):
- assert self.__tracking_scheduled
-
- self.__tracking_scheduled = False
- self.__tracking_done = True
-
- self.__update_state_recursively()
-
- # _track():
- #
- # Calls track() on the Element sources
- #
- # Raises:
- # SourceError: If one of the element sources has an error
- #
- # Returns:
- # (list): A list of Source object ids and their new references
- #
- def _track(self):
- refs = []
- for index, source in enumerate(self.__sources):
- old_ref = source.get_ref()
- new_ref = source._track(self.__sources[0:index])
- refs.append((source._unique_id, new_ref))
-
- # Complimentary warning that the new ref will be unused.
- if old_ref != new_ref and self._get_workspace():
- detail = "This source has an open workspace.\n" \
- + "To start using the new reference, please close the existing workspace."
- source.warn("Updated reference will be ignored as source has open workspace", detail=detail)
-
- return refs
-
- # _prepare_sandbox():
- #
- # This stages things for either _shell() (below) or also
- # is used to stage things by the `bst artifact checkout` codepath
- #
- @contextmanager
- def _prepare_sandbox(self, scope, directory, shell=False, integrate=True, usebuildtree=False):
- # bst shell and bst artifact checkout require a local sandbox.
- bare_directory = bool(directory)
- with self.__sandbox(directory, config=self.__sandbox_config, allow_remote=False,
- bare_directory=bare_directory) as sandbox:
- sandbox._usebuildtree = usebuildtree
-
- # Configure always comes first, and we need it.
- self.__configure_sandbox(sandbox)
-
- # Stage something if we need it
- if not directory:
- if shell and scope == Scope.BUILD:
- self.stage(sandbox)
- else:
- # Stage deps in the sandbox root
- with self.timed_activity("Staging dependencies", silent_nested=True):
- self.stage_dependency_artifacts(sandbox, scope)
-
- # Run any integration commands provided by the dependencies
- # once they are all staged and ready
- if integrate:
- with self.timed_activity("Integrating sandbox"):
- for dep in self.dependencies(scope):
- dep.integrate(sandbox)
-
- yield sandbox
-
- # _stage_sources_in_sandbox():
- #
- # Stage this element's sources to a directory inside sandbox
- #
- # Args:
- # sandbox (:class:`.Sandbox`): The build sandbox
- # directory (str): An absolute path to stage the sources at
- # mount_workspaces (bool): mount workspaces if True, copy otherwise
- #
- def _stage_sources_in_sandbox(self, sandbox, directory, mount_workspaces=True):
-
- # Only artifact caches that implement diff() are allowed to
- # perform incremental builds.
- if mount_workspaces and self.__can_build_incrementally():
- workspace = self._get_workspace()
- sandbox.mark_directory(directory)
- sandbox._set_mount_source(directory, workspace.get_absolute_path())
-
- # Stage all sources that need to be copied
- sandbox_vroot = sandbox.get_virtual_directory()
- host_vdirectory = sandbox_vroot.descend(*directory.lstrip(os.sep).split(os.sep), create=True)
- self._stage_sources_at(host_vdirectory, mount_workspaces=mount_workspaces, usebuildtree=sandbox._usebuildtree)
-
- # _stage_sources_at():
- #
- # Stage this element's sources to a directory
- #
- # Args:
- # vdirectory (:class:`.storage.Directory`): A virtual directory object to stage sources into.
- # mount_workspaces (bool): mount workspaces if True, copy otherwise
- # usebuildtree (bool): use a the elements build tree as its source.
- #
- def _stage_sources_at(self, vdirectory, mount_workspaces=True, usebuildtree=False):
-
- context = self._get_context()
-
- # It's advantageous to have this temporary directory on
- # the same file system as the rest of our cache.
- with self.timed_activity("Staging sources", silent_nested=True), \
- utils._tempdir(dir=context.tmpdir, prefix='staging-temp') as temp_staging_directory:
-
- import_dir = temp_staging_directory
-
- if not isinstance(vdirectory, Directory):
- vdirectory = FileBasedDirectory(vdirectory)
- if not vdirectory.is_empty():
- raise ElementError("Staging directory '{}' is not empty".format(vdirectory))
-
- workspace = self._get_workspace()
- if workspace:
- # If mount_workspaces is set and we're doing incremental builds,
- # the workspace is already mounted into the sandbox.
- if not (mount_workspaces and self.__can_build_incrementally()):
- with self.timed_activity("Staging local files at {}"
- .format(workspace.get_absolute_path())):
- workspace.stage(import_dir)
-
- # Check if we have a cached buildtree to use
- elif usebuildtree:
- import_dir = self.__artifact.get_buildtree()
- if import_dir.is_empty():
- detail = "Element type either does not expect a buildtree or it was explictily cached without one."
- self.warn("WARNING: {} Artifact contains an empty buildtree".format(self.name), detail=detail)
-
- # No workspace or cached buildtree, stage source from source cache
- else:
- # Ensure sources are cached
- self.__cache_sources()
-
- if self.__sources:
-
- sourcecache = context.sourcecache
- # find last required source
- last_required_previous_ix = self.__last_source_requires_previous()
- import_dir = CasBasedDirectory(context.get_cascache())
-
- try:
- for source in self.__sources[last_required_previous_ix:]:
- source_dir = sourcecache.export(source)
- import_dir.import_files(source_dir)
- except SourceCacheError as e:
- raise ElementError("Error trying to export source for {}: {}"
- .format(self.name, e))
- except VirtualDirectoryError as e:
- raise ElementError("Error trying to import sources together for {}: {}"
- .format(self.name, e),
- reason="import-source-files-fail")
-
- with utils._deterministic_umask():
- vdirectory.import_files(import_dir)
-
- # Ensure deterministic mtime of sources at build time
- vdirectory.set_deterministic_mtime()
- # Ensure deterministic owners of sources at build time
- vdirectory.set_deterministic_user()
-
- # _set_required():
- #
- # Mark this element and its runtime dependencies as required.
- # This unblocks pull/fetch/build.
- #
- def _set_required(self):
- if self.__required:
- # Already done
- return
-
- self.__required = True
-
- # Request artifacts of runtime dependencies
- for dep in self.dependencies(Scope.RUN, recurse=False):
- dep._set_required()
-
- self._update_state()
-
- # _is_required():
- #
- # Returns whether this element has been marked as required.
- #
- def _is_required(self):
- return self.__required
-
- # _set_artifact_files_required():
- #
- # Mark artifact files for this element and its runtime dependencies as
- # required in the local cache.
- #
- def _set_artifact_files_required(self):
- if self.__artifact_files_required:
- # Already done
- return
-
- self.__artifact_files_required = True
-
- # Request artifact files of runtime dependencies
- for dep in self.dependencies(Scope.RUN, recurse=False):
- dep._set_artifact_files_required()
-
- # _artifact_files_required():
- #
- # Returns whether artifact files for this element have been marked as required.
- #
- def _artifact_files_required(self):
- return self.__artifact_files_required
-
- # _schedule_assemble():
- #
- # This is called in the main process before the element is assembled
- # in a subprocess.
- #
- def _schedule_assemble(self):
- assert not self.__assemble_scheduled
- self.__assemble_scheduled = True
-
- # Requests artifacts of build dependencies
- for dep in self.dependencies(Scope.BUILD, recurse=False):
- dep._set_required()
-
- self._set_required()
-
- # Invalidate workspace key as the build modifies the workspace directory
- workspace = self._get_workspace()
- if workspace:
- workspace.invalidate_key()
-
- self._update_state()
-
- # _assemble_done():
- #
- # This is called in the main process after the element has been assembled
- # and in the a subprocess after assembly completes.
- #
- # This will result in updating the element state.
- #
- def _assemble_done(self):
- assert self.__assemble_scheduled
-
- self.__assemble_scheduled = False
- self.__assemble_done = True
-
- self.__update_state_recursively()
-
- if self._get_workspace() and self._cached_success():
- assert utils._is_main_process(), \
- "Attempted to save workspace configuration from child process"
- #
- # Note that this block can only happen in the
- # main process, since `self._cached_success()` cannot
- # be true when assembly is successful in the task.
- #
- # For this reason, it is safe to update and
- # save the workspaces configuration
- #
- key = self._get_cache_key()
- workspace = self._get_workspace()
- workspace.last_successful = key
- workspace.clear_running_files()
- self._get_context().get_workspaces().save_config()
-
- # This element will have already been marked as
- # required, but we bump the atime again, in case
- # we did not know the cache key until now.
- #
- # FIXME: This is not exactly correct, we should be
- # doing this at the time which we have discovered
- # a new cache key, this just happens to be the
- # last place where that can happen.
- #
- # Ultimately, we should be refactoring
- # Element._update_state() such that we know
- # when a cache key is actually discovered.
- #
- self.__artifacts.mark_required_elements([self])
-
- # _assemble():
- #
- # Internal method for running the entire build phase.
- #
- # This will:
- # - Prepare a sandbox for the build
- # - Call the public abstract methods for the build phase
- # - Cache the resulting artifact
- #
- # Returns:
- # (int): The size of the newly cached artifact
- #
- def _assemble(self):
-
- # Assert call ordering
- assert not self._cached_success()
-
- context = self._get_context()
- with self._output_file() as output_file:
-
- if not self.__sandbox_config_supported:
- self.warn("Sandbox configuration is not supported by the platform.",
- detail="Falling back to UID {} GID {}. Artifact will not be pushed."
- .format(self.__sandbox_config.build_uid, self.__sandbox_config.build_gid))
-
- # Explicitly clean it up, keep the build dir around if exceptions are raised
- os.makedirs(context.builddir, exist_ok=True)
- rootdir = tempfile.mkdtemp(prefix="{}-".format(self.normal_name), dir=context.builddir)
-
- # Cleanup the build directory on explicit SIGTERM
- def cleanup_rootdir():
- utils._force_rmtree(rootdir)
-
- with _signals.terminator(cleanup_rootdir), \
- self.__sandbox(rootdir, output_file, output_file, self.__sandbox_config) as sandbox: # noqa
-
- # Let the sandbox know whether the buildtree will be required.
- # This allows the remote execution sandbox to skip buildtree
- # download when it's not needed.
- buildroot = self.get_variable('build-root')
- cache_buildtrees = context.cache_buildtrees
- if cache_buildtrees != 'never':
- always_cache_buildtrees = cache_buildtrees == 'always'
- sandbox._set_build_directory(buildroot, always=always_cache_buildtrees)
-
- if not self.BST_RUN_COMMANDS:
- # Element doesn't need to run any commands in the sandbox.
- #
- # Disable Sandbox.run() to allow CasBasedDirectory for all
- # sandboxes.
- sandbox._disable_run()
-
- # By default, the dynamic public data is the same as the static public data.
- # The plugin's assemble() method may modify this, though.
- self.__dynamic_public = _yaml.node_copy(self.__public)
-
- # Call the abstract plugin methods
-
- # Step 1 - Configure
- self.__configure_sandbox(sandbox)
- # Step 2 - Stage
- self.stage(sandbox)
- try:
- if self.__batch_prepare_assemble:
- cm = sandbox.batch(self.__batch_prepare_assemble_flags,
- collect=self.__batch_prepare_assemble_collect)
- else:
- cm = contextlib.suppress()
-
- with cm:
- # Step 3 - Prepare
- self.__prepare(sandbox)
- # Step 4 - Assemble
- collect = self.assemble(sandbox) # pylint: disable=assignment-from-no-return
-
- self.__set_build_result(success=True, description="succeeded")
- except (ElementError, SandboxCommandError) as e:
- # Shelling into a sandbox is useful to debug this error
- e.sandbox = True
-
- # If there is a workspace open on this element, it will have
- # been mounted for sandbox invocations instead of being staged.
- #
- # In order to preserve the correct failure state, we need to
- # copy over the workspace files into the appropriate directory
- # in the sandbox.
- #
- workspace = self._get_workspace()
- if workspace and self.__staged_sources_directory:
- sandbox_vroot = sandbox.get_virtual_directory()
- path_components = self.__staged_sources_directory.lstrip(os.sep).split(os.sep)
- sandbox_vpath = sandbox_vroot.descend(*path_components)
- try:
- sandbox_vpath.import_files(workspace.get_absolute_path())
- except UtilError as e2:
- self.warn("Failed to preserve workspace state for failed build sysroot: {}"
- .format(e2))
-
- self.__set_build_result(success=False, description=str(e), detail=e.detail)
- self._cache_artifact(rootdir, sandbox, e.collect)
-
- raise
- else:
- return self._cache_artifact(rootdir, sandbox, collect)
- finally:
- cleanup_rootdir()
-
- def _cache_artifact(self, rootdir, sandbox, collect):
-
- context = self._get_context()
- buildresult = self.__build_result
- publicdata = self.__dynamic_public
- sandbox_vroot = sandbox.get_virtual_directory()
- collectvdir = None
- sandbox_build_dir = None
-
- cache_buildtrees = context.cache_buildtrees
- build_success = buildresult[0]
-
- # cache_buildtrees defaults to 'auto', only caching buildtrees
- # when necessary, which includes failed builds.
- # If only caching failed artifact buildtrees, then query the build
- # result. Element types without a build-root dir will be cached
- # with an empty buildtreedir regardless of this configuration.
-
- if cache_buildtrees == 'always' or (cache_buildtrees == 'auto' and not build_success):
- try:
- sandbox_build_dir = sandbox_vroot.descend(
- *self.get_variable('build-root').lstrip(os.sep).split(os.sep))
- except VirtualDirectoryError:
- # Directory could not be found. Pre-virtual
- # directory behaviour was to continue silently
- # if the directory could not be found.
- pass
-
- if collect is not None:
- try:
- collectvdir = sandbox_vroot.descend(*collect.lstrip(os.sep).split(os.sep))
- except VirtualDirectoryError:
- pass
-
- # ensure we have cache keys
- self._assemble_done()
-
- with self.timed_activity("Caching artifact"):
- artifact_size = self.__artifact.cache(rootdir, sandbox_build_dir, collectvdir,
- buildresult, publicdata)
-
- if collect is not None and collectvdir is None:
- raise ElementError(
- "Directory '{}' was not found inside the sandbox, "
- "unable to collect artifact contents"
- .format(collect))
-
- return artifact_size
-
- def _get_build_log(self):
- return self._build_log_path
-
- # _fetch_done()
- #
- # Indicates that fetching the sources for this element has been done.
- #
- def _fetch_done(self):
- # We are not updating the state recursively here since fetching can
- # never end up in updating them.
-
- # Fetching changes the source state from RESOLVED to CACHED
- # Fetching cannot change the source state from INCONSISTENT to CACHED because
- # we prevent fetching when it's INCONSISTENT.
- # Therefore, only the source state will change.
- self.__update_source_state()
-
- # _pull_pending()
- #
- # Check whether the artifact will be pulled. If the pull operation is to
- # include a specific subdir of the element artifact (from cli or user conf)
- # then the local cache is queried for the subdirs existence.
- #
- # Returns:
- # (bool): Whether a pull operation is pending
- #
- def _pull_pending(self):
- if self._get_workspace():
- # Workspace builds are never pushed to artifact servers
- return False
-
- # Check whether the pull has been invoked with a specific subdir requested
- # in user context, as to complete a partial artifact
- pull_buildtrees = self._get_context().pull_buildtrees
-
- if self.__strict_artifact:
- if self.__strict_artifact.cached() and pull_buildtrees:
- # If we've specified a subdir, check if the subdir is cached locally
- # or if it's possible to get
- if self._cached_buildtree() or not self._buildtree_exists():
- return False
- elif self.__strict_artifact.cached():
- return False
-
- # Pull is pending if artifact remote server available
- # and pull has not been attempted yet
- return self.__artifacts.has_fetch_remotes(plugin=self) and not self.__pull_done
-
- # _pull_done()
- #
- # Indicate that pull was attempted.
- #
- # This needs to be called in the main process after a pull
- # succeeds or fails so that we properly update the main
- # process data model
- #
- # This will result in updating the element state.
- #
- def _pull_done(self):
- self.__pull_done = True
-
- self.__update_state_recursively()
-
- # _pull():
- #
- # Pull artifact from remote artifact repository into local artifact cache.
- #
- # Returns: True if the artifact has been downloaded, False otherwise
- #
- def _pull(self):
- context = self._get_context()
-
- # Get optional specific subdir to pull and optional list to not pull
- # based off of user context
- pull_buildtrees = context.pull_buildtrees
-
- # Attempt to pull artifact without knowing whether it's available
- pulled = self.__pull_strong(pull_buildtrees=pull_buildtrees)
-
- if not pulled and not self._cached() and not context.get_strict():
- pulled = self.__pull_weak(pull_buildtrees=pull_buildtrees)
-
- if not pulled:
- return False
-
- # Notify successfull download
- return True
-
- def _skip_source_push(self):
- if not self.__sources or self._get_workspace():
- return True
- return not (self.__sourcecache.has_push_remotes(plugin=self) and
- self._source_cached())
-
- def _source_push(self):
- # try and push sources if we've got them
- if self.__sourcecache.has_push_remotes(plugin=self) and self._source_cached():
- for source in self.sources():
- if not self.__sourcecache.push(source):
- return False
-
- # Notify successful upload
- return True
-
- # _skip_push():
- #
- # Determine whether we should create a push job for this element.
- #
- # Returns:
- # (bool): True if this element does not need a push job to be created
- #
- def _skip_push(self):
- if not self.__artifacts.has_push_remotes(plugin=self):
- # No push remotes for this element's project
- return True
-
- # Do not push elements that aren't cached, or that are cached with a dangling buildtree
- # ref unless element type is expected to have an an empty buildtree directory
- if not self._cached_buildtree() and self._buildtree_exists():
- return True
-
- # Do not push tainted artifact
- if self.__get_tainted():
- return True
-
- return False
-
- # _push():
- #
- # Push locally cached artifact to remote artifact repository.
- #
- # Returns:
- # (bool): True if the remote was updated, False if it already existed
- # and no updated was required
- #
- def _push(self):
- self.__assert_cached()
-
- if self.__get_tainted():
- self.warn("Not pushing tainted artifact.")
- return False
-
- # Push all keys used for local commit via the Artifact member
- pushed = self.__artifacts.push(self, self.__artifact)
- if not pushed:
- return False
-
- # Notify successful upload
- return True
-
- # _shell():
- #
- # Connects the terminal with a shell running in a staged
- # environment
- #
- # Args:
- # scope (Scope): Either BUILD or RUN scopes are valid, or None
- # directory (str): A directory to an existing sandbox, or None
- # mounts (list): A list of (str, str) tuples, representing host/target paths to mount
- # isolate (bool): Whether to isolate the environment like we do in builds
- # prompt (str): A suitable prompt string for PS1
- # command (list): An argv to launch in the sandbox
- # usebuildtree (bool): Use the buildtree as its source
- #
- # Returns: Exit code
- #
- # If directory is not specified, one will be staged using scope
- def _shell(self, scope=None, directory=None, *, mounts=None, isolate=False, prompt=None, command=None,
- usebuildtree=False):
-
- with self._prepare_sandbox(scope, directory, shell=True, usebuildtree=usebuildtree) as sandbox:
- environment = self.get_environment()
- environment = copy.copy(environment)
- flags = SandboxFlags.INTERACTIVE | SandboxFlags.ROOT_READ_ONLY
-
- # Fetch the main toplevel project, in case this is a junctioned
- # subproject, we want to use the rules defined by the main one.
- context = self._get_context()
- project = context.get_toplevel_project()
- shell_command, shell_environment, shell_host_files = project.get_shell_config()
-
- if prompt is not None:
- environment['PS1'] = prompt
-
- # Special configurations for non-isolated sandboxes
- if not isolate:
-
- # Open the network, and reuse calling uid/gid
- #
- flags |= SandboxFlags.NETWORK_ENABLED | SandboxFlags.INHERIT_UID
-
- # Apply project defined environment vars to set for a shell
- for key, value in _yaml.node_items(shell_environment):
- environment[key] = value
-
- # Setup any requested bind mounts
- if mounts is None:
- mounts = []
-
- for mount in shell_host_files + mounts:
- if not os.path.exists(mount.host_path):
- if not mount.optional:
- self.warn("Not mounting non-existing host file: {}".format(mount.host_path))
- else:
- sandbox.mark_directory(mount.path)
- sandbox._set_mount_source(mount.path, mount.host_path)
-
- if command:
- argv = [arg for arg in command]
- else:
- argv = shell_command
-
- self.status("Running command", detail=" ".join(argv))
-
- # Run shells with network enabled and readonly root.
- return sandbox.run(argv, flags, env=environment)
-
- # _open_workspace():
- #
- # "Open" a workspace for this element
- #
- # This requires that a workspace already be created in
- # the workspaces metadata first.
- #
- def _open_workspace(self):
- context = self._get_context()
- workspace = self._get_workspace()
- assert workspace is not None
-
- # First lets get a temp dir in our build directory
- # and stage there, then link the files over to the desired
- # path.
- #
- # We do this so that force opening workspaces which overwrites
- # files in the target directory actually works without any
- # additional support from Source implementations.
- #
- os.makedirs(context.builddir, exist_ok=True)
- with utils._tempdir(dir=context.builddir, prefix='workspace-{}'
- .format(self.normal_name)) as temp:
- for source in self.sources():
- source._init_workspace(temp)
-
- # Now hardlink the files into the workspace target.
- utils.link_files(temp, workspace.get_absolute_path())
-
- # _get_workspace():
- #
- # Returns:
- # (Workspace|None): A workspace associated with this element
- #
- def _get_workspace(self):
- workspaces = self._get_context().get_workspaces()
- return workspaces.get_workspace(self._get_full_name())
-
- # _write_script():
- #
- # Writes a script to the given directory.
- def _write_script(self, directory):
- with open(_site.build_module_template, "r") as f:
- script_template = f.read()
-
- variable_string = ""
- for var, val in self.get_environment().items():
- variable_string += "{0}={1} ".format(var, val)
-
- script = script_template.format(
- name=self.normal_name,
- build_root=self.get_variable('build-root'),
- install_root=self.get_variable('install-root'),
- variables=variable_string,
- commands=self.generate_script()
- )
-
- os.makedirs(directory, exist_ok=True)
- script_path = os.path.join(directory, "build-" + self.normal_name)
-
- with self.timed_activity("Writing build script", silent_nested=True):
- with utils.save_file_atomic(script_path, "w") as script_file:
- script_file.write(script)
-
- os.chmod(script_path, stat.S_IEXEC | stat.S_IREAD)
-
- # _subst_string()
- #
- # Substitue a string, this is an internal function related
- # to how junctions are loaded and needs to be more generic
- # than the public node_subst_member()
- #
- # Args:
- # value (str): A string value
- #
- # Returns:
- # (str): The string after substitutions have occurred
- #
- def _subst_string(self, value):
- return self.__variables.subst(value)
-
- # Returns the element whose sources this element is ultimately derived from.
- #
- # This is intended for being used to redirect commands that operate on an
- # element to the element whose sources it is ultimately derived from.
- #
- # For example, element A is a build element depending on source foo,
- # element B is a filter element that depends on element A. The source
- # element of B is A, since B depends on A, and A has sources.
- #
- def _get_source_element(self):
- return self
-
- # _cached_buildtree()
- #
- # Check if element artifact contains expected buildtree. An
- # element's buildtree artifact will not be present if the rest
- # of the partial artifact is not cached.
- #
- # Returns:
- # (bool): True if artifact cached with buildtree, False if
- # element not cached or missing expected buildtree.
- # Note this only confirms if a buildtree is present,
- # not its contents.
- #
- def _cached_buildtree(self):
- if not self._cached():
- return False
-
- return self.__artifact.cached_buildtree()
-
- # _buildtree_exists()
- #
- # Check if artifact was created with a buildtree. This does not check
- # whether the buildtree is present in the local cache.
- #
- # Returns:
- # (bool): True if artifact was created with buildtree, False if
- # element not cached or not created with a buildtree.
- #
- def _buildtree_exists(self):
- if not self._cached():
- return False
-
- return self.__artifact.buildtree_exists()
-
- # _cached_logs()
- #
- # Check if the artifact is cached with log files.
- #
- # Returns:
- # (bool): True if artifact is cached with logs, False if
- # element not cached or missing logs.
- #
- def _cached_logs(self):
- return self.__artifact.cached_logs()
-
- # _fetch()
- #
- # Fetch the element's sources.
- #
- # Raises:
- # SourceError: If one of the element sources has an error
- #
- def _fetch(self, fetch_original=False):
- previous_sources = []
- sources = self.__sources
- fetch_needed = False
- if sources and not fetch_original:
- for source in self.__sources:
- if self.__sourcecache.contains(source):
- continue
-
- # try and fetch from source cache
- if source._get_consistency() < Consistency.CACHED and \
- self.__sourcecache.has_fetch_remotes():
- if self.__sourcecache.pull(source):
- continue
-
- fetch_needed = True
-
- # We need to fetch original sources
- if fetch_needed or fetch_original:
- for source in self.sources():
- source_consistency = source._get_consistency()
- if source_consistency != Consistency.CACHED:
- source._fetch(previous_sources)
- previous_sources.append(source)
-
- self.__cache_sources()
-
- # _calculate_cache_key():
- #
- # Calculates the cache key
- #
- # Returns:
- # (str): A hex digest cache key for this Element, or None
- #
- # None is returned if information for the cache key is missing.
- #
- def _calculate_cache_key(self, dependencies):
- # No cache keys for dependencies which have no cache keys
- if None in dependencies:
- return None
-
- # Generate dict that is used as base for all cache keys
- if self.__cache_key_dict is None:
- # Filter out nocache variables from the element's environment
- cache_env = {
- key: value
- for key, value in self.__environment.items()
- if key not in self.__env_nocache
- }
-
- context = self._get_context()
- project = self._get_project()
- workspace = self._get_workspace()
-
- self.__cache_key_dict = {
- 'artifact-version': "{}.{}".format(BST_CORE_ARTIFACT_VERSION,
- self.BST_ARTIFACT_VERSION),
- 'context': context.get_cache_key(),
- 'project': project.get_cache_key(),
- 'element': self.get_unique_key(),
- 'execution-environment': self.__sandbox_config.get_unique_key(),
- 'environment': cache_env,
- 'sources': [s._get_unique_key(workspace is None) for s in self.__sources],
- 'workspace': '' if workspace is None else workspace.get_key(self._get_project()),
- 'public': self.__public,
- 'cache': 'CASCache'
- }
-
- self.__cache_key_dict['fatal-warnings'] = sorted(project._fatal_warnings)
-
- cache_key_dict = self.__cache_key_dict.copy()
- cache_key_dict['dependencies'] = dependencies
-
- return _cachekey.generate_key(cache_key_dict)
-
- # Check if sources are cached, generating the source key if it hasn't been
- def _source_cached(self):
- if self.__sources:
- sourcecache = self._get_context().sourcecache
-
- # Go through sources we'll cache generating keys
- for ix, source in enumerate(self.__sources):
- if not source._key:
- if source.BST_REQUIRES_PREVIOUS_SOURCES_STAGE:
- source._generate_key(self.__sources[:ix])
- else:
- source._generate_key([])
-
- # Check all sources are in source cache
- for source in self.__sources:
- if not sourcecache.contains(source):
- return False
-
- return True
-
- def _should_fetch(self, fetch_original=False):
- """ return bool of if we need to run the fetch stage for this element
-
- Args:
- fetch_original (bool): whether we need to original unstaged source
- """
- if (self._get_consistency() == Consistency.CACHED and fetch_original) or \
- (self._source_cached() and not fetch_original):
- return False
- else:
- return True
-
- #############################################################
- # Private Local Methods #
- #############################################################
-
- # __update_source_state()
- #
- # Updates source consistency state
- #
- # An element's source state must be resolved before it may compute
- # cache keys, because the source's ref, whether defined in yaml or
- # from the workspace, is a component of the element's cache keys.
- #
- def __update_source_state(self):
-
- # Cannot resolve source state until tracked
- if self.__tracking_scheduled:
- return
-
- self.__consistency = Consistency.CACHED
- workspace = self._get_workspace()
-
- # Special case for workspaces
- if workspace:
-
- # A workspace is considered inconsistent in the case
- # that its directory went missing
- #
- fullpath = workspace.get_absolute_path()
- if not os.path.exists(fullpath):
- self.__consistency = Consistency.INCONSISTENT
- else:
-
- # Determine overall consistency of the element
- for source in self.__sources:
- source._update_state()
- self.__consistency = min(self.__consistency, source._get_consistency())
-
- # __can_build_incrementally()
- #
- # Check if the element can be built incrementally, this
- # is used to decide how to stage things
- #
- # Returns:
- # (bool): Whether this element can be built incrementally
- #
- def __can_build_incrementally(self):
- return bool(self._get_workspace())
-
- # __configure_sandbox():
- #
- # Internal method for calling public abstract configure_sandbox() method.
- #
- def __configure_sandbox(self, sandbox):
- self.__batch_prepare_assemble = False
-
- self.configure_sandbox(sandbox)
-
- # __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:
- def mark_workspace_prepared():
- workspace.prepared = True
-
- # Defer workspace.prepared setting until pending batch commands
- # have been executed.
- sandbox._callback(mark_workspace_prepared)
-
- # __assert_cached()
- #
- # Raises an error if the artifact is not cached.
- #
- def __assert_cached(self):
- assert self._cached(), "{}: Missing artifact {}".format(
- self, self._get_brief_display_key())
-
- # __get_tainted():
- #
- # Checkes whether this artifact should be pushed to an artifact cache.
- #
- # Args:
- # recalculate (bool) - Whether to force recalculation
- #
- # Returns:
- # (bool) False if this artifact should be excluded from pushing.
- #
- # Note:
- # This method should only be called after the element's
- # artifact is present in the local artifact cache.
- #
- def __get_tainted(self, recalculate=False):
- if recalculate or self.__tainted is None:
-
- # Whether this artifact has a workspace
- workspaced = self.__artifact.get_metadata_workspaced()
-
- # Whether this artifact's dependencies have workspaces
- workspaced_dependencies = self.__artifact.get_metadata_workspaced_dependencies()
-
- # Other conditions should be or-ed
- self.__tainted = (workspaced or workspaced_dependencies or
- not self.__sandbox_config_supported)
-
- return self.__tainted
-
- # __use_remote_execution():
- #
- # Returns True if remote execution is configured and the element plugin
- # supports it.
- #
- def __use_remote_execution(self):
- return bool(self.__remote_execution_specs)
-
- # __sandbox():
- #
- # A context manager to prepare a Sandbox object at the specified directory,
- # if the directory is None, then a directory will be chosen automatically
- # in the configured build directory.
- #
- # Args:
- # directory (str): The local directory where the sandbox will live, or None
- # stdout (fileobject): The stream for stdout for the sandbox
- # stderr (fileobject): The stream for stderr for the sandbox
- # config (SandboxConfig): The SandboxConfig object
- # allow_remote (bool): Whether the sandbox is allowed to be remote
- # bare_directory (bool): Whether the directory is bare i.e. doesn't have
- # a separate 'root' subdir
- #
- # Yields:
- # (Sandbox): A usable sandbox
- #
- @contextmanager
- def __sandbox(self, directory, stdout=None, stderr=None, config=None, allow_remote=True, bare_directory=False):
- context = self._get_context()
- project = self._get_project()
- platform = Platform.get_platform()
-
- if directory is not None and allow_remote and self.__use_remote_execution():
-
- if not self.BST_VIRTUAL_DIRECTORY:
- raise ElementError("Element {} is configured to use remote execution but plugin does not support it."
- .format(self.name), detail="Plugin '{kind}' does not support virtual directories."
- .format(kind=self.get_kind()))
-
- self.info("Using a remote sandbox for artifact {} with directory '{}'".format(self.name, directory))
-
- output_files_required = context.require_artifact_files or self._artifact_files_required()
-
- sandbox = SandboxRemote(context, project,
- directory,
- plugin=self,
- stdout=stdout,
- stderr=stderr,
- config=config,
- specs=self.__remote_execution_specs,
- bare_directory=bare_directory,
- allow_real_directory=False,
- output_files_required=output_files_required)
- yield sandbox
-
- elif directory is not None and os.path.exists(directory):
-
- sandbox = platform.create_sandbox(context, project,
- directory,
- plugin=self,
- stdout=stdout,
- stderr=stderr,
- config=config,
- bare_directory=bare_directory,
- allow_real_directory=not self.BST_VIRTUAL_DIRECTORY)
- yield sandbox
-
- else:
- os.makedirs(context.builddir, exist_ok=True)
- rootdir = tempfile.mkdtemp(prefix="{}-".format(self.normal_name), dir=context.builddir)
-
- # Recursive contextmanager...
- with self.__sandbox(rootdir, stdout=stdout, stderr=stderr, config=config,
- allow_remote=allow_remote, bare_directory=False) as sandbox:
- yield sandbox
-
- # Cleanup the build dir
- utils._force_rmtree(rootdir)
-
- @classmethod
- def __compose_default_splits(cls, project, defaults, is_junction):
-
- element_public = _yaml.node_get(defaults, Mapping, 'public', default_value={})
- element_bst = _yaml.node_get(element_public, Mapping, 'bst', default_value={})
- element_splits = _yaml.node_get(element_bst, Mapping, 'split-rules', default_value={})
-
- if is_junction:
- splits = _yaml.node_copy(element_splits)
- else:
- assert project._splits is not None
-
- splits = _yaml.node_copy(project._splits)
- # Extend project wide split rules with any split rules defined by the element
- _yaml.composite(splits, element_splits)
-
- _yaml.node_set(element_bst, 'split-rules', splits)
- _yaml.node_set(element_public, 'bst', element_bst)
- _yaml.node_set(defaults, 'public', element_public)
-
- @classmethod
- def __init_defaults(cls, project, plugin_conf, kind, is_junction):
- # Defaults are loaded once per class and then reused
- #
- if cls.__defaults is None:
- defaults = _yaml.new_empty_node()
-
- if plugin_conf is not None:
- # Load the plugin's accompanying .yaml file if one was provided
- try:
- defaults = _yaml.load(plugin_conf, os.path.basename(plugin_conf))
- except LoadError as e:
- if e.reason != LoadErrorReason.MISSING_FILE:
- raise e
-
- # Special case; compose any element-wide split-rules declarations
- cls.__compose_default_splits(project, defaults, is_junction)
-
- # Override the element's defaults with element specific
- # overrides from the project.conf
- if is_junction:
- elements = project.first_pass_config.element_overrides
- else:
- elements = project.element_overrides
-
- overrides = _yaml.node_get(elements, Mapping, kind, default_value=None)
- if overrides:
- _yaml.composite(defaults, overrides)
-
- # Set the data class wide
- cls.__defaults = defaults
-
- # This will acquire the environment to be used when
- # creating sandboxes for this element
- #
- @classmethod
- def __extract_environment(cls, project, meta):
- default_env = _yaml.node_get(cls.__defaults, Mapping, 'environment', default_value={})
-
- if meta.is_junction:
- environment = _yaml.new_empty_node()
- else:
- environment = _yaml.node_copy(project.base_environment)
-
- _yaml.composite(environment, default_env)
- _yaml.composite(environment, meta.environment)
- _yaml.node_final_assertions(environment)
-
- return environment
-
- # This will resolve the final environment to be used when
- # creating sandboxes for this element
- #
- def __expand_environment(self, environment):
- # Resolve variables in environment value strings
- final_env = {}
- for key, _ in self.node_items(environment):
- final_env[key] = self.node_subst_member(environment, key)
-
- return final_env
-
- @classmethod
- def __extract_env_nocache(cls, project, meta):
- if meta.is_junction:
- project_nocache = []
- else:
- project_nocache = project.base_env_nocache
-
- default_nocache = _yaml.node_get(cls.__defaults, list, 'environment-nocache', default_value=[])
- element_nocache = meta.env_nocache
-
- # Accumulate values from the element default, the project and the element
- # itself to form a complete list of nocache env vars.
- env_nocache = set(project_nocache + default_nocache + element_nocache)
-
- # Convert back to list now we know they're unique
- return list(env_nocache)
-
- # This will resolve the final variables to be used when
- # substituting command strings to be run in the sandbox
- #
- @classmethod
- def __extract_variables(cls, project, meta):
- default_vars = _yaml.node_get(cls.__defaults, Mapping, 'variables',
- default_value={})
-
- if meta.is_junction:
- variables = _yaml.node_copy(project.first_pass_config.base_variables)
- else:
- variables = _yaml.node_copy(project.base_variables)
-
- _yaml.composite(variables, default_vars)
- _yaml.composite(variables, meta.variables)
- _yaml.node_final_assertions(variables)
-
- for var in ('project-name', 'element-name', 'max-jobs'):
- provenance = _yaml.node_get_provenance(variables, var)
- if provenance and not provenance.is_synthetic:
- raise LoadError(LoadErrorReason.PROTECTED_VARIABLE_REDEFINED,
- "{}: invalid redefinition of protected variable '{}'"
- .format(provenance, var))
-
- return variables
-
- # This will resolve the final configuration to be handed
- # off to element.configure()
- #
- @classmethod
- def __extract_config(cls, meta):
-
- # The default config is already composited with the project overrides
- config = _yaml.node_get(cls.__defaults, Mapping, 'config', default_value={})
- config = _yaml.node_copy(config)
-
- _yaml.composite(config, meta.config)
- _yaml.node_final_assertions(config)
-
- return config
-
- # Sandbox-specific configuration data, to be passed to the sandbox's constructor.
- #
- @classmethod
- def __extract_sandbox_config(cls, project, meta):
- if meta.is_junction:
- sandbox_config = _yaml.new_node_from_dict({
- 'build-uid': 0,
- 'build-gid': 0
- })
- else:
- sandbox_config = _yaml.node_copy(project._sandbox)
-
- # Get the platform to ask for host architecture
- platform = Platform.get_platform()
- host_arch = platform.get_host_arch()
- host_os = platform.get_host_os()
-
- # The default config is already composited with the project overrides
- sandbox_defaults = _yaml.node_get(cls.__defaults, Mapping, 'sandbox', default_value={})
- sandbox_defaults = _yaml.node_copy(sandbox_defaults)
-
- _yaml.composite(sandbox_config, sandbox_defaults)
- _yaml.composite(sandbox_config, meta.sandbox)
- _yaml.node_final_assertions(sandbox_config)
-
- # Sandbox config, unlike others, has fixed members so we should validate them
- _yaml.node_validate(sandbox_config, ['build-uid', 'build-gid', 'build-os', 'build-arch'])
-
- build_arch = _yaml.node_get(sandbox_config, str, 'build-arch', default_value=None)
- if build_arch:
- build_arch = Platform.canonicalize_arch(build_arch)
- else:
- build_arch = host_arch
-
- return SandboxConfig(
- _yaml.node_get(sandbox_config, int, 'build-uid'),
- _yaml.node_get(sandbox_config, int, 'build-gid'),
- _yaml.node_get(sandbox_config, str, 'build-os', default_value=host_os),
- build_arch)
-
- # This makes a special exception for the split rules, which
- # elements may extend but whos defaults are defined in the project.
- #
- @classmethod
- def __extract_public(cls, meta):
- base_public = _yaml.node_get(cls.__defaults, Mapping, 'public', default_value={})
- base_public = _yaml.node_copy(base_public)
-
- base_bst = _yaml.node_get(base_public, Mapping, 'bst', default_value={})
- base_splits = _yaml.node_get(base_bst, Mapping, 'split-rules', default_value={})
-
- element_public = _yaml.node_copy(meta.public)
- element_bst = _yaml.node_get(element_public, Mapping, 'bst', default_value={})
- element_splits = _yaml.node_get(element_bst, Mapping, 'split-rules', default_value={})
-
- # Allow elements to extend the default splits defined in their project or
- # element specific defaults
- _yaml.composite(base_splits, element_splits)
-
- _yaml.node_set(element_bst, 'split-rules', base_splits)
- _yaml.node_set(element_public, 'bst', element_bst)
-
- _yaml.node_final_assertions(element_public)
-
- return element_public
-
- # Expand the splits in the public data using the Variables in the element
- def __expand_splits(self, element_public):
- element_bst = _yaml.node_get(element_public, Mapping, 'bst', default_value={})
- element_splits = _yaml.node_get(element_bst, Mapping, 'split-rules', default_value={})
-
- # Resolve any variables in the public split rules directly
- for domain, splits in self.node_items(element_splits):
- splits = [
- self.__variables.subst(split.strip())
- for split in splits
- ]
- _yaml.node_set(element_splits, domain, splits)
-
- return element_public
-
- def __init_splits(self):
- bstdata = self.get_public_data('bst')
- splits = self.node_get_member(bstdata, dict, 'split-rules')
- self.__splits = {
- domain: re.compile('^(?:' + '|'.join([utils._glob2re(r) for r in rules]) + ')$')
- for domain, rules in self.node_items(splits)
- }
-
- # __split_filter():
- #
- # Returns True if the file with the specified `path` is included in the
- # specified split domains. This is used by `__split_filter_func()` to create
- # a filter callback.
- #
- # Args:
- # element_domains (list): All domains for this element
- # include (list): A list of domains to include files from
- # exclude (list): A list of domains to exclude files from
- # orphans (bool): Whether to include files not spoken for by split domains
- # path (str): The relative path of the file
- #
- # Returns:
- # (bool): Whether to include the specified file
- #
- def __split_filter(self, element_domains, include, exclude, orphans, path):
- # Absolute path is required for matching
- filename = os.path.join(os.sep, path)
-
- include_file = False
- exclude_file = False
- claimed_file = False
-
- for domain in element_domains:
- if self.__splits[domain].match(filename):
- claimed_file = True
- if domain in include:
- include_file = True
- if domain in exclude:
- exclude_file = True
-
- if orphans and not claimed_file:
- include_file = True
-
- return include_file and not exclude_file
-
- # __split_filter_func():
- #
- # Returns callable split filter function for use with `copy_files()`,
- # `link_files()` or `Directory.import_files()`.
- #
- # Args:
- # include (list): An optional list of domains to include files from
- # exclude (list): An optional list of domains to exclude files from
- # orphans (bool): Whether to include files not spoken for by split domains
- #
- # Returns:
- # (callable): Filter callback that returns True if the file is included
- # in the specified split domains.
- #
- def __split_filter_func(self, include=None, exclude=None, orphans=True):
- # No splitting requested, no filter needed
- if orphans and not (include or exclude):
- return None
-
- if not self.__splits:
- self.__init_splits()
-
- element_domains = list(self.__splits.keys())
- if not include:
- include = element_domains
- if not exclude:
- exclude = []
-
- # Ignore domains that dont apply to this element
- #
- include = [domain for domain in include if domain in element_domains]
- exclude = [domain for domain in exclude if domain in element_domains]
-
- # The arguments element_domains, include, exclude, and orphans are
- # the same for all files. Use `partial` to create a function with
- # the required callback signature: a single `path` parameter.
- return partial(self.__split_filter, element_domains, include, exclude, orphans)
-
- def __compute_splits(self, include=None, exclude=None, orphans=True):
- filter_func = self.__split_filter_func(include=include, exclude=exclude, orphans=orphans)
-
- files_vdir = self.__artifact.get_files()
-
- element_files = files_vdir.list_relative_paths()
-
- if not filter_func:
- # No splitting requested, just report complete artifact
- yield from element_files
- else:
- for filename in element_files:
- if filter_func(filename):
- yield filename
-
- def __file_is_whitelisted(self, path):
- # Considered storing the whitelist regex for re-use, but public data
- # can be altered mid-build.
- # Public data is not guaranteed to stay the same for the duration of
- # the build, but I can think of no reason to change it mid-build.
- # If this ever changes, things will go wrong unexpectedly.
- if not self.__whitelist_regex:
- bstdata = self.get_public_data('bst')
- whitelist = _yaml.node_get(bstdata, list, 'overlap-whitelist', default_value=[])
- whitelist_expressions = [utils._glob2re(self.__variables.subst(exp.strip())) for exp in whitelist]
- expression = ('^(?:' + '|'.join(whitelist_expressions) + ')$')
- self.__whitelist_regex = re.compile(expression)
- return self.__whitelist_regex.match(os.path.join(os.sep, path))
-
- # __load_public_data():
- #
- # Loads the public data from the cached artifact
- #
- def __load_public_data(self):
- self.__assert_cached()
- assert self.__dynamic_public is None
-
- self.__dynamic_public = self.__artifact.load_public_data()
-
- def __load_build_result(self):
- self.__assert_cached()
- assert self.__build_result is None
-
- self.__build_result = self.__artifact.load_build_result()
-
- # __pull_strong():
- #
- # Attempt pulling given element from configured artifact caches with
- # the strict cache key
- #
- # Args:
- # progress (callable): The progress callback, if any
- # subdir (str): The optional specific subdir to pull
- # excluded_subdirs (list): The optional list of subdirs to not pull
- #
- # Returns:
- # (bool): Whether or not the pull was successful
- #
- def __pull_strong(self, *, pull_buildtrees):
- weak_key = self._get_cache_key(strength=_KeyStrength.WEAK)
- key = self.__strict_cache_key
- if not self.__artifacts.pull(self, key, pull_buildtrees=pull_buildtrees):
- return False
-
- # update weak ref by pointing it to this newly fetched artifact
- self.__artifacts.link_key(self, key, weak_key)
-
- return True
-
- # __pull_weak():
- #
- # Attempt pulling given element from configured artifact caches with
- # the weak cache key
- #
- # Args:
- # subdir (str): The optional specific subdir to pull
- # excluded_subdirs (list): The optional list of subdirs to not pull
- #
- # Returns:
- # (bool): Whether or not the pull was successful
- #
- def __pull_weak(self, *, pull_buildtrees):
- weak_key = self._get_cache_key(strength=_KeyStrength.WEAK)
- if not self.__artifacts.pull(self, weak_key,
- pull_buildtrees=pull_buildtrees):
- return False
-
- # extract strong cache key from this newly fetched artifact
- self._pull_done()
-
- # create tag for strong cache key
- key = self._get_cache_key(strength=_KeyStrength.STRONG)
- self.__artifacts.link_key(self, weak_key, key)
-
- return True
-
- # __cache_sources():
- #
- # Caches the sources into the local CAS
- #
- def __cache_sources(self):
- if self.__sources and not self._source_cached():
- last_requires_previous = 0
- # commit all other sources by themselves
- for ix, source in enumerate(self.__sources):
- if source.BST_REQUIRES_PREVIOUS_SOURCES_STAGE:
- self.__sourcecache.commit(source, self.__sources[last_requires_previous:ix])
- last_requires_previous = ix
- else:
- self.__sourcecache.commit(source, [])
-
- # __last_source_requires_previous
- #
- # This is the last source that requires previous sources to be cached.
- # Sources listed after this will be cached separately.
- #
- # Returns:
- # (int): index of last source that requires previous sources
- #
- def __last_source_requires_previous(self):
- if self.__last_source_requires_previous_ix is None:
- last_requires_previous = 0
- for ix, source in enumerate(self.__sources):
- if source.BST_REQUIRES_PREVIOUS_SOURCES_STAGE:
- last_requires_previous = ix
- self.__last_source_requires_previous_ix = last_requires_previous
- return self.__last_source_requires_previous_ix
-
- # __update_state_recursively()
- #
- # Update the state of all reverse dependencies, recursively.
- #
- def __update_state_recursively(self):
- queue = _UniquePriorityQueue()
- queue.push(self._unique_id, self)
-
- while queue:
- element = queue.pop()
-
- old_ready_for_runtime = element.__ready_for_runtime
- old_strict_cache_key = element.__strict_cache_key
- element._update_state()
-
- if element.__ready_for_runtime != old_ready_for_runtime or \
- element.__strict_cache_key != old_strict_cache_key:
- for rdep in element.__reverse_dependencies:
- queue.push(rdep._unique_id, rdep)
-
- # __reset_cache_data()
- #
- # Resets all data related to cache key calculation and whether an artifact
- # is cached.
- #
- # This is useful because we need to know whether a workspace is cached
- # before we know whether to assemble it, and doing that would generate a
- # different cache key to the initial one.
- #
- def __reset_cache_data(self):
- self.__build_result = None
- self.__cache_key_dict = None
- self.__cache_key = None
- self.__weak_cache_key = None
- self.__strict_cache_key = None
- self.__artifact = None
- self.__strict_artifact = None
-
- # __update_cache_keys()
- #
- # Updates weak and strict cache keys
- #
- # Note that it does not update *all* cache keys - In non-strict mode, the
- # strong cache key is updated in __update_cache_key_non_strict()
- #
- # If the cache keys are not stable (i.e. workspace that isn't cached),
- # then cache keys are erased.
- # Otherwise, the weak and strict cache keys will be calculated if not
- # already set.
- # The weak cache key is a cache key that doesn't necessarily change when
- # its dependencies change, useful for avoiding full rebuilds when one's
- # dependencies guarantee stability across versions.
- # The strict cache key is a cache key that changes if any build-dependency
- # has changed.
- #
- def __update_cache_keys(self):
- if self.__weak_cache_key is None:
- # Calculate weak cache key
- # Weak cache key includes names of direct build dependencies
- # but does not include keys of dependencies.
- if self.BST_STRICT_REBUILD:
- dependencies = [
- e._get_cache_key(strength=_KeyStrength.WEAK)
- for e in self.dependencies(Scope.BUILD)
- ]
- else:
- dependencies = [
- e.name for e in self.dependencies(Scope.BUILD, recurse=False)
- ]
-
- self.__weak_cache_key = self._calculate_cache_key(dependencies)
-
- if self.__weak_cache_key is None:
- # Weak cache key could not be calculated yet, therefore
- # the Strict cache key also can't be calculated yet.
- return
-
- if self.__strict_cache_key is None:
- dependencies = [
- e.__strict_cache_key for e in self.dependencies(Scope.BUILD)
- ]
- self.__strict_cache_key = self._calculate_cache_key(dependencies)
-
- # __update_artifact_state()
- #
- # Updates the data involved in knowing about the artifact corresponding
- # to this element.
- #
- # This involves erasing all data pertaining to artifacts if the cache
- # key is unstable.
- #
- # Element.__update_cache_keys() must be called before this to have
- # meaningful results, because the element must know its cache key before
- # it can check whether an artifact exists for that cache key.
- #
- def __update_artifact_state(self):
- context = self._get_context()
-
- if not self.__weak_cache_key:
- return
-
- if not context.get_strict() and not self.__artifact:
- # We've calculated the weak_key, so instantiate artifact instance member
- self.__artifact = Artifact(self, context, weak_key=self.__weak_cache_key)
-
- if not self.__strict_cache_key:
- return
-
- if not self.__strict_artifact:
- self.__strict_artifact = Artifact(self, context, strong_key=self.__strict_cache_key,
- weak_key=self.__weak_cache_key)
-
- # In strict mode, the strong cache key always matches the strict cache key
- if context.get_strict():
- self.__cache_key = self.__strict_cache_key
- self.__artifact = self.__strict_artifact
-
- # Allow caches to be queried, since they may now be cached
- # The next invocation of Artifact.cached() will access the filesystem.
- # Note that this will safely do nothing if the artifacts are already cached.
- self.__strict_artifact.reset_cached()
- self.__artifact.reset_cached()
-
- # __update_cache_key_non_strict()
- #
- # Calculates the strong cache key if it hasn't already been set.
- #
- # When buildstream runs in strict mode, this is identical to the
- # strict cache key, so no work needs to be done.
- #
- # When buildstream is not run in strict mode, this requires the artifact
- # state (as set in Element.__update_artifact_state()) to be set accordingly,
- # as the cache key can be loaded from the cache (possibly pulling from
- # a remote cache).
- #
- def __update_cache_key_non_strict(self):
- if not self.__strict_artifact:
- return
-
- # The final cache key can be None here only in non-strict mode
- if self.__cache_key is None:
- if self._pull_pending():
- # Effective strong cache key is unknown until after the pull
- pass
- elif self._cached():
- # Load the strong cache key from the artifact
- strong_key, _ = self.__artifact.get_metadata_keys()
- self.__cache_key = strong_key
- elif self.__assemble_scheduled or self.__assemble_done:
- # Artifact will or has been built, not downloaded
- dependencies = [
- e._get_cache_key() for e in self.dependencies(Scope.BUILD)
- ]
- self.__cache_key = self._calculate_cache_key(dependencies)
-
- if self.__cache_key is None:
- # Strong cache key could not be calculated yet
- return
-
- # Now we have the strong cache key, update the Artifact
- self.__artifact._cache_key = self.__cache_key
-
-
-def _overlap_error_detail(f, forbidden_overlap_elements, elements):
- if forbidden_overlap_elements:
- return ("/{}: {} {} not permitted to overlap other elements, order {} \n"
- .format(f, " and ".join(forbidden_overlap_elements),
- "is" if len(forbidden_overlap_elements) == 1 else "are",
- " above ".join(reversed(elements))))
- else:
- return ""
-
-
-# _get_normal_name():
-#
-# Get the element name without path separators or
-# the extension.
-#
-# Args:
-# element_name (str): The element's name
-#
-# Returns:
-# (str): The normalised element name
-#
-def _get_normal_name(element_name):
- return os.path.splitext(element_name.replace(os.sep, '-'))[0]
-
-
-# _compose_artifact_name():
-#
-# Compose the completely resolved 'artifact_name' as a filepath
-#
-# Args:
-# project_name (str): The project's name
-# normal_name (str): The element's normalised name
-# cache_key (str): The relevant cache key
-#
-# Returns:
-# (str): The constructed artifact name path
-#
-def _compose_artifact_name(project_name, normal_name, cache_key):
- valid_chars = string.digits + string.ascii_letters + '-._'
- normal_name = ''.join([
- x if x in valid_chars else '_'
- for x in normal_name
- ])
-
- # Note that project names are not allowed to contain slashes. Element names containing
- # a '/' will have this replaced with a '-' upon Element object instantiation.
- return '{0}/{1}/{2}'.format(project_name, normal_name, cache_key)
diff --git a/buildstream/plugin.py b/buildstream/plugin.py
deleted file mode 100644
index d8b6a7359..000000000
--- a/buildstream/plugin.py
+++ /dev/null
@@ -1,929 +0,0 @@
-#
-# Copyright (C) 2018 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>
-"""
-Plugin - Base plugin class
-==========================
-BuildStream supports third party plugins to define additional kinds of
-:mod:`Elements <buildstream.element>` and :mod:`Sources <buildstream.source>`.
-
-The common API is documented here, along with some information on how
-external plugin packages are structured.
-
-
-.. _core_plugin_abstract_methods:
-
-Abstract Methods
-----------------
-For both :mod:`Elements <buildstream.element>` and :mod:`Sources <buildstream.source>`,
-it is mandatory to implement the following abstract methods:
-
-* :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>`
-
- Loads the user provided configuration YAML for the given source or element
-
-* :func:`Plugin.preflight() <buildstream.plugin.Plugin.preflight>`
-
- Early preflight checks allow plugins to bail out early with an error
- in the case that it can predict that failure is inevitable.
-
-* :func:`Plugin.get_unique_key() <buildstream.plugin.Plugin.get_unique_key>`
-
- Once all configuration has been loaded and preflight checks have passed,
- this method is used to inform the core of a plugin's unique configuration.
-
-Configurable Warnings
----------------------
-Warnings raised through calling :func:`Plugin.warn() <buildstream.plugin.Plugin.warn>` can provide an optional
-parameter ``warning_token``, this will raise a :class:`PluginError` if the warning is configured as fatal within
-the project configuration.
-
-Configurable warnings will be prefixed with :func:`Plugin.get_kind() <buildstream.plugin.Plugin.get_kind>`
-within buildstream and must be prefixed as such in project configurations. For more detail on project configuration
-see :ref:`Configurable Warnings <configurable_warnings>`.
-
-It is important to document these warnings in your plugin documentation to allow users to make full use of them
-while configuring their projects.
-
-Example
-~~~~~~~
-If the :class:`git <buildstream.plugins.sources.git.GitSource>` plugin uses the warning ``"inconsistent-submodule"``
-then it could be referenced in project configuration as ``"git:inconsistent-submodule"``.
-
-Plugin Structure
-----------------
-A plugin should consist of a `setuptools package
-<http://setuptools.readthedocs.io/en/latest/setuptools.html>`_ that
-advertises contained plugins using `entry points
-<http://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_.
-
-A plugin entry point must be a module that extends a class in the
-:ref:`core_framework` to be discovered by BuildStream. A YAML file
-defining plugin default settings with the same name as the module can
-also be defined in the same directory as the plugin module.
-
-.. note::
-
- BuildStream does not support function/class entry points.
-
-A sample plugin could be structured as such:
-
-.. code-block:: text
-
- .
- ├── elements
- │   ├── autotools.py
- │   ├── autotools.yaml
- │   └── __init__.py
- ├── MANIFEST.in
- └── setup.py
-
-The setuptools configuration should then contain at least:
-
-setup.py:
-
-.. literalinclude:: ../source/sample_plugin/setup.py
- :language: python
-
-MANIFEST.in:
-
-.. literalinclude:: ../source/sample_plugin/MANIFEST.in
- :language: text
-
-Class Reference
----------------
-"""
-
-import itertools
-import os
-import subprocess
-import sys
-from contextlib import contextmanager
-from weakref import WeakValueDictionary
-
-from . import _yaml
-from . import utils
-from ._exceptions import PluginError, ImplError
-from ._message import Message, MessageType
-from .types import CoreWarnings
-
-
-class Plugin():
- """Plugin()
-
- Base Plugin class.
-
- Some common features to both Sources and Elements are found
- in this class.
-
- .. note::
-
- Derivation of plugins is not supported. Plugins may only
- derive from the base :mod:`Source <buildstream.source>` and
- :mod:`Element <buildstream.element>` types, and any convenience
- subclasses (like :mod:`BuildElement <buildstream.buildelement>`)
- which are included in the buildstream namespace.
- """
-
- BST_REQUIRED_VERSION_MAJOR = 0
- """Minimum required major version"""
-
- BST_REQUIRED_VERSION_MINOR = 0
- """Minimum required minor version"""
-
- BST_FORMAT_VERSION = 0
- """The plugin's YAML format version
-
- This should be set to ``1`` the first time any new configuration
- is understood by your :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>`
- implementation and subsequently bumped every time your
- configuration is enhanced.
-
- .. note::
-
- Plugins are expected to maintain backward compatibility
- in the format and configurations they expose. The versioning
- is intended to track availability of new features only.
-
- For convenience, the format version for plugins maintained and
- distributed with BuildStream are revisioned with BuildStream's
- core format version :ref:`core format version <project_format_version>`.
- """
-
- BST_PLUGIN_DEPRECATED = False
- """True if this element plugin has been deprecated.
-
- If this is set to true, BuildStream will emmit a deprecation
- warning when this plugin is loaded. This deprecation warning may
- be suppressed on a plugin by plugin basis by setting
- ``suppress-deprecation-warnings: true`` in the relevent section of
- the project's :ref:`plugin configuration overrides <project_overrides>`.
-
- """
-
- BST_PLUGIN_DEPRECATION_MESSAGE = ""
- """ The message printed when this element shows a deprecation warning.
-
- This should be set if BST_PLUGIN_DEPRECATED is True and should direct the user
- to the deprecated plug-in's replacement.
-
- """
-
- # Unique id generator for Plugins
- #
- # Each plugin gets a unique id at creation.
- #
- # Ids are a monotically increasing integer which
- # starts as 1 (a falsy plugin ID is considered unset
- # in various parts of the codebase).
- #
- __id_generator = itertools.count(1)
-
- # Hold on to a lookup table by counter of all instantiated plugins.
- # We use this to send the id back from child processes so we can lookup
- # corresponding element/source in the master process.
- #
- # Use WeakValueDictionary() so the map we use to lookup objects does not
- # keep the plugins alive after pipeline destruction.
- #
- # Note that Plugins can only be instantiated in the main process before
- # scheduling tasks.
- __TABLE = WeakValueDictionary()
-
- def __init__(self, name, context, project, provenance, type_tag, unique_id=None):
-
- self.name = name
- """The plugin name
-
- For elements, this is the project relative bst filename,
- for sources this is the owning element's name with a suffix
- indicating its index on the owning element.
-
- For sources this is for display purposes only.
- """
-
- # Unique ID
- #
- # This id allows to uniquely identify a plugin.
- #
- # /!\ the unique id must be an increasing value /!\
- # This is because we are depending on it in buildstream.element.Element
- # to give us a topological sort over all elements.
- # Modifying how we handle ids here will modify the behavior of the
- # Element's state handling.
- if unique_id is None:
- # Register ourself in the table containing all existing plugins
- self._unique_id = next(self.__id_generator)
- self.__TABLE[self._unique_id] = self
- else:
- # If the unique ID is passed in the constructor, then it is a cloned
- # plugin in a subprocess and should use the same ID.
- self._unique_id = unique_id
-
- self.__context = context # The Context object
- self.__project = project # The Project object
- self.__provenance = provenance # The Provenance information
- self.__type_tag = type_tag # The type of plugin (element or source)
- self.__configuring = False # Whether we are currently configuring
-
- # Infer the kind identifier
- modulename = type(self).__module__
- self.__kind = modulename.split('.')[-1]
- self.debug("Created: {}".format(self))
-
- # If this plugin has been deprecated, emit a warning.
- if self.BST_PLUGIN_DEPRECATED and not self.__deprecation_warning_silenced():
- detail = "Using deprecated plugin {}: {}".format(self.__kind,
- self.BST_PLUGIN_DEPRECATION_MESSAGE)
- self.__message(MessageType.WARN, detail)
-
- def __del__(self):
- # Dont send anything through the Message() pipeline at destruction time,
- # any subsequent lookup of plugin by unique id would raise KeyError.
- if self.__context.log_debug:
- sys.stderr.write("DEBUG: Destroyed: {}\n".format(self))
-
- def __str__(self):
- return "{kind} {typetag} at {provenance}".format(
- kind=self.__kind,
- typetag=self.__type_tag,
- provenance=self.__provenance)
-
- #############################################################
- # Abstract Methods #
- #############################################################
- def configure(self, node):
- """Configure the Plugin from loaded configuration data
-
- Args:
- node (dict): The loaded configuration dictionary
-
- Raises:
- :class:`.SourceError`: If it's a :class:`.Source` implementation
- :class:`.ElementError`: If it's an :class:`.Element` implementation
-
- Plugin implementors should implement this method to read configuration
- data and store it.
-
- Plugins should use the :func:`Plugin.node_get_member() <buildstream.plugin.Plugin.node_get_member>`
- and :func:`Plugin.node_get_list_element() <buildstream.plugin.Plugin.node_get_list_element>`
- methods to fetch values from the passed `node`. This will ensure that a nice human readable error
- message will be raised if the expected configuration is not found, indicating the filename,
- line and column numbers.
-
- Further the :func:`Plugin.node_validate() <buildstream.plugin.Plugin.node_validate>` method
- should be used to ensure that the user has not specified keys in `node` which are unsupported
- by the plugin.
-
- .. note::
-
- For Elements, when variable substitution is desirable, the
- :func:`Element.node_subst_member() <buildstream.element.Element.node_subst_member>`
- and :func:`Element.node_subst_list_element() <buildstream.element.Element.node_subst_list_element>`
- methods can be used.
- """
- raise ImplError("{tag} plugin '{kind}' does not implement configure()".format(
- tag=self.__type_tag, kind=self.get_kind()))
-
- def preflight(self):
- """Preflight Check
-
- Raises:
- :class:`.SourceError`: If it's a :class:`.Source` implementation
- :class:`.ElementError`: If it's an :class:`.Element` implementation
-
- This method is run after :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>`
- and after the pipeline is fully constructed.
-
- Implementors should simply raise :class:`.SourceError` or :class:`.ElementError`
- with an informative message in the case that the host environment is
- unsuitable for operation.
-
- Plugins which require host tools (only sources usually) should obtain
- them with :func:`utils.get_host_tool() <buildstream.utils.get_host_tool>` which
- will raise an error automatically informing the user that a host tool is needed.
- """
- raise ImplError("{tag} plugin '{kind}' does not implement preflight()".format(
- tag=self.__type_tag, kind=self.get_kind()))
-
- def get_unique_key(self):
- """Return something which uniquely identifies the plugin input
-
- Returns:
- A string, list or dictionary which uniquely identifies the input
-
- This is used to construct unique cache keys for elements and sources,
- sources should return something which uniquely identifies the payload,
- such as an sha256 sum of a tarball content.
-
- Elements and Sources should implement this by collecting any configurations
- which could possibly affect the output and return a dictionary of these settings.
-
- For Sources, this is guaranteed to only be called if
- :func:`Source.get_consistency() <buildstream.source.Source.get_consistency>`
- has not returned :func:`Consistency.INCONSISTENT <buildstream.source.Consistency.INCONSISTENT>`
- which is to say that the Source is expected to have an exact *ref* indicating
- exactly what source is going to be staged.
- """
- raise ImplError("{tag} plugin '{kind}' does not implement get_unique_key()".format(
- tag=self.__type_tag, kind=self.get_kind()))
-
- #############################################################
- # Public Methods #
- #############################################################
- def get_kind(self):
- """Fetches the kind of this plugin
-
- Returns:
- (str): The kind of this plugin
- """
- return self.__kind
-
- def node_items(self, node):
- """Iterate over a dictionary loaded from YAML
-
- Args:
- node (dict): The YAML loaded dictionary object
-
- Returns:
- list: List of key/value tuples to iterate over
-
- BuildStream holds some private data in dictionaries loaded from
- the YAML in order to preserve information to report in errors.
-
- This convenience function should be used instead of the dict.items()
- builtin function provided by python.
- """
- yield from _yaml.node_items(node)
-
- def node_provenance(self, node, member_name=None):
- """Gets the provenance for `node` and `member_name`
-
- This reports a string with file, line and column information suitable
- for reporting an error or warning.
-
- Args:
- node (dict): The YAML loaded dictionary object
- member_name (str): The name of the member to check, or None for the node itself
-
- Returns:
- (str): A string describing the provenance of the node and member
- """
- provenance = _yaml.node_get_provenance(node, key=member_name)
- return str(provenance)
-
- def node_get_member(self, node, expected_type, member_name, default=_yaml._sentinel, *, allow_none=False):
- """Fetch the value of a node member, raising an error if the value is
- missing or incorrectly typed.
-
- Args:
- node (dict): A dictionary loaded from YAML
- expected_type (type): The expected type of the node member
- member_name (str): The name of the member to fetch
- default (expected_type): A value to return when *member_name* is not specified in *node*
- allow_none (bool): Allow explicitly set None values in the YAML (*Since: 1.4*)
-
- Returns:
- The value of *member_name* in *node*, otherwise *default*
-
- Raises:
- :class:`.LoadError`: When *member_name* is not found and no *default* was provided
-
- Note:
- Returned strings are stripped of leading and trailing whitespace
-
- **Example:**
-
- .. code:: python
-
- # Expect a string 'name' in 'node'
- name = self.node_get_member(node, str, 'name')
-
- # Fetch an optional integer
- level = self.node_get_member(node, int, 'level', -1)
- """
- return _yaml.node_get(node, expected_type, member_name, default_value=default, allow_none=allow_none)
-
- def node_set_member(self, node, key, value):
- """Set the value of a node member
- Args:
- node (node): A dictionary loaded from YAML
- key (str): The key name
- value: The value
-
- Returns:
- None
-
- Raises:
- None
-
- **Example:**
-
- .. code:: python
-
- # Set a string 'tomjon' in node[name]
- self.node_set_member(node, 'name', 'tomjon')
- """
- _yaml.node_set(node, key, value)
-
- def new_empty_node(self):
- """Create an empty 'Node' object to be handled by BuildStream's core
- Args:
- None
-
- Returns:
- Node: An empty Node object
-
- Raises:
- None
-
- **Example:**
-
- .. code:: python
-
- # Create an empty Node object to store metadata information
- metadata = self.new_empty_node()
- """
- return _yaml.new_empty_node()
-
- def node_get_project_path(self, node, key, *,
- check_is_file=False, check_is_dir=False):
- """Fetches a project path from a dictionary node and validates it
-
- Paths are asserted to never lead to a directory outside of the
- project directory. In addition, paths can not point to symbolic
- links, fifos, sockets and block/character devices.
-
- The `check_is_file` and `check_is_dir` parameters can be used to
- perform additional validations on the path. Note that an
- exception will always be raised if both parameters are set to
- ``True``.
-
- Args:
- node (dict): A dictionary loaded from YAML
- key (str): The key whose value contains a path to validate
- check_is_file (bool): If ``True`` an error will also be raised
- if path does not point to a regular file.
- Defaults to ``False``
- check_is_dir (bool): If ``True`` an error will also be raised
- if path does not point to a directory.
- Defaults to ``False``
-
- Returns:
- (str): The project path
-
- Raises:
- :class:`.LoadError`: In the case that the project path is not
- valid or does not exist
-
- *Since: 1.2*
-
- **Example:**
-
- .. code:: python
-
- path = self.node_get_project_path(node, 'path')
-
- """
-
- return self.__project.get_path_from_node(node, key,
- check_is_file=check_is_file,
- check_is_dir=check_is_dir)
-
- def node_validate(self, node, valid_keys):
- """This should be used in :func:`~buildstream.plugin.Plugin.configure`
- implementations to assert that users have only entered
- valid configuration keys.
-
- Args:
- node (dict): A dictionary loaded from YAML
- valid_keys (iterable): A list of valid keys for the node
-
- Raises:
- :class:`.LoadError`: When an invalid key is found
-
- **Example:**
-
- .. code:: python
-
- # Ensure our node only contains valid autotools config keys
- self.node_validate(node, [
- 'configure-commands', 'build-commands',
- 'install-commands', 'strip-commands'
- ])
-
- """
- _yaml.node_validate(node, valid_keys)
-
- def node_get_list_element(self, node, expected_type, member_name, indices):
- """Fetch the value of a list element from a node member, raising an error if the
- value is incorrectly typed.
-
- Args:
- node (dict): A dictionary loaded from YAML
- expected_type (type): The expected type of the node member
- member_name (str): The name of the member to fetch
- indices (list of int): List of indices to search, in case of nested lists
-
- Returns:
- The value of the list element in *member_name* at the specified *indices*
-
- Raises:
- :class:`.LoadError`
-
- Note:
- Returned strings are stripped of leading and trailing whitespace
-
- **Example:**
-
- .. code:: python
-
- # Fetch the list itself
- things = self.node_get_member(node, list, 'things')
-
- # Iterate over the list indices
- for i in range(len(things)):
-
- # Fetch dict things
- thing = self.node_get_list_element(
- node, dict, 'things', [ i ])
- """
- return _yaml.node_get(node, expected_type, member_name, indices=indices)
-
- def debug(self, brief, *, detail=None):
- """Print a debugging message
-
- Args:
- brief (str): The brief message
- detail (str): An optional detailed message, can be multiline output
- """
- if self.__context.log_debug:
- self.__message(MessageType.DEBUG, brief, detail=detail)
-
- def status(self, brief, *, detail=None):
- """Print a status message
-
- Args:
- brief (str): The brief message
- detail (str): An optional detailed message, can be multiline output
-
- Note: Status messages tell about what a plugin is currently doing
- """
- self.__message(MessageType.STATUS, brief, detail=detail)
-
- def info(self, brief, *, detail=None):
- """Print an informative message
-
- Args:
- brief (str): The brief message
- detail (str): An optional detailed message, can be multiline output
-
- Note: Informative messages tell the user something they might want
- to know, like if refreshing an element caused it to change.
- """
- self.__message(MessageType.INFO, brief, detail=detail)
-
- def warn(self, brief, *, detail=None, warning_token=None):
- """Print a warning message, checks warning_token against project configuration
-
- Args:
- brief (str): The brief message
- detail (str): An optional detailed message, can be multiline output
- warning_token (str): An optional configurable warning assosciated with this warning,
- this will cause PluginError to be raised if this warning is configured as fatal.
- (*Since 1.4*)
-
- Raises:
- (:class:`.PluginError`): When warning_token is considered fatal by the project configuration
- """
- if warning_token:
- warning_token = _prefix_warning(self, warning_token)
- brief = "[{}]: {}".format(warning_token, brief)
- project = self._get_project()
-
- if project._warning_is_fatal(warning_token):
- detail = detail if detail else ""
- raise PluginError(message="{}\n{}".format(brief, detail), reason=warning_token)
-
- self.__message(MessageType.WARN, brief=brief, detail=detail)
-
- def log(self, brief, *, detail=None):
- """Log a message into the plugin's log file
-
- The message will not be shown in the master log at all (so it will not
- be displayed to the user on the console).
-
- Args:
- brief (str): The brief message
- detail (str): An optional detailed message, can be multiline output
- """
- self.__message(MessageType.LOG, brief, detail=detail)
-
- @contextmanager
- def timed_activity(self, activity_name, *, detail=None, silent_nested=False):
- """Context manager for performing timed activities in plugins
-
- Args:
- activity_name (str): The name of the activity
- detail (str): An optional detailed message, can be multiline output
- silent_nested (bool): If specified, nested messages will be silenced
-
- This function lets you perform timed tasks in your plugin,
- the core will take care of timing the duration of your
- task and printing start / fail / success messages.
-
- **Example**
-
- .. code:: python
-
- # Activity will be logged and timed
- with self.timed_activity("Mirroring {}".format(self.url)):
-
- # This will raise SourceError on its own
- self.call(... command which takes time ...)
- """
- with self.__context.timed_activity(activity_name,
- unique_id=self._unique_id,
- detail=detail,
- silent_nested=silent_nested):
- yield
-
- def call(self, *popenargs, fail=None, fail_temporarily=False, **kwargs):
- """A wrapper for subprocess.call()
-
- Args:
- popenargs (list): Popen() arguments
- fail (str): A message to display if the process returns
- a non zero exit code
- fail_temporarily (bool): Whether any exceptions should
- be raised as temporary. (*Since: 1.2*)
- rest_of_args (kwargs): Remaining arguments to subprocess.call()
-
- Returns:
- (int): The process exit code.
-
- Raises:
- (:class:`.PluginError`): If a non-zero return code is received and *fail* is specified
-
- Note: If *fail* is not specified, then the return value of subprocess.call()
- is returned even on error, and no exception is automatically raised.
-
- **Example**
-
- .. code:: python
-
- # Call some host tool
- self.tool = utils.get_host_tool('toolname')
- self.call(
- [self.tool, '--download-ponies', self.mirror_directory],
- "Failed to download ponies from {}".format(
- self.mirror_directory))
- """
- exit_code, _ = self.__call(*popenargs, fail=fail, fail_temporarily=fail_temporarily, **kwargs)
- return exit_code
-
- def check_output(self, *popenargs, fail=None, fail_temporarily=False, **kwargs):
- """A wrapper for subprocess.check_output()
-
- Args:
- popenargs (list): Popen() arguments
- fail (str): A message to display if the process returns
- a non zero exit code
- fail_temporarily (bool): Whether any exceptions should
- be raised as temporary. (*Since: 1.2*)
- rest_of_args (kwargs): Remaining arguments to subprocess.call()
-
- Returns:
- (int): The process exit code
- (str): The process standard output
-
- Raises:
- (:class:`.PluginError`): If a non-zero return code is received and *fail* is specified
-
- Note: If *fail* is not specified, then the return value of subprocess.check_output()
- is returned even on error, and no exception is automatically raised.
-
- **Example**
-
- .. code:: python
-
- # Get the tool at preflight time
- self.tool = utils.get_host_tool('toolname')
-
- # Call the tool, automatically raise an error
- _, output = self.check_output(
- [self.tool, '--print-ponies'],
- "Failed to print the ponies in {}".format(
- self.mirror_directory),
- cwd=self.mirror_directory)
-
- # Call the tool, inspect exit code
- exit_code, output = self.check_output(
- [self.tool, 'get-ref', tracking],
- cwd=self.mirror_directory)
-
- if exit_code == 128:
- return
- elif exit_code != 0:
- fmt = "{plugin}: Failed to get ref for tracking: {track}"
- raise SourceError(
- fmt.format(plugin=self, track=tracking)) from e
- """
- return self.__call(*popenargs, collect_stdout=True, fail=fail, fail_temporarily=fail_temporarily, **kwargs)
-
- #############################################################
- # Private Methods used in BuildStream #
- #############################################################
-
- # _lookup():
- #
- # Fetch a plugin in the current process by its
- # unique identifier
- #
- # Args:
- # unique_id: The unique identifier as returned by
- # plugin._unique_id
- #
- # Returns:
- # (Plugin): The plugin for the given ID, or None
- #
- @classmethod
- def _lookup(cls, unique_id):
- assert unique_id != 0, "Looking up invalid plugin ID 0, ID counter starts at 1"
- try:
- return cls.__TABLE[unique_id]
- except KeyError:
- assert False, "Could not find plugin with ID {}".format(unique_id)
- raise # In case a user is running with "python -O"
-
- # _get_context()
- #
- # Fetches the invocation context
- #
- def _get_context(self):
- return self.__context
-
- # _get_project()
- #
- # Fetches the project object associated with this plugin
- #
- def _get_project(self):
- return self.__project
-
- # _get_provenance():
- #
- # Fetch bst file, line and column of the entity
- #
- def _get_provenance(self):
- return self.__provenance
-
- # Context manager for getting the open file handle to this
- # plugin's log. Used in the child context to add stuff to
- # a log.
- #
- @contextmanager
- def _output_file(self):
- log = self.__context.get_log_handle()
- if log is None:
- with open(os.devnull, "w") as output:
- yield output
- else:
- yield log
-
- # _configure():
- #
- # Calls configure() for the plugin, this must be called by
- # the core instead of configure() directly, so that the
- # _get_configuring() state is up to date.
- #
- # Args:
- # node (dict): The loaded configuration dictionary
- #
- def _configure(self, node):
- self.__configuring = True
- self.configure(node)
- self.__configuring = False
-
- # _get_configuring():
- #
- # Checks whether the plugin is in the middle of having
- # its Plugin.configure() method called
- #
- # Returns:
- # (bool): Whether we are currently configuring
- def _get_configuring(self):
- return self.__configuring
-
- # _preflight():
- #
- # Calls preflight() for the plugin, and allows generic preflight
- # checks to be added
- #
- # Raises:
- # SourceError: If it's a Source implementation
- # ElementError: If it's an Element implementation
- # ProgramNotFoundError: If a required host tool is not found
- #
- def _preflight(self):
- self.preflight()
-
- #############################################################
- # Local Private Methods #
- #############################################################
-
- # Internal subprocess implementation for the call() and check_output() APIs
- #
- def __call(self, *popenargs, collect_stdout=False, fail=None, fail_temporarily=False, **kwargs):
-
- with self._output_file() as output_file:
- if 'stdout' not in kwargs:
- kwargs['stdout'] = output_file
- if 'stderr' not in kwargs:
- kwargs['stderr'] = output_file
- if collect_stdout:
- kwargs['stdout'] = subprocess.PIPE
-
- self.__note_command(output_file, *popenargs, **kwargs)
-
- exit_code, output = utils._call(*popenargs, **kwargs)
-
- if fail and exit_code:
- raise PluginError("{plugin}: {message}".format(plugin=self, message=fail),
- temporary=fail_temporarily)
-
- return (exit_code, output)
-
- def __message(self, message_type, brief, **kwargs):
- message = Message(self._unique_id, message_type, brief, **kwargs)
- self.__context.message(message)
-
- def __note_command(self, output, *popenargs, **kwargs):
- workdir = kwargs.get('cwd', os.getcwd())
- command = " ".join(popenargs[0])
- output.write('Running host command {}: {}\n'.format(workdir, command))
- output.flush()
- self.status('Running host command', detail=command)
-
- def _get_full_name(self):
- project = self.__project
- if project.junction:
- return '{}:{}'.format(project.junction.name, self.name)
- else:
- return self.name
-
- def __deprecation_warning_silenced(self):
- if not self.BST_PLUGIN_DEPRECATED:
- return False
- else:
- silenced_warnings = set()
- project = self.__project
-
- for key, value in self.node_items(project.element_overrides):
- if _yaml.node_get(value, bool, 'suppress-deprecation-warnings', default_value=False):
- silenced_warnings.add(key)
- for key, value in self.node_items(project.source_overrides):
- if _yaml.node_get(value, bool, 'suppress-deprecation-warnings', default_value=False):
- silenced_warnings.add(key)
-
- return self.get_kind() in silenced_warnings
-
-
-# A local table for _prefix_warning()
-#
-__CORE_WARNINGS = [
- value
- for name, value in CoreWarnings.__dict__.items()
- if not name.startswith("__")
-]
-
-
-# _prefix_warning():
-#
-# Prefix a warning with the plugin kind. CoreWarnings are not prefixed.
-#
-# Args:
-# plugin (Plugin): The plugin which raised the warning
-# warning (str): The warning to prefix
-#
-# Returns:
-# (str): A prefixed warning
-#
-def _prefix_warning(plugin, warning):
- if any((warning is core_warning for core_warning in __CORE_WARNINGS)):
- return warning
- return "{}:{}".format(plugin.get_kind(), warning)
diff --git a/buildstream/plugins/elements/__init__.py b/buildstream/plugins/elements/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/plugins/elements/__init__.py
+++ /dev/null
diff --git a/buildstream/plugins/elements/autotools.py b/buildstream/plugins/elements/autotools.py
deleted file mode 100644
index 2243a73f9..000000000
--- a/buildstream/plugins/elements/autotools.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-# Copyright (C) 2016, 2018 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>
-
-"""
-autotools - Autotools build element
-===================================
-This is a :mod:`BuildElement <buildstream.buildelement>` implementation for
-using Autotools build scripts (also known as the `GNU Build System
-<https://en.wikipedia.org/wiki/GNU_Build_System>`_).
-
-You will often want to pass additional arguments to ``configure``. This should
-be done on a per-element basis by setting the ``conf-local`` variable. Here is
-an example:
-
-.. code:: yaml
-
- variables:
- conf-local: |
- --disable-foo --enable-bar
-
-If you want to pass extra options to ``configure`` for every element in your
-project, set the ``conf-global`` variable in your project.conf file. Here is
-an example of that:
-
-.. code:: yaml
-
- elements:
- autotools:
- variables:
- conf-global: |
- --disable-gtk-doc --disable-static
-
-Here is the default configuration for the ``autotools`` element in full:
-
- .. literalinclude:: ../../../buildstream/plugins/elements/autotools.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'autotools' kind.
-class AutotoolsElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return AutotoolsElement
diff --git a/buildstream/plugins/elements/autotools.yaml b/buildstream/plugins/elements/autotools.yaml
deleted file mode 100644
index 85f7393e7..000000000
--- a/buildstream/plugins/elements/autotools.yaml
+++ /dev/null
@@ -1,129 +0,0 @@
-# Autotools default configurations
-
-variables:
-
- autogen: |
- export NOCONFIGURE=1;
-
- if [ -x %{conf-cmd} ]; then true;
- elif [ -x %{conf-root}/autogen ]; then %{conf-root}/autogen;
- elif [ -x %{conf-root}/autogen.sh ]; then %{conf-root}/autogen.sh;
- elif [ -x %{conf-root}/bootstrap ]; then %{conf-root}/bootstrap;
- elif [ -x %{conf-root}/bootstrap.sh ]; then %{conf-root}/bootstrap.sh;
- else autoreconf -ivf %{conf-root};
- fi
-
- # Project-wide extra arguments to be passed to `configure`
- conf-global: ''
-
- # Element-specific extra arguments to be passed to `configure`.
- conf-local: ''
-
- # For backwards compatibility only, do not use.
- conf-extra: ''
-
- conf-cmd: "%{conf-root}/configure"
-
- conf-args: |
-
- --prefix=%{prefix} \
- --exec-prefix=%{exec_prefix} \
- --bindir=%{bindir} \
- --sbindir=%{sbindir} \
- --sysconfdir=%{sysconfdir} \
- --datadir=%{datadir} \
- --includedir=%{includedir} \
- --libdir=%{libdir} \
- --libexecdir=%{libexecdir} \
- --localstatedir=%{localstatedir} \
- --sharedstatedir=%{sharedstatedir} \
- --mandir=%{mandir} \
- --infodir=%{infodir} %{conf-extra} %{conf-global} %{conf-local}
-
- configure: |
-
- %{conf-cmd} %{conf-args}
-
- make: make
- make-install: make -j1 DESTDIR="%{install-root}" install
-
- # Set this if the sources cannot handle parallelization.
- #
- # notparallel: True
-
-
- # Automatically remove libtool archive files
- #
- # Set remove-libtool-modules to "true" to remove .la files for
- # modules intended to be opened with lt_dlopen()
- #
- # Set remove-libtool-libraries to "true" to remove .la files for
- # libraries
- #
- # Value must be "true" or "false"
- remove-libtool-modules: "false"
- remove-libtool-libraries: "false"
-
- delete-libtool-archives: |
- if %{remove-libtool-modules} || %{remove-libtool-libraries}; then
- find "%{install-root}" -name "*.la" -print0 | while read -d '' -r file; do
- if grep '^shouldnotlink=yes$' "${file}" &>/dev/null; then
- if %{remove-libtool-modules}; then
- echo "Removing ${file}."
- rm "${file}"
- else
- echo "Not removing ${file}."
- fi
- else
- if %{remove-libtool-libraries}; then
- echo "Removing ${file}."
- rm "${file}"
- else
- echo "Not removing ${file}."
- fi
- fi
- done
- fi
-
-config:
-
- # Commands for configuring the software
- #
- configure-commands:
- - |
- %{autogen}
- - |
- %{configure}
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{make}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{make-install}
- - |
- %{delete-libtool-archives}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
-
-# Use max-jobs CPUs for building and enable verbosity
-environment:
- MAKEFLAGS: -j%{max-jobs}
- V: 1
-
-# And dont consider MAKEFLAGS or V as something which may
-# affect build output.
-environment-nocache:
-- MAKEFLAGS
-- V
diff --git a/buildstream/plugins/elements/cmake.py b/buildstream/plugins/elements/cmake.py
deleted file mode 100644
index a1bea0cd6..000000000
--- a/buildstream/plugins/elements/cmake.py
+++ /dev/null
@@ -1,74 +0,0 @@
-#
-# Copyright (C) 2016, 2018 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>
-
-"""
-cmake - CMake build element
-===========================
-This is a :mod:`BuildElement <buildstream.buildelement>` implementation for
-using the `CMake <https://cmake.org/>`_ build system.
-
-You will often want to pass additional arguments to the ``cmake`` program for
-specific configuration options. This should be done on a per-element basis by
-setting the ``cmake-local`` variable. Here is an example:
-
-.. code:: yaml
-
- variables:
- cmake-local: |
- -DCMAKE_BUILD_TYPE=Debug
-
-If you want to pass extra options to ``cmake`` for every element in your
-project, set the ``cmake-global`` variable in your project.conf file. Here is
-an example of that:
-
-.. code:: yaml
-
- elements:
- cmake:
- variables:
- cmake-global: |
- -DCMAKE_BUILD_TYPE=Release
-
-Here is the default configuration for the ``cmake`` element in full:
-
- .. literalinclude:: ../../../buildstream/plugins/elements/cmake.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'cmake' kind.
-class CMakeElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return CMakeElement
diff --git a/buildstream/plugins/elements/cmake.yaml b/buildstream/plugins/elements/cmake.yaml
deleted file mode 100644
index ba20d7ce6..000000000
--- a/buildstream/plugins/elements/cmake.yaml
+++ /dev/null
@@ -1,72 +0,0 @@
-# CMake default configuration
-
-variables:
-
- build-dir: _builddir
-
- # Project-wide extra arguments to be passed to `cmake`
- cmake-global: ''
-
- # Element-specific extra arguments to be passed to `cmake`.
- cmake-local: ''
-
- # For backwards compatibility only, do not use.
- cmake-extra: ''
-
- # The cmake generator to use
- generator: Unix Makefiles
-
- cmake-args: |
-
- -DCMAKE_INSTALL_PREFIX:PATH="%{prefix}" \
- -DCMAKE_INSTALL_LIBDIR:PATH="%{lib}" %{cmake-extra} %{cmake-global} %{cmake-local}
-
- cmake: |
-
- cmake -B%{build-dir} -H"%{conf-root}" -G"%{generator}" %{cmake-args}
-
- make: cmake --build %{build-dir} -- ${JOBS}
- make-install: env DESTDIR="%{install-root}" cmake --build %{build-dir} --target install
-
- # Set this if the sources cannot handle parallelization.
- #
- # notparallel: True
-
-config:
-
- # Commands for configuring the software
- #
- configure-commands:
- - |
- %{cmake}
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{make}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{make-install}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
-
-# Use max-jobs CPUs for building and enable verbosity
-environment:
- JOBS: -j%{max-jobs}
- V: 1
-
-# And dont consider JOBS or V as something which may
-# affect build output.
-environment-nocache:
-- JOBS
-- V
diff --git a/buildstream/plugins/elements/compose.py b/buildstream/plugins/elements/compose.py
deleted file mode 100644
index f45ffd76a..000000000
--- a/buildstream/plugins/elements/compose.py
+++ /dev/null
@@ -1,194 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-"""
-compose - Compose the output of multiple elements
-=================================================
-This element creates a selective composition of its dependencies.
-
-This is normally used at near the end of a pipeline to prepare
-something for later deployment.
-
-Since this element's output includes its dependencies, it may only
-depend on elements as `build` type dependencies.
-
-The default configuration and possible options are as such:
- .. literalinclude:: ../../../buildstream/plugins/elements/compose.yaml
- :language: yaml
-"""
-
-import os
-from buildstream import Element, Scope
-
-
-# Element implementation for the 'compose' kind.
-class ComposeElement(Element):
- # pylint: disable=attribute-defined-outside-init
-
- # The compose element's output is its dependencies, so
- # we must rebuild if the dependencies change even when
- # not in strict build plans.
- #
- BST_STRICT_REBUILD = True
-
- # Compose artifacts must never have indirect dependencies,
- # so runtime dependencies are forbidden.
- BST_FORBID_RDEPENDS = True
-
- # This element ignores sources, so we should forbid them from being
- # added, to reduce the potential for confusion
- BST_FORBID_SOURCES = True
-
- # This plugin has been modified to avoid the use of Sandbox.get_directory
- BST_VIRTUAL_DIRECTORY = True
-
- def configure(self, node):
- self.node_validate(node, [
- 'integrate', 'include', 'exclude', 'include-orphans'
- ])
-
- # We name this variable 'integration' only to avoid
- # collision with the Element.integrate() method.
- self.integration = self.node_get_member(node, bool, 'integrate')
- self.include = self.node_get_member(node, list, 'include')
- self.exclude = self.node_get_member(node, list, 'exclude')
- self.include_orphans = self.node_get_member(node, bool, 'include-orphans')
-
- def preflight(self):
- pass
-
- def get_unique_key(self):
- key = {'integrate': self.integration,
- 'include': sorted(self.include),
- 'orphans': self.include_orphans}
-
- if self.exclude:
- key['exclude'] = sorted(self.exclude)
-
- return key
-
- def configure_sandbox(self, sandbox):
- pass
-
- def stage(self, sandbox):
- pass
-
- def assemble(self, sandbox):
-
- require_split = self.include or self.exclude or not self.include_orphans
-
- # Stage deps in the sandbox root
- with self.timed_activity("Staging dependencies", silent_nested=True):
- self.stage_dependency_artifacts(sandbox, Scope.BUILD)
-
- manifest = set()
- if require_split:
- with self.timed_activity("Computing split", silent_nested=True):
- for dep in self.dependencies(Scope.BUILD):
- files = dep.compute_manifest(include=self.include,
- exclude=self.exclude,
- orphans=self.include_orphans)
- manifest.update(files)
-
- # Make a snapshot of all the files.
- vbasedir = sandbox.get_virtual_directory()
- modified_files = set()
- removed_files = set()
- added_files = set()
-
- # Run any integration commands provided by the dependencies
- # once they are all staged and ready
- if self.integration:
- with self.timed_activity("Integrating sandbox"):
- if require_split:
-
- # Make a snapshot of all the files before integration-commands are run.
- snapshot = set(vbasedir.list_relative_paths())
- vbasedir.mark_unmodified()
-
- with sandbox.batch(0):
- for dep in self.dependencies(Scope.BUILD):
- dep.integrate(sandbox)
-
- if require_split:
- # Calculate added, modified and removed files
- post_integration_snapshot = vbasedir.list_relative_paths()
- modified_files = set(vbasedir.list_modified_paths())
- basedir_contents = set(post_integration_snapshot)
- for path in manifest:
- if path in snapshot and path not in basedir_contents:
- removed_files.add(path)
-
- for path in basedir_contents:
- if path not in snapshot:
- added_files.add(path)
- self.info("Integration modified {}, added {} and removed {} files"
- .format(len(modified_files), len(added_files), len(removed_files)))
-
- # The remainder of this is expensive, make an early exit if
- # we're not being selective about what is to be included.
- if not require_split:
- return '/'
-
- # Do we want to force include files which were modified by
- # the integration commands, even if they were not added ?
- #
- manifest.update(added_files)
- manifest.difference_update(removed_files)
-
- # XXX We should be moving things outside of the build sandbox
- # instead of into a subdir. The element assemble() method should
- # support this in some way.
- #
- installdir = vbasedir.descend('buildstream', 'install', create=True)
-
- # We already saved the manifest for created files in the integration phase,
- # now collect the rest of the manifest.
- #
-
- lines = []
- if self.include:
- lines.append("Including files from domains: " + ", ".join(self.include))
- else:
- lines.append("Including files from all domains")
-
- if self.exclude:
- lines.append("Excluding files from domains: " + ", ".join(self.exclude))
-
- if self.include_orphans:
- lines.append("Including orphaned files")
- else:
- lines.append("Excluding orphaned files")
-
- detail = "\n".join(lines)
-
- def import_filter(path):
- return path in manifest
-
- with self.timed_activity("Creating composition", detail=detail, silent_nested=True):
- self.info("Composing {} files".format(len(manifest)))
- installdir.import_files(vbasedir, filter_callback=import_filter, can_link=True)
-
- # And we're done
- return os.path.join(os.sep, 'buildstream', 'install')
-
-
-# Plugin entry point
-def setup():
- return ComposeElement
diff --git a/buildstream/plugins/elements/compose.yaml b/buildstream/plugins/elements/compose.yaml
deleted file mode 100644
index fd2eb9358..000000000
--- a/buildstream/plugins/elements/compose.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-# Compose element configuration
-config:
-
- # Whether to run the integration commands for the
- # staged dependencies.
- #
- integrate: True
-
- # A list of domains to include from each artifact, as
- # they were defined in the element's 'split-rules'.
- #
- # Since domains can be added, it is not an error to
- # specify domains which may not exist for all of the
- # elements in this composition.
- #
- # The default empty list indicates that all domains
- # from each dependency should be included.
- #
- include: []
-
- # A list of domains to exclude from each artifact, as
- # they were defined in the element's 'split-rules'.
- #
- # In the case that a file is spoken for by a domain
- # in the 'include' list and another in the 'exclude'
- # list, then the file will be excluded.
- exclude: []
-
- # Whether to include orphan files which are not
- # included by any of the 'split-rules' present on
- # a given element.
- #
- include-orphans: True
diff --git a/buildstream/plugins/elements/distutils.py b/buildstream/plugins/elements/distutils.py
deleted file mode 100644
index 94d7a9705..000000000
--- a/buildstream/plugins/elements/distutils.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-"""
-distutils - Python distutils element
-====================================
-A :mod:`BuildElement <buildstream.buildelement>` implementation for using
-python distutils
-
-The distutils default configuration:
- .. literalinclude:: ../../../buildstream/plugins/elements/distutils.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the python 'distutils' kind.
-class DistutilsElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return DistutilsElement
diff --git a/buildstream/plugins/elements/distutils.yaml b/buildstream/plugins/elements/distutils.yaml
deleted file mode 100644
index cec7da6e9..000000000
--- a/buildstream/plugins/elements/distutils.yaml
+++ /dev/null
@@ -1,49 +0,0 @@
-# Default python distutils configuration
-
-variables:
-
- # When building for python2 distutils, simply
- # override this in the element declaration
- python: python3
-
- python-build: |
-
- %{python} %{conf-root}/setup.py build
-
- install-args: |
-
- --prefix "%{prefix}" \
- --root "%{install-root}"
-
- python-install: |
-
- %{python} %{conf-root}/setup.py install %{install-args}
-
-
-config:
-
- # Commands for configuring the software
- #
- configure-commands: []
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{python-build}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{python-install}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
- - |
- %{fix-pyc-timestamps}
diff --git a/buildstream/plugins/elements/filter.py b/buildstream/plugins/elements/filter.py
deleted file mode 100644
index 940e820a0..000000000
--- a/buildstream/plugins/elements/filter.py
+++ /dev/null
@@ -1,256 +0,0 @@
-#
-# Copyright (C) 2018 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>
-
-"""
-filter - Extract a subset of files from another element
-=======================================================
-Filter another element by producing an output that is a subset of
-the parent element's output. Subsets are defined by the parent element's
-:ref:`split rules <public_split_rules>`.
-
-Overview
---------
-A filter element must have exactly one *build* dependency, where said
-dependency is the 'parent' element which we would like to filter.
-Runtime dependencies may also be specified, which can be useful to propagate
-forward from this filter element onto its reverse dependencies.
-See :ref:`Dependencies <format_dependencies>` to see how we specify dependencies.
-
-When workspaces are opened, closed or reset on a filter element, or this
-element is tracked, the filter element will transparently pass on the command
-to its parent element (the sole build-dependency).
-
-Example
--------
-Consider a simple import element, ``import.bst`` which imports the local files
-'foo', 'bar' and 'baz' (each stored in ``files/``, relative to the project's root):
-
-.. code:: yaml
-
- kind: import
-
- # Specify sources to import
- sources:
- - kind: local
- path: files
-
- # Specify public domain data, visible to other elements
- public:
- bst:
- split-rules:
- foo:
- - /foo
- bar:
- - /bar
-
-.. note::
-
- We can make an element's metadata visible to all reverse dependencies by making use
- of the ``public:`` field. See the :ref:`public data documentation <format_public>`
- for more information.
-
-In this example, ``import.bst`` will serve as the 'parent' of the filter element, thus
-its output will be filtered. It is important to understand that the artifact of the
-above element will contain the files: 'foo', 'bar' and 'baz'.
-
-Now, to produce an element whose artifact contains the file 'foo', and exlusively 'foo',
-we can define the following filter, ``filter-foo.bst``:
-
-.. code:: yaml
-
- kind: filter
-
- # Declare the sole build-dependency of the filter element
- depends:
- - filename: import.bst
- type: build
-
- # Declare a list of domains to include in the filter's artifact
- config:
- include:
- - foo
-
-.. note::
-
- We can also specify build-dependencies with a 'build-depends' field which has been
- available since :ref:`format version 14 <project_format_version>`. See the
- :ref:`Build-Depends documentation <format_build_depends>` for more detail.
-
-It should be noted that an 'empty' ``include:`` list would, by default, include all
-split-rules specified in the parent element, which, in this example, would be the
-files 'foo' and 'bar' (the file 'baz' was not covered by any split rules).
-
-Equally, we can use the ``exclude:`` statement to create the same artifact (which
-only contains the file 'foo') by declaring the following element, ``exclude-bar.bst``:
-
-.. code:: yaml
-
- kind: filter
-
- # Declare the sole build-dependency of the filter element
- depends:
- - filename: import.bst
- type: build
-
- # Declare a list of domains to exclude in the filter's artifact
- config:
- exclude:
- - bar
-
-In addition to the ``include:`` and ``exclude:`` fields, there exists an ``include-orphans:``
-(Boolean) field, which defaults to ``False``. This will determine whether to include files
-which are not present in the 'split-rules'. For example, if we wanted to filter out all files
-which are not included as split rules we can define the following element, ``filter-misc.bst``:
-
-.. code:: yaml
-
- kind: filter
-
- # Declare the sole build-dependency of the filter element
- depends:
- - filename: import.bst
- type: build
-
- # Filter out all files which are not declared as split rules
- config:
- exclude:
- - foo
- - bar
- include-orphans: True
-
-The artifact of ``filter-misc.bst`` will only contain the file 'baz'.
-
-Below is more information regarding the the default configurations and possible options
-of the filter element:
-
-.. literalinclude:: ../../../buildstream/plugins/elements/filter.yaml
- :language: yaml
-"""
-
-from buildstream import Element, ElementError, Scope
-
-
-class FilterElement(Element):
- # pylint: disable=attribute-defined-outside-init
-
- BST_ARTIFACT_VERSION = 1
-
- # The filter element's output is its dependencies, so
- # we must rebuild if the dependencies change even when
- # not in strict build plans.
- BST_STRICT_REBUILD = True
-
- # This element ignores sources, so we should forbid them from being
- # added, to reduce the potential for confusion
- BST_FORBID_SOURCES = True
-
- # This plugin has been modified to avoid the use of Sandbox.get_directory
- BST_VIRTUAL_DIRECTORY = True
-
- # Filter elements do not run any commands
- BST_RUN_COMMANDS = False
-
- def configure(self, node):
- self.node_validate(node, [
- 'include', 'exclude', 'include-orphans'
- ])
-
- self.include = self.node_get_member(node, list, 'include')
- self.exclude = self.node_get_member(node, list, 'exclude')
- self.include_orphans = self.node_get_member(node, bool, 'include-orphans')
- self.include_provenance = self.node_provenance(node, member_name='include')
- self.exclude_provenance = self.node_provenance(node, member_name='exclude')
-
- def preflight(self):
- # Exactly one build-depend is permitted
- build_deps = list(self.dependencies(Scope.BUILD, recurse=False))
- if len(build_deps) != 1:
- detail = "Full list of build-depends:\n"
- deps_list = " \n".join([x.name for x in build_deps])
- detail += deps_list
- raise ElementError("{}: {} element must have exactly 1 build-dependency, actually have {}"
- .format(self, type(self).__name__, len(build_deps)),
- detail=detail, reason="filter-bdepend-wrong-count")
-
- # That build-depend must not also be a runtime-depend
- runtime_deps = list(self.dependencies(Scope.RUN, recurse=False))
- if build_deps[0] in runtime_deps:
- detail = "Full list of runtime depends:\n"
- deps_list = " \n".join([x.name for x in runtime_deps])
- detail += deps_list
- raise ElementError("{}: {} element's build dependency must not also be a runtime dependency"
- .format(self, type(self).__name__),
- detail=detail, reason="filter-bdepend-also-rdepend")
-
- def get_unique_key(self):
- key = {
- 'include': sorted(self.include),
- 'exclude': sorted(self.exclude),
- 'orphans': self.include_orphans,
- }
- return key
-
- def configure_sandbox(self, sandbox):
- pass
-
- def stage(self, sandbox):
- pass
-
- def assemble(self, sandbox):
- with self.timed_activity("Staging artifact", silent_nested=True):
- for dep in self.dependencies(Scope.BUILD, recurse=False):
- # Check that all the included/excluded domains exist
- pub_data = dep.get_public_data('bst')
- split_rules = self.node_get_member(pub_data, dict, 'split-rules', {})
- unfound_includes = []
- for domain in self.include:
- if domain not in split_rules:
- unfound_includes.append(domain)
- unfound_excludes = []
- for domain in self.exclude:
- if domain not in split_rules:
- unfound_excludes.append(domain)
-
- detail = []
- if unfound_includes:
- detail.append("Unknown domains were used in {}".format(self.include_provenance))
- detail.extend([' - {}'.format(domain) for domain in unfound_includes])
-
- if unfound_excludes:
- detail.append("Unknown domains were used in {}".format(self.exclude_provenance))
- detail.extend([' - {}'.format(domain) for domain in unfound_excludes])
-
- if detail:
- detail = '\n'.join(detail)
- raise ElementError("Unknown domains declared.", detail=detail)
-
- dep.stage_artifact(sandbox, include=self.include,
- exclude=self.exclude, orphans=self.include_orphans)
- return ""
-
- def _get_source_element(self):
- # Filter elements act as proxies for their sole build-dependency
- build_deps = list(self.dependencies(Scope.BUILD, recurse=False))
- assert len(build_deps) == 1
- output_elm = build_deps[0]._get_source_element()
- return output_elm
-
-
-def setup():
- return FilterElement
diff --git a/buildstream/plugins/elements/filter.yaml b/buildstream/plugins/elements/filter.yaml
deleted file mode 100644
index 9c2bf69f4..000000000
--- a/buildstream/plugins/elements/filter.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-# Filter element configuration
-config:
-
- # A list of domains to include in each artifact, as
- # they were defined as public data in the parent
- # element's 'split-rules'.
- #
- # If a domain is specified that does not exist, the
- # filter element will fail to build.
- #
- # The default empty list indicates that all domains
- # of the parent's artifact should be included.
- #
- include: []
-
- # A list of domains to exclude from each artifact, as
- # they were defined in the parent element's 'split-rules'.
- #
- # In the case that a file is spoken for by a domain
- # in the 'include' list and another in the 'exclude'
- # list, then the file will be excluded.
- exclude: []
-
- # Whether to include orphan files which are not
- # included by any of the 'split-rules' present in
- # the parent element.
- #
- include-orphans: False
diff --git a/buildstream/plugins/elements/import.py b/buildstream/plugins/elements/import.py
deleted file mode 100644
index 73884214f..000000000
--- a/buildstream/plugins/elements/import.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-"""
-import - Import sources directly
-================================
-Import elements produce artifacts directly from its sources
-without any kind of processing. These are typically used to
-import an SDK to build on top of or to overlay your build with
-some configuration data.
-
-The empty configuration is as such:
- .. literalinclude:: ../../../buildstream/plugins/elements/import.yaml
- :language: yaml
-"""
-
-import os
-from buildstream import Element, ElementError
-
-
-# Element implementation for the 'import' kind.
-class ImportElement(Element):
- # pylint: disable=attribute-defined-outside-init
-
- # This plugin has been modified to avoid the use of Sandbox.get_directory
- BST_VIRTUAL_DIRECTORY = True
-
- # Import elements do not run any commands
- BST_RUN_COMMANDS = False
-
- def configure(self, node):
- self.node_validate(node, [
- 'source', 'target'
- ])
-
- self.source = self.node_subst_member(node, 'source')
- self.target = self.node_subst_member(node, 'target')
-
- def preflight(self):
- # Assert that we have at least one source to fetch.
-
- sources = list(self.sources())
- if not sources:
- raise ElementError("{}: An import element must have at least one source.".format(self))
-
- def get_unique_key(self):
- return {
- 'source': self.source,
- 'target': self.target
- }
-
- def configure_sandbox(self, sandbox):
- pass
-
- def stage(self, sandbox):
- pass
-
- def assemble(self, sandbox):
-
- # Stage sources into the input directory
- # Do not mount workspaces as the files are copied from outside the sandbox
- self._stage_sources_in_sandbox(sandbox, 'input', mount_workspaces=False)
-
- rootdir = sandbox.get_virtual_directory()
- inputdir = rootdir.descend('input')
- outputdir = rootdir.descend('output', create=True)
-
- # The directory to grab
- inputdir = inputdir.descend(*self.source.strip(os.sep).split(os.sep))
-
- # The output target directory
- outputdir = outputdir.descend(*self.target.strip(os.sep).split(os.sep), create=True)
-
- if inputdir.is_empty():
- raise ElementError("{}: No files were found inside directory '{}'"
- .format(self, self.source))
-
- # Move it over
- outputdir.import_files(inputdir)
-
- # And we're done
- return '/output'
-
- def generate_script(self):
- build_root = self.get_variable('build-root')
- install_root = self.get_variable('install-root')
- commands = []
-
- # The directory to grab
- inputdir = os.path.join(build_root, self.normal_name, self.source.lstrip(os.sep))
- inputdir = inputdir.rstrip(os.sep)
-
- # The output target directory
- outputdir = os.path.join(install_root, self.target.lstrip(os.sep))
- outputdir = outputdir.rstrip(os.sep)
-
- # Ensure target directory parent exists but target directory doesn't
- commands.append("mkdir -p {}".format(os.path.dirname(outputdir)))
- commands.append("[ ! -e {outputdir} ] || rmdir {outputdir}".format(outputdir=outputdir))
-
- # Move it over
- commands.append("mv {} {}".format(inputdir, outputdir))
-
- script = ""
- for cmd in commands:
- script += "(set -ex; {}\n) || exit 1\n".format(cmd)
-
- return script
-
-
-# Plugin entry point
-def setup():
- return ImportElement
diff --git a/buildstream/plugins/elements/import.yaml b/buildstream/plugins/elements/import.yaml
deleted file mode 100644
index 698111b55..000000000
--- a/buildstream/plugins/elements/import.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-# The import element simply stages the given sources
-# directly to the root of the sandbox and then collects
-# the output to create an output artifact.
-#
-config:
-
- # By default we collect everything staged, specify a
- # directory here to output only a subset of the staged
- # input sources.
- source: /
-
- # Prefix the output with an optional directory, by default
- # the input is found at the root of the produced artifact.
- target: /
diff --git a/buildstream/plugins/elements/junction.py b/buildstream/plugins/elements/junction.py
deleted file mode 100644
index 15ef115d9..000000000
--- a/buildstream/plugins/elements/junction.py
+++ /dev/null
@@ -1,229 +0,0 @@
-#
-# 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:
-# Jürg Billeter <juerg.billeter@codethink.co.uk>
-
-"""
-junction - Integrate subprojects
-================================
-This element is a link to another BuildStream project. It allows integration
-of multiple projects into a single pipeline.
-
-Overview
---------
-
-.. code:: yaml
-
- kind: junction
-
- # Specify the BuildStream project source
- sources:
- - kind: git
- url: upstream:projectname.git
- track: master
- ref: d0b38561afb8122a3fc6bafc5a733ec502fcaed6
-
- # Specify the junction configuration
- config:
-
- # Override project options
- options:
- machine_arch: "%{machine_arch}"
- debug: True
-
- # Optionally look in a subpath of the source repository for the project
- path: projects/hello
-
- # Optionally specify another junction element to serve as a target for
- # this element. Target should be defined using the syntax
- # ``{junction-name}:{element-name}``.
- #
- # Note that this option cannot be used in conjunction with sources.
- target: sub-project.bst:sub-sub-project.bst
-
-.. note::
-
- The configuration option to allow specifying junction targets is available
- since :ref:`format version 24 <project_format_version>`.
-
-.. note::
-
- Junction elements may not specify any dependencies as they are simply
- links to other projects and are not in the dependency graph on their own.
-
-With a junction element in place, local elements can depend on elements in
-the other BuildStream project using the additional ``junction`` attribute in the
-dependency dictionary:
-
-.. code:: yaml
-
- depends:
- - junction: toolchain.bst
- filename: gcc.bst
- type: build
-
-While junctions are elements, only a limited set of element operations is
-supported. They can be tracked and fetched like other elements.
-However, junction elements do not produce any artifacts, which means that
-they cannot be built or staged. It also means that another element cannot
-depend on a junction element itself.
-
-.. note::
-
- BuildStream does not implicitly track junction elements. This means
- that if we were to invoke: `bst build --track-all ELEMENT` on an element
- which uses a junction element, the ref of the junction element
- will not automatically be updated if a more recent version exists.
-
- Therefore, if you require the most up-to-date version of a subproject,
- you must explicitly track the junction element by invoking:
- `bst source track JUNCTION_ELEMENT`.
-
- Furthermore, elements within the subproject are also not tracked by default.
- For this, we must specify the `--track-cross-junctions` option. This option
- must be preceeded by `--track ELEMENT` or `--track-all`.
-
-
-Sources
--------
-``bst show`` does not implicitly fetch junction sources if they haven't been
-cached yet. However, they can be fetched explicitly:
-
-.. code::
-
- bst source fetch junction.bst
-
-Other commands such as ``bst build`` implicitly fetch junction sources.
-
-Options
--------
-.. code:: yaml
-
- options:
- machine_arch: "%{machine_arch}"
- debug: True
-
-Junctions can configure options of the linked project. Options are never
-implicitly inherited across junctions, however, variables can be used to
-explicitly assign the same value to a subproject option.
-
-.. _core_junction_nested:
-
-Nested Junctions
-----------------
-Junctions can be nested. That is, subprojects are allowed to have junctions on
-their own. Nested junctions in different subprojects may point to the same
-project, however, in most use cases the same project should be loaded only once.
-BuildStream uses the junction element name as key to determine which junctions
-to merge. It is recommended that the name of a junction is set to the same as
-the name of the linked project.
-
-As the junctions may differ in source version and options, BuildStream cannot
-simply use one junction and ignore the others. Due to this, BuildStream requires
-the user to resolve possibly conflicting nested junctions by creating a junction
-with the same name in the top-level project, which then takes precedence.
-
-Targeting other junctions
-~~~~~~~~~~~~~~~~~~~~~~~~~
-When working with nested junctions, you can also create a junction element that
-targets another junction element in the sub-project. This can be useful if you
-need to ensure that both the top-level project and the sub-project are using
-the same version of the sub-sub-project.
-
-This can be done using the ``target`` configuration option. See below for an
-example:
-
-.. code:: yaml
-
- kind: junction
-
- config:
- target: subproject.bst:subsubproject.bst
-
-In the above example, this junction element would be targeting the junction
-element named ``subsubproject.bst`` in the subproject referred to by
-``subproject.bst``.
-
-Note that when targeting another junction, the names of the junction element
-must not be the same as the name of the target.
-"""
-
-from collections.abc import Mapping
-from buildstream import Element, ElementError
-from buildstream._pipeline import PipelineError
-
-
-# Element implementation for the 'junction' kind.
-class JunctionElement(Element):
- # pylint: disable=attribute-defined-outside-init
-
- # Junctions are not allowed any dependencies
- BST_FORBID_BDEPENDS = True
- BST_FORBID_RDEPENDS = True
-
- def configure(self, node):
- self.path = self.node_get_member(node, str, 'path', default='')
- self.options = self.node_get_member(node, Mapping, 'options', default={})
- self.target = self.node_get_member(node, str, 'target', default=None)
- self.target_element = None
- self.target_junction = None
-
- def preflight(self):
- # "target" cannot be used in conjunction with:
- # 1. sources
- # 2. config['options']
- # 3. config['path']
- if self.target and any(self.sources()):
- raise ElementError("junction elements cannot define both 'sources' and 'target' config option")
- if self.target and any(self.node_items(self.options)):
- raise ElementError("junction elements cannot define both 'options' and 'target'")
- if self.target and self.path:
- raise ElementError("junction elements cannot define both 'path' and 'target'")
-
- # Validate format of target, if defined
- if self.target:
- try:
- self.target_junction, self.target_element = self.target.split(":")
- except ValueError:
- raise ElementError("'target' option must be in format '{junction-name}:{element-name}'")
-
- # We cannot target a junction that has the same name as us, since that
- # will cause an infinite recursion while trying to load it.
- if self.name == self.target_element:
- raise ElementError("junction elements cannot target an element with the same name")
-
- def get_unique_key(self):
- # Junctions do not produce artifacts. get_unique_key() implementation
- # is still required for `bst source fetch`.
- return 1
-
- def configure_sandbox(self, sandbox):
- raise PipelineError("Cannot build junction elements")
-
- def stage(self, sandbox):
- raise PipelineError("Cannot stage junction elements")
-
- def generate_script(self):
- raise PipelineError("Cannot build junction elements")
-
- def assemble(self, sandbox):
- raise PipelineError("Cannot build junction elements")
-
-
-# Plugin entry point
-def setup():
- return JunctionElement
diff --git a/buildstream/plugins/elements/make.py b/buildstream/plugins/elements/make.py
deleted file mode 100644
index 262dc2b3f..000000000
--- a/buildstream/plugins/elements/make.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Copyright 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:
-# Ed Baunton <ebaunton1@bloomberg.net>
-
-"""
-make - Make build element
-=========================
-This is a :mod:`BuildElement <buildstream.buildelement>` implementation for
-using GNU make based build.
-
-.. note::
-
- The ``make`` element is available since :ref:`format version 9 <project_format_version>`
-
-Here is the default configuration for the ``make`` element in full:
-
- .. literalinclude:: ../../../buildstream/plugins/elements/make.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'make' kind.
-class MakeElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return MakeElement
diff --git a/buildstream/plugins/elements/make.yaml b/buildstream/plugins/elements/make.yaml
deleted file mode 100644
index 83e5c658f..000000000
--- a/buildstream/plugins/elements/make.yaml
+++ /dev/null
@@ -1,42 +0,0 @@
-# make default configurations
-
-variables:
- make: make PREFIX="%{prefix}"
- make-install: make -j1 PREFIX="%{prefix}" DESTDIR="%{install-root}" install
-
- # Set this if the sources cannot handle parallelization.
- #
- # notparallel: True
-
-config:
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{make}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{make-install}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
-
-# Use max-jobs CPUs for building and enable verbosity
-environment:
- MAKEFLAGS: -j%{max-jobs}
- V: 1
-
-# And dont consider MAKEFLAGS or V as something which may
-# affect build output.
-environment-nocache:
-- MAKEFLAGS
-- V
diff --git a/buildstream/plugins/elements/makemaker.py b/buildstream/plugins/elements/makemaker.py
deleted file mode 100644
index c3161581a..000000000
--- a/buildstream/plugins/elements/makemaker.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-"""
-makemaker - Perl MakeMaker build element
-========================================
-A :mod:`BuildElement <buildstream.buildelement>` implementation for using
-the Perl ExtUtil::MakeMaker build system
-
-The MakeMaker default configuration:
- .. literalinclude:: ../../../buildstream/plugins/elements/makemaker.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'makemaker' kind.
-class MakeMakerElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return MakeMakerElement
diff --git a/buildstream/plugins/elements/makemaker.yaml b/buildstream/plugins/elements/makemaker.yaml
deleted file mode 100644
index c9c4622cb..000000000
--- a/buildstream/plugins/elements/makemaker.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
-# Default configuration for the Perl ExtUtil::MakeMaker
-# build system
-
-variables:
-
- # To install perl distributions into the correct location
- # in our chroot we need to set PREFIX to <destdir>/<prefix>
- # in the configure-commands.
- #
- # The mapping between PREFIX and the final installation
- # directories is complex and depends upon the configuration
- # of perl see,
- # https://metacpan.org/pod/distribution/perl/INSTALL#Installation-Directories
- # and ExtUtil::MakeMaker's documentation for more details.
- configure: |
-
- perl Makefile.PL PREFIX=%{install-root}%{prefix}
-
- make: make
- make-install: make install
-
-config:
-
- # Commands for configuring the software
- #
- configure-commands:
- - |
- %{configure}
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{make}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{make-install}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
diff --git a/buildstream/plugins/elements/manual.py b/buildstream/plugins/elements/manual.py
deleted file mode 100644
index 7ca761428..000000000
--- a/buildstream/plugins/elements/manual.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-"""
-manual - Manual build element
-=============================
-The most basic build element does nothing but allows users to
-add custom build commands to the array understood by the :mod:`BuildElement <buildstream.buildelement>`
-
-The empty configuration is as such:
- .. literalinclude:: ../../../buildstream/plugins/elements/manual.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'manual' kind.
-class ManualElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return ManualElement
diff --git a/buildstream/plugins/elements/manual.yaml b/buildstream/plugins/elements/manual.yaml
deleted file mode 100644
index 38fe7d163..000000000
--- a/buildstream/plugins/elements/manual.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-# Manual build element does not provide any default
-# build commands
-config:
-
- # Commands for configuring the software
- #
- configure-commands: []
-
- # Commands for building the software
- #
- build-commands: []
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands: []
-
- # Commands for stripping installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
diff --git a/buildstream/plugins/elements/meson.py b/buildstream/plugins/elements/meson.py
deleted file mode 100644
index b16f025a0..000000000
--- a/buildstream/plugins/elements/meson.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright (C) 2017 Patrick Griffis
-# Copyright (C) 2018 Codethink Ltd.
-#
-# 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/>.
-
-"""
-meson - Meson build element
-===========================
-This is a :mod:`BuildElement <buildstream.buildelement>` implementation for
-using `Meson <http://mesonbuild.com/>`_ build scripts.
-
-You will often want to pass additional arguments to ``meson``. This should
-be done on a per-element basis by setting the ``meson-local`` variable. Here is
-an example:
-
-.. code:: yaml
-
- variables:
- meson-local: |
- -Dmonkeys=yes
-
-If you want to pass extra options to ``meson`` for every element in your
-project, set the ``meson-global`` variable in your project.conf file. Here is
-an example of that:
-
-.. code:: yaml
-
- elements:
- meson:
- variables:
- meson-global: |
- -Dmonkeys=always
-
-Here is the default configuration for the ``meson`` element in full:
-
- .. literalinclude:: ../../../buildstream/plugins/elements/meson.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'meson' kind.
-class MesonElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return MesonElement
diff --git a/buildstream/plugins/elements/meson.yaml b/buildstream/plugins/elements/meson.yaml
deleted file mode 100644
index 2172cb34c..000000000
--- a/buildstream/plugins/elements/meson.yaml
+++ /dev/null
@@ -1,79 +0,0 @@
-# Meson default configuration
-
-variables:
-
- build-dir: _builddir
-
- # Project-wide extra arguments to be passed to `meson`
- meson-global: ''
-
- # Element-specific extra arguments to be passed to `meson`.
- meson-local: ''
-
- # For backwards compatibility only, do not use.
- meson-extra: ''
-
- meson-args: |
-
- --prefix=%{prefix} \
- --bindir=%{bindir} \
- --sbindir=%{sbindir} \
- --sysconfdir=%{sysconfdir} \
- --datadir=%{datadir} \
- --includedir=%{includedir} \
- --libdir=%{libdir} \
- --libexecdir=%{libexecdir} \
- --localstatedir=%{localstatedir} \
- --sharedstatedir=%{sharedstatedir} \
- --mandir=%{mandir} \
- --infodir=%{infodir} %{meson-extra} %{meson-global} %{meson-local}
-
- meson: meson %{conf-root} %{build-dir} %{meson-args}
-
- ninja: |
- ninja -j ${NINJAJOBS} -C %{build-dir}
-
- ninja-install: |
- env DESTDIR="%{install-root}" ninja -C %{build-dir} install
-
- # Set this if the sources cannot handle parallelization.
- #
- # notparallel: True
-
-config:
-
- # Commands for configuring the software
- #
- configure-commands:
- - |
- %{meson}
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{ninja}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{ninja-install}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
-
-# Use max-jobs CPUs for building
-environment:
- NINJAJOBS: |
- %{max-jobs}
-
-# And dont consider NINJAJOBS as something which may
-# affect build output.
-environment-nocache:
-- NINJAJOBS
diff --git a/buildstream/plugins/elements/modulebuild.py b/buildstream/plugins/elements/modulebuild.py
deleted file mode 100644
index a83d2705d..000000000
--- a/buildstream/plugins/elements/modulebuild.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-"""
-modulebuild - Perl Module::Build build element
-==============================================
-A :mod:`BuildElement <buildstream.buildelement>` implementation for using
-the Perl Module::Build build system
-
-The modulebuild default configuration:
- .. literalinclude:: ../../../buildstream/plugins/elements/modulebuild.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'modulebuild' kind.
-class ModuleBuildElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return ModuleBuildElement
diff --git a/buildstream/plugins/elements/modulebuild.yaml b/buildstream/plugins/elements/modulebuild.yaml
deleted file mode 100644
index 18f034bab..000000000
--- a/buildstream/plugins/elements/modulebuild.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
-# Default configuration for the Perl Module::Build
-# build system.
-
-variables:
-
- # To install perl distributions into the correct location
- # in our chroot we need to set PREFIX to <destdir>/<prefix>
- # in the configure-commands.
- #
- # The mapping between PREFIX and the final installation
- # directories is complex and depends upon the configuration
- # of perl see,
- # https://metacpan.org/pod/distribution/perl/INSTALL#Installation-Directories
- # and ExtUtil::MakeMaker's documentation for more details.
- configure: |
-
- perl Build.PL --prefix "%{install-root}%{prefix}"
-
- perl-build: ./Build
- perl-install: ./Build install
-
-config:
-
- # Commands for configuring the software
- #
- configure-commands:
- - |
- %{configure}
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{perl-build}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{perl-install}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
diff --git a/buildstream/plugins/elements/pip.py b/buildstream/plugins/elements/pip.py
deleted file mode 100644
index 31e1071f0..000000000
--- a/buildstream/plugins/elements/pip.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2017 Mathieu Bridon
-#
-# 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:
-# Mathieu Bridon <bochecha@daitauha.fr>
-
-"""
-pip - Pip build element
-=======================
-A :mod:`BuildElement <buildstream.buildelement>` implementation for installing
-Python modules with pip
-
-The pip default configuration:
- .. literalinclude:: ../../../buildstream/plugins/elements/pip.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'pip' kind.
-class PipElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return PipElement
diff --git a/buildstream/plugins/elements/pip.yaml b/buildstream/plugins/elements/pip.yaml
deleted file mode 100644
index 294d4ad9a..000000000
--- a/buildstream/plugins/elements/pip.yaml
+++ /dev/null
@@ -1,36 +0,0 @@
-# Pip default configurations
-
-variables:
-
- pip: pip
- pip-flags: |
- %{pip} install --no-deps --root=%{install-root} --prefix=%{prefix}
- pip-install-package: |
- %{pip-flags} %{conf-root}
- pip-download-dir: |
- .bst_pip_downloads
- pip-install-dependencies: |
- if [ -e %{pip-download-dir} ]; then %{pip-flags} %{pip-download-dir}/*; fi
-
-config:
-
- configure-commands: []
- build-commands: []
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{pip-install-package}
- - |
- %{pip-install-dependencies}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
- - |
- %{fix-pyc-timestamps}
diff --git a/buildstream/plugins/elements/qmake.py b/buildstream/plugins/elements/qmake.py
deleted file mode 100644
index 1bb5fd74a..000000000
--- a/buildstream/plugins/elements/qmake.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-"""
-qmake - QMake build element
-===========================
-A :mod:`BuildElement <buildstream.buildelement>` implementation for using
-the qmake build system
-
-The qmake default configuration:
- .. literalinclude:: ../../../buildstream/plugins/elements/qmake.yaml
- :language: yaml
-
-See :ref:`built-in functionality documentation <core_buildelement_builtins>` for
-details on common configuration options for build elements.
-"""
-
-from buildstream import BuildElement, SandboxFlags
-
-
-# Element implementation for the 'qmake' kind.
-class QMakeElement(BuildElement):
- # Supports virtual directories (required for remote execution)
- BST_VIRTUAL_DIRECTORY = True
-
- # Enable command batching across prepare() and assemble()
- def configure_sandbox(self, sandbox):
- super().configure_sandbox(sandbox)
- self.batch_prepare_assemble(SandboxFlags.ROOT_READ_ONLY,
- collect=self.get_variable('install-root'))
-
-
-# Plugin entry point
-def setup():
- return QMakeElement
diff --git a/buildstream/plugins/elements/qmake.yaml b/buildstream/plugins/elements/qmake.yaml
deleted file mode 100644
index 4ac31932e..000000000
--- a/buildstream/plugins/elements/qmake.yaml
+++ /dev/null
@@ -1,50 +0,0 @@
-# QMake default configuration
-
-variables:
-
- qmake: qmake -makefile %{conf-root}
- make: make
- make-install: make -j1 INSTALL_ROOT="%{install-root}" install
-
- # Set this if the sources cannot handle parallelization.
- #
- # notparallel: True
-
-config:
-
- # Commands for configuring the software
- #
- configure-commands:
- - |
- %{qmake}
-
- # Commands for building the software
- #
- build-commands:
- - |
- %{make}
-
- # Commands for installing the software into a
- # destination folder
- #
- install-commands:
- - |
- %{make-install}
-
- # Commands for stripping debugging information out of
- # installed binaries
- #
- strip-commands:
- - |
- %{strip-binaries}
-
-# Use max-jobs CPUs for building and enable verbosity
-environment:
- MAKEFLAGS: -j%{max-jobs}
- V: 1
-
-# And dont consider MAKEFLAGS or V as something which may
-# affect build output.
-environment-nocache:
-- MAKEFLAGS
-- V
diff --git a/buildstream/plugins/elements/script.py b/buildstream/plugins/elements/script.py
deleted file mode 100644
index 6c33ecf95..000000000
--- a/buildstream/plugins/elements/script.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-# Jonathan Maw <jonathan.maw@codethink.co.uk>
-
-"""
-script - Run scripts to create output
-=====================================
-This element allows one to run some commands to mutate the
-input and create some output.
-
-.. note::
-
- Script elements may only specify build dependencies. See
- :ref:`the format documentation <format_dependencies>` for more
- detail on specifying dependencies.
-
-The default configuration and possible options are as such:
- .. literalinclude:: ../../../buildstream/plugins/elements/script.yaml
- :language: yaml
-"""
-
-import buildstream
-
-
-# Element implementation for the 'script' kind.
-class ScriptElement(buildstream.ScriptElement):
- # pylint: disable=attribute-defined-outside-init
-
- # This plugin has been modified to avoid the use of Sandbox.get_directory
- BST_VIRTUAL_DIRECTORY = True
-
- def configure(self, node):
- for n in self.node_get_member(node, list, 'layout', []):
- dst = self.node_subst_member(n, 'destination')
- elm = self.node_subst_member(n, 'element', None)
- self.layout_add(elm, dst)
-
- self.node_validate(node, [
- 'commands', 'root-read-only', 'layout'
- ])
-
- cmds = self.node_subst_list(node, "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
-def setup():
- return ScriptElement
diff --git a/buildstream/plugins/elements/script.yaml b/buildstream/plugins/elements/script.yaml
deleted file mode 100644
index b388378da..000000000
--- a/buildstream/plugins/elements/script.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-# Common script element variables
-variables:
- # Defines the directory commands will be run from.
- cwd: /
-
-# Script element configuration
-config:
-
- # 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 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.
- commands: []
-
diff --git a/buildstream/plugins/elements/stack.py b/buildstream/plugins/elements/stack.py
deleted file mode 100644
index 97517ca48..000000000
--- a/buildstream/plugins/elements/stack.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Copyright (C) 2016 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.
-"""
-
-from buildstream import Element
-
-
-# Element implementation for the 'stack' kind.
-class StackElement(Element):
-
- # This plugin has been modified to avoid the use of Sandbox.get_directory
- BST_VIRTUAL_DIRECTORY = True
-
- def configure(self, node):
- pass
-
- def preflight(self):
- pass
-
- 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
diff --git a/buildstream/plugins/sources/__init__.py b/buildstream/plugins/sources/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/plugins/sources/__init__.py
+++ /dev/null
diff --git a/buildstream/plugins/sources/_downloadablefilesource.py b/buildstream/plugins/sources/_downloadablefilesource.py
deleted file mode 100644
index b9b15e268..000000000
--- a/buildstream/plugins/sources/_downloadablefilesource.py
+++ /dev/null
@@ -1,250 +0,0 @@
-"""A base abstract class for source implementations which download a file"""
-
-import os
-import urllib.request
-import urllib.error
-import contextlib
-import shutil
-import netrc
-
-from buildstream import Source, SourceError, Consistency
-from buildstream import utils
-
-
-class _NetrcFTPOpener(urllib.request.FTPHandler):
-
- def __init__(self, netrc_config):
- self.netrc = netrc_config
-
- def _split(self, netloc):
- userpass, hostport = urllib.parse.splituser(netloc)
- host, port = urllib.parse.splitport(hostport)
- if userpass:
- user, passwd = urllib.parse.splitpasswd(userpass)
- else:
- user = None
- passwd = None
- return host, port, user, passwd
-
- def _unsplit(self, host, port, user, passwd):
- if port:
- host = '{}:{}'.format(host, port)
- if user:
- if passwd:
- user = '{}:{}'.format(user, passwd)
- host = '{}@{}'.format(user, host)
-
- return host
-
- def ftp_open(self, req):
- host, port, user, passwd = self._split(req.host)
-
- if user is None and self.netrc:
- entry = self.netrc.authenticators(host)
- if entry:
- user, _, passwd = entry
-
- req.host = self._unsplit(host, port, user, passwd)
-
- return super().ftp_open(req)
-
-
-class _NetrcPasswordManager:
-
- def __init__(self, netrc_config):
- self.netrc = netrc_config
-
- def add_password(self, realm, uri, user, passwd):
- pass
-
- def find_user_password(self, realm, authuri):
- if not self.netrc:
- return None, None
- parts = urllib.parse.urlsplit(authuri)
- entry = self.netrc.authenticators(parts.hostname)
- if not entry:
- return None, None
- else:
- login, _, password = entry
- return login, password
-
-
-class DownloadableFileSource(Source):
- # pylint: disable=attribute-defined-outside-init
-
- COMMON_CONFIG_KEYS = Source.COMMON_CONFIG_KEYS + ['url', 'ref', 'etag']
-
- __urlopener = None
-
- def configure(self, node):
- self.original_url = self.node_get_member(node, str, 'url')
- self.ref = self.node_get_member(node, str, 'ref', None)
- self.url = self.translate_url(self.original_url)
- self._warn_deprecated_etag(node)
-
- def preflight(self):
- return
-
- def get_unique_key(self):
- return [self.original_url, self.ref]
-
- def get_consistency(self):
- if self.ref is None:
- return Consistency.INCONSISTENT
-
- if os.path.isfile(self._get_mirror_file()):
- return Consistency.CACHED
-
- else:
- return Consistency.RESOLVED
-
- def load_ref(self, node):
- self.ref = self.node_get_member(node, str, 'ref', None)
- self._warn_deprecated_etag(node)
-
- def get_ref(self):
- return self.ref
-
- def set_ref(self, ref, node):
- node['ref'] = self.ref = ref
-
- def track(self):
- # there is no 'track' field in the source to determine what/whether
- # or not to update refs, because tracking a ref is always a conscious
- # decision by the user.
- with self.timed_activity("Tracking {}".format(self.url),
- silent_nested=True):
- new_ref = self._ensure_mirror()
-
- if self.ref and self.ref != new_ref:
- detail = "When tracking, new ref differs from current ref:\n" \
- + " Tracked URL: {}\n".format(self.url) \
- + " Current ref: {}\n".format(self.ref) \
- + " New ref: {}\n".format(new_ref)
- self.warn("Potential man-in-the-middle attack!", detail=detail)
-
- return new_ref
-
- def fetch(self):
-
- # Just a defensive check, it is impossible for the
- # file to be already cached because Source.fetch() will
- # not be called if the source is already Consistency.CACHED.
- #
- if os.path.isfile(self._get_mirror_file()):
- return # pragma: nocover
-
- # Download the file, raise hell if the sha256sums don't match,
- # and mirror the file otherwise.
- with self.timed_activity("Fetching {}".format(self.url), silent_nested=True):
- sha256 = self._ensure_mirror()
- if sha256 != self.ref:
- raise SourceError("File downloaded from {} has sha256sum '{}', not '{}'!"
- .format(self.url, sha256, self.ref))
-
- def _warn_deprecated_etag(self, node):
- etag = self.node_get_member(node, str, 'etag', None)
- if etag:
- provenance = self.node_provenance(node, member_name='etag')
- self.warn('{} "etag" is deprecated and ignored.'.format(provenance))
-
- def _get_etag(self, ref):
- etagfilename = os.path.join(self._get_mirror_dir(), '{}.etag'.format(ref))
- if os.path.exists(etagfilename):
- with open(etagfilename, 'r') as etagfile:
- return etagfile.read()
-
- return None
-
- def _store_etag(self, ref, etag):
- etagfilename = os.path.join(self._get_mirror_dir(), '{}.etag'.format(ref))
- with utils.save_file_atomic(etagfilename) as etagfile:
- etagfile.write(etag)
-
- def _ensure_mirror(self):
- # Downloads from the url and caches it according to its sha256sum.
- try:
- with self.tempdir() as td:
- default_name = os.path.basename(self.url)
- request = urllib.request.Request(self.url)
- request.add_header('Accept', '*/*')
-
- # We do not use etag in case what we have in cache is
- # not matching ref in order to be able to recover from
- # corrupted download.
- if self.ref:
- etag = self._get_etag(self.ref)
-
- # Do not re-download the file if the ETag matches.
- if etag and self.get_consistency() == Consistency.CACHED:
- request.add_header('If-None-Match', etag)
-
- opener = self.__get_urlopener()
- with contextlib.closing(opener.open(request)) as response:
- info = response.info()
-
- etag = info['ETag'] if 'ETag' in info else None
-
- filename = info.get_filename(default_name)
- filename = os.path.basename(filename)
- local_file = os.path.join(td, filename)
- with open(local_file, 'wb') as dest:
- shutil.copyfileobj(response, dest)
-
- # Make sure url-specific mirror dir exists.
- if not os.path.isdir(self._get_mirror_dir()):
- os.makedirs(self._get_mirror_dir())
-
- # Store by sha256sum
- sha256 = utils.sha256sum(local_file)
- # Even if the file already exists, move the new file over.
- # In case the old file was corrupted somehow.
- os.rename(local_file, self._get_mirror_file(sha256))
-
- if etag:
- self._store_etag(sha256, etag)
- return sha256
-
- except urllib.error.HTTPError as e:
- if e.code == 304:
- # 304 Not Modified.
- # Because we use etag only for matching ref, currently specified ref is what
- # we would have downloaded.
- return self.ref
- raise SourceError("{}: Error mirroring {}: {}"
- .format(self, self.url, e), temporary=True) from e
-
- except (urllib.error.URLError, urllib.error.ContentTooShortError, OSError, ValueError) as e:
- # Note that urllib.request.Request in the try block may throw a
- # ValueError for unknown url types, so we handle it here.
- raise SourceError("{}: Error mirroring {}: {}"
- .format(self, self.url, e), temporary=True) from e
-
- def _get_mirror_dir(self):
- return os.path.join(self.get_mirror_directory(),
- utils.url_directory_name(self.original_url))
-
- def _get_mirror_file(self, sha=None):
- return os.path.join(self._get_mirror_dir(), sha or self.ref)
-
- def __get_urlopener(self):
- if not DownloadableFileSource.__urlopener:
- try:
- netrc_config = netrc.netrc()
- except OSError:
- # If the .netrc file was not found, FileNotFoundError will be
- # raised, but OSError will be raised directly by the netrc package
- # in the case that $HOME is not set.
- #
- # This will catch both cases.
- #
- DownloadableFileSource.__urlopener = urllib.request.build_opener()
- except netrc.NetrcParseError as e:
- self.warn('{}: While reading .netrc: {}'.format(self, e))
- return urllib.request.build_opener()
- else:
- netrc_pw_mgr = _NetrcPasswordManager(netrc_config)
- http_auth = urllib.request.HTTPBasicAuthHandler(netrc_pw_mgr)
- ftp_handler = _NetrcFTPOpener(netrc_config)
- DownloadableFileSource.__urlopener = urllib.request.build_opener(http_auth, ftp_handler)
- return DownloadableFileSource.__urlopener
diff --git a/buildstream/plugins/sources/bzr.py b/buildstream/plugins/sources/bzr.py
deleted file mode 100644
index e59986da6..000000000
--- a/buildstream/plugins/sources/bzr.py
+++ /dev/null
@@ -1,210 +0,0 @@
-# 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>
-
-"""
-bzr - stage files from a bazaar repository
-==========================================
-
-**Host dependencies:**
-
- * bzr
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the bzr source kind
- kind: bzr
-
- # Specify the bzr url. Bazaar URLs come in many forms, see
- # `bzr help urlspec` for more information. Using an alias defined
- # in your project configuration is encouraged.
- url: https://launchpad.net/bzr
-
- # Specify the tracking branch. This is mandatory, as bzr cannot identify
- # an individual revision outside its branch. bzr URLs that omit the branch
- # name implicitly specify the trunk branch, but bst requires this to be
- # explicit.
- track: trunk
-
- # Specify the ref. This is a revision number. This is usually a decimal,
- # but revisions on a branch are of the form
- # <revision-branched-from>.<branch-number>.<revision-since-branching>
- # e.g. 6622.1.6.
- # The ref must be specified to build, and 'bst source track' will update the
- # revision number to the one on the tip of the branch specified in 'track'.
- ref: 6622
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-"""
-
-import os
-import shutil
-import fcntl
-from contextlib import contextmanager
-
-from buildstream import Source, SourceError, Consistency
-from buildstream import utils
-
-
-class BzrSource(Source):
- # pylint: disable=attribute-defined-outside-init
-
- def configure(self, node):
- self.node_validate(node, ['url', 'track', 'ref', *Source.COMMON_CONFIG_KEYS])
-
- self.original_url = self.node_get_member(node, str, 'url')
- self.tracking = self.node_get_member(node, str, 'track')
- self.ref = self.node_get_member(node, str, 'ref', None)
- self.url = self.translate_url(self.original_url)
-
- def preflight(self):
- # Check if bzr is installed, get the binary at the same time.
- self.host_bzr = utils.get_host_tool('bzr')
-
- def get_unique_key(self):
- return [self.original_url, self.tracking, self.ref]
-
- def get_consistency(self):
- if self.ref is None or self.tracking is None:
- return Consistency.INCONSISTENT
-
- # Lock for the _check_ref()
- with self._locked():
- if self._check_ref():
- return Consistency.CACHED
- else:
- return Consistency.RESOLVED
-
- def load_ref(self, node):
- self.ref = self.node_get_member(node, str, 'ref', None)
-
- def get_ref(self):
- return self.ref
-
- def set_ref(self, ref, node):
- node['ref'] = self.ref = ref
-
- def track(self):
- with self.timed_activity("Tracking {}".format(self.url),
- silent_nested=True), self._locked():
- self._ensure_mirror(skip_ref_check=True)
- ret, out = self.check_output([self.host_bzr, "version-info",
- "--custom", "--template={revno}",
- self._get_branch_dir()],
- fail="Failed to read the revision number at '{}'"
- .format(self._get_branch_dir()))
- if ret != 0:
- raise SourceError("{}: Failed to get ref for tracking {}".format(self, self.tracking))
-
- return out
-
- def fetch(self):
- with self.timed_activity("Fetching {}".format(self.url),
- silent_nested=True), self._locked():
- self._ensure_mirror()
-
- def stage(self, directory):
- self.call([self.host_bzr, "checkout", "--lightweight",
- "--revision=revno:{}".format(self.ref),
- self._get_branch_dir(), directory],
- fail="Failed to checkout revision {} from branch {} to {}"
- .format(self.ref, self._get_branch_dir(), directory))
- # Remove .bzr dir
- shutil.rmtree(os.path.join(directory, ".bzr"))
-
- def init_workspace(self, directory):
- url = os.path.join(self.url, self.tracking)
- with self.timed_activity('Setting up workspace "{}"'.format(directory), silent_nested=True):
- # Checkout from the cache
- self.call([self.host_bzr, "branch",
- "--use-existing-dir",
- "--revision=revno:{}".format(self.ref),
- self._get_branch_dir(), directory],
- fail="Failed to branch revision {} from branch {} to {}"
- .format(self.ref, self._get_branch_dir(), directory))
- # Switch the parent branch to the source's origin
- self.call([self.host_bzr, "switch",
- "--directory={}".format(directory), url],
- fail="Failed to switch workspace's parent branch to {}".format(url))
-
- # _locked()
- #
- # This context manager ensures exclusive access to the
- # bzr repository.
- #
- @contextmanager
- def _locked(self):
- lockdir = os.path.join(self.get_mirror_directory(), 'locks')
- lockfile = os.path.join(
- lockdir,
- utils.url_directory_name(self.original_url) + '.lock'
- )
- os.makedirs(lockdir, exist_ok=True)
- with open(lockfile, 'w') as lock:
- fcntl.flock(lock, fcntl.LOCK_EX)
- try:
- yield
- finally:
- fcntl.flock(lock, fcntl.LOCK_UN)
-
- def _check_ref(self):
- # If the mirror doesnt exist yet, then we dont have the ref
- if not os.path.exists(self._get_branch_dir()):
- return False
-
- return self.call([self.host_bzr, "revno",
- "--revision=revno:{}".format(self.ref),
- self._get_branch_dir()]) == 0
-
- def _get_branch_dir(self):
- return os.path.join(self._get_mirror_dir(), self.tracking)
-
- def _get_mirror_dir(self):
- return os.path.join(self.get_mirror_directory(),
- utils.url_directory_name(self.original_url))
-
- def _ensure_mirror(self, skip_ref_check=False):
- mirror_dir = self._get_mirror_dir()
- bzr_metadata_dir = os.path.join(mirror_dir, ".bzr")
- if not os.path.exists(bzr_metadata_dir):
- self.call([self.host_bzr, "init-repo", "--no-trees", mirror_dir],
- fail="Failed to initialize bzr repository")
-
- branch_dir = os.path.join(mirror_dir, self.tracking)
- branch_url = self.url + "/" + self.tracking
- if not os.path.exists(branch_dir):
- # `bzr branch` the branch if it doesn't exist
- # to get the upstream code
- self.call([self.host_bzr, "branch", branch_url, branch_dir],
- fail="Failed to branch from {} to {}".format(branch_url, branch_dir))
-
- else:
- # `bzr pull` the branch if it does exist
- # to get any changes to the upstream code
- self.call([self.host_bzr, "pull", "--directory={}".format(branch_dir), branch_url],
- fail="Failed to pull new changes for {}".format(branch_dir))
-
- if not skip_ref_check and not self._check_ref():
- raise SourceError("Failed to ensure ref '{}' was mirrored".format(self.ref),
- reason="ref-not-mirrored")
-
-
-def setup():
- return BzrSource
diff --git a/buildstream/plugins/sources/deb.py b/buildstream/plugins/sources/deb.py
deleted file mode 100644
index e45994951..000000000
--- a/buildstream/plugins/sources/deb.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# 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:
-# Phillip Smyth <phillip.smyth@codethink.co.uk>
-# Jonathan Maw <jonathan.maw@codethink.co.uk>
-# Richard Maw <richard.maw@codethink.co.uk>
-
-"""
-deb - stage files from .deb packages
-====================================
-
-**Host dependencies:**
-
- * arpy (python package)
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the deb source kind
- kind: deb
-
- # Specify the deb url. Using an alias defined in your project
- # configuration is encouraged. 'bst source track' will update the
- # sha256sum in 'ref' to the downloaded file's sha256sum.
- url: upstream:foo.deb
-
- # Specify the ref. It's a sha256sum of the file you download.
- ref: 6c9f6f68a131ec6381da82f2bff978083ed7f4f7991d931bfa767b7965ebc94b
-
- # Specify the basedir to return only the specified dir and its children
- base-dir: ''
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-"""
-
-import tarfile
-from contextlib import contextmanager
-import arpy # pylint: disable=import-error
-
-from .tar import TarSource
-
-
-class DebSource(TarSource):
- # pylint: disable=attribute-defined-outside-init
-
- def configure(self, node):
- super().configure(node)
-
- self.base_dir = self.node_get_member(node, str, 'base-dir', None)
-
- def preflight(self):
- return
-
- @contextmanager
- def _get_tar(self):
- with open(self._get_mirror_file(), 'rb') as deb_file:
- arpy_archive = arpy.Archive(fileobj=deb_file)
- arpy_archive.read_all_headers()
- data_tar_arpy = [v for k, v in arpy_archive.archived_files.items() if b"data.tar" in k][0]
- # ArchiveFileData is not enough like a file object for tarfile to use.
- # Monkey-patching a seekable method makes it close enough for TarFile to open.
- data_tar_arpy.seekable = lambda *args: True
- tar = tarfile.open(fileobj=data_tar_arpy, mode="r:*")
- yield tar
-
-
-def setup():
- return DebSource
diff --git a/buildstream/plugins/sources/git.py b/buildstream/plugins/sources/git.py
deleted file mode 100644
index 5e6834979..000000000
--- a/buildstream/plugins/sources/git.py
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Copyright (C) 2016 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>
-
-"""
-git - stage files from a git repository
-=======================================
-
-**Host dependencies:**
-
- * git
-
-.. attention::
-
- Note that this plugin **will checkout git submodules by default**; even if
- they are not specified in the `.bst` file.
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the git source kind
- kind: git
-
- # Specify the repository url, using an alias defined
- # in your project configuration is recommended.
- url: upstream:foo.git
-
- # Optionally specify a symbolic tracking branch or tag, this
- # will be used to update the 'ref' when refreshing the pipeline.
- track: master
-
- # Optionally specify the ref format used for tracking.
- # The default is 'sha1' for the raw commit hash.
- # If you specify 'git-describe', the commit hash will be prefixed
- # with the closest tag.
- ref-format: sha1
-
- # Specify the commit ref, this must be specified in order to
- # checkout sources and build, but can be automatically updated
- # if the 'track' attribute was specified.
- ref: d63cbb6fdc0bbdadc4a1b92284826a6d63a7ebcd
-
- # Optionally specify whether submodules should be checked-out.
- # If not set, this will default to 'True'
- checkout-submodules: True
-
- # If your repository has submodules, explicitly specifying the
- # url from which they are to be fetched allows you to easily
- # rebuild the same sources from a different location. This is
- # especially handy when used with project defined aliases which
- # can be redefined at a later time.
- # You may also explicitly specify whether to check out this
- # submodule. If 'checkout' is set, it will override
- # 'checkout-submodules' with the value set below.
- submodules:
- plugins/bar:
- url: upstream:bar.git
- checkout: True
- plugins/baz:
- url: upstream:baz.git
- checkout: False
-
- # Enable tag tracking.
- #
- # This causes the `tags` metadata to be populated automatically
- # as a result of tracking the git source.
- #
- # By default this is 'False'.
- #
- track-tags: True
-
- # If the list of tags below is set, then a lightweight dummy
- # git repository will be staged along with the content at
- # build time.
- #
- # This is useful for a growing number of modules which use
- # `git describe` at build time in order to determine the version
- # which will be encoded into the built software.
- #
- # The 'tags' below is considered as a part of the git source
- # reference and will be stored in the 'project.refs' file if
- # that has been selected as your project's ref-storage.
- #
- # Migration notes:
- #
- # If you are upgrading from BuildStream 1.2, which used to
- # stage the entire repository by default, you will notice that
- # some modules which use `git describe` are broken, and will
- # need to enable this feature in order to fix them.
- #
- # If you need to enable this feature without changing the
- # the specific commit that you are building, then we recommend
- # the following migration steps for any git sources where
- # `git describe` is required:
- #
- # o Enable `track-tags` feature
- # o Set the `track` parameter to the desired commit sha which
- # the current `ref` points to
- # o Run `bst source track` for these elements, this will result in
- # populating the `tags` portion of the refs without changing
- # the refs
- # o Restore the `track` parameter to the branches which you have
- # previously been tracking afterwards.
- #
- tags:
- - tag: lightweight-example
- commit: 04ad0dc656cb7cc6feb781aa13bdbf1d67d0af78
- annotated: false
- - tag: annotated-example
- commit: 10abe77fe8d77385d86f225b503d9185f4ef7f3a
- annotated: true
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-
-**Configurable Warnings:**
-
-This plugin provides the following :ref:`configurable warnings <configurable_warnings>`:
-
-- ``git:inconsistent-submodule`` - A submodule present in the git repository's .gitmodules was never
- added with `git submodule add`.
-
-- ``git:unlisted-submodule`` - A submodule is present in the git repository but was not specified in
- the source configuration and was not disabled for checkout.
-
- .. note::
-
- The ``git:unlisted-submodule`` warning is available since :ref:`format version 20 <project_format_version>`
-
-- ``git:invalid-submodule`` - A submodule is specified in the source configuration but does not exist
- in the repository.
-
- .. note::
-
- The ``git:invalid-submodule`` warning is available since :ref:`format version 20 <project_format_version>`
-
-This plugin also utilises the following configurable :class:`core warnings <buildstream.types.CoreWarnings>`:
-
-- :attr:`ref-not-in-track <buildstream.types.CoreWarnings.REF_NOT_IN_TRACK>` - The provided ref was not
- found in the provided track in the element's git repository.
-"""
-
-from buildstream import _GitSourceBase
-
-
-class GitSource(_GitSourceBase):
- pass
-
-
-# Plugin entry point
-def setup():
- return GitSource
diff --git a/buildstream/plugins/sources/local.py b/buildstream/plugins/sources/local.py
deleted file mode 100644
index 50df85427..000000000
--- a/buildstream/plugins/sources/local.py
+++ /dev/null
@@ -1,147 +0,0 @@
-#
-# Copyright (C) 2018 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>
-# Tiago Gomes <tiago.gomes@codethink.co.uk>
-
-"""
-local - stage local files and directories
-=========================================
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the local source kind
- kind: local
-
- # Specify the project relative path to a file or directory
- path: files/somefile.txt
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-"""
-
-import os
-import stat
-from buildstream import Source, Consistency
-from buildstream import utils
-
-
-class LocalSource(Source):
- # pylint: disable=attribute-defined-outside-init
-
- def __init__(self, context, project, meta):
- super().__init__(context, project, meta)
-
- # Cached unique key to avoid multiple file system traversal if the unique key is requested multiple times.
- self.__unique_key = None
-
- def configure(self, node):
- self.node_validate(node, ['path', *Source.COMMON_CONFIG_KEYS])
- self.path = self.node_get_project_path(node, 'path')
- self.fullpath = os.path.join(self.get_project_directory(), self.path)
-
- def preflight(self):
- pass
-
- def get_unique_key(self):
- if self.__unique_key is None:
- # Get a list of tuples of the the project relative paths and fullpaths
- if os.path.isdir(self.fullpath):
- filelist = utils.list_relative_paths(self.fullpath)
- filelist = [(relpath, os.path.join(self.fullpath, relpath)) for relpath in filelist]
- else:
- filelist = [(self.path, self.fullpath)]
-
- # Return a list of (relative filename, sha256 digest) tuples, a sorted list
- # has already been returned by list_relative_paths()
- self.__unique_key = [(relpath, unique_key(fullpath)) for relpath, fullpath in filelist]
- return self.__unique_key
-
- def get_consistency(self):
- return Consistency.CACHED
-
- # We dont have a ref, we're a local file...
- def load_ref(self, node):
- pass
-
- def get_ref(self):
- return None # pragma: nocover
-
- def set_ref(self, ref, node):
- pass # pragma: nocover
-
- def fetch(self):
- # Nothing to do here for a local source
- pass # pragma: nocover
-
- def stage(self, directory):
-
- # Dont use hardlinks to stage sources, they are not write protected
- # in the sandbox.
- with self.timed_activity("Staging local files at {}".format(self.path)):
-
- if os.path.isdir(self.fullpath):
- files = list(utils.list_relative_paths(self.fullpath))
- utils.copy_files(self.fullpath, directory)
- else:
- destfile = os.path.join(directory, os.path.basename(self.path))
- files = [os.path.basename(self.path)]
- utils.safe_copy(self.fullpath, destfile)
-
- for f in files:
- # Non empty directories are not listed by list_relative_paths
- dirs = f.split(os.sep)
- for i in range(1, len(dirs)):
- d = os.path.join(directory, *(dirs[:i]))
- assert os.path.isdir(d) and not os.path.islink(d)
- os.chmod(d, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
-
- path = os.path.join(directory, f)
- if os.path.islink(path):
- pass
- elif os.path.isdir(path):
- os.chmod(path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
- else:
- st = os.stat(path)
- if st.st_mode & stat.S_IXUSR:
- os.chmod(path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
- else:
- os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
-
- def _get_local_path(self):
- return self.fullpath
-
-
-# Create a unique key for a file
-def unique_key(filename):
-
- # Return some hard coded things for files which
- # have no content to calculate a key for
- if os.path.islink(filename):
- # For a symbolic link, use the link target as its unique identifier
- return os.readlink(filename)
- elif os.path.isdir(filename):
- return "0"
-
- return utils.sha256sum(filename)
-
-
-# Plugin entry point
-def setup():
- return LocalSource
diff --git a/buildstream/plugins/sources/patch.py b/buildstream/plugins/sources/patch.py
deleted file mode 100644
index e42868264..000000000
--- a/buildstream/plugins/sources/patch.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#
-# Copyright Bloomberg Finance LP
-# Copyright (C) 2018 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:
-# Chandan Singh <csingh43@bloomberg.net>
-# Tiago Gomes <tiago.gomes@codethink.co.uk>
-
-"""
-patch - apply locally stored patches
-====================================
-
-**Host dependencies:**
-
- * patch
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the local source kind
- kind: patch
-
- # Specify the project relative path to a patch file
- path: files/somefile.diff
-
- # Optionally specify the strip level, defaults to 1
- strip-level: 1
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-"""
-
-import os
-from buildstream import Source, SourceError, Consistency
-from buildstream import utils
-
-
-class PatchSource(Source):
- # pylint: disable=attribute-defined-outside-init
-
- BST_REQUIRES_PREVIOUS_SOURCES_STAGE = True
-
- def configure(self, node):
- self.path = self.node_get_project_path(node, 'path',
- check_is_file=True)
- self.strip_level = self.node_get_member(node, int, "strip-level", 1)
- self.fullpath = os.path.join(self.get_project_directory(), self.path)
-
- def preflight(self):
- # Check if patch is installed, get the binary at the same time
- self.host_patch = utils.get_host_tool("patch")
-
- def get_unique_key(self):
- return [self.path, utils.sha256sum(self.fullpath), self.strip_level]
-
- def get_consistency(self):
- return Consistency.CACHED
-
- def load_ref(self, node):
- pass
-
- def get_ref(self):
- return None # pragma: nocover
-
- def set_ref(self, ref, node):
- pass # pragma: nocover
-
- def fetch(self):
- # Nothing to do here for a local source
- pass # pragma: nocover
-
- def stage(self, directory):
- with self.timed_activity("Applying local patch: {}".format(self.path)):
-
- # Bail out with a comprehensive message if the target directory is empty
- if not os.listdir(directory):
- raise SourceError("Nothing to patch in directory '{}'".format(directory),
- reason="patch-no-files")
-
- strip_level_option = "-p{}".format(self.strip_level)
- self.call([self.host_patch, strip_level_option, "-i", self.fullpath, "-d", directory],
- fail="Failed to apply patch {}".format(self.path))
-
-
-# Plugin entry point
-def setup():
- return PatchSource
diff --git a/buildstream/plugins/sources/pip.py b/buildstream/plugins/sources/pip.py
deleted file mode 100644
index 9d6c40d74..000000000
--- a/buildstream/plugins/sources/pip.py
+++ /dev/null
@@ -1,254 +0,0 @@
-#
-# Copyright 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:
-# Chandan Singh <csingh43@bloomberg.net>
-
-"""
-pip - stage python packages using pip
-=====================================
-
-**Host depndencies:**
-
- * ``pip`` python module
-
-This plugin will download source distributions for specified packages using
-``pip`` but will not install them. It is expected that the elements using this
-source will install the downloaded packages.
-
-Downloaded tarballs will be stored in a directory called ".bst_pip_downloads".
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the pip source kind
- kind: pip
-
- # Optionally specify index url, defaults to PyPi
- # This url is used to discover new versions of packages and download them
- # Projects intending to mirror their sources to a permanent location should
- # use an aliased url, and declare the alias in the project configuration
- url: https://mypypi.example.com/simple
-
- # Optionally specify the path to requirements files
- # Note that either 'requirements-files' or 'packages' must be defined
- requirements-files:
- - requirements.txt
-
- # Optionally specify a list of additional packages
- # Note that either 'requirements-files' or 'packages' must be defined
- packages:
- - flake8
-
- # Specify the ref. It is a list of strings of format
- # "<package-name>==<version>", separated by "\\n".
- # Usually this will be contents of a requirements.txt file where all
- # package versions have been frozen.
- ref: "flake8==3.5.0\\nmccabe==0.6.1\\npkg-resources==0.0.0\\npycodestyle==2.3.1\\npyflakes==1.6.0"
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-
-.. note::
-
- The ``pip`` plugin is available since :ref:`format version 16 <project_format_version>`
-"""
-
-import hashlib
-import os
-import re
-
-from buildstream import Consistency, Source, SourceError, utils
-
-_OUTPUT_DIRNAME = '.bst_pip_downloads'
-_PYPI_INDEX_URL = 'https://pypi.org/simple/'
-
-# Used only for finding pip command
-_PYTHON_VERSIONS = [
- 'python', # when running in a venv, we might not have the exact version
- 'python2.7',
- 'python3.0',
- 'python3.1',
- 'python3.2',
- 'python3.3',
- 'python3.4',
- 'python3.5',
- 'python3.6',
- 'python3.7',
-]
-
-# List of allowed extensions taken from
-# https://docs.python.org/3/distutils/sourcedist.html.
-# Names of source distribution archives must be of the form
-# '%{package-name}-%{version}.%{extension}'.
-_SDIST_RE = re.compile(
- r'^([\w.-]+?)-((?:[\d.]+){2,})\.(?:tar|tar.bz2|tar.gz|tar.xz|tar.Z|zip)$',
- re.IGNORECASE)
-
-
-class PipSource(Source):
- # pylint: disable=attribute-defined-outside-init
-
- # We need access to previous sources at track time to use requirements.txt
- # but not at fetch time as self.ref should contain sufficient information
- # for this plugin
- BST_REQUIRES_PREVIOUS_SOURCES_TRACK = True
-
- def configure(self, node):
- self.node_validate(node, ['url', 'packages', 'ref', 'requirements-files'] +
- Source.COMMON_CONFIG_KEYS)
- self.ref = self.node_get_member(node, str, 'ref', None)
- self.original_url = self.node_get_member(node, str, 'url', _PYPI_INDEX_URL)
- self.index_url = self.translate_url(self.original_url)
- self.packages = self.node_get_member(node, list, 'packages', [])
- self.requirements_files = self.node_get_member(node, list, 'requirements-files', [])
-
- if not (self.packages or self.requirements_files):
- raise SourceError("{}: Either 'packages' or 'requirements-files' must be specified". format(self))
-
- def preflight(self):
- # Try to find a pip version that supports download command
- self.host_pip = None
- for python in reversed(_PYTHON_VERSIONS):
- try:
- host_python = utils.get_host_tool(python)
- rc = self.call([host_python, '-m', 'pip', 'download', '--help'])
- if rc == 0:
- self.host_pip = [host_python, '-m', 'pip']
- break
- except utils.ProgramNotFoundError:
- pass
-
- if self.host_pip is None:
- raise SourceError("{}: Unable to find a suitable pip command".format(self))
-
- def get_unique_key(self):
- return [self.original_url, self.ref]
-
- def get_consistency(self):
- if not self.ref:
- return Consistency.INCONSISTENT
- if os.path.exists(self._mirror) and os.listdir(self._mirror):
- return Consistency.CACHED
- return Consistency.RESOLVED
-
- def get_ref(self):
- return self.ref
-
- def load_ref(self, node):
- self.ref = self.node_get_member(node, str, 'ref', None)
-
- def set_ref(self, ref, node):
- node['ref'] = self.ref = ref
-
- def track(self, previous_sources_dir):
- # XXX pip does not offer any public API other than the CLI tool so it
- # is not feasible to correctly parse the requirements file or to check
- # which package versions pip is going to install.
- # See https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program
- # for details.
- # As a result, we have to wastefully install the packages during track.
- with self.tempdir() as tmpdir:
- install_args = self.host_pip + ['download',
- '--no-binary', ':all:',
- '--index-url', self.index_url,
- '--dest', tmpdir]
- for requirement_file in self.requirements_files:
- fpath = os.path.join(previous_sources_dir, requirement_file)
- install_args += ['-r', fpath]
- install_args += self.packages
-
- self.call(install_args, fail="Failed to install python packages")
- reqs = self._parse_sdist_names(tmpdir)
-
- return '\n'.join(["{}=={}".format(pkg, ver) for pkg, ver in reqs])
-
- def fetch(self):
- with self.tempdir() as tmpdir:
- packages = self.ref.strip().split('\n')
- package_dir = os.path.join(tmpdir, 'packages')
- os.makedirs(package_dir)
- self.call([*self.host_pip,
- 'download',
- '--no-binary', ':all:',
- '--index-url', self.index_url,
- '--dest', package_dir,
- *packages],
- fail="Failed to install python packages: {}".format(packages))
-
- # If the mirror directory already exists, assume that some other
- # process has fetched the sources before us and ensure that we do
- # not raise an error in that case.
- try:
- utils.move_atomic(package_dir, self._mirror)
- except utils.DirectoryExistsError:
- # Another process has beaten us and has fetched the sources
- # before us.
- pass
- except OSError as e:
- raise SourceError("{}: Failed to move downloaded pip packages from '{}' to '{}': {}"
- .format(self, package_dir, self._mirror, e)) from e
-
- def stage(self, directory):
- with self.timed_activity("Staging Python packages", silent_nested=True):
- utils.copy_files(self._mirror, os.path.join(directory, _OUTPUT_DIRNAME))
-
- # Directory where this source should stage its files
- #
- @property
- def _mirror(self):
- if not self.ref:
- return None
- return os.path.join(self.get_mirror_directory(),
- utils.url_directory_name(self.original_url),
- hashlib.sha256(self.ref.encode()).hexdigest())
-
- # Parse names of downloaded source distributions
- #
- # Args:
- # basedir (str): Directory containing source distribution archives
- #
- # Returns:
- # (list): List of (package_name, version) tuples in sorted order
- #
- def _parse_sdist_names(self, basedir):
- reqs = []
- for f in os.listdir(basedir):
- pkg = _match_package_name(f)
- if pkg is not None:
- reqs.append(pkg)
-
- return sorted(reqs)
-
-
-# Extract the package name and version of a source distribution
-#
-# Args:
-# filename (str): Filename of the source distribution
-#
-# Returns:
-# (tuple): A tuple of (package_name, version)
-#
-def _match_package_name(filename):
- pkg_match = _SDIST_RE.match(filename)
- if pkg_match is None:
- return None
- return pkg_match.groups()
-
-
-def setup():
- return PipSource
diff --git a/buildstream/plugins/sources/remote.py b/buildstream/plugins/sources/remote.py
deleted file mode 100644
index 562a8f226..000000000
--- a/buildstream/plugins/sources/remote.py
+++ /dev/null
@@ -1,93 +0,0 @@
-#
-# Copyright 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:
-# Ed Baunton <ebaunton1@bloomberg.net>
-
-"""
-remote - stage files from remote urls
-=====================================
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the remote source kind
- kind: remote
-
- # Optionally specify a relative staging filename.
- # If not specified, the basename of the url will be used.
- # filename: customfilename
-
- # Optionally specify whether the downloaded file should be
- # marked executable.
- # executable: true
-
- # Specify the url. Using an alias defined in your project
- # configuration is encouraged. 'bst source track' will update the
- # sha256sum in 'ref' to the downloaded file's sha256sum.
- url: upstream:foo
-
- # Specify the ref. It's a sha256sum of the file you download.
- ref: 6c9f6f68a131ec6381da82f2bff978083ed7f4f7991d931bfa767b7965ebc94b
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-
-.. note::
-
- The ``remote`` plugin is available since :ref:`format version 10 <project_format_version>`
-"""
-import os
-from buildstream import SourceError, utils
-from ._downloadablefilesource import DownloadableFileSource
-
-
-class RemoteSource(DownloadableFileSource):
- # pylint: disable=attribute-defined-outside-init
-
- def configure(self, node):
- super().configure(node)
-
- self.filename = self.node_get_member(node, str, 'filename', os.path.basename(self.url))
- self.executable = self.node_get_member(node, bool, 'executable', False)
-
- if os.sep in self.filename:
- raise SourceError('{}: filename parameter cannot contain directories'.format(self),
- reason="filename-contains-directory")
- self.node_validate(node, DownloadableFileSource.COMMON_CONFIG_KEYS + ['filename', 'executable'])
-
- def get_unique_key(self):
- return super().get_unique_key() + [self.filename, self.executable]
-
- def stage(self, directory):
- # Same as in local plugin, don't use hardlinks to stage sources, they
- # are not write protected in the sandbox.
- dest = os.path.join(directory, self.filename)
- with self.timed_activity("Staging remote file to {}".format(dest)):
-
- utils.safe_copy(self._get_mirror_file(), dest)
-
- # To prevent user's umask introducing variability here, explicitly set
- # file modes.
- if self.executable:
- os.chmod(dest, 0o755)
- else:
- os.chmod(dest, 0o644)
-
-
-def setup():
- return RemoteSource
diff --git a/buildstream/plugins/sources/tar.py b/buildstream/plugins/sources/tar.py
deleted file mode 100644
index 31dc17497..000000000
--- a/buildstream/plugins/sources/tar.py
+++ /dev/null
@@ -1,202 +0,0 @@
-#
-# 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>
-
-"""
-tar - stage files from tar archives
-===================================
-
-**Host dependencies:**
-
- * lzip (for .tar.lz files)
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the tar source kind
- kind: tar
-
- # Specify the tar url. Using an alias defined in your project
- # configuration is encouraged. 'bst source track' will update the
- # sha256sum in 'ref' to the downloaded file's sha256sum.
- url: upstream:foo.tar
-
- # Specify the ref. It's a sha256sum of the file you download.
- ref: 6c9f6f68a131ec6381da82f2bff978083ed7f4f7991d931bfa767b7965ebc94b
-
- # Specify a glob pattern to indicate the base directory to extract
- # from the tarball. The first matching directory will be used.
- #
- # Note that this is '*' by default since most standard release
- # tarballs contain a self named subdirectory at the root which
- # contains the files one normally wants to extract to build.
- #
- # To extract the root of the tarball directly, this can be set
- # to an empty string.
- base-dir: '*'
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-"""
-
-import os
-import tarfile
-from contextlib import contextmanager
-from tempfile import TemporaryFile
-
-from buildstream import SourceError
-from buildstream import utils
-
-from ._downloadablefilesource import DownloadableFileSource
-
-
-class TarSource(DownloadableFileSource):
- # pylint: disable=attribute-defined-outside-init
-
- def configure(self, node):
- super().configure(node)
-
- self.base_dir = self.node_get_member(node, str, 'base-dir', '*') or None
-
- self.node_validate(node, DownloadableFileSource.COMMON_CONFIG_KEYS + ['base-dir'])
-
- def preflight(self):
- self.host_lzip = None
- if self.url.endswith('.lz'):
- self.host_lzip = utils.get_host_tool('lzip')
-
- def get_unique_key(self):
- return super().get_unique_key() + [self.base_dir]
-
- @contextmanager
- def _run_lzip(self):
- assert self.host_lzip
- with TemporaryFile() as lzip_stdout:
- with open(self._get_mirror_file(), 'r') as lzip_file:
- self.call([self.host_lzip, '-d'],
- stdin=lzip_file,
- stdout=lzip_stdout)
-
- lzip_stdout.seek(0, 0)
- yield lzip_stdout
-
- @contextmanager
- def _get_tar(self):
- if self.url.endswith('.lz'):
- with self._run_lzip() as lzip_dec:
- with tarfile.open(fileobj=lzip_dec, mode='r:') as tar:
- yield tar
- else:
- with tarfile.open(self._get_mirror_file()) as tar:
- yield tar
-
- def stage(self, directory):
- try:
- with self._get_tar() as tar:
- base_dir = None
- if self.base_dir:
- base_dir = self._find_base_dir(tar, self.base_dir)
-
- if base_dir:
- tar.extractall(path=directory, members=self._extract_members(tar, base_dir))
- else:
- tar.extractall(path=directory)
-
- except (tarfile.TarError, OSError) as e:
- raise SourceError("{}: Error staging source: {}".format(self, e)) from e
-
- # Override and translate which filenames to extract
- def _extract_members(self, tar, base_dir):
- if not base_dir.endswith(os.sep):
- base_dir = base_dir + os.sep
-
- L = len(base_dir)
- for member in tar.getmembers():
-
- # First, ensure that a member never starts with `./`
- if member.path.startswith('./'):
- member.path = member.path[2:]
-
- # Now extract only the paths which match the normalized path
- if member.path.startswith(base_dir):
-
- # If it's got a link name, give it the same treatment, we
- # need the link targets to match up with what we are staging
- #
- # NOTE: Its possible this is not perfect, we may need to
- # consider links which point outside of the chosen
- # base directory.
- #
- if member.type == tarfile.LNKTYPE:
- member.linkname = member.linkname[L:]
-
- member.path = member.path[L:]
- yield member
-
- # We want to iterate over all paths of a tarball, but getmembers()
- # is not enough because some tarballs simply do not contain the leading
- # directory paths for the archived files.
- def _list_tar_paths(self, tar):
-
- visited = set()
- for member in tar.getmembers():
-
- # Remove any possible leading './', offer more consistent behavior
- # across tarballs encoded with or without a leading '.'
- member_name = member.name.lstrip('./')
-
- if not member.isdir():
-
- # Loop over the components of a path, for a path of a/b/c/d
- # we will first visit 'a', then 'a/b' and then 'a/b/c', excluding
- # the final component
- components = member_name.split('/')
- for i in range(len(components) - 1):
- dir_component = '/'.join([components[j] for j in range(i + 1)])
- if dir_component not in visited:
- visited.add(dir_component)
- try:
- # Dont yield directory members which actually do
- # exist in the archive
- _ = tar.getmember(dir_component)
- except KeyError:
- if dir_component != '.':
- yield dir_component
-
- continue
-
- # Avoid considering the '.' directory, if any is included in the archive
- # this is to avoid the default 'base-dir: *' value behaving differently
- # depending on whether the tarball was encoded with a leading '.' or not
- elif member_name == '.':
- continue
-
- yield member_name
-
- def _find_base_dir(self, tar, pattern):
- paths = self._list_tar_paths(tar)
- matches = sorted(list(utils.glob(paths, pattern)))
- if not matches:
- raise SourceError("{}: Could not find base directory matching pattern: {}".format(self, pattern))
-
- return matches[0]
-
-
-def setup():
- return TarSource
diff --git a/buildstream/plugins/sources/zip.py b/buildstream/plugins/sources/zip.py
deleted file mode 100644
index 03efcef79..000000000
--- a/buildstream/plugins/sources/zip.py
+++ /dev/null
@@ -1,181 +0,0 @@
-#
-# Copyright (C) 2017 Mathieu Bridon
-#
-# 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:
-# Mathieu Bridon <bochecha@daitauha.fr>
-
-"""
-zip - stage files from zip archives
-===================================
-
-**Usage:**
-
-.. code:: yaml
-
- # Specify the zip source kind
- kind: zip
-
- # Specify the zip url. Using an alias defined in your project
- # configuration is encouraged. 'bst source track' will update the
- # sha256sum in 'ref' to the downloaded file's sha256sum.
- url: upstream:foo.zip
-
- # Specify the ref. It's a sha256sum of the file you download.
- ref: 6c9f6f68a131ec6381da82f2bff978083ed7f4f7991d931bfa767b7965ebc94b
-
- # Specify a glob pattern to indicate the base directory to extract
- # from the archive. The first matching directory will be used.
- #
- # Note that this is '*' by default since most standard release
- # archives contain a self named subdirectory at the root which
- # contains the files one normally wants to extract to build.
- #
- # To extract the root of the archive directly, this can be set
- # to an empty string.
- base-dir: '*'
-
-See :ref:`built-in functionality doumentation <core_source_builtins>` for
-details on common configuration options for sources.
-
-.. attention::
-
- File permissions are not preserved. All extracted directories have
- permissions 0755 and all extracted files have permissions 0644.
-"""
-
-import os
-import zipfile
-import stat
-
-from buildstream import SourceError
-from buildstream import utils
-
-from ._downloadablefilesource import DownloadableFileSource
-
-
-class ZipSource(DownloadableFileSource):
- # pylint: disable=attribute-defined-outside-init
-
- def configure(self, node):
- super().configure(node)
-
- self.base_dir = self.node_get_member(node, str, 'base-dir', '*') or None
-
- self.node_validate(node, DownloadableFileSource.COMMON_CONFIG_KEYS + ['base-dir'])
-
- def get_unique_key(self):
- return super().get_unique_key() + [self.base_dir]
-
- def stage(self, directory):
- exec_rights = (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) & ~(stat.S_IWGRP | stat.S_IWOTH)
- noexec_rights = exec_rights & ~(stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
-
- try:
- with zipfile.ZipFile(self._get_mirror_file()) as archive:
- base_dir = None
- if self.base_dir:
- base_dir = self._find_base_dir(archive, self.base_dir)
-
- if base_dir:
- members = self._extract_members(archive, base_dir)
- else:
- members = archive.namelist()
-
- for member in members:
- written = archive.extract(member, path=directory)
-
- # zipfile.extract might create missing directories
- rel = os.path.relpath(written, start=directory)
- assert not os.path.isabs(rel)
- rel = os.path.dirname(rel)
- while rel:
- os.chmod(os.path.join(directory, rel), exec_rights)
- rel = os.path.dirname(rel)
-
- if os.path.islink(written):
- pass
- elif os.path.isdir(written):
- os.chmod(written, exec_rights)
- else:
- os.chmod(written, noexec_rights)
-
- except (zipfile.BadZipFile, zipfile.LargeZipFile, OSError) as e:
- raise SourceError("{}: Error staging source: {}".format(self, e)) from e
-
- # Override and translate which filenames to extract
- def _extract_members(self, archive, base_dir):
- if not base_dir.endswith(os.sep):
- base_dir = base_dir + os.sep
-
- L = len(base_dir)
- for member in archive.infolist():
- if member.filename == base_dir:
- continue
-
- if member.filename.startswith(base_dir):
- member.filename = member.filename[L:]
- yield member
-
- # We want to iterate over all paths of an archive, but namelist()
- # is not enough because some archives simply do not contain the leading
- # directory paths for the archived files.
- def _list_archive_paths(self, archive):
-
- visited = {}
- for member in archive.infolist():
-
- # ZipInfo.is_dir() is only available in python >= 3.6, but all
- # it does is check for a trailing '/' in the name
- #
- if not member.filename.endswith('/'):
-
- # Loop over the components of a path, for a path of a/b/c/d
- # we will first visit 'a', then 'a/b' and then 'a/b/c', excluding
- # the final component
- components = member.filename.split('/')
- for i in range(len(components) - 1):
- dir_component = '/'.join([components[j] for j in range(i + 1)])
- if dir_component not in visited:
- visited[dir_component] = True
- try:
- # Dont yield directory members which actually do
- # exist in the archive
- _ = archive.getinfo(dir_component)
- except KeyError:
- if dir_component != '.':
- yield dir_component
-
- continue
-
- # Avoid considering the '.' directory, if any is included in the archive
- # this is to avoid the default 'base-dir: *' value behaving differently
- # depending on whether the archive was encoded with a leading '.' or not
- elif member.filename == '.' or member.filename == './':
- continue
-
- yield member.filename
-
- def _find_base_dir(self, archive, pattern):
- paths = self._list_archive_paths(archive)
- matches = sorted(list(utils.glob(paths, pattern)))
- if not matches:
- raise SourceError("{}: Could not find base directory matching pattern: {}".format(self, pattern))
-
- return matches[0]
-
-
-def setup():
- return ZipSource
diff --git a/buildstream/sandbox/__init__.py b/buildstream/sandbox/__init__.py
deleted file mode 100644
index 5966d194f..000000000
--- a/buildstream/sandbox/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# 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:
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-from .sandbox import Sandbox, SandboxFlags, SandboxCommandError
-from ._sandboxremote import SandboxRemote
-from ._sandboxdummy import SandboxDummy
diff --git a/buildstream/sandbox/_config.py b/buildstream/sandbox/_config.py
deleted file mode 100644
index 457f92b3c..000000000
--- a/buildstream/sandbox/_config.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# Copyright (C) 2018 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:
-# Jim MacArthur <jim.macarthur@codethink.co.uk>
-
-
-# SandboxConfig
-#
-# A container for sandbox configuration data. We want the internals
-# of this to be opaque, hence putting it in its own private file.
-class SandboxConfig():
- def __init__(self, build_uid, build_gid, build_os=None, build_arch=None):
- self.build_uid = build_uid
- self.build_gid = build_gid
- self.build_os = build_os
- self.build_arch = build_arch
-
- # get_unique_key():
- #
- # This returns the SandboxConfig's contribution
- # to an element's cache key.
- #
- # Returns:
- # (dict): A dictionary to add to an element's cache key
- #
- def get_unique_key(self):
-
- # Currently operating system and machine architecture
- # are not configurable and we have no sandbox implementation
- # which can conform to such configurations.
- #
- # However this should be the right place to support
- # such configurations in the future.
- #
- unique_key = {
- 'os': self.build_os,
- 'arch': self.build_arch
- }
-
- # Avoid breaking cache key calculation with
- # the addition of configurabuild build uid/gid
- if self.build_uid != 0:
- unique_key['build-uid'] = self.build_uid
-
- if self.build_gid != 0:
- unique_key['build-gid'] = self.build_gid
-
- return unique_key
diff --git a/buildstream/sandbox/_mount.py b/buildstream/sandbox/_mount.py
deleted file mode 100644
index c0f26c8d7..000000000
--- a/buildstream/sandbox/_mount.py
+++ /dev/null
@@ -1,149 +0,0 @@
-#
-# 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:
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-import os
-from collections import OrderedDict
-from contextlib import contextmanager, ExitStack
-
-from .. import utils
-from .._fuse import SafeHardlinks
-
-
-# Mount()
-#
-# Helper data object representing a single mount point in the mount map
-#
-class Mount():
- def __init__(self, sandbox, mount_point, safe_hardlinks, fuse_mount_options=None):
- # Getting _get_underlying_directory() here is acceptable as
- # we're part of the sandbox code. This will fail if our
- # directory is CAS-based.
- root_directory = sandbox.get_virtual_directory()._get_underlying_directory()
-
- self.mount_point = mount_point
- self.safe_hardlinks = safe_hardlinks
- self._fuse_mount_options = {} if fuse_mount_options is None else fuse_mount_options
-
- # FIXME: When the criteria for mounting something and its parent
- # mount is identical, then there is no need to mount an additional
- # fuse layer (i.e. if the root is read-write and there is a directory
- # marked for staged artifacts directly within the rootfs, they can
- # safely share the same fuse layer).
- #
- # In these cases it would be saner to redirect the sub-mount to
- # a regular mount point within the parent's redirected mount.
- #
- if self.safe_hardlinks:
- scratch_directory = sandbox._get_scratch_directory()
- # Redirected mount
- self.mount_origin = os.path.join(root_directory, mount_point.lstrip(os.sep))
- self.mount_base = os.path.join(scratch_directory, utils.url_directory_name(mount_point))
- self.mount_source = os.path.join(self.mount_base, 'mount')
- self.mount_tempdir = os.path.join(self.mount_base, 'temp')
- os.makedirs(self.mount_origin, exist_ok=True)
- os.makedirs(self.mount_tempdir, exist_ok=True)
- else:
- # No redirection needed
- self.mount_source = os.path.join(root_directory, mount_point.lstrip(os.sep))
-
- external_mount_sources = sandbox._get_mount_sources()
- external_mount_source = external_mount_sources.get(mount_point)
-
- if external_mount_source is None:
- os.makedirs(self.mount_source, exist_ok=True)
- else:
- if os.path.isdir(external_mount_source):
- os.makedirs(self.mount_source, exist_ok=True)
- else:
- # When mounting a regular file, ensure the parent
- # directory exists in the sandbox; and that an empty
- # file is created at the mount location.
- parent_dir = os.path.dirname(self.mount_source.rstrip('/'))
- os.makedirs(parent_dir, exist_ok=True)
- if not os.path.exists(self.mount_source):
- with open(self.mount_source, 'w'):
- pass
-
- @contextmanager
- def mounted(self, sandbox):
- if self.safe_hardlinks:
- mount = SafeHardlinks(self.mount_origin, self.mount_tempdir, self._fuse_mount_options)
- with mount.mounted(self.mount_source):
- yield
- else:
- # Nothing to mount here
- yield
-
-
-# MountMap()
-#
-# Helper object for mapping of the sandbox mountpoints
-#
-# Args:
-# sandbox (Sandbox): The sandbox object
-# root_readonly (bool): Whether the sandbox root is readonly
-#
-class MountMap():
-
- def __init__(self, sandbox, root_readonly, fuse_mount_options=None):
- # We will be doing the mounts in the order in which they were declared.
- self.mounts = OrderedDict()
-
- if fuse_mount_options is None:
- fuse_mount_options = {}
-
- # We want safe hardlinks on rootfs whenever root is not readonly
- self.mounts['/'] = Mount(sandbox, '/', not root_readonly, fuse_mount_options)
-
- for mark in sandbox._get_marked_directories():
- directory = mark['directory']
- artifact = mark['artifact']
-
- # We want safe hardlinks for any non-root directory where
- # artifacts will be staged to
- self.mounts[directory] = Mount(sandbox, directory, artifact, fuse_mount_options)
-
- # get_mount_source()
- #
- # Gets the host directory where the mountpoint in the
- # sandbox should be bind mounted from
- #
- # Args:
- # mountpoint (str): The absolute mountpoint path inside the sandbox
- #
- # Returns:
- # The host path to be mounted at the mount point
- #
- def get_mount_source(self, mountpoint):
- return self.mounts[mountpoint].mount_source
-
- # mounted()
- #
- # A context manager which ensures all the mount sources
- # were mounted with any fuse layers which may have been needed.
- #
- # Args:
- # sandbox (Sandbox): The sandbox
- #
- @contextmanager
- def mounted(self, sandbox):
- with ExitStack() as stack:
- for _, mount in self.mounts.items():
- stack.enter_context(mount.mounted(sandbox))
- yield
diff --git a/buildstream/sandbox/_mounter.py b/buildstream/sandbox/_mounter.py
deleted file mode 100644
index e6054c20d..000000000
--- a/buildstream/sandbox/_mounter.py
+++ /dev/null
@@ -1,147 +0,0 @@
-#
-# 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:
-# Tristan Maat <tristan.maat@codethink.co.uk>
-
-import sys
-from contextlib import contextmanager
-
-from .._exceptions import SandboxError
-from .. import utils, _signals
-
-
-# A class to wrap the `mount` and `umount` system commands
-class Mounter():
- @classmethod
- def _mount(cls, dest, src=None, mount_type=None,
- stdout=sys.stdout, stderr=sys.stderr, options=None,
- flags=None):
-
- argv = [utils.get_host_tool('mount')]
- if mount_type:
- argv.extend(['-t', mount_type])
- if options:
- argv.extend(['-o', options])
- if flags:
- argv.extend(flags)
-
- if src is not None:
- argv += [src]
- argv += [dest]
-
- status, _ = utils._call(
- argv,
- terminate=True,
- stdout=stdout,
- stderr=stderr
- )
-
- if status != 0:
- raise SandboxError('`{}` failed with exit code {}'
- .format(' '.join(argv), status))
-
- return dest
-
- @classmethod
- def _umount(cls, path, stdout=sys.stdout, stderr=sys.stderr):
-
- cmd = [utils.get_host_tool('umount'), '-R', path]
- status, _ = utils._call(
- cmd,
- terminate=True,
- stdout=stdout,
- stderr=stderr
- )
-
- if status != 0:
- raise SandboxError('`{}` failed with exit code {}'
- .format(' '.join(cmd), status))
-
- # mount()
- #
- # A wrapper for the `mount` command. The device is unmounted when
- # the context is left.
- #
- # Args:
- # dest (str) - The directory to mount to
- # src (str) - The directory to mount
- # stdout (file) - stdout
- # stderr (file) - stderr
- # mount_type (str|None) - The mount type (can be omitted or None)
- # kwargs - Arguments to pass to the mount command, such as `ro=True`
- #
- # Yields:
- # (str) The path to the destination
- #
- @classmethod
- @contextmanager
- def mount(cls, dest, src=None, stdout=sys.stdout,
- stderr=sys.stderr, mount_type=None, **kwargs):
-
- def kill_proc():
- cls._umount(dest, stdout, stderr)
-
- options = ','.join([key for key, val in kwargs.items() if val])
-
- path = cls._mount(dest, src, mount_type, stdout=stdout, stderr=stderr, options=options)
- try:
- with _signals.terminator(kill_proc):
- yield path
- finally:
- cls._umount(dest, stdout, stderr)
-
- # bind_mount()
- #
- # Mount a directory to a different location (a hardlink for all
- # intents and purposes). The directory is unmounted when the
- # context is left.
- #
- # Args:
- # dest (str) - The directory to mount to
- # src (str) - The directory to mount
- # stdout (file) - stdout
- # stderr (file) - stderr
- # kwargs - Arguments to pass to the mount command, such as `ro=True`
- #
- # Yields:
- # (str) The path to the destination
- #
- # While this is equivalent to `mount --rbind`, this option may not
- # exist and can be dangerous, requiring careful cleanupIt is
- # recommended to use this function over a manual mount invocation.
- #
- @classmethod
- @contextmanager
- def bind_mount(cls, dest, src=None, stdout=sys.stdout,
- stderr=sys.stderr, **kwargs):
-
- def kill_proc():
- cls._umount(dest, stdout, stderr)
-
- kwargs['rbind'] = True
- options = ','.join([key for key, val in kwargs.items() if val])
-
- path = cls._mount(dest, src, None, stdout, stderr, options)
-
- try:
- with _signals.terminator(kill_proc):
- # Make the rbind a slave to avoid unmounting vital devices in
- # /proc
- cls._mount(dest, flags=['--make-rslave'])
- yield path
- finally:
- cls._umount(dest, stdout, stderr)
diff --git a/buildstream/sandbox/_sandboxbwrap.py b/buildstream/sandbox/_sandboxbwrap.py
deleted file mode 100644
index d2abc33d0..000000000
--- a/buildstream/sandbox/_sandboxbwrap.py
+++ /dev/null
@@ -1,433 +0,0 @@
-#
-# Copyright (C) 2016 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:
-# Andrew Leeming <andrew.leeming@codethink.co.uk>
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-import collections
-import json
-import os
-import sys
-import time
-import errno
-import signal
-import subprocess
-import shutil
-from contextlib import ExitStack, suppress
-from tempfile import TemporaryFile
-
-import psutil
-
-from .._exceptions import SandboxError
-from .. import utils, _signals
-from ._mount import MountMap
-from . import Sandbox, SandboxFlags
-
-
-# SandboxBwrap()
-#
-# Default bubblewrap based sandbox implementation.
-#
-class SandboxBwrap(Sandbox):
-
- # Minimal set of devices for the sandbox
- DEVICES = [
- '/dev/full',
- '/dev/null',
- '/dev/urandom',
- '/dev/random',
- '/dev/zero'
- ]
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.user_ns_available = kwargs['user_ns_available']
- self.die_with_parent_available = kwargs['die_with_parent_available']
- self.json_status_available = kwargs['json_status_available']
- self.linux32 = kwargs['linux32']
-
- def _run(self, command, flags, *, cwd, env):
- stdout, stderr = self._get_output()
-
- # Allowable access to underlying storage as we're part of the sandbox
- root_directory = self.get_virtual_directory()._get_underlying_directory()
-
- if not self._has_command(command[0], env):
- raise SandboxError("Staged artifacts do not provide command "
- "'{}'".format(command[0]),
- reason='missing-command')
-
- # Create the mount map, this will tell us where
- # each mount point needs to be mounted from and to
- mount_map = MountMap(self, flags & SandboxFlags.ROOT_READ_ONLY)
- root_mount_source = mount_map.get_mount_source('/')
-
- # start command with linux32 if needed
- if self.linux32:
- bwrap_command = [utils.get_host_tool('linux32')]
- else:
- bwrap_command = []
-
- # Grab the full path of the bwrap binary
- bwrap_command += [utils.get_host_tool('bwrap')]
-
- for k, v in env.items():
- bwrap_command += ['--setenv', k, v]
- for k in os.environ.keys() - env.keys():
- bwrap_command += ['--unsetenv', k]
-
- # Create a new pid namespace, this also ensures that any subprocesses
- # are cleaned up when the bwrap process exits.
- bwrap_command += ['--unshare-pid']
-
- # Ensure subprocesses are cleaned up when the bwrap parent dies.
- if self.die_with_parent_available:
- bwrap_command += ['--die-with-parent']
-
- # Add in the root filesystem stuff first.
- #
- # The rootfs is mounted as RW initially so that further mounts can be
- # placed on top. If a RO root is required, after all other mounts are
- # complete, root is remounted as RO
- bwrap_command += ["--bind", root_mount_source, "/"]
-
- if not flags & SandboxFlags.NETWORK_ENABLED:
- bwrap_command += ['--unshare-net']
- bwrap_command += ['--unshare-uts', '--hostname', 'buildstream']
- bwrap_command += ['--unshare-ipc']
-
- # Give it a proc and tmpfs
- bwrap_command += [
- '--proc', '/proc',
- '--tmpfs', '/tmp'
- ]
-
- # In interactive mode, we want a complete devpts inside
- # the container, so there is a /dev/console and such. In
- # the regular non-interactive sandbox, we want to hand pick
- # a minimal set of devices to expose to the sandbox.
- #
- if flags & SandboxFlags.INTERACTIVE:
- bwrap_command += ['--dev', '/dev']
- else:
- for device in self.DEVICES:
- bwrap_command += ['--dev-bind', device, device]
-
- # Add bind mounts to any marked directories
- marked_directories = self._get_marked_directories()
- mount_source_overrides = self._get_mount_sources()
- for mark in marked_directories:
- mount_point = mark['directory']
- if mount_point in mount_source_overrides: # pylint: disable=consider-using-get
- mount_source = mount_source_overrides[mount_point]
- else:
- mount_source = mount_map.get_mount_source(mount_point)
-
- # Use --dev-bind for all mounts, this is simply a bind mount which does
- # not restrictive about devices.
- #
- # While it's important for users to be able to mount devices
- # into the sandbox for `bst shell` testing purposes, it is
- # harmless to do in a build environment where the directories
- # we mount just never contain device files.
- #
- bwrap_command += ['--dev-bind', mount_source, mount_point]
-
- if flags & SandboxFlags.ROOT_READ_ONLY:
- bwrap_command += ["--remount-ro", "/"]
-
- if cwd is not None:
- bwrap_command += ['--dir', cwd]
- bwrap_command += ['--chdir', cwd]
-
- # Set UID and GUI
- if self.user_ns_available:
- bwrap_command += ['--unshare-user']
- if not flags & SandboxFlags.INHERIT_UID:
- uid = self._get_config().build_uid
- gid = self._get_config().build_gid
- bwrap_command += ['--uid', str(uid), '--gid', str(gid)]
-
- with ExitStack() as stack:
- pass_fds = ()
- # Improve error reporting with json-status if available
- if self.json_status_available:
- json_status_file = stack.enter_context(TemporaryFile())
- pass_fds = (json_status_file.fileno(),)
- bwrap_command += ['--json-status-fd', str(json_status_file.fileno())]
-
- # Add the command
- bwrap_command += command
-
- # bwrap might create some directories while being suid
- # and may give them to root gid, if it does, we'll want
- # to clean them up after, so record what we already had
- # there just in case so that we can safely cleanup the debris.
- #
- existing_basedirs = {
- directory: os.path.exists(os.path.join(root_directory, directory))
- for directory in ['tmp', 'dev', 'proc']
- }
-
- # Use the MountMap context manager to ensure that any redirected
- # mounts through fuse layers are in context and ready for bwrap
- # to mount them from.
- #
- stack.enter_context(mount_map.mounted(self))
-
- # If we're interactive, we want to inherit our stdin,
- # otherwise redirect to /dev/null, ensuring process
- # disconnected from terminal.
- if flags & SandboxFlags.INTERACTIVE:
- stdin = sys.stdin
- else:
- stdin = stack.enter_context(open(os.devnull, "r"))
-
- # Run bubblewrap !
- exit_code = self.run_bwrap(bwrap_command, stdin, stdout, stderr,
- (flags & SandboxFlags.INTERACTIVE), pass_fds)
-
- # Cleanup things which bwrap might have left behind, while
- # everything is still mounted because bwrap can be creating
- # the devices on the fuse mount, so we should remove it there.
- if not flags & SandboxFlags.INTERACTIVE:
- for device in self.DEVICES:
- device_path = os.path.join(root_mount_source, device.lstrip('/'))
-
- # This will remove the device in a loop, allowing some
- # retries in case the device file leaked by bubblewrap is still busy
- self.try_remove_device(device_path)
-
- # Remove /tmp, this is a bwrap owned thing we want to be sure
- # never ends up in an artifact
- for basedir in ['tmp', 'dev', 'proc']:
-
- # Skip removal of directories which already existed before
- # launching bwrap
- if existing_basedirs[basedir]:
- continue
-
- base_directory = os.path.join(root_mount_source, basedir)
-
- if flags & SandboxFlags.INTERACTIVE:
- # Be more lenient in interactive mode here.
- #
- # In interactive mode; it's possible that the project shell
- # configuration has mounted some things below the base
- # directories, such as /dev/dri, and in this case it's less
- # important to consider cleanup, as we wont be collecting
- # this build result and creating an artifact.
- #
- # Note: Ideally; we should instead fix upstream bubblewrap to
- # cleanup any debris it creates at startup time, and do
- # the same ourselves for any directories we explicitly create.
- #
- shutil.rmtree(base_directory, ignore_errors=True)
- else:
- try:
- os.rmdir(base_directory)
- except FileNotFoundError:
- # ignore this, if bwrap cleaned up properly then it's not a problem.
- #
- # If the directory was not empty on the other hand, then this is clearly
- # a bug, bwrap mounted a tempfs here and when it exits, that better be empty.
- pass
-
- if self.json_status_available:
- json_status_file.seek(0, 0)
- child_exit_code = None
- # The JSON status file's output is a JSON object per line
- # with the keys present identifying the type of message.
- # The only message relevant to us now is the exit-code of the subprocess.
- for line in json_status_file:
- with suppress(json.decoder.JSONDecodeError):
- o = json.loads(line)
- if isinstance(o, collections.abc.Mapping) and 'exit-code' in o:
- child_exit_code = o['exit-code']
- break
- if child_exit_code is None:
- raise SandboxError("`bwrap' terminated during sandbox setup with exitcode {}".format(exit_code),
- reason="bwrap-sandbox-fail")
- exit_code = child_exit_code
-
- self._vdir._mark_changed()
- return exit_code
-
- def run_bwrap(self, argv, stdin, stdout, stderr, interactive, pass_fds):
- # Wrapper around subprocess.Popen() with common settings.
- #
- # This function blocks until the subprocess has terminated.
- #
- # It then returns a tuple of (exit code, stdout output, stderr output).
- # If stdout was not equal to subprocess.PIPE, stdout will be None. Same for
- # stderr.
-
- # Fetch the process actually launched inside the bwrap sandbox, or the
- # intermediat control bwrap processes.
- #
- # NOTE:
- # The main bwrap process itself is setuid root and as such we cannot
- # send it any signals. Since we launch bwrap with --unshare-pid, it's
- # direct child is another bwrap process which retains ownership of the
- # pid namespace. This is the right process to kill when terminating.
- #
- # The grandchild is the binary which we asked bwrap to launch on our
- # behalf, whatever this binary is, it is the right process to use
- # for suspending and resuming. In the case that this is a shell, the
- # shell will be group leader and all build scripts will stop/resume
- # with that shell.
- #
- def get_user_proc(bwrap_pid, grand_child=False):
- bwrap_proc = psutil.Process(bwrap_pid)
- bwrap_children = bwrap_proc.children()
- if bwrap_children:
- if grand_child:
- bwrap_grand_children = bwrap_children[0].children()
- if bwrap_grand_children:
- return bwrap_grand_children[0]
- else:
- return bwrap_children[0]
- return None
-
- def terminate_bwrap():
- if process:
- user_proc = get_user_proc(process.pid)
- if user_proc:
- user_proc.kill()
-
- def suspend_bwrap():
- if process:
- user_proc = get_user_proc(process.pid, grand_child=True)
- if user_proc:
- group_id = os.getpgid(user_proc.pid)
- os.killpg(group_id, signal.SIGSTOP)
-
- def resume_bwrap():
- if process:
- user_proc = get_user_proc(process.pid, grand_child=True)
- if user_proc:
- group_id = os.getpgid(user_proc.pid)
- os.killpg(group_id, signal.SIGCONT)
-
- with ExitStack() as stack:
-
- # We want to launch bwrap in a new session in non-interactive
- # mode so that we handle the SIGTERM and SIGTSTP signals separately
- # from the nested bwrap process, but in interactive mode this
- # causes launched shells to lack job control (we dont really
- # know why that is).
- #
- if interactive:
- new_session = False
- else:
- new_session = True
- stack.enter_context(_signals.suspendable(suspend_bwrap, resume_bwrap))
- stack.enter_context(_signals.terminator(terminate_bwrap))
-
- process = subprocess.Popen(
- argv,
- # The default is to share file descriptors from the parent process
- # to the subprocess, which is rarely good for sandboxing.
- close_fds=True,
- pass_fds=pass_fds,
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- start_new_session=new_session
- )
-
- # Wait for the child process to finish, ensuring that
- # a SIGINT has exactly the effect the user probably
- # expects (i.e. let the child process handle it).
- try:
- while True:
- try:
- _, status = os.waitpid(process.pid, 0)
- # If the process exits due to a signal, we
- # brutally murder it to avoid zombies
- if not os.WIFEXITED(status):
- user_proc = get_user_proc(process.pid)
- if user_proc:
- utils._kill_process_tree(user_proc.pid)
-
- # If we receive a KeyboardInterrupt we continue
- # waiting for the process since we are in the same
- # process group and it should also have received
- # the SIGINT.
- except KeyboardInterrupt:
- continue
-
- break
- # If we can't find the process, it has already died of its
- # own accord, and therefore we don't need to check or kill
- # anything.
- except psutil.NoSuchProcess:
- pass
-
- # Return the exit code - see the documentation for
- # os.WEXITSTATUS to see why this is required.
- if os.WIFEXITED(status):
- exit_code = os.WEXITSTATUS(status)
- else:
- exit_code = -1
-
- if interactive and stdin.isatty():
- # Make this process the foreground process again, otherwise the
- # next read() on stdin will trigger SIGTTIN and stop the process.
- # This is required because the sandboxed process does not have
- # permission to do this on its own (running in separate PID namespace).
- #
- # tcsetpgrp() will trigger SIGTTOU when called from a background
- # process, so ignore it temporarily.
- handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
- os.tcsetpgrp(0, os.getpid())
- signal.signal(signal.SIGTTOU, handler)
-
- return exit_code
-
- def try_remove_device(self, device_path):
-
- # Put some upper limit on the tries here
- max_tries = 1000
- tries = 0
-
- while True:
- try:
- os.unlink(device_path)
- except OSError as e:
- if e.errno == errno.EBUSY:
- # This happens on some machines, seems there is a race sometimes
- # after bubblewrap returns and the device files it bind-mounted did
- # not finish unmounting.
- #
- if tries < max_tries:
- tries += 1
- time.sleep(1 / 100)
- continue
- else:
- # We've reached the upper limit of tries, bail out now
- # because something must have went wrong
- #
- raise
- elif e.errno == errno.ENOENT:
- # Bubblewrap cleaned it up for us, no problem if we cant remove it
- break
- else:
- # Something unexpected, reraise this error
- raise
- else:
- # Successfully removed the symlink
- break
diff --git a/buildstream/sandbox/_sandboxchroot.py b/buildstream/sandbox/_sandboxchroot.py
deleted file mode 100644
index 7266a00e3..000000000
--- a/buildstream/sandbox/_sandboxchroot.py
+++ /dev/null
@@ -1,325 +0,0 @@
-#
-# 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:
-# Tristan Maat <tristan.maat@codethink.co.uk>
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-
-import os
-import sys
-import stat
-import signal
-import subprocess
-from contextlib import contextmanager, ExitStack
-import psutil
-
-from .._exceptions import SandboxError
-from .. import utils
-from .. import _signals
-from ._mounter import Mounter
-from ._mount import MountMap
-from . import Sandbox, SandboxFlags
-
-
-class SandboxChroot(Sandbox):
-
- _FUSE_MOUNT_OPTIONS = {'dev': True}
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- uid = self._get_config().build_uid
- gid = self._get_config().build_gid
- if uid != 0 or gid != 0:
- raise SandboxError("Chroot sandboxes cannot specify a non-root uid/gid "
- "({},{} were supplied via config)".format(uid, gid))
-
- self.mount_map = None
-
- def _run(self, command, flags, *, cwd, env):
-
- if not self._has_command(command[0], env):
- raise SandboxError("Staged artifacts do not provide command "
- "'{}'".format(command[0]),
- reason='missing-command')
-
- stdout, stderr = self._get_output()
-
- # Create the mount map, this will tell us where
- # each mount point needs to be mounted from and to
- self.mount_map = MountMap(self, flags & SandboxFlags.ROOT_READ_ONLY,
- self._FUSE_MOUNT_OPTIONS)
-
- # Create a sysroot and run the command inside it
- with ExitStack() as stack:
- os.makedirs('/var/run/buildstream', exist_ok=True)
-
- # FIXME: While we do not currently do anything to prevent
- # network access, we also don't copy /etc/resolv.conf to
- # the new rootfs.
- #
- # This effectively disables network access, since DNs will
- # never resolve, so anything a normal process wants to do
- # will fail. Malicious processes could gain rights to
- # anything anyway.
- #
- # Nonetheless a better solution could perhaps be found.
-
- rootfs = stack.enter_context(utils._tempdir(dir='/var/run/buildstream'))
- stack.enter_context(self.create_devices(self._root, flags))
- stack.enter_context(self.mount_dirs(rootfs, flags, stdout, stderr))
-
- if flags & SandboxFlags.INTERACTIVE:
- stdin = sys.stdin
- else:
- stdin = stack.enter_context(open(os.devnull, 'r'))
-
- # Ensure the cwd exists
- if cwd is not None:
- workdir = os.path.join(rootfs, cwd.lstrip(os.sep))
- os.makedirs(workdir, exist_ok=True)
- status = self.chroot(rootfs, command, stdin, stdout,
- stderr, cwd, env, flags)
-
- self._vdir._mark_changed()
- return status
-
- # chroot()
- #
- # A helper function to chroot into the rootfs.
- #
- # Args:
- # rootfs (str): The path of the sysroot to chroot into
- # command (list): The command to execute in the chroot env
- # stdin (file): The stdin
- # stdout (file): The stdout
- # stderr (file): The stderr
- # cwd (str): The current working directory
- # env (dict): The environment variables to use while executing the command
- # flags (:class:`SandboxFlags`): The flags to enable on the sandbox
- #
- # Returns:
- # (int): The exit code of the executed command
- #
- def chroot(self, rootfs, command, stdin, stdout, stderr, cwd, env, flags):
- def kill_proc():
- if process:
- # First attempt to gracefully terminate
- proc = psutil.Process(process.pid)
- proc.terminate()
-
- try:
- proc.wait(20)
- except psutil.TimeoutExpired:
- utils._kill_process_tree(process.pid)
-
- def suspend_proc():
- group_id = os.getpgid(process.pid)
- os.killpg(group_id, signal.SIGSTOP)
-
- def resume_proc():
- group_id = os.getpgid(process.pid)
- os.killpg(group_id, signal.SIGCONT)
-
- try:
- with _signals.suspendable(suspend_proc, resume_proc), _signals.terminator(kill_proc):
- process = subprocess.Popen( # pylint: disable=subprocess-popen-preexec-fn
- command,
- close_fds=True,
- cwd=os.path.join(rootfs, cwd.lstrip(os.sep)),
- env=env,
- stdin=stdin,
- stdout=stdout,
- stderr=stderr,
- # If you try to put gtk dialogs here Tristan (either)
- # will personally scald you
- preexec_fn=lambda: (os.chroot(rootfs), os.chdir(cwd)),
- start_new_session=flags & SandboxFlags.INTERACTIVE
- )
-
- # Wait for the child process to finish, ensuring that
- # a SIGINT has exactly the effect the user probably
- # expects (i.e. let the child process handle it).
- try:
- while True:
- try:
- _, status = os.waitpid(process.pid, 0)
- # If the process exits due to a signal, we
- # brutally murder it to avoid zombies
- if not os.WIFEXITED(status):
- utils._kill_process_tree(process.pid)
-
- # Unlike in the bwrap case, here only the main
- # process seems to receive the SIGINT. We pass
- # on the signal to the child and then continue
- # to wait.
- except KeyboardInterrupt:
- process.send_signal(signal.SIGINT)
- continue
-
- break
- # If we can't find the process, it has already died of
- # its own accord, and therefore we don't need to check
- # or kill anything.
- except psutil.NoSuchProcess:
- pass
-
- # Return the exit code - see the documentation for
- # os.WEXITSTATUS to see why this is required.
- if os.WIFEXITED(status):
- code = os.WEXITSTATUS(status)
- else:
- code = -1
-
- except subprocess.SubprocessError as e:
- # Exceptions in preexec_fn are simply reported as
- # 'Exception occurred in preexec_fn', turn these into
- # a more readable message.
- if str(e) == 'Exception occurred in preexec_fn.':
- raise SandboxError('Could not chroot into {} or chdir into {}. '
- 'Ensure you are root and that the relevant directory exists.'
- .format(rootfs, cwd)) from e
- else:
- raise SandboxError('Could not run command {}: {}'.format(command, e)) from e
-
- return code
-
- # create_devices()
- #
- # Create the nodes in /dev/ usually required for builds (null,
- # none, etc.)
- #
- # Args:
- # rootfs (str): The path of the sysroot to prepare
- # flags (:class:`.SandboxFlags`): The sandbox flags
- #
- @contextmanager
- def create_devices(self, rootfs, flags):
-
- devices = []
- # When we are interactive, we'd rather mount /dev due to the
- # sheer number of devices
- if not flags & SandboxFlags.INTERACTIVE:
-
- for device in Sandbox.DEVICES:
- location = os.path.join(rootfs, device.lstrip(os.sep))
- os.makedirs(os.path.dirname(location), exist_ok=True)
- try:
- if os.path.exists(location):
- os.remove(location)
-
- devices.append(self.mknod(device, location))
- except OSError as err:
- if err.errno == 1:
- raise SandboxError("Permission denied while creating device node: {}.".format(err) +
- "BuildStream reqiures root permissions for these setttings.")
- else:
- raise
-
- yield
-
- for device in devices:
- os.remove(device)
-
- # mount_dirs()
- #
- # Mount paths required for the command.
- #
- # Args:
- # rootfs (str): The path of the sysroot to prepare
- # flags (:class:`.SandboxFlags`): The sandbox flags
- # stdout (file): The stdout
- # stderr (file): The stderr
- #
- @contextmanager
- def mount_dirs(self, rootfs, flags, stdout, stderr):
-
- # FIXME: This should probably keep track of potentially
- # already existing files a la _sandboxwrap.py:239
-
- @contextmanager
- def mount_point(point, **kwargs):
- mount_source_overrides = self._get_mount_sources()
- if point in mount_source_overrides: # pylint: disable=consider-using-get
- mount_source = mount_source_overrides[point]
- else:
- mount_source = self.mount_map.get_mount_source(point)
- mount_point = os.path.join(rootfs, point.lstrip(os.sep))
-
- with Mounter.bind_mount(mount_point, src=mount_source, stdout=stdout, stderr=stderr, **kwargs):
- yield
-
- @contextmanager
- def mount_src(src, **kwargs):
- mount_point = os.path.join(rootfs, src.lstrip(os.sep))
- os.makedirs(mount_point, exist_ok=True)
-
- with Mounter.bind_mount(mount_point, src=src, stdout=stdout, stderr=stderr, **kwargs):
- yield
-
- with ExitStack() as stack:
- stack.enter_context(self.mount_map.mounted(self))
-
- stack.enter_context(mount_point('/'))
-
- if flags & SandboxFlags.INTERACTIVE:
- stack.enter_context(mount_src('/dev'))
-
- stack.enter_context(mount_src('/tmp'))
- stack.enter_context(mount_src('/proc'))
-
- for mark in self._get_marked_directories():
- stack.enter_context(mount_point(mark['directory']))
-
- # Remount root RO if necessary
- if flags & flags & SandboxFlags.ROOT_READ_ONLY:
- root_mount = Mounter.mount(rootfs, stdout=stdout, stderr=stderr, remount=True, ro=True, bind=True)
- # Since the exit stack has already registered a mount
- # for this path, we do not need to register another
- # umount call.
- root_mount.__enter__()
-
- yield
-
- # mknod()
- #
- # Create a device node equivalent to the given source node
- #
- # Args:
- # source (str): Path of the device to mimic (e.g. '/dev/null')
- # target (str): Location to create the new device in
- #
- # Returns:
- # target (str): The location of the created node
- #
- def mknod(self, source, target):
- try:
- dev = os.stat(source)
- major = os.major(dev.st_rdev)
- minor = os.minor(dev.st_rdev)
-
- target_dev = os.makedev(major, minor)
-
- os.mknod(target, mode=stat.S_IFCHR | dev.st_mode, device=target_dev)
-
- except PermissionError as e:
- raise SandboxError('Could not create device {}, ensure that you have root permissions: {}')
-
- except OSError as e:
- raise SandboxError('Could not create device {}: {}'
- .format(target, e)) from e
-
- return target
diff --git a/buildstream/sandbox/_sandboxdummy.py b/buildstream/sandbox/_sandboxdummy.py
deleted file mode 100644
index 750ddb05d..000000000
--- a/buildstream/sandbox/_sandboxdummy.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# 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:
-
-from .._exceptions import SandboxError
-from .sandbox import Sandbox
-
-
-class SandboxDummy(Sandbox):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._reason = kwargs.get("dummy_reason", "no reason given")
-
- def _run(self, command, flags, *, cwd, env):
-
- if not self._has_command(command[0], env):
- raise SandboxError("Staged artifacts do not provide command "
- "'{}'".format(command[0]),
- reason='missing-command')
-
- raise SandboxError("This platform does not support local builds: {}".format(self._reason),
- reason="unavailable-local-sandbox")
diff --git a/buildstream/sandbox/_sandboxremote.py b/buildstream/sandbox/_sandboxremote.py
deleted file mode 100644
index 2cb7e2538..000000000
--- a/buildstream/sandbox/_sandboxremote.py
+++ /dev/null
@@ -1,577 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018 Bloomberg 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:
-# Jim MacArthur <jim.macarthur@codethink.co.uk>
-
-import os
-import shlex
-from collections import namedtuple
-from urllib.parse import urlparse
-from functools import partial
-
-import grpc
-
-from .. import utils
-from .._message import Message, MessageType
-from .sandbox import Sandbox, SandboxCommandError, _SandboxBatch
-from ..storage.directory import VirtualDirectoryError
-from ..storage._casbaseddirectory import CasBasedDirectory
-from .. import _signals
-from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2, remote_execution_pb2_grpc
-from .._protos.google.rpc import code_pb2
-from .._exceptions import BstError, SandboxError
-from .. import _yaml
-from .._protos.google.longrunning import operations_pb2, operations_pb2_grpc
-from .._cas import CASRemote, CASRemoteSpec
-
-
-class RemoteExecutionSpec(namedtuple('RemoteExecutionSpec', 'exec_service storage_service action_service')):
- pass
-
-
-# SandboxRemote()
-#
-# This isn't really a sandbox, it's a stub which sends all the sources and build
-# commands to a remote server and retrieves the results from it.
-#
-class SandboxRemote(Sandbox):
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- self._output_files_required = kwargs.get('output_files_required', True)
-
- config = kwargs['specs'] # This should be a RemoteExecutionSpec
- if config is None:
- return
-
- self.storage_url = config.storage_service['url']
- self.exec_url = config.exec_service['url']
-
- exec_certs = {}
- for key in ['client-cert', 'client-key', 'server-cert']:
- if key in config.exec_service:
- with open(config.exec_service[key], 'rb') as f:
- exec_certs[key] = f.read()
-
- self.exec_credentials = grpc.ssl_channel_credentials(
- root_certificates=exec_certs.get('server-cert'),
- private_key=exec_certs.get('client-key'),
- certificate_chain=exec_certs.get('client-cert'))
-
- action_certs = {}
- for key in ['client-cert', 'client-key', 'server-cert']:
- if key in config.action_service:
- with open(config.action_service[key], 'rb') as f:
- action_certs[key] = f.read()
-
- if config.action_service:
- self.action_url = config.action_service['url']
- self.action_instance = config.action_service.get('instance-name', None)
- self.action_credentials = grpc.ssl_channel_credentials(
- root_certificates=action_certs.get('server-cert'),
- private_key=action_certs.get('client-key'),
- certificate_chain=action_certs.get('client-cert'))
- else:
- self.action_url = None
- self.action_instance = None
- self.action_credentials = None
-
- self.exec_instance = config.exec_service.get('instance-name', None)
- self.storage_instance = config.storage_service.get('instance-name', None)
-
- self.storage_remote_spec = CASRemoteSpec(self.storage_url, push=True,
- server_cert=config.storage_service.get('server-cert'),
- client_key=config.storage_service.get('client-key'),
- client_cert=config.storage_service.get('client-cert'),
- instance_name=self.storage_instance)
- self.operation_name = None
-
- def info(self, msg):
- self._get_context().message(Message(None, MessageType.INFO, msg))
-
- @staticmethod
- def specs_from_config_node(config_node, basedir=None):
-
- def require_node(config, keyname):
- val = _yaml.node_get(config, dict, keyname, default_value=None)
- if val is None:
- provenance = _yaml.node_get_provenance(remote_config, key=keyname)
- raise _yaml.LoadError(_yaml.LoadErrorReason.INVALID_DATA,
- "{}: '{}' was not present in the remote "
- "execution configuration (remote-execution). "
- .format(str(provenance), keyname))
- return val
-
- remote_config = _yaml.node_get(config_node, dict, 'remote-execution', default_value=None)
- if remote_config is None:
- return None
-
- service_keys = ['execution-service', 'storage-service', 'action-cache-service']
-
- _yaml.node_validate(remote_config, ['url', *service_keys])
-
- exec_config = require_node(remote_config, 'execution-service')
- storage_config = require_node(remote_config, 'storage-service')
- action_config = _yaml.node_get(remote_config, dict, 'action-cache-service', default_value={})
-
- tls_keys = ['client-key', 'client-cert', 'server-cert']
-
- _yaml.node_validate(exec_config, ['url', 'instance-name', *tls_keys])
- _yaml.node_validate(storage_config, ['url', 'instance-name', *tls_keys])
- if action_config:
- _yaml.node_validate(action_config, ['url', 'instance-name', *tls_keys])
-
- # Maintain some backwards compatibility with older configs, in which
- # 'url' was the only valid key for remote-execution:
- if 'url' in remote_config:
- if 'execution-service' not in remote_config:
- exec_config = _yaml.new_node_from_dict({'url': remote_config['url']})
- else:
- provenance = _yaml.node_get_provenance(remote_config, key='url')
- raise _yaml.LoadError(_yaml.LoadErrorReason.INVALID_DATA,
- "{}: 'url' and 'execution-service' keys were found in the remote "
- "execution configuration (remote-execution). "
- "You can only specify one of these."
- .format(str(provenance)))
-
- service_configs = [exec_config, storage_config, action_config]
-
- def resolve_path(path):
- if basedir and path:
- return os.path.join(basedir, path)
- else:
- return path
-
- for config_key, config in zip(service_keys, service_configs):
- # Either both or none of the TLS client key/cert pair must be specified:
- if ('client-key' in config) != ('client-cert' in config):
- provenance = _yaml.node_get_provenance(remote_config, key=config_key)
- raise _yaml.LoadError(_yaml.LoadErrorReason.INVALID_DATA,
- "{}: TLS client key/cert pair is incomplete. "
- "You must specify both 'client-key' and 'client-cert' "
- "for authenticated HTTPS connections."
- .format(str(provenance)))
-
- for tls_key in tls_keys:
- if tls_key in config:
- _yaml.node_set(config, tls_key, resolve_path(_yaml.node_get(config, str, tls_key)))
-
- return RemoteExecutionSpec(*[_yaml.node_sanitize(conf) for conf in service_configs])
-
- def run_remote_command(self, channel, action_digest):
- # Sends an execution request to the remote execution server.
- #
- # This function blocks until it gets a response from the server.
-
- # Try to create a communication channel to the BuildGrid server.
- stub = remote_execution_pb2_grpc.ExecutionStub(channel)
- request = remote_execution_pb2.ExecuteRequest(instance_name=self.exec_instance,
- action_digest=action_digest,
- skip_cache_lookup=False)
-
- def __run_remote_command(stub, execute_request=None, running_operation=None):
- try:
- last_operation = None
- if execute_request is not None:
- operation_iterator = stub.Execute(execute_request)
- else:
- request = remote_execution_pb2.WaitExecutionRequest(name=running_operation.name)
- operation_iterator = stub.WaitExecution(request)
-
- for operation in operation_iterator:
- if not self.operation_name:
- self.operation_name = operation.name
- if operation.done:
- return operation
- else:
- last_operation = operation
-
- except grpc.RpcError as e:
- status_code = e.code()
- if status_code == grpc.StatusCode.UNAVAILABLE:
- raise SandboxError("Failed contacting remote execution server at {}."
- .format(self.exec_url))
-
- elif status_code in (grpc.StatusCode.INVALID_ARGUMENT,
- grpc.StatusCode.FAILED_PRECONDITION,
- grpc.StatusCode.RESOURCE_EXHAUSTED,
- grpc.StatusCode.INTERNAL,
- grpc.StatusCode.DEADLINE_EXCEEDED):
- raise SandboxError("{} ({}).".format(e.details(), status_code.name))
-
- elif running_operation and status_code == grpc.StatusCode.UNIMPLEMENTED:
- raise SandboxError("Failed trying to recover from connection loss: "
- "server does not support operation status polling recovery.")
-
- return last_operation
-
- # Set up signal handler to trigger cancel_operation on SIGTERM
- operation = None
- with self._get_context().timed_activity("Waiting for the remote build to complete"), \
- _signals.terminator(partial(self.cancel_operation, channel)):
- operation = __run_remote_command(stub, execute_request=request)
- if operation is None:
- return None
- elif operation.done:
- return operation
- while operation is not None and not operation.done:
- operation = __run_remote_command(stub, running_operation=operation)
-
- return operation
-
- def cancel_operation(self, channel):
- # If we don't have the name can't send request.
- if self.operation_name is None:
- return
-
- stub = operations_pb2_grpc.OperationsStub(channel)
- request = operations_pb2.CancelOperationRequest(
- name=str(self.operation_name))
-
- try:
- stub.CancelOperation(request)
- except grpc.RpcError as e:
- if (e.code() == grpc.StatusCode.UNIMPLEMENTED or
- e.code() == grpc.StatusCode.INVALID_ARGUMENT):
- pass
- else:
- raise SandboxError("Failed trying to send CancelOperation request: "
- "{} ({})".format(e.details(), e.code().name))
-
- 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.")
- elif not output_directories:
- error_text = "No output directory was returned from the build server."
- raise SandboxError(error_text)
- elif len(output_directories) > 1:
- error_text = "More than one output directory was returned from the build server: {}."
- raise SandboxError(error_text.format(output_directories))
-
- tree_digest = output_directories[0].tree_digest
- if tree_digest is None or not tree_digest.hash:
- raise SandboxError("Output directory structure had no digest attached.")
-
- context = self._get_context()
- project = self._get_project()
- cascache = context.get_cascache()
- artifactcache = context.artifactcache
- casremote = CASRemote(self.storage_remote_spec)
-
- # Now do a pull to ensure we have the full directory structure.
- dir_digest = cascache.pull_tree(casremote, tree_digest)
- 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(context.artifactcache.cas, digest=dir_digest)
- self._set_virtual_directory(new_dir)
-
- # Fetch the file blobs if needed
- if self._output_files_required or artifactcache.has_push_remotes():
- required_blobs = []
- directories = []
-
- directories.append(self._output_directory)
- if self._build_directory and (self._build_directory_always or failure):
- directories.append(self._build_directory)
-
- for directory in directories:
- try:
- vdir = new_dir.descend(*directory.strip(os.sep).split(os.sep))
- dir_digest = vdir._get_digest()
- required_blobs += cascache.required_blobs_for_directory(dir_digest)
- except VirtualDirectoryError:
- # If the directory does not exist, there is no need to
- # download file blobs.
- pass
-
- local_missing_blobs = cascache.local_missing_blobs(required_blobs)
- if local_missing_blobs:
- if self._output_files_required:
- # Fetch all blobs from Remote Execution CAS server
- blobs_to_fetch = local_missing_blobs
- else:
- # Output files are not required in the local cache,
- # however, artifact push remotes will need them.
- # Only fetch blobs that are missing on one or multiple
- # artifact servers.
- blobs_to_fetch = artifactcache.find_missing_blobs(project, local_missing_blobs)
-
- remote_missing_blobs = cascache.fetch_blobs(casremote, blobs_to_fetch)
- if remote_missing_blobs:
- raise SandboxError("{} output files are missing on the CAS server"
- .format(len(remote_missing_blobs)))
-
- def _run(self, command, flags, *, cwd, env):
- stdout, stderr = self._get_output()
-
- context = self._get_context()
- project = self._get_project()
- cascache = context.get_cascache()
- artifactcache = context.artifactcache
-
- # set up virtual dircetory
- upload_vdir = self.get_virtual_directory()
-
- # Create directories for all marked directories. This emulates
- # some of the behaviour of other sandboxes, which create these
- # to use as mount points.
- for mark in self._get_marked_directories():
- directory = mark['directory']
- # Create each marked directory
- upload_vdir.descend(*directory.split(os.path.sep), create=True)
-
- # Generate action_digest first
- input_root_digest = upload_vdir._get_digest()
- command_proto = self._create_command(command, cwd, env)
- command_digest = utils._message_digest(command_proto.SerializeToString())
- action = remote_execution_pb2.Action(command_digest=command_digest,
- input_root_digest=input_root_digest)
- action_digest = utils._message_digest(action.SerializeToString())
-
- # Next, try to create a communication channel to the BuildGrid server.
- url = urlparse(self.exec_url)
- if not url.port:
- raise SandboxError("You must supply a protocol and port number in the execution-service url, "
- "for example: http://buildservice:50051.")
- if url.scheme == 'http':
- channel = grpc.insecure_channel('{}:{}'.format(url.hostname, url.port))
- elif url.scheme == 'https':
- channel = grpc.secure_channel('{}:{}'.format(url.hostname, url.port), self.exec_credentials)
- else:
- raise SandboxError("Remote execution currently only supports the 'http' protocol "
- "and '{}' was supplied.".format(url.scheme))
-
- # check action cache download and download if there
- action_result = self._check_action_cache(action_digest)
-
- if not action_result:
- casremote = CASRemote(self.storage_remote_spec)
- try:
- casremote.init()
- except grpc.RpcError as e:
- raise SandboxError("Failed to contact remote execution CAS endpoint at {}: {}"
- .format(self.storage_url, e)) from e
-
- # Determine blobs missing on remote
- try:
- missing_blobs = cascache.remote_missing_blobs_for_directory(casremote, input_root_digest)
- except grpc.RpcError as e:
- raise SandboxError("Failed to determine missing blobs: {}".format(e)) from e
-
- # Check if any blobs are also missing locally (partial artifact)
- # and pull them from the artifact cache.
- try:
- local_missing_blobs = cascache.local_missing_blobs(missing_blobs)
- if local_missing_blobs:
- artifactcache.fetch_missing_blobs(project, local_missing_blobs)
- except (grpc.RpcError, BstError) as e:
- raise SandboxError("Failed to pull missing blobs from artifact cache: {}".format(e)) from e
-
- # Now, push the missing blobs to the remote.
- try:
- cascache.send_blobs(casremote, missing_blobs)
- except grpc.RpcError as e:
- raise SandboxError("Failed to push source directory to remote: {}".format(e)) from e
-
- # Push command and action
- try:
- casremote.push_message(command_proto)
- except grpc.RpcError as e:
- raise SandboxError("Failed to push command to remote: {}".format(e))
-
- try:
- casremote.push_message(action)
- except grpc.RpcError as e:
- raise SandboxError("Failed to push action to remote: {}".format(e))
-
- # Now request to execute the action
- operation = self.run_remote_command(channel, action_digest)
- action_result = self._extract_action_result(operation)
-
- # 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 _check_action_cache(self, action_digest):
- # Checks the action cache to see if this artifact has already been built
- #
- # Should return either the action response or None if not found, raise
- # Sandboxerror if other grpc error was raised
- if not self.action_url:
- return None
- url = urlparse(self.action_url)
- if not url.port:
- raise SandboxError("You must supply a protocol and port number in the action-cache-service url, "
- "for example: http://buildservice:50051.")
- if url.scheme == 'http':
- channel = grpc.insecure_channel('{}:{}'.format(url.hostname, url.port))
- elif url.scheme == 'https':
- channel = grpc.secure_channel('{}:{}'.format(url.hostname, url.port), self.action_credentials)
-
- request = remote_execution_pb2.GetActionResultRequest(instance_name=self.action_instance,
- action_digest=action_digest)
- stub = remote_execution_pb2_grpc.ActionCacheStub(channel)
- try:
- result = stub.GetActionResult(request)
- except grpc.RpcError as e:
- if e.code() != grpc.StatusCode.NOT_FOUND:
- raise SandboxError("Failed to query action cache: {} ({})"
- .format(e.code(), e.details()))
- else:
- return None
- else:
- self.info("Action result found in action cache")
- return result
-
- 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)
-
- @staticmethod
- def _extract_action_result(operation):
- if operation is None:
- # Failure of remote execution, usually due to an error in BuildStream
- raise SandboxError("No response returned from server")
-
- assert not operation.HasField('error') and operation.HasField('response')
-
- execution_response = remote_execution_pb2.ExecuteResponse()
- # The response is expected to be an ExecutionResponse message
- assert operation.response.Is(execution_response.DESCRIPTOR)
-
- operation.response.Unpack(execution_response)
-
- if execution_response.status.code != code_pb2.OK:
- # An unexpected error during execution: the remote execution
- # system failed at processing the execution request.
- if execution_response.status.message:
- raise SandboxError(execution_response.status.message)
- else:
- raise SandboxError("Remote server failed at executing the build request.")
-
- return execution_response.result
-
- def _create_batch(self, main_group, flags, *, collect=None):
- return _SandboxRemoteBatch(self, main_group, flags, collect=collect)
-
- def _use_cas_based_directory(self):
- # Always use CasBasedDirectory for remote execution
- return True
-
-
-# _SandboxRemoteBatch()
-#
-# Command batching by shell script generation.
-#
-class _SandboxRemoteBatch(_SandboxBatch):
-
- def __init__(self, sandbox, main_group, flags, *, collect=None):
- super().__init__(sandbox, main_group, flags, collect=collect)
-
- self.script = None
- self.first_command = None
- self.cwd = None
- self.env = None
-
- def execute(self):
- self.script = ""
-
- self.main_group.execute(self)
-
- first = self.first_command
- if first and self.sandbox.run(['sh', '-c', '-e', self.script], self.flags, cwd=first.cwd, env=first.env) != 0:
- raise SandboxCommandError("Command execution failed", collect=self.collect)
-
- def execute_group(self, group):
- group.execute_children(self)
-
- def execute_command(self, command):
- if self.first_command is None:
- # First command in batch
- # Initial working directory and environment of script already matches
- # the command configuration.
- self.first_command = command
- else:
- # Change working directory for this command
- if command.cwd != self.cwd:
- self.script += "mkdir -p {}\n".format(command.cwd)
- self.script += "cd {}\n".format(command.cwd)
-
- # Update environment for this command
- for key in self.env.keys():
- if key not in command.env:
- self.script += "unset {}\n".format(key)
- for key, value in command.env.items():
- if key not in self.env or self.env[key] != value:
- self.script += "export {}={}\n".format(key, shlex.quote(value))
-
- # Keep track of current working directory and environment
- self.cwd = command.cwd
- self.env = command.env
-
- # Actual command execution
- cmdline = ' '.join(shlex.quote(cmd) for cmd in command.command)
- self.script += "(set -ex; {})".format(cmdline)
-
- # Error handling
- label = command.label or cmdline
- quoted_label = shlex.quote("'{}'".format(label))
- self.script += " || (echo Command {} failed with exitcode $? >&2 ; exit 1)\n".format(quoted_label)
-
- def execute_call(self, call):
- raise SandboxError("SandboxRemote does not support callbacks in command batches")
diff --git a/buildstream/sandbox/sandbox.py b/buildstream/sandbox/sandbox.py
deleted file mode 100644
index c96ccb57b..000000000
--- a/buildstream/sandbox/sandbox.py
+++ /dev/null
@@ -1,717 +0,0 @@
-#
-# Copyright (C) 2017 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:
-# Andrew Leeming <andrew.leeming@codethink.co.uk>
-# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
-"""
-Sandbox - The build sandbox
-===========================
-:class:`.Element` plugins which want to interface with the sandbox
-need only understand this interface, while it may be given a different
-sandbox implementation, any sandbox implementation it is given will
-conform to this interface.
-
-See also: :ref:`sandboxing`.
-"""
-
-import os
-import shlex
-import contextlib
-from contextlib import contextmanager
-
-from .._exceptions import ImplError, BstError, SandboxError
-from .._message import Message, MessageType
-from ..storage._filebaseddirectory import FileBasedDirectory
-from ..storage._casbaseddirectory import CasBasedDirectory
-
-
-class SandboxFlags():
- """Flags indicating how the sandbox should be run.
- """
-
- NONE = 0
- """Use default sandbox configuration.
- """
-
- ROOT_READ_ONLY = 0x01
- """The root filesystem is read only.
-
- This is normally true except when running integration commands
- on staged dependencies, where we have to update caches and run
- things such as ldconfig.
- """
-
- NETWORK_ENABLED = 0x02
- """Whether to expose host network.
-
- This should not be set when running builds, but can
- be allowed for running a shell in a sandbox.
- """
-
- INTERACTIVE = 0x04
- """Whether to run the sandbox interactively
-
- This determines if the sandbox should attempt to connect
- the terminal through to the calling process, or detach
- the terminal entirely.
- """
-
- INHERIT_UID = 0x08
- """Whether to use the user id and group id from the host environment
-
- This determines if processes in the sandbox should run with the
- same user id and group id as BuildStream itself. By default,
- processes run with user id and group id 0, protected by a user
- namespace where available.
- """
-
-
-class SandboxCommandError(SandboxError):
- """Raised by :class:`.Sandbox` implementations when a command fails.
-
- Args:
- message (str): The error message to report to the user
- detail (str): The detailed error string
- collect (str): An optional directory containing partial install contents
- """
- def __init__(self, message, *, detail=None, collect=None):
- super().__init__(message, detail=detail, reason='command-failed')
-
- self.collect = collect
-
-
-class Sandbox():
- """Sandbox()
-
- Sandbox programming interface for :class:`.Element` plugins.
- """
-
- # Minimal set of devices for the sandbox
- DEVICES = [
- '/dev/urandom',
- '/dev/random',
- '/dev/zero',
- '/dev/null'
- ]
-
- def __init__(self, context, project, directory, **kwargs):
- self.__context = context
- self.__project = project
- self.__directories = []
- self.__cwd = None
- self.__env = None
- self.__mount_sources = {}
- self.__allow_real_directory = kwargs['allow_real_directory']
- self.__allow_run = True
-
- # Plugin ID for logging
- plugin = kwargs.get('plugin', None)
- if plugin:
- self.__plugin_id = plugin._unique_id
- else:
- self.__plugin_id = None
-
- # Configuration from kwargs common to all subclasses
- self.__config = kwargs['config']
- self.__stdout = kwargs['stdout']
- self.__stderr = kwargs['stderr']
- self.__bare_directory = kwargs['bare_directory']
-
- # Setup the directories. Root and output_directory should be
- # available to subclasses, hence being single-underscore. The
- # others are private to this class.
- # If the directory is bare, it probably doesn't need scratch
- if self.__bare_directory:
- self._root = directory
- self.__scratch = None
- os.makedirs(self._root, exist_ok=True)
- else:
- self._root = os.path.join(directory, 'root')
- self.__scratch = os.path.join(directory, 'scratch')
- for directory_ in [self._root, self.__scratch]:
- os.makedirs(directory_, exist_ok=True)
-
- self._output_directory = None
- self._build_directory = None
- self._build_directory_always = None
- self._vdir = None
- self._usebuildtree = False
-
- # This is set if anyone requests access to the underlying
- # directory via get_directory.
- self._never_cache_vdirs = False
-
- # Pending command batch
- self.__batch = None
-
- def get_directory(self):
- """Fetches the sandbox root directory
-
- The root directory is where artifacts for the base
- runtime environment should be staged. Only works if
- BST_VIRTUAL_DIRECTORY is not set.
-
- Returns:
- (str): The sandbox root directory
-
- """
- if self.__allow_real_directory:
- self._never_cache_vdirs = True
- return self._root
- else:
- raise BstError("You can't use get_directory")
-
- def get_virtual_directory(self):
- """Fetches the sandbox root directory as a virtual Directory.
-
- The root directory is where artifacts for the base
- runtime environment should be staged.
-
- Use caution if you use get_directory and
- get_virtual_directory. If you alter the contents of the
- directory returned by get_directory, all objects returned by
- get_virtual_directory or derived from them are invalid and you
- must call get_virtual_directory again to get a new copy.
-
- Returns:
- (Directory): The sandbox root directory
-
- """
- if self._vdir is None or self._never_cache_vdirs:
- if self._use_cas_based_directory():
- cascache = self.__context.get_cascache()
- self._vdir = CasBasedDirectory(cascache)
- else:
- self._vdir = FileBasedDirectory(self._root)
- return self._vdir
-
- def _set_virtual_directory(self, virtual_directory):
- """ Sets virtual directory. Useful after remote execution
- has rewritten the working directory.
- """
- self._vdir = virtual_directory
-
- def set_environment(self, environment):
- """Sets the environment variables for the sandbox
-
- Args:
- environment (dict): The environment variables to use in the sandbox
- """
- self.__env = environment
-
- def set_work_directory(self, directory):
- """Sets the work directory for commands run in the sandbox
-
- Args:
- directory (str): An absolute path within the sandbox
- """
- self.__cwd = directory
-
- def set_output_directory(self, directory):
- """Sets the output directory - the directory which is preserved
- as an artifact after assembly.
-
- Args:
- directory (str): An absolute path within the sandbox
- """
- self._output_directory = directory
-
- def mark_directory(self, directory, *, artifact=False):
- """Marks a sandbox directory and ensures it will exist
-
- Args:
- directory (str): An absolute path within the sandbox to mark
- artifact (bool): Whether the content staged at this location
- contains artifacts
-
- .. note::
- Any marked directories will be read-write in the sandboxed
- environment, only the root directory is allowed to be readonly.
- """
- self.__directories.append({
- 'directory': directory,
- 'artifact': artifact
- })
-
- def run(self, command, flags, *, cwd=None, env=None, label=None):
- """Run a command in the sandbox.
-
- If this is called outside a batch context, the command is immediately
- executed.
-
- If this is called in a batch context, the command is added to the batch
- for later execution. If the command fails, later commands will not be
- executed. Command flags must match batch flags.
-
- Args:
- command (list): The command to run in the sandboxed environment, as a list
- of strings starting with the binary to run.
- flags (:class:`.SandboxFlags`): The flags for running this command.
- cwd (str): The sandbox relative working directory in which to run the command.
- env (dict): A dictionary of string key, value pairs to set as environment
- variables inside the sandbox environment.
- label (str): An optional label for the command, used for logging. (*Since: 1.4*)
-
- Returns:
- (int|None): The program exit code, or None if running in batch context.
-
- Raises:
- (:class:`.ProgramNotFoundError`): If a host tool which the given sandbox
- implementation requires is not found.
-
- .. note::
-
- The optional *cwd* argument will default to the value set with
- :func:`~buildstream.sandbox.Sandbox.set_work_directory` and this
- function must make sure the directory will be created if it does
- not exist yet, even if a workspace is being used.
- """
-
- if not self.__allow_run:
- raise SandboxError("Sandbox.run() has been disabled")
-
- # Fallback to the sandbox default settings for
- # the cwd and env.
- #
- cwd = self._get_work_directory(cwd=cwd)
- env = self._get_environment(cwd=cwd, env=env)
-
- # Convert single-string argument to a list
- if isinstance(command, str):
- command = [command]
-
- if self.__batch:
- assert flags == self.__batch.flags, \
- "Inconsistent sandbox flags in single command batch"
-
- batch_command = _SandboxBatchCommand(command, cwd=cwd, env=env, label=label)
-
- current_group = self.__batch.current_group
- current_group.append(batch_command)
- return None
- else:
- return self._run(command, flags, cwd=cwd, env=env)
-
- @contextmanager
- def batch(self, flags, *, label=None, collect=None):
- """Context manager for command batching
-
- This provides a batch context that defers execution of commands until
- the end of the context. If a command fails, the batch will be aborted
- and subsequent commands will not be executed.
-
- Command batches may be nested. Execution will start only when the top
- level batch context ends.
-
- Args:
- flags (:class:`.SandboxFlags`): The flags for this command batch.
- label (str): An optional label for the batch group, used for logging.
- collect (str): An optional directory containing partial install contents
- on command failure.
-
- Raises:
- (:class:`.SandboxCommandError`): If a command fails.
-
- *Since: 1.4*
- """
-
- group = _SandboxBatchGroup(label=label)
-
- if self.__batch:
- # Nested batch
- assert flags == self.__batch.flags, \
- "Inconsistent sandbox flags in single command batch"
-
- parent_group = self.__batch.current_group
- parent_group.append(group)
- self.__batch.current_group = group
- try:
- yield
- finally:
- self.__batch.current_group = parent_group
- else:
- # Top-level batch
- batch = self._create_batch(group, flags, collect=collect)
-
- self.__batch = batch
- try:
- yield
- finally:
- self.__batch = None
-
- batch.execute()
-
- #####################################################
- # Abstract Methods for Sandbox implementations #
- #####################################################
-
- # _run()
- #
- # Abstract method for running a single command
- #
- # Args:
- # command (list): The command to run in the sandboxed environment, as a list
- # of strings starting with the binary to run.
- # flags (:class:`.SandboxFlags`): The flags for running this command.
- # cwd (str): The sandbox relative working directory in which to run the command.
- # env (dict): A dictionary of string key, value pairs to set as environment
- # variables inside the sandbox environment.
- #
- # Returns:
- # (int): The program exit code.
- #
- def _run(self, command, flags, *, cwd, env):
- raise ImplError("Sandbox of type '{}' does not implement _run()"
- .format(type(self).__name__))
-
- # _create_batch()
- #
- # Abstract method for creating a batch object. Subclasses can override
- # this method to instantiate a subclass of _SandboxBatch.
- #
- # Args:
- # main_group (:class:`_SandboxBatchGroup`): The top level batch group.
- # flags (:class:`.SandboxFlags`): The flags for commands in this batch.
- # collect (str): An optional directory containing partial install contents
- # on command failure.
- #
- def _create_batch(self, main_group, flags, *, collect=None):
- return _SandboxBatch(self, main_group, flags, collect=collect)
-
- # _use_cas_based_directory()
- #
- # Whether to use CasBasedDirectory as sandbox root. If this returns `False`,
- # FileBasedDirectory will be used.
- #
- # Returns:
- # (bool): Whether to use CasBasedDirectory
- #
- def _use_cas_based_directory(self):
- # Use CasBasedDirectory as sandbox root if neither Sandbox.get_directory()
- # nor Sandbox.run() are required. This allows faster staging.
- if not self.__allow_real_directory and not self.__allow_run:
- return True
-
- return 'BST_CAS_DIRECTORIES' in os.environ
-
- ################################################
- # Private methods #
- ################################################
- # _get_context()
- #
- # Fetches the context BuildStream was launched with.
- #
- # Returns:
- # (Context): The context of this BuildStream invocation
- def _get_context(self):
- return self.__context
-
- # _get_project()
- #
- # Fetches the Project this sandbox was created to build for.
- #
- # Returns:
- # (Project): The project this sandbox was created for.
- def _get_project(self):
- return self.__project
-
- # _get_marked_directories()
- #
- # Fetches the marked directories in the sandbox
- #
- # Returns:
- # (list): A list of directory mark objects.
- #
- # The returned objects are dictionaries with the following attributes:
- # directory: The absolute path within the sandbox
- # artifact: Whether the path will contain artifacts or not
- #
- def _get_marked_directories(self):
- return self.__directories
-
- # _get_mount_source()
- #
- # Fetches the list of mount sources
- #
- # Returns:
- # (dict): A dictionary where keys are mount points and values are the mount sources
- def _get_mount_sources(self):
- return self.__mount_sources
-
- # _set_mount_source()
- #
- # Sets the mount source for a given mountpoint
- #
- # Args:
- # mountpoint (str): The absolute mountpoint path inside the sandbox
- # mount_source (str): the host path to be mounted at the mount point
- def _set_mount_source(self, mountpoint, mount_source):
- self.__mount_sources[mountpoint] = mount_source
-
- # _get_environment()
- #
- # Fetches the environment variables for running commands
- # in the sandbox.
- #
- # Args:
- # cwd (str): The working directory the command has been requested to run in, if any.
- # env (str): The environment the command has been requested to run in, if any.
- #
- # Returns:
- # (str): The sandbox work directory
- def _get_environment(self, *, cwd=None, env=None):
- cwd = self._get_work_directory(cwd=cwd)
- if env is None:
- env = self.__env
-
- # Naive getcwd implementations can break when bind-mounts to different
- # paths on the same filesystem are present. Letting the command know
- # what directory it is in makes it unnecessary to call the faulty
- # getcwd.
- env = dict(env)
- env['PWD'] = cwd
-
- return env
-
- # _get_work_directory()
- #
- # Fetches the working directory for running commands
- # in the sandbox.
- #
- # Args:
- # cwd (str): The working directory the command has been requested to run in, if any.
- #
- # Returns:
- # (str): The sandbox work directory
- def _get_work_directory(self, *, cwd=None):
- return cwd or self.__cwd or '/'
-
- # _get_scratch_directory()
- #
- # Fetches the sandbox scratch directory, this directory can
- # be used by the sandbox implementation to cache things or
- # redirect temporary fuse mounts.
- #
- # The scratch directory is guaranteed to be on the same
- # filesystem as the root directory.
- #
- # Returns:
- # (str): The sandbox scratch directory
- def _get_scratch_directory(self):
- assert not self.__bare_directory, "Scratch is not going to work with bare directories"
- return self.__scratch
-
- # _get_output()
- #
- # Fetches the stdout & stderr
- #
- # Returns:
- # (file): The stdout, or None to inherit
- # (file): The stderr, or None to inherit
- def _get_output(self):
- return (self.__stdout, self.__stderr)
-
- # _get_config()
- #
- # Fetches the sandbox configuration object.
- #
- # Returns:
- # (SandboxConfig): An object containing the configuration
- # data passed in during construction.
- def _get_config(self):
- return self.__config
-
- # _has_command()
- #
- # Tests whether a command exists inside the sandbox
- #
- # Args:
- # command (list): The command to test.
- # env (dict): A dictionary of string key, value pairs to set as environment
- # variables inside the sandbox environment.
- # Returns:
- # (bool): Whether a command exists inside the sandbox.
- def _has_command(self, command, env=None):
- if os.path.isabs(command):
- return os.path.lexists(os.path.join(
- self._root, command.lstrip(os.sep)))
-
- for path in env.get('PATH').split(':'):
- if os.path.lexists(os.path.join(
- self._root, path.lstrip(os.sep), command)):
- return True
-
- return False
-
- # _get_plugin_id()
- #
- # Get the plugin's unique identifier
- #
- def _get_plugin_id(self):
- return self.__plugin_id
-
- # _callback()
- #
- # If this is called outside a batch context, the specified function is
- # invoked immediately.
- #
- # If this is called in a batch context, the function is added to the batch
- # for later invocation.
- #
- # Args:
- # callback (callable): The function to invoke
- #
- def _callback(self, callback):
- if self.__batch:
- batch_call = _SandboxBatchCall(callback)
-
- current_group = self.__batch.current_group
- current_group.append(batch_call)
- else:
- callback()
-
- # _disable_run()
- #
- # Raise exception if `Sandbox.run()` is called. This enables use of
- # CasBasedDirectory for faster staging when command execution is not
- # required.
- #
- def _disable_run(self):
- self.__allow_run = False
-
- # _set_build_directory()
- #
- # Sets the build directory - the directory which may be preserved as
- # buildtree in the artifact.
- #
- # Args:
- # directory (str): An absolute path within the sandbox
- # always (bool): True if the build directory should always be downloaded,
- # False if it should be downloaded only on failure
- #
- def _set_build_directory(self, directory, *, always):
- self._build_directory = directory
- self._build_directory_always = always
-
-
-# _SandboxBatch()
-#
-# A batch of sandbox commands.
-#
-class _SandboxBatch():
-
- def __init__(self, sandbox, main_group, flags, *, collect=None):
- self.sandbox = sandbox
- self.main_group = main_group
- self.current_group = main_group
- self.flags = flags
- self.collect = collect
-
- def execute(self):
- self.main_group.execute(self)
-
- def execute_group(self, group):
- if group.label:
- context = self.sandbox._get_context()
- cm = context.timed_activity(group.label, unique_id=self.sandbox._get_plugin_id())
- else:
- cm = contextlib.suppress()
-
- with cm:
- group.execute_children(self)
-
- def execute_command(self, command):
- if command.label:
- context = self.sandbox._get_context()
- message = Message(self.sandbox._get_plugin_id(), MessageType.STATUS,
- 'Running command', detail=command.label)
- context.message(message)
-
- exitcode = self.sandbox._run(command.command, self.flags, cwd=command.cwd, env=command.env)
- if exitcode != 0:
- cmdline = ' '.join(shlex.quote(cmd) for cmd in command.command)
- label = command.label or cmdline
- raise SandboxCommandError("Command failed with exitcode {}".format(exitcode),
- detail=label, collect=self.collect)
-
- def execute_call(self, call):
- call.callback()
-
-
-# _SandboxBatchItem()
-#
-# An item in a command batch.
-#
-class _SandboxBatchItem():
-
- def __init__(self, *, label=None):
- self.label = label
-
-
-# _SandboxBatchCommand()
-#
-# A command item in a command batch.
-#
-class _SandboxBatchCommand(_SandboxBatchItem):
-
- def __init__(self, command, *, cwd, env, label=None):
- super().__init__(label=label)
-
- self.command = command
- self.cwd = cwd
- self.env = env
-
- def execute(self, batch):
- batch.execute_command(self)
-
-
-# _SandboxBatchGroup()
-#
-# A group in a command batch.
-#
-class _SandboxBatchGroup(_SandboxBatchItem):
-
- def __init__(self, *, label=None):
- super().__init__(label=label)
-
- self.children = []
-
- def append(self, item):
- self.children.append(item)
-
- def execute(self, batch):
- batch.execute_group(self)
-
- def execute_children(self, batch):
- for item in self.children:
- item.execute(batch)
-
-
-# _SandboxBatchCall()
-#
-# A call item in a command batch.
-#
-class _SandboxBatchCall(_SandboxBatchItem):
-
- def __init__(self, callback):
- super().__init__()
-
- self.callback = callback
-
- def execute(self, batch):
- batch.execute_call(self)
diff --git a/buildstream/scriptelement.py b/buildstream/scriptelement.py
deleted file mode 100644
index dfdbb45c0..000000000
--- a/buildstream/scriptelement.py
+++ /dev/null
@@ -1,297 +0,0 @@
-#
-# 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>
-
-"""
-ScriptElement - Abstract class for scripting elements
-=====================================================
-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 .element import Element, ElementError
-from .sandbox import SandboxFlags
-from .types import Scope
-
-
-class ScriptElement(Element):
- __install_root = "/"
- __cwd = "/"
- __root_read_only = False
- __commands = None
- __layout = []
-
- # The compose element's output is its dependencies, so
- # we must rebuild if the dependencies change even when
- # not in strict build plans.
- #
- BST_STRICT_REBUILD = True
-
- # Script artifacts must never have indirect dependencies,
- # so runtime dependencies are forbidden.
- BST_FORBID_RDEPENDS = True
-
- # This element ignores sources, so we should forbid them from being
- # added, to reduce the potential for confusion
- BST_FORBID_SOURCES = True
-
- 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, or None. 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.
-
- .. note::
-
- The order of directories in the layout is significant as they
- will be mounted into the sandbox. It is an error to specify a parent
- directory which will shadow a directory already present in the layout.
-
- .. note::
-
- In the case that no element is specified, a read-write directory will
- be made available at the specified location.
- """
- #
- # Even if this is an empty list by default, make sure that its
- # instance data instead of appending stuff directly onto class data.
- #
- 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.
- for item in self.__layout:
- if item['element']:
- if not self.search(Scope.BUILD, item['element']):
- raise ElementError("{}: '{}' in layout not found in dependencies"
- .format(self, item['element']))
-
- def preflight(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 configure_sandbox(self, sandbox):
-
- # Setup the environment and work directory
- sandbox.set_work_directory(self.__cwd)
-
- # Setup environment
- sandbox.set_environment(self.get_environment())
-
- # Tell the sandbox to mount the install root
- directories = {self.__install_root: False}
-
- # Mark the artifact directories in the layout
- for item in self.__layout:
- destination = item['destination']
- was_artifact = directories.get(destination, False)
- directories[destination] = item['element'] or was_artifact
-
- for directory, artifact in directories.items():
- # Root does not need to be marked as it is always mounted
- # with artifact (unless explicitly marked non-artifact)
- if directory != '/':
- sandbox.mark_directory(directory, artifact=artifact)
-
- def stage(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.name), silent_nested=True):
- build_dep.stage_dependency_artifacts(sandbox, Scope.RUN, path="/")
-
- with sandbox.batch(SandboxFlags.NONE):
- for build_dep in self.dependencies(Scope.BUILD, recurse=False):
- with self.timed_activity("Integrating {}".format(build_dep.name), 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:
-
- # Skip layout members which dont stage an element
- if not item['element']:
- continue
-
- element = self.search(Scope.BUILD, item['element'])
- if item['destination'] == '/':
- with self.timed_activity("Staging {} at /".format(element.name),
- silent_nested=True):
- element.stage_dependency_artifacts(sandbox, Scope.RUN)
- else:
- with self.timed_activity("Staging {} at {}"
- .format(element.name, item['destination']),
- silent_nested=True):
- virtual_dstdir = sandbox.get_virtual_directory()
- virtual_dstdir.descend(*item['destination'].lstrip(os.sep).split(os.sep), create=True)
- element.stage_dependency_artifacts(sandbox, Scope.RUN, path=item['destination'])
-
- with sandbox.batch(SandboxFlags.NONE):
- for item in self.__layout:
-
- # Skip layout members which dont stage an element
- if not item['element']:
- continue
-
- element = self.search(Scope.BUILD, item['element'])
-
- # Integration commands can only be run for elements staged to /
- if item['destination'] == '/':
- with self.timed_activity("Integrating {}".format(element.name),
- silent_nested=True):
- for dep in element.dependencies(Scope.RUN):
- dep.integrate(sandbox)
-
- install_root_path_components = self.__install_root.lstrip(os.sep).split(os.sep)
- sandbox.get_virtual_directory().descend(*install_root_path_components, create=True)
-
- def assemble(self, sandbox):
-
- flags = SandboxFlags.NONE
- if self.__root_read_only:
- flags |= SandboxFlags.ROOT_READ_ONLY
-
- with sandbox.batch(flags, collect=self.__install_root):
- for groupname, commands in self.__commands.items():
- with sandbox.batch(flags, label="Running '{}'".format(groupname)):
- for cmd in commands:
- # Note the -e switch to 'sh' means to exit with an error
- # if any untested command fails.
- sandbox.run(['sh', '-c', '-e', cmd + '\n'],
- flags,
- label=cmd)
-
- # Return where the result can be collected from
- return self.__install_root
-
-
-def setup():
- return ScriptElement
diff --git a/buildstream/source.py b/buildstream/source.py
deleted file mode 100644
index fe94a15d7..000000000
--- a/buildstream/source.py
+++ /dev/null
@@ -1,1274 +0,0 @@
-#
-# Copyright (C) 2016 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>
-"""
-Source - Base source class
-==========================
-
-.. _core_source_builtins:
-
-Built-in functionality
-----------------------
-
-The Source base class provides built in functionality that may be overridden
-by individual plugins.
-
-* Directory
-
- The ``directory`` variable can be set for all sources of a type in project.conf
- or per source within a element.
-
- This sets the location within the build root that the content of the source
- will be loaded in to. If the location does not exist, it will be created.
-
-.. _core_source_abstract_methods:
-
-Abstract Methods
-----------------
-For loading and configuration purposes, Sources must implement the
-:ref:`Plugin base class abstract methods <core_plugin_abstract_methods>`.
-
-.. attention::
-
- In order to ensure that all configuration data is processed at
- load time, it is important that all URLs have been processed during
- :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>`.
-
- Source implementations *must* either call
- :func:`Source.translate_url() <buildstream.source.Source.translate_url>` or
- :func:`Source.mark_download_url() <buildstream.source.Source.mark_download_url>`
- for every URL that has been specified in the configuration during
- :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>`
-
-Sources expose the following abstract methods. Unless explicitly mentioned,
-these methods are mandatory to implement.
-
-* :func:`Source.get_consistency() <buildstream.source.Source.get_consistency>`
-
- Report the sources consistency state.
-
-* :func:`Source.load_ref() <buildstream.source.Source.load_ref>`
-
- Load the ref from a specific YAML node
-
-* :func:`Source.get_ref() <buildstream.source.Source.get_ref>`
-
- Fetch the source ref
-
-* :func:`Source.set_ref() <buildstream.source.Source.set_ref>`
-
- Set a new ref explicitly
-
-* :func:`Source.track() <buildstream.source.Source.track>`
-
- Automatically derive a new ref from a symbolic tracking branch
-
-* :func:`Source.fetch() <buildstream.source.Source.fetch>`
-
- Fetch the actual payload for the currently set ref
-
-* :func:`Source.stage() <buildstream.source.Source.stage>`
-
- Stage the sources for a given ref at a specified location
-
-* :func:`Source.init_workspace() <buildstream.source.Source.init_workspace>`
-
- Stage sources in a local directory for use as a workspace.
-
- **Optional**: If left unimplemented, this will default to calling
- :func:`Source.stage() <buildstream.source.Source.stage>`
-
-* :func:`Source.get_source_fetchers() <buildstream.source.Source.get_source_fetchers>`
-
- Get the objects that are used for fetching.
-
- **Optional**: This only needs to be implemented for sources that need to
- download from multiple URLs while fetching (e.g. a git repo and its
- submodules). For details on how to define a SourceFetcher, see
- :ref:`SourceFetcher <core_source_fetcher>`.
-
-* :func:`Source.validate_cache() <buildstream.source.Source.validate_cache>`
-
- Perform any validations which require the sources to be cached.
-
- **Optional**: This is completely optional and will do nothing if left unimplemented.
-
-Accessing previous sources
---------------------------
-*Since: 1.4*
-
-In the general case, all sources are fetched and tracked independently of one
-another. In situations where a source needs to access previous source(s) in
-order to perform its own track and/or fetch, following attributes can be set to
-request access to previous sources:
-
-* :attr:`~buildstream.source.Source.BST_REQUIRES_PREVIOUS_SOURCES_TRACK`
-
- Indicate that access to previous sources is required during track
-
-* :attr:`~buildstream.source.Source.BST_REQUIRES_PREVIOUS_SOURCES_FETCH`
-
- Indicate that access to previous sources is required during fetch
-
-The intended use of such plugins is to fetch external dependencies of other
-sources, typically using some kind of package manager, such that all the
-dependencies of the original source(s) are available at build time.
-
-When implementing such a plugin, implementors should adhere to the following
-guidelines:
-
-* Implementations must be able to store the obtained artifacts in a
- subdirectory.
-
-* Implementations must be able to deterministically generate a unique ref, such
- that two refs are different if and only if they produce different outputs.
-
-* Implementations must not introduce host contamination.
-
-
-.. _core_source_fetcher:
-
-SourceFetcher - Object for fetching individual URLs
-===================================================
-
-
-Abstract Methods
-----------------
-SourceFetchers expose the following abstract methods. Unless explicitly
-mentioned, these methods are mandatory to implement.
-
-* :func:`SourceFetcher.fetch() <buildstream.source.SourceFetcher.fetch>`
-
- Fetches the URL associated with this SourceFetcher, optionally taking an
- alias override.
-
-Class Reference
----------------
-"""
-
-import os
-from collections.abc import Mapping
-from contextlib import contextmanager
-
-from . import _yaml, utils
-from .plugin import Plugin
-from .types import Consistency
-from ._exceptions import BstError, ImplError, ErrorDomain
-from ._loader.metasource import MetaSource
-from ._projectrefs import ProjectRefStorage
-from ._cachekey import generate_key
-
-
-class SourceError(BstError):
- """This exception should be raised by :class:`.Source` implementations
- to report errors to the user.
-
- Args:
- message (str): The breif error description to report to the user
- detail (str): A possibly multiline, more detailed error message
- reason (str): An optional machine readable reason string, used for test cases
- temporary (bool): An indicator to whether the error may occur if the operation was run again. (*Since: 1.2*)
- """
- def __init__(self, message, *, detail=None, reason=None, temporary=False):
- super().__init__(message, detail=detail, domain=ErrorDomain.SOURCE, reason=reason, temporary=temporary)
-
-
-class SourceFetcher():
- """SourceFetcher()
-
- This interface exists so that a source that downloads from multiple
- places (e.g. a git source with submodules) has a consistent interface for
- fetching and substituting aliases.
-
- *Since: 1.2*
-
- .. attention::
-
- When implementing a SourceFetcher, remember to call
- :func:`Source.mark_download_url() <buildstream.source.Source.mark_download_url>`
- for every URL found in the configuration data at
- :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>` time.
- """
- def __init__(self):
- self.__alias = None
-
- #############################################################
- # Abstract Methods #
- #############################################################
- def fetch(self, alias_override=None, **kwargs):
- """Fetch remote sources and mirror them locally, ensuring at least
- that the specific reference is cached locally.
-
- Args:
- alias_override (str): The alias to use instead of the default one
- defined by the :ref:`aliases <project_source_aliases>` field
- in the project's config.
-
- Raises:
- :class:`.SourceError`
-
- Implementors should raise :class:`.SourceError` if the there is some
- network error or if the source reference could not be matched.
- """
- raise ImplError("SourceFetcher '{}' does not implement fetch()".format(type(self)))
-
- #############################################################
- # Public Methods #
- #############################################################
- def mark_download_url(self, url):
- """Identifies the URL that this SourceFetcher uses to download
-
- This must be called during the fetcher's initialization
-
- Args:
- url (str): The url used to download.
- """
- self.__alias = _extract_alias(url)
-
- #############################################################
- # Private Methods used in BuildStream #
- #############################################################
-
- # Returns the alias used by this fetcher
- def _get_alias(self):
- return self.__alias
-
-
-class Source(Plugin):
- """Source()
-
- Base Source class.
-
- All Sources derive from this class, this interface defines how
- the core will be interacting with Sources.
- """
- __defaults = {} # The defaults from the project
- __defaults_set = False # Flag, in case there are not defaults at all
-
- BST_REQUIRES_PREVIOUS_SOURCES_TRACK = False
- """Whether access to previous sources is required during track
-
- When set to True:
- * all sources listed before this source in the given element will be
- fetched before this source is tracked
- * Source.track() will be called with an additional keyword argument
- `previous_sources_dir` where previous sources will be staged
- * this source can not be the first source for an element
-
- *Since: 1.4*
- """
-
- BST_REQUIRES_PREVIOUS_SOURCES_FETCH = False
- """Whether access to previous sources is required during fetch
-
- When set to True:
- * all sources listed before this source in the given element will be
- fetched before this source is fetched
- * Source.fetch() will be called with an additional keyword argument
- `previous_sources_dir` where previous sources will be staged
- * this source can not be the first source for an element
-
- *Since: 1.4*
- """
-
- BST_REQUIRES_PREVIOUS_SOURCES_STAGE = False
- """Whether access to previous sources is required during cache
-
- When set to True:
- * All sources listed before current source in the given element will be
- staged with the source when it's cached.
- * This source can not be the first source for an element.
-
- *Since: 1.4*
- """
-
- def __init__(self, context, project, meta, *, alias_override=None, unique_id=None):
- provenance = _yaml.node_get_provenance(meta.config)
- super().__init__("{}-{}".format(meta.element_name, meta.element_index),
- context, project, provenance, "source", unique_id=unique_id)
-
- self.__source_cache = context.sourcecache
-
- self.__element_name = meta.element_name # The name of the element owning this source
- self.__element_index = meta.element_index # The index of the source in the owning element's source list
- self.__element_kind = meta.element_kind # The kind of the element owning this source
- self.__directory = meta.directory # Staging relative directory
- self.__consistency = Consistency.INCONSISTENT # Cached consistency state
-
- self.__key = None # Cache key for source
-
- # The alias_override is only set on a re-instantiated Source
- self.__alias_override = alias_override # Tuple of alias and its override to use instead
- self.__expected_alias = None # The primary alias
- self.__marked_urls = set() # Set of marked download URLs
-
- # Collect the composited element configuration and
- # ask the element to configure itself.
- self.__init_defaults(project, meta)
- self.__config = self.__extract_config(meta)
- self.__first_pass = meta.first_pass
-
- self._configure(self.__config)
-
- COMMON_CONFIG_KEYS = ['kind', 'directory']
- """Common source config keys
-
- Source config keys that must not be accessed in configure(), and
- should be checked for using node_validate().
- """
-
- #############################################################
- # Abstract Methods #
- #############################################################
- def get_consistency(self):
- """Report whether the source has a resolved reference
-
- Returns:
- (:class:`.Consistency`): The source consistency
- """
- raise ImplError("Source plugin '{}' does not implement get_consistency()".format(self.get_kind()))
-
- def load_ref(self, node):
- """Loads the *ref* for this Source from the specified *node*.
-
- Args:
- node (dict): The YAML node to load the ref from
-
- .. note::
-
- The *ref* for the Source is expected to be read at
- :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>` time,
- this will only be used for loading refs from alternative locations
- than in the `element.bst` file where the given Source object has
- been declared.
-
- *Since: 1.2*
- """
- raise ImplError("Source plugin '{}' does not implement load_ref()".format(self.get_kind()))
-
- def get_ref(self):
- """Fetch the internal ref, however it is represented
-
- Returns:
- (simple object): The internal source reference, or ``None``
-
- .. note::
-
- The reference is the user provided (or track resolved) value
- the plugin uses to represent a specific input, like a commit
- in a VCS or a tarball's checksum. Usually the reference is a string,
- but the plugin may choose to represent it with a tuple or such.
-
- Implementations *must* return a ``None`` value in the case that
- the ref was not loaded. E.g. a ``(None, None)`` tuple is not acceptable.
- """
- raise ImplError("Source plugin '{}' does not implement get_ref()".format(self.get_kind()))
-
- def set_ref(self, ref, node):
- """Applies the internal ref, however it is represented
-
- Args:
- ref (simple object): The internal source reference to set, or ``None``
- node (dict): The same dictionary which was previously passed
- to :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>`
-
- See :func:`Source.get_ref() <buildstream.source.Source.get_ref>`
- for a discussion on the *ref* parameter.
-
- .. note::
-
- Implementors must support the special ``None`` value here to
- allow clearing any existing ref.
- """
- raise ImplError("Source plugin '{}' does not implement set_ref()".format(self.get_kind()))
-
- def track(self, **kwargs):
- """Resolve a new ref from the plugin's track option
-
- Args:
- previous_sources_dir (str): directory where previous sources are staged.
- Note that this keyword argument is available only when
- :attr:`~buildstream.source.Source.BST_REQUIRES_PREVIOUS_SOURCES_TRACK`
- is set to True.
-
- Returns:
- (simple object): A new internal source reference, or None
-
- If the backend in question supports resolving references from
- a symbolic tracking branch or tag, then this should be implemented
- to perform this task on behalf of :ref:`bst source track <invoking_source_track>`
- commands.
-
- This usually requires fetching new content from a remote origin
- to see if a new ref has appeared for your branch or tag. If the
- backend store allows one to query for a new ref from a symbolic
- tracking data without downloading then that is desirable.
-
- See :func:`Source.get_ref() <buildstream.source.Source.get_ref>`
- for a discussion on the *ref* parameter.
- """
- # Allow a non implementation
- return None
-
- def fetch(self, **kwargs):
- """Fetch remote sources and mirror them locally, ensuring at least
- that the specific reference is cached locally.
-
- Args:
- previous_sources_dir (str): directory where previous sources are staged.
- Note that this keyword argument is available only when
- :attr:`~buildstream.source.Source.BST_REQUIRES_PREVIOUS_SOURCES_FETCH`
- is set to True.
-
- Raises:
- :class:`.SourceError`
-
- Implementors should raise :class:`.SourceError` if the there is some
- network error or if the source reference could not be matched.
- """
- raise ImplError("Source plugin '{}' does not implement fetch()".format(self.get_kind()))
-
- def stage(self, directory):
- """Stage the sources to a directory
-
- Args:
- directory (str): Path to stage the source
-
- Raises:
- :class:`.SourceError`
-
- Implementors should assume that *directory* already exists
- and stage already cached sources to the passed directory.
-
- Implementors should raise :class:`.SourceError` when encountering
- some system error.
- """
- raise ImplError("Source plugin '{}' does not implement stage()".format(self.get_kind()))
-
- def init_workspace(self, directory):
- """Initialises a new workspace
-
- Args:
- directory (str): Path of the workspace to init
-
- Raises:
- :class:`.SourceError`
-
- Default implementation is to call
- :func:`Source.stage() <buildstream.source.Source.stage>`.
-
- Implementors overriding this method should assume that *directory*
- already exists.
-
- Implementors should raise :class:`.SourceError` when encountering
- some system error.
- """
- self.stage(directory)
-
- def get_source_fetchers(self):
- """Get the objects that are used for fetching
-
- If this source doesn't download from multiple URLs,
- returning None and falling back on the default behaviour
- is recommended.
-
- Returns:
- iterable: The Source's SourceFetchers, if any.
-
- .. note::
-
- Implementors can implement this as a generator.
-
- The :func:`SourceFetcher.fetch() <buildstream.source.SourceFetcher.fetch>`
- method will be called on the returned fetchers one by one,
- before consuming the next fetcher in the list.
-
- *Since: 1.2*
- """
- return []
-
- def validate_cache(self):
- """Implement any validations once we know the sources are cached
-
- This is guaranteed to be called only once for a given session
- once the sources are known to be
- :attr:`Consistency.CACHED <buildstream.types.Consistency.CACHED>`,
- if source tracking is enabled in the session for this source,
- then this will only be called if the sources become cached after
- tracking completes.
-
- *Since: 1.4*
- """
-
- #############################################################
- # Public Methods #
- #############################################################
- def get_mirror_directory(self):
- """Fetches the directory where this source should store things
-
- Returns:
- (str): The directory belonging to this source
- """
-
- # Create the directory if it doesnt exist
- context = self._get_context()
- directory = os.path.join(context.sourcedir, self.get_kind())
- os.makedirs(directory, exist_ok=True)
- return directory
-
- def translate_url(self, url, *, alias_override=None, primary=True):
- """Translates the given url which may be specified with an alias
- into a fully qualified url.
-
- Args:
- url (str): A URL, which may be using an alias
- alias_override (str): Optionally, an URI to override the alias with. (*Since: 1.2*)
- primary (bool): Whether this is the primary URL for the source. (*Since: 1.2*)
-
- Returns:
- str: The fully qualified URL, with aliases resolved
- .. note::
-
- This must be called for every URL in the configuration during
- :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>` if
- :func:`Source.mark_download_url() <buildstream.source.Source.mark_download_url>`
- is not called.
- """
- # Ensure that the download URL is also marked
- self.mark_download_url(url, primary=primary)
-
- # Alias overriding can happen explicitly (by command-line) or
- # implicitly (the Source being constructed with an __alias_override).
- if alias_override or self.__alias_override:
- url_alias, url_body = url.split(utils._ALIAS_SEPARATOR, 1)
- if url_alias:
- if alias_override:
- url = alias_override + url_body
- else:
- # Implicit alias overrides may only be done for one
- # specific alias, so that sources that fetch from multiple
- # URLs and use different aliases default to only overriding
- # one alias, rather than getting confused.
- override_alias = self.__alias_override[0]
- override_url = self.__alias_override[1]
- if url_alias == override_alias:
- url = override_url + url_body
- return url
- else:
- project = self._get_project()
- return project.translate_url(url, first_pass=self.__first_pass)
-
- def mark_download_url(self, url, *, primary=True):
- """Identifies the URL that this Source uses to download
-
- Args:
- url (str): The URL used to download
- primary (bool): Whether this is the primary URL for the source
-
- .. note::
-
- This must be called for every URL in the configuration during
- :func:`Plugin.configure() <buildstream.plugin.Plugin.configure>` if
- :func:`Source.translate_url() <buildstream.source.Source.translate_url>`
- is not called.
-
- *Since: 1.2*
- """
- # Only mark the Source level aliases on the main instance, not in
- # a reinstantiated instance in mirroring.
- if not self.__alias_override:
- if primary:
- expected_alias = _extract_alias(url)
-
- assert (self.__expected_alias is None or
- self.__expected_alias == expected_alias), \
- "Primary URL marked twice with different URLs"
-
- self.__expected_alias = expected_alias
-
- # Enforce proper behaviour of plugins by ensuring that all
- # aliased URLs have been marked at Plugin.configure() time.
- #
- if self._get_configuring():
- # Record marked urls while configuring
- #
- self.__marked_urls.add(url)
- else:
- # If an unknown aliased URL is seen after configuring,
- # this is an error.
- #
- # It is still possible that a URL that was not mentioned
- # in the element configuration can be marked, this is
- # the case for git submodules which might be automatically
- # discovered.
- #
- assert (url in self.__marked_urls or not _extract_alias(url)), \
- "URL was not seen at configure time: {}".format(url)
-
- def get_project_directory(self):
- """Fetch the project base directory
-
- This is useful for sources which need to load resources
- stored somewhere inside the project.
-
- Returns:
- str: The project base directory
- """
- project = self._get_project()
- return project.directory
-
- @contextmanager
- def tempdir(self):
- """Context manager for working in a temporary directory
-
- Yields:
- (str): A path to a temporary directory
-
- This should be used by source plugins directly instead of the tempfile
- module. This one will automatically cleanup in case of termination by
- catching the signal before os._exit(). It will also use the 'mirror
- directory' as expected for a source.
- """
- mirrordir = self.get_mirror_directory()
- with utils._tempdir(dir=mirrordir) as tempdir:
- yield tempdir
-
- #############################################################
- # Private Abstract Methods used in BuildStream #
- #############################################################
-
- # Returns the local path to the source
- #
- # If the source is locally available, this method returns the absolute
- # path. Otherwise, the return value is None.
- #
- # This is an optimization for local sources and optional to implement.
- #
- # Returns:
- # (str): The local absolute path, or None
- #
- def _get_local_path(self):
- return None
-
- #############################################################
- # Private Methods used in BuildStream #
- #############################################################
-
- # Wrapper around preflight() method
- #
- def _preflight(self):
- try:
- self.preflight()
- except BstError as e:
- # Prepend provenance to the error
- raise SourceError("{}: {}".format(self, e), reason=e.reason) from e
-
- # Update cached consistency for a source
- #
- # This must be called whenever the state of a source may have changed.
- #
- def _update_state(self):
-
- if self.__consistency < Consistency.CACHED:
-
- # Source consistency interrogations are silent.
- context = self._get_context()
- with context.silence():
- self.__consistency = self.get_consistency() # pylint: disable=assignment-from-no-return
-
- # Give the Source an opportunity to validate the cached
- # sources as soon as the Source becomes Consistency.CACHED.
- if self.__consistency == Consistency.CACHED:
- self.validate_cache()
-
- # Return cached consistency
- #
- def _get_consistency(self):
- return self.__consistency
-
- # Wrapper function around plugin provided fetch method
- #
- # Args:
- # previous_sources (list): List of Sources listed prior to this source
- # fetch_original (bool): whether to fetch full source, or use local CAS
- #
- def _fetch(self, previous_sources):
-
- if self.BST_REQUIRES_PREVIOUS_SOURCES_FETCH:
- self.__ensure_previous_sources(previous_sources)
- with self.tempdir() as staging_directory:
- for src in previous_sources:
- src._stage(staging_directory)
- self.__do_fetch(previous_sources_dir=self.__ensure_directory(staging_directory))
- else:
- self.__do_fetch()
-
- def _cache(self, previous_sources):
- # stage the source into the source cache
- self.__source_cache.commit(self, previous_sources)
-
- # Wrapper for stage() api which gives the source
- # plugin a fully constructed path considering the
- # 'directory' option
- #
- def _stage(self, directory):
- staging_directory = self.__ensure_directory(directory)
-
- self.stage(staging_directory)
-
- # Wrapper for init_workspace()
- def _init_workspace(self, directory):
- directory = self.__ensure_directory(directory)
-
- self.init_workspace(directory)
-
- # _get_unique_key():
- #
- # Wrapper for get_unique_key() api
- #
- # Args:
- # include_source (bool): Whether to include the delegated source key
- #
- def _get_unique_key(self, include_source):
- key = {}
-
- key['directory'] = self.__directory
- if include_source:
- key['unique'] = self.get_unique_key() # pylint: disable=assignment-from-no-return
-
- return key
-
- # _project_refs():
- #
- # Gets the appropriate ProjectRefs object for this source,
- # which depends on whether the owning element is a junction
- #
- # Args:
- # project (Project): The project to check
- #
- def _project_refs(self, project):
- element_kind = self.__element_kind
- if element_kind == 'junction':
- return project.junction_refs
- return project.refs
-
- # _load_ref():
- #
- # Loads the ref for the said source.
- #
- # Raises:
- # (SourceError): If the source does not implement load_ref()
- #
- # Returns:
- # (ref): A redundant ref specified inline for a project.refs using project
- #
- # This is partly a wrapper around `Source.load_ref()`, it will decide
- # where to load the ref from depending on which project the source belongs
- # to and whether that project uses a project.refs file.
- #
- # Note the return value is used to construct a summarized warning in the
- # case that the toplevel project uses project.refs and also lists refs
- # which will be ignored.
- #
- def _load_ref(self):
- context = self._get_context()
- project = self._get_project()
- toplevel = context.get_toplevel_project()
- redundant_ref = None
-
- element_name = self.__element_name
- element_idx = self.__element_index
-
- def do_load_ref(node):
- try:
- self.load_ref(ref_node)
- except ImplError as e:
- raise SourceError("{}: Storing refs in project.refs is not supported by '{}' sources"
- .format(self, self.get_kind()),
- reason="unsupported-load-ref") from e
-
- # If the main project overrides the ref, use the override
- if project is not toplevel and toplevel.ref_storage == ProjectRefStorage.PROJECT_REFS:
- refs = self._project_refs(toplevel)
- ref_node = refs.lookup_ref(project.name, element_name, element_idx)
- if ref_node is not None:
- do_load_ref(ref_node)
-
- # If the project itself uses project.refs, clear the ref which
- # was already loaded via Source.configure(), as this would
- # violate the rule of refs being either in project.refs or in
- # the elements themselves.
- #
- elif project.ref_storage == ProjectRefStorage.PROJECT_REFS:
-
- # First warn if there is a ref already loaded, and reset it
- redundant_ref = self.get_ref() # pylint: disable=assignment-from-no-return
- if redundant_ref is not None:
- self.set_ref(None, {})
-
- # Try to load the ref
- refs = self._project_refs(project)
- ref_node = refs.lookup_ref(project.name, element_name, element_idx)
- if ref_node is not None:
- do_load_ref(ref_node)
-
- return redundant_ref
-
- # _set_ref()
- #
- # Persists the ref for this source. This will decide where to save the
- # ref, or refuse to persist it, depending on active ref-storage project
- # settings.
- #
- # Args:
- # new_ref (smth): The new reference to save
- # save (bool): Whether to write the new reference to file or not
- #
- # Returns:
- # (bool): Whether the ref has changed
- #
- # Raises:
- # (SourceError): In the case we encounter errors saving a file to disk
- #
- def _set_ref(self, new_ref, *, save):
-
- context = self._get_context()
- project = self._get_project()
- toplevel = context.get_toplevel_project()
- toplevel_refs = self._project_refs(toplevel)
- provenance = self._get_provenance()
-
- element_name = self.__element_name
- element_idx = self.__element_index
-
- #
- # Step 1 - Obtain the node
- #
- node = {}
- if toplevel.ref_storage == ProjectRefStorage.PROJECT_REFS:
- node = toplevel_refs.lookup_ref(project.name, element_name, element_idx, write=True)
-
- if project is toplevel and not node:
- node = provenance.node
-
- # Ensure the node is not from a junction
- if not toplevel.ref_storage == ProjectRefStorage.PROJECT_REFS and provenance.project is not toplevel:
- if provenance.project is project:
- self.warn("{}: Not persisting new reference in junctioned project".format(self))
- elif provenance.project is None:
- assert provenance.filename == ""
- assert provenance.shortname == ""
- raise SourceError("{}: Error saving source reference to synthetic node."
- .format(self))
- else:
- raise SourceError("{}: Cannot track source in a fragment from a junction"
- .format(provenance.shortname),
- reason="tracking-junction-fragment")
-
- #
- # Step 2 - Set the ref in memory, and determine changed state
- #
- clean = _yaml.node_sanitize(node, dict_type=dict)
- to_modify = _yaml.node_sanitize(node, dict_type=dict)
-
- current_ref = self.get_ref() # pylint: disable=assignment-from-no-return
-
- # Set the ref regardless of whether it changed, the
- # TrackQueue() will want to update a specific node with
- # the ref, regardless of whether the original has changed.
- self.set_ref(new_ref, to_modify)
-
- if current_ref == new_ref or not save:
- # Note: We do not look for and propagate changes at this point
- # which might result in desync depending if something changes about
- # tracking in the future. For now, this is quite safe.
- return False
-
- actions = {}
- for k, v in clean.items():
- if k not in to_modify:
- actions[k] = 'del'
- else:
- if v != to_modify[k]:
- actions[k] = 'mod'
- for k in to_modify.keys():
- if k not in clean:
- actions[k] = 'add'
-
- def walk_container(container, path):
- # For each step along path, synthesise if we need to.
- # If we're synthesising missing list entries, we know we're
- # doing this for project.refs so synthesise empty dicts for the
- # intervening entries too
- lpath = [step for step in path]
- lpath.append("") # We know the last step will be a string key
- for step, next_step in zip(lpath, lpath[1:]):
- if type(step) is str: # pylint: disable=unidiomatic-typecheck
- # handle dict container
- if step not in container:
- if type(next_step) is str: # pylint: disable=unidiomatic-typecheck
- container[step] = {}
- else:
- container[step] = []
- container = container[step]
- else:
- # handle list container
- if len(container) <= step:
- while len(container) <= step:
- container.append({})
- container = container[step]
- return container
-
- def process_value(action, container, path, key, new_value):
- container = walk_container(container, path)
- if action == 'del':
- del container[key]
- elif action == 'mod':
- container[key] = new_value
- elif action == 'add':
- container[key] = new_value
- else:
- assert False, \
- "BUG: Unknown action: {}".format(action)
-
- roundtrip_cache = {}
- for key, action in actions.items():
- # Obtain the top level node and its file
- if action == 'add':
- provenance = _yaml.node_get_provenance(node)
- else:
- provenance = _yaml.node_get_provenance(node, key=key)
-
- toplevel_node = provenance.toplevel
-
- # Get the path to whatever changed
- if action == 'add':
- path = _yaml.node_find_target(toplevel_node, node)
- else:
- path = _yaml.node_find_target(toplevel_node, node, key=key)
-
- roundtrip_file = roundtrip_cache.get(provenance.filename)
- if not roundtrip_file:
- roundtrip_file = roundtrip_cache[provenance.filename] = _yaml.roundtrip_load(
- provenance.filename,
- allow_missing=True
- )
-
- # Get the value of the round trip file that we need to change
- process_value(action, roundtrip_file, path, key, to_modify.get(key))
-
- #
- # Step 3 - Apply the change in project data
- #
- for filename, data in roundtrip_cache.items():
- # This is our roundtrip dump from the track
- try:
- _yaml.roundtrip_dump(data, filename)
- except OSError as e:
- raise SourceError("{}: Error saving source reference to '{}': {}"
- .format(self, filename, e),
- reason="save-ref-error") from e
-
- return True
-
- # Wrapper for track()
- #
- # Args:
- # previous_sources (list): List of Sources listed prior to this source
- #
- def _track(self, previous_sources):
- if self.BST_REQUIRES_PREVIOUS_SOURCES_TRACK:
- self.__ensure_previous_sources(previous_sources)
- with self.tempdir() as staging_directory:
- for src in previous_sources:
- src._stage(staging_directory)
- new_ref = self.__do_track(previous_sources_dir=self.__ensure_directory(staging_directory))
- else:
- new_ref = self.__do_track()
-
- current_ref = self.get_ref() # pylint: disable=assignment-from-no-return
-
- if new_ref is None:
- # No tracking, keep current ref
- new_ref = current_ref
-
- if current_ref != new_ref:
- self.info("Found new revision: {}".format(new_ref))
-
- # Save ref in local process for subsequent sources
- self._set_ref(new_ref, save=False)
-
- return new_ref
-
- # _requires_previous_sources()
- #
- # If a plugin requires access to previous sources at track or fetch time,
- # then it cannot be the first source of an elemenet.
- #
- # Returns:
- # (bool): Whether this source requires access to previous sources
- #
- def _requires_previous_sources(self):
- return self.BST_REQUIRES_PREVIOUS_SOURCES_TRACK or self.BST_REQUIRES_PREVIOUS_SOURCES_FETCH
-
- # Returns the alias if it's defined in the project
- def _get_alias(self):
- alias = self.__expected_alias
- project = self._get_project()
- if project.get_alias_uri(alias, first_pass=self.__first_pass):
- # The alias must already be defined in the project's aliases
- # otherwise http://foo gets treated like it contains an alias
- return alias
- else:
- return None
-
- def _generate_key(self, previous_sources):
- keys = [self._get_unique_key(True)]
-
- if self.BST_REQUIRES_PREVIOUS_SOURCES_STAGE:
- for previous_source in previous_sources:
- keys.append(previous_source._get_unique_key(True))
-
- self.__key = generate_key(keys)
-
- @property
- def _key(self):
- return self.__key
-
- # Gives a ref path that points to where sources are kept in the CAS
- def _get_source_name(self):
- # @ is used to prevent conflicts with project names
- return "{}/{}/{}".format(
- '@sources',
- self.get_kind(),
- self._key)
-
- def _get_brief_display_key(self):
- context = self._get_context()
- key = self._key
-
- length = min(len(key), context.log_key_length)
- return key[:length]
-
- #############################################################
- # Local Private Methods #
- #############################################################
-
- # __clone_for_uri()
- #
- # Clone the source with an alternative URI setup for the alias
- # which this source uses.
- #
- # This is used for iteration over source mirrors.
- #
- # Args:
- # uri (str): The alternative URI for this source's alias
- #
- # Returns:
- # (Source): A new clone of this Source, with the specified URI
- # as the value of the alias this Source has marked as
- # primary with either mark_download_url() or
- # translate_url().
- #
- def __clone_for_uri(self, uri):
- project = self._get_project()
- context = self._get_context()
- alias = self._get_alias()
- source_kind = type(self)
-
- # Rebuild a MetaSource from the current element
- meta = MetaSource(
- self.__element_name,
- self.__element_index,
- self.__element_kind,
- self.get_kind(),
- self.__config,
- self.__directory,
- )
-
- meta.first_pass = self.__first_pass
-
- clone = source_kind(context, project, meta,
- alias_override=(alias, uri),
- unique_id=self._unique_id)
-
- # Do the necessary post instantiation routines here
- #
- clone._preflight()
- clone._load_ref()
- clone._update_state()
-
- return clone
-
- # Tries to call fetch for every mirror, stopping once it succeeds
- def __do_fetch(self, **kwargs):
- project = self._get_project()
- context = self._get_context()
-
- # Silence the STATUS messages which might happen as a result
- # of checking the source fetchers.
- with context.silence():
- source_fetchers = self.get_source_fetchers()
-
- # Use the source fetchers if they are provided
- #
- if source_fetchers:
-
- # Use a contorted loop here, this is to allow us to
- # silence the messages which can result from consuming
- # the items of source_fetchers, if it happens to be a generator.
- #
- source_fetchers = iter(source_fetchers)
-
- while True:
-
- with context.silence():
- try:
- fetcher = next(source_fetchers)
- except StopIteration:
- # as per PEP479, we are not allowed to let StopIteration
- # thrown from a context manager.
- # Catching it here and breaking instead.
- break
-
- alias = fetcher._get_alias()
- for uri in project.get_alias_uris(alias, first_pass=self.__first_pass):
- try:
- fetcher.fetch(uri)
- # FIXME: Need to consider temporary vs. permanent failures,
- # and how this works with retries.
- except BstError as e:
- last_error = e
- continue
-
- # No error, we're done with this fetcher
- break
-
- else:
- # No break occurred, raise the last detected error
- raise last_error
-
- # Default codepath is to reinstantiate the Source
- #
- else:
- alias = self._get_alias()
- if self.__first_pass:
- mirrors = project.first_pass_config.mirrors
- else:
- mirrors = project.config.mirrors
- if not mirrors or not alias:
- self.fetch(**kwargs)
- return
-
- for uri in project.get_alias_uris(alias, first_pass=self.__first_pass):
- new_source = self.__clone_for_uri(uri)
- try:
- new_source.fetch(**kwargs)
- # FIXME: Need to consider temporary vs. permanent failures,
- # and how this works with retries.
- except BstError as e:
- last_error = e
- continue
-
- # No error, we're done here
- return
-
- # Re raise the last detected error
- raise last_error
-
- # Tries to call track for every mirror, stopping once it succeeds
- def __do_track(self, **kwargs):
- project = self._get_project()
- alias = self._get_alias()
- if self.__first_pass:
- mirrors = project.first_pass_config.mirrors
- else:
- mirrors = project.config.mirrors
- # If there are no mirrors, or no aliases to replace, there's nothing to do here.
- if not mirrors or not alias:
- return self.track(**kwargs)
-
- # NOTE: We are assuming here that tracking only requires substituting the
- # first alias used
- for uri in reversed(project.get_alias_uris(alias, first_pass=self.__first_pass)):
- new_source = self.__clone_for_uri(uri)
- try:
- ref = new_source.track(**kwargs) # pylint: disable=assignment-from-none
- # FIXME: Need to consider temporary vs. permanent failures,
- # and how this works with retries.
- except BstError as e:
- last_error = e
- continue
- return ref
- raise last_error
-
- # Ensures a fully constructed path and returns it
- def __ensure_directory(self, directory):
-
- if self.__directory is not None:
- directory = os.path.join(directory, self.__directory.lstrip(os.sep))
-
- try:
- os.makedirs(directory, exist_ok=True)
- except OSError as e:
- raise SourceError("Failed to create staging directory: {}"
- .format(e),
- reason="ensure-stage-dir-fail") from e
- return directory
-
- @classmethod
- def __init_defaults(cls, project, meta):
- if not cls.__defaults_set:
- if meta.first_pass:
- sources = project.first_pass_config.source_overrides
- else:
- sources = project.source_overrides
- cls.__defaults = _yaml.node_get(sources, Mapping, meta.kind, default_value={})
- cls.__defaults_set = True
-
- # This will resolve the final configuration to be handed
- # off to source.configure()
- #
- @classmethod
- def __extract_config(cls, meta):
- config = _yaml.node_get(cls.__defaults, Mapping, 'config', default_value={})
- config = _yaml.node_copy(config)
-
- _yaml.composite(config, meta.config)
- _yaml.node_final_assertions(config)
-
- return config
-
- # Ensures that previous sources have been tracked and fetched.
- #
- def __ensure_previous_sources(self, previous_sources):
- for index, src in enumerate(previous_sources):
- # BuildStream should track sources in the order they appear so
- # previous sources should never be in an inconsistent state
- assert src.get_consistency() != Consistency.INCONSISTENT
-
- if src.get_consistency() == Consistency.RESOLVED:
- src._fetch(previous_sources[0:index])
-
-
-def _extract_alias(url):
- parts = url.split(utils._ALIAS_SEPARATOR, 1)
- if len(parts) > 1 and not parts[0].lower() in utils._URI_SCHEMES:
- return parts[0]
- else:
- return ""
diff --git a/buildstream/storage/__init__.py b/buildstream/storage/__init__.py
deleted file mode 100644
index 33424ac8d..000000000
--- a/buildstream/storage/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python3
-#
-# 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:
-# Jim MacArthur <jim.macarthur@codethink.co.uk>
-
-from ._filebaseddirectory import FileBasedDirectory
-from .directory import Directory
diff --git a/buildstream/storage/_casbaseddirectory.py b/buildstream/storage/_casbaseddirectory.py
deleted file mode 100644
index 2aff29b98..000000000
--- a/buildstream/storage/_casbaseddirectory.py
+++ /dev/null
@@ -1,622 +0,0 @@
-#
-# Copyright (C) 2018 Bloomberg 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:
-# Jim MacArthur <jim.macarthur@codethink.co.uk>
-
-"""
-CasBasedDirectory
-=========
-
-Implementation of the Directory class which backs onto a Merkle-tree based content
-addressable storage system.
-
-See also: :ref:`sandboxing`.
-"""
-
-import os
-
-from .._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
-from .directory import Directory, VirtualDirectoryError, _FileType
-from ._filebaseddirectory import FileBasedDirectory
-from ..utils import FileListResult, _magic_timestamp
-
-
-class IndexEntry():
- """ Directory entry used in CasBasedDirectory.index """
- def __init__(self, name, entrytype, *, digest=None, target=None, is_executable=False,
- buildstream_object=None, modified=False):
- self.name = name
- self.type = entrytype
- self.digest = digest
- self.target = target
- self.is_executable = is_executable
- self.buildstream_object = buildstream_object
- self.modified = modified
-
- def get_directory(self, parent):
- if not self.buildstream_object:
- self.buildstream_object = CasBasedDirectory(parent.cas_cache, digest=self.digest,
- parent=parent, filename=self.name)
- self.digest = None
-
- return self.buildstream_object
-
- def get_digest(self):
- if self.digest:
- return self.digest
- else:
- return self.buildstream_object._get_digest()
-
-
-class ResolutionException(VirtualDirectoryError):
- """ Superclass of all exceptions that can be raised by
- CasBasedDirectory._resolve. Should not be used outside this module. """
-
-
-class InfiniteSymlinkException(ResolutionException):
- """ Raised when an infinite symlink loop is found. """
-
-
-class AbsoluteSymlinkException(ResolutionException):
- """Raised if we try to follow an absolute symlink (i.e. one whose
- target starts with the path separator) and we have disallowed
- following such symlinks.
- """
-
-
-class UnexpectedFileException(ResolutionException):
- """Raised if we were found a file where a directory or symlink was
- expected, for example we try to resolve a symlink pointing to
- /a/b/c but /a/b is a file.
- """
- def __init__(self, message=""):
- """Allow constructor with no arguments, since this can be raised in
- places where there isn't sufficient information to write the
- message.
- """
- super().__init__(message)
-
-
-# CasBasedDirectory intentionally doesn't call its superclass constuctor,
-# which is meant to be unimplemented.
-# pylint: disable=super-init-not-called
-
-class CasBasedDirectory(Directory):
- """
- CAS-based directories can have two names; one is a 'common name' which has no effect
- on functionality, and the 'filename'. If a CasBasedDirectory has a parent, then 'filename'
- must be the name of an entry in the parent directory's index which points to this object.
- This is used to inform a parent directory that it must update the given hash for this
- object when this object changes.
-
- Typically a top-level CasBasedDirectory will have a common_name and no filename, and
- subdirectories wil have a filename and no common_name. common_name can used to identify
- CasBasedDirectory objects in a log file, since they have no unique position in a file
- system.
- """
-
- # Two constants which define the separators used by the remote execution API.
- _pb2_path_sep = "/"
- _pb2_absolute_path_prefix = "/"
-
- def __init__(self, cas_cache, *, digest=None, parent=None, common_name="untitled", filename=None):
- self.filename = filename
- self.common_name = common_name
- self.cas_cache = cas_cache
- self.__digest = digest
- self.index = {}
- self.parent = parent
- if digest:
- self._populate_index(digest)
-
- def _populate_index(self, digest):
- try:
- pb2_directory = remote_execution_pb2.Directory()
- with open(self.cas_cache.objpath(digest), 'rb') as f:
- pb2_directory.ParseFromString(f.read())
- except FileNotFoundError as e:
- raise VirtualDirectoryError("Directory not found in local cache: {}".format(e)) from e
-
- for entry in pb2_directory.directories:
- self.index[entry.name] = IndexEntry(entry.name, _FileType.DIRECTORY,
- digest=entry.digest)
- for entry in pb2_directory.files:
- self.index[entry.name] = IndexEntry(entry.name, _FileType.REGULAR_FILE,
- digest=entry.digest,
- is_executable=entry.is_executable)
- for entry in pb2_directory.symlinks:
- self.index[entry.name] = IndexEntry(entry.name, _FileType.SYMLINK,
- target=entry.target)
-
- def _find_self_in_parent(self):
- assert self.parent is not None
- parent = self.parent
- for (k, v) in parent.index.items():
- if v.buildstream_object == self:
- return k
- return None
-
- def _add_directory(self, name):
- assert name not in self.index
-
- newdir = CasBasedDirectory(self.cas_cache, parent=self, filename=name)
-
- self.index[name] = IndexEntry(name, _FileType.DIRECTORY, buildstream_object=newdir)
-
- self.__invalidate_digest()
-
- return newdir
-
- def _add_file(self, basename, filename, modified=False):
- entry = IndexEntry(filename, _FileType.REGULAR_FILE,
- modified=modified or filename in self.index)
- path = os.path.join(basename, filename)
- entry.digest = self.cas_cache.add_object(path=path)
- entry.is_executable = os.access(path, os.X_OK)
- self.index[filename] = entry
-
- self.__invalidate_digest()
-
- def _copy_link_from_filesystem(self, basename, filename):
- self._add_new_link_direct(filename, os.readlink(os.path.join(basename, filename)))
-
- def _add_new_link_direct(self, name, target):
- self.index[name] = IndexEntry(name, _FileType.SYMLINK, target=target, modified=name in self.index)
-
- self.__invalidate_digest()
-
- def delete_entry(self, name):
- if name in self.index:
- del self.index[name]
-
- self.__invalidate_digest()
-
- def descend(self, *paths, create=False):
- """Descend one or more levels of directory hierarchy and return a new
- Directory object for that directory.
-
- Arguments:
- * *paths (str): A list of strings which are all directory names.
- * create (boolean): If this is true, the directories will be created if
- they don't already exist.
-
- Note: At the moment, creating a directory by descending does
- not update this object in the CAS cache. However, performing
- an import_files() into a subdirectory of any depth obtained by
- descending from this object *will* cause this directory to be
- updated and stored.
-
- """
-
- current_dir = self
-
- for path in paths:
- # Skip empty path segments
- if not path:
- continue
-
- entry = current_dir.index.get(path)
- if entry:
- if entry.type == _FileType.DIRECTORY:
- current_dir = entry.get_directory(current_dir)
- else:
- error = "Cannot descend into {}, which is a '{}' in the directory {}"
- raise VirtualDirectoryError(error.format(path,
- current_dir.index[path].type,
- current_dir))
- else:
- if create:
- current_dir = current_dir._add_directory(path)
- else:
- error = "'{}' not found in {}"
- raise VirtualDirectoryError(error.format(path, str(current_dir)))
-
- return current_dir
-
- def _check_replacement(self, name, relative_pathname, fileListResult):
- """ Checks whether 'name' exists, and if so, whether we can overwrite it.
- If we can, add the name to 'overwritten_files' and delete the existing entry.
- Returns 'True' if the import should go ahead.
- fileListResult.overwritten and fileListResult.ignore are updated depending
- on the result. """
- existing_entry = self.index.get(name)
- if existing_entry is None:
- return True
- elif existing_entry.type == _FileType.DIRECTORY:
- # If 'name' maps to a DirectoryNode, then there must be an entry in index
- # pointing to another Directory.
- subdir = existing_entry.get_directory(self)
- if subdir.is_empty():
- self.delete_entry(name)
- fileListResult.overwritten.append(relative_pathname)
- return True
- else:
- # We can't overwrite a non-empty directory, so we just ignore it.
- fileListResult.ignored.append(relative_pathname)
- return False
- else:
- self.delete_entry(name)
- fileListResult.overwritten.append(relative_pathname)
- return True
-
- def _import_files_from_directory(self, source_directory, filter_callback, *, path_prefix="", result):
- """ Import files from a traditional directory. """
-
- for direntry in os.scandir(source_directory):
- # The destination filename, relative to the root where the import started
- relative_pathname = os.path.join(path_prefix, direntry.name)
-
- is_dir = direntry.is_dir(follow_symlinks=False)
-
- if is_dir:
- src_subdir = os.path.join(source_directory, direntry.name)
-
- try:
- create_subdir = direntry.name not in self.index
- dest_subdir = self.descend(direntry.name, create=create_subdir)
- except VirtualDirectoryError:
- filetype = self.index[direntry.name].type
- raise VirtualDirectoryError('Destination is a {}, not a directory: /{}'
- .format(filetype, relative_pathname))
-
- dest_subdir._import_files_from_directory(src_subdir, filter_callback,
- path_prefix=relative_pathname, result=result)
-
- if filter_callback and not filter_callback(relative_pathname):
- if is_dir and create_subdir and dest_subdir.is_empty():
- # Complete subdirectory has been filtered out, remove it
- self.delete_entry(direntry.name)
-
- # Entry filtered out, move to next
- continue
-
- if direntry.is_file(follow_symlinks=False):
- if self._check_replacement(direntry.name, relative_pathname, result):
- self._add_file(source_directory, direntry.name, modified=relative_pathname in result.overwritten)
- result.files_written.append(relative_pathname)
- elif direntry.is_symlink():
- if self._check_replacement(direntry.name, relative_pathname, result):
- self._copy_link_from_filesystem(source_directory, direntry.name)
- result.files_written.append(relative_pathname)
-
- def _partial_import_cas_into_cas(self, source_directory, filter_callback, *, path_prefix="", result):
- """ Import files from a CAS-based directory. """
-
- for name, entry in source_directory.index.items():
- # The destination filename, relative to the root where the import started
- relative_pathname = os.path.join(path_prefix, name)
-
- is_dir = entry.type == _FileType.DIRECTORY
-
- if is_dir:
- create_subdir = name not in self.index
-
- if create_subdir and not filter_callback:
- # If subdirectory does not exist yet and there is no filter,
- # we can import the whole source directory by digest instead
- # of importing each directory entry individually.
- subdir_digest = entry.get_digest()
- dest_entry = IndexEntry(name, _FileType.DIRECTORY, digest=subdir_digest)
- self.index[name] = dest_entry
- self.__invalidate_digest()
-
- # However, we still need to iterate over the directory entries
- # to fill in `result.files_written`.
-
- # Use source subdirectory object if it already exists,
- # otherwise create object for destination subdirectory.
- # This is based on the assumption that the destination
- # subdirectory is more likely to be modified later on
- # (e.g., by further import_files() calls).
- if entry.buildstream_object:
- subdir = entry.buildstream_object
- else:
- subdir = dest_entry.get_directory(self)
-
- subdir.__add_files_to_result(path_prefix=relative_pathname, result=result)
- else:
- src_subdir = source_directory.descend(name)
-
- try:
- dest_subdir = self.descend(name, create=create_subdir)
- except VirtualDirectoryError:
- filetype = self.index[name].type
- raise VirtualDirectoryError('Destination is a {}, not a directory: /{}'
- .format(filetype, relative_pathname))
-
- dest_subdir._partial_import_cas_into_cas(src_subdir, filter_callback,
- path_prefix=relative_pathname, result=result)
-
- if filter_callback and not filter_callback(relative_pathname):
- if is_dir and create_subdir and dest_subdir.is_empty():
- # Complete subdirectory has been filtered out, remove it
- self.delete_entry(name)
-
- # Entry filtered out, move to next
- continue
-
- if not is_dir:
- if self._check_replacement(name, relative_pathname, result):
- if entry.type == _FileType.REGULAR_FILE:
- self.index[name] = IndexEntry(name, _FileType.REGULAR_FILE,
- digest=entry.digest,
- is_executable=entry.is_executable,
- modified=True)
- self.__invalidate_digest()
- else:
- assert entry.type == _FileType.SYMLINK
- self._add_new_link_direct(name=name, target=entry.target)
- result.files_written.append(relative_pathname)
-
- def import_files(self, external_pathspec, *,
- filter_callback=None,
- report_written=True, update_mtime=False,
- can_link=False):
- """ See superclass Directory for arguments """
-
- result = FileListResult()
-
- if isinstance(external_pathspec, FileBasedDirectory):
- source_directory = external_pathspec._get_underlying_directory()
- self._import_files_from_directory(source_directory, filter_callback, result=result)
- elif isinstance(external_pathspec, str):
- source_directory = external_pathspec
- self._import_files_from_directory(source_directory, filter_callback, result=result)
- else:
- assert isinstance(external_pathspec, CasBasedDirectory)
- self._partial_import_cas_into_cas(external_pathspec, filter_callback, result=result)
-
- # TODO: No notice is taken of report_written, update_mtime or can_link.
- # Current behaviour is to fully populate the report, which is inefficient,
- # but still correct.
-
- return result
-
- def set_deterministic_mtime(self):
- """ Sets a static modification time for all regular files in this directory.
- Since we don't store any modification time, we don't need to do anything.
- """
-
- def set_deterministic_user(self):
- """ Sets all files in this directory to the current user's euid/egid.
- We also don't store user data, so this can be ignored.
- """
-
- def export_files(self, to_directory, *, can_link=False, can_destroy=False):
- """Copies everything from this into to_directory, which must be the name
- of a traditional filesystem directory.
-
- Arguments:
-
- to_directory (string): a path outside this directory object
- where the contents will be copied to.
-
- can_link (bool): Whether we can create hard links in to_directory
- instead of copying.
-
- can_destroy (bool): Whether we can destroy elements in this
- directory to export them (e.g. by renaming them as the
- target).
-
- """
-
- self.cas_cache.checkout(to_directory, self._get_digest(), can_link=can_link)
-
- def export_to_tar(self, tarfile, destination_dir, mtime=_magic_timestamp):
- raise NotImplementedError()
-
- def mark_changed(self):
- """ It should not be possible to externally modify a CAS-based
- directory at the moment."""
- raise NotImplementedError()
-
- def is_empty(self):
- """ Return true if this directory has no files, subdirectories or links in it.
- """
- return len(self.index) == 0
-
- def _mark_directory_unmodified(self):
- # Marks all entries in this directory and all child directories as unmodified.
- for i in self.index.values():
- i.modified = False
- if i.type == _FileType.DIRECTORY and i.buildstream_object:
- i.buildstream_object._mark_directory_unmodified()
-
- def _mark_entry_unmodified(self, name):
- # Marks an entry as unmodified. If the entry is a directory, it will
- # recursively mark all its tree as unmodified.
- self.index[name].modified = False
- if self.index[name].buildstream_object:
- self.index[name].buildstream_object._mark_directory_unmodified()
-
- def mark_unmodified(self):
- """ Marks all files in this directory (recursively) as unmodified.
- If we have a parent, we mark our own entry as unmodified in that parent's
- index.
- """
- if self.parent:
- self.parent._mark_entry_unmodified(self._find_self_in_parent())
- else:
- self._mark_directory_unmodified()
-
- def _lightweight_resolve_to_index(self, path):
- """A lightweight function for transforming paths into IndexEntry
- objects. This does not follow symlinks.
-
- path: The string to resolve. This should be a series of path
- components separated by the protocol buffer path separator
- _pb2_path_sep.
-
- Returns: the IndexEntry found, or None if any of the path components were not present.
-
- """
- directory = self
- path_components = path.split(CasBasedDirectory._pb2_path_sep)
- for component in path_components[:-1]:
- if component not in directory.index:
- return None
- if directory.index[component].type == _FileType.DIRECTORY:
- directory = directory.index[component].get_directory(self)
- else:
- return None
- return directory.index.get(path_components[-1], None)
-
- def list_modified_paths(self):
- """Provide a list of relative paths which have been modified since the
- last call to mark_unmodified.
-
- Return value: List(str) - list of modified paths
- """
-
- for p in self.list_relative_paths():
- i = self._lightweight_resolve_to_index(p)
- if i and i.modified:
- yield p
-
- def list_relative_paths(self, relpath=""):
- """Provide a list of all relative paths.
-
- Return value: List(str) - list of all paths
- """
-
- file_list = list(filter(lambda i: i[1].type != _FileType.DIRECTORY,
- self.index.items()))
- directory_list = filter(lambda i: i[1].type == _FileType.DIRECTORY,
- self.index.items())
-
- if relpath != "":
- yield relpath
-
- for (k, v) in sorted(file_list):
- yield os.path.join(relpath, k)
-
- for (k, v) in sorted(directory_list):
- subdir = v.get_directory(self)
- yield from subdir.list_relative_paths(relpath=os.path.join(relpath, k))
-
- def get_size(self):
- digest = self._get_digest()
- total = digest.size_bytes
- for i in self.index.values():
- if i.type == _FileType.DIRECTORY:
- subdir = i.get_directory(self)
- total += subdir.get_size()
- elif i.type == _FileType.REGULAR_FILE:
- total += i.digest.size_bytes
- # Symlink nodes are encoded as part of the directory serialization.
- return total
-
- def _get_identifier(self):
- path = ""
- if self.parent:
- path = self.parent._get_identifier()
- if self.filename:
- path += "/" + self.filename
- else:
- path += "/" + self.common_name
- return path
-
- def __str__(self):
- return "[CAS:{}]".format(self._get_identifier())
-
- def _get_underlying_directory(self):
- """ There is no underlying directory for a CAS-backed directory, so
- throw an exception. """
- raise VirtualDirectoryError("_get_underlying_directory was called on a CAS-backed directory," +
- " which has no underlying directory.")
-
- # _get_digest():
- #
- # Return the Digest for this directory.
- #
- # Returns:
- # (Digest): The Digest protobuf object for the Directory protobuf
- #
- def _get_digest(self):
- if not self.__digest:
- # Create updated Directory proto
- pb2_directory = remote_execution_pb2.Directory()
-
- for name, entry in sorted(self.index.items()):
- if entry.type == _FileType.DIRECTORY:
- dirnode = pb2_directory.directories.add()
- dirnode.name = name
-
- # Update digests for subdirectories in DirectoryNodes.
- # No need to call entry.get_directory().
- # If it hasn't been instantiated, digest must be up-to-date.
- subdir = entry.buildstream_object
- if subdir:
- dirnode.digest.CopyFrom(subdir._get_digest())
- else:
- dirnode.digest.CopyFrom(entry.digest)
- elif entry.type == _FileType.REGULAR_FILE:
- filenode = pb2_directory.files.add()
- filenode.name = name
- filenode.digest.CopyFrom(entry.digest)
- filenode.is_executable = entry.is_executable
- elif entry.type == _FileType.SYMLINK:
- symlinknode = pb2_directory.symlinks.add()
- symlinknode.name = name
- symlinknode.target = entry.target
-
- self.__digest = self.cas_cache.add_object(buffer=pb2_directory.SerializeToString())
-
- return self.__digest
-
- def _get_child_digest(self, *path):
- subdir = self.descend(*path[:-1])
- entry = subdir.index[path[-1]]
- if entry.type == _FileType.DIRECTORY:
- subdir = entry.buildstream_object
- if subdir:
- return subdir._get_digest()
- else:
- return entry.digest
- elif entry.type == _FileType.REGULAR_FILE:
- return entry.digest
- else:
- raise VirtualDirectoryError("Directory entry has no digest: {}".format(os.path.join(*path)))
-
- def _objpath(self, *path):
- subdir = self.descend(*path[:-1])
- entry = subdir.index[path[-1]]
- return self.cas_cache.objpath(entry.digest)
-
- def _exists(self, *path):
- try:
- subdir = self.descend(*path[:-1])
- return path[-1] in subdir.index
- except VirtualDirectoryError:
- return False
-
- def __invalidate_digest(self):
- if self.__digest:
- self.__digest = None
- if self.parent:
- self.parent.__invalidate_digest()
-
- def __add_files_to_result(self, *, path_prefix="", result):
- for name, entry in self.index.items():
- # The destination filename, relative to the root where the import started
- relative_pathname = os.path.join(path_prefix, name)
-
- if entry.type == _FileType.DIRECTORY:
- subdir = self.descend(name)
- subdir.__add_files_to_result(path_prefix=relative_pathname, result=result)
- else:
- result.files_written.append(relative_pathname)
diff --git a/buildstream/storage/_filebaseddirectory.py b/buildstream/storage/_filebaseddirectory.py
deleted file mode 100644
index 9a746f731..000000000
--- a/buildstream/storage/_filebaseddirectory.py
+++ /dev/null
@@ -1,273 +0,0 @@
-#!/usr/bin/env python3
-#
-# 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:
-# Jim MacArthur <jim.macarthur@codethink.co.uk>
-
-"""
-FileBasedDirectory
-=========
-
-Implementation of the Directory class which backs onto a normal POSIX filing system.
-
-See also: :ref:`sandboxing`.
-"""
-
-import os
-import stat
-import time
-
-from .directory import Directory, VirtualDirectoryError, _FileType
-from .. import utils
-from ..utils import link_files, copy_files, list_relative_paths, _get_link_mtime, _magic_timestamp
-from ..utils import _set_deterministic_user, _set_deterministic_mtime
-from ..utils import FileListResult
-
-# FileBasedDirectory intentionally doesn't call its superclass constuctor,
-# which is meant to be unimplemented.
-# pylint: disable=super-init-not-called
-
-
-class FileBasedDirectory(Directory):
- def __init__(self, external_directory=None):
- self.external_directory = external_directory
-
- def descend(self, *paths, create=False):
- """ See superclass Directory for arguments """
-
- current_dir = self
-
- for path in paths:
- # Skip empty path segments
- if not path:
- continue
-
- new_path = os.path.join(current_dir.external_directory, path)
- try:
- st = os.lstat(new_path)
- if not stat.S_ISDIR(st.st_mode):
- raise VirtualDirectoryError("Cannot descend into '{}': '{}' is not a directory"
- .format(path, new_path))
- except FileNotFoundError:
- if create:
- os.mkdir(new_path)
- else:
- raise VirtualDirectoryError("Cannot descend into '{}': '{}' does not exist"
- .format(path, new_path))
-
- current_dir = FileBasedDirectory(new_path)
-
- return current_dir
-
- def import_files(self, external_pathspec, *,
- filter_callback=None,
- report_written=True, update_mtime=False,
- can_link=False):
- """ See superclass Directory for arguments """
-
- from ._casbaseddirectory import CasBasedDirectory # pylint: disable=cyclic-import
-
- if isinstance(external_pathspec, CasBasedDirectory):
- if can_link and not update_mtime:
- actionfunc = utils.safe_link
- else:
- actionfunc = utils.safe_copy
-
- import_result = FileListResult()
- self._import_files_from_cas(external_pathspec, actionfunc, filter_callback, result=import_result)
- else:
- if isinstance(external_pathspec, Directory):
- source_directory = external_pathspec.external_directory
- else:
- source_directory = external_pathspec
-
- if can_link and not update_mtime:
- import_result = link_files(source_directory, self.external_directory,
- filter_callback=filter_callback,
- ignore_missing=False, report_written=report_written)
- else:
- import_result = copy_files(source_directory, self.external_directory,
- filter_callback=filter_callback,
- ignore_missing=False, report_written=report_written)
-
- if update_mtime:
- cur_time = time.time()
-
- for f in import_result.files_written:
- os.utime(os.path.join(self.external_directory, f), times=(cur_time, cur_time))
- return import_result
-
- def _mark_changed(self):
- pass
-
- def set_deterministic_mtime(self):
- _set_deterministic_mtime(self.external_directory)
-
- def set_deterministic_user(self):
- _set_deterministic_user(self.external_directory)
-
- def export_files(self, to_directory, *, can_link=False, can_destroy=False):
- if can_destroy:
- # Try a simple rename of the sandbox root; if that
- # doesnt cut it, then do the regular link files code path
- try:
- os.rename(self.external_directory, to_directory)
- return
- except OSError:
- # Proceed using normal link/copy
- pass
-
- os.makedirs(to_directory, exist_ok=True)
- if can_link:
- link_files(self.external_directory, to_directory)
- else:
- copy_files(self.external_directory, to_directory)
-
- # Add a directory entry deterministically to a tar file
- #
- # This function takes extra steps to ensure the output is deterministic.
- # First, it sorts the results of os.listdir() to ensure the ordering of
- # the files in the archive is the same. Second, it sets a fixed
- # timestamp for each entry. See also https://bugs.python.org/issue24465.
- def export_to_tar(self, tf, dir_arcname, mtime=_magic_timestamp):
- # We need directories here, including non-empty ones,
- # so list_relative_paths is not used.
- for filename in sorted(os.listdir(self.external_directory)):
- source_name = os.path.join(self.external_directory, filename)
- arcname = os.path.join(dir_arcname, filename)
- tarinfo = tf.gettarinfo(source_name, arcname)
- tarinfo.mtime = mtime
-
- if tarinfo.isreg():
- with open(source_name, "rb") as f:
- tf.addfile(tarinfo, f)
- elif tarinfo.isdir():
- tf.addfile(tarinfo)
- self.descend(*filename.split(os.path.sep)).export_to_tar(tf, arcname, mtime)
- else:
- tf.addfile(tarinfo)
-
- def is_empty(self):
- it = os.scandir(self.external_directory)
- return next(it, None) is None
-
- def mark_unmodified(self):
- """ Marks all files in this directory (recursively) as unmodified.
- """
- _set_deterministic_mtime(self.external_directory)
-
- def list_modified_paths(self):
- """Provide a list of relative paths which have been modified since the
- last call to mark_unmodified.
-
- Return value: List(str) - list of modified paths
- """
- return [f for f in list_relative_paths(self.external_directory)
- if _get_link_mtime(os.path.join(self.external_directory, f)) != _magic_timestamp]
-
- def list_relative_paths(self):
- """Provide a list of all relative paths.
-
- Return value: List(str) - list of all paths
- """
-
- return list_relative_paths(self.external_directory)
-
- def get_size(self):
- return utils._get_dir_size(self.external_directory)
-
- def __str__(self):
- # This returns the whole path (since we don't know where the directory started)
- # which exposes the sandbox directory; we will have to assume for the time being
- # that people will not abuse __str__.
- return self.external_directory
-
- def _get_underlying_directory(self) -> str:
- """ Returns the underlying (real) file system directory this
- object refers to. """
- return self.external_directory
-
- def _get_filetype(self, name=None):
- path = self.external_directory
-
- if name:
- path = os.path.join(path, name)
-
- st = os.lstat(path)
- if stat.S_ISDIR(st.st_mode):
- return _FileType.DIRECTORY
- elif stat.S_ISLNK(st.st_mode):
- return _FileType.SYMLINK
- elif stat.S_ISREG(st.st_mode):
- return _FileType.REGULAR_FILE
- else:
- return _FileType.SPECIAL_FILE
-
- def _import_files_from_cas(self, source_directory, actionfunc, filter_callback, *, path_prefix="", result):
- """ Import files from a CAS-based directory. """
-
- for name, entry in source_directory.index.items():
- # The destination filename, relative to the root where the import started
- relative_pathname = os.path.join(path_prefix, name)
-
- # The full destination path
- dest_path = os.path.join(self.external_directory, name)
-
- is_dir = entry.type == _FileType.DIRECTORY
-
- if is_dir:
- src_subdir = source_directory.descend(name)
-
- try:
- create_subdir = not os.path.lexists(dest_path)
- dest_subdir = self.descend(name, create=create_subdir)
- except VirtualDirectoryError:
- filetype = self._get_filetype(name)
- raise VirtualDirectoryError('Destination is a {}, not a directory: /{}'
- .format(filetype, relative_pathname))
-
- dest_subdir._import_files_from_cas(src_subdir, actionfunc, filter_callback,
- path_prefix=relative_pathname, result=result)
-
- if filter_callback and not filter_callback(relative_pathname):
- if is_dir and create_subdir and dest_subdir.is_empty():
- # Complete subdirectory has been filtered out, remove it
- os.rmdir(dest_subdir.external_directory)
-
- # Entry filtered out, move to next
- continue
-
- if not is_dir:
- if os.path.lexists(dest_path):
- # Collect overlaps
- if not os.path.isdir(dest_path):
- result.overwritten.append(relative_pathname)
-
- if not utils.safe_remove(dest_path):
- result.ignored.append(relative_pathname)
- continue
-
- if entry.type == _FileType.REGULAR_FILE:
- src_path = source_directory.cas_cache.objpath(entry.digest)
- actionfunc(src_path, dest_path, result=result)
- if entry.is_executable:
- os.chmod(dest_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
- stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
- else:
- assert entry.type == _FileType.SYMLINK
- os.symlink(entry.target, dest_path)
- result.files_written.append(relative_pathname)
diff --git a/buildstream/storage/directory.py b/buildstream/storage/directory.py
deleted file mode 100644
index bad818fef..000000000
--- a/buildstream/storage/directory.py
+++ /dev/null
@@ -1,211 +0,0 @@
-#!/usr/bin/env python3
-#
-# 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:
-# Jim MacArthur <jim.macarthur@codethink.co.uk>
-
-"""
-Directory
-=========
-
-This is a virtual Directory class to isolate the rest of BuildStream
-from the backing store implementation. Sandboxes are allowed to read
-from and write to the underlying storage, but all others must use this
-Directory class to access files and directories in the sandbox.
-
-See also: :ref:`sandboxing`.
-
-"""
-
-from enum import Enum
-
-from .._exceptions import BstError, ErrorDomain
-from ..utils import _magic_timestamp
-
-
-class VirtualDirectoryError(BstError):
- """Raised by Directory functions when system calls fail.
- This will be handled internally by the BuildStream core,
- if you need to handle this error, then it should be reraised,
- or either of the :class:`.ElementError` or :class:`.SourceError`
- exceptions should be raised from this error.
- """
- def __init__(self, message, reason=None):
- super().__init__(message, domain=ErrorDomain.VIRTUAL_FS, reason=reason)
-
-
-class Directory():
- def __init__(self, external_directory=None):
- raise NotImplementedError()
-
- def descend(self, *paths, create=False):
- """Descend one or more levels of directory hierarchy and return a new
- Directory object for that directory.
-
- Args:
- *paths (str): A list of strings which are all directory names.
- create (boolean): If this is true, the directories will be created if
- they don't already exist.
-
- Yields:
- A Directory object representing the found directory.
-
- Raises:
- VirtualDirectoryError: if any of the components in subdirectory_spec
- cannot be found, or are files, or symlinks to files.
-
- """
- raise NotImplementedError()
-
- # Import and export of files and links
- def import_files(self, external_pathspec, *,
- filter_callback=None,
- report_written=True, update_mtime=False,
- can_link=False):
- """Imports some or all files from external_path into this directory.
-
- Args:
- external_pathspec: Either a string containing a pathname, or a
- Directory object, to use as the source.
- filter_callback (callable): Optional filter callback. Called with the
- relative path as argument for every file in the source directory.
- The file is imported only if the callable returns True.
- If no filter callback is specified, all files will be imported.
- report_written (bool): Return the full list of files
- written. Defaults to true. If false, only a list of
- overwritten files is returned.
- update_mtime (bool): Update the access and modification time
- of each file copied to the current time.
- can_link (bool): Whether it's OK to create a hard link to the
- original content, meaning the stored copy will change when the
- original files change. Setting this doesn't guarantee hard
- links will be made. can_link will never be used if
- update_mtime is set.
-
- Yields:
- (FileListResult) - A report of files imported and overwritten.
-
- """
-
- raise NotImplementedError()
-
- def export_files(self, to_directory, *, can_link=False, can_destroy=False):
- """Copies everything from this into to_directory.
-
- Args:
- to_directory (string): a path outside this directory object
- where the contents will be copied to.
- can_link (bool): Whether we can create hard links in to_directory
- instead of copying. Setting this does not guarantee hard links will be used.
- can_destroy (bool): Can we destroy the data already in this
- directory when exporting? If set, this may allow data to be
- moved rather than copied which will be quicker.
- """
-
- raise NotImplementedError()
-
- def export_to_tar(self, tarfile, destination_dir, mtime=_magic_timestamp):
- """ Exports this directory into the given tar file.
-
- Args:
- tarfile (TarFile): A Python TarFile object to export into.
- destination_dir (str): The prefix for all filenames inside the archive.
- mtime (int): mtimes of all files in the archive are set to this.
- """
- raise NotImplementedError()
-
- # Convenience functions
- def is_empty(self):
- """ Return true if this directory has no files, subdirectories or links in it.
- """
- raise NotImplementedError()
-
- def set_deterministic_mtime(self):
- """ Sets a static modification time for all regular files in this directory.
- The magic number for timestamps is 2011-11-11 11:11:11.
- """
- raise NotImplementedError()
-
- def set_deterministic_user(self):
- """ Sets all files in this directory to the current user's euid/egid.
- """
- raise NotImplementedError()
-
- def mark_unmodified(self):
- """ Marks all files in this directory (recursively) as unmodified.
- """
- raise NotImplementedError()
-
- def list_modified_paths(self):
- """Provide a list of relative paths which have been modified since the
- last call to mark_unmodified. Includes directories only if
- they are empty.
-
- Yields:
- (List(str)) - list of all modified files with relative paths.
-
- """
- raise NotImplementedError()
-
- def list_relative_paths(self):
- """Provide a list of all relative paths in this directory. Includes
- directories only if they are empty.
-
- Yields:
- (List(str)) - list of all files with relative paths.
-
- """
- raise NotImplementedError()
-
- def _mark_changed(self):
- """Internal function to mark this directory as having been changed
- outside this API. This normally can only happen by calling the
- Sandbox's `run` method. This does *not* mark everything as modified
- (i.e. list_modified_paths will not necessarily return the same results
- as list_relative_paths after calling this.)
-
- """
- raise NotImplementedError()
-
- def get_size(self):
- """ Get an approximation of the storage space in bytes used by this directory
- and all files and subdirectories in it. Storage space varies by implementation
- and effective space used may be lower than this number due to deduplication. """
- raise NotImplementedError()
-
-
-# FileType:
-#
-# Type of file or directory entry.
-#
-class _FileType(Enum):
-
- # Directory
- DIRECTORY = 1
-
- # Regular file
- REGULAR_FILE = 2
-
- # Symbolic link
- SYMLINK = 3
-
- # Special file (FIFO, character device, block device, or socket)
- SPECIAL_FILE = 4
-
- def __str__(self):
- # https://github.com/PyCQA/pylint/issues/2062
- return self.name.lower().replace('_', ' ') # pylint: disable=no-member
diff --git a/buildstream/testing/__init__.py b/buildstream/testing/__init__.py
deleted file mode 100644
index 0b1c1fd73..000000000
--- a/buildstream/testing/__init__.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#
-# Copyright (C) 2019 Codethink Limited
-# Copyright (C) 2019 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/>.
-
-"""
-This package contains various utilities which make it easier to test plugins.
-"""
-
-import os
-from collections import OrderedDict
-from . import _sourcetests
-from .repo import Repo
-from .runcli import cli, cli_integration, cli_remote_execution
-from .integration import integration_cache
-
-# To make use of these test utilities it is necessary to have pytest
-# available. However, we don't want to have a hard dependency on
-# pytest.
-try:
- import pytest
-except ImportError:
- module_name = globals()['__name__']
- msg = "Could not import pytest:\n" \
- "To use the {} module, you must have pytest installed.".format(module_name)
- raise ImportError(msg)
-
-
-ALL_REPO_KINDS = OrderedDict()
-
-
-def create_repo(kind, directory, subdir='repo'):
- """Convenience method for creating a Repo
-
- Args:
- kind (str): The kind of repo to create (a source plugin basename). This
- must have previously been registered using
- `register_repo_kind`
- directory (str): The path where the repo will keep a cache
-
- Returns:
- (Repo): A new Repo object
- """
- try:
- constructor = ALL_REPO_KINDS[kind]
- except KeyError as e:
- raise AssertionError("Unsupported repo kind {}".format(kind)) from e
-
- return constructor(directory, subdir=subdir)
-
-
-def register_repo_kind(kind, cls):
- """Register a new repo kind.
-
- Registering a repo kind will allow the use of the `create_repo`
- method for that kind and include that repo kind in ALL_REPO_KINDS
-
- In addition, repo_kinds registred prior to
- `sourcetests_collection_hook` being called will be automatically
- used to test the basic behaviour of their associated source
- plugins using the tests in `testing._sourcetests`.
-
- Args:
- kind (str): The kind of repo to create (a source plugin basename)
- cls (cls) : A class derived from Repo.
-
- """
- ALL_REPO_KINDS[kind] = cls
-
-
-def sourcetests_collection_hook(session):
- """ Used to hook the templated source plugin tests into a pyest test suite.
-
- This should be called via the `pytest_sessionstart
- hook <https://docs.pytest.org/en/latest/reference.html#collection-hooks>`_.
- The tests in the _sourcetests package will be collected as part of
- whichever test package this hook is called from.
-
- Args:
- session (pytest.Session): The current pytest session
- """
- def should_collect_tests(config):
- args = config.args
- rootdir = config.rootdir
- # When no args are supplied, pytest defaults the arg list to
- # just include the session's root_dir. We want to collect
- # tests as part of the default collection
- if args == [str(rootdir)]:
- return True
-
- # If specific tests are passed, don't collect
- # everything. Pytest will handle this correctly without
- # modification.
- if len(args) > 1 or rootdir not in args:
- return False
-
- # If in doubt, collect them, this will be an easier bug to
- # spot and is less likely to result in bug not being found.
- return True
-
- SOURCE_TESTS_PATH = os.path.dirname(_sourcetests.__file__)
- # Add the location of the source tests to the session's
- # python_files config. Without this, pytest may filter out these
- # tests during collection.
- session.config.addinivalue_line("python_files", os.path.join(SOURCE_TESTS_PATH, "*.py"))
- # If test invocation has specified specic tests, don't
- # automatically collect templated tests.
- if should_collect_tests(session.config):
- session.config.args.append(SOURCE_TESTS_PATH)
diff --git a/buildstream/testing/_sourcetests/__init__.py b/buildstream/testing/_sourcetests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/testing/_sourcetests/__init__.py
+++ /dev/null
diff --git a/buildstream/testing/_sourcetests/build_checkout.py b/buildstream/testing/_sourcetests/build_checkout.py
deleted file mode 100644
index 3619d2b7e..000000000
--- a/buildstream/testing/_sourcetests/build_checkout.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-#
-
-# Pylint doesn't play well with fixtures and dependency injection from pytest
-# pylint: disable=redefined-outer-name
-
-import os
-import pytest
-
-from buildstream.testing import create_repo, ALL_REPO_KINDS
-from buildstream.testing import cli # pylint: disable=unused-import
-from buildstream import _yaml
-
-# Project directory
-TOP_DIR = os.path.dirname(os.path.realpath(__file__))
-DATA_DIR = os.path.join(TOP_DIR, 'project')
-
-fetch_build_checkout_combos = \
- [("strict", kind) for kind in ALL_REPO_KINDS] + \
- [("non-strict", kind) for kind in ALL_REPO_KINDS]
-
-
-def strict_args(args, strict):
- if strict != "strict":
- return ['--no-strict', *args]
- return args
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("strict,kind", fetch_build_checkout_combos)
-def test_fetch_build_checkout(cli, tmpdir, datafiles, strict, kind):
- checkout = os.path.join(cli.directory, 'checkout')
- project = str(datafiles)
- dev_files_path = os.path.join(project, 'files', 'dev-files')
- element_path = os.path.join(project, 'elements')
- element_name = 'build-test-{}.bst'.format(kind)
-
- # Create our repo object of the given source type with
- # the dev files, and then collect the initial ref.
- #
- repo = create_repo(kind, str(tmpdir))
- ref = repo.create(dev_files_path)
-
- # Write out our test target
- element = {
- 'kind': 'import',
- 'sources': [
- repo.source_config(ref=ref)
- ]
- }
- _yaml.dump(element,
- os.path.join(element_path,
- element_name))
-
- assert cli.get_element_state(project, element_name) == 'fetch needed'
- result = cli.run(project=project, args=strict_args(['build', element_name], strict))
- result.assert_success()
- assert cli.get_element_state(project, element_name) == 'cached'
-
- # Now check it out
- result = cli.run(project=project, args=strict_args([
- 'artifact', 'checkout', element_name, '--directory', checkout
- ], strict))
- result.assert_success()
-
- # Check that the pony.h include from files/dev-files exists
- filename = os.path.join(checkout, 'usr', 'include', 'pony.h')
- assert os.path.exists(filename)
diff --git a/buildstream/testing/_sourcetests/fetch.py b/buildstream/testing/_sourcetests/fetch.py
deleted file mode 100644
index aaf92a14d..000000000
--- a/buildstream/testing/_sourcetests/fetch.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-#
-
-# Pylint doesn't play well with fixtures and dependency injection from pytest
-# pylint: disable=redefined-outer-name
-
-import os
-import pytest
-
-from buildstream import _yaml
-from .._utils import generate_junction, configure_project
-from .. import create_repo, ALL_REPO_KINDS
-from .. import cli # pylint: disable=unused-import
-
-# Project directory
-TOP_DIR = os.path.dirname(os.path.realpath(__file__))
-DATA_DIR = os.path.join(TOP_DIR, 'project')
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_fetch(cli, tmpdir, datafiles, kind):
- project = str(datafiles)
- bin_files_path = os.path.join(project, 'files', 'bin-files')
- element_path = os.path.join(project, 'elements')
- element_name = 'fetch-test-{}.bst'.format(kind)
-
- # Create our repo object of the given source type with
- # the bin files, and then collect the initial ref.
- #
- repo = create_repo(kind, str(tmpdir))
- ref = repo.create(bin_files_path)
-
- # Write out our test target
- element = {
- 'kind': 'import',
- 'sources': [
- repo.source_config(ref=ref)
- ]
- }
- _yaml.dump(element,
- os.path.join(element_path,
- element_name))
-
- # Assert that a fetch is needed
- assert cli.get_element_state(project, element_name) == 'fetch needed'
-
- # Now try to fetch it
- result = cli.run(project=project, args=['source', 'fetch', element_name])
- result.assert_success()
-
- # Assert that we are now buildable because the source is
- # now cached.
- assert cli.get_element_state(project, element_name) == 'buildable'
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("ref_storage", [('inline'), ('project.refs')])
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_fetch_cross_junction(cli, tmpdir, datafiles, ref_storage, kind):
- project = str(datafiles)
- subproject_path = os.path.join(project, 'files', 'sub-project')
- junction_path = os.path.join(project, 'elements', 'junction.bst')
-
- import_etc_path = os.path.join(subproject_path, 'elements', 'import-etc-repo.bst')
- etc_files_path = os.path.join(subproject_path, 'files', 'etc-files')
-
- repo = create_repo(kind, str(tmpdir.join('import-etc')))
- ref = repo.create(etc_files_path)
-
- element = {
- 'kind': 'import',
- 'sources': [
- repo.source_config(ref=(ref if ref_storage == 'inline' else None))
- ]
- }
- _yaml.dump(element, import_etc_path)
-
- configure_project(project, {
- 'ref-storage': ref_storage
- })
-
- generate_junction(tmpdir, subproject_path, junction_path, store_ref=(ref_storage == 'inline'))
-
- if ref_storage == 'project.refs':
- result = cli.run(project=project, args=['source', 'track', 'junction.bst'])
- result.assert_success()
- result = cli.run(project=project, args=['source', 'track', 'junction.bst:import-etc.bst'])
- result.assert_success()
-
- result = cli.run(project=project, args=['source', 'fetch', 'junction.bst:import-etc.bst'])
- result.assert_success()
diff --git a/buildstream/testing/_sourcetests/mirror.py b/buildstream/testing/_sourcetests/mirror.py
deleted file mode 100644
index d682bb2ef..000000000
--- a/buildstream/testing/_sourcetests/mirror.py
+++ /dev/null
@@ -1,427 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-#
-
-# Pylint doesn't play well with fixtures and dependency injection from pytest
-# pylint: disable=redefined-outer-name
-
-import os
-import pytest
-
-from buildstream import _yaml
-from buildstream._exceptions import ErrorDomain
-from .._utils import generate_junction
-from .. import create_repo, ALL_REPO_KINDS
-from .. import cli # pylint: disable=unused-import
-
-# Project directory
-TOP_DIR = os.path.dirname(os.path.realpath(__file__))
-DATA_DIR = os.path.join(TOP_DIR, 'project')
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_mirror_fetch(cli, tmpdir, datafiles, kind):
- bin_files_path = os.path.join(str(datafiles), 'files', 'bin-files', 'usr')
- dev_files_path = os.path.join(str(datafiles), 'files', 'dev-files', 'usr')
- upstream_repodir = os.path.join(str(tmpdir), 'upstream')
- mirror_repodir = os.path.join(str(tmpdir), 'mirror')
- project_dir = os.path.join(str(tmpdir), 'project')
- os.makedirs(project_dir)
- element_dir = os.path.join(project_dir, 'elements')
-
- # Create repo objects of the upstream and mirror
- upstream_repo = create_repo(kind, upstream_repodir)
- upstream_repo.create(bin_files_path)
- mirror_repo = upstream_repo.copy(mirror_repodir)
- upstream_ref = upstream_repo.create(dev_files_path)
-
- element = {
- 'kind': 'import',
- 'sources': [
- upstream_repo.source_config(ref=upstream_ref)
- ]
- }
- element_name = 'test.bst'
- element_path = os.path.join(element_dir, element_name)
- full_repo = element['sources'][0]['url']
- upstream_map, repo_name = os.path.split(full_repo)
- alias = 'foo-' + kind
- aliased_repo = alias + ':' + repo_name
- element['sources'][0]['url'] = aliased_repo
- full_mirror = mirror_repo.source_config()['url']
- mirror_map, _ = os.path.split(full_mirror)
- os.makedirs(element_dir)
- _yaml.dump(element, element_path)
-
- project = {
- 'name': 'test',
- 'element-path': 'elements',
- 'aliases': {
- alias: upstream_map + "/"
- },
- 'mirrors': [
- {
- 'name': 'middle-earth',
- 'aliases': {
- alias: [mirror_map + "/"],
- },
- },
- ]
- }
- project_file = os.path.join(project_dir, 'project.conf')
- _yaml.dump(project, project_file)
-
- # No obvious ways of checking that the mirror has been fetched
- # But at least we can be sure it succeeds
- result = cli.run(project=project_dir, args=['source', 'fetch', element_name])
- result.assert_success()
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_mirror_fetch_upstream_absent(cli, tmpdir, datafiles, kind):
- dev_files_path = os.path.join(str(datafiles), 'files', 'dev-files', 'usr')
- upstream_repodir = os.path.join(str(tmpdir), 'upstream')
- mirror_repodir = os.path.join(str(tmpdir), 'mirror')
- project_dir = os.path.join(str(tmpdir), 'project')
- os.makedirs(project_dir)
- element_dir = os.path.join(project_dir, 'elements')
-
- # Create repo objects of the upstream and mirror
- upstream_repo = create_repo(kind, upstream_repodir)
- ref = upstream_repo.create(dev_files_path)
- mirror_repo = upstream_repo.copy(mirror_repodir)
-
- element = {
- 'kind': 'import',
- 'sources': [
- upstream_repo.source_config(ref=ref)
- ]
- }
-
- element_name = 'test.bst'
- element_path = os.path.join(element_dir, element_name)
- full_repo = element['sources'][0]['url']
- _, repo_name = os.path.split(full_repo)
- alias = 'foo-' + kind
- aliased_repo = alias + ':' + repo_name
- element['sources'][0]['url'] = aliased_repo
- full_mirror = mirror_repo.source_config()['url']
- mirror_map, _ = os.path.split(full_mirror)
- os.makedirs(element_dir)
- _yaml.dump(element, element_path)
-
- project = {
- 'name': 'test',
- 'element-path': 'elements',
- 'aliases': {
- alias: 'http://www.example.com/'
- },
- 'mirrors': [
- {
- 'name': 'middle-earth',
- 'aliases': {
- alias: [mirror_map + "/"],
- },
- },
- ]
- }
- project_file = os.path.join(project_dir, 'project.conf')
- _yaml.dump(project, project_file)
-
- result = cli.run(project=project_dir, args=['source', 'fetch', element_name])
- result.assert_success()
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_mirror_from_includes(cli, tmpdir, datafiles, kind):
- bin_files_path = os.path.join(str(datafiles), 'files', 'bin-files', 'usr')
- upstream_repodir = os.path.join(str(tmpdir), 'upstream')
- mirror_repodir = os.path.join(str(tmpdir), 'mirror')
- project_dir = os.path.join(str(tmpdir), 'project')
- os.makedirs(project_dir)
- element_dir = os.path.join(project_dir, 'elements')
-
- # Create repo objects of the upstream and mirror
- upstream_repo = create_repo(kind, upstream_repodir)
- upstream_ref = upstream_repo.create(bin_files_path)
- mirror_repo = upstream_repo.copy(mirror_repodir)
-
- element = {
- 'kind': 'import',
- 'sources': [
- upstream_repo.source_config(ref=upstream_ref)
- ]
- }
- element_name = 'test.bst'
- element_path = os.path.join(element_dir, element_name)
- full_repo = element['sources'][0]['url']
- upstream_map, repo_name = os.path.split(full_repo)
- alias = 'foo-' + kind
- aliased_repo = alias + ':' + repo_name
- element['sources'][0]['url'] = aliased_repo
- full_mirror = mirror_repo.source_config()['url']
- mirror_map, _ = os.path.split(full_mirror)
- os.makedirs(element_dir)
- _yaml.dump(element, element_path)
-
- config_project_dir = str(tmpdir.join('config'))
- os.makedirs(config_project_dir, exist_ok=True)
- config_project = {
- 'name': 'config'
- }
- _yaml.dump(config_project, os.path.join(config_project_dir, 'project.conf'))
- extra_mirrors = {
- 'mirrors': [
- {
- 'name': 'middle-earth',
- 'aliases': {
- alias: [mirror_map + "/"],
- }
- }
- ]
- }
- _yaml.dump(extra_mirrors, os.path.join(config_project_dir, 'mirrors.yml'))
- generate_junction(str(tmpdir.join('config_repo')),
- config_project_dir,
- os.path.join(element_dir, 'config.bst'))
-
- project = {
- 'name': 'test',
- 'element-path': 'elements',
- 'aliases': {
- alias: upstream_map + "/"
- },
- '(@)': [
- 'config.bst:mirrors.yml'
- ]
- }
- project_file = os.path.join(project_dir, 'project.conf')
- _yaml.dump(project, project_file)
-
- # Now make the upstream unavailable.
- os.rename(upstream_repo.repo, '{}.bak'.format(upstream_repo.repo))
- result = cli.run(project=project_dir, args=['source', 'fetch', element_name])
- result.assert_success()
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_mirror_junction_from_includes(cli, tmpdir, datafiles, kind):
- bin_files_path = os.path.join(str(datafiles), 'files', 'bin-files', 'usr')
- upstream_repodir = os.path.join(str(tmpdir), 'upstream')
- mirror_repodir = os.path.join(str(tmpdir), 'mirror')
- project_dir = os.path.join(str(tmpdir), 'project')
- os.makedirs(project_dir)
- element_dir = os.path.join(project_dir, 'elements')
-
- # Create repo objects of the upstream and mirror
- upstream_repo = create_repo(kind, upstream_repodir)
- upstream_ref = upstream_repo.create(bin_files_path)
- mirror_repo = upstream_repo.copy(mirror_repodir)
-
- element = {
- 'kind': 'junction',
- 'sources': [
- upstream_repo.source_config(ref=upstream_ref)
- ]
- }
- element_name = 'test.bst'
- element_path = os.path.join(element_dir, element_name)
- full_repo = element['sources'][0]['url']
- upstream_map, repo_name = os.path.split(full_repo)
- alias = 'foo-' + kind
- aliased_repo = alias + ':' + repo_name
- element['sources'][0]['url'] = aliased_repo
- full_mirror = mirror_repo.source_config()['url']
- mirror_map, _ = os.path.split(full_mirror)
- os.makedirs(element_dir)
- _yaml.dump(element, element_path)
-
- config_project_dir = str(tmpdir.join('config'))
- os.makedirs(config_project_dir, exist_ok=True)
- config_project = {
- 'name': 'config'
- }
- _yaml.dump(config_project, os.path.join(config_project_dir, 'project.conf'))
- extra_mirrors = {
- 'mirrors': [
- {
- 'name': 'middle-earth',
- 'aliases': {
- alias: [mirror_map + "/"],
- }
- }
- ]
- }
- _yaml.dump(extra_mirrors, os.path.join(config_project_dir, 'mirrors.yml'))
- generate_junction(str(tmpdir.join('config_repo')),
- config_project_dir,
- os.path.join(element_dir, 'config.bst'))
-
- project = {
- 'name': 'test',
- 'element-path': 'elements',
- 'aliases': {
- alias: upstream_map + "/"
- },
- '(@)': [
- 'config.bst:mirrors.yml'
- ]
- }
- project_file = os.path.join(project_dir, 'project.conf')
- _yaml.dump(project, project_file)
-
- # Now make the upstream unavailable.
- os.rename(upstream_repo.repo, '{}.bak'.format(upstream_repo.repo))
- result = cli.run(project=project_dir, args=['source', 'fetch', element_name])
- result.assert_main_error(ErrorDomain.STREAM, None)
- # Now make the upstream available again.
- os.rename('{}.bak'.format(upstream_repo.repo), upstream_repo.repo)
- result = cli.run(project=project_dir, args=['source', 'fetch', element_name])
- result.assert_success()
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_mirror_track_upstream_present(cli, tmpdir, datafiles, kind):
- bin_files_path = os.path.join(str(datafiles), 'files', 'bin-files', 'usr')
- dev_files_path = os.path.join(str(datafiles), 'files', 'dev-files', 'usr')
- upstream_repodir = os.path.join(str(tmpdir), 'upstream')
- mirror_repodir = os.path.join(str(tmpdir), 'mirror')
- project_dir = os.path.join(str(tmpdir), 'project')
- os.makedirs(project_dir)
- element_dir = os.path.join(project_dir, 'elements')
-
- # Create repo objects of the upstream and mirror
- upstream_repo = create_repo(kind, upstream_repodir)
- upstream_repo.create(bin_files_path)
- mirror_repo = upstream_repo.copy(mirror_repodir)
- upstream_ref = upstream_repo.create(dev_files_path)
-
- element = {
- 'kind': 'import',
- 'sources': [
- upstream_repo.source_config(ref=upstream_ref)
- ]
- }
-
- element_name = 'test.bst'
- element_path = os.path.join(element_dir, element_name)
- full_repo = element['sources'][0]['url']
- upstream_map, repo_name = os.path.split(full_repo)
- alias = 'foo-' + kind
- aliased_repo = alias + ':' + repo_name
- element['sources'][0]['url'] = aliased_repo
- full_mirror = mirror_repo.source_config()['url']
- mirror_map, _ = os.path.split(full_mirror)
- os.makedirs(element_dir)
- _yaml.dump(element, element_path)
-
- project = {
- 'name': 'test',
- 'element-path': 'elements',
- 'aliases': {
- alias: upstream_map + "/"
- },
- 'mirrors': [
- {
- 'name': 'middle-earth',
- 'aliases': {
- alias: [mirror_map + "/"],
- },
- },
- ]
- }
- project_file = os.path.join(project_dir, 'project.conf')
- _yaml.dump(project, project_file)
-
- result = cli.run(project=project_dir, args=['source', 'track', element_name])
- result.assert_success()
-
- # Tracking tries upstream first. Check the ref is from upstream.
- new_element = _yaml.load(element_path)
- source = _yaml.node_get(new_element, dict, 'sources', [0])
- if 'ref' in source:
- assert _yaml.node_get(source, str, 'ref') == upstream_ref
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_mirror_track_upstream_absent(cli, tmpdir, datafiles, kind):
- bin_files_path = os.path.join(str(datafiles), 'files', 'bin-files', 'usr')
- dev_files_path = os.path.join(str(datafiles), 'files', 'dev-files', 'usr')
- upstream_repodir = os.path.join(str(tmpdir), 'upstream')
- mirror_repodir = os.path.join(str(tmpdir), 'mirror')
- project_dir = os.path.join(str(tmpdir), 'project')
- os.makedirs(project_dir)
- element_dir = os.path.join(project_dir, 'elements')
-
- # Create repo objects of the upstream and mirror
- upstream_repo = create_repo(kind, upstream_repodir)
- upstream_ref = upstream_repo.create(bin_files_path)
- mirror_repo = upstream_repo.copy(mirror_repodir)
- mirror_ref = upstream_ref
- upstream_ref = upstream_repo.create(dev_files_path)
-
- element = {
- 'kind': 'import',
- 'sources': [
- upstream_repo.source_config(ref=upstream_ref)
- ]
- }
-
- element_name = 'test.bst'
- element_path = os.path.join(element_dir, element_name)
- full_repo = element['sources'][0]['url']
- _, repo_name = os.path.split(full_repo)
- alias = 'foo-' + kind
- aliased_repo = alias + ':' + repo_name
- element['sources'][0]['url'] = aliased_repo
- full_mirror = mirror_repo.source_config()['url']
- mirror_map, _ = os.path.split(full_mirror)
- os.makedirs(element_dir)
- _yaml.dump(element, element_path)
-
- project = {
- 'name': 'test',
- 'element-path': 'elements',
- 'aliases': {
- alias: 'http://www.example.com/'
- },
- 'mirrors': [
- {
- 'name': 'middle-earth',
- 'aliases': {
- alias: [mirror_map + "/"],
- },
- },
- ]
- }
- project_file = os.path.join(project_dir, 'project.conf')
- _yaml.dump(project, project_file)
-
- result = cli.run(project=project_dir, args=['source', 'track', element_name])
- result.assert_success()
-
- # Check that tracking fell back to the mirror
- new_element = _yaml.load(element_path)
- source = _yaml.node_get(new_element, dict, 'sources', [0])
- if 'ref' in source:
- assert _yaml.node_get(source, str, 'ref') == mirror_ref
diff --git a/buildstream/testing/_sourcetests/project/elements/base.bst b/buildstream/testing/_sourcetests/project/elements/base.bst
deleted file mode 100644
index 428afa736..000000000
--- a/buildstream/testing/_sourcetests/project/elements/base.bst
+++ /dev/null
@@ -1,5 +0,0 @@
-# elements/base.bst
-
-kind: stack
-depends:
- - base/base-alpine.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/base/base-alpine.bst b/buildstream/testing/_sourcetests/project/elements/base/base-alpine.bst
deleted file mode 100644
index c5833095d..000000000
--- a/buildstream/testing/_sourcetests/project/elements/base/base-alpine.bst
+++ /dev/null
@@ -1,17 +0,0 @@
-kind: import
-
-description: |
- Alpine Linux base for tests
-
- Generated using the `tests/integration-tests/base/generate-base.sh` script.
-
-sources:
- - kind: tar
- base-dir: ''
- (?):
- - arch == "x86-64":
- ref: 3eb559250ba82b64a68d86d0636a6b127aa5f6d25d3601a79f79214dc9703639
- url: "alpine:integration-tests-base.v1.x86_64.tar.xz"
- - arch == "aarch64":
- ref: 431fb5362032ede6f172e70a3258354a8fd71fcbdeb1edebc0e20968c792329a
- url: "alpine:integration-tests-base.v1.aarch64.tar.xz"
diff --git a/buildstream/testing/_sourcetests/project/elements/import-bin.bst b/buildstream/testing/_sourcetests/project/elements/import-bin.bst
deleted file mode 100644
index a847c0c23..000000000
--- a/buildstream/testing/_sourcetests/project/elements/import-bin.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: import
-sources:
-- kind: local
- path: files/bin-files
diff --git a/buildstream/testing/_sourcetests/project/elements/import-dev.bst b/buildstream/testing/_sourcetests/project/elements/import-dev.bst
deleted file mode 100644
index 152a54667..000000000
--- a/buildstream/testing/_sourcetests/project/elements/import-dev.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: import
-sources:
-- kind: local
- path: files/dev-files
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/horsey.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/horsey.bst
deleted file mode 100644
index bd1ffae9c..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/horsey.bst
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: autotools
-depends:
- - multiple_targets/dependency/pony.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/pony.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/pony.bst
deleted file mode 100644
index 3c29b4ea1..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/pony.bst
+++ /dev/null
@@ -1 +0,0 @@
-kind: autotools
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/zebry.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/zebry.bst
deleted file mode 100644
index 98447ab52..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/dependency/zebry.bst
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: autotools
-depends:
- - multiple_targets/dependency/horsey.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/0.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/0.bst
deleted file mode 100644
index a99be06a0..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/0.bst
+++ /dev/null
@@ -1,7 +0,0 @@
-kind: autotools
-description: Root node
-depends:
- - multiple_targets/order/2.bst
- - multiple_targets/order/3.bst
- - filename: multiple_targets/order/run.bst
- type: runtime
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/1.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/1.bst
deleted file mode 100644
index 82b507a62..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/1.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: autotools
-description: Root node
-depends:
- - multiple_targets/order/9.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/2.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/2.bst
deleted file mode 100644
index ee1afae20..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/2.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: autotools
-description: First dependency level
-depends:
- - multiple_targets/order/3.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/3.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/3.bst
deleted file mode 100644
index 4c3a23dab..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/3.bst
+++ /dev/null
@@ -1,6 +0,0 @@
-kind: autotools
-description: Second dependency level
-depends:
- - multiple_targets/order/4.bst
- - multiple_targets/order/5.bst
- - multiple_targets/order/6.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/4.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/4.bst
deleted file mode 100644
index b663a0b52..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/4.bst
+++ /dev/null
@@ -1,2 +0,0 @@
-kind: autotools
-description: Third level dependency
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/5.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/5.bst
deleted file mode 100644
index b9efcf71b..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/5.bst
+++ /dev/null
@@ -1,2 +0,0 @@
-kind: autotools
-description: Fifth level dependency
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/6.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/6.bst
deleted file mode 100644
index 6c19d04e3..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/6.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: autotools
-description: Fourth level dependency
-depends:
- - multiple_targets/order/5.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/7.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/7.bst
deleted file mode 100644
index 6805b3e6d..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/7.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: autotools
-description: Third level dependency
-depends:
- - multiple_targets/order/6.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/8.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/8.bst
deleted file mode 100644
index b8d8964a0..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/8.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: autotools
-description: Second level dependency
-depends:
- - multiple_targets/order/7.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/9.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/9.bst
deleted file mode 100644
index cc13bf3f0..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/9.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: autotools
-description: First level dependency
-depends:
- - multiple_targets/order/8.bst
diff --git a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/run.bst b/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/run.bst
deleted file mode 100644
index 9b3d2446c..000000000
--- a/buildstream/testing/_sourcetests/project/elements/multiple_targets/order/run.bst
+++ /dev/null
@@ -1,2 +0,0 @@
-kind: autotools
-description: Not a root node, yet built at the same time as root nodes
diff --git a/buildstream/testing/_sourcetests/project/files/bar b/buildstream/testing/_sourcetests/project/files/bar
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/testing/_sourcetests/project/files/bar
+++ /dev/null
diff --git a/buildstream/testing/_sourcetests/project/files/bin-files/usr/bin/hello b/buildstream/testing/_sourcetests/project/files/bin-files/usr/bin/hello
deleted file mode 100755
index f534a4083..000000000
--- a/buildstream/testing/_sourcetests/project/files/bin-files/usr/bin/hello
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-echo "Hello !"
diff --git a/buildstream/testing/_sourcetests/project/files/dev-files/usr/include/pony.h b/buildstream/testing/_sourcetests/project/files/dev-files/usr/include/pony.h
deleted file mode 100644
index 40bd0c2e7..000000000
--- a/buildstream/testing/_sourcetests/project/files/dev-files/usr/include/pony.h
+++ /dev/null
@@ -1,12 +0,0 @@
-#ifndef __PONY_H__
-#define __PONY_H__
-
-#define PONY_BEGIN "Once upon a time, there was a pony."
-#define PONY_END "And they lived happily ever after, the end."
-
-#define MAKE_PONY(story) \
- PONY_BEGIN \
- story \
- PONY_END
-
-#endif /* __PONY_H__ */
diff --git a/buildstream/testing/_sourcetests/project/files/etc-files/etc/buildstream/config b/buildstream/testing/_sourcetests/project/files/etc-files/etc/buildstream/config
deleted file mode 100644
index 04204c7c9..000000000
--- a/buildstream/testing/_sourcetests/project/files/etc-files/etc/buildstream/config
+++ /dev/null
@@ -1 +0,0 @@
-config
diff --git a/buildstream/testing/_sourcetests/project/files/foo b/buildstream/testing/_sourcetests/project/files/foo
deleted file mode 100644
index e69de29bb..000000000
--- a/buildstream/testing/_sourcetests/project/files/foo
+++ /dev/null
diff --git a/buildstream/testing/_sourcetests/project/files/source-bundle/llamas.txt b/buildstream/testing/_sourcetests/project/files/source-bundle/llamas.txt
deleted file mode 100644
index f98b24871..000000000
--- a/buildstream/testing/_sourcetests/project/files/source-bundle/llamas.txt
+++ /dev/null
@@ -1 +0,0 @@
-llamas
diff --git a/buildstream/testing/_sourcetests/project/files/sub-project/elements/import-etc.bst b/buildstream/testing/_sourcetests/project/files/sub-project/elements/import-etc.bst
deleted file mode 100644
index f0171990e..000000000
--- a/buildstream/testing/_sourcetests/project/files/sub-project/elements/import-etc.bst
+++ /dev/null
@@ -1,4 +0,0 @@
-kind: import
-sources:
-- kind: local
- path: files/etc-files
diff --git a/buildstream/testing/_sourcetests/project/files/sub-project/files/etc-files/etc/animal.conf b/buildstream/testing/_sourcetests/project/files/sub-project/files/etc-files/etc/animal.conf
deleted file mode 100644
index db8c36cba..000000000
--- a/buildstream/testing/_sourcetests/project/files/sub-project/files/etc-files/etc/animal.conf
+++ /dev/null
@@ -1 +0,0 @@
-animal=Pony
diff --git a/buildstream/testing/_sourcetests/project/files/sub-project/project.conf b/buildstream/testing/_sourcetests/project/files/sub-project/project.conf
deleted file mode 100644
index bbb8414a3..000000000
--- a/buildstream/testing/_sourcetests/project/files/sub-project/project.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-# Project config for frontend build test
-name: subtest
-
-element-path: elements
diff --git a/buildstream/testing/_sourcetests/project/project.conf b/buildstream/testing/_sourcetests/project/project.conf
deleted file mode 100644
index 05b68bfeb..000000000
--- a/buildstream/testing/_sourcetests/project/project.conf
+++ /dev/null
@@ -1,27 +0,0 @@
-# Project config for frontend build test
-name: test
-element-path: elements
-aliases:
- alpine: https://bst-integration-test-images.ams3.cdn.digitaloceanspaces.com/
- project_dir: file://{project_dir}
-options:
- linux:
- type: bool
- description: Whether to expect a linux platform
- default: True
- arch:
- type: arch
- description: Current architecture
- values:
- - x86-64
- - aarch64
-split-rules:
- test:
- - |
- /tests
- - |
- /tests/*
-
-fatal-warnings:
-- bad-element-suffix
-- bad-characters-in-name
diff --git a/buildstream/testing/_sourcetests/source_determinism.py b/buildstream/testing/_sourcetests/source_determinism.py
deleted file mode 100644
index 3a5c264d9..000000000
--- a/buildstream/testing/_sourcetests/source_determinism.py
+++ /dev/null
@@ -1,114 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-#
-
-# Pylint doesn't play well with fixtures and dependency injection from pytest
-# pylint: disable=redefined-outer-name
-
-import os
-import pytest
-
-from buildstream import _yaml
-from .._utils.site import HAVE_SANDBOX
-from .. import create_repo, ALL_REPO_KINDS
-from .. import cli # pylint: disable=unused-import
-
-# Project directory
-TOP_DIR = os.path.dirname(os.path.realpath(__file__))
-DATA_DIR = os.path.join(TOP_DIR, 'project')
-
-
-def create_test_file(*path, mode=0o644, content='content\n'):
- path = os.path.join(*path)
- os.makedirs(os.path.dirname(path), exist_ok=True)
- with open(path, 'w') as f:
- f.write(content)
- os.fchmod(f.fileno(), mode)
-
-
-def create_test_directory(*path, mode=0o644):
- create_test_file(*path, '.keep', content='')
- path = os.path.join(*path)
- os.chmod(path, mode)
-
-
-@pytest.mark.integration
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [*ALL_REPO_KINDS])
-@pytest.mark.skipif(not HAVE_SANDBOX, reason='Only available with a functioning sandbox')
-def test_deterministic_source_umask(cli, tmpdir, datafiles, kind):
- project = str(datafiles)
- element_name = 'list.bst'
- element_path = os.path.join(project, 'elements', element_name)
- repodir = os.path.join(str(tmpdir), 'repo')
- sourcedir = os.path.join(project, 'source')
-
- create_test_file(sourcedir, 'a.txt', mode=0o700)
- create_test_file(sourcedir, 'b.txt', mode=0o755)
- create_test_file(sourcedir, 'c.txt', mode=0o600)
- create_test_file(sourcedir, 'd.txt', mode=0o400)
- create_test_file(sourcedir, 'e.txt', mode=0o644)
- create_test_file(sourcedir, 'f.txt', mode=0o4755)
- create_test_file(sourcedir, 'g.txt', mode=0o2755)
- create_test_file(sourcedir, 'h.txt', mode=0o1755)
- create_test_directory(sourcedir, 'dir-a', mode=0o0700)
- create_test_directory(sourcedir, 'dir-c', mode=0o0755)
- create_test_directory(sourcedir, 'dir-d', mode=0o4755)
- create_test_directory(sourcedir, 'dir-e', mode=0o2755)
- create_test_directory(sourcedir, 'dir-f', mode=0o1755)
-
- repo = create_repo(kind, repodir)
- ref = repo.create(sourcedir)
- source = repo.source_config(ref=ref)
- element = {
- 'kind': 'manual',
- 'depends': [
- {
- 'filename': 'base.bst',
- 'type': 'build'
- }
- ],
- 'sources': [
- source
- ],
- 'config': {
- 'install-commands': [
- 'ls -l >"%{install-root}/ls-l"'
- ]
- }
- }
- _yaml.dump(element, element_path)
-
- def get_value_for_umask(umask):
- checkoutdir = os.path.join(str(tmpdir), 'checkout-{}'.format(umask))
-
- old_umask = os.umask(umask)
-
- try:
- result = cli.run(project=project, args=['build', element_name])
- result.assert_success()
-
- result = cli.run(project=project, args=['artifact', 'checkout', element_name, '--directory', checkoutdir])
- result.assert_success()
-
- with open(os.path.join(checkoutdir, 'ls-l'), 'r') as f:
- return f.read()
- finally:
- os.umask(old_umask)
- cli.remove_artifact_from_cache(project, element_name)
-
- assert get_value_for_umask(0o022) == get_value_for_umask(0o077)
diff --git a/buildstream/testing/_sourcetests/track.py b/buildstream/testing/_sourcetests/track.py
deleted file mode 100644
index 668ea29e5..000000000
--- a/buildstream/testing/_sourcetests/track.py
+++ /dev/null
@@ -1,420 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-#
-
-# Pylint doesn't play well with fixtures and dependency injection from pytest
-# pylint: disable=redefined-outer-name
-
-import os
-import pytest
-
-from buildstream import _yaml
-from buildstream._exceptions import ErrorDomain
-from .._utils import generate_junction, configure_project
-from .. import create_repo, ALL_REPO_KINDS
-from .. import cli # pylint: disable=unused-import
-
-# Project directory
-TOP_DIR = os.path.dirname(os.path.realpath(__file__))
-DATA_DIR = os.path.join(TOP_DIR, 'project')
-
-
-def generate_element(repo, element_path, dep_name=None):
- element = {
- 'kind': 'import',
- 'sources': [
- repo.source_config()
- ]
- }
- if dep_name:
- element['depends'] = [dep_name]
-
- _yaml.dump(element, element_path)
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("ref_storage", [('inline'), ('project.refs')])
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_track(cli, tmpdir, datafiles, ref_storage, kind):
- project = str(datafiles)
- dev_files_path = os.path.join(project, 'files', 'dev-files')
- element_path = os.path.join(project, 'elements')
- element_name = 'track-test-{}.bst'.format(kind)
-
- configure_project(project, {
- 'ref-storage': ref_storage
- })
-
- # Create our repo object of the given source type with
- # the dev files, and then collect the initial ref.
- #
- repo = create_repo(kind, str(tmpdir))
- repo.create(dev_files_path)
-
- # Generate the element
- generate_element(repo, os.path.join(element_path, element_name))
-
- # Assert that a fetch is needed
- assert cli.get_element_state(project, element_name) == 'no reference'
-
- # Now first try to track it
- result = cli.run(project=project, args=['source', 'track', element_name])
- result.assert_success()
-
- # And now fetch it: The Source has probably already cached the
- # latest ref locally, but it is not required to have cached
- # the associated content of the latest ref at track time, that
- # is the job of fetch.
- result = cli.run(project=project, args=['source', 'fetch', element_name])
- result.assert_success()
-
- # Assert that we are now buildable because the source is
- # now cached.
- assert cli.get_element_state(project, element_name) == 'buildable'
-
- # Assert there was a project.refs created, depending on the configuration
- if ref_storage == 'project.refs':
- assert os.path.exists(os.path.join(project, 'project.refs'))
- else:
- assert not os.path.exists(os.path.join(project, 'project.refs'))
-
-
-# NOTE:
-#
-# This test checks that recursive tracking works by observing
-# element states after running a recursive tracking operation.
-#
-# However, this test is ALSO valuable as it stresses the source
-# plugins in a situation where many source plugins are operating
-# at once on the same backing repository.
-#
-# Do not change this test to use a separate 'Repo' per element
-# as that would defeat the purpose of the stress test, otherwise
-# please refactor that aspect into another test.
-#
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("amount", [(1), (10)])
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_track_recurse(cli, tmpdir, datafiles, kind, amount):
- project = str(datafiles)
- dev_files_path = os.path.join(project, 'files', 'dev-files')
- element_path = os.path.join(project, 'elements')
-
- # Try to actually launch as many fetch jobs as possible at the same time
- #
- # This stresses the Source plugins and helps to ensure that
- # they handle concurrent access to the store correctly.
- cli.configure({
- 'scheduler': {
- 'fetchers': amount,
- }
- })
-
- # Create our repo object of the given source type with
- # the dev files, and then collect the initial ref.
- #
- repo = create_repo(kind, str(tmpdir))
- repo.create(dev_files_path)
-
- # Write out our test targets
- element_names = []
- last_element_name = None
- for i in range(amount + 1):
- element_name = 'track-test-{}-{}.bst'.format(kind, i + 1)
- filename = os.path.join(element_path, element_name)
-
- element_names.append(element_name)
-
- generate_element(repo, filename, dep_name=last_element_name)
- last_element_name = element_name
-
- # Assert that a fetch is needed
- states = cli.get_element_states(project, [last_element_name])
- for element_name in element_names:
- assert states[element_name] == 'no reference'
-
- # Now first try to track it
- result = cli.run(project=project, args=[
- 'source', 'track', '--deps', 'all',
- last_element_name])
- result.assert_success()
-
- # And now fetch it: The Source has probably already cached the
- # latest ref locally, but it is not required to have cached
- # the associated content of the latest ref at track time, that
- # is the job of fetch.
- result = cli.run(project=project, args=[
- 'source', 'fetch', '--deps', 'all',
- last_element_name])
- result.assert_success()
-
- # Assert that the base is buildable and the rest are waiting
- states = cli.get_element_states(project, [last_element_name])
- for element_name in element_names:
- if element_name == element_names[0]:
- assert states[element_name] == 'buildable'
- else:
- assert states[element_name] == 'waiting'
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_track_recurse_except(cli, tmpdir, datafiles, kind):
- project = str(datafiles)
- dev_files_path = os.path.join(project, 'files', 'dev-files')
- element_path = os.path.join(project, 'elements')
- element_dep_name = 'track-test-dep-{}.bst'.format(kind)
- element_target_name = 'track-test-target-{}.bst'.format(kind)
-
- # Create our repo object of the given source type with
- # the dev files, and then collect the initial ref.
- #
- repo = create_repo(kind, str(tmpdir))
- repo.create(dev_files_path)
-
- # Write out our test targets
- generate_element(repo, os.path.join(element_path, element_dep_name))
- generate_element(repo, os.path.join(element_path, element_target_name),
- dep_name=element_dep_name)
-
- # Assert that a fetch is needed
- states = cli.get_element_states(project, [element_target_name])
- assert states[element_dep_name] == 'no reference'
- assert states[element_target_name] == 'no reference'
-
- # Now first try to track it
- result = cli.run(project=project, args=[
- 'source', 'track', '--deps', 'all', '--except', element_dep_name,
- element_target_name])
- result.assert_success()
-
- # And now fetch it: The Source has probably already cached the
- # latest ref locally, but it is not required to have cached
- # the associated content of the latest ref at track time, that
- # is the job of fetch.
- result = cli.run(project=project, args=[
- 'source', 'fetch', '--deps', 'none',
- element_target_name])
- result.assert_success()
-
- # Assert that the dependency is buildable and the target is waiting
- states = cli.get_element_states(project, [element_target_name])
- assert states[element_dep_name] == 'no reference'
- assert states[element_target_name] == 'waiting'
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("ref_storage", [('inline'), ('project.refs')])
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_cross_junction(cli, tmpdir, datafiles, ref_storage, kind):
- project = str(datafiles)
- subproject_path = os.path.join(project, 'files', 'sub-project')
- junction_path = os.path.join(project, 'elements', 'junction.bst')
- etc_files = os.path.join(subproject_path, 'files', 'etc-files')
- repo_element_path = os.path.join(subproject_path, 'elements',
- 'import-etc-repo.bst')
-
- configure_project(project, {
- 'ref-storage': ref_storage
- })
-
- repo = create_repo(kind, str(tmpdir.join('element_repo')))
- repo.create(etc_files)
-
- generate_element(repo, repo_element_path)
-
- generate_junction(str(tmpdir.join('junction_repo')),
- subproject_path, junction_path, store_ref=False)
-
- # Track the junction itself first.
- result = cli.run(project=project, args=['source', 'track', 'junction.bst'])
- result.assert_success()
-
- assert cli.get_element_state(project, 'junction.bst:import-etc-repo.bst') == 'no reference'
-
- # Track the cross junction element. -J is not given, it is implied.
- result = cli.run(project=project, args=['source', 'track', 'junction.bst:import-etc-repo.bst'])
-
- if ref_storage == 'inline':
- # This is not allowed to track cross junction without project.refs.
- result.assert_main_error(ErrorDomain.PIPELINE, 'untrackable-sources')
- else:
- result.assert_success()
-
- assert cli.get_element_state(project, 'junction.bst:import-etc-repo.bst') == 'buildable'
-
- assert os.path.exists(os.path.join(project, 'project.refs'))
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("ref_storage", [('inline'), ('project.refs')])
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_track_include(cli, tmpdir, datafiles, ref_storage, kind):
- project = str(datafiles)
- dev_files_path = os.path.join(project, 'files', 'dev-files')
- element_path = os.path.join(project, 'elements')
- element_name = 'track-test-{}.bst'.format(kind)
-
- configure_project(project, {
- 'ref-storage': ref_storage
- })
-
- # Create our repo object of the given source type with
- # the dev files, and then collect the initial ref.
- #
- repo = create_repo(kind, str(tmpdir))
- ref = repo.create(dev_files_path)
-
- # Generate the element
- element = {
- 'kind': 'import',
- '(@)': ['elements/sources.yml']
- }
- sources = {
- 'sources': [
- repo.source_config()
- ]
- }
-
- _yaml.dump(element, os.path.join(element_path, element_name))
- _yaml.dump(sources, os.path.join(element_path, 'sources.yml'))
-
- # Assert that a fetch is needed
- assert cli.get_element_state(project, element_name) == 'no reference'
-
- # Now first try to track it
- result = cli.run(project=project, args=['source', 'track', element_name])
- result.assert_success()
-
- # And now fetch it: The Source has probably already cached the
- # latest ref locally, but it is not required to have cached
- # the associated content of the latest ref at track time, that
- # is the job of fetch.
- result = cli.run(project=project, args=['source', 'fetch', element_name])
- result.assert_success()
-
- # Assert that we are now buildable because the source is
- # now cached.
- assert cli.get_element_state(project, element_name) == 'buildable'
-
- # Assert there was a project.refs created, depending on the configuration
- if ref_storage == 'project.refs':
- assert os.path.exists(os.path.join(project, 'project.refs'))
- else:
- assert not os.path.exists(os.path.join(project, 'project.refs'))
-
- new_sources = _yaml.load(os.path.join(element_path, 'sources.yml'))
-
- # Get all of the sources
- assert 'sources' in new_sources
- sources_list = _yaml.node_get(new_sources, list, 'sources')
- assert len(sources_list) == 1
-
- # Get the first source from the sources list
- new_source = _yaml.node_get(new_sources, dict, 'sources', indices=[0])
- assert 'ref' in new_source
- assert ref == _yaml.node_get(new_source, str, 'ref')
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("ref_storage", [('inline'), ('project.refs')])
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_track_include_junction(cli, tmpdir, datafiles, ref_storage, kind):
- project = str(datafiles)
- dev_files_path = os.path.join(project, 'files', 'dev-files')
- element_path = os.path.join(project, 'elements')
- element_name = 'track-test-{}.bst'.format(kind)
- subproject_path = os.path.join(project, 'files', 'sub-project')
- sub_element_path = os.path.join(subproject_path, 'elements')
- junction_path = os.path.join(element_path, 'junction.bst')
-
- configure_project(project, {
- 'ref-storage': ref_storage
- })
-
- # Create our repo object of the given source type with
- # the dev files, and then collect the initial ref.
- #
- repo = create_repo(kind, str(tmpdir.join('element_repo')))
- repo.create(dev_files_path)
-
- # Generate the element
- element = {
- 'kind': 'import',
- '(@)': ['junction.bst:elements/sources.yml']
- }
- sources = {
- 'sources': [
- repo.source_config()
- ]
- }
-
- _yaml.dump(element, os.path.join(element_path, element_name))
- _yaml.dump(sources, os.path.join(sub_element_path, 'sources.yml'))
-
- generate_junction(str(tmpdir.join('junction_repo')),
- subproject_path, junction_path, store_ref=True)
-
- result = cli.run(project=project, args=['source', 'track', 'junction.bst'])
- result.assert_success()
-
- # Assert that a fetch is needed
- assert cli.get_element_state(project, element_name) == 'no reference'
-
- # Now first try to track it
- result = cli.run(project=project, args=['source', 'track', element_name])
-
- # Assert there was a project.refs created, depending on the configuration
- if ref_storage == 'inline':
- # FIXME: We should expect an error. But only a warning is emitted
- # result.assert_main_error(ErrorDomain.SOURCE, 'tracking-junction-fragment')
-
- assert 'junction.bst:elements/sources.yml: Cannot track source in a fragment from a junction' in result.stderr
- else:
- assert os.path.exists(os.path.join(project, 'project.refs'))
-
- # And now fetch it: The Source has probably already cached the
- # latest ref locally, but it is not required to have cached
- # the associated content of the latest ref at track time, that
- # is the job of fetch.
- result = cli.run(project=project, args=['source', 'fetch', element_name])
- result.assert_success()
-
- # Assert that we are now buildable because the source is
- # now cached.
- assert cli.get_element_state(project, element_name) == 'buildable'
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("ref_storage", [('inline'), ('project.refs')])
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_track_junction_included(cli, tmpdir, datafiles, ref_storage, kind):
- project = str(datafiles)
- element_path = os.path.join(project, 'elements')
- subproject_path = os.path.join(project, 'files', 'sub-project')
- junction_path = os.path.join(element_path, 'junction.bst')
-
- configure_project(project, {
- 'ref-storage': ref_storage,
- '(@)': ['junction.bst:test.yml']
- })
-
- generate_junction(str(tmpdir.join('junction_repo')),
- subproject_path, junction_path, store_ref=False)
-
- result = cli.run(project=project, args=['source', 'track', 'junction.bst'])
- result.assert_success()
diff --git a/buildstream/testing/_sourcetests/track_cross_junction.py b/buildstream/testing/_sourcetests/track_cross_junction.py
deleted file mode 100644
index ece3e0b8f..000000000
--- a/buildstream/testing/_sourcetests/track_cross_junction.py
+++ /dev/null
@@ -1,186 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-#
-
-# Pylint doesn't play well with fixtures and dependency injection from pytest
-# pylint: disable=redefined-outer-name
-
-import os
-import pytest
-
-from buildstream import _yaml
-from .._utils import generate_junction
-from .. import create_repo, ALL_REPO_KINDS
-from .. import cli # pylint: disable=unused-import
-
-# Project directory
-TOP_DIR = os.path.dirname(os.path.realpath(__file__))
-DATA_DIR = os.path.join(TOP_DIR, 'project')
-
-
-def generate_element(repo, element_path, dep_name=None):
- element = {
- 'kind': 'import',
- 'sources': [
- repo.source_config()
- ]
- }
- if dep_name:
- element['depends'] = [dep_name]
-
- _yaml.dump(element, element_path)
-
-
-def generate_import_element(tmpdir, kind, project, name):
- element_name = 'import-{}.bst'.format(name)
- repo_element_path = os.path.join(project, 'elements', element_name)
- files = str(tmpdir.join("imported_files_{}".format(name)))
- os.makedirs(files)
-
- with open(os.path.join(files, '{}.txt'.format(name)), 'w') as f:
- f.write(name)
-
- repo = create_repo(kind, str(tmpdir.join('element_{}_repo'.format(name))))
- repo.create(files)
-
- generate_element(repo, repo_element_path)
-
- return element_name
-
-
-def generate_project(tmpdir, name, config=None):
- if config is None:
- config = {}
-
- project_name = 'project-{}'.format(name)
- subproject_path = os.path.join(str(tmpdir.join(project_name)))
- os.makedirs(os.path.join(subproject_path, 'elements'))
-
- project_conf = {
- 'name': name,
- 'element-path': 'elements'
- }
- project_conf.update(config)
- _yaml.dump(project_conf, os.path.join(subproject_path, 'project.conf'))
-
- return project_name, subproject_path
-
-
-def generate_simple_stack(project, name, dependencies):
- element_name = '{}.bst'.format(name)
- element_path = os.path.join(project, 'elements', element_name)
- element = {
- 'kind': 'stack',
- 'depends': dependencies
- }
- _yaml.dump(element, element_path)
-
- return element_name
-
-
-def generate_cross_element(project, subproject_name, import_name):
- basename, _ = os.path.splitext(import_name)
- return generate_simple_stack(project, 'import-{}-{}'.format(subproject_name, basename),
- [{
- 'junction': '{}.bst'.format(subproject_name),
- 'filename': import_name
- }])
-
-
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_cross_junction_multiple_projects(cli, tmpdir, kind):
- tmpdir = tmpdir.join(kind)
-
- # Generate 3 projects: main, a, b
- _, project = generate_project(tmpdir, 'main', {'ref-storage': 'project.refs'})
- project_a, project_a_path = generate_project(tmpdir, 'a')
- project_b, project_b_path = generate_project(tmpdir, 'b')
-
- # Generate an element with a trackable source for each project
- element_a = generate_import_element(tmpdir, kind, project_a_path, 'a')
- element_b = generate_import_element(tmpdir, kind, project_b_path, 'b')
- element_c = generate_import_element(tmpdir, kind, project, 'c')
-
- # Create some indirections to the elements with dependencies to test --deps
- stack_a = generate_simple_stack(project_a_path, 'stack-a', [element_a])
- stack_b = generate_simple_stack(project_b_path, 'stack-b', [element_b])
-
- # Create junctions for projects a and b in main.
- junction_a = '{}.bst'.format(project_a)
- junction_a_path = os.path.join(project, 'elements', junction_a)
- generate_junction(tmpdir.join('repo_a'), project_a_path, junction_a_path, store_ref=False)
-
- junction_b = '{}.bst'.format(project_b)
- junction_b_path = os.path.join(project, 'elements', junction_b)
- generate_junction(tmpdir.join('repo_b'), project_b_path, junction_b_path, store_ref=False)
-
- # Track the junctions.
- result = cli.run(project=project, args=['source', 'track', junction_a, junction_b])
- result.assert_success()
-
- # Import elements from a and b in to main.
- imported_a = generate_cross_element(project, project_a, stack_a)
- imported_b = generate_cross_element(project, project_b, stack_b)
-
- # Generate a top level stack depending on everything
- all_bst = generate_simple_stack(project, 'all', [imported_a, imported_b, element_c])
-
- # Track without following junctions. But explicitly also track the elements in project a.
- result = cli.run(project=project, args=['source', 'track',
- '--deps', 'all',
- all_bst,
- '{}:{}'.format(junction_a, stack_a)])
- result.assert_success()
-
- # Elements in project b should not be tracked. But elements in project a and main should.
- expected = [element_c,
- '{}:{}'.format(junction_a, element_a)]
- assert set(result.get_tracked_elements()) == set(expected)
-
-
-@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
-def test_track_exceptions(cli, tmpdir, kind):
- tmpdir = tmpdir.join(kind)
-
- _, project = generate_project(tmpdir, 'main', {'ref-storage': 'project.refs'})
- project_a, project_a_path = generate_project(tmpdir, 'a')
-
- element_a = generate_import_element(tmpdir, kind, project_a_path, 'a')
- element_b = generate_import_element(tmpdir, kind, project_a_path, 'b')
-
- all_bst = generate_simple_stack(project_a_path, 'all', [element_a,
- element_b])
-
- junction_a = '{}.bst'.format(project_a)
- junction_a_path = os.path.join(project, 'elements', junction_a)
- generate_junction(tmpdir.join('repo_a'), project_a_path, junction_a_path, store_ref=False)
-
- result = cli.run(project=project, args=['source', 'track', junction_a])
- result.assert_success()
-
- imported_b = generate_cross_element(project, project_a, element_b)
- indirection = generate_simple_stack(project, 'indirection', [imported_b])
-
- result = cli.run(project=project,
- args=['source', 'track', '--deps', 'all',
- '--except', indirection,
- '{}:{}'.format(junction_a, all_bst), imported_b])
- result.assert_success()
-
- expected = ['{}:{}'.format(junction_a, element_a),
- '{}:{}'.format(junction_a, element_b)]
- assert set(result.get_tracked_elements()) == set(expected)
diff --git a/buildstream/testing/_sourcetests/workspace.py b/buildstream/testing/_sourcetests/workspace.py
deleted file mode 100644
index 5218f8f1e..000000000
--- a/buildstream/testing/_sourcetests/workspace.py
+++ /dev/null
@@ -1,161 +0,0 @@
-#
-# Copyright (C) 2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-#
-
-# Pylint doesn't play well with fixtures and dependency injection from pytest
-# pylint: disable=redefined-outer-name
-
-import os
-import shutil
-import pytest
-
-from buildstream import _yaml
-from .. import create_repo, ALL_REPO_KINDS
-from .. import cli # pylint: disable=unused-import
-
-# Project directory
-TOP_DIR = os.path.dirname(os.path.realpath(__file__))
-DATA_DIR = os.path.join(TOP_DIR, 'project')
-
-
-class WorkspaceCreator():
- def __init__(self, cli, tmpdir, datafiles, project_path=None):
- self.cli = cli
- self.tmpdir = tmpdir
- self.datafiles = datafiles
-
- if not project_path:
- project_path = str(datafiles)
- else:
- shutil.copytree(str(datafiles), project_path)
-
- self.project_path = project_path
- self.bin_files_path = os.path.join(project_path, 'files', 'bin-files')
-
- self.workspace_cmd = os.path.join(self.project_path, 'workspace_cmd')
-
- def create_workspace_element(self, kind, track, suffix='', workspace_dir=None,
- element_attrs=None):
- element_name = 'workspace-test-{}{}.bst'.format(kind, suffix)
- element_path = os.path.join(self.project_path, 'elements')
- if not workspace_dir:
- workspace_dir = os.path.join(self.workspace_cmd, element_name)
- if workspace_dir[-4:] == '.bst':
- workspace_dir = workspace_dir[:-4]
-
- # Create our repo object of the given source type with
- # the bin files, and then collect the initial ref.
- repo = create_repo(kind, str(self.tmpdir))
- ref = repo.create(self.bin_files_path)
- if track:
- ref = None
-
- # Write out our test target
- element = {
- 'kind': 'import',
- 'sources': [
- repo.source_config(ref=ref)
- ]
- }
- if element_attrs:
- element = {**element, **element_attrs}
- _yaml.dump(element,
- os.path.join(element_path,
- element_name))
- return element_name, element_path, workspace_dir
-
- def create_workspace_elements(self, kinds, track, suffixs=None, workspace_dir_usr=None,
- element_attrs=None):
-
- element_tuples = []
-
- if suffixs is None:
- suffixs = ['', ] * len(kinds)
- else:
- if len(suffixs) != len(kinds):
- raise "terable error"
-
- for suffix, kind in zip(suffixs, kinds):
- element_name, _, workspace_dir = \
- self.create_workspace_element(kind, track, suffix, workspace_dir_usr,
- element_attrs)
- element_tuples.append((element_name, workspace_dir))
-
- # Assert that there is no reference, a track & fetch is needed
- states = self.cli.get_element_states(self.project_path, [
- e for e, _ in element_tuples
- ])
- if track:
- assert not any(states[e] != 'no reference' for e, _ in element_tuples)
- else:
- assert not any(states[e] != 'fetch needed' for e, _ in element_tuples)
-
- return element_tuples
-
- def open_workspaces(self, kinds, track, suffixs=None, workspace_dir=None,
- element_attrs=None, no_checkout=False):
-
- element_tuples = self.create_workspace_elements(kinds, track, suffixs, workspace_dir,
- element_attrs)
- os.makedirs(self.workspace_cmd, exist_ok=True)
-
- # Now open the workspace, this should have the effect of automatically
- # tracking & fetching the source from the repo.
- args = ['workspace', 'open']
- if track:
- args.append('--track')
- if no_checkout:
- args.append('--no-checkout')
- if workspace_dir is not None:
- assert len(element_tuples) == 1, "test logic error"
- _, workspace_dir = element_tuples[0]
- args.extend(['--directory', workspace_dir])
-
- args.extend([element_name for element_name, workspace_dir_suffix in element_tuples])
- result = self.cli.run(cwd=self.workspace_cmd, project=self.project_path, args=args)
-
- result.assert_success()
-
- if not no_checkout:
- # Assert that we are now buildable because the source is now cached.
- states = self.cli.get_element_states(self.project_path, [
- e for e, _ in element_tuples
- ])
- assert not any(states[e] != 'buildable' for e, _ in element_tuples)
-
- # Check that the executable hello file is found in each workspace
- for _, workspace in element_tuples:
- filename = os.path.join(workspace, 'usr', 'bin', 'hello')
- assert os.path.exists(filename)
-
- return element_tuples
-
-
-def open_workspace(cli, tmpdir, datafiles, kind, track, suffix='', workspace_dir=None,
- project_path=None, element_attrs=None, no_checkout=False):
- workspace_object = WorkspaceCreator(cli, tmpdir, datafiles, project_path)
- workspaces = workspace_object.open_workspaces((kind, ), track, (suffix, ), workspace_dir,
- element_attrs, no_checkout)
- assert len(workspaces) == 1
- element_name, workspace = workspaces[0]
- return element_name, workspace_object.project_path, workspace
-
-
-@pytest.mark.datafiles(DATA_DIR)
-@pytest.mark.parametrize("kind", ALL_REPO_KINDS)
-def test_open(cli, tmpdir, datafiles, kind):
- open_workspace(cli, tmpdir, datafiles, kind, False)
diff --git a/buildstream/testing/_utils/__init__.py b/buildstream/testing/_utils/__init__.py
deleted file mode 100644
index b419d72b7..000000000
--- a/buildstream/testing/_utils/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import os
-
-from buildstream import _yaml
-from .junction import generate_junction
-
-
-def configure_project(path, config):
- config['name'] = 'test'
- config['element-path'] = 'elements'
- _yaml.dump(config, os.path.join(path, 'project.conf'))
diff --git a/buildstream/testing/_utils/junction.py b/buildstream/testing/_utils/junction.py
deleted file mode 100644
index ca059eb8b..000000000
--- a/buildstream/testing/_utils/junction.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import subprocess
-import pytest
-
-from buildstream import _yaml
-from .. import Repo
-from .site import HAVE_GIT, GIT, GIT_ENV
-
-
-# generate_junction()
-#
-# Generates a junction element with a git repository
-#
-# Args:
-# tmpdir: The tmpdir fixture, for storing the generated git repo
-# subproject_path: The path for the subproject, to add to the git repo
-# junction_path: The location to store the generated junction element
-# store_ref: Whether to store the ref in the junction.bst file
-#
-# Returns:
-# (str): The ref
-#
-def generate_junction(tmpdir, subproject_path, junction_path, *, store_ref=True):
- # Create a repo to hold the subproject and generate
- # a junction element for it
- #
- repo = _SimpleGit(str(tmpdir))
- source_ref = ref = repo.create(subproject_path)
- if not store_ref:
- source_ref = None
-
- element = {
- 'kind': 'junction',
- 'sources': [
- repo.source_config(ref=source_ref)
- ]
- }
- _yaml.dump(element, junction_path)
-
- return ref
-
-
-# A barebones Git Repo class to use for generating junctions
-class _SimpleGit(Repo):
- def __init__(self, directory, subdir='repo'):
- if not HAVE_GIT:
- pytest.skip('git is not available')
- super().__init__(directory, subdir)
-
- def create(self, directory):
- self.copy_directory(directory, self.repo)
- self._run_git('init', '.')
- self._run_git('add', '.')
- self._run_git('commit', '-m', 'Initial commit')
- return self.latest_commit()
-
- def latest_commit(self):
- return self._run_git(
- 'rev-parse', 'HEAD',
- stdout=subprocess.PIPE,
- universal_newlines=True,
- ).stdout.strip()
-
- def source_config(self, ref=None, checkout_submodules=None):
- config = {
- 'kind': 'git',
- 'url': 'file://' + self.repo,
- 'track': 'master'
- }
- if ref is not None:
- config['ref'] = ref
- if checkout_submodules is not None:
- config['checkout-submodules'] = checkout_submodules
-
- return config
-
- def _run_git(self, *args, **kwargs):
- argv = [GIT]
- argv.extend(args)
- if 'env' not in kwargs:
- kwargs['env'] = dict(GIT_ENV, PWD=self.repo)
- kwargs.setdefault('cwd', self.repo)
- kwargs.setdefault('check', True)
- return subprocess.run(argv, **kwargs)
diff --git a/buildstream/testing/_utils/site.py b/buildstream/testing/_utils/site.py
deleted file mode 100644
index 54c5b467b..000000000
--- a/buildstream/testing/_utils/site.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Some things resolved about the execution site,
-# so we dont have to repeat this everywhere
-#
-import os
-import sys
-import platform
-
-from buildstream import _site, utils, ProgramNotFoundError
-
-
-try:
- GIT = utils.get_host_tool('git')
- HAVE_GIT = True
- GIT_ENV = {
- 'GIT_AUTHOR_DATE': '1320966000 +0200',
- 'GIT_AUTHOR_NAME': 'tomjon',
- 'GIT_AUTHOR_EMAIL': 'tom@jon.com',
- 'GIT_COMMITTER_DATE': '1320966000 +0200',
- 'GIT_COMMITTER_NAME': 'tomjon',
- 'GIT_COMMITTER_EMAIL': 'tom@jon.com'
- }
-except ProgramNotFoundError:
- GIT = None
- HAVE_GIT = False
- GIT_ENV = dict()
-
-try:
- utils.get_host_tool('bwrap')
- HAVE_BWRAP = True
- HAVE_BWRAP_JSON_STATUS = _site.get_bwrap_version() >= (0, 3, 2)
-except ProgramNotFoundError:
- HAVE_BWRAP = False
- HAVE_BWRAP_JSON_STATUS = False
-
-IS_LINUX = os.getenv('BST_FORCE_BACKEND', sys.platform).startswith('linux')
-IS_WSL = (IS_LINUX and 'Microsoft' in platform.uname().release)
-IS_WINDOWS = (os.name == 'nt')
-
-if not IS_LINUX:
- HAVE_SANDBOX = True # fallback to a chroot sandbox on unix
-elif IS_WSL:
- HAVE_SANDBOX = False # Sandboxes are inoperable under WSL due to lack of FUSE
-elif IS_LINUX and HAVE_BWRAP:
- HAVE_SANDBOX = True
-else:
- HAVE_SANDBOX = False
diff --git a/buildstream/testing/integration.py b/buildstream/testing/integration.py
deleted file mode 100644
index 01635de74..000000000
--- a/buildstream/testing/integration.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#
-# Copyright (C) 2017 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/>.
-"""
-Integration - tools for inspecting the output of plugin integration tests
-=========================================================================
-
-This module contains utilities for inspecting the artifacts produced during
-integration tests.
-"""
-
-import os
-import shutil
-import tempfile
-
-import pytest
-
-
-# Return a list of files relative to the given directory
-def walk_dir(root):
- for dirname, dirnames, filenames in os.walk(root):
- # ensure consistent traversal order, needed for consistent
- # handling of symlinks.
- dirnames.sort()
- filenames.sort()
-
- # print path to all subdirectories first.
- for subdirname in dirnames:
- yield os.path.join(dirname, subdirname)[len(root):]
-
- # print path to all filenames.
- for filename in filenames:
- yield os.path.join(dirname, filename)[len(root):]
-
-
-# Ensure that a directory contains the given filenames.
-def assert_contains(directory, expected):
- missing = set(expected)
- missing.difference_update(walk_dir(directory))
- if missing:
- raise AssertionError("Missing {} expected elements from list: {}"
- .format(len(missing), missing))
-
-
-class IntegrationCache:
-
- def __init__(self, cache):
- self.root = os.path.abspath(cache)
- os.makedirs(cache, exist_ok=True)
-
- # Use the same sources every time
- self.sources = os.path.join(self.root, 'sources')
-
- # Create a temp directory for the duration of the test for
- # the artifacts directory
- try:
- self.cachedir = tempfile.mkdtemp(dir=self.root, prefix='cache-')
- except OSError as e:
- raise AssertionError("Unable to create test directory !") from e
-
-
-@pytest.fixture(scope='session')
-def integration_cache(request):
- # Set the cache dir to the INTEGRATION_CACHE variable, or the
- # default if that is not set.
- if 'INTEGRATION_CACHE' in os.environ:
- cache_dir = os.environ['INTEGRATION_CACHE']
- else:
- cache_dir = os.path.abspath('./integration-cache')
-
- cache = IntegrationCache(cache_dir)
-
- yield cache
-
- # Clean up the artifacts after each test session - we only want to
- # cache sources between tests
- try:
- shutil.rmtree(cache.cachedir)
- except FileNotFoundError:
- pass
- try:
- shutil.rmtree(os.path.join(cache.root, 'cas'))
- except FileNotFoundError:
- pass
diff --git a/buildstream/testing/repo.py b/buildstream/testing/repo.py
deleted file mode 100644
index c1538685d..000000000
--- a/buildstream/testing/repo.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# Copyright (C) 2016-2018 Codethink Limited
-# Copyright (C) 2019 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/>.
-
-"""
-Repo - Utility class for testing source plugins
-===============================================
-
-
-"""
-import os
-import shutil
-
-
-class Repo():
- """Repo()
-
- Abstract class providing scaffolding for generating data to be
- used with various sources. Subclasses of Repo may be registered to
- run through the suite of generic source plugin tests provided in
- buildstream.testing.
-
- Args:
- directory (str): The base temp directory for the test
- subdir (str): The subdir for the repo, in case there is more than one
-
- """
- def __init__(self, directory, subdir='repo'):
-
- # The working directory for the repo object
- #
- self.directory = os.path.abspath(directory)
-
- # The directory the actual repo will be stored in
- self.repo = os.path.join(self.directory, subdir)
-
- os.makedirs(self.repo, exist_ok=True)
-
- def create(self, directory):
- """Create a repository in self.directory and add the initial content
-
- Args:
- directory: A directory with content to commit
-
- Returns:
- (smth): A new ref corresponding to this commit, which can
- be passed as the ref in the Repo.source_config() API.
- """
- raise NotImplementedError("create method has not been implemeted")
-
- def source_config(self, ref=None):
- """
- Args:
- ref (smth): An optional abstract ref object, usually a string.
-
- Returns:
- (dict): A configuration which can be serialized as a
- source when generating an element file on the fly
-
- """
- raise NotImplementedError("source_config method has not been implemeted")
-
- def copy_directory(self, src, dest):
- """ Copies the content of src to the directory dest
-
- Like shutil.copytree(), except dest is expected
- to exist.
-
- Args:
- src (str): The source directory
- dest (str): The destination directory
- """
- for filename in os.listdir(src):
- src_path = os.path.join(src, filename)
- dest_path = os.path.join(dest, filename)
- if os.path.isdir(src_path):
- shutil.copytree(src_path, dest_path)
- else:
- shutil.copy2(src_path, dest_path)
-
- def copy(self, dest):
- """Creates a copy of this repository in the specified destination.
-
- Args:
- dest (str): The destination directory
-
- Returns:
- (Repo): A Repo object for the new repository.
- """
- subdir = self.repo[len(self.directory):].lstrip(os.sep)
- new_dir = os.path.join(dest, subdir)
- os.makedirs(new_dir, exist_ok=True)
- self.copy_directory(self.repo, new_dir)
- repo_type = type(self)
- new_repo = repo_type(dest, subdir)
- return new_repo
diff --git a/buildstream/testing/runcli.py b/buildstream/testing/runcli.py
deleted file mode 100644
index 8b3185143..000000000
--- a/buildstream/testing/runcli.py
+++ /dev/null
@@ -1,883 +0,0 @@
-#
-# Copyright (C) 2017 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/>.
-"""
-runcli - Test fixtures used for running BuildStream commands
-============================================================
-
-:function:'cli' Use result = cli.run([arg1, arg2]) to run buildstream commands
-
-:function:'cli_integration' A variant of the main fixture that keeps persistent
- artifact and source caches. It also does not use
- the click test runner to avoid deadlock issues when
- running `bst shell`, but unfortunately cannot produce
- nice stacktraces.
-
-"""
-
-
-import os
-import re
-import sys
-import shutil
-import tempfile
-import itertools
-import traceback
-from contextlib import contextmanager, ExitStack
-from ruamel import yaml
-import pytest
-
-# XXX Using pytest private internals here
-#
-# We use pytest internals to capture the stdout/stderr during
-# a run of the buildstream CLI. We do this because click's
-# CliRunner convenience API (click.testing module) does not support
-# separation of stdout/stderr.
-#
-from _pytest.capture import MultiCapture, FDCapture, FDCaptureBinary
-
-# Import the main cli entrypoint
-from buildstream._frontend import cli as bst_cli
-from buildstream import _yaml
-from buildstream._cas import CASCache
-from buildstream.element import _get_normal_name, _compose_artifact_name
-
-# Special private exception accessor, for test case purposes
-from buildstream._exceptions import BstError, get_last_exception, get_last_task_error
-from buildstream._protos.buildstream.v2 import artifact_pb2
-
-
-# Wrapper for the click.testing result
-class Result():
-
- def __init__(self,
- exit_code=None,
- exception=None,
- exc_info=None,
- output=None,
- stderr=None):
- self.exit_code = exit_code
- self.exc = exception
- self.exc_info = exc_info
- self.output = output
- self.stderr = stderr
- self.unhandled_exception = False
-
- # The last exception/error state is stored at exception
- # creation time in BstError(), but this breaks down with
- # recoverable errors where code blocks ignore some errors
- # and fallback to alternative branches.
- #
- # For this reason, we just ignore the exception and errors
- # in the case that the exit code reported is 0 (success).
- #
- if self.exit_code != 0:
-
- # Check if buildstream failed to handle an
- # exception, topevel CLI exit should always
- # be a SystemExit exception.
- #
- if not isinstance(exception, SystemExit):
- self.unhandled_exception = True
-
- self.exception = get_last_exception()
- self.task_error_domain, \
- self.task_error_reason = get_last_task_error()
- else:
- self.exception = None
- self.task_error_domain = None
- self.task_error_reason = None
-
- # assert_success()
- #
- # Asserts that the buildstream session completed successfully
- #
- # Args:
- # fail_message (str): An optional message to override the automatic
- # assertion error messages
- # Raises:
- # (AssertionError): If the session did not complete successfully
- #
- def assert_success(self, fail_message=''):
- assert self.exit_code == 0, fail_message
- assert self.exc is None, fail_message
- assert self.exception is None, fail_message
- assert self.unhandled_exception is False
-
- # assert_main_error()
- #
- # Asserts that the buildstream session failed, and that
- # the main process error report is as expected
- #
- # Args:
- # error_domain (ErrorDomain): The domain of the error which occurred
- # error_reason (any): The reason field of the error which occurred
- # fail_message (str): An optional message to override the automatic
- # assertion error messages
- # debug (bool): If true, prints information regarding the exit state of the result()
- # Raises:
- # (AssertionError): If any of the assertions fail
- #
- def assert_main_error(self,
- error_domain,
- error_reason,
- fail_message='',
- *, debug=False):
- if debug:
- print(
- """
- Exit code: {}
- Exception: {}
- Domain: {}
- Reason: {}
- """.format(
- self.exit_code,
- self.exception,
- self.exception.domain,
- self.exception.reason
- ))
- assert self.exit_code == -1, fail_message
- assert self.exc is not None, fail_message
- assert self.exception is not None, fail_message
- assert isinstance(self.exception, BstError), fail_message
- assert self.unhandled_exception is False
-
- assert self.exception.domain == error_domain, fail_message
- assert self.exception.reason == error_reason, fail_message
-
- # assert_task_error()
- #
- # Asserts that the buildstream session failed, and that
- # the child task error which caused buildstream to exit
- # is as expected.
- #
- # Args:
- # error_domain (ErrorDomain): The domain of the error which occurred
- # error_reason (any): The reason field of the error which occurred
- # fail_message (str): An optional message to override the automatic
- # assertion error messages
- # Raises:
- # (AssertionError): If any of the assertions fail
- #
- def assert_task_error(self,
- error_domain,
- error_reason,
- fail_message=''):
-
- assert self.exit_code == -1, fail_message
- assert self.exc is not None, fail_message
- assert self.exception is not None, fail_message
- assert isinstance(self.exception, BstError), fail_message
- assert self.unhandled_exception is False
-
- assert self.task_error_domain == error_domain, fail_message
- assert self.task_error_reason == error_reason, fail_message
-
- # assert_shell_error()
- #
- # Asserts that the buildstream created a shell and that the task in the
- # shell failed.
- #
- # Args:
- # fail_message (str): An optional message to override the automatic
- # assertion error messages
- # Raises:
- # (AssertionError): If any of the assertions fail
- #
- def assert_shell_error(self, fail_message=''):
- assert self.exit_code == 1, fail_message
-
- # get_start_order()
- #
- # Gets the list of elements processed in a given queue, in the
- # order of their first appearances in the session.
- #
- # Args:
- # activity (str): The queue activity name (like 'fetch')
- #
- # Returns:
- # (list): A list of element names in the order which they first appeared in the result
- #
- def get_start_order(self, activity):
- results = re.findall(r'\[\s*{}:(\S+)\s*\]\s*START\s*.*\.log'.format(activity), self.stderr)
- if results is None:
- return []
- return list(results)
-
- # get_tracked_elements()
- #
- # Produces a list of element names on which tracking occurred
- # during the session.
- #
- # This is done by parsing the buildstream stderr log
- #
- # Returns:
- # (list): A list of element names
- #
- def get_tracked_elements(self):
- tracked = re.findall(r'\[\s*track:(\S+)\s*]', self.stderr)
- if tracked is None:
- return []
-
- return list(tracked)
-
- def get_pushed_elements(self):
- pushed = re.findall(r'\[\s*push:(\S+)\s*\]\s*INFO\s*Pushed artifact', self.stderr)
- if pushed is None:
- return []
-
- return list(pushed)
-
- def get_pulled_elements(self):
- pulled = re.findall(r'\[\s*pull:(\S+)\s*\]\s*INFO\s*Pulled artifact', self.stderr)
- if pulled is None:
- return []
-
- return list(pulled)
-
-
-class Cli():
-
- def __init__(self, directory, verbose=True, default_options=None):
- self.directory = directory
- self.config = None
- self.verbose = verbose
- self.artifact = TestArtifact()
-
- if default_options is None:
- default_options = []
-
- self.default_options = default_options
-
- # configure():
- #
- # Serializes a user configuration into a buildstream.conf
- # to use for this test cli.
- #
- # Args:
- # config (dict): The user configuration to use
- #
- def configure(self, config):
- if self.config is None:
- self.config = {}
-
- for key, val in config.items():
- self.config[key] = val
-
- # remove_artifact_from_cache():
- #
- # Remove given element artifact from artifact cache
- #
- # Args:
- # project (str): The project path under test
- # element_name (str): The name of the element artifact
- # cache_dir (str): Specific cache dir to remove artifact from
- #
- def remove_artifact_from_cache(self, project, element_name,
- *, cache_dir=None):
- # Read configuration to figure out where artifacts are stored
- if not cache_dir:
- default = os.path.join(project, 'cache')
-
- if self.config is not None:
- cache_dir = self.config.get('cachedir', default)
- else:
- cache_dir = default
-
- self.artifact.remove_artifact_from_cache(cache_dir, element_name)
-
- # run():
- #
- # Runs buildstream with the given arguments, additionally
- # also passes some global options to buildstream in order
- # to stay contained in the testing environment.
- #
- # Args:
- # configure (bool): Whether to pass a --config argument
- # project (str): An optional path to a project
- # silent (bool): Whether to pass --no-verbose
- # env (dict): Environment variables to temporarily set during the test
- # args (list): A list of arguments to pass buildstream
- # binary_capture (bool): Whether to capture the stdout/stderr as binary
- #
- def run(self, configure=True, project=None, silent=False, env=None,
- cwd=None, options=None, args=None, binary_capture=False):
- if args is None:
- args = []
- if options is None:
- options = []
-
- # We may have been passed e.g. pathlib.Path or py.path
- args = [str(x) for x in args]
- project = str(project)
-
- options = self.default_options + options
-
- with ExitStack() as stack:
- bst_args = ['--no-colors']
-
- if silent:
- bst_args += ['--no-verbose']
-
- if configure:
- config_file = stack.enter_context(
- configured(self.directory, self.config)
- )
- bst_args += ['--config', config_file]
-
- if project:
- bst_args += ['--directory', project]
-
- for option, value in options:
- bst_args += ['--option', option, value]
-
- bst_args += args
-
- if cwd is not None:
- stack.enter_context(chdir(cwd))
-
- if env is not None:
- stack.enter_context(environment(env))
-
- # Ensure we have a working stdout - required to work
- # around a bug that appears to cause AIX to close
- # sys.__stdout__ after setup.py
- try:
- sys.__stdout__.fileno()
- except ValueError:
- sys.__stdout__ = open('/dev/stdout', 'w')
-
- result = self._invoke(bst_cli, bst_args, binary_capture=binary_capture)
-
- # Some informative stdout we can observe when anything fails
- if self.verbose:
- command = "bst " + " ".join(bst_args)
- print("BuildStream exited with code {} for invocation:\n\t{}"
- .format(result.exit_code, command))
- if result.output:
- print("Program output was:\n{}".format(result.output))
- if result.stderr:
- print("Program stderr was:\n{}".format(result.stderr))
-
- if result.exc_info and result.exc_info[0] != SystemExit:
- traceback.print_exception(*result.exc_info)
-
- return result
-
- def _invoke(self, cli_object, args=None, binary_capture=False):
- exc_info = None
- exception = None
- exit_code = 0
-
- # Temporarily redirect sys.stdin to /dev/null to ensure that
- # Popen doesn't attempt to read pytest's dummy stdin.
- old_stdin = sys.stdin
- with open(os.devnull) as devnull:
- sys.stdin = devnull
- capture_kind = FDCaptureBinary if binary_capture else FDCapture
- capture = MultiCapture(out=True, err=True, in_=False, Capture=capture_kind)
- capture.start_capturing()
-
- try:
- cli_object.main(args=args or (), prog_name=cli_object.name)
- except SystemExit as e:
- if e.code != 0:
- exception = e
-
- exc_info = sys.exc_info()
-
- exit_code = e.code
- if not isinstance(exit_code, int):
- sys.stdout.write('Program exit code was not an integer: ')
- sys.stdout.write(str(exit_code))
- sys.stdout.write('\n')
- exit_code = 1
- except Exception as e: # pylint: disable=broad-except
- exception = e
- exit_code = -1
- exc_info = sys.exc_info()
- finally:
- sys.stdout.flush()
-
- sys.stdin = old_stdin
- out, err = capture.readouterr()
- capture.stop_capturing()
-
- return Result(exit_code=exit_code,
- exception=exception,
- exc_info=exc_info,
- output=out,
- stderr=err)
-
- # Fetch an element state by name by
- # invoking bst show on the project with the CLI
- #
- # If you need to get the states of multiple elements,
- # then use get_element_states(s) instead.
- #
- def get_element_state(self, project, element_name):
- result = self.run(project=project, silent=True, args=[
- 'show',
- '--deps', 'none',
- '--format', '%{state}',
- element_name
- ])
- result.assert_success()
- return result.output.strip()
-
- # Fetch the states of elements for a given target / deps
- #
- # Returns a dictionary with the element names as keys
- #
- def get_element_states(self, project, targets, deps='all'):
- result = self.run(project=project, silent=True, args=[
- 'show',
- '--deps', deps,
- '--format', '%{name}||%{state}',
- *targets
- ])
- result.assert_success()
- lines = result.output.splitlines()
- states = {}
- for line in lines:
- split = line.split(sep='||')
- states[split[0]] = split[1]
- return states
-
- # Fetch an element's cache key by invoking bst show
- # on the project with the CLI
- #
- def get_element_key(self, project, element_name):
- result = self.run(project=project, silent=True, args=[
- 'show',
- '--deps', 'none',
- '--format', '%{full-key}',
- element_name
- ])
- result.assert_success()
- return result.output.strip()
-
- # Get the decoded config of an element.
- #
- def get_element_config(self, project, element_name):
- result = self.run(project=project, silent=True, args=[
- 'show',
- '--deps', 'none',
- '--format', '%{config}',
- element_name
- ])
-
- result.assert_success()
- return yaml.safe_load(result.output)
-
- # Fetch the elements that would be in the pipeline with the given
- # arguments.
- #
- def get_pipeline(self, project, elements, except_=None, scope='plan'):
- if except_ is None:
- except_ = []
-
- args = ['show', '--deps', scope, '--format', '%{name}']
- args += list(itertools.chain.from_iterable(zip(itertools.repeat('--except'), except_)))
-
- result = self.run(project=project, silent=True, args=args + elements)
- result.assert_success()
- return result.output.splitlines()
-
- # Fetch an element's complete artifact name, cache_key will be generated
- # if not given.
- #
- def get_artifact_name(self, project, project_name, element_name, cache_key=None):
- if not cache_key:
- cache_key = self.get_element_key(project, element_name)
-
- # Replace path separator and chop off the .bst suffix for normal name
- normal_name = _get_normal_name(element_name)
- return _compose_artifact_name(project_name, normal_name, cache_key)
-
-
-class CliIntegration(Cli):
-
- # run()
- #
- # This supports the same arguments as Cli.run() and additionally
- # it supports the project_config keyword argument.
- #
- # This will first load the project.conf file from the specified
- # project directory ('project' keyword argument) and perform substitutions
- # of any {project_dir} specified in the existing project.conf.
- #
- # If the project_config parameter is specified, it is expected to
- # be a dictionary of additional project configuration options, and
- # will be composited on top of the already loaded project.conf
- #
- def run(self, *args, project_config=None, **kwargs):
-
- # First load the project.conf and substitute {project_dir}
- #
- # Save the original project.conf, because we will run more than
- # once in the same temp directory
- #
- project_directory = kwargs['project']
- project_filename = os.path.join(project_directory, 'project.conf')
- project_backup = os.path.join(project_directory, 'project.conf.backup')
- project_load_filename = project_filename
-
- if not os.path.exists(project_backup):
- shutil.copy(project_filename, project_backup)
- else:
- project_load_filename = project_backup
-
- with open(project_load_filename) as f:
- config = f.read()
- config = config.format(project_dir=project_directory)
-
- if project_config is not None:
-
- # If a custom project configuration dictionary was
- # specified, composite it on top of the already
- # substituted base project configuration
- #
- base_config = _yaml.load_data(config)
-
- # In order to leverage _yaml.composite_dict(), both
- # dictionaries need to be loaded via _yaml.load_data() first
- #
- with tempfile.TemporaryDirectory(dir=project_directory) as scratchdir:
-
- temp_project = os.path.join(scratchdir, 'project.conf')
- with open(temp_project, 'w') as f:
- yaml.safe_dump(project_config, f)
-
- project_config = _yaml.load(temp_project)
-
- _yaml.composite_dict(base_config, project_config)
-
- base_config = _yaml.node_sanitize(base_config)
- _yaml.dump(base_config, project_filename)
-
- else:
-
- # Otherwise, just dump it as is
- with open(project_filename, 'w') as f:
- f.write(config)
-
- return super().run(*args, **kwargs)
-
-
-class CliRemote(CliIntegration):
-
- # ensure_services():
- #
- # Make sure that required services are configured and that
- # non-required ones are not.
- #
- # Args:
- # actions (bool): Whether to use the 'action-cache' service
- # artifacts (bool): Whether to use the 'artifact-cache' service
- # execution (bool): Whether to use the 'execution' service
- # sources (bool): Whether to use the 'source-cache' service
- # storage (bool): Whether to use the 'storage' service
- #
- # Returns a list of configured services (by names).
- #
- def ensure_services(self, actions=True, execution=True, storage=True,
- artifacts=False, sources=False):
- # Build a list of configured services by name:
- configured_services = []
- if not self.config:
- return configured_services
-
- if 'remote-execution' in self.config:
- rexec_config = self.config['remote-execution']
-
- if 'action-cache-service' in rexec_config:
- if actions:
- configured_services.append('action-cache')
- else:
- rexec_config.pop('action-cache-service')
-
- if 'execution-service' in rexec_config:
- if execution:
- configured_services.append('execution')
- else:
- rexec_config.pop('execution-service')
-
- if 'storage-service' in rexec_config:
- if storage:
- configured_services.append('storage')
- else:
- rexec_config.pop('storage-service')
-
- if 'artifacts' in self.config:
- if artifacts:
- configured_services.append('artifact-cache')
- else:
- self.config.pop('artifacts')
-
- if 'source-caches' in self.config:
- if sources:
- configured_services.append('source-cache')
- else:
- self.config.pop('source-caches')
-
- return configured_services
-
-
-class TestArtifact():
-
- # remove_artifact_from_cache():
- #
- # Remove given element artifact from artifact cache
- #
- # Args:
- # cache_dir (str): Specific cache dir to remove artifact from
- # element_name (str): The name of the element artifact
- #
- def remove_artifact_from_cache(self, cache_dir, element_name):
-
- cache_dir = os.path.join(cache_dir, 'artifacts', 'refs')
-
- normal_name = element_name.replace(os.sep, '-')
- cache_dir = os.path.splitext(os.path.join(cache_dir, 'test', normal_name))[0]
- shutil.rmtree(cache_dir)
-
- # is_cached():
- #
- # Check if given element has a cached artifact
- #
- # Args:
- # cache_dir (str): Specific cache dir to check
- # element (Element): The element object
- # element_key (str): The element's cache key
- #
- # Returns:
- # (bool): If the cache contains the element's artifact
- #
- def is_cached(self, cache_dir, element, element_key):
-
- # cas = CASCache(str(cache_dir))
- artifact_ref = element.get_artifact_name(element_key)
- return os.path.exists(os.path.join(cache_dir, 'artifacts', 'refs', artifact_ref))
-
- # get_digest():
- #
- # Get the digest for a given element's artifact files
- #
- # Args:
- # cache_dir (str): Specific cache dir to check
- # element (Element): The element object
- # element_key (str): The element's cache key
- #
- # Returns:
- # (Digest): The digest stored in the ref
- #
- def get_digest(self, cache_dir, element, element_key):
-
- artifact_ref = element.get_artifact_name(element_key)
- artifact_dir = os.path.join(cache_dir, 'artifacts', 'refs')
- artifact_proto = artifact_pb2.Artifact()
- with open(os.path.join(artifact_dir, artifact_ref), 'rb') as f:
- artifact_proto.ParseFromString(f.read())
- return artifact_proto.files
-
- # extract_buildtree():
- #
- # Context manager for extracting an elements artifact buildtree for
- # inspection.
- #
- # Args:
- # tmpdir (LocalPath): pytest fixture for the tests tmp dir
- # digest (Digest): The element directory digest to extract
- #
- # Yields:
- # (str): path to extracted buildtree directory, does not guarantee
- # existence.
- @contextmanager
- def extract_buildtree(self, cache_dir, tmpdir, ref):
- artifact = artifact_pb2.Artifact()
- try:
- with open(os.path.join(cache_dir, 'artifacts', 'refs', ref), 'rb') as f:
- artifact.ParseFromString(f.read())
- except FileNotFoundError:
- yield None
- else:
- if str(artifact.buildtree):
- with self._extract_subdirectory(tmpdir, artifact.buildtree) as f:
- yield f
- else:
- yield None
-
- # _extract_subdirectory():
- #
- # Context manager for extracting an element artifact for inspection,
- # providing an expected path for a given subdirectory
- #
- # Args:
- # tmpdir (LocalPath): pytest fixture for the tests tmp dir
- # digest (Digest): The element directory digest to extract
- # subdir (str): Subdirectory to path
- #
- # Yields:
- # (str): path to extracted subdir directory, does not guarantee
- # existence.
- @contextmanager
- def _extract_subdirectory(self, tmpdir, digest):
- with tempfile.TemporaryDirectory() as extractdir:
- try:
- cas = CASCache(str(tmpdir))
- cas.checkout(extractdir, digest)
- yield extractdir
- except FileNotFoundError:
- yield None
-
-
-# Main fixture
-#
-# Use result = cli.run([arg1, arg2]) to run buildstream commands
-#
-@pytest.fixture()
-def cli(tmpdir):
- directory = os.path.join(str(tmpdir), 'cache')
- os.makedirs(directory)
- return Cli(directory)
-
-
-# A variant of the main fixture that keeps persistent artifact and
-# source caches.
-#
-# It also does not use the click test runner to avoid deadlock issues
-# when running `bst shell`, but unfortunately cannot produce nice
-# stacktraces.
-@pytest.fixture()
-def cli_integration(tmpdir, integration_cache):
- directory = os.path.join(str(tmpdir), 'cache')
- os.makedirs(directory)
-
- if os.environ.get('BST_FORCE_BACKEND') == 'unix':
- fixture = CliIntegration(directory, default_options=[('linux', 'False')])
- else:
- fixture = CliIntegration(directory)
-
- # We want to cache sources for integration tests more permanently,
- # to avoid downloading the huge base-sdk repeatedly
- fixture.configure({
- 'cachedir': integration_cache.cachedir,
- 'sourcedir': integration_cache.sources,
- })
-
- yield fixture
-
- # remove following folders if necessary
- try:
- shutil.rmtree(os.path.join(integration_cache.cachedir, 'build'))
- except FileNotFoundError:
- pass
- try:
- shutil.rmtree(os.path.join(integration_cache.cachedir, 'tmp'))
- except FileNotFoundError:
- pass
-
-
-# A variant of the main fixture that is configured for remote-execution.
-#
-# It also does not use the click test runner to avoid deadlock issues
-# when running `bst shell`, but unfortunately cannot produce nice
-# stacktraces.
-@pytest.fixture()
-def cli_remote_execution(tmpdir, remote_services):
- directory = os.path.join(str(tmpdir), 'cache')
- os.makedirs(directory)
-
- fixture = CliRemote(directory)
-
- if remote_services.artifact_service:
- fixture.configure({'artifacts': [{
- 'url': remote_services.artifact_service,
- }]})
-
- remote_execution = {}
- if remote_services.action_service:
- remote_execution['action-cache-service'] = {
- 'url': remote_services.action_service,
- }
- if remote_services.exec_service:
- remote_execution['execution-service'] = {
- 'url': remote_services.exec_service,
- }
- if remote_services.storage_service:
- remote_execution['storage-service'] = {
- 'url': remote_services.storage_service,
- }
- if remote_execution:
- fixture.configure({'remote-execution': remote_execution})
-
- if remote_services.source_service:
- fixture.configure({'source-caches': [{
- 'url': remote_services.source_service,
- }]})
-
- return fixture
-
-
-@contextmanager
-def chdir(directory):
- old_dir = os.getcwd()
- os.chdir(directory)
- yield
- os.chdir(old_dir)
-
-
-@contextmanager
-def environment(env):
-
- old_env = {}
- for key, value in env.items():
- old_env[key] = os.environ.get(key)
- if value is None:
- os.environ.pop(key, None)
- else:
- os.environ[key] = value
-
- yield
-
- for key, value in old_env.items():
- if value is None:
- os.environ.pop(key, None)
- else:
- os.environ[key] = value
-
-
-@contextmanager
-def configured(directory, config=None):
-
- # Ensure we've at least relocated the caches to a temp directory
- if not config:
- config = {}
-
- if not config.get('sourcedir', False):
- config['sourcedir'] = os.path.join(directory, 'sources')
- if not config.get('cachedir', False):
- config['cachedir'] = directory
- if not config.get('logdir', False):
- config['logdir'] = os.path.join(directory, 'logs')
-
- # Dump it and yield the filename for test scripts to feed it
- # to buildstream as an artument
- filename = os.path.join(directory, "buildstream.conf")
- _yaml.dump(config, filename)
-
- yield filename
diff --git a/buildstream/types.py b/buildstream/types.py
deleted file mode 100644
index d54bf0b6e..000000000
--- a/buildstream/types.py
+++ /dev/null
@@ -1,177 +0,0 @@
-#
-# Copyright (C) 2018 Bloomberg 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>
-# Jim MacArthur <jim.macarthur@codethink.co.uk>
-# Benjamin Schubert <bschubert15@bloomberg.net>
-
-"""
-Foundation types
-================
-
-"""
-
-from enum import Enum
-import heapq
-
-
-class Scope(Enum):
- """Defines the scope of dependencies to include for a given element
- when iterating over the dependency graph in APIs like
- :func:`Element.dependencies() <buildstream.element.Element.dependencies>`
- """
-
- ALL = 1
- """All elements which the given element depends on, following
- all elements required for building. Including the element itself.
- """
-
- BUILD = 2
- """All elements required for building the element, including their
- respective run dependencies. Not including the given element itself.
- """
-
- RUN = 3
- """All elements required for running the element. Including the element
- itself.
- """
-
- NONE = 4
- """Just the element itself, no dependencies.
-
- *Since: 1.4*
- """
-
-
-class Consistency():
- """Defines the various consistency states of a :class:`.Source`.
- """
-
- INCONSISTENT = 0
- """Inconsistent
-
- Inconsistent sources have no explicit reference set. They cannot
- produce a cache key, be fetched or staged. They can only be tracked.
- """
-
- RESOLVED = 1
- """Resolved
-
- Resolved sources have a reference and can produce a cache key and
- be fetched, however they cannot be staged.
- """
-
- CACHED = 2
- """Cached
-
- Sources have a cached unstaged copy in the source directory.
- """
-
-
-class CoreWarnings():
- """CoreWarnings()
-
- Some common warnings which are raised by core functionalities within BuildStream are found in this class.
- """
-
- OVERLAPS = "overlaps"
- """
- This warning will be produced when buildstream detects an overlap on an element
- which is not whitelisted. See :ref:`Overlap Whitelist <public_overlap_whitelist>`
- """
-
- REF_NOT_IN_TRACK = "ref-not-in-track"
- """
- This warning will be produced when a source is configured with a reference
- which is found to be invalid based on the configured track
- """
-
- BAD_ELEMENT_SUFFIX = "bad-element-suffix"
- """
- This warning will be produced when an element whose name does not end in .bst
- is referenced either on the command line or by another element
- """
-
- BAD_CHARACTERS_IN_NAME = "bad-characters-in-name"
- """
- This warning will be produces when filename for a target contains invalid
- characters in its name.
- """
-
-
-# _KeyStrength():
-#
-# Strength of cache key
-#
-class _KeyStrength(Enum):
-
- # Includes strong cache keys of all build dependencies and their
- # runtime dependencies.
- STRONG = 1
-
- # Includes names of direct build dependencies but does not include
- # cache keys of dependencies.
- WEAK = 2
-
-
-# _UniquePriorityQueue():
-#
-# Implements a priority queue that adds only each key once.
-#
-# The queue will store and priority based on a tuple (key, item).
-#
-class _UniquePriorityQueue:
-
- def __init__(self):
- self._items = set()
- self._heap = []
-
- # push():
- #
- # Push a new item in the queue.
- #
- # If the item is already present in the queue as identified by the key,
- # this is a noop.
- #
- # Args:
- # key (hashable, comparable): unique key to use for checking for
- # the object's existence and used for
- # ordering
- # item (any): item to push to the queue
- #
- def push(self, key, item):
- if key not in self._items:
- self._items.add(key)
- heapq.heappush(self._heap, (key, item))
-
- # pop():
- #
- # Pop the next item from the queue, by priority order.
- #
- # Returns:
- # (any): the next item
- #
- # Throw:
- # IndexError: when the list is empty
- #
- def pop(self):
- key, item = heapq.heappop(self._heap)
- self._items.remove(key)
- return item
-
- def __len__(self):
- return len(self._heap)
diff --git a/buildstream/utils.py b/buildstream/utils.py
deleted file mode 100644
index ade593750..000000000
--- a/buildstream/utils.py
+++ /dev/null
@@ -1,1293 +0,0 @@
-#
-# Copyright (C) 2016-2018 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>
-"""
-Utilities
-=========
-"""
-
-import calendar
-import errno
-import hashlib
-import os
-import re
-import shutil
-import signal
-import stat
-from stat import S_ISDIR
-import string
-import subprocess
-import tempfile
-import itertools
-from contextlib import contextmanager
-
-import psutil
-
-from . import _signals
-from ._exceptions import BstError, ErrorDomain
-from ._protos.build.bazel.remote.execution.v2 import remote_execution_pb2
-
-# The magic number for timestamps: 2011-11-11 11:11:11
-_magic_timestamp = calendar.timegm([2011, 11, 11, 11, 11, 11])
-
-
-# The separator we use for user specified aliases
-_ALIAS_SEPARATOR = ':'
-_URI_SCHEMES = ["http", "https", "ftp", "file", "git", "sftp", "ssh"]
-
-
-class UtilError(BstError):
- """Raised by utility functions when system calls fail.
-
- This will be handled internally by the BuildStream core,
- if you need to handle this error, then it should be reraised,
- or either of the :class:`.ElementError` or :class:`.SourceError`
- exceptions should be raised from this error.
- """
- def __init__(self, message, reason=None):
- super().__init__(message, domain=ErrorDomain.UTIL, reason=reason)
-
-
-class ProgramNotFoundError(BstError):
- """Raised if a required program is not found.
-
- It is normally unneeded to handle this exception from plugin code.
- """
- def __init__(self, message, reason=None):
- super().__init__(message, domain=ErrorDomain.PROG_NOT_FOUND, reason=reason)
-
-
-class DirectoryExistsError(OSError):
- """Raised when a `os.rename` is attempted but the destination is an existing directory.
- """
-
-
-class FileListResult():
- """An object which stores the result of one of the operations
- which run on a list of files.
- """
-
- def __init__(self):
-
- self.overwritten = []
- """List of files which were overwritten in the target directory"""
-
- self.ignored = []
- """List of files which were ignored, because they would have
- replaced a non empty directory"""
-
- self.failed_attributes = []
- """List of files for which attributes could not be copied over"""
-
- self.files_written = []
- """List of files that were written."""
-
- def combine(self, other):
- """Create a new FileListResult that contains the results of both.
- """
- ret = FileListResult()
-
- ret.overwritten = self.overwritten + other.overwritten
- ret.ignored = self.ignored + other.ignored
- ret.failed_attributes = self.failed_attributes + other.failed_attributes
- ret.files_written = self.files_written + other.files_written
-
- return ret
-
-
-def list_relative_paths(directory):
- """A generator for walking directory relative paths
-
- This generator is useful for checking the full manifest of
- a directory.
-
- Symbolic links will not be followed, but will be included
- in the manifest.
-
- Args:
- directory (str): The directory to list files in
-
- Yields:
- Relative filenames in `directory`
- """
- for (dirpath, dirnames, filenames) in os.walk(directory):
-
- # os.walk does not decend into symlink directories, which
- # makes sense because otherwise we might have redundant
- # directories, or end up descending into directories outside
- # of the walk() directory.
- #
- # But symlinks to directories are still identified as
- # subdirectories in the walked `dirpath`, so we extract
- # these symlinks from `dirnames` and add them to `filenames`.
- #
- for d in dirnames:
- fullpath = os.path.join(dirpath, d)
- if os.path.islink(fullpath):
- filenames.append(d)
-
- # Modifying the dirnames directly ensures that the os.walk() generator
- # allows us to specify the order in which they will be iterated.
- dirnames.sort()
- filenames.sort()
-
- relpath = os.path.relpath(dirpath, directory)
-
- # We don't want "./" pre-pended to all the entries in the root of
- # `directory`, prefer to have no prefix in that case.
- basepath = relpath if relpath != '.' and dirpath != directory else ''
-
- # First yield the walked directory itself, except for the root
- if basepath != '':
- yield basepath
-
- # List the filenames in the walked directory
- for f in filenames:
- yield os.path.join(basepath, f)
-
-
-# pylint: disable=anomalous-backslash-in-string
-def glob(paths, pattern):
- """A generator to yield paths which match the glob pattern
-
- Args:
- paths (iterable): The paths to check
- pattern (str): A glob pattern
-
- This generator will iterate over the passed *paths* and
- yield only the filenames which matched the provided *pattern*.
-
- +--------+------------------------------------------------------------------+
- | Meta | Description |
- +========+==================================================================+
- | \* | Zero or more of any character, excepting path separators |
- +--------+------------------------------------------------------------------+
- | \** | Zero or more of any character, including path separators |
- +--------+------------------------------------------------------------------+
- | ? | One of any character, except for path separators |
- +--------+------------------------------------------------------------------+
- | [abc] | One of any of the specified characters |
- +--------+------------------------------------------------------------------+
- | [a-z] | One of the characters in the specified range |
- +--------+------------------------------------------------------------------+
- | [!abc] | Any single character, except the specified characters |
- +--------+------------------------------------------------------------------+
- | [!a-z] | Any single character, except those in the specified range |
- +--------+------------------------------------------------------------------+
-
- .. note::
-
- Escaping of the metacharacters is not possible
-
- """
- # Ensure leading slash, just because we want patterns
- # to match file lists regardless of whether the patterns
- # or file lists had a leading slash or not.
- if not pattern.startswith(os.sep):
- pattern = os.sep + pattern
-
- expression = _glob2re(pattern)
- regexer = re.compile(expression)
-
- for filename in paths:
- filename_try = filename
- if not filename_try.startswith(os.sep):
- filename_try = os.sep + filename_try
-
- if regexer.match(filename_try):
- yield filename
-
-
-def sha256sum(filename):
- """Calculate the sha256sum of a file
-
- Args:
- filename (str): A path to a file on disk
-
- Returns:
- (str): An sha256 checksum string
-
- Raises:
- UtilError: In the case there was an issue opening
- or reading `filename`
- """
- try:
- h = hashlib.sha256()
- with open(filename, "rb") as f:
- for chunk in iter(lambda: f.read(65536), b""):
- h.update(chunk)
-
- except OSError as e:
- raise UtilError("Failed to get a checksum of file '{}': {}"
- .format(filename, e)) from e
-
- return h.hexdigest()
-
-
-def safe_copy(src, dest, *, result=None):
- """Copy a file while preserving attributes
-
- Args:
- src (str): The source filename
- dest (str): The destination filename
- result (:class:`~.FileListResult`): An optional collective result
-
- Raises:
- UtilError: In the case of unexpected system call failures
-
- This is almost the same as shutil.copy2(), except that
- we unlink *dest* before overwriting it if it exists, just
- incase *dest* is a hardlink to a different file.
- """
- # First unlink the target if it exists
- try:
- os.unlink(dest)
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise UtilError("Failed to remove destination file '{}': {}"
- .format(dest, e)) from e
-
- shutil.copyfile(src, dest)
- try:
- shutil.copystat(src, dest)
- except PermissionError:
- # If we failed to copy over some file stats, dont treat
- # it as an unrecoverable error, but provide some feedback
- # we can use for a warning.
- #
- # This has a tendency of happening when attempting to copy
- # over extended file attributes.
- if result:
- result.failed_attributes.append(dest)
-
- except shutil.Error as e:
- raise UtilError("Failed to copy '{} -> {}': {}"
- .format(src, dest, e)) from e
-
-
-def safe_link(src, dest, *, result=None, _unlink=False):
- """Try to create a hardlink, but resort to copying in the case of cross device links.
-
- Args:
- src (str): The source filename
- dest (str): The destination filename
- result (:class:`~.FileListResult`): An optional collective result
-
- Raises:
- UtilError: In the case of unexpected system call failures
- """
-
- if _unlink:
- # First unlink the target if it exists
- try:
- os.unlink(dest)
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise UtilError("Failed to remove destination file '{}': {}"
- .format(dest, e)) from e
-
- # If we can't link it due to cross-device hardlink, copy
- try:
- os.link(src, dest)
- except OSError as e:
- if e.errno == errno.EEXIST and not _unlink:
- # Target exists already, unlink and try again
- safe_link(src, dest, result=result, _unlink=True)
- elif e.errno == errno.EXDEV:
- safe_copy(src, dest)
- else:
- raise UtilError("Failed to link '{} -> {}': {}"
- .format(src, dest, e)) from e
-
-
-def safe_remove(path):
- """Removes a file or directory
-
- This will remove a file if it exists, and will
- remove a directory if the directory is empty.
-
- Args:
- path (str): The path to remove
-
- Returns:
- True if `path` was removed or did not exist, False
- if `path` was a non empty directory.
-
- Raises:
- UtilError: In the case of unexpected system call failures
- """
- try:
- if S_ISDIR(os.lstat(path).st_mode):
- os.rmdir(path)
- else:
- os.unlink(path)
-
- # File removed/unlinked successfully
- return True
-
- except OSError as e:
- if e.errno == errno.ENOTEMPTY:
- # Path is non-empty directory
- return False
- elif e.errno == errno.ENOENT:
- # Path does not exist
- return True
-
- raise UtilError("Failed to remove '{}': {}"
- .format(path, e))
-
-
-def copy_files(src, dest, *, filter_callback=None, ignore_missing=False, report_written=False):
- """Copy files from source to destination.
-
- Args:
- src (str): The source file or directory
- dest (str): The destination directory
- filter_callback (callable): Optional filter callback. Called with the relative path as
- argument for every file in the source directory. The file is
- copied only if the callable returns True. If no filter callback
- is specified, all files will be copied.
- ignore_missing (bool): Dont raise any error if a source file is missing
- report_written (bool): Add to the result object the full list of files written
-
- Returns:
- (:class:`~.FileListResult`): The result describing what happened during this file operation
-
- Raises:
- UtilError: In the case of unexpected system call failures
-
- .. note::
-
- Directories in `dest` are replaced with files from `src`,
- unless the existing directory in `dest` is not empty in which
- case the path will be reported in the return value.
-
- UNIX domain socket files from `src` are ignored.
- """
- result = FileListResult()
- try:
- _process_list(src, dest, safe_copy, result,
- filter_callback=filter_callback,
- ignore_missing=ignore_missing,
- report_written=report_written)
- except OSError as e:
- raise UtilError("Failed to copy '{} -> {}': {}"
- .format(src, dest, e))
- return result
-
-
-def link_files(src, dest, *, filter_callback=None, ignore_missing=False, report_written=False):
- """Hardlink files from source to destination.
-
- Args:
- src (str): The source file or directory
- dest (str): The destination directory
- filter_callback (callable): Optional filter callback. Called with the relative path as
- argument for every file in the source directory. The file is
- hardlinked only if the callable returns True. If no filter
- callback is specified, all files will be hardlinked.
- ignore_missing (bool): Dont raise any error if a source file is missing
- report_written (bool): Add to the result object the full list of files written
-
- Returns:
- (:class:`~.FileListResult`): The result describing what happened during this file operation
-
- Raises:
- UtilError: In the case of unexpected system call failures
-
- .. note::
-
- Directories in `dest` are replaced with files from `src`,
- unless the existing directory in `dest` is not empty in which
- case the path will be reported in the return value.
-
- .. note::
-
- If a hardlink cannot be created due to crossing filesystems,
- then the file will be copied instead.
-
- UNIX domain socket files from `src` are ignored.
- """
- result = FileListResult()
- try:
- _process_list(src, dest, safe_link, result,
- filter_callback=filter_callback,
- ignore_missing=ignore_missing,
- report_written=report_written)
- except OSError as e:
- raise UtilError("Failed to link '{} -> {}': {}"
- .format(src, dest, e))
-
- return result
-
-
-def get_host_tool(name):
- """Get the full path of a host tool
-
- Args:
- name (str): The name of the program to search for
-
- Returns:
- The full path to the program, if found
-
- Raises:
- :class:`.ProgramNotFoundError`
- """
- search_path = os.environ.get('PATH')
- program_path = shutil.which(name, path=search_path)
-
- if not program_path:
- raise ProgramNotFoundError("Did not find '{}' in PATH: {}".format(name, search_path))
-
- return program_path
-
-
-def url_directory_name(url):
- """Normalizes a url into a directory name
-
- Args:
- url (str): A url string
-
- Returns:
- A string which can be used as a directory name
- """
- valid_chars = string.digits + string.ascii_letters + '%_'
-
- def transl(x):
- return x if x in valid_chars else '_'
-
- return ''.join([transl(x) for x in url])
-
-
-def get_bst_version():
- """Gets the major, minor release portion of the
- BuildStream version.
-
- Returns:
- (int): The major version
- (int): The minor version
- """
- # Import this only conditionally, it's not resolved at bash complete time
- from . import __version__ # pylint: disable=cyclic-import
- versions = __version__.split('.')[:2]
-
- if versions[0] == '0+untagged':
- raise UtilError("Your git repository has no tags - BuildStream can't "
- "determine its version. Please run `git fetch --tags`.")
-
- try:
- return (int(versions[0]), int(versions[1]))
- except IndexError:
- raise UtilError("Cannot detect Major and Minor parts of the version\n"
- "Version: {} not in XX.YY.whatever format"
- .format(__version__))
- except ValueError:
- raise UtilError("Cannot convert version to integer numbers\n"
- "Version: {} not in Integer.Integer.whatever format"
- .format(__version__))
-
-
-def move_atomic(source, destination, *, ensure_parents=True):
- """Move the source to the destination using atomic primitives.
-
- This uses `os.rename` to move a file or directory to a new destination.
- It wraps some `OSError` thrown errors to ensure their handling is correct.
-
- The main reason for this to exist is that rename can throw different errors
- for the same symptom (https://www.unix.com/man-page/POSIX/3posix/rename/)
- when we are moving a directory.
-
- We are especially interested here in the case when the destination already
- exists, is a directory and is not empty. In this case, either EEXIST or
- ENOTEMPTY can be thrown.
-
- In order to ensure consistent handling of these exceptions, this function
- should be used instead of `os.rename`
-
- Args:
- source (str or Path): source to rename
- destination (str or Path): destination to which to move the source
- ensure_parents (bool): Whether or not to create the parent's directories
- of the destination (default: True)
- Raises:
- DirectoryExistsError: if the destination directory already exists and is
- not empty
- OSError: if another filesystem level error occured
- """
- if ensure_parents:
- os.makedirs(os.path.dirname(str(destination)), exist_ok=True)
-
- try:
- os.rename(str(source), str(destination))
- except OSError as exc:
- if exc.errno in (errno.EEXIST, errno.ENOTEMPTY):
- raise DirectoryExistsError(*exc.args) from exc
- raise
-
-
-@contextmanager
-def save_file_atomic(filename, mode='w', *, buffering=-1, encoding=None,
- errors=None, newline=None, closefd=True, opener=None, tempdir=None):
- """Save a file with a temporary name and rename it into place when ready.
-
- This is a context manager which is meant for saving data to files.
- The data is written to a temporary file, which gets renamed to the target
- name when the context is closed. This avoids readers of the file from
- getting an incomplete file.
-
- **Example:**
-
- .. code:: python
-
- with save_file_atomic('/path/to/foo', 'w') as f:
- f.write(stuff)
-
- The file will be called something like ``tmpCAFEBEEF`` until the
- context block ends, at which point it gets renamed to ``foo``. The
- temporary file will be created in the same directory as the output file.
- The ``filename`` parameter must be an absolute path.
-
- If an exception occurs or the process is terminated, the temporary file will
- be deleted.
- """
- # This feature has been proposed for upstream Python in the past, e.g.:
- # https://bugs.python.org/issue8604
-
- assert os.path.isabs(filename), "The utils.save_file_atomic() parameter ``filename`` must be an absolute path"
- if tempdir is None:
- tempdir = os.path.dirname(filename)
- fd, tempname = tempfile.mkstemp(dir=tempdir)
- os.close(fd)
-
- f = open(tempname, mode=mode, buffering=buffering, encoding=encoding,
- errors=errors, newline=newline, closefd=closefd, opener=opener)
-
- def cleanup_tempfile():
- f.close()
- try:
- os.remove(tempname)
- except FileNotFoundError:
- pass
- except OSError as e:
- raise UtilError("Failed to cleanup temporary file {}: {}".format(tempname, e)) from e
-
- try:
- with _signals.terminator(cleanup_tempfile):
- f.real_filename = filename
- yield f
- f.close()
- # This operation is atomic, at least on platforms we care about:
- # https://bugs.python.org/issue8828
- os.replace(tempname, filename)
- except Exception:
- cleanup_tempfile()
- raise
-
-
-# _get_dir_size():
-#
-# Get the disk usage of a given directory in bytes.
-#
-# This function assumes that files do not inadvertantly
-# disappear while this function is running.
-#
-# Arguments:
-# (str) The path whose size to check.
-#
-# Returns:
-# (int) The size on disk in bytes.
-#
-def _get_dir_size(path):
- path = os.path.abspath(path)
-
- def get_size(path):
- total = 0
-
- for f in os.scandir(path):
- total += f.stat(follow_symlinks=False).st_size
-
- if f.is_dir(follow_symlinks=False):
- total += get_size(f.path)
-
- return total
-
- return get_size(path)
-
-
-# _get_volume_size():
-#
-# Gets the overall usage and total size of a mounted filesystem in bytes.
-#
-# Args:
-# path (str): The path to check
-#
-# Returns:
-# (int): The total number of bytes on the volume
-# (int): The number of available bytes on the volume
-#
-def _get_volume_size(path):
- try:
- stat_ = os.statvfs(path)
- except OSError as e:
- raise UtilError("Failed to retrieve stats on volume for path '{}': {}"
- .format(path, e)) from e
-
- return stat_.f_bsize * stat_.f_blocks, stat_.f_bsize * stat_.f_bavail
-
-
-# _parse_size():
-#
-# Convert a string representing data size to a number of
-# bytes. E.g. "2K" -> 2048.
-#
-# This uses the same format as systemd's
-# [resource-control](https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#).
-#
-# Arguments:
-# size (str) The string to parse
-# volume (str) A path on the volume to consider for percentage
-# specifications
-#
-# Returns:
-# (int|None) The number of bytes, or None if 'infinity' was specified.
-#
-# Raises:
-# UtilError if the string is not a valid data size.
-#
-def _parse_size(size, volume):
- if size == 'infinity':
- return None
-
- matches = re.fullmatch(r'([0-9]+\.?[0-9]*)([KMGT%]?)', size)
- if matches is None:
- raise UtilError("{} is not a valid data size.".format(size))
-
- num, unit = matches.groups()
-
- if unit == '%':
- num = float(num)
- if num > 100:
- raise UtilError("{}% is not a valid percentage value.".format(num))
-
- disk_size, _ = _get_volume_size(volume)
-
- return disk_size * (num / 100)
-
- units = ('', 'K', 'M', 'G', 'T')
- return int(num) * 1024**units.index(unit)
-
-
-# _pretty_size()
-#
-# Converts a number of bytes into a string representation in KiB, MiB, GiB, TiB
-# represented as K, M, G, T etc.
-#
-# Args:
-# size (int): The size to convert in bytes.
-# dec_places (int): The number of decimal places to output to.
-#
-# Returns:
-# (str): The string representation of the number of bytes in the largest
-def _pretty_size(size, dec_places=0):
- psize = size
- unit = 'B'
- units = ('B', 'K', 'M', 'G', 'T')
- for unit in units:
- if psize < 1024:
- break
- elif unit != units[-1]:
- psize /= 1024
- return "{size:g}{unit}".format(size=round(psize, dec_places), unit=unit)
-
-
-# Main process pid
-_main_pid = os.getpid()
-
-
-# _is_main_process()
-#
-# Return whether we are in the main process or not.
-#
-def _is_main_process():
- assert _main_pid is not None
- return os.getpid() == _main_pid
-
-
-# Recursively remove directories, ignoring file permissions as much as
-# possible.
-def _force_rmtree(rootpath, **kwargs):
- for root, dirs, _ in os.walk(rootpath):
- for d in dirs:
- path = os.path.join(root, d.lstrip('/'))
- if os.path.exists(path) and not os.path.islink(path):
- try:
- os.chmod(path, 0o755)
- except OSError as e:
- raise UtilError("Failed to ensure write permission on file '{}': {}"
- .format(path, e))
-
- try:
- shutil.rmtree(rootpath, **kwargs)
- except OSError as e:
- raise UtilError("Failed to remove cache directory '{}': {}"
- .format(rootpath, e))
-
-
-# Recursively make directories in target area
-def _copy_directories(srcdir, destdir, target):
- this_dir = os.path.dirname(target)
- new_dir = os.path.join(destdir, this_dir)
-
- if not os.path.lexists(new_dir):
- if this_dir:
- yield from _copy_directories(srcdir, destdir, this_dir)
-
- old_dir = os.path.join(srcdir, this_dir)
- if os.path.lexists(old_dir):
- dir_stat = os.lstat(old_dir)
- mode = dir_stat.st_mode
-
- if stat.S_ISDIR(mode) or stat.S_ISLNK(mode):
- os.makedirs(new_dir)
- yield (new_dir, mode)
- else:
- raise UtilError('Source directory tree has file where '
- 'directory expected: {}'.format(old_dir))
-
-
-# _ensure_real_directory()
-#
-# Ensure `path` is a real directory and there are no symlink components.
-#
-# Symlink components are allowed in `root`.
-#
-def _ensure_real_directory(root, path):
- destpath = root
- for name in os.path.split(path):
- destpath = os.path.join(destpath, name)
- try:
- deststat = os.lstat(destpath)
- if not stat.S_ISDIR(deststat.st_mode):
- relpath = destpath[len(root):]
-
- if stat.S_ISLNK(deststat.st_mode):
- filetype = 'symlink'
- elif stat.S_ISREG(deststat.st_mode):
- filetype = 'regular file'
- else:
- filetype = 'special file'
-
- raise UtilError('Destination is a {}, not a directory: {}'.format(filetype, relpath))
- except FileNotFoundError:
- os.makedirs(destpath)
-
-
-# _process_list()
-#
-# Internal helper for copying/moving/linking file lists
-#
-# This will handle directories, symlinks and special files
-# internally, the `actionfunc` will only be called for regular files.
-#
-# Args:
-# srcdir: The source base directory
-# destdir: The destination base directory
-# actionfunc: The function to call for regular files
-# result: The FileListResult
-# filter_callback: Optional callback to invoke for every directory entry
-# ignore_missing: Dont raise any error if a source file is missing
-#
-#
-def _process_list(srcdir, destdir, actionfunc, result,
- filter_callback=None,
- ignore_missing=False, report_written=False):
-
- # Keep track of directory permissions, since these need to be set
- # *after* files have been written.
- permissions = []
-
- filelist = list_relative_paths(srcdir)
-
- if filter_callback:
- filelist = [path for path in filelist if filter_callback(path)]
-
- # Now walk the list
- for path in filelist:
- srcpath = os.path.join(srcdir, path)
- destpath = os.path.join(destdir, path)
-
- # Ensure that the parent of the destination path exists without symlink
- # components.
- _ensure_real_directory(destdir, os.path.dirname(path))
-
- # Add to the results the list of files written
- if report_written:
- result.files_written.append(path)
-
- # Collect overlaps
- if os.path.lexists(destpath) and not os.path.isdir(destpath):
- result.overwritten.append(path)
-
- # The destination directory may not have been created separately
- permissions.extend(_copy_directories(srcdir, destdir, path))
-
- try:
- file_stat = os.lstat(srcpath)
- mode = file_stat.st_mode
-
- except FileNotFoundError as e:
- # Skip this missing file
- if ignore_missing:
- continue
- else:
- raise UtilError("Source file is missing: {}".format(srcpath)) from e
-
- if stat.S_ISDIR(mode):
- # Ensure directory exists in destination
- _ensure_real_directory(destdir, path)
- permissions.append((destpath, os.stat(srcpath).st_mode))
-
- elif stat.S_ISLNK(mode):
- if not safe_remove(destpath):
- result.ignored.append(path)
- continue
-
- target = os.readlink(srcpath)
- os.symlink(target, destpath)
-
- elif stat.S_ISREG(mode):
- # Process the file.
- if not safe_remove(destpath):
- result.ignored.append(path)
- continue
-
- actionfunc(srcpath, destpath, result=result)
-
- elif stat.S_ISCHR(mode) or stat.S_ISBLK(mode):
- # Block or character device. Put contents of st_dev in a mknod.
- if not safe_remove(destpath):
- result.ignored.append(path)
- continue
-
- if os.path.lexists(destpath):
- os.remove(destpath)
- os.mknod(destpath, file_stat.st_mode, file_stat.st_rdev)
- os.chmod(destpath, file_stat.st_mode)
-
- elif stat.S_ISFIFO(mode):
- os.mkfifo(destpath, mode)
-
- elif stat.S_ISSOCK(mode):
- # We can't duplicate the process serving the socket anyway
- pass
-
- else:
- # Unsupported type.
- raise UtilError('Cannot extract {} into staging-area. Unsupported type.'.format(srcpath))
-
- # Write directory permissions now that all files have been written
- for d, perms in permissions:
- os.chmod(d, perms)
-
-
-# _set_deterministic_user()
-#
-# Set the uid/gid for every file in a directory tree to the process'
-# euid/guid.
-#
-# Args:
-# directory (str): The directory to recursively set the uid/gid on
-#
-def _set_deterministic_user(directory):
- user = os.geteuid()
- group = os.getegid()
-
- for root, dirs, files in os.walk(directory.encode("utf-8"), topdown=False):
- for filename in files:
- os.chown(os.path.join(root, filename), user, group, follow_symlinks=False)
-
- for dirname in dirs:
- os.chown(os.path.join(root, dirname), user, group, follow_symlinks=False)
-
-
-# _set_deterministic_mtime()
-#
-# Set the mtime for every file in a directory tree to the same.
-#
-# Args:
-# directory (str): The directory to recursively set the mtime on
-#
-def _set_deterministic_mtime(directory):
- for dirname, _, filenames in os.walk(directory.encode("utf-8"), topdown=False):
- for filename in filenames:
- pathname = os.path.join(dirname, filename)
-
- # Python's os.utime only ever modifies the timestamp
- # of the target, it is not acceptable to set the timestamp
- # of the target here, if we are staging the link target we
- # will also set its timestamp.
- #
- # We should however find a way to modify the actual link's
- # timestamp, this outdated python bug report claims that
- # it is impossible:
- #
- # http://bugs.python.org/issue623782
- #
- # However, nowadays it is possible at least on gnuish systems
- # with with the lutimes glibc function.
- if not os.path.islink(pathname):
- os.utime(pathname, (_magic_timestamp, _magic_timestamp))
-
- os.utime(dirname, (_magic_timestamp, _magic_timestamp))
-
-
-# _tempdir()
-#
-# A context manager for doing work in a temporary directory.
-#
-# Args:
-# dir (str): A path to a parent directory for the temporary directory
-# suffix (str): A suffix for the temproary directory name
-# prefix (str): A prefix for the temporary directory name
-#
-# Yields:
-# (str): The temporary directory
-#
-# In addition to the functionality provided by python's
-# tempfile.TemporaryDirectory() context manager, this one additionally
-# supports cleaning up the temp directory on SIGTERM.
-#
-@contextmanager
-def _tempdir(suffix="", prefix="tmp", dir=None): # pylint: disable=redefined-builtin
- tempdir = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=dir)
-
- def cleanup_tempdir():
- if os.path.isdir(tempdir):
- _force_rmtree(tempdir)
-
- try:
- with _signals.terminator(cleanup_tempdir):
- yield tempdir
- finally:
- cleanup_tempdir()
-
-
-# _tempnamedfile()
-#
-# A context manager for doing work on an open temporary file
-# which is guaranteed to be named and have an entry in the filesystem.
-#
-# Args:
-# dir (str): A path to a parent directory for the temporary file
-# suffix (str): A suffix for the temproary file name
-# prefix (str): A prefix for the temporary file name
-#
-# Yields:
-# (str): The temporary file handle
-#
-# Do not use tempfile.NamedTemporaryFile() directly, as this will
-# leak files on the filesystem when BuildStream exits a process
-# on SIGTERM.
-#
-@contextmanager
-def _tempnamedfile(suffix="", prefix="tmp", dir=None): # pylint: disable=redefined-builtin
- temp = None
-
- def close_tempfile():
- if temp is not None:
- temp.close()
-
- with _signals.terminator(close_tempfile), \
- tempfile.NamedTemporaryFile(suffix=suffix, prefix=prefix, dir=dir) as temp:
- yield temp
-
-
-# _kill_process_tree()
-#
-# Brutally murder a process and all of its children
-#
-# Args:
-# pid (int): Process ID
-#
-def _kill_process_tree(pid):
- proc = psutil.Process(pid)
- children = proc.children(recursive=True)
-
- def kill_proc(p):
- try:
- p.kill()
- except psutil.AccessDenied:
- # Ignore this error, it can happen with
- # some setuid bwrap processes.
- pass
- except psutil.NoSuchProcess:
- # It is certain that this has already been sent
- # SIGTERM, so there is a window where the process
- # could have exited already.
- pass
-
- # Bloody Murder
- for child in children:
- kill_proc(child)
- kill_proc(proc)
-
-
-# _call()
-#
-# A wrapper for subprocess.call() supporting suspend and resume
-#
-# Args:
-# popenargs (list): Popen() arguments
-# terminate (bool): Whether to attempt graceful termination before killing
-# rest_of_args (kwargs): Remaining arguments to subprocess.call()
-#
-# Returns:
-# (int): The process exit code.
-# (str): The program output.
-#
-def _call(*popenargs, terminate=False, **kwargs):
-
- kwargs['start_new_session'] = True
-
- process = None
-
- old_preexec_fn = kwargs.get('preexec_fn')
- if 'preexec_fn' in kwargs:
- del kwargs['preexec_fn']
-
- def preexec_fn():
- os.umask(stat.S_IWGRP | stat.S_IWOTH)
- if old_preexec_fn is not None:
- old_preexec_fn()
-
- # Handle termination, suspend and resume
- def kill_proc():
- if process:
-
- # Some callers know that their subprocess can be
- # gracefully terminated, make an attempt first
- if terminate:
- proc = psutil.Process(process.pid)
- proc.terminate()
-
- try:
- proc.wait(20)
- except psutil.TimeoutExpired:
- # Did not terminate within the timeout: murder
- _kill_process_tree(process.pid)
-
- else:
- # FIXME: This is a brutal but reliable approach
- #
- # Other variations I've tried which try SIGTERM first
- # and then wait for child processes to exit gracefully
- # have not reliably cleaned up process trees and have
- # left orphaned git or ssh processes alive.
- #
- # This cleans up the subprocesses reliably but may
- # cause side effects such as possibly leaving stale
- # locks behind. Hopefully this should not be an issue
- # as long as any child processes only interact with
- # the temp directories which we control and cleanup
- # ourselves.
- #
- _kill_process_tree(process.pid)
-
- def suspend_proc():
- if process:
- group_id = os.getpgid(process.pid)
- os.killpg(group_id, signal.SIGSTOP)
-
- def resume_proc():
- if process:
- group_id = os.getpgid(process.pid)
- os.killpg(group_id, signal.SIGCONT)
-
- with _signals.suspendable(suspend_proc, resume_proc), _signals.terminator(kill_proc):
- process = subprocess.Popen( # pylint: disable=subprocess-popen-preexec-fn
- *popenargs, preexec_fn=preexec_fn, universal_newlines=True, **kwargs)
- output, _ = process.communicate()
- exit_code = process.poll()
-
- return (exit_code, output)
-
-
-# _glob2re()
-#
-# Function to translate a glob style pattern into a regex
-#
-# Args:
-# pat (str): The glob pattern
-#
-# This is a modified version of the python standard library's
-# fnmatch.translate() function which supports path like globbing
-# a bit more correctly, and additionally supports recursive glob
-# patterns with double asterisk.
-#
-# Note that this will only support the most basic of standard
-# glob patterns, and additionally the recursive double asterisk.
-#
-# Support includes:
-#
-# * Match any pattern except a path separator
-# ** Match any pattern, including path separators
-# ? Match any single character
-# [abc] Match one of the specified characters
-# [A-Z] Match one of the characters in the specified range
-# [!abc] Match any single character, except the specified characters
-# [!A-Z] Match any single character, except those in the specified range
-#
-def _glob2re(pat):
- i, n = 0, len(pat)
- res = '(?ms)'
- while i < n:
- c = pat[i]
- i = i + 1
- if c == '*':
- # fnmatch.translate() simply uses the '.*' separator here,
- # we only want that for double asterisk (bash 'globstar' behavior)
- #
- if i < n and pat[i] == '*':
- res = res + '.*'
- i = i + 1
- else:
- res = res + '[^/]*'
- elif c == '?':
- # fnmatch.translate() simply uses the '.' wildcard here, but
- # we dont want to match path separators here
- res = res + '[^/]'
- elif c == '[':
- j = i
- if j < n and pat[j] == '!':
- j = j + 1
- if j < n and pat[j] == ']':
- j = j + 1
- while j < n and pat[j] != ']':
- j = j + 1
- if j >= n:
- res = res + '\\['
- else:
- stuff = pat[i:j].replace('\\', '\\\\')
- i = j + 1
- if stuff[0] == '!':
- stuff = '^' + stuff[1:]
- elif stuff[0] == '^':
- stuff = '\\' + stuff
- res = '{}[{}]'.format(res, stuff)
- else:
- res = res + re.escape(c)
- return res + r'\Z'
-
-
-# _deduplicate()
-#
-# Remove duplicate entries in a list or other iterable.
-#
-# Copied verbatim from the unique_everseen() example at
-# https://docs.python.org/3/library/itertools.html#itertools-recipes
-#
-# Args:
-# iterable (iterable): What to deduplicate
-# key (callable): Optional function to map from list entry to value
-#
-# Returns:
-# (generator): Generator that produces a deduplicated version of 'iterable'
-#
-def _deduplicate(iterable, key=None):
- seen = set()
- seen_add = seen.add
- if key is None:
- for element in itertools.filterfalse(seen.__contains__, iterable):
- seen_add(element)
- yield element
- else:
- for element in iterable:
- k = key(element)
- if k not in seen:
- seen_add(k)
- yield element
-
-
-# Like os.path.getmtime(), but returns the mtime of a link rather than
-# the target, if the filesystem supports that.
-#
-def _get_link_mtime(path):
- path_stat = os.lstat(path)
- return path_stat.st_mtime
-
-
-# _message_digest()
-#
-# Args:
-# message_buffer (str): String to create digest of
-#
-# Returns:
-# (remote_execution_pb2.Digest): Content digest
-#
-def _message_digest(message_buffer):
- sha = hashlib.sha256(message_buffer)
- digest = remote_execution_pb2.Digest()
- digest.hash = sha.hexdigest()
- digest.size_bytes = len(message_buffer)
- return digest
-
-
-# _search_upward_for_files()
-#
-# Searches upwards (from directory, then directory's parent directory...)
-# for any of the files listed in `filenames`.
-#
-# If multiple filenames are specified, and present in the same directory,
-# the first filename in the list will be returned.
-#
-# Args:
-# directory (str): The directory to begin searching for files from
-# filenames (list of str): The names of files to search for
-#
-# Returns:
-# (str): The directory a file was found in, or None
-# (str): The name of the first file that was found in that directory, or None
-#
-def _search_upward_for_files(directory, filenames):
- directory = os.path.abspath(directory)
- while True:
- for filename in filenames:
- file_path = os.path.join(directory, filename)
- if os.path.isfile(file_path):
- return directory, filename
-
- parent_dir = os.path.dirname(directory)
- if directory == parent_dir:
- # i.e. we've reached the root of the filesystem
- return None, None
- directory = parent_dir
-
-
-# _deterministic_umask()
-#
-# Context managed to apply a umask to a section that may be affected by a users
-# umask. Restores old mask afterwards.
-#
-@contextmanager
-def _deterministic_umask():
- old_umask = os.umask(0o022)
-
- try:
- yield
- finally:
- os.umask(old_umask)