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

Proposed by Sergio Schvezov
Status: Merged
Approved by: Sergio Schvezov
Approved revision: 178
Merged at revision: 159
Proposed branch: lp:~sergiusens/snapcraft/filesets
Merge into: lp:~snappy-dev/snapcraft/core
Prerequisite: lp:~sergiusens/snapcraft/plugins-are-types
Diff against target: 732 lines (+346/-142)
19 files modified
examples/gopaste/snapcraft.yaml (+1/-1)
plugins/ant-project.yaml (+2/-0)
plugins/autotools-project.yaml (+2/-0)
plugins/cmake-project.yaml (+10/-8)
plugins/copy.yaml (+2/-0)
plugins/go-project.yaml (+2/-0)
plugins/make-project.yaml (+8/-6)
plugins/maven-project.yaml (+2/-0)
plugins/python2-project.yaml (+2/-0)
plugins/python3-project.yaml (+2/-0)
plugins/tar-content.yaml (+2/-0)
snapcraft/__init__.py (+6/-6)
snapcraft/plugin.py (+60/-51)
snapcraft/plugins/go.py (+0/-6)
snapcraft/plugins/jdk.py (+6/-4)
snapcraft/plugins/qml.py (+4/-4)
snapcraft/tests/test_plugin.py (+138/-53)
snapcraft/tests/test_yaml.py (+73/-2)
snapcraft/yaml.py (+24/-1)
To merge this branch: bzr merge lp:~sergiusens/snapcraft/filesets
Reviewer Review Type Date Requested Status
Sergio Schvezov Approve
Michael Vogt (community) Approve
Review via email: mp+270455@code.launchpad.net

Commit message

Filesets based filtering for stage and snap

Description of the change

I'm bit torn on this one.

Right now, the plugin can add additional filters to the snap stage, but that is not in spec. I'm not sure we want this in the part declaration, but that breaks bad when we have plugins that depend on other plugins to run providing no means to configure them easily.

I can try and hack this into the plugin yaml files (at the top level with options and build-deps) but that is one layer above.

I'm also hesitant to provide a 'stage_fileset' method if we don't go down the yaml path.

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks, this looks very nice, some inline comments but mostly idle remarks, fine to land IMO. I have no good answer for your questions though. I agree that it feels better to keep a way to filter in the plugin and I think thats ok for part -> stage. There was some controversy about stage -> final (snap).

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

@mvo, thanks for the review!

lp:~sergiusens/snapcraft/filesets updated
177. By Sergio Schvezov

escape char

178. By Sergio Schvezov

not so aggresive filtering for plugins or everything is included

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

fixes from review

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'examples/gopaste/snapcraft.yaml'
2--- examples/gopaste/snapcraft.yaml 2015-09-08 11:48:30 +0000
3+++ examples/gopaste/snapcraft.yaml 2015-09-11 13:39:05 +0000
4@@ -4,7 +4,7 @@
5 services:
6 - name: gopaste
7 description: "gopaste"
8- start: bin/gopaste
9+ start: bin/gopasted
10 summary: Simple pasting tool
11 description: Runs a service that allows you to paste to and share.
12 icon: icon.png
13
14=== modified file 'plugins/ant-project.yaml'
15--- plugins/ant-project.yaml 2015-09-02 21:27:17 +0000
16+++ plugins/ant-project.yaml 2015-09-11 13:39:05 +0000
17@@ -9,5 +9,7 @@
18 source-tag:
19 source-branch:
20 stage-packages:
21+ snap:
22+ stage:
23 build-tools:
24 - ant
25
26=== modified file 'plugins/autotools-project.yaml'
27--- plugins/autotools-project.yaml 2015-09-02 15:10:58 +0000
28+++ plugins/autotools-project.yaml 2015-09-11 13:39:05 +0000
29@@ -9,3 +9,5 @@
30 configflags:
31 required: false
32 stage-packages:
33+ snap:
34+ stage:
35
36=== modified file 'plugins/cmake-project.yaml'
37--- plugins/cmake-project.yaml 2015-09-02 15:10:58 +0000
38+++ plugins/cmake-project.yaml 2015-09-11 13:39:05 +0000
39@@ -1,11 +1,13 @@
40 module: cmake_project
41 build-tools: [cmake]
42 options:
43- source:
44- required: true
45- source-type:
46- source-tag:
47- source-branch:
48- configflags:
49- required: false
50- stage-packages:
51+ source:
52+ required: true
53+ source-type:
54+ source-tag:
55+ source-branch:
56+ configflags:
57+ required: false
58+ stage-packages:
59+ snap:
60+ stage:
61
62=== modified file 'plugins/copy.yaml'
63--- plugins/copy.yaml 2015-09-02 15:10:58 +0000
64+++ plugins/copy.yaml 2015-09-11 13:39:05 +0000
65@@ -2,3 +2,5 @@
66 files:
67 required: true
68 stage-packages:
69+ snap:
70+ stage:
71
72=== modified file 'plugins/go-project.yaml'
73--- plugins/go-project.yaml 2015-09-02 15:10:58 +0000
74+++ plugins/go-project.yaml 2015-09-11 13:39:05 +0000
75@@ -7,3 +7,5 @@
76 configflags:
77 required: false
78 stage-packages:
79+ snap:
80+ stage:
81
82=== modified file 'plugins/make-project.yaml'
83--- plugins/make-project.yaml 2015-09-02 15:10:58 +0000
84+++ plugins/make-project.yaml 2015-09-11 13:39:05 +0000
85@@ -1,9 +1,11 @@
86 module: make_project
87 build-tools: [make]
88 options:
89- source:
90- required: true
91- source-type:
92- source-tag:
93- source-branch:
94- stage-packages:
95+ source:
96+ required: true
97+ source-type:
98+ source-tag:
99+ source-branch:
100+ stage-packages:
101+ snap:
102+ stage:
103
104=== modified file 'plugins/maven-project.yaml'
105--- plugins/maven-project.yaml 2015-09-02 15:10:58 +0000
106+++ plugins/maven-project.yaml 2015-09-11 13:39:05 +0000
107@@ -10,3 +10,5 @@
108 source-tag:
109 source-branch:
110 stage-packages:
111+ snap:
112+ stage:
113
114=== modified file 'plugins/python2-project.yaml'
115--- plugins/python2-project.yaml 2015-09-02 15:10:58 +0000
116+++ plugins/python2-project.yaml 2015-09-11 13:39:05 +0000
117@@ -8,3 +8,5 @@
118 source-tag:
119 source-branch:
120 stage-packages:
121+ snap:
122+ stage:
123
124=== modified file 'plugins/python3-project.yaml'
125--- plugins/python3-project.yaml 2015-09-02 15:10:58 +0000
126+++ plugins/python3-project.yaml 2015-09-11 13:39:05 +0000
127@@ -8,3 +8,5 @@
128 source-tag:
129 source-branch:
130 stage-packages:
131+ snap:
132+ stage:
133
134=== modified file 'plugins/tar-content.yaml'
135--- plugins/tar-content.yaml 2015-09-02 15:10:58 +0000
136+++ plugins/tar-content.yaml 2015-09-11 13:39:05 +0000
137@@ -3,3 +3,5 @@
138 source:
139 required: true
140 stage-packages:
141+ snap:
142+ stage:
143
144=== modified file 'snapcraft/__init__.py'
145--- snapcraft/__init__.py 2015-09-07 19:25:14 +0000
146+++ snapcraft/__init__.py 2015-09-11 13:39:05 +0000
147@@ -48,12 +48,12 @@
148 def build(self):
149 return True
150
151- def snap_files(self):
152- """Returns two iteratables of globs:
153- - the first is the set of files/dirs to include
154- - the second is the set of files/dirs to exclude
155- For example: (['bin', 'lib'], ['lib/*.a'])"""
156- return (['*'], [])
157+ def snap_fileset(self):
158+ """Returns one iteratables of globs specific to the plugin:
159+ - includes can be just listed
160+ - excludes must be preceded by -
161+ For example: (['bin', 'lib', '-include'])"""
162+ return ([])
163
164 def env(self, root):
165 return []
166
167=== modified file 'snapcraft/plugin.py'
168--- snapcraft/plugin.py 2015-09-04 20:54:34 +0000
169+++ snapcraft/plugin.py 2015-09-11 13:39:05 +0000
170@@ -206,8 +206,10 @@
171 return True
172
173 self.notify_stage("Staging")
174- common.run(['cp', '-arT', self.installdir, self.stagedir])
175+ fileset = getattr(self.code.options, 'stage', ['*']) or ['*']
176+ _migrate_files(fileset, self.installdir, self.stagedir)
177 self.mark_done('stage')
178+
179 return True
180
181 def snap(self, force=False):
182@@ -215,39 +217,15 @@
183 return True
184 self.makedirs()
185
186- if self.code and hasattr(self.code, 'snap_files'):
187- self.notify_stage("Snapping")
188-
189- includes, excludes = getattr(self.code, 'snap_files')()
190- snapDirs, snap_files = self.collect_snap_files(includes, excludes)
191-
192- if snapDirs:
193- common.run(['mkdir', '-p'] + list(snapDirs), cwd=self.stagedir)
194- if snap_files:
195- common.run(['cp', '-a', '--parent'] + list(snap_files) + [self.snapdir], cwd=self.stagedir)
196-
197- self.mark_done('snap')
198+ self.notify_stage("Snapping")
199+ plugin_fileset = self.code.snap_fileset()
200+ fileset = getattr(self.code.options, 'snap', ['*']) or ['*']
201+ fileset.extend(plugin_fileset)
202+ _migrate_files(fileset, self.installdir, self.snapdir)
203+ self.mark_done('snap')
204+
205 return True
206
207- def collect_snap_files(self, includes, excludes):
208- # validate
209- _validate_relative_paths(includes + excludes)
210-
211- source_files = _generate_source_set(self.installdir)
212- include_files = _generate_include_set(self.stagedir, includes)
213- exclude_files, exclude_dirs = _generate_exclude_set(self.stagedir, excludes)
214-
215- # And chop files, including whole trees if any dirs are mentioned
216- snap_files = (include_files & source_files) - exclude_files
217- for exclude_dir in exclude_dirs:
218- snap_files = set([x for x in snap_files if not x.startswith(exclude_dir + '/')])
219-
220- # Separate dirs from files
221- snap_dirs = set([x for x in snap_files if os.path.isdir(os.path.join(self.stagedir, x)) and not os.path.islink(os.path.join(self.stagedir, x))])
222- snap_files = snap_files - snap_dirs
223-
224- return snap_dirs, snap_files
225-
226 def env(self, root):
227 if self.code and hasattr(self.code, 'env'):
228 return getattr(self.code, 'env')(root)
229@@ -262,44 +240,75 @@
230 return part
231
232
233-def _generate_source_set(installdir):
234- source_files = set()
235- for root, dirs, files in os.walk(installdir):
236- source_files |= set([os.path.join(root, d) for d in dirs])
237- source_files |= set([os.path.join(root, f) for f in files])
238- source_files = set([os.path.relpath(x, installdir) for x in source_files])
239-
240- return source_files
241-
242-
243-def _generate_include_set(stagedir, includes):
244+def _migrate_files(fileset, srcdir, dstdir):
245+ # validate
246+ includes, excludes = _get_file_list(fileset)
247+
248+ include_files = _generate_include_set(srcdir, includes)
249+ exclude_files, exclude_dirs = _generate_exclude_set(srcdir, excludes)
250+
251+ # And chop files, including whole trees if any dirs are mentioned
252+ snap_files = include_files - exclude_files
253+ for exclude_dir in exclude_dirs:
254+ snap_files = set([x for x in snap_files if not x.startswith(exclude_dir + '/')])
255+
256+ # Separate dirs from files
257+ snap_dirs = set([x for x in snap_files if os.path.isdir(os.path.join(srcdir, x)) and not os.path.islink(os.path.join(srcdir, x))])
258+ snap_files = snap_files - snap_dirs
259+
260+ if snap_dirs:
261+ common.run(['mkdir', '-p'] + list(snap_dirs), cwd=dstdir)
262+ if snap_files:
263+ common.run(['cp', '-a', '--parent'] + list(snap_files) + [dstdir], cwd=srcdir)
264+
265+
266+def _get_file_list(stage_set):
267+ includes = []
268+ excludes = []
269+
270+ for item in stage_set:
271+ if item.startswith('-'):
272+ excludes.append(item[1:])
273+ elif item.startswith('\\'):
274+ includes.append(item[1:])
275+ else:
276+ includes.append(item)
277+
278+ _validate_relative_paths(includes + excludes)
279+
280+ includes = includes or ['*']
281+
282+ return includes, excludes
283+
284+
285+def _generate_include_set(directory, includes):
286 include_files = set()
287 for include in includes:
288- matches = glob.glob(os.path.join(stagedir, include))
289+ matches = glob.glob(os.path.join(directory, include))
290 include_files |= set(matches)
291
292 include_dirs = [x for x in include_files if os.path.isdir(x)]
293- include_files = set([os.path.relpath(x, stagedir) for x in include_files])
294+ include_files = set([os.path.relpath(x, directory) for x in include_files])
295
296 # Expand includeFiles, so that an exclude like '*/*.so' will still match
297 # files from an include like 'lib'
298 for include_dir in include_dirs:
299 for root, dirs, files in os.walk(include_dir):
300- include_files |= set([os.path.relpath(os.path.join(root, d), stagedir) for d in dirs])
301- include_files |= set([os.path.relpath(os.path.join(root, f), stagedir) for f in files])
302+ include_files |= set([os.path.relpath(os.path.join(root, d), directory) for d in dirs])
303+ include_files |= set([os.path.relpath(os.path.join(root, f), directory) for f in files])
304
305 return include_files
306
307
308-def _generate_exclude_set(stagedir, excludes):
309+def _generate_exclude_set(directory, excludes):
310 exclude_files = set()
311
312 for exclude in excludes:
313- matches = glob.glob(os.path.join(stagedir, exclude))
314+ matches = glob.glob(os.path.join(directory, exclude))
315 exclude_files |= set(matches)
316
317- exclude_dirs = [os.path.relpath(x, stagedir) for x in exclude_files if os.path.isdir(x)]
318- exclude_files = set([os.path.relpath(x, stagedir) for x in exclude_files])
319+ exclude_dirs = [os.path.relpath(x, directory) for x in exclude_files if os.path.isdir(x)]
320+ exclude_files = set([os.path.relpath(x, directory) for x in exclude_files])
321
322 return exclude_files, exclude_dirs
323
324
325=== modified file 'snapcraft/plugins/go.py'
326--- snapcraft/plugins/go.py 2015-08-14 20:04:30 +0000
327+++ snapcraft/plugins/go.py 2015-09-11 13:39:05 +0000
328@@ -19,14 +19,8 @@
329
330 class GoPlugin(snapcraft.BasePlugin):
331
332- def __init__(self, name, options):
333- super().__init__(name, options)
334-
335 def env(self, root):
336 # usr/lib/go/bin on newer Ubuntus, usr/bin on trusty
337 return [
338 "GOPATH={}/go".format(root),
339 ]
340-
341- def snap_files(self):
342- return ([], []) # Go statically links everything, no need for runtime
343
344=== modified file 'snapcraft/plugins/jdk.py'
345--- snapcraft/plugins/jdk.py 2015-09-07 19:25:14 +0000
346+++ snapcraft/plugins/jdk.py 2015-09-11 13:39:05 +0000
347@@ -28,8 +28,10 @@
348 'PATH=%s/usr/lib/jvm/default-java/bin:'
349 '%s/usr/lib/jvm/default-java/jre/bin:$PATH' % (root, root)]
350
351- def snap_files(self):
352+ def snap_fileset(self):
353 # Cut out jdk bits (jre bits are in default-java/jre)
354- return (['*'], ['usr/lib/jvm/default-java/bin',
355- 'usr/lib/jvm/default-java/include',
356- 'usr/lib/jvm/default-java/lib'])
357+ return (['-usr/lib/jvm/default-java/bin',
358+ '-usr/lib/jvm/default-java/include',
359+ '-usr/lib/jvm/default-java/lib',
360+ '-usr/share/doc',
361+ ])
362
363=== modified file 'snapcraft/plugins/qml.py'
364--- snapcraft/plugins/qml.py 2015-09-07 19:25:14 +0000
365+++ snapcraft/plugins/qml.py 2015-09-11 13:39:05 +0000
366@@ -57,10 +57,10 @@
367 "qml-module-ubuntu-onlineaccounts-client",
368 ]
369
370- def snap_files(self):
371- include, exclude = ['*'], []
372- include.append('./etc/xdg/qtchooser/snappy-qt5.conf')
373- return (include, exclude)
374+ def snap_fileset(self):
375+ return ['*',
376+ 'etc/xdg/qtchooser/snappy-qt5.conf',
377+ ]
378
379 def build_qt_config(self):
380 arch = snapcraft.common.get_arch_triplet()
381
382=== modified file 'snapcraft/tests/test_plugin.py'
383--- snapcraft/tests/test_plugin.py 2015-08-05 14:38:33 +0000
384+++ snapcraft/tests/test_plugin.py 2015-09-11 13:39:05 +0000
385@@ -65,53 +65,140 @@
386 p.pull()
387 self.assertFalse(p.code.pull.called)
388
389- def test_collect_snap_files(self):
390- p = get_test_plugin()
391-
392- tmpdirObject = tempfile.TemporaryDirectory()
393- self.addCleanup(tmpdirObject.cleanup)
394- tmpdir = tmpdirObject.name
395-
396- p.installdir = tmpdir + '/install'
397- os.makedirs(tmpdir + '/install/1/1a/1b')
398- os.makedirs(tmpdir + '/install/2/2a')
399- os.makedirs(tmpdir + '/install/3')
400- open(tmpdir + '/install/a', mode='w').close()
401- open(tmpdir + '/install/b', mode='w').close()
402- open(tmpdir + '/install/1/a', mode='w').close()
403- open(tmpdir + '/install/3/a', mode='w').close()
404-
405- p.stagedir = tmpdir + '/stage'
406- os.makedirs(tmpdir + '/stage/1/1a/1b')
407- os.makedirs(tmpdir + '/stage/2/2a')
408- os.makedirs(tmpdir + '/stage/2/2b')
409- os.makedirs(tmpdir + '/stage/3')
410- open(tmpdir + '/stage/a', mode='w').close()
411- open(tmpdir + '/stage/b', mode='w').close()
412- open(tmpdir + '/stage/c', mode='w').close()
413- open(tmpdir + '/stage/1/a', mode='w').close()
414- open(tmpdir + '/stage/2/2b/a', mode='w').close()
415- open(tmpdir + '/stage/3/a', mode='w').close()
416-
417- self.assertEqual(p.collect_snap_files([], []), (set(), set()))
418-
419- self.assertEqual(p.collect_snap_files(['*'], []), (
420- set(['1', '1/1a', '1/1a/1b', '2', '2/2a', '3']),
421- set(['a', 'b', '1/a', '3/a'])))
422-
423- self.assertEqual(p.collect_snap_files(['*'], ['1']), (
424- set(['2', '2/2a', '3']),
425- set(['a', 'b', '3/a'])))
426-
427- self.assertEqual(p.collect_snap_files(['a'], ['*']), (set(), set()))
428-
429- self.assertEqual(p.collect_snap_files(['*'], ['*/*']), (
430- set(['1', '2', '3']),
431- set(['a', 'b'])))
432-
433- self.assertEqual(p.collect_snap_files(['1', '2'], ['*/a']), (
434- set(['1', '1/1a', '1/1a/1b', '2', '2/2a']),
435- set()))
436+ def test_fileset_include_excludes(self):
437+ stage_set = [
438+ '-etc',
439+ 'opt/something',
440+ '-usr/lib/*.a',
441+ 'usr/bin',
442+ '\-everything',
443+ r'\\a',
444+ ]
445+
446+ include, exclude = plugin._get_file_list(stage_set)
447+
448+ self.assertEqual(include, ['opt/something', 'usr/bin', '-everything', r'\a'])
449+ self.assertEqual(exclude, ['etc', 'usr/lib/*.a'])
450+
451+ def test_fileset_only_includes(self):
452+ stage_set = [
453+ 'opt/something',
454+ 'usr/bin',
455+ ]
456+
457+ include, exclude = plugin._get_file_list(stage_set)
458+
459+ self.assertEqual(include, ['opt/something', 'usr/bin'])
460+ self.assertEqual(exclude, [])
461+
462+ def test_fileset_only_excludes(self):
463+ stage_set = [
464+ '-etc',
465+ '-usr/lib/*.a',
466+ ]
467+
468+ include, exclude = plugin._get_file_list(stage_set)
469+
470+ self.assertEqual(include, ['*'])
471+ self.assertEqual(exclude, ['etc', 'usr/lib/*.a'])
472+
473+ def test_migrate_snap_files(self):
474+ filesets = {
475+ 'nothing': {
476+ 'fileset': ['-*'],
477+ 'result': []
478+ },
479+ 'all': {
480+ 'fileset': ['*'],
481+ 'result': [
482+ 'stage/1',
483+ 'stage/1/1a/1b',
484+ 'stage/1/1a',
485+ 'stage/1/a',
486+ 'stage/2',
487+ 'stage/2/2a',
488+ 'stage/3',
489+ 'stage/3/a',
490+ 'stage/a',
491+ 'stage/b',
492+ ],
493+ },
494+ 'no1': {
495+ 'fileset': ['-1'],
496+ 'result': [
497+ 'stage/2',
498+ 'stage/2/2a',
499+ 'stage/3',
500+ 'stage/3/a',
501+ 'stage/a',
502+ 'stage/b',
503+ ],
504+ },
505+ 'onlya': {
506+ 'fileset': ['a'],
507+ 'result': [
508+ 'stage/a',
509+ ],
510+ },
511+ 'onlybase': {
512+ 'fileset': ['*', '-*/*'],
513+ 'result': [
514+ 'stage/a',
515+ 'stage/b',
516+ 'stage/1',
517+ 'stage/2',
518+ 'stage/3',
519+ ],
520+ },
521+ 'nostara': {
522+ 'fileset': ['-*/a'],
523+ 'result': [
524+ 'stage/1',
525+ 'stage/1/1a/1b',
526+ 'stage/1/1a',
527+ 'stage/2',
528+ 'stage/2/2a',
529+ 'stage/3',
530+ 'stage/a',
531+ 'stage/b',
532+ ],
533+ },
534+ }
535+
536+ for key in filesets:
537+ with self.subTest(key=key):
538+ tmpdirObject = tempfile.TemporaryDirectory()
539+ self.addCleanup(tmpdirObject.cleanup)
540+ tmpdir = tmpdirObject.name
541+
542+ srcdir = tmpdir + '/install'
543+ os.makedirs(tmpdir + '/install/1/1a/1b')
544+ os.makedirs(tmpdir + '/install/2/2a')
545+ os.makedirs(tmpdir + '/install/3')
546+ open(tmpdir + '/install/a', mode='w').close()
547+ open(tmpdir + '/install/b', mode='w').close()
548+ open(tmpdir + '/install/1/a', mode='w').close()
549+ open(tmpdir + '/install/3/a', mode='w').close()
550+
551+ dstdir = tmpdir + '/stage'
552+ os.makedirs(dstdir)
553+
554+ plugin._migrate_files(filesets[key]['fileset'], srcdir, dstdir)
555+
556+ expected = []
557+ for item in filesets[key]['result']:
558+ expected.append(os.path.join(tmpdir, item))
559+ expected.sort()
560+
561+ result = []
562+ for root, subdirs, files in os.walk(dstdir):
563+ for item in files:
564+ result.append(os.path.join(root, item))
565+ for item in subdirs:
566+ result.append(os.path.join(root, item))
567+ result.sort()
568+
569+ self.assertEqual(expected, result)
570
571 def test_notify_stage_must_log_information(self):
572 fake_logger = fixtures.FakeLogger(level=logging.INFO)
573@@ -145,18 +232,16 @@
574 plugin.PluginHandler(
575 "mock", "mock-part", {}, load_config=False, load_code=True)
576
577- def test_collect_snap_files_with_absolute_includes_must_raise_error(self):
578- p = get_test_plugin()
579+ def test_filesets_includes_without_relative_paths(self):
580 with self.assertRaises(plugin.PluginError) as raised:
581- p.collect_snap_files(includes=['rel', '/abs/include'], excludes=[])
582+ plugin._get_file_list(['rel', '/abs/include'])
583
584 self.assertEqual(
585 "path '/abs/include' must be relative", str(raised.exception))
586
587- def test_collect_snap_files_with_absolute_excludes_must_raise_error(self):
588- p = get_test_plugin()
589+ def test_filesets_exlcudes_without_relative_paths(self):
590 with self.assertRaises(plugin.PluginError) as raised:
591- p.collect_snap_files(includes=[], excludes=['rel', '/abs/exclude'])
592+ plugin._get_file_list(['rel', '-/abs/exclude'])
593
594 self.assertEqual(
595 "path '/abs/exclude' must be relative", str(raised.exception))
596
597=== modified file 'snapcraft/tests/test_yaml.py'
598--- snapcraft/tests/test_yaml.py 2015-09-08 11:48:30 +0000
599+++ snapcraft/tests/test_yaml.py 2015-09-11 13:39:05 +0000
600@@ -61,8 +61,9 @@
601 stage-packages: [fswebcam]
602 """)
603 snapcraft.yaml.Config()
604- mock_loadPlugin.assert_called_with("part1", "go", {
605- "stage-packages": ["fswebcam"],
606+ mock_loadPlugin.assert_called_with('part1', 'go', {
607+ 'stage-packages': ['fswebcam'],
608+ 'stage': [], 'snap': [],
609 })
610
611 def test_config_raises_on_missing_snapcraft_yaml(self):
612@@ -211,6 +212,40 @@
613 'found character \'\\t\' that cannot start any token '
614 'on line 2 of snapcraft.yaml')
615
616+ @unittest.mock.patch('snapcraft.yaml.Config.load_plugin')
617+ def test_config_expands_filesets(self, mock_loadPlugin):
618+ self.make_snapcraft_yaml("""name: test
619+version: "1"
620+vendor: me <me@me.com>
621+summary: test
622+description: test
623+icon: my-icon.png
624+
625+parts:
626+ part1:
627+ type: go
628+ stage-packages: [fswebcam]
629+ filesets:
630+ wget:
631+ - /usr/lib/wget.so
632+ - /usr/bin/wget
633+ build-wget:
634+ - /usr/lib/wget.a
635+ stage:
636+ - $wget
637+ - $build-wget
638+ snap:
639+ - $wget
640+ - /usr/share/my-icon.png
641+""")
642+ snapcraft.yaml.Config()
643+
644+ mock_loadPlugin.assert_called_with('part1', 'go', {
645+ 'snap': ['/usr/lib/wget.so', '/usr/bin/wget', '/usr/share/my-icon.png'],
646+ 'stage-packages': ['fswebcam'],
647+ 'stage': ['/usr/lib/wget.so', '/usr/bin/wget', '/usr/lib/wget.a'],
648+ })
649+
650
651 class TestValidation(TestCase):
652
653@@ -354,3 +389,39 @@
654
655 expected_message = '\'my-icon.png\' is not a \'icon-path\''
656 self.assertEqual(raised.exception.message, expected_message, msg=self.data)
657+
658+
659+class TestFilesets(TestCase):
660+
661+ def setUp(self):
662+ super().setUp()
663+
664+ self.properties = {
665+ 'filesets': {
666+ '1': ['1', '2', '3'],
667+ '2': [],
668+ }
669+ }
670+
671+ def test_expand_var(self):
672+ self.properties['stage'] = ['$1']
673+
674+ fs = snapcraft.yaml._expand_filesets_for('stage', self.properties)
675+ self.assertEqual(fs, ['1', '2', '3'])
676+
677+ def test_no_expansion(self):
678+ self.properties['stage'] = ['1']
679+
680+ fs = snapcraft.yaml._expand_filesets_for('stage', self.properties)
681+ self.assertEqual(fs, ['1'])
682+
683+ def test_invalid_expansion(self):
684+ self.properties['stage'] = ['$3']
685+
686+ with self.assertRaises(snapcraft.yaml.SnapcraftLogicError) as raised:
687+ snapcraft.yaml._expand_filesets_for('stage', self.properties)
688+
689+ self.assertEqual(
690+ raised.exception.message,
691+ '\'$3\' referred to in the \'stage\' fileset but it is not '
692+ 'in filesets')
693
694=== modified file 'snapcraft/yaml.py'
695--- snapcraft/yaml.py 2015-09-08 11:48:30 +0000
696+++ snapcraft/yaml.py 2015-09-11 13:39:05 +0000
697@@ -86,7 +86,11 @@
698 after_requests[part_name] = properties["after"]
699 del properties["after"]
700
701- # TODO: support 'filter' or 'blacklist' field to filter what gets put in snap/
702+ properties['stage'] = _expand_filesets_for('stage', properties)
703+ properties['snap'] = _expand_filesets_for('snap', properties)
704+
705+ if 'filesets' in properties:
706+ del properties['filesets']
707
708 self.load_plugin(part_name, plugin_name, properties)
709
710@@ -224,3 +228,22 @@
711 raise SnapcraftSchemaError(
712 '{} on line {} of {}'.format(
713 e.problem, e.problem_mark.line, yaml_file))
714+
715+
716+def _expand_filesets_for(stage, properties):
717+ filesets = properties.get('filesets', {})
718+ fileset_for_stage = properties.get(stage, {})
719+ new_stage_set = []
720+
721+ for item in fileset_for_stage:
722+ if item.startswith('$'):
723+ try:
724+ new_stage_set.extend(filesets[item[1:]])
725+ except KeyError:
726+ raise SnapcraftLogicError(
727+ '\'{}\' referred to in the \'{}\' fileset but it is not '
728+ 'in filesets'.format(item, stage))
729+ else:
730+ new_stage_set.append(item)
731+
732+ return new_stage_set

Subscribers

People subscribed via source and target branches