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

Proposed by Sergio Schvezov
Status: Merged
Approved by: Sergio Schvezov
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
John Lenton (community) Approve
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
158. By Björn Tillenius

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

159. By Sergio Schvezov

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

160. By Sergio Schvezov

snapcraft init with templated values by sergiusens approved by mvo

161. By Sergio Schvezov

regex for binary and service names by sergiusens approved by mvo

Revision history for this message
Sergio Schvezov (sergiusens) wrote :

hmm, my config.py is missing :/

lp:~sergiusens/snapcraft/config updated
162. By Sergio Schvezov

docs refresh by sergiusens approved by chipaca

163. By Sergio Schvezov

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

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

Fix small typo. by dholbach approved by sergiusens

Revision history for this message
John Lenton (chipaca) wrote :

LGTM. Minor nit.

review: Approve
Revision history for this message
Sergio Schvezov (sergiusens) wrote :

I fixed the nit

review: Approve
lp:~sergiusens/snapcraft/config updated
166. By Sergio Schvezov

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