Merge ~morphis/snappy-hwe-snaps/+git/jenkins-jobs:f/kernel-cross-build-support into ~snappy-hwe-team/snappy-hwe-snaps/+git/jenkins-jobs:master

Proposed by Simon Fels
Status: Merged
Approved by: Alfonso Sanchez-Beato
Approved revision: c8dd33592bcd3af6477148d134a43800938e2b02
Merged at revision: 5d818f78ab6785efa28bb6a8776dc4f7ca5569fc
Proposed branch: ~morphis/snappy-hwe-snaps/+git/jenkins-jobs:f/kernel-cross-build-support
Merge into: ~snappy-hwe-team/snappy-hwe-snaps/+git/jenkins-jobs:master
Diff against target: 609 lines (+503/-11)
3 files modified
jobs/snap/snap-build-worker.sh (+14/-1)
tools/shyaml (+454/-0)
tools/snapbuild.sh (+35/-10)
Reviewer Review Type Date Requested Status
Alfonso Sanchez-Beato Approve
System Enablement Bot continuous-integration Approve
Review via email: mp+325979@code.launchpad.net

Description of the change

Implement cross-build support for kernel type snaps

This imports the shyaml script into the tree as we don't have another way to install shyaml in our jenkins environment (even the proxy disallows python pip access).

To post a comment you must log in.
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alfonso Sanchez-Beato (alfonsosanchezbeato) wrote :

A couple of inline comments. Also:

1. Please add to the header of "tools/shyaml" the external repo/commit from which it was taken so we cant track the origin an update easily in the future.

2. It would be a good opportunity to clean scripts of shellcheck warnings, see
https://pastebin.canonical.com/191399/
and
https://pastebin.canonical.com/191400/

review: Needs Fixing
Revision history for this message
Simon Fels (morphis) wrote :

Let's move the shellcheck cleanup into another MP.

Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alfonso Sanchez-Beato (alfonsosanchezbeato) wrote :

Thanks for the changes. Re: shellcheck, alright, but tbh 90% of the warnings are just adding quotes around, so no big deal. We should develop the habit of running shellcheck whenever we modify a shell script, it is an incredibly helpful tool.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/jobs/snap/snap-build-worker.sh b/jobs/snap/snap-build-worker.sh
index e393c74..0c62b2c 100644
--- a/jobs/snap/snap-build-worker.sh
+++ b/jobs/snap/snap-build-worker.sh
@@ -82,12 +82,25 @@ sed -i "s/~$LAUNCHPAD_TEAM\/$LAUNCHPAD_PROJECT\/+git\/$REPO_NAME/~$LAUNCHPAD_TEA
82# $WORKSPACE/.build_env file.82# $WORKSPACE/.build_env file.
8383
84if [ "$BUILD_ON_LAUNCHPAD" = False ]; then84if [ "$BUILD_ON_LAUNCHPAD" = False ]; then
85 SNAPBUILD_EXTRA_ARGS=
86 SNAP_TYPE=$($BUILD_SCRIPTS/tools/shyaml get-value type < $SNAPCRAFT_YAML_PATH)
87 case "$SNAP_TYPE" in
88 kernel)
89 if [ "$ARCH" != amd64 ]; then
90 # If we're building a kernel snap we have to cross-build it
91 # instead of building it in a native environment.
92 SNAPBUILD_EXTRA_ARGS="--cross-build"
93 fi
94 ;;
95 esac
96
85 sudo $BUILD_SCRIPTS/tools/snapbuild.sh \97 sudo $BUILD_SCRIPTS/tools/snapbuild.sh \
86 --source-dir=$WORKSPACE/src \98 --source-dir=$WORKSPACE/src \
87 --results-dir=$WORKSPACE/results \99 --results-dir=$WORKSPACE/results \
88 --arch=$ARCHITECTURE \100 --arch=$ARCHITECTURE \
89 --series=$SERIES \101 --series=$SERIES \
90 --proxy=squid.internal:3128102 --proxy=squid.internal:3128 \
103 $SNAPBUILD_EXTRA_ARGS
91else104else
92 git remote add jenkins-ci git+ssh://$BOT_USERNAME@git.launchpad.net/~$LAUNCHPAD_TEAM/$LAUNCHPAD_PROJECT/+git/$CI_REPO105 git remote add jenkins-ci git+ssh://$BOT_USERNAME@git.launchpad.net/~$LAUNCHPAD_TEAM/$LAUNCHPAD_PROJECT/+git/$CI_REPO
93 git push jenkins-ci --all106 git push jenkins-ci --all
diff --git a/tools/shyaml b/tools/shyaml
94new file mode 100755107new file mode 100755
index 0000000..e4618ec
--- /dev/null
+++ b/tools/shyaml
@@ -0,0 +1,454 @@
1#!/usr/bin/env python
2
3# Taken from upstream git repository https://github.com/0k/shyaml
4# at revision d77e30599a0971c51896ef97d21883550e7e9979
5
6## Note: to launch test, you can use:
7## python -m doctest -d shyaml.py
8## or
9## nosetests
10
11from __future__ import print_function
12
13import sys
14import yaml
15import os.path
16import re
17
18PY3 = sys.version_info[0] >= 3
19
20EXNAME = os.path.basename(sys.argv[0])
21
22USAGE = """\
23Usage:
24
25 %(exname)s (-h|--help)
26 %(exname)s [-y|--yaml] ACTION KEY [DEFAULT]
27""" % {"exname": EXNAME}
28
29HELP = """
30Parses and output chosen subpart or values from YAML input.
31It reads YAML in stdin and will output on stdout it's return value.
32
33%(usage)s
34
35Options:
36
37 -y, --yaml
38 Output only YAML safe value, more precisely, even
39 literal values will be YAML quoted. This behavior
40 is required if you want to output YAML subparts and
41 further process it. If you know you have are dealing
42 with safe literal value, then you don't need this.
43 (Default: no safe YAML output)
44
45 ACTION Depending on the type of data you've targetted
46 thanks to the KEY, ACTION can be:
47
48 These ACTIONs applies to any YAML type:
49
50 get-type ## returns a short string
51 get-value ## returns YAML
52
53 This ACTION applies to 'sequence' and 'struct' YAML type:
54
55 get-values{,-0} ## return list of YAML
56
57 These ACTION applies to 'struct' YAML type:
58
59 keys{,-0} ## return list of YAML
60 values{,-0} ## return list of YAML
61 key-values,{,-0} ## return list of YAML
62
63 Note that any value returned is returned on stdout, and
64 when returning ``list of YAML``, it'll be separated by
65 ``\\n`` or ``NUL`` char depending of you've used the
66 ``-0`` suffixed ACTION.
67
68 KEY Identifier to browse and target subvalues into YAML
69 structure. Use ``.`` to parse a subvalue. If you need
70 to use a literal ``.`` or ``\``, use ``\`` to quote it.
71
72 Use struct keyword to browse ``struct`` YAML data and use
73 integers to browse ``sequence`` YAML data.
74
75 DEFAULT if not provided and given KEY do not match any value in
76 the provided YAML, then DEFAULT will be returned. If no
77 default is provided and the KEY do not match any value
78 in the provided YAML, %(exname)s will fail with an error
79 message.
80
81Examples:
82
83 ## get last grocery
84 cat recipe.yaml | %(exname)s get-value groceries.-1
85
86 ## get all words of my french dictionary
87 cat dictionaries.yaml | %(exname)s keys-0 french.dictionary
88
89 ## get YAML config part of 'myhost'
90 cat hosts_config.yaml | %(exname)s get-value cfgs.myhost
91
92""" % {"exname": EXNAME, "usage": USAGE}
93
94##
95## Keep previous order in YAML
96##
97
98try:
99 # included in standard lib from Python 2.7
100 from collections import OrderedDict
101except ImportError:
102 # try importing the backported drop-in replacement
103 # it's available on PyPI
104 from ordereddict import OrderedDict
105
106
107## Ensure that there are no collision with legacy OrderedDict
108## that could be used for omap for instance.
109class MyOrderedDict(OrderedDict):
110 pass
111
112yaml.add_representer(
113 MyOrderedDict,
114 lambda cls, data: cls.represent_dict(data.items()))
115
116
117def construct_omap(cls, node):
118 ## Force unfolding reference and merges
119 ## otherwise it would fail on 'merge'
120 cls.flatten_mapping(node)
121 return MyOrderedDict(cls.construct_pairs(node))
122
123
124yaml.add_constructor(
125 yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
126 construct_omap)
127
128
129##
130## Key specifier
131##
132
133def tokenize(s):
134 r"""Returns an iterable through all subparts of string splitted by '.'
135
136 So:
137
138 >>> list(tokenize('foo.bar.wiz'))
139 ['foo', 'bar', 'wiz']
140
141 Contrary to traditional ``.split()`` method, this function has to
142 deal with any type of data in the string. So it actually
143 interprets the string. Characters with meaning are '.' and '\'.
144 Both of these can be included in a token by quoting them with '\'.
145
146 So dot of slashes can be contained in token:
147
148 >>> print('\n'.join(tokenize(r'foo.dot<\.>.slash<\\>')))
149 foo
150 dot<.>
151 slash<\>
152
153 Notice that empty keys are also supported:
154
155 >>> list(tokenize(r'foo..bar'))
156 ['foo', '', 'bar']
157
158 Given an empty string:
159
160 >>> list(tokenize(r''))
161 ['']
162
163 And a None value:
164
165 >>> list(tokenize(None))
166 []
167
168 """
169 if s is None:
170 raise StopIteration
171 tokens = (re.sub(r'\\(\\|\.)', r'\1', m.group(0))
172 for m in re.finditer(r'((\\.|[^.\\])*)', s))
173 ## an empty string superfluous token is added after all non-empty token
174 for token in tokens:
175 if len(token) != 0:
176 next(tokens)
177 yield token
178
179
180def mget(dct, key):
181 r"""Allow to get values deep in recursive dict with doted keys
182
183 Accessing leaf values is quite straightforward:
184
185 >>> dct = {'a': {'x': 1, 'b': {'c': 2}}}
186 >>> mget(dct, 'a.x')
187 1
188 >>> mget(dct, 'a.b.c')
189 2
190
191 But you can also get subdict if your key is not targeting a
192 leaf value:
193
194 >>> mget(dct, 'a.b')
195 {'c': 2}
196
197 As a special feature, list access is also supported by providing a
198 (possibily signed) integer, it'll be interpreted as usual python
199 sequence access using bracket notation:
200
201 >>> mget({'a': {'x': [1, 5], 'b': {'c': 2}}}, 'a.x.-1')
202 5
203 >>> mget({'a': {'x': 1, 'b': [{'c': 2}]}}, 'a.b.0.c')
204 2
205
206 Keys that contains '.' can be accessed by escaping them:
207
208 >>> dct = {'a': {'x': 1}, 'a.x': 3, 'a.y': 4}
209 >>> mget(dct, 'a.x')
210 1
211 >>> mget(dct, r'a\.x')
212 3
213 >>> mget(dct, r'a.y') ## doctest: +IGNORE_EXCEPTION_DETAIL
214 Traceback (most recent call last):
215 ...
216 MissingKeyError: missing key 'y' in dict.
217 >>> mget(dct, r'a\.y')
218 4
219
220 As a consequence, if your key contains a '\', you should also escape it:
221
222 >>> dct = {r'a\x': 3, r'a\.x': 4, 'a.x': 5, 'a\\': {'x': 6}}
223 >>> mget(dct, r'a\\x')
224 3
225 >>> mget(dct, r'a\\\.x')
226 4
227 >>> mget(dct, r'a\\.x')
228 6
229 >>> mget({'a\\': {'b': 1}}, r'a\\.b')
230 1
231 >>> mget({r'a.b\.c': 1}, r'a\.b\\\.c')
232 1
233
234 And even empty strings key are supported:
235
236 >>> dct = {r'a': {'': {'y': 3}, 'y': 4}, 'b': {'': {'': 1}}, '': 2}
237 >>> mget(dct, r'a..y')
238 3
239 >>> mget(dct, r'a.y')
240 4
241 >>> mget(dct, r'')
242 2
243 >>> mget(dct, r'b..')
244 1
245
246 It will complain if you are trying to get into a leaf:
247
248 >>> mget({'a': 1}, 'a.y') ## doctest: +IGNORE_EXCEPTION_DETAIL
249 Traceback (most recent call last):
250 ...
251 NonDictLikeTypeError: can't query subvalue 'y' of a leaf...
252
253 if the key is None, the whole dct should be sent back:
254
255 >>> mget({'a': 1}, None)
256 {'a': 1}
257
258 """
259 return aget(dct, tokenize(key))
260
261
262class MissingKeyError(KeyError):
263 """Raised when querying a dict-like structure on non-existing keys"""
264
265 def __str__(self):
266 return self.message
267
268
269class NonDictLikeTypeError(TypeError):
270 """Raised when attempting to traverse non-dict like structure"""
271
272
273class IndexNotIntegerError(ValueError):
274 """Raised when attempting to traverse sequence without using an integer"""
275
276
277class IndexOutOfRange(IndexError):
278 """Raised when attempting to traverse sequence without using an integer"""
279
280
281def aget(dct, key):
282 r"""Allow to get values deep in a dict with iterable keys
283
284 Accessing leaf values is quite straightforward:
285
286 >>> dct = {'a': {'x': 1, 'b': {'c': 2}}}
287 >>> aget(dct, ('a', 'x'))
288 1
289 >>> aget(dct, ('a', 'b', 'c'))
290 2
291
292 If key is empty, it returns unchanged the ``dct`` value.
293
294 >>> aget({'x': 1}, ())
295 {'x': 1}
296
297 """
298 key = iter(key)
299 try:
300 head = next(key)
301 except StopIteration:
302 return dct
303
304 if isinstance(dct, list):
305 try:
306 idx = int(head)
307 except ValueError:
308 raise IndexNotIntegerError(
309 "non-integer index %r provided on a list."
310 % head)
311 try:
312 value = dct[idx]
313 except IndexError:
314 raise IndexOutOfRange(
315 "index %d is out of range (%d elements in list)."
316 % (idx, len(dct)))
317 else:
318 try:
319 value = dct[head]
320 except KeyError:
321 ## Replace with a more informative KeyError
322 raise MissingKeyError(
323 "missing key %r in dict."
324 % (head, ))
325 except:
326 raise NonDictLikeTypeError(
327 "can't query subvalue %r of a leaf%s."
328 % (head,
329 (" (leaf value is %r)" % dct)
330 if len(repr(dct)) < 15 else ""))
331 return aget(value, key)
332
333
334def stderr(msg):
335 """Convenience function to write short message to stderr."""
336 sys.stderr.write(msg)
337
338
339def stdout(value):
340 """Convenience function to write short message to stdout."""
341 sys.stdout.write(value)
342
343
344def die(msg, errlvl=1, prefix="Error: "):
345 """Convenience function to write short message to stderr and quit."""
346 stderr("%s%s\n" % (prefix, msg))
347 sys.exit(errlvl)
348
349SIMPLE_TYPES = (str if PY3 else basestring, int, float, type(None))
350COMPLEX_TYPES = (list, dict)
351
352
353def magic_dump(value):
354 """Returns a representation of values directly usable by bash.
355
356 Literal types are printed as-is (avoiding quotes around string for
357 instance). But complex type are written in a YAML useable format.
358
359 """
360 return value if isinstance(value, SIMPLE_TYPES) \
361 else yaml.dump(value, default_flow_style=False)
362
363def yaml_dump(value):
364 """Returns a representation of values directly usable by bash.
365
366 Literal types are quoted and safe to use as YAML.
367
368 """
369 return yaml.dump(value, default_flow_style=False)
370
371
372def type_name(value):
373 """Returns pseudo-YAML type name of given value."""
374 return "struct" if isinstance(value, dict) else \
375 "sequence" if isinstance(value, (tuple, list)) else \
376 type(value).__name__
377
378
379def main(args): ## pylint: disable=too-many-branches
380 """Entrypoint of the whole application"""
381
382 if len(args) == 0:
383 stderr("Error: Bad number of arguments.\n")
384 die(USAGE, errlvl=1, prefix="")
385
386 if len(args) == 1 and args[0] in ("-h", "--help"):
387 stdout(HELP)
388 exit(0)
389
390 dump = magic_dump
391 for arg in ["-y", "--yaml"]:
392 if arg in args:
393 args.remove(arg)
394 dump = yaml_dump
395
396 action = args[0]
397 key_value = None if len(args) == 1 else args[1]
398 default = args[2] if len(args) > 2 else None
399 contents = yaml.load(sys.stdin)
400
401 try:
402 try:
403 value = mget(contents, key_value)
404 except (IndexOutOfRange, MissingKeyError):
405 if default is None:
406 raise
407 value = default
408 except (IndexOutOfRange, MissingKeyError,
409 NonDictLikeTypeError, IndexNotIntegerError) as exc:
410 msg = str(exc.message)
411 die("invalid path %r, %s"
412 % (key_value,
413 msg.replace('list', 'sequence').replace('dict', 'struct')))
414
415 tvalue = type_name(value)
416 termination = "\0" if action.endswith("-0") else "\n"
417
418 if action == "get-value":
419 print(dump(value), end='')
420 elif action in ("get-values", "get-values-0"):
421 if isinstance(value, dict):
422 for k, v in value.iteritems():
423 stdout("%s%s%s%s" % (dump(k), termination,
424 dump(v), termination))
425 elif isinstance(value, list):
426 for l in value:
427 stdout("%s%s" % (dump(l), termination))
428 else:
429 die("%s does not support %r type. "
430 "Please provide or select a sequence or struct."
431 % (action, tvalue))
432 elif action == "get-type":
433 print(tvalue)
434 elif action in ("keys", "keys-0",
435 "values", "values-0",
436 "key-values", "key-values-0"):
437 if isinstance(value, dict):
438 method = value.keys if action.startswith("keys") else \
439 value.items if action.startswith("key-values") else \
440 value.values
441 output = (lambda x: termination.join(str(dump(e)) for e in x)) \
442 if action.startswith("key-values") else \
443 dump
444 for k in method():
445 stdout("%s%s" % (output(k), termination))
446 else:
447 die("%s does not support %r type. "
448 "Please provide or select a struct." % (action, tvalue))
449 else:
450 die("Invalid argument.\n%s" % USAGE)
451
452
453if __name__ == "__main__":
454 sys.exit(main(sys.argv[1:]))
diff --git a/tools/snapbuild.sh b/tools/snapbuild.sh
index 20ca763..c98fcb4 100755
--- a/tools/snapbuild.sh
+++ b/tools/snapbuild.sh
@@ -24,10 +24,16 @@ fi
24SERIES=xenial24SERIES=xenial
25SOURCE_DIR=25SOURCE_DIR=
26RESULTS_DIR=26RESULTS_DIR=
27# Whenever you change the chroot in a way which needs a regeneration
28# on the build server bump the version here. This will tell the
29# job which updates the chroots to generate a new one.
27CHROOT_VERSION=130CHROOT_VERSION=1
28ARCH=amd6431BUILD_ARCH=amd64
32TARGET_ARCH=amd64
29UPDATE_CHROOT=false33UPDATE_CHROOT=false
30PROXY=34PROXY=
35CROSS_BUILD=false
36SNAPCRAFT_EXTRA_ARGS=
3137
32while [ -n "$1" ]; do38while [ -n "$1" ]; do
33 case "$1" in39 case "$1" in
@@ -44,7 +50,8 @@ while [ -n "$1" ]; do
44 shift50 shift
45 ;;51 ;;
46 --arch=*)52 --arch=*)
47 ARCH=${1#*=}53 TARGET_ARCH=${1#*=}
54 BUILD_ARCH=$TARGET_ARCH
48 shift55 shift
49 ;;56 ;;
50 --update-chroot)57 --update-chroot)
@@ -55,6 +62,10 @@ while [ -n "$1" ]; do
55 PROXY=${1#*=}62 PROXY=${1#*=}
56 shift63 shift
57 ;;64 ;;
65 --cross-build)
66 CROSS_BUILD=true
67 shift
68 ;;
58 *)69 *)
59 echo "ERROR: Unknown options $1"70 echo "ERROR: Unknown options $1"
60 exit 171 exit 1
@@ -66,8 +77,14 @@ if [ -z "$SERIES" ]; then
66 exit 177 exit 1
67fi78fi
6879
80# If we're cross-building we have to switch the architecture we're using
81# for our build environment to match our host arch.
82if [ "$CROSS_BUILD" = true ]; then
83 HOST_ARCH=$(dpkg --print-architecture)
84fi
85
69CHROOT_STORE_PATH=/build/chroots86CHROOT_STORE_PATH=/build/chroots
70CHROOT_TARBALL=$SERIES-$ARCH-$CHROOT_VERSION-rootfs.tar87CHROOT_TARBALL=$SERIES-$BUILD_ARCH-$CHROOT_VERSION-rootfs.tar
7188
72if [ "$UPDATE_CHROOT" = true ]; then89if [ "$UPDATE_CHROOT" = true ]; then
73 if [ ! -e $CHROOT_STORE_PATH/$CHROOT_TARBALL ] ; then90 if [ ! -e $CHROOT_STORE_PATH/$CHROOT_TARBALL ] ; then
@@ -77,7 +94,7 @@ if [ "$UPDATE_CHROOT" = true ]; then
7794
78 DEBOOTSTRAP=debootstrap95 DEBOOTSTRAP=debootstrap
79 DEB_REPO_URL=96 DEB_REPO_URL=
80 case "$ARCH" in97 case "$BUILD_ARCH" in
81 amd64)98 amd64)
82 DEB_REPO_URL="http://archive.ubuntu.com/ubuntu/"99 DEB_REPO_URL="http://archive.ubuntu.com/ubuntu/"
83 ;;100 ;;
@@ -86,7 +103,7 @@ if [ "$UPDATE_CHROOT" = true ]; then
86 DEB_REPO_URL="http://ports.ubuntu.com/ubuntu-ports"103 DEB_REPO_URL="http://ports.ubuntu.com/ubuntu-ports"
87 ;;104 ;;
88 *)105 *)
89 echo "ERROR: Unsupported architecture $ARCH"106 echo "ERROR: Unsupported architecture $BUILD_ARCH"
90 exit 1107 exit 1
91 ;;108 ;;
92 esac109 esac
@@ -97,7 +114,7 @@ if [ "$UPDATE_CHROOT" = true ]; then
97114
98 trap cleanup INT EXIT115 trap cleanup INT EXIT
99116
100 $DEBOOTSTRAP --components=main,universe --arch $ARCH $SERIES $WORKDIR/rootfs117 $DEBOOTSTRAP --components=main,universe --arch $BUILD_ARCH $SERIES $WORKDIR/rootfs
101 cat << EOF > $WORKDIR/rootfs/etc/apt/sources.list.d/updates.list118 cat << EOF > $WORKDIR/rootfs/etc/apt/sources.list.d/updates.list
102deb $DEB_REPO_URL xenial universe119deb $DEB_REPO_URL xenial universe
103deb $DEB_REPO_URL xenial-updates main universe120deb $DEB_REPO_URL xenial-updates main universe
@@ -147,19 +164,27 @@ tar xf $CHROOT_STORE_PATH/$CHROOT_TARBALL -C $BUILDDIR
147164
148cp -ra $SOURCE_DIR $BUILDDIR/src165cp -ra $SOURCE_DIR $BUILDDIR/src
149166
167if [ "$CROSS_BUILD" = true ]; then
168 SNAPCRAFT_EXTRA_ARGS="$SNAPCRAFT_EXTRA_ARGS --target-arch=$TARGET_ARCH"
169fi
170
150cat << EOF > $BUILDDIR/do-build.sh171cat << EOF > $BUILDDIR/do-build.sh
151#!/bin/sh172#!/bin/sh
152set -ex173set -ex
153apt update174apt update
154apt upgrade -y175apt upgrade -y
155cd /src176cd /src
177
178# snapcraft is pretty unhappy when LC_ALL and LANG aren't set
179export LC_ALL=C.UTF-8
180export LANG=C.UTF-8
181
182# To access certain things like the snap store we need a proxy in place
183# in some environments
156export http_proxy=$PROXY184export http_proxy=$PROXY
157export https_proxy=$PROXY185export https_proxy=$PROXY
158SNAPCRAFT_EXTRA_ARGS=186
159snapcraft clean187snapcraft clean
160if [ -e /src/.ci-setup.sh ]; then
161 . /src/.ci-setup.sh
162fi
163snapcraft $SNAPCRAFT_EXTRA_ARGS188snapcraft $SNAPCRAFT_EXTRA_ARGS
164EOF189EOF
165chmod +x $BUILDDIR/do-build.sh190chmod +x $BUILDDIR/do-build.sh

Subscribers

People subscribed via source and target branches

to all changes: