Qinusty pushed to branch Qinusty/600-recursive-variables at BuildStream / buildstream
Commits:
-
10eeb503
by Javier Jardón at 2018-08-28T22:22:00Z
-
d3c32ca2
by Javier Jardón at 2018-08-28T22:22:00Z
-
3ae5fd05
by Javier Jardón at 2018-08-28T22:22:00Z
-
c6fb5ba7
by Javier Jardón at 2018-08-28T22:22:00Z
-
c5eca59d
by Tristan Van Berkom at 2018-08-29T10:45:33Z
-
63c6ee72
by Tristan Van Berkom at 2018-08-29T10:46:30Z
-
2d527052
by Tristan Van Berkom at 2018-08-29T10:46:30Z
-
29e7eea8
by Tristan Van Berkom at 2018-08-29T10:46:30Z
-
5d508779
by Tristan Van Berkom at 2018-08-29T11:13:53Z
-
323a5403
by Chandan Singh at 2018-08-29T12:13:28Z
-
5fc7b5ac
by Tristan Van Berkom at 2018-08-29T12:37:08Z
-
42ad937d
by Josh Smith at 2018-08-29T12:38:15Z
-
aa3a33b3
by Tristan Van Berkom at 2018-08-29T13:03:58Z
-
2992a675
by Josh Smith at 2018-08-29T14:35:19Z
-
601180ac
by Josh Smith at 2018-08-29T15:36:09Z
26 changed files:
- .gitignore
- .gitlab-ci.yml
- README.rst
- buildstream/_exceptions.py
- buildstream/_variables.py
- buildstream/plugins/sources/tar.py
- buildstream/plugins/sources/zip.py
- buildstream/source.py
- buildstream/utils.py
- dev-requirements.txt
- doc/Makefile
- + doc/badges.py
- doc/bst2html.py
- doc/source/conf.py
- doc/source/install_versions.rst
- doc/source/main_install.rst
- + doc/source/release-badge.rst
- + doc/source/snapshot-badge.rst
- setup.py
- tests/cachekey/cachekey.py
- tests/cachekey/update.py
- tests/format/variables.py
- + tests/format/variables/cyclic_variables/cyclic.bst
- + tests/format/variables/cyclic_variables/project.conf
- tests/frontend/buildcheckout.py
- tests/testutils/runcli.py
Changes:
| ... | ... | @@ -26,6 +26,7 @@ __pycache__/ |
| 26 | 26 |
buildstream/__version__.py
|
| 27 | 27 |
|
| 28 | 28 |
# Autogenerated doc
|
| 29 |
+doc/source/badges/
|
|
| 29 | 30 |
doc/source/sessions/
|
| 30 | 31 |
doc/source/elements/
|
| 31 | 32 |
doc/source/sources/
|
| ... | ... | @@ -84,25 +84,25 @@ source_dist: |
| 84 | 84 |
- coverage-linux/
|
| 85 | 85 |
|
| 86 | 86 |
tests-debian-9:
|
| 87 |
- image: buildstream/testsuite-debian:9-master-114-4cab18e3
|
|
| 87 |
+ image: buildstream/testsuite-debian:9-master-117-aa3a33b3
|
|
| 88 | 88 |
<<: *linux-tests
|
| 89 | 89 |
|
| 90 | 90 |
tests-fedora-27:
|
| 91 |
- image: buildstream/testsuite-fedora:27-master-114-4cab18e3
|
|
| 91 |
+ image: buildstream/testsuite-fedora:27-master-117-aa3a33b3
|
|
| 92 | 92 |
<<: *linux-tests
|
| 93 | 93 |
|
| 94 | 94 |
tests-fedora-28:
|
| 95 |
- image: buildstream/testsuite-fedora:28-master-114-4cab18e3
|
|
| 95 |
+ image: buildstream/testsuite-fedora:28-master-117-aa3a33b3
|
|
| 96 | 96 |
<<: *linux-tests
|
| 97 | 97 |
|
| 98 | 98 |
tests-ubuntu-18.04:
|
| 99 |
- image: buildstream/testsuite-ubuntu:18.04-master-114-4cab18e3
|
|
| 99 |
+ image: buildstream/testsuite-ubuntu:18.04-master-117-aa3a33b3
|
|
| 100 | 100 |
<<: *linux-tests
|
| 101 | 101 |
|
| 102 | 102 |
tests-unix:
|
| 103 | 103 |
# Use fedora here, to a) run a test on fedora and b) ensure that we
|
| 104 | 104 |
# can get rid of ostree - this is not possible with debian-8
|
| 105 |
- image: buildstream/testsuite-fedora:27-master-114-4cab18e3
|
|
| 105 |
+ image: buildstream/testsuite-fedora:27-master-117-aa3a33b3
|
|
| 106 | 106 |
stage: test
|
| 107 | 107 |
variables:
|
| 108 | 108 |
BST_FORCE_BACKEND: "unix"
|
| 1 | 1 |
About
|
| 2 | 2 |
-----
|
| 3 |
+ |
|
| 4 |
+.. image:: https://buildstream.gitlab.io/buildstream/_static/release.svg
|
|
| 5 |
+ :target: https://gitlab.com/BuildStream/buildstream/commits/bst-1.2
|
|
| 6 |
+ |
|
| 7 |
+.. image:: https://buildstream.gitlab.io/buildstream/_static/snapshot.svg
|
|
| 8 |
+ :target: https://gitlab.com/BuildStream/buildstream/commits/master
|
|
| 9 |
+ |
|
| 3 | 10 |
.. image:: https://gitlab.com/BuildStream/buildstream/badges/master/pipeline.svg
|
| 4 | 11 |
:target: https://gitlab.com/BuildStream/buildstream/commits/master
|
| 5 | 12 |
|
| ... | ... | @@ -217,6 +217,9 @@ class LoadErrorReason(Enum): |
| 217 | 217 |
# A recursive include has been encountered.
|
| 218 | 218 |
RECURSIVE_INCLUDE = 21
|
| 219 | 219 |
|
| 220 |
+ # A recursive variable has been encountered
|
|
| 221 |
+ RECURSIVE_VARIABLE = 22
|
|
| 222 |
+ |
|
| 220 | 223 |
|
| 221 | 224 |
# LoadError
|
| 222 | 225 |
#
|
| ... | ... | @@ -61,7 +61,7 @@ class Variables(): |
| 61 | 61 |
# LoadError, if the string contains unresolved variable references.
|
| 62 | 62 |
#
|
| 63 | 63 |
def subst(self, string):
|
| 64 |
- substitute, unmatched = self._subst(string, self.variables)
|
|
| 64 |
+ substitute, unmatched, _ = self._subst(string, self.variables)
|
|
| 65 | 65 |
unmatched = list(set(unmatched))
|
| 66 | 66 |
if unmatched:
|
| 67 | 67 |
if len(unmatched) == 1:
|
| ... | ... | @@ -82,6 +82,7 @@ class Variables(): |
| 82 | 82 |
def subst_callback(match):
|
| 83 | 83 |
nonlocal variables
|
| 84 | 84 |
nonlocal unmatched
|
| 85 |
+ nonlocal matched
|
|
| 85 | 86 |
|
| 86 | 87 |
token = match.group(0)
|
| 87 | 88 |
varname = match.group(1)
|
| ... | ... | @@ -91,6 +92,7 @@ class Variables(): |
| 91 | 92 |
# We have to check if the inner string has variables
|
| 92 | 93 |
# and return unmatches for those
|
| 93 | 94 |
unmatched += re.findall(_VARIABLE_MATCH, value)
|
| 95 |
+ matched += [varname]
|
|
| 94 | 96 |
else:
|
| 95 | 97 |
# Return unmodified token
|
| 96 | 98 |
unmatched += [varname]
|
| ... | ... | @@ -98,10 +100,11 @@ class Variables(): |
| 98 | 100 |
|
| 99 | 101 |
return value
|
| 100 | 102 |
|
| 103 |
+ matched = []
|
|
| 101 | 104 |
unmatched = []
|
| 102 | 105 |
replacement = re.sub(_VARIABLE_MATCH, subst_callback, string)
|
| 103 | 106 |
|
| 104 |
- return (replacement, unmatched)
|
|
| 107 |
+ return (replacement, unmatched, matched)
|
|
| 105 | 108 |
|
| 106 | 109 |
# Variable resolving code
|
| 107 | 110 |
#
|
| ... | ... | @@ -131,7 +134,15 @@ class Variables(): |
| 131 | 134 |
# Ensure stringness of the value before substitution
|
| 132 | 135 |
value = _yaml.node_get(variables, str, key)
|
| 133 | 136 |
|
| 134 |
- resolved_var, item_unmatched = self._subst(value, variables)
|
|
| 137 |
+ resolved_var, item_unmatched, matched = self._subst(value, variables)
|
|
| 138 |
+ |
|
| 139 |
+ if _wrap_variable(key) in resolved_var:
|
|
| 140 |
+ referenced_through = find_recursive_variable(key, matched, variables)
|
|
| 141 |
+ raise LoadError(LoadErrorReason.RECURSIVE_VARIABLE,
|
|
| 142 |
+ "{}: ".format(_yaml.node_get_provenance(variables, key)) +
|
|
| 143 |
+ ("Variable '{}' expands to contain a reference to itself. " +
|
|
| 144 |
+ "Perhaps '{}' contains '{}").format(key, referenced_through, _wrap_variable(key)))
|
|
| 145 |
+ |
|
| 135 | 146 |
resolved[key] = resolved_var
|
| 136 | 147 |
unmatched += item_unmatched
|
| 137 | 148 |
|
| ... | ... | @@ -168,8 +179,21 @@ class Variables(): |
| 168 | 179 |
# Helper function to fetch information about the node referring to a variable
|
| 169 | 180 |
#
|
| 170 | 181 |
def _find_references(self, varname):
|
| 171 |
- fullname = '%{' + varname + '}'
|
|
| 182 |
+ fullname = _wrap_variable(varname)
|
|
| 172 | 183 |
for key, value in _yaml.node_items(self.original):
|
| 173 | 184 |
if fullname in value:
|
| 174 | 185 |
provenance = _yaml.node_get_provenance(self.original, key)
|
| 175 | 186 |
yield (key, provenance)
|
| 187 |
+ |
|
| 188 |
+ |
|
| 189 |
+def find_recursive_variable(variable, matched_variables, all_vars):
|
|
| 190 |
+ matched_values = (_yaml.node_get(all_vars, str, key) for key in matched_variables)
|
|
| 191 |
+ for key, value in zip(matched_variables, matched_values):
|
|
| 192 |
+ if _wrap_variable(variable) in value:
|
|
| 193 |
+ return key
|
|
| 194 |
+ else:
|
|
| 195 |
+ return None
|
|
| 196 |
+ |
|
| 197 |
+ |
|
| 198 |
+def _wrap_variable(var):
|
|
| 199 |
+ return "%{" + var + "}"
|
| ... | ... | @@ -127,7 +127,7 @@ class TarSource(DownloadableFileSource): |
| 127 | 127 |
if not base_dir.endswith(os.sep):
|
| 128 | 128 |
base_dir = base_dir + os.sep
|
| 129 | 129 |
|
| 130 |
- l = len(base_dir)
|
|
| 130 |
+ L = len(base_dir)
|
|
| 131 | 131 |
for member in tar.getmembers():
|
| 132 | 132 |
|
| 133 | 133 |
# First, ensure that a member never starts with `./`
|
| ... | ... | @@ -145,9 +145,9 @@ class TarSource(DownloadableFileSource): |
| 145 | 145 |
# base directory.
|
| 146 | 146 |
#
|
| 147 | 147 |
if member.type == tarfile.LNKTYPE:
|
| 148 |
- member.linkname = member.linkname[l:]
|
|
| 148 |
+ member.linkname = member.linkname[L:]
|
|
| 149 | 149 |
|
| 150 |
- member.path = member.path[l:]
|
|
| 150 |
+ member.path = member.path[L:]
|
|
| 151 | 151 |
yield member
|
| 152 | 152 |
|
| 153 | 153 |
# We want to iterate over all paths of a tarball, but getmembers()
|
| ... | ... | @@ -121,13 +121,13 @@ class ZipSource(DownloadableFileSource): |
| 121 | 121 |
if not base_dir.endswith(os.sep):
|
| 122 | 122 |
base_dir = base_dir + os.sep
|
| 123 | 123 |
|
| 124 |
- l = len(base_dir)
|
|
| 124 |
+ L = len(base_dir)
|
|
| 125 | 125 |
for member in archive.infolist():
|
| 126 | 126 |
if member.filename == base_dir:
|
| 127 | 127 |
continue
|
| 128 | 128 |
|
| 129 | 129 |
if member.filename.startswith(base_dir):
|
| 130 |
- member.filename = member.filename[l:]
|
|
| 130 |
+ member.filename = member.filename[L:]
|
|
| 131 | 131 |
yield member
|
| 132 | 132 |
|
| 133 | 133 |
# We want to iterate over all paths of an archive, but namelist()
|
| ... | ... | @@ -219,10 +219,7 @@ class SourceFetcher(): |
| 219 | 219 |
Args:
|
| 220 | 220 |
url (str): The url used to download.
|
| 221 | 221 |
"""
|
| 222 |
- # Not guaranteed to be a valid alias yet.
|
|
| 223 |
- # Ensuring it's a valid alias currently happens in Project.get_alias_uris
|
|
| 224 |
- alias, _ = url.split(utils._ALIAS_SEPARATOR, 1)
|
|
| 225 |
- self.__alias = alias
|
|
| 222 |
+ self.__alias = _extract_alias(url)
|
|
| 226 | 223 |
|
| 227 | 224 |
#############################################################
|
| 228 | 225 |
# Private Methods used in BuildStream #
|
| ... | ... | @@ -459,8 +456,7 @@ class Source(Plugin): |
| 459 | 456 |
|
| 460 | 457 |
*Since: 1.2*
|
| 461 | 458 |
"""
|
| 462 |
- alias, _ = url.split(utils._ALIAS_SEPARATOR, 1)
|
|
| 463 |
- self.__expected_alias = alias
|
|
| 459 |
+ self.__expected_alias = _extract_alias(url)
|
|
| 464 | 460 |
|
| 465 | 461 |
def get_source_fetchers(self):
|
| 466 | 462 |
"""Get the objects that are used for fetching
|
| ... | ... | @@ -525,8 +521,7 @@ class Source(Plugin): |
| 525 | 521 |
else:
|
| 526 | 522 |
# Sneakily store the alias if it hasn't already been stored
|
| 527 | 523 |
if not self.__expected_alias and url and utils._ALIAS_SEPARATOR in url:
|
| 528 |
- url_alias, _ = url.split(utils._ALIAS_SEPARATOR, 1)
|
|
| 529 |
- self.__expected_alias = url_alias
|
|
| 524 |
+ self.mark_download_url(url)
|
|
| 530 | 525 |
|
| 531 | 526 |
project = self._get_project()
|
| 532 | 527 |
return project.translate_url(url, first_pass=self.__first_pass)
|
| ... | ... | @@ -914,12 +909,12 @@ class Source(Plugin): |
| 914 | 909 |
# Tries to call track for every mirror, stopping once it succeeds
|
| 915 | 910 |
def __do_track(self, **kwargs):
|
| 916 | 911 |
project = self._get_project()
|
| 917 |
- # If there are no mirrors, or no aliases to replace, there's nothing to do here.
|
|
| 918 | 912 |
alias = self._get_alias()
|
| 919 | 913 |
if self.__first_pass:
|
| 920 | 914 |
mirrors = project.first_pass_config.mirrors
|
| 921 | 915 |
else:
|
| 922 | 916 |
mirrors = project.config.mirrors
|
| 917 |
+ # If there are no mirrors, or no aliases to replace, there's nothing to do here.
|
|
| 923 | 918 |
if not mirrors or not alias:
|
| 924 | 919 |
return self.track(**kwargs)
|
| 925 | 920 |
|
| ... | ... | @@ -988,3 +983,11 @@ class Source(Plugin): |
| 988 | 983 |
|
| 989 | 984 |
if src.get_consistency() == Consistency.RESOLVED:
|
| 990 | 985 |
src._fetch(previous_sources[0:index])
|
| 986 |
+ |
|
| 987 |
+ |
|
| 988 |
+def _extract_alias(url):
|
|
| 989 |
+ parts = url.split(utils._ALIAS_SEPARATOR, 1)
|
|
| 990 |
+ if len(parts) > 1 and not parts[0].lower() in utils._URI_SCHEMES:
|
|
| 991 |
+ return parts[0]
|
|
| 992 |
+ else:
|
|
| 993 |
+ return ""
|
| ... | ... | @@ -47,6 +47,7 @@ _magic_timestamp = calendar.timegm([2011, 11, 11, 11, 11, 11]) |
| 47 | 47 |
|
| 48 | 48 |
# The separator we use for user specified aliases
|
| 49 | 49 |
_ALIAS_SEPARATOR = ':'
|
| 50 |
+_URI_SCHEMES = ["http", "https", "ftp", "file", "git", "sftp", "ssh"]
|
|
| 50 | 51 |
|
| 51 | 52 |
|
| 52 | 53 |
class UtilError(BstError):
|
| ... | ... | @@ -645,6 +646,7 @@ def _pretty_size(size, dec_places=0): |
| 645 | 646 |
psize /= 1024
|
| 646 | 647 |
return "{size:g}{unit}".format(size=round(psize, dec_places), unit=unit)
|
| 647 | 648 |
|
| 649 |
+ |
|
| 648 | 650 |
# A sentinel to be used as a default argument for functions that need
|
| 649 | 651 |
# to distinguish between a kwarg set to None and an unset kwarg.
|
| 650 | 652 |
_sentinel = object()
|
| ... | ... | @@ -8,3 +8,4 @@ pytest-env |
| 8 | 8 |
pytest-pep8
|
| 9 | 9 |
pytest-pylint
|
| 10 | 10 |
pytest-xdist
|
| 11 |
+pytest-timeout
|
| ... | ... | @@ -35,7 +35,7 @@ endif |
| 35 | 35 |
PYTHONPATH=$(CURDIR)/..:$(CURDIR)/../buildstream/plugins
|
| 36 | 36 |
|
| 37 | 37 |
|
| 38 |
-.PHONY: all clean templates templates-clean sessions sessions-prep sessions-clean html devhelp
|
|
| 38 |
+.PHONY: all clean templates templates-clean sessions sessions-prep sessions-clean badges badges-clean html devhelp
|
|
| 39 | 39 |
|
| 40 | 40 |
# Canned recipe for generating plugin api skeletons
|
| 41 | 41 |
# $1 = the plugin directory
|
| ... | ... | @@ -70,9 +70,13 @@ endef |
| 70 | 70 |
|
| 71 | 71 |
all: html devhelp
|
| 72 | 72 |
|
| 73 |
-clean: templates-clean sessions-clean
|
|
| 73 |
+clean: templates-clean sessions-clean badges-clean
|
|
| 74 | 74 |
rm -rf build
|
| 75 | 75 |
|
| 76 |
+############################################################
|
|
| 77 |
+# Plugin doc templates #
|
|
| 78 |
+############################################################
|
|
| 79 |
+ |
|
| 76 | 80 |
# Generate rst templates for the docs using a mix of sphinx-apidoc and
|
| 77 | 81 |
# our 'plugin-doc-skeleton' routine for plugin pages.
|
| 78 | 82 |
templates:
|
| ... | ... | @@ -86,6 +90,10 @@ templates-clean: |
| 86 | 90 |
rm -rf source/elements
|
| 87 | 91 |
rm -rf source/sources
|
| 88 | 92 |
|
| 93 |
+############################################################
|
|
| 94 |
+# Session captures #
|
|
| 95 |
+############################################################
|
|
| 96 |
+ |
|
| 89 | 97 |
# Stage the stored sessions into the place where they will
|
| 90 | 98 |
# be used in the build.
|
| 91 | 99 |
#
|
| ... | ... | @@ -111,10 +119,27 @@ sessions: sessions-prep |
| 111 | 119 |
sessions-clean:
|
| 112 | 120 |
rm -rf source/sessions
|
| 113 | 121 |
|
| 122 |
+ |
|
| 123 |
+############################################################
|
|
| 124 |
+# Generate release badges #
|
|
| 125 |
+############################################################
|
|
| 126 |
+badges-clean:
|
|
| 127 |
+ rm -rf source/badges
|
|
| 128 |
+ |
|
| 129 |
+badges:
|
|
| 130 |
+ mkdir -p source/badges
|
|
| 131 |
+ $(CURDIR)/badges.py > source/badges/snapshot.svg
|
|
| 132 |
+ $(CURDIR)/badges.py --release > source/badges/release.svg
|
|
| 133 |
+ |
|
| 134 |
+ |
|
| 135 |
+############################################################
|
|
| 136 |
+# Main sphinx build #
|
|
| 137 |
+############################################################
|
|
| 138 |
+ |
|
| 114 | 139 |
# Targets which generate docs with sphinx build
|
| 115 | 140 |
#
|
| 116 | 141 |
#
|
| 117 |
-html devhelp: templates sessions
|
|
| 142 |
+html devhelp: templates sessions badges
|
|
| 118 | 143 |
@echo "Building $@..."
|
| 119 | 144 |
PYTHONPATH=$(PYTHONPATH) \
|
| 120 | 145 |
$(SPHINXBUILD) -b $@ $(ALLSPHINXOPTS) "$(BUILDDIR)/$@" \
|
| 1 |
+#!/usr/bin/env python3
|
|
| 2 |
+#
|
|
| 3 |
+# Copyright (C) 2018 Codethink Limited
|
|
| 4 |
+#
|
|
| 5 |
+# This program is free software; you can redistribute it and/or
|
|
| 6 |
+# modify it under the terms of the GNU Lesser General Public
|
|
| 7 |
+# License as published by the Free Software Foundation; either
|
|
| 8 |
+# version 2 of the License, or (at your option) any later version.
|
|
| 9 |
+#
|
|
| 10 |
+# This library is distributed in the hope that it will be useful,
|
|
| 11 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 12 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
| 13 |
+# Lesser General Public License for more details.
|
|
| 14 |
+#
|
|
| 15 |
+# You should have received a copy of the GNU Lesser General Public
|
|
| 16 |
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
| 17 |
+#
|
|
| 18 |
+# Authors:
|
|
| 19 |
+# Tristan Van Berkom <tristan vanberkom codethink co uk>
|
|
| 20 |
+#
|
|
| 21 |
+import click
|
|
| 22 |
+import subprocess
|
|
| 23 |
+import re
|
|
| 24 |
+ |
|
| 25 |
+# The badge template is modeled after the gitlab badge svgs
|
|
| 26 |
+#
|
|
| 27 |
+BADGE_TEMPLATE = """
|
|
| 28 |
+<svg xmlns="http://www.w3.org/2000/svg"
|
|
| 29 |
+ xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
| 30 |
+ width="116" height="20">
|
|
| 31 |
+ <a xlink:href="">
|
|
| 32 |
+ <linearGradient id="{badge_name}_b" x2="0" y2="100%">
|
|
| 33 |
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
| 34 |
+ <stop offset="1" stop-opacity=".1"/>
|
|
| 35 |
+ </linearGradient>
|
|
| 36 |
+ |
|
| 37 |
+ <mask id="{badge_name}_a">
|
|
| 38 |
+ <rect width="116" height="20" rx="3" fill="#fff"/>
|
|
| 39 |
+ </mask>
|
|
| 40 |
+ |
|
| 41 |
+ <g mask="url(#{badge_name}_a)">
|
|
| 42 |
+ <path fill="#555"
|
|
| 43 |
+ d="M0 0 h62 v20 H0 z"/>
|
|
| 44 |
+ <path fill="{color}"
|
|
| 45 |
+ d="M62 0 h54 v20 H62 z"/>
|
|
| 46 |
+ <path fill="url(#{badge_name}_b)"
|
|
| 47 |
+ d="M0 0 h116 v20 H0 z"/>
|
|
| 48 |
+ </g>
|
|
| 49 |
+ |
|
| 50 |
+ <g fill="#fff" text-anchor="middle">
|
|
| 51 |
+ <g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
|
| 52 |
+ <text x="31" y="15" fill="#010101" fill-opacity=".3">
|
|
| 53 |
+ {badge_name}
|
|
| 54 |
+ </text>
|
|
| 55 |
+ <text x="31" y="14">
|
|
| 56 |
+ {badge_name}
|
|
| 57 |
+ </text>
|
|
| 58 |
+ <text x="89" y="15" fill="#010101" fill-opacity=".3">
|
|
| 59 |
+ {version}
|
|
| 60 |
+ </text>
|
|
| 61 |
+ <text x="89" y="14">
|
|
| 62 |
+ {version}
|
|
| 63 |
+ </text>
|
|
| 64 |
+ </g>
|
|
| 65 |
+ </g>
|
|
| 66 |
+ </a>
|
|
| 67 |
+</svg>
|
|
| 68 |
+"""
|
|
| 69 |
+ |
|
| 70 |
+URL_FORMAT = 'https://download.gnome.org/sources/BuildStream/{brief_version}/BuildStream-{full_version}.tar.xz'
|
|
| 71 |
+RELEASE_COLOR = '#0040FF'
|
|
| 72 |
+SNAPSHOT_COLOR = '#FF8000'
|
|
| 73 |
+VERSION_TAG_MATCH = r'([0-9]*)\.([0-9]*)\.([0-9]*)'
|
|
| 74 |
+ |
|
| 75 |
+ |
|
| 76 |
+# Parse a release tag and return a three tuple
|
|
| 77 |
+# of the major, minor and micro version.
|
|
| 78 |
+#
|
|
| 79 |
+# Tags which do not follow the release tag format
|
|
| 80 |
+# will just be returned as (0, 0, 0)
|
|
| 81 |
+#
|
|
| 82 |
+def parse_tag(tag):
|
|
| 83 |
+ match = re.search(VERSION_TAG_MATCH, tag)
|
|
| 84 |
+ if match:
|
|
| 85 |
+ major = match.group(1)
|
|
| 86 |
+ minor = match.group(2)
|
|
| 87 |
+ micro = match.group(3)
|
|
| 88 |
+ return (int(major), int(minor), int(micro))
|
|
| 89 |
+ |
|
| 90 |
+ return (0, 0, 0)
|
|
| 91 |
+ |
|
| 92 |
+ |
|
| 93 |
+# Call out to git and guess the latest version,
|
|
| 94 |
+# this will just return (0, 0, 0) in case of any error.
|
|
| 95 |
+#
|
|
| 96 |
+def guess_version(release):
|
|
| 97 |
+ try:
|
|
| 98 |
+ tags_output = subprocess.check_output(['git', 'tag'])
|
|
| 99 |
+ except CalledProcessError:
|
|
| 100 |
+ return (0, 0, 0)
|
|
| 101 |
+ |
|
| 102 |
+ # Parse the `git tag` output into a list of integer tuples
|
|
| 103 |
+ tags_output = tags_output.decode('UTF-8')
|
|
| 104 |
+ all_tags = tags_output.splitlines()
|
|
| 105 |
+ all_versions = [parse_tag(tag) for tag in all_tags]
|
|
| 106 |
+ |
|
| 107 |
+ # Filter the list by the minor point version, if
|
|
| 108 |
+ # we are checking for the latest "release" version, then
|
|
| 109 |
+ # only pickup even number minor points.
|
|
| 110 |
+ #
|
|
| 111 |
+ filtered_versions = [
|
|
| 112 |
+ version for version in all_versions
|
|
| 113 |
+ if (version[1] % 2) == (not release)
|
|
| 114 |
+ ]
|
|
| 115 |
+ |
|
| 116 |
+ # Make sure they are sorted, and take the last one
|
|
| 117 |
+ sorted_versions = sorted(filtered_versions)
|
|
| 118 |
+ latest_version = sorted_versions[-1]
|
|
| 119 |
+ |
|
| 120 |
+ return latest_version
|
|
| 121 |
+ |
|
| 122 |
+ |
|
| 123 |
+@click.command(short_help="Generate the version badges")
|
|
| 124 |
+@click.option('--release', is_flag=True, default=False,
|
|
| 125 |
+ help="Whether to generate the badge for the release version")
|
|
| 126 |
+def generate_badges(release):
|
|
| 127 |
+ """Generate the version badge svg files
|
|
| 128 |
+ """
|
|
| 129 |
+ major, minor, micro = guess_version(release)
|
|
| 130 |
+ |
|
| 131 |
+ if release:
|
|
| 132 |
+ badge_name = 'release'
|
|
| 133 |
+ color = RELEASE_COLOR
|
|
| 134 |
+ else:
|
|
| 135 |
+ badge_name = 'snapshot'
|
|
| 136 |
+ color = SNAPSHOT_COLOR
|
|
| 137 |
+ |
|
| 138 |
+ brief_version = '{major}.{minor}'.format(major=major, minor=minor)
|
|
| 139 |
+ full_version = '{major}.{minor}.{micro}'.format(major=major, minor=minor, micro=micro)
|
|
| 140 |
+ url_target = URL_FORMAT.format(brief_version=brief_version, full_version=full_version)
|
|
| 141 |
+ badge = BADGE_TEMPLATE.format(badge_name=badge_name,
|
|
| 142 |
+ version=full_version,
|
|
| 143 |
+ color=color,
|
|
| 144 |
+ url_target=url_target)
|
|
| 145 |
+ click.echo(badge, nl=False)
|
|
| 146 |
+ return 0
|
|
| 147 |
+ |
|
| 148 |
+ |
|
| 149 |
+if __name__ == '__main__':
|
|
| 150 |
+ generate_badges()
|
| ... | ... | @@ -96,8 +96,8 @@ def _ansi2html_get_styles(palette): |
| 96 | 96 |
|
| 97 | 97 |
for g in range(24):
|
| 98 | 98 |
i = g + 232
|
| 99 |
- l = g * 10 + 8
|
|
| 100 |
- indexed_style['%s' % i] = ''.join('%02X' % c if 0 <= c <= 255 else None for c in (l, l, l))
|
|
| 99 |
+ L = g * 10 + 8
|
|
| 100 |
+ indexed_style['%s' % i] = ''.join('%02X' % c if 0 <= c <= 255 else None for c in (L, L, L))
|
|
| 101 | 101 |
|
| 102 | 102 |
_ANSI2HTML_STYLES[palette] = (regular_style, bold_style, indexed_style)
|
| 103 | 103 |
return _ANSI2HTML_STYLES[palette]
|
| ... | ... | @@ -455,6 +455,7 @@ def run_bst(directory, force, source_cache, description, palette): |
| 455 | 455 |
|
| 456 | 456 |
return 0
|
| 457 | 457 |
|
| 458 |
+ |
|
| 458 | 459 |
if __name__ == '__main__':
|
| 459 | 460 |
try:
|
| 460 | 461 |
run_bst()
|
| ... | ... | @@ -19,10 +19,10 @@ |
| 19 | 19 |
#
|
| 20 | 20 |
import os
|
| 21 | 21 |
import sys
|
| 22 |
-sys.path.insert(0, os.path.abspath('..'))
|
|
| 23 |
- |
|
| 24 | 22 |
from buildstream import __version__
|
| 25 | 23 |
|
| 24 |
+sys.path.insert(0, os.path.abspath('..'))
|
|
| 25 |
+ |
|
| 26 | 26 |
# -- General configuration ------------------------------------------------
|
| 27 | 27 |
|
| 28 | 28 |
# If your documentation needs a minimal Sphinx version, state it here.
|
| ... | ... | @@ -112,7 +112,7 @@ add_module_names = False |
| 112 | 112 |
pygments_style = 'sphinx'
|
| 113 | 113 |
|
| 114 | 114 |
# A list of ignored prefixes for module index sorting.
|
| 115 |
-modindex_common_prefix = [ 'buildstream.' ]
|
|
| 115 |
+modindex_common_prefix = ['buildstream.']
|
|
| 116 | 116 |
|
| 117 | 117 |
# If true, keep warnings as "system message" paragraphs in the built documents.
|
| 118 | 118 |
# keep_warnings = False
|
| ... | ... | @@ -160,7 +160,7 @@ html_theme = 'sphinx_rtd_theme' |
| 160 | 160 |
# Add any paths that contain custom static files (such as style sheets) here,
|
| 161 | 161 |
# relative to this directory. They are copied after the builtin static files,
|
| 162 | 162 |
# so a file named "default.css" will overwrite the builtin "default.css".
|
| 163 |
-html_static_path = []
|
|
| 163 |
+html_static_path = ['badges']
|
|
| 164 | 164 |
|
| 165 | 165 |
# Add any extra paths that contain custom files (such as robots.txt or
|
| 166 | 166 |
# .htaccess) here, relative to this directory. These files are copied
|
| ... | ... | @@ -13,8 +13,12 @@ For example, for a given version number ``X.Y.Z`` |
| 13 | 13 |
* The ``X.<odd number>.*`` versions are development spanshots intended for testing.
|
| 14 | 14 |
|
| 15 | 15 |
If you are :ref:`installing from git <install_git_checkout>`, please look for the latest
|
| 16 |
-tag in the latest release branch to ensure you're getting the latest release.
|
|
| 16 |
+tag to ensure you're getting the latest release.
|
|
| 17 | 17 |
|
| 18 |
-Current release branches:
|
|
| 19 |
- * `bst-1.2 (latest) <https://gitlab.com/BuildStream/buildstream/commits/bst-1.2>`_
|
|
| 20 |
- * `bst-1.0 (deprecated) <https://gitlab.com/BuildStream/buildstream/commits/bst-1.0>`_
|
|
| 18 |
+* Latest release:
|
|
| 19 |
+ |
|
| 20 |
+ .. include:: release-badge.rst
|
|
| 21 |
+ |
|
| 22 |
+* Latest development snapshot:
|
|
| 23 |
+ |
|
| 24 |
+ .. include:: snapshot-badge.rst
|
| ... | ... | @@ -4,6 +4,11 @@ |
| 4 | 4 |
|
| 5 | 5 |
Install
|
| 6 | 6 |
=======
|
| 7 |
+ |
|
| 8 |
+.. include:: release-badge.rst
|
|
| 9 |
+ |
|
| 10 |
+.. include:: snapshot-badge.rst
|
|
| 11 |
+ |
|
| 7 | 12 |
This section provides instructions for installing BuildStream and its
|
| 8 | 13 |
companion artifact server on various platforms, along with any installation
|
| 9 | 14 |
related materials.
|
| 1 |
+ |
|
| 2 |
+.. Use this file to include the badge in the documentation, but not in
|
|
| 3 |
+ the README.rst or gitlab rendered materials, that doesnt work.
|
|
| 4 |
+ |
|
| 5 |
+ This is partly a workaround for a sphinx issue, we will be able
|
|
| 6 |
+ to avoid the raw html once this is implemented in sphinx:
|
|
| 7 |
+ |
|
| 8 |
+ https://github.com/sphinx-doc/sphinx/issues/2240
|
|
| 9 |
+ |
|
| 10 |
+ Using the <object> tag instead of the <img> tag which sphinx generates
|
|
| 11 |
+ allows the svg to be "interactive", for us this basically means that
|
|
| 12 |
+ the link we encode in the badge svg is used, rather than static urls
|
|
| 13 |
+ which need to be used around the <img> tag.
|
|
| 14 |
+ |
|
| 15 |
+ WARNING: The custom CSS on the style tag will need to change if we
|
|
| 16 |
+ change the theme, so that the <object> tag behaves similar
|
|
| 17 |
+ to how the <img> tag is themed by the style sheets.
|
|
| 18 |
+ |
|
| 19 |
+.. raw:: html
|
|
| 20 |
+ |
|
| 21 |
+ <a class="reference external image-reference">
|
|
| 22 |
+ <object style="margin-bottom:24px;vertical-align:middle"
|
|
| 23 |
+ data=""
|
|
| 24 |
+ type="image/svg+xml"/>
|
|
| 25 |
+ </object>
|
|
| 26 |
+ </a>
|
| 1 |
+ |
|
| 2 |
+.. Use this file to include the badge in the documentation, but not in
|
|
| 3 |
+ the README.rst or gitlab rendered materials, that doesnt work.
|
|
| 4 |
+ |
|
| 5 |
+ This is partly a workaround for a sphinx issue, we will be able
|
|
| 6 |
+ to avoid the raw html once this is implemented in sphinx:
|
|
| 7 |
+ |
|
| 8 |
+ https://github.com/sphinx-doc/sphinx/issues/2240
|
|
| 9 |
+ |
|
| 10 |
+ Using the <object> tag instead of the <img> tag which sphinx generates
|
|
| 11 |
+ allows the svg to be "interactive", for us this basically means that
|
|
| 12 |
+ the link we encode in the badge svg is used, rather than static urls
|
|
| 13 |
+ which need to be used around the <img> tag.
|
|
| 14 |
+ |
|
| 15 |
+ WARNING: The custom CSS on the style tag will need to change if we
|
|
| 16 |
+ change the theme, so that the <object> tag behaves similar
|
|
| 17 |
+ to how the <img> tag is themed by the style sheets.
|
|
| 18 |
+ |
|
| 19 |
+.. raw:: html
|
|
| 20 |
+ |
|
| 21 |
+ <a class="reference external image-reference">
|
|
| 22 |
+ <object style="margin-bottom:24px;vertical-align:middle"
|
|
| 23 |
+ data=""
|
|
| 24 |
+ type="image/svg+xml"/>
|
|
| 25 |
+ </object>
|
|
| 26 |
+ </a>
|
| ... | ... | @@ -25,7 +25,14 @@ import subprocess |
| 25 | 25 |
import sys
|
| 26 | 26 |
import versioneer
|
| 27 | 27 |
|
| 28 |
-if sys.version_info[0] != 3 or sys.version_info[1] < 5:
|
|
| 28 |
+ |
|
| 29 |
+##################################################################
|
|
| 30 |
+# Python requirements
|
|
| 31 |
+##################################################################
|
|
| 32 |
+REQUIRED_PYTHON_MAJOR = 3
|
|
| 33 |
+REQUIRED_PYTHON_MINOR = 5
|
|
| 34 |
+ |
|
| 35 |
+if sys.version_info[0] != REQUIRED_PYTHON_MAJOR or sys.version_info[1] < REQUIRED_PYTHON_MINOR:
|
|
| 29 | 36 |
print("BuildStream requires Python >= 3.5")
|
| 30 | 37 |
sys.exit(1)
|
| 31 | 38 |
|
| ... | ... | @@ -242,11 +249,28 @@ setup(name='BuildStream', |
| 242 | 249 |
|
| 243 | 250 |
author='BuildStream Developers',
|
| 244 | 251 |
author_email='buildstream-list gnome org',
|
| 252 |
+ classifiers=[
|
|
| 253 |
+ 'Environment :: Console',
|
|
| 254 |
+ 'Intended Audience :: Developers',
|
|
| 255 |
+ 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)',
|
|
| 256 |
+ 'Operating System :: POSIX',
|
|
| 257 |
+ 'Programming Language :: Python :: 3',
|
|
| 258 |
+ 'Programming Language :: Python :: 3.5',
|
|
| 259 |
+ 'Programming Language :: Python :: 3.6',
|
|
| 260 |
+ 'Programming Language :: Python :: 3.7',
|
|
| 261 |
+ 'Topic :: Software Development :: Build Tools'
|
|
| 262 |
+ ],
|
|
| 245 | 263 |
description='A framework for modelling build pipelines in YAML',
|
| 246 | 264 |
license='LGPL',
|
| 247 | 265 |
long_description=long_description,
|
| 248 | 266 |
long_description_content_type='text/x-rst; charset=UTF-8',
|
| 249 | 267 |
url='https://gitlab.com/BuildStream/buildstream',
|
| 268 |
+ project_urls={
|
|
| 269 |
+ 'Documentation': 'https://buildstream.gitlab.io/buildstream/',
|
|
| 270 |
+ 'Tracker': 'https://gitlab.com/BuildStream/buildstream/issues',
|
|
| 271 |
+ 'Mailing List': 'https://mail.gnome.org/mailman/listinfo/buildstream-list'
|
|
| 272 |
+ },
|
|
| 273 |
+ python_requires='~={}.{}'.format(REQUIRED_PYTHON_MAJOR, REQUIRED_PYTHON_MINOR),
|
|
| 250 | 274 |
packages=find_packages(exclude=('tests', 'tests.*')),
|
| 251 | 275 |
package_data={'buildstream': ['plugins/*/*.py', 'plugins/*/*.yaml',
|
| 252 | 276 |
'data/*.yaml', 'data/*.sh.in']},
|
| ... | ... | @@ -129,6 +129,7 @@ def assert_cache_keys(project_dir, output): |
| 129 | 129 |
"Use tests/cachekey/update.py to automatically " +
|
| 130 | 130 |
"update this test case")
|
| 131 | 131 |
|
| 132 |
+ |
|
| 132 | 133 |
##############################################
|
| 133 | 134 |
# Test Entry Point #
|
| 134 | 135 |
##############################################
|
| ... | ... | @@ -65,5 +65,6 @@ def update_keys(): |
| 65 | 65 |
|
| 66 | 66 |
write_expected_key(element_name, actual_keys[element_name])
|
| 67 | 67 |
|
| 68 |
+ |
|
| 68 | 69 |
if __name__ == '__main__':
|
| 69 | 70 |
update_keys()
|
| 1 | 1 |
import os
|
| 2 | 2 |
import pytest
|
| 3 |
+import sys
|
|
| 3 | 4 |
from buildstream import _yaml
|
| 4 | 5 |
from buildstream._exceptions import ErrorDomain, LoadErrorReason
|
| 5 | 6 |
from tests.testutils.runcli import cli
|
| ... | ... | @@ -72,3 +73,20 @@ def test_missing_variable(cli, datafiles, tmpdir): |
| 72 | 73 |
])
|
| 73 | 74 |
result.assert_main_error(ErrorDomain.LOAD,
|
| 74 | 75 |
LoadErrorReason.UNRESOLVED_VARIABLE)
|
| 76 |
+ |
|
| 77 |
+@pytest.mark.timeout(3, method="signal")
|
|
| 78 |
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'cyclic_variables'))
|
|
| 79 |
+def test_cyclic_variables(cli, datafiles):
|
|
| 80 |
+ print_warning("Performing cyclic test, if this test times out it will " +
|
|
| 81 |
+ "exit the test sequence")
|
|
| 82 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 83 |
+ result = cli.run(project=project, silent=True, args=[
|
|
| 84 |
+ "build", "cyclic.bst"
|
|
| 85 |
+ ])
|
|
| 86 |
+ result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.RECURSIVE_VARIABLE)
|
|
| 87 |
+ |
|
| 88 |
+ |
|
| 89 |
+def print_warning(msg):
|
|
| 90 |
+ RED, END = "\033[91m", "\033[0m"
|
|
| 91 |
+ print(("\n{}{}{}").format(RED, msg, END), file=sys.stderr)
|
|
| 92 |
+ |
| 1 |
+kind: manual
|
|
| 2 |
+ |
|
| 3 |
+variables:
|
|
| 4 |
+ a: "%{prefix}/a"
|
|
| 5 |
+ prefix: "%{a}/some_prefix/"
|
|
| \ No newline at end of file |
| 1 |
+name: test
|
| ... | ... | @@ -288,6 +288,7 @@ def test_build_checkout_force_tarball(datafiles, cli): |
| 288 | 288 |
assert os.path.join('.', 'usr', 'bin', 'hello') in tar.getnames()
|
| 289 | 289 |
assert os.path.join('.', 'usr', 'include', 'pony.h') in tar.getnames()
|
| 290 | 290 |
|
| 291 |
+ |
|
| 291 | 292 |
fetch_build_checkout_combos = \
|
| 292 | 293 |
[("strict", kind) for kind in ALL_REPO_KINDS] + \
|
| 293 | 294 |
[("non-strict", kind) for kind in ALL_REPO_KINDS]
|
| ... | ... | @@ -94,14 +94,28 @@ class Result(): |
| 94 | 94 |
# error_reason (any): The reason field of the error which occurred
|
| 95 | 95 |
# fail_message (str): An optional message to override the automatic
|
| 96 | 96 |
# assertion error messages
|
| 97 |
+ # debug (bool): If true, prints information regarding the exit state of the result()
|
|
| 97 | 98 |
# Raises:
|
| 98 | 99 |
# (AssertionError): If any of the assertions fail
|
| 99 | 100 |
#
|
| 100 | 101 |
def assert_main_error(self,
|
| 101 | 102 |
error_domain,
|
| 102 | 103 |
error_reason,
|
| 103 |
- fail_message=''):
|
|
| 104 |
- |
|
| 104 |
+ fail_message='',
|
|
| 105 |
+ *, debug=False):
|
|
| 106 |
+ if debug:
|
|
| 107 |
+ print(
|
|
| 108 |
+ """
|
|
| 109 |
+ Exit code: {}
|
|
| 110 |
+ Exception: {}
|
|
| 111 |
+ Domain: {}
|
|
| 112 |
+ Reason: {}
|
|
| 113 |
+ """.format(
|
|
| 114 |
+ self.exit_code,
|
|
| 115 |
+ self.exception,
|
|
| 116 |
+ self.exception.domain,
|
|
| 117 |
+ self.exception.reason
|
|
| 118 |
+ ))
|
|
| 105 | 119 |
assert self.exit_code == -1, fail_message
|
| 106 | 120 |
assert self.exc is not None, fail_message
|
| 107 | 121 |
assert self.exception is not None, fail_message
|
