Merge lp:click/devel into lp:click

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 529
Merged at revision: 446
Proposed branch: lp:click/devel
Merge into: lp:click
Diff against target: 706 lines (+263/-100)
14 files modified
click/chroot.py (+31/-6)
click/commands/chroot.py (+19/-9)
click/commands/framework.py (+29/-0)
click/commands/install.py (+1/-1)
click/install.py (+7/-0)
click/tests/helpers.py (+8/-0)
click/tests/integration/test_chroot.py (+31/-25)
click/tests/test_database.py (+2/-12)
click/tests/test_user.py (+28/-2)
debian/changelog (+24/-0)
doc/manpage.rst (+6/-0)
lib/click/database.vala (+26/-45)
lib/click/hooks.vala (+1/-0)
lib/click/user.vala (+50/-0)
To merge this branch: bzr merge lp:click/devel
Reviewer Review Type Date Requested Status
Colin Watson Approve
Review via email: mp+236345@code.launchpad.net

Commit message

Click 0.4.33: Add scope APIs; fix GC; add "click chroot --name"; make "click destroy" more robust; stop apps when uninstalling them.

Description of the change

  [ Pete Woods ]
  * Add scope-facing APIs to chroot build (LP: #1370727).

  [ Colin Watson ]
  * Warn that "click install" without a registration may result in later
    garbage-collection.
  * Rearrange garbage-collection to remove versions of packages that have no
    user registrations and are not running, rather than using the artificial
    @gcinuse registration which never really worked properly.
  * Run garbage-collection immediately before running system hooks on system
    startup (LP: #1342858).
  * Add new -n/--name option to "click chroot", defaulting to "click"
    (LP: #1364327).

  [ Michael Vogt ]
  * Make click destroy more robust by unmounting any mounted filesystem
    inside the schroot first (LP: #1346723).
  * Stop apps if necessary when uninstalling them (LP: #1232130).
  * Add new "click framework {info,get-field}" subcommands.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'click/chroot.py'
2--- click/chroot.py 2014-08-06 22:38:57 +0000
3+++ click/chroot.py 2014-09-29 14:09:31 +0000
4@@ -77,6 +77,11 @@
5 ],
6 "ubuntu-sdk-14.04": [
7 "cmake",
8+ "google-mock:TARGET",
9+ "libboost1.54-dev:TARGET",
10+ "libjsoncpp-dev:TARGET",
11+ "libprocess-cpp-dev:TARGET",
12+ "libproperties-cpp-dev:TARGET",
13 "libqt5svg5-dev:TARGET",
14 "libqt5webkit5-dev:TARGET",
15 "libqt5xmlpatterns5-dev:TARGET",
16@@ -99,7 +104,13 @@
17 ],
18 "ubuntu-sdk-14.10": [
19 "cmake",
20+ "google-mock:TARGET",
21+ "libboost1.55-dev:TARGET",
22 "libcontent-hub-dev:TARGET",
23+ "libjsoncpp-dev:TARGET",
24+ "libnet-cpp-dev:TARGET",
25+ "libprocess-cpp-dev:TARGET",
26+ "libproperties-cpp-dev:TARGET",
27 "libqt5keychain0:TARGET",
28 "libqt5sensors5-dev:TARGET",
29 "libqt5svg5-dev:TARGET",
30@@ -546,12 +557,26 @@
31 return self.clean()
32
33 def destroy(self):
34- if not self.exists():
35- raise ClickChrootDoesNotExistException(
36- "Chroot %s does not exist" % self.full_name)
37- os.remove(self.chroot_config)
38- mount = "%s/%s" % (self.chroots_dir, self.full_name)
39- shutil.rmtree(mount)
40+ # remove config
41+ if os.path.exists(self.chroot_config):
42+ os.remove(self.chroot_config)
43+ # find all schroot mount points, this is actually quite complicated
44+ mount_dir = os.path.abspath(
45+ os.path.join(self.chroots_dir, "..", "mount"))
46+ needle = os.path.join(mount_dir, self.full_name)
47+ all_mounts = []
48+ with open("/proc/mounts") as f:
49+ for line in f.readlines():
50+ mp = line.split()[1]
51+ if mp.startswith(needle):
52+ all_mounts.append(mp)
53+ # reverse order is important in case of submounts
54+ for mp in sorted(all_mounts, key=len, reverse=True):
55+ subprocess.call(["umount", mp])
56+ # now remove the rest
57+ chroot_dir = "%s/%s" % (self.chroots_dir, self.full_name)
58+ if os.path.exists(chroot_dir):
59+ shutil.rmtree(chroot_dir)
60 return 0
61
62 def begin_session(self):
63
64=== modified file 'click/commands/chroot.py'
65--- click/commands/chroot.py 2014-07-01 07:12:49 +0000
66+++ click/commands/chroot.py 2014-09-29 14:09:31 +0000
67@@ -61,7 +61,8 @@
68 "debootstrap not installed and configured; install click-dev and "
69 "debootstrap")
70 requires_root(parser)
71- chroot = ClickChroot(args.architecture, args.framework, series=args.series)
72+ chroot = ClickChroot(
73+ args.architecture, args.framework, name=args.name, series=args.series)
74 with message_on_error(
75 ClickChrootAlreadyExistsException, ErrorMessages.EXISTS):
76 return chroot.create(args.keep_broken_chroot)
77@@ -71,7 +72,7 @@
78
79 def install(parser, args):
80 packages = args.packages
81- chroot = ClickChroot(args.architecture, args.framework)
82+ chroot = ClickChroot(args.architecture, args.framework, name=args.name)
83 with message_on_error(
84 ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS):
85 return chroot.install(*packages)
86@@ -82,7 +83,7 @@
87 def destroy(parser, args):
88 requires_root(parser)
89 # ask for confirmation?
90- chroot = ClickChroot(args.architecture, args.framework)
91+ chroot = ClickChroot(args.architecture, args.framework, name=args.name)
92 with message_on_error(
93 ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS):
94 return chroot.destroy()
95@@ -95,7 +96,8 @@
96 if not program:
97 program = ["/bin/bash"]
98 chroot = ClickChroot(
99- args.architecture, args.framework, session=args.session)
100+ args.architecture, args.framework, name=args.name,
101+ session=args.session)
102 with message_on_error(
103 ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS):
104 return chroot.run(*program)
105@@ -108,7 +110,8 @@
106 if not program:
107 program = ["/bin/bash"]
108 chroot = ClickChroot(
109- args.architecture, args.framework, session=args.session)
110+ args.architecture, args.framework, name=args.name,
111+ session=args.session)
112 with message_on_error(
113 ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS):
114 return chroot.maint(*program)
115@@ -117,7 +120,7 @@
116
117
118 def upgrade(parser, args):
119- chroot = ClickChroot(args.architecture, args.framework)
120+ chroot = ClickChroot(args.architecture, args.framework, name=args.name)
121 with message_on_error(
122 ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS):
123 return chroot.upgrade()
124@@ -127,7 +130,8 @@
125
126 def begin_session(parser, args):
127 chroot = ClickChroot(
128- args.architecture, args.framework, session=args.session)
129+ args.architecture, args.framework, name=args.name,
130+ session=args.session)
131 with message_on_error(
132 ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS):
133 return chroot.begin_session()
134@@ -137,7 +141,8 @@
135
136 def end_session(parser, args):
137 chroot = ClickChroot(
138- args.architecture, args.framework, session=args.session)
139+ args.architecture, args.framework, name=args.name,
140+ session=args.session)
141 with message_on_error(
142 ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS):
143 return chroot.end_session()
144@@ -146,7 +151,7 @@
145
146
147 def exists(parser, args):
148- chroot = ClickChroot(args.architecture, args.framework)
149+ chroot = ClickChroot(args.architecture, args.framework, name=args.name)
150 # return shell exit codes 0 on success, 1 on failure
151 if chroot.exists():
152 return 0
153@@ -169,6 +174,11 @@
154 "-s", "--series",
155 help="series to use for a newly-created chroot (defaults to a series "
156 "appropriate for the framework)")
157+ parser.add_argument(
158+ "-n", "--name", default="click",
159+ help=(
160+ "name of the chroot (default: click; the framework and "
161+ "architecture will be appended)"))
162 create_parser = subparsers.add_parser(
163 "create",
164 help="create a chroot of the provided architecture")
165
166=== modified file 'click/commands/framework.py'
167--- click/commands/framework.py 2014-05-20 07:02:53 +0000
168+++ click/commands/framework.py 2014-09-29 14:09:31 +0000
169@@ -28,6 +28,17 @@
170 return 0
171
172
173+def info(parser, args):
174+ framework = Click.Framework.open(args.framework_name)
175+ for field in sorted(framework.get_fields()):
176+ print("%s: %s" % (field, framework.get_field(field)))
177+
178+
179+def get_field(parser, args):
180+ framework = Click.Framework.open(args.framework_name)
181+ print(framework.get_field(args.field_name))
182+
183+
184 def run(argv):
185 parser = ArgumentParser("click framework")
186 subparsers = parser.add_subparsers()
187@@ -35,6 +46,24 @@
188 "list",
189 help="list available frameworks")
190 list_parser.set_defaults(func=list)
191+ info_parser = subparsers.add_parser(
192+ "info",
193+ help="show info about a specific framework")
194+ info_parser.add_argument(
195+ "framework_name",
196+ help="framework name with the information")
197+ info_parser.set_defaults(func=info)
198+ get_field_parser = subparsers.add_parser(
199+ "get-field",
200+ help="get a field from a given framework")
201+ get_field_parser.add_argument(
202+ "framework_name",
203+ help="framework name with the information")
204+ get_field_parser.add_argument(
205+ "field_name",
206+ help="the field name (e.g. base-version)")
207+ get_field_parser.set_defaults(func=get_field)
208+
209 args = parser.parse_args(argv)
210 if not hasattr(args, "func"):
211 parser.print_help()
212
213=== modified file 'click/commands/install.py'
214--- click/commands/install.py 2014-08-07 21:25:48 +0000
215+++ click/commands/install.py 2014-09-29 14:09:31 +0000
216@@ -45,7 +45,7 @@
217 help="register package for all users")
218 parser.add_option(
219 "--allow-unauthenticated", default=False, action="store_true",
220- help="allow installing packages with no sigantures")
221+ help="allow installing packages with no signatures")
222 options, args = parser.parse_args(argv)
223 if len(args) < 1:
224 parser.error("need package file name")
225
226=== modified file 'click/install.py'
227--- click/install.py 2014-09-04 14:15:08 +0000
228+++ click/install.py 2014-09-29 14:09:31 +0000
229@@ -38,6 +38,7 @@
230 import subprocess
231 import sys
232 import tempfile
233+from textwrap import dedent
234
235 from contextlib import closing
236
237@@ -450,6 +451,12 @@
238 else:
239 registry = Click.User.for_user(self.db, name=user)
240 registry.set_version(package_name, package_version)
241+ else:
242+ print(dedent("""\
243+ %s %s has not been registered for any users.
244+ It may be garbage-collected the next time the system starts.
245+ To avoid this, use "click register".
246+ """) % (package_name, package_version))
247
248 if old_version is not None:
249 self.db.maybe_remove(package_name, old_version)
250
251=== modified file 'click/tests/helpers.py'
252--- click/tests/helpers.py 2014-06-23 21:14:06 +0000
253+++ click/tests/helpers.py 2014-09-29 14:09:31 +0000
254@@ -326,3 +326,11 @@
255 def touch(path):
256 with mkfile(path, mode="a"):
257 pass
258+
259+
260+def make_file_with_content(filename, content, mode=0o644):
261+ """Create a file with the given content and mode"""
262+ Click.ensuredir(os.path.dirname(filename))
263+ with open(filename, "w") as f:
264+ f.write(content)
265+ os.chmod(filename, mode)
266
267=== modified file 'click/tests/integration/test_chroot.py'
268--- click/tests/integration/test_chroot.py 2014-09-05 09:04:33 +0000
269+++ click/tests/integration/test_chroot.py 2014-09-29 14:09:31 +0000
270@@ -27,52 +27,58 @@
271 class TestChroot(ClickTestCase):
272
273 @classmethod
274+ def command(cls, arch, *args):
275+ return [cls.click_binary, "chroot", "-a", arch] + list(args)
276+
277+ @classmethod
278 def setUpClass(cls):
279 super(TestChroot, cls).setUpClass()
280 require_root()
281 require_network()
282 cls.arch = subprocess.check_output(
283 ["dpkg", "--print-architecture"], universal_newlines=True).strip()
284- subprocess.check_call([
285- cls.click_binary,
286- "chroot", "-a", cls.arch,
287- "create"])
288+ subprocess.check_call(cls.command(cls.arch, "create"))
289
290 @classmethod
291 def tearDownClass(cls):
292- subprocess.check_call([
293- cls.click_binary,
294- "chroot", "-a", cls.arch,
295- "destroy"])
296+ subprocess.check_call(cls.command(cls.arch, "destroy"))
297
298 def test_upgrade(self):
299- subprocess.check_call([
300- self.click_binary, "chroot", "-a", self.arch,
301- "upgrade"])
302+ subprocess.check_call(self.command(self.arch, "upgrade"))
303
304 def test_install(self):
305- subprocess.check_call([
306- self.click_binary, "chroot", "-a", self.arch,
307- "install", "apt-utils"])
308+ subprocess.check_call(self.command(self.arch, "install", "apt-utils"))
309
310 def test_run(self):
311- output = subprocess.check_output([
312- self.click_binary, "chroot", "-a", self.arch,
313- "run", "echo", "hello world"], universal_newlines=True)
314+ output = subprocess.check_output(
315+ self.command(self.arch, "run", "echo", "hello world"),
316+ universal_newlines=True)
317 self.assertEqual(output, "hello world\n")
318
319 def test_maint(self):
320- output = subprocess.check_output([
321- self.click_binary, "chroot", "-a", self.arch,
322- "maint", "id"], universal_newlines=True)
323+ output = subprocess.check_output(
324+ self.command(self.arch, "maint", "id"),
325+ universal_newlines=True)
326 self.assertEqual(output, "uid=0(root) gid=0(root) groups=0(root)\n")
327
328 def test_exists_ok(self):
329- subprocess.check_call([
330- self.click_binary, "chroot", "-a", self.arch, "exists"])
331+ subprocess.check_call(self.command(self.arch, "exists"))
332
333 def test_exists_no(self):
334 with self.assertRaises(subprocess.CalledProcessError):
335- subprocess.check_call([
336- self.click_binary,
337- "chroot", "-a", "arch-that-does-not-exist"])
338+ subprocess.check_call(self.command("arch-that-does-not-exist"))
339+
340+
341+class TestChrootName(TestChroot):
342+ """Run the chroot tests again with a different --name."""
343+
344+ @classmethod
345+ def command(cls, arch, *args):
346+ return super(TestChrootName, cls).command(
347+ arch, "-n", "testname", *args)
348+
349+ def test_exists_different_name_fails(self):
350+ # "click chroot exists" fails for a non-existent name.
351+ with self.assertRaises(subprocess.CalledProcessError):
352+ subprocess.check_call(super(TestChrootName, self).command(
353+ self.arch, "-n", "testname2", "exists"))
354
355=== modified file 'click/tests/test_database.py'
356--- click/tests/test_database.py 2014-07-11 17:20:51 +0000
357+++ click/tests/test_database.py 2014-09-29 14:09:31 +0000
358@@ -331,14 +331,6 @@
359 self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0})
360 preloads["click_find_on_path"].return_value = True
361 self.db.maybe_remove("a", "1.0")
362- gcinuse_path = os.path.join(
363- self.temp_dir, ".click", "users", "@gcinuse", "a")
364- self.assertTrue(os.path.islink(gcinuse_path))
365- self.assertEqual(version_path, os.readlink(gcinuse_path))
366- self.assertTrue(os.path.exists(version_path))
367- self.db.maybe_remove("a", "1.0")
368- self.assertTrue(os.path.islink(gcinuse_path))
369- self.assertEqual(version_path, os.readlink(gcinuse_path))
370 self.assertTrue(os.path.exists(version_path))
371
372 def test_maybe_remove_not_running(self):
373@@ -358,9 +350,6 @@
374 self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8})
375 preloads["click_find_on_path"].return_value = True
376 self.db.maybe_remove("a", "1.0")
377- gcinuse_path = os.path.join(
378- self.temp_dir, ".click", "users", "@gcinuse", "a")
379- self.assertFalse(os.path.islink(gcinuse_path))
380 self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "a")))
381
382 def test_gc(self):
383@@ -399,8 +388,9 @@
384 preloads["click_find_on_path"].return_value = True
385 self.db.gc()
386 self.assertTrue(os.path.exists(a_path))
387+ self.assertFalse(os.path.exists(b_gcinuse_path))
388 self.assertFalse(os.path.exists(b_path))
389- self.assertTrue(os.path.exists(c_path))
390+ self.assertFalse(os.path.exists(c_path))
391
392 def test_gc_ignores_non_directory(self):
393 with self.run_in_subprocess(
394
395=== modified file 'click/tests/test_user.py'
396--- click/tests/test_user.py 2014-07-09 12:56:21 +0000
397+++ click/tests/test_user.py 2014-09-29 14:09:31 +0000
398@@ -26,12 +26,18 @@
399 import json
400 import os
401 import shutil
402+from textwrap import dedent
403
404 from gi.repository import Click, GLib
405
406 from click.json_helpers import json_array_to_python, json_object_to_python
407 from click.tests.gimock_types import Passwd
408-from click.tests.helpers import TestCase, mkfile, touch
409+from click.tests.helpers import (
410+ TestCase,
411+ mkfile,
412+ make_file_with_content,
413+ touch,
414+)
415
416
417 class TestClickUser(TestCase):
418@@ -53,7 +59,8 @@
419 a_1_0 = os.path.join(self.temp_dir, "custom", "a", "1.0")
420 os.makedirs(a_1_0)
421 with mkfile(os.path.join(a_1_0, ".click", "info", "a.manifest")) as m:
422- json.dump({"name": "a", "version": "1.0"}, m)
423+ json.dump({"name": "a", "version": "1.0",
424+ "hooks": {"a-app": {}}}, m)
425 b_2_0 = os.path.join(self.temp_dir, "custom", "b", "2.0")
426 os.makedirs(b_2_0)
427 with mkfile(os.path.join(b_2_0, ".click", "info", "b.manifest")) as m:
428@@ -549,3 +556,22 @@
429 self.assertEqual("2.0", registry.get_version("b"))
430 self.assertEqual(b_overlay, registry.get_path("b"))
431 self.assertTrue(registry.is_removable("b"))
432+
433+ def test_app_stops_on_remove(self):
434+ fake_app_stop = os.path.join(self.temp_dir, "bin", "ubuntu-app-stop")
435+ fake_app_stop_output = os.path.join(self.temp_dir, "fake-app-stop.out")
436+ fake_app_stop_content = dedent("""\
437+ #!/bin/sh
438+ echo "$@" >> %s
439+ """ % fake_app_stop_output)
440+ make_file_with_content(fake_app_stop, fake_app_stop_content, 0o755)
441+ # its ok to modify env here, click.helpers.TestCase will take care
442+ # of it
443+ os.environ["PATH"] = "%s:%s" % (
444+ os.path.dirname(fake_app_stop), os.environ["PATH"])
445+ # get a app with manifest etc all
446+ _, registry = self._setUpMultiDB()
447+ registry.remove("a")
448+ # ensure that stop was called with the right app
449+ with open(fake_app_stop_output) as f:
450+ self.assertEqual("a_a-app_1.1", f.read().strip())
451
452=== modified file 'debian/changelog'
453--- debian/changelog 2014-09-09 10:02:01 +0000
454+++ debian/changelog 2014-09-29 14:09:31 +0000
455@@ -1,3 +1,27 @@
456+click (0.4.33) UNRELEASED; urgency=medium
457+
458+ [ Pete Woods ]
459+ * Add scope-facing APIs to chroot build (LP: #1370727).
460+
461+ [ Colin Watson ]
462+ * Warn that "click install" without a registration may result in later
463+ garbage-collection.
464+ * Rearrange garbage-collection to remove versions of packages that have no
465+ user registrations and are not running, rather than using the artificial
466+ @gcinuse registration which never really worked properly.
467+ * Run garbage-collection immediately before running system hooks on system
468+ startup (LP: #1342858).
469+ * Add new -n/--name option to "click chroot", defaulting to "click"
470+ (LP: #1364327).
471+
472+ [ Michael Vogt ]
473+ * Make click destroy more robust by unmounting any mounted filesystem
474+ inside the schroot first (LP: #1346723).
475+ * Stop apps if necessary when uninstalling them (LP: #1232130).
476+ * Add new "click framework {info,get-field}" subcommands.
477+
478+ -- Colin Watson <cjwatson@ubuntu.com> Wed, 10 Sep 2014 12:47:46 +0100
479+
480 click (0.4.32.1) utopic; urgency=low
481
482 [ Michael Vogt ]
483
484=== modified file 'doc/manpage.rst'
485--- doc/manpage.rst 2014-09-05 16:18:38 +0000
486+++ doc/manpage.rst 2014-09-29 14:09:31 +0000
487@@ -245,6 +245,12 @@
488 development versions of SDKs which have not yet put a framework declaration
489 in place.
490
491+You should always register installed packages either for a specific user or
492+for all users; if you do not do this then the packages may be
493+garbage-collected later. You can do this using the ``--user`` or
494+``--all-users`` options to this command, or using the ``click register``
495+command.
496+
497 Options:
498
499 --root=PATH Install packages underneath PATH.
500
501=== modified file 'lib/click/database.vala'
502--- lib/click/database.vala 2014-06-23 16:00:56 +0000
503+++ lib/click/database.vala 2014-09-29 14:09:31 +0000
504@@ -330,12 +330,8 @@
505 private void
506 remove_unless_running (string package, string version) throws Error
507 {
508- if (any_app_running (package, version)) {
509- var gc_in_use_user_db =
510- new User.for_gc_in_use (master_db);
511- gc_in_use_user_db.set_version (package, version);
512+ if (any_app_running (package, version))
513 return;
514- }
515
516 var version_path = get_path (package, version);
517 if (show_messages ())
518@@ -386,14 +382,10 @@
519 * Remove a package version if it is not in use.
520 *
521 * "In use" may mean registered for another user, or running. In
522- * the latter case we construct a fake registration so that we can
523- * tell the difference later between a package version that was in
524- * use at the time of removal and one that was never registered for
525- * any user.
526- *
527- * (This is unfortunately complex, and perhaps some day we can
528- * require that installations always have some kind of registration
529- * to avoid this complexity.)
530+ * either case, we do nothing. We will already have removed at
531+ * least one registration by this point, so if no registrations are
532+ * left but it is running, then gc will be able to come along later
533+ * and clean things out.
534 */
535 public void
536 maybe_remove (string package, string version) throws Error
537@@ -408,11 +400,8 @@
538 continue;
539 }
540 if (reg_version == version) {
541- if (user_db.is_gc_in_use)
542- user_db.remove (package);
543- else
544- /* In use. */
545- return;
546+ /* In use. */
547+ return;
548 }
549 }
550
551@@ -422,33 +411,33 @@
552 /**
553 * gc:
554 *
555- * Remove package versions with no user registrations.
556- *
557- * To avoid accidentally removing packages that were installed
558- * without ever having a user registration, we only garbage-collect
559- * packages that were not removed by maybe_remove() due to having a
560- * running application at the time.
561- *
562- * (This is unfortunately complex, and perhaps some day we can
563- * require that installations always have some kind of registration
564- * to avoid this complexity.)
565+ * Remove package versions that have no user registrations and that
566+ * are not running.
567+ *
568+ * This is rather like maybe_remove, but is suitable for bulk use,
569+ * since it only needs to scan the database once rather than once
570+ * per package.
571+ *
572+ * For historical reasons, we don't count @gcinuse as a real user
573+ * registration, and remove any such registrations we find. We can
574+ * drop this once we no longer care about upgrading versions from
575+ * before this change to something more current in a single step.
576 */
577 public void
578 gc () throws Error
579 {
580 var users_db = new Users (master_db);
581 var user_reg = new Gee.HashMultiMap<string, string> ();
582- var gc_in_use = new Gee.HashMultiMap<string, string> ();
583 foreach (var user_name in users_db.get_user_names ()) {
584 var user_db = users_db.get_user (user_name);
585 foreach (var package in user_db.get_package_names ()) {
586 var version = user_db.get_version (package);
587+ if (version == "current")
588+ continue;
589 /* Odd multimap syntax; this should really
590 * be more like foo[package] += version.
591 */
592- if (user_db.is_gc_in_use)
593- gc_in_use[package] = version;
594- else
595+ if (! user_db.is_gc_in_use)
596 user_reg[package] = version;
597 }
598 }
599@@ -462,22 +451,14 @@
600 continue;
601 foreach (var version in Click.Dir.open
602 (package_path)) {
603+ if (version == "current")
604+ continue;
605 if (version in user_reg[package])
606 /* In use. */
607 continue;
608- if (! (version in gc_in_use[package])) {
609- if (show_messages ()) {
610- var version_path =
611- Path.build_filename
612- (package_path,
613- version);
614- message ("Not removing %s " +
615- "(never registered).",
616- version_path);
617- }
618- continue;
619- }
620- gc_in_use_user_db.remove (package);
621+ if (gc_in_use_user_db.has_package_name
622+ (package))
623+ gc_in_use_user_db.remove (package);
624 remove_unless_running (package, version);
625 }
626 }
627
628=== modified file 'lib/click/hooks.vala'
629--- lib/click/hooks.vala 2014-05-19 13:19:16 +0000
630+++ lib/click/hooks.vala 2014-09-29 14:09:31 +0000
631@@ -1170,6 +1170,7 @@
632 public void
633 run_system_hooks (DB db) throws Error
634 {
635+ db.gc ();
636 db.ensure_ownership ();
637 string[] failed = {};
638 foreach (var hook in Hook.open_all (db)) {
639
640=== modified file 'lib/click/user.vala'
641--- lib/click/user.vala 2014-07-11 17:16:35 +0000
642+++ lib/click/user.vala 2014-09-29 14:09:31 +0000
643@@ -598,6 +598,48 @@
644 old_version, version, name);
645 }
646
647+ private bool
648+ stop_running_app (string package, string version)
649+ {
650+ var res = true;
651+ if (! find_on_path ("ubuntu-app-stop"))
652+ return false;
653+
654+ Json.Object manifest;
655+ try {
656+ manifest = get_manifest (package);
657+ } catch (Error e) {
658+ warning ("Can not get manifest for %s", package);
659+ return false;
660+ }
661+
662+ if (! manifest.has_member ("hooks")) {
663+ warning ("No hooks in manifest %s", package);
664+ return false;
665+ }
666+ var hooks = manifest.get_object_member ("hooks");
667+ foreach (unowned string app_name in hooks.get_members ())
668+ {
669+ // FIXME: move this into a "stop_single_app" helper
670+ string[] command = {
671+ "ubuntu-app-stop",
672+ @"$(package)_$(app_name)_$(version)"
673+ };
674+ try {
675+ int exit_status;
676+ Process.spawn_sync
677+ (null, command, null,
678+ SpawnFlags.SEARCH_PATH |
679+ SpawnFlags.STDOUT_TO_DEV_NULL,
680+ null, null, null, out exit_status);
681+ res &= Process.check_exit_status (exit_status);
682+ } catch (Error e) {
683+ res &= false;
684+ }
685+ }
686+ return res;
687+ }
688+
689 /**
690 * remove:
691 * @package: A package name.
692@@ -636,6 +678,14 @@
693 regain_privileges ();
694 }
695 }
696+
697+ drop_privileges ();
698+ try {
699+ stop_running_app (package, old_version);
700+ } finally {
701+ regain_privileges ();
702+ }
703+
704 if (! is_pseudo_user)
705 package_remove_hooks (db, package, old_version, name);
706 }

Subscribers

People subscribed via source and target branches

to all changes: