Merge lp:click into lp:~ubuntu-managed-branches/click/click

Proposed by Michael Vogt on 2014-05-12
Status: Merged
Merged at revision: 351
Proposed branch: lp:click
Merge into: lp:~ubuntu-managed-branches/click/click
Diff against target: 485 lines (+269/-70)
11 files modified
README (+1/-0)
click/build.py (+10/-22)
click/framework.py (+138/-0)
click/install.py (+8/-28)
click/paths.py.in (+1/-0)
click/tests/helpers.py (+29/-0)
click/tests/test_build.py (+39/-5)
click/tests/test_install.py (+2/-4)
debian/changelog (+8/-0)
doc/file-format.rst (+1/-2)
doc/index.rst (+32/-9)
To merge this branch: bzr merge lp:click
Reviewer Review Type Date Requested Status
Colin Watson Approve on 2014-05-13
Ubuntu CI managed package branches 2014-05-12 Pending
Review via email: mp+219244@code.launchpad.net

Commit message

Support for declaring dependencies on multiple frameworks in a click package; Small documentation updates

Description of the change

* Add support for declaring dependencies on multiple frameworks in a click package (LP: #1318757)
* Small documentation updates

To post a comment you must log in.
lp:click updated on 2014-05-13
423. By Colin Watson on 2014-05-13

note closure of LP: #1318757

Colin Watson (cjwatson) :
review: Approve
lp:click updated on 2014-05-13
424. By Colin Watson on 2014-05-13

merge lp:~mvo/click/avoid-exposing-envvars

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'README'
2--- README 1970-01-01 00:00:00 +0000
3+++ README 2014-05-13 21:05:36 +0000
4@@ -0,0 +1,1 @@
5+See doc/index.rst to get started.
6
7=== modified file 'click/build.py'
8--- click/build.py 2014-01-23 17:19:09 +0000
9+++ click/build.py 2014-05-13 21:05:36 +0000
10@@ -50,6 +50,11 @@
11 from click.preinst import static_preinst
12 from click.versions import spec_version
13
14+from click.framework import (
15+ validate_framework,
16+ ClickFrameworkInvalid,
17+)
18+
19
20 @contextlib.contextmanager
21 def make_temp_dir():
22@@ -196,30 +201,13 @@
23 package.add_file("control.tar.gz", control_tar_path)
24 package.add_file("data.tar.gz", data_tar_path)
25
26- def _validate_framework(self, framework):
27+ def _validate_framework(self, framework_string):
28 """Apply policy checks to framework declarations."""
29 try:
30- apt_pkg
31- except NameError:
32- return
33-
34- try:
35- parsed_framework = apt_pkg.parse_depends(framework)
36- except ValueError:
37- raise ClickBuildError('Could not parse framework "%s"' % framework)
38- if len(parsed_framework) > 1:
39- raise ClickBuildError(
40- 'Multiple dependencies in framework "%s" not yet allowed' %
41- framework)
42- for or_dep in parsed_framework:
43- if len(or_dep) > 1:
44- raise ClickBuildError(
45- 'Alternative dependencies in framework "%s" not yet '
46- 'allowed' % framework)
47- if or_dep[0][1] or or_dep[0][2]:
48- raise ClickBuildError(
49- 'Version relationship in framework "%s" not yet allowed' %
50- framework)
51+ validate_framework(
52+ framework_string, ignore_missing_frameworks=True)
53+ except ClickFrameworkInvalid as e:
54+ raise ClickBuildError(str(e))
55
56 def build(self, dest_dir, manifest_path="manifest.json"):
57 with make_temp_dir() as temp_dir:
58
59=== added file 'click/framework.py'
60--- click/framework.py 1970-01-01 00:00:00 +0000
61+++ click/framework.py 2014-05-13 21:05:36 +0000
62@@ -0,0 +1,138 @@
63+# Copyright (C) 2014 Canonical Ltd.
64+# Author: Michael Vogt <michael.vogt@canonical.com>
65+
66+# This program is free software: you can redistribute it and/or modify
67+# it under the terms of the GNU General Public License as published by
68+# the Free Software Foundation; version 3 of the License.
69+#
70+# This program is distributed in the hope that it will be useful,
71+# but WITHOUT ANY WARRANTY; without even the implied warranty of
72+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
73+# GNU General Public License for more details.
74+#
75+# You should have received a copy of the GNU General Public License
76+# along with this program. If not, see <http://www.gnu.org/licenses/>.
77+
78+"""Pure python click framework handling support."""
79+
80+import logging
81+import os
82+import re
83+
84+try:
85+ import apt_pkg
86+except:
87+ pass
88+
89+import click.paths
90+
91+
92+class ClickFrameworkInvalid(Exception):
93+ pass
94+
95+
96+# FIXME: use native lib if available
97+#from gi.repository import Click
98+#click_framework_get_base_version = Click.framework_get_base_version
99+#click_framework_has_framework = Click.has_framework
100+
101+
102+# python version of the vala parse_deb822_file()
103+def parse_deb822_file(filename):
104+ data = {}
105+ with open(filename) as f:
106+ for line in f:
107+ line = line.strip()
108+ # from deb822.vala
109+ field_re_posix = r'^([^:[:space:]]+)[[:space:]]*:[[:space:]]'\
110+ '([^[:space:]].*?)[[:space:]]*$'
111+ # python does not do posix char classes
112+ field_re = field_re_posix.replace("[:space:]", "\s")
113+ blank_re_posix = r'^[[:space:]]*$'
114+ blank_re = blank_re_posix.replace("[:space:]", "\s")
115+ if re.match(blank_re, line):
116+ break
117+ match = re.match(field_re, line)
118+ if match and match.group(1) and match.group(2):
119+ data[match.group(1).lower()] = match.group(2)
120+ return data
121+
122+
123+# python version of vala get_frameworks_dir
124+def get_frameworks_dir():
125+ return click.paths.frameworks_dir
126+
127+
128+def get_framework_path(framework_name):
129+ framework_path = os.path.join(
130+ get_frameworks_dir(), framework_name+".framework")
131+ return framework_path
132+
133+
134+# python version of the vala click_framework_get_base_version()
135+def click_framework_get_base_version(framework_name):
136+ deb822 = parse_deb822_file(get_framework_path(framework_name))
137+ return deb822.get("base-version", None)
138+
139+
140+# python version of the vala click_framework_has_framework
141+def click_framework_has_framework(framework_name):
142+ return os.path.exists(get_framework_path(framework_name))
143+
144+
145+def validate_framework(framework_string, ignore_missing_frameworks=False):
146+ try:
147+ apt_pkg
148+ except NameError:
149+ logging.warning("No apt_pkg module, skipping validate_framework")
150+ return
151+
152+ try:
153+ parsed_framework = apt_pkg.parse_depends(framework_string)
154+ except ValueError:
155+ raise ClickFrameworkInvalid(
156+ 'Could not parse framework "%s"' % framework_string)
157+
158+ framework_base_versions = set()
159+ missing_frameworks = []
160+ for or_dep in parsed_framework:
161+ if len(or_dep) > 1:
162+ raise ClickFrameworkInvalid(
163+ 'Alternative dependencies in framework "%s" not yet '
164+ 'allowed' % framework_string)
165+ if or_dep[0][1] or or_dep[0][2]:
166+ raise ClickFrameworkInvalid(
167+ 'Version relationship in framework "%s" not yet allowed' %
168+ framework_string)
169+ # now verify that different base versions are not mixed
170+ framework_name = or_dep[0][0]
171+ if not click_framework_has_framework(framework_name):
172+ missing_frameworks.append(framework_name)
173+ continue
174+ framework_base_version = click_framework_get_base_version(
175+ framework_name)
176+ framework_base_versions.add(framework_base_version)
177+
178+ if not ignore_missing_frameworks:
179+ if len(missing_frameworks) > 1:
180+ raise ClickFrameworkInvalid(
181+ 'Frameworks %s not present on system (use '
182+ '--force-missing-framework option to override)' %
183+ ", ".join('"%s"' % f for f in missing_frameworks))
184+ elif missing_frameworks:
185+ raise ClickFrameworkInvalid(
186+ 'Framework "%s" not present on system (use '
187+ '--force-missing-framework option to override)' %
188+ missing_frameworks[0])
189+ else:
190+ if len(missing_frameworks) > 1:
191+ logging.warning("Ignoring missing frameworks %s" % (
192+ ", ".join('"%s"' % f for f in missing_frameworks)))
193+ elif missing_frameworks:
194+ logging.warning('Ignoring missing framework "%s"' % (
195+ missing_frameworks[0]))
196+
197+ if len(framework_base_versions) > 1:
198+ raise ClickFrameworkInvalid(
199+ 'Multiple frameworks with different base versions are not '
200+ 'allowed. Found: {0}'.format(framework_base_versions))
201
202=== modified file 'click/install.py'
203--- click/install.py 2014-04-03 08:52:02 +0000
204+++ click/install.py 2014-05-13 21:05:36 +0000
205@@ -49,6 +49,11 @@
206 from click.preinst import static_preinst_matches
207 from click.versions import spec_version
208
209+from click.framework import (
210+ validate_framework,
211+ ClickFrameworkInvalid,
212+)
213+
214
215 try:
216 _DebFile.close
217@@ -189,34 +194,9 @@
218 raise ClickInstallerAuditError(
219 'No "framework" entry in manifest')
220 try:
221- parsed_framework = apt_pkg.parse_depends(framework)
222- except ValueError:
223- raise ClickInstallerAuditError(
224- 'Could not parse framework "%s"' % framework)
225- for or_dep in parsed_framework:
226- if len(or_dep) > 1:
227- raise ClickInstallerAuditError(
228- 'Alternative dependencies in framework "%s" not yet '
229- 'allowed' % framework)
230- if or_dep[0][1] or or_dep[0][2]:
231- raise ClickInstallerAuditError(
232- 'Version relationship in framework "%s" not yet '
233- 'allowed' % framework)
234- if not self.force_missing_framework:
235- missing_frameworks = []
236- for or_dep in parsed_framework:
237- if not Click.Framework.has_framework(or_dep[0][0]):
238- missing_frameworks.append(or_dep[0][0])
239- if len(missing_frameworks) > 1:
240- raise ClickInstallerAuditError(
241- 'Frameworks %s not present on system (use '
242- '--force-missing-framework option to override)' %
243- ", ".join('"%s"' % f for f in missing_frameworks))
244- elif missing_frameworks:
245- raise ClickInstallerAuditError(
246- 'Framework "%s" not present on system (use '
247- '--force-missing-framework option to override)' %
248- missing_frameworks[0])
249+ validate_framework(framework, self.force_missing_framework)
250+ except ClickFrameworkInvalid as e:
251+ raise ClickInstallerAuditError(str(e))
252
253 if check_arch:
254 architecture = manifest.get("architecture", "all")
255
256=== modified file 'click/paths.py.in'
257--- click/paths.py.in 2014-03-10 14:49:27 +0000
258+++ click/paths.py.in 2014-05-13 21:05:36 +0000
259@@ -16,3 +16,4 @@
260 """Click paths."""
261
262 preload_path = "@pkglibdir@/libclickpreload.so"
263+frameworks_dir = "@pkgdatadir@/frameworks"
264
265=== modified file 'click/tests/helpers.py'
266--- click/tests/helpers.py 2014-03-10 14:49:27 +0000
267+++ click/tests/helpers.py 2014-05-13 21:05:36 +0000
268@@ -30,6 +30,7 @@
269
270 import contextlib
271 import os
272+import re
273 import shutil
274 import sys
275 import tempfile
276@@ -107,6 +108,34 @@
277 self.assertRaisesGError(
278 "click_user_error-quark", code, callableObj, *args, **kwargs)
279
280+ def _create_mock_framework_dir(self, frameworks_dir=None):
281+ if frameworks_dir is None:
282+ frameworks_dir = os.path.join(self.temp_dir, "frameworks")
283+ patcher = mock.patch('click.paths.frameworks_dir', frameworks_dir)
284+ patcher.start()
285+ self.addCleanup(patcher.stop)
286+ Click.ensuredir(frameworks_dir)
287+ return frameworks_dir
288+
289+ def _create_mock_framework_file(self, framework_name):
290+ self.use_temp_dir()
291+ self._create_mock_framework_dir()
292+ r = r'(?P<name>[a-z]+-sdk)-(?P<ver>[0-9.]+)(-[a-z0-9-]+)?'
293+ match = re.match(r, framework_name)
294+ if match is None:
295+ name = "unknown"
296+ ver = "1.0"
297+ else:
298+ name = match.group("name")
299+ ver = match.group("ver")
300+ framework_filename = os.path.join(
301+ self.temp_dir, "frameworks",
302+ "{0}.framework".format(framework_name))
303+ with open(framework_filename, "w") as f:
304+ f.write("Base-Name: {0}\n".format(name))
305+ f.write("Base-Version: {0}\n".format(ver))
306+
307+
308
309 if not hasattr(mock, "call"):
310 # mock 0.7.2, the version in Ubuntu 12.04 LTS, lacks mock.ANY and
311
312=== modified file 'click/tests/test_build.py'
313--- click/tests/test_build.py 2014-01-23 17:19:09 +0000
314+++ click/tests/test_build.py 2014-05-13 21:05:36 +0000
315@@ -245,6 +245,7 @@
316 del target_json["installed-size"]
317 self.assertEqual(source_json, target_json)
318
319+ # FIXME: DRY violation with test_build_multiple_architectures etc
320 def test_build_multiple_frameworks(self):
321 self.use_temp_dir()
322 scratch = os.path.join(self.temp_dir, "scratch")
323@@ -259,11 +260,44 @@
324 "ubuntu-sdk-14.04-basic, ubuntu-sdk-14.04-webapps",
325 }, f)
326 self.builder.add_file(scratch, "/")
327- self.assertRaisesRegex(
328- ClickBuildError,
329- 'Multiple dependencies in framework "ubuntu-sdk-14.04-basic, '
330- 'ubuntu-sdk-14.04-webapps" not yet allowed',
331- self.builder.build, self.temp_dir)
332+ path = self.builder.build(self.temp_dir)
333+ control_path = os.path.join(self.temp_dir, "control")
334+ subprocess.check_call(["dpkg-deb", "-e", path, control_path])
335+ manifest_path = os.path.join(control_path, "manifest")
336+ with open(os.path.join(scratch, "manifest.json")) as source, \
337+ open(manifest_path) as target:
338+ source_json = json.load(source)
339+ target_json = json.load(target)
340+ del target_json["installed-size"]
341+ self.assertEqual(source_json, target_json)
342+
343+
344+class TestClickFrameworkValidation(TestCase):
345+ def setUp(self):
346+ super(TestClickFrameworkValidation, self).setUp()
347+ self.builder = ClickBuilder()
348+ for framework_name in ("ubuntu-sdk-13.10",
349+ "ubuntu-sdk-14.04-papi",
350+ "ubuntu-sdk-14.04-html"):
351+ self._create_mock_framework_file(framework_name)
352+
353+ def test_validate_framework_good(self):
354+ valid_framework_values = (
355+ "ubuntu-sdk-13.10",
356+ "ubuntu-sdk-14.04-papi, ubuntu-sdk-14.04-html",
357+ )
358+ for framework in valid_framework_values:
359+ self.builder._validate_framework(framework)
360+
361+ def test_validate_framework_bad(self):
362+ invalid_framework_values = (
363+ "ubuntu-sdk-13.10, ubuntu-sdk-14.04-papi",
364+ "ubuntu-sdk-13.10 (>= 13.10)",
365+ "ubuntu-sdk-13.10 | ubuntu-sdk-14.04",
366+ )
367+ for framework in invalid_framework_values:
368+ with self.assertRaises(ClickBuildError):
369+ self.builder._validate_framework(framework)
370
371
372 class TestClickSourceBuilder(TestCase, TestClickBuilderBaseMixin):
373
374=== modified file 'click/tests/test_install.py'
375--- click/tests/test_install.py 2014-04-03 08:52:02 +0000
376+++ click/tests/test_install.py 2014-05-13 21:05:36 +0000
377@@ -104,12 +104,10 @@
378 return package_path
379
380 def _setup_frameworks(self, preloads, frameworks_dir=None, frameworks=[]):
381- if frameworks_dir is None:
382- frameworks_dir = os.path.join(self.temp_dir, "frameworks")
383+ frameworks_dir = self._create_mock_framework_dir(frameworks_dir)
384 shutil.rmtree(frameworks_dir, ignore_errors=True)
385- Click.ensuredir(frameworks_dir)
386 for framework in frameworks:
387- touch(os.path.join(frameworks_dir, "%s.framework" % framework))
388+ self._create_mock_framework_file(framework)
389 preloads["click_get_frameworks_dir"].side_effect = (
390 lambda: self.make_string(frameworks_dir))
391
392
393=== modified file 'debian/changelog'
394--- debian/changelog 2014-04-08 09:41:55 +0000
395+++ debian/changelog 2014-05-13 21:05:36 +0000
396@@ -1,3 +1,11 @@
397+click (0.4.22) UNRELEASED; urgency=medium
398+
399+ [ Michael Vogt ]
400+ * Update documentation for building the project.
401+ * Add support for multiple frameworks (LP: #1318757).
402+
403+ -- Colin Watson <cjwatson@ubuntu.com> Thu, 08 May 2014 13:47:55 +0100
404+
405 click (0.4.21.1) trusty; urgency=medium
406
407 [ Colin Watson ]
408
409=== modified file 'doc/file-format.rst'
410--- doc/file-format.rst 2014-03-07 15:09:17 +0000
411+++ doc/file-format.rst 2014-05-13 21:05:36 +0000
412@@ -103,8 +103,7 @@
413 "framework": "ubuntu-sdk-13.10", or a list of simple names all of which must
414 be satisfied, e.g. "framework": "ubuntu-sdk-14.04-qml,
415 ubuntu-sdk-14.04-webapps"; version relationships and alternative
416-dependencies are not currently allowed. At the moment, ``click build`` will
417-only allow building with a single simple name, pending policy decisions.
418+dependencies are not currently allowed.
419
420 The manifest may contain arbitrary additional optional keys; new optional
421 keys may be defined without changing the version number of this
422
423=== modified file 'doc/index.rst'
424--- doc/index.rst 2014-03-13 10:38:22 +0000
425+++ doc/index.rst 2014-05-13 21:05:36 +0000
426@@ -25,20 +25,34 @@
427 Currently, this package should remain compatible with Python 2.7, 3.2, 3.3,
428 and 3.4; Ubuntu 12.04 LTS, Ubuntu 13.10, and Ubuntu 14.04 LTS.
429
430+Build
431+=====
432+
433+If you run from a fresh bzr checkout, please ensure you have the required
434+build dependencies first by running::
435+
436+ $ dpkg-checkbuilddeps
437+
438+and installing anything that is missing here.
439+
440+Then run::
441+
442+ $ ./autogen.sh
443+ $ ./configure --with-systemdsystemunitdir=/lib/systemd/system \
444+ --with-systemduserunitdir=/usr/lib/systemd/user
445+ $ make
446+
447+to build the project.
448+
449
450 Dependencies
451 ------------
452
453-For Ubuntu 13.10, make sure you have the *python2.7* and *python3.3*
454+For Ubuntu 14.04, make sure you have the *python2.7* and *python3.4*
455 packages installed. Unless you upgraded from a previous version of Ubuntu
456-and haven't removed it yet, you won't have Python 3.2 available. Build it
457-from source if necessary, install them say into ``/usr/local``, and make
458-sure it is on your ``$PATH``.
459-
460-You'll need *gcc* in order to build the preload shared library. Assuming you
461-have this, do the following::
462-
463- $ (cd preload && make)
464+and haven't removed it yet, you won't have Python 3.3 and Python 3.2
465+available. Build them from source if necessary, install them say into
466+``/usr/local``, and make sure they are on your ``$PATH``.
467
468 You'll need *tox* (Ubuntu package *python-tox*) installed in order to run the
469 full test suite. You should be able to just say::
470@@ -66,6 +80,15 @@
471 an additional check to make sure you've got the preload shared library
472 built.
473
474+To run a specific testcase, use the standard python unittest syntax like::
475+
476+ $ python3 -m unittest click.tests.test_install
477+
478+or::
479+
480+ $ python2 -m unittest click.tests.test_build.TestClickBuilder.test_build
481+
482+
483
484 Documentation
485 =============

Subscribers

People subscribed via source and target branches