Merge lp:~sergiusens/snapcraft/filesets into lp:~snappy-dev/snapcraft/core
- filesets
- Merge into 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 |
Related bugs: |
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
Sergio Schvezov (sergiusens) wrote : | # |
@mvo, thanks for the review!
- 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 |
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).