Merge lp:~sergiusens/snapcraft/config into lp:~snappy-dev/snapcraft/core

Proposed by Sergio Schvezov on 2015-09-14
Status: Merged
Approved by: Sergio Schvezov on 2015-09-15
Approved revision: no longer in the source branch.
Merged at revision: 166
Proposed branch: lp:~sergiusens/snapcraft/config
Merge into: lp:~snappy-dev/snapcraft/core
Prerequisite: lp:~sergiusens/snapcraft/collisions
Diff against target: 456 lines (+231/-65)
9 files modified
examples/webcam-webui/config.py (+57/-0)
examples/webcam-webui/setup.py (+16/-0)
examples/webcam-webui/snapcraft.yaml (+11/-1)
examples/webcam-webui/webcam-webui (+7/-1)
integration-tests/data/assemble/binary1.after (+1/-0)
schema/snapcraft.yaml (+3/-0)
snapcraft/meta.py (+33/-9)
snapcraft/sources.py (+3/-1)
snapcraft/tests/test_meta.py (+100/-53)
To merge this branch: bzr merge lp:~sergiusens/snapcraft/config
Reviewer Review Type Date Requested Status
Sergio Schvezov Approve on 2015-09-15
John Lenton 2015-09-14 Approve on 2015-09-15
Review via email: mp+270989@code.launchpad.net

Commit Message

Support config

Description of the Change

This branch ends up being more complicated than necessary because I chose the config hook to be python based and setuptools rewrites the shebang. (We probably need to do the reverse in our plugin).

To post a comment you must log in.
lp:~sergiusens/snapcraft/config updated on 2015-09-14
158. By Björn Tillenius on 2015-09-14

Add support for setuptools based python projects. by bjornt approved by sergiusens,ted

159. By Sergio Schvezov on 2015-09-14

Filesets based filtering for stage and snap by sergiusens approved by sergiusens,mvo

160. By Sergio Schvezov on 2015-09-14

snapcraft init with templated values by sergiusens approved by mvo

161. By Sergio Schvezov on 2015-09-14

regex for binary and service names by sergiusens approved by mvo

Sergio Schvezov (sergiusens) wrote :

hmm, my config.py is missing :/

lp:~sergiusens/snapcraft/config updated on 2015-09-15
162. By Sergio Schvezov on 2015-09-14

docs refresh by sergiusens approved by chipaca

163. By Sergio Schvezov on 2015-09-14

Collision logic updates with an introduction of _BUILTIN_OPTIONS so all parts have a certain set of options by default even if not declared. by sergiusens approved by mvo

164. By Daniel Holbach on 2015-09-15

fix crash when stage_packages is defined in the yaml for the plugin, but empty (getattr does not use [] as default return value in that case) by dholbach approved by sergiusens

165. By Daniel Holbach on 2015-09-15

Fix small typo. by dholbach approved by sergiusens

John Lenton (chipaca) wrote :

LGTM. Minor nit.

review: Approve
Sergio Schvezov (sergiusens) wrote :

I fixed the nit

review: Approve
lp:~sergiusens/snapcraft/config updated on 2015-09-15
166. By Sergio Schvezov on 2015-09-15

Support config by sergiusens approved by sergiusens,chipaca

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'examples/webcam-webui/config.py'
2--- examples/webcam-webui/config.py 1970-01-01 00:00:00 +0000
3+++ examples/webcam-webui/config.py 2015-09-15 20:53:59 +0000
4@@ -0,0 +1,57 @@
5+#!/usr/bin/env python3
6+
7+import os
8+import os.path
9+import sys
10+import yaml
11+
12+_CONFIG = 'configuration'
13+
14+_DEFAULT_INTERVAL = 10
15+
16+
17+def main():
18+ config_file = os.path.join(os.environ['SNAP_APP_DATA_PATH'], _CONFIG)
19+
20+ config_yaml = yaml.load(sys.stdin)
21+ if config_yaml:
22+ set_config(config_file, config_yaml)
23+
24+ yaml.dump(get_config(config_file), stream=sys.stdout, default_flow_style=False)
25+
26+
27+def set_config(config_file, config_yaml={}):
28+ with open(config_file, 'w') as f:
29+ yaml.dump(_config(config_yaml), stream=f, default_flow_style=False)
30+
31+ return config_yaml
32+
33+
34+def get_config(config_file):
35+ try:
36+ with open(config_file) as f:
37+ return yaml.load(f)
38+ except FileNotFoundError:
39+ return _config()
40+
41+
42+def _config(config_yaml={}):
43+ try:
44+ interval_value = config_yaml['config'][os.environ['SNAP_NAME']]['interval']
45+ if not isinstance(interval_value, int):
46+ config_yaml['config'][os.environ['SNAP_NAME']]['interval'] = _DEFAULT_INTERVAL
47+ except KeyError:
48+ interval = {
49+ 'config': {
50+ os.environ['SNAP_NAME']: {
51+ 'interval': _DEFAULT_INTERVAL
52+ }
53+ }
54+ }
55+ config_yaml.update(interval)
56+
57+ return config_yaml
58+
59+
60+if __name__ == '__main__':
61+ main()
62
63=== added file 'examples/webcam-webui/setup.py'
64--- examples/webcam-webui/setup.py 1970-01-01 00:00:00 +0000
65+++ examples/webcam-webui/setup.py 2015-09-15 20:53:59 +0000
66@@ -0,0 +1,16 @@
67+#!/usr/bin/env python3
68+
69+from setuptools import setup
70+
71+setup(
72+ name='config',
73+ description='config for webcam-webui',
74+ author='Sergio Schvezov <sergio.schvezov@canonical.com>',
75+ license='GPLv3',
76+ install_requires=[
77+ 'pyyaml',
78+ ],
79+ scripts=[
80+ 'config.py',
81+ ],
82+)
83
84=== modified file 'examples/webcam-webui/snapcraft.yaml'
85--- examples/webcam-webui/snapcraft.yaml 2015-09-11 14:20:10 +0000
86+++ examples/webcam-webui/snapcraft.yaml 2015-09-15 20:53:59 +0000
87@@ -7,6 +7,7 @@
88 services:
89 - name: webcam-webui
90 start: bin/webcam-webui
91+config: python3 usr/bin/config.py
92
93 parts:
94 cam:
95@@ -21,11 +22,20 @@
96 - usr/lib
97 go-server:
98 - bin/golang-*
99+ ignore:
100+ - -lib/x86_64-linux-gnu/libexpat*
101+ - -usr/lib/x86_64-linux-gnu/libexpat*
102+ - -usr/share/doc
103+ stage:
104+ - $ignore
105 snap:
106+ - $ignore
107 - $fswebcam
108 - $go-server
109 glue:
110 type: copy
111 files:
112 webcam-webui: bin/webcam-webui
113-
114+ config:
115+ type: python3-project
116+ source: .
117
118=== modified file 'examples/webcam-webui/webcam-webui'
119--- examples/webcam-webui/webcam-webui 2015-09-11 14:20:10 +0000
120+++ examples/webcam-webui/webcam-webui 2015-09-15 20:53:59 +0000
121@@ -3,9 +3,15 @@
122
123 cd "$SNAP_APP_DATA_PATH"
124
125+[ -f configuration ] && interval=$(sed -n 's/.*interval: `\([0-9.]\+\).*/\1/p' configuration)
126+
127+[ -z "$interval" ] && interval=10
128+
129+echo "Snapping every $interval seconds"
130+
131 golang-static-http &
132
133 while :; do
134 fswebcam shot.jpeg
135- sleep 10
136+ sleep $interval
137 done
138
139=== modified file 'integration-tests/data/assemble/binary1.after'
140--- integration-tests/data/assemble/binary1.after 2015-08-05 15:39:18 +0000
141+++ integration-tests/data/assemble/binary1.after 2015-09-15 20:53:59 +0000
142@@ -1,4 +1,5 @@
143 #!/bin/sh
144 export PATH="$SNAP_APP_PATH/bin:$SNAP_APP_PATH/usr/bin:$PATH"
145 export LD_LIBRARY_PATH="$SNAP_APP_PATH/lib:$SNAP_APP_PATH/usr/lib:$SNAP_APP_PATH/lib/@MULTIARCH@:$SNAP_APP_PATH/usr/lib/@MULTIARCH@:$LD_LIBRARY_PATH"
146+
147 exec "$SNAP_APP_PATH/binary1" $*
148
149=== modified file 'schema/snapcraft.yaml'
150--- schema/snapcraft.yaml 2015-09-14 19:22:59 +0000
151+++ schema/snapcraft.yaml 2015-09-15 20:53:59 +0000
152@@ -38,6 +38,9 @@
153 uniqueItems: true
154 items:
155 - type: string
156+ config:
157+ type: string
158+ description: path to a configure hook to expose configuration for the package
159 services:
160 type: array
161 items:
162
163=== modified file 'snapcraft/meta.py'
164--- snapcraft/meta.py 2015-08-27 21:33:56 +0000
165+++ snapcraft/meta.py 2015-09-15 20:53:59 +0000
166@@ -57,6 +57,9 @@
167 _write_package_yaml(meta_dir, config_data, arches)
168 _write_readme_md(meta_dir, config_data)
169
170+ if 'config' in config_data:
171+ _setup_config_hook(meta_dir, config_data['config'])
172+
173 return meta_dir
174
175
176@@ -76,6 +79,18 @@
177 f.write(readme_md)
178
179
180+def _setup_config_hook(meta_dir, config):
181+ hooks_dir = os.path.join(meta_dir, 'hooks')
182+
183+ os.makedirs(hooks_dir)
184+
185+ execparts = shlex.split(config)
186+ args = execparts[1:] if len(execparts) > 1 else []
187+
188+ config_hook_path = os.path.join(hooks_dir, 'config')
189+ _write_wrap_exe(execparts[0], config_hook_path, args=args, cwd='$SNAP_APP_PATH')
190+
191+
192 def _copy_icon(meta_dir, icon_path):
193 new_icon_path = os.path.join(meta_dir, os.path.basename(icon_path))
194 shutil.copyfile(icon_path, new_icon_path)
195@@ -122,6 +137,23 @@
196 return ' '.join([shlex.quote(x) for x in newparts])
197
198
199+def _write_wrap_exe(wrapexec, wrappath, args=[], cwd=None):
200+ args = ' '.join(args) + ' $*' if args else '$*'
201+ cwd = 'cd {}'.format(cwd) if cwd else ''
202+
203+ snap_dir = common.get_snapdir()
204+ assembled_env = common.assemble_env().replace(snap_dir, '$SNAP_APP_PATH')
205+ script = ('#!/bin/sh\n' +
206+ '{}\n'.format(assembled_env) +
207+ '{}\n'.format(cwd) +
208+ 'exec "{}" {}\n'.format(wrapexec, args))
209+
210+ with open(wrappath, 'w+') as f:
211+ f.write(script)
212+
213+ os.chmod(wrappath, 0o755)
214+
215+
216 def _wrap_exe(relexepath):
217 snap_dir = common.get_snapdir()
218 exepath = os.path.join(snap_dir, relexepath)
219@@ -149,15 +181,7 @@
220 else:
221 logger.warning('Warning: unable to find "{}" in the path'.format(relexepath))
222
223- assembled_env = common.assemble_env().replace(snap_dir, '$SNAP_APP_PATH')
224- script = ('#!/bin/sh\n' +
225- '{}\n'.format(assembled_env) +
226- 'exec "{}" $*\n'.format(wrapexec))
227-
228- with open(wrappath, 'w+') as f:
229- f.write(script)
230-
231- os.chmod(wrappath, 0o755)
232+ _write_wrap_exe(wrapexec, wrappath)
233
234 return os.path.relpath(wrappath, snap_dir)
235
236
237=== modified file 'snapcraft/sources.py'
238--- snapcraft/sources.py 2015-09-01 10:09:30 +0000
239+++ snapcraft/sources.py 2015-09-15 20:53:59 +0000
240@@ -177,7 +177,9 @@
241
242 def provision(self, dst):
243 path = os.path.abspath(self.source)
244- if os.path.isdir(dst):
245+ if os.path.islink(dst):
246+ os.remove(dst)
247+ elif os.path.isdir(dst):
248 os.rmdir(dst)
249 else:
250 os.remove(dst)
251
252=== modified file 'snapcraft/tests/test_meta.py'
253--- snapcraft/tests/test_meta.py 2015-08-27 21:33:56 +0000
254+++ snapcraft/tests/test_meta.py 2015-09-15 20:53:59 +0000
255@@ -17,7 +17,6 @@
256 import os
257 from unittest.mock import (
258 call,
259- mock_open,
260 patch,
261 )
262
263@@ -45,7 +44,6 @@
264 }
265
266 def test_plain_no_binaries_or_services(self):
267-
268 y = meta._compose_package_yaml(self.config_data, ['armhf', 'amd64'])
269
270 expected = {
271@@ -59,7 +57,6 @@
272 self.assertEqual(y, expected)
273
274 def test_plain_no_binaries_or_services_or_arches(self):
275-
276 y = meta._compose_package_yaml(self.config_data, None)
277
278 expected = {
279@@ -186,6 +183,15 @@
280 self.mock_copyfile = patcher_copyfile.start()
281 self.addCleanup(patcher_copyfile.stop)
282
283+ patcher_move = patch('shutil.move')
284+ self.mock_move = patcher_move.start()
285+ self.addCleanup(patcher_move.stop)
286+
287+ patcher_exists = patch('os.path.exists')
288+ self.mock_exists = patcher_exists.start()
289+ self.mock_exists.return_value = True
290+ self.addCleanup(patcher_exists.stop)
291+
292 self.config_data = {
293 'name': 'my-package',
294 'version': '1.0',
295@@ -193,62 +199,103 @@
296 'description': 'my description',
297 'summary': 'my summary',
298 'icon': 'my-icon.png',
299+ 'config': 'bin/config'
300 }
301
302- def test_create_meta(self):
303- mock_the_open = mock_open()
304-
305- with patch('snapcraft.meta.open', mock_the_open, create=True):
306- meta.create(self.config_data, ['amd64'])
307-
308- meta_dir = os.path.join(os.path.abspath(os.curdir), 'snap', 'meta')
309-
310- self.mock_makedirs.assert_called_once_with(meta_dir, exist_ok=True)
311-
312- mock_the_open.assert_has_calls([
313- call(os.path.join(meta_dir, 'package.yaml'), 'w'),
314+ self.meta_dir = os.path.join(os.path.abspath(os.curdir), 'snap', 'meta')
315+
316+ self.expected_open_calls = [
317+ call(os.path.join(self.meta_dir, 'package.yaml'), 'w'),
318 call().__enter__(),
319- call().write('architectures'),
320- call().write(':'),
321- call().write('\n'),
322- call().write('-'),
323- call().write(' '),
324- call().write('amd64'),
325- call().write('\n'),
326- call().write('icon'),
327- call().write(':'),
328- call().write(' '),
329- call().write('meta/my-icon.png'),
330- call().write('\n'),
331- call().write('name'),
332- call().write(':'),
333- call().write(' '),
334- call().write('my-package'),
335- call().write('\n'),
336- call().write('vendor'),
337- call().write(':'),
338- call().write(' '),
339- call().write('Sergio'),
340- call().write(' '),
341- call().write('Schvezov'),
342- call().write(' '),
343- call().write('<sergio.schvezov@canonical.com>'),
344- call().write('\n'),
345- call().write('version'),
346- call().write(':'),
347- call().write(" '"),
348- call().write('1.0'),
349- call().write("'"),
350- call().write('\n'),
351- call().flush(),
352- call().flush(),
353+ call().__enter__().write('architectures'),
354+ call().__enter__().write(':'),
355+ call().__enter__().write('\n'),
356+ call().__enter__().write('-'),
357+ call().__enter__().write(' '),
358+ call().__enter__().write('amd64'),
359+ call().__enter__().write('\n'),
360+ call().__enter__().write('icon'),
361+ call().__enter__().write(':'),
362+ call().__enter__().write(' '),
363+ call().__enter__().write('meta/my-icon.png'),
364+ call().__enter__().write('\n'),
365+ call().__enter__().write('name'),
366+ call().__enter__().write(':'),
367+ call().__enter__().write(' '),
368+ call().__enter__().write('my-package'),
369+ call().__enter__().write('\n'),
370+ call().__enter__().write('vendor'),
371+ call().__enter__().write(':'),
372+ call().__enter__().write(' '),
373+ call().__enter__().write('Sergio'),
374+ call().__enter__().write(' '),
375+ call().__enter__().write('Schvezov'),
376+ call().__enter__().write(' '),
377+ call().__enter__().write('<sergio.schvezov@canonical.com>'),
378+ call().__enter__().write('\n'),
379+ call().__enter__().write('version'),
380+ call().__enter__().write(':'),
381+ call().__enter__().write(" '"),
382+ call().__enter__().write('1.0'),
383+ call().__enter__().write("'"),
384+ call().__enter__().write('\n'),
385+ call().__enter__().flush(),
386+ call().__enter__().flush(),
387 call().__exit__(None, None, None),
388- call(os.path.join(meta_dir, 'readme.md'), 'w'),
389+ call(os.path.join(self.meta_dir, 'readme.md'), 'w'),
390 call().__enter__(),
391- call().write('my summary\nmy description\n'),
392+ call().__enter__().write('my summary\nmy description\n'),
393 call().__exit__(None, None, None),
394 ]
395- )
396+
397+ self.hooks_dir = os.path.join(self.meta_dir, 'hooks')
398+
399+ @patch('snapcraft.meta._write_wrap_exe')
400+ @patch('snapcraft.meta.open', create=True)
401+ def test_create_meta(self, mock_the_open, mock_wrap_exe):
402+ meta.create(self.config_data, ['amd64'])
403+
404+ self.mock_makedirs.assert_has_calls([
405+ call(self.meta_dir, exist_ok=True),
406+ call(self.hooks_dir),
407+ ])
408+
409+ mock_the_open.assert_has_calls(self.expected_open_calls)
410+ mock_wrap_exe.assert_called_once_with(
411+ 'bin/config',
412+ os.path.join(os.path.abspath(os.curdir), 'snap', 'meta', 'hooks', 'config'),
413+ args=[],
414+ cwd='$SNAP_APP_PATH',
415+ )
416+
417+ @patch('snapcraft.meta._write_wrap_exe')
418+ @patch('snapcraft.meta.open', create=True)
419+ def test_create_meta_with_vararg_config(self, mock_the_open, mock_wrap_exe):
420+ self.config_data['config'] = 'python3 my.py --config'
421+
422+ meta.create(self.config_data, ['amd64'])
423+
424+ self.mock_makedirs.assert_has_calls([
425+ call(self.meta_dir, exist_ok=True),
426+ call(self.hooks_dir),
427+ ])
428+
429+ mock_the_open.assert_has_calls(self.expected_open_calls)
430+ mock_wrap_exe.assert_called_once_with(
431+ 'python3',
432+ os.path.join(os.path.abspath(os.curdir), 'snap', 'meta', 'hooks', 'config'),
433+ args=['my.py', '--config'],
434+ cwd='$SNAP_APP_PATH',
435+ )
436+
437+ @patch('snapcraft.meta.open', create=True)
438+ def test_create_meta_without_config(self, mock_the_open):
439+ del self.config_data['config']
440+
441+ meta.create(self.config_data, ['amd64'])
442+
443+ self.mock_makedirs.assert_called_once_with(self.meta_dir, exist_ok=True)
444+ mock_the_open.assert_has_calls(self.expected_open_calls)
445
446
447 # TODO this needs more tests.
448@@ -262,7 +309,7 @@
449 wrapper_path = os.path.join(snapdir, relative_wrapper_path)
450
451 expected = ('#!/bin/sh\n'
452- '\n'
453+ '\n\n'
454 'exec "$SNAP_APP_PATH/test_relexepath" $*\n')
455 with open(wrapper_path) as wrapper_file:
456 wrapper_contents = wrapper_file.read()

Subscribers

People subscribed via source and target branches