diff -Nru click-0.4.18.3/click/chroot.py click-0.4.19/click/chroot.py --- click-0.4.18.3/click/chroot.py 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/click/chroot.py 2014-03-18 14:27:44.000000000 +0000 @@ -32,8 +32,17 @@ import subprocess +framework_base = { + "ubuntu-sdk-13.10": "ubuntu-sdk-13.10", + "ubuntu-sdk-14.04-html-dev1": "ubuntu-sdk-14.04", + "ubuntu-sdk-14.04-papi-dev1": "ubuntu-sdk-14.04", + "ubuntu-sdk-14.04-qml-dev1": "ubuntu-sdk-14.04", + } + + framework_series = { "ubuntu-sdk-13.10": "saucy", + "ubuntu-sdk-14.04": "trusty", } @@ -56,6 +65,23 @@ "qtsensors5-dev:TARGET", "qttools5-dev:TARGET", ], + "ubuntu-sdk-14.04": [ + "cmake", + "libqt5svg5-dev:TARGET", + "libqt5webkit5-dev:TARGET", + "libqt5xmlpatterns5-dev:TARGET", + "qt3d5-dev:TARGET", + "qt5-default:TARGET", + "qtbase5-dev:TARGET", + "qtdeclarative5-dev:TARGET", + "qtdeclarative5-dev-tools", + "qtlocation5-dev:TARGET", + "qtmultimedia5-dev:TARGET", + "qtscript5-dev:TARGET", + "qtsensors5-dev:TARGET", + "qttools5-dev:TARGET", + "qttools5-dev-tools:TARGET", + ], } @@ -67,15 +93,16 @@ class ClickChroot: - def __init__(self, target_arch, framework, name=None, series=None): - if name is None: - name = "click" - if series is None: - series = framework_series[framework] + def __init__(self, target_arch, framework, name=None, series=None, session=None): self.target_arch = target_arch self.framework = framework + if name is None: + name = "click" self.name = name + if series is None: + series = framework_series[self.framework_base] self.series = series + self.session = session self.native_arch = subprocess.check_output( ["dpkg", "--print-architecture"], universal_newlines=True).strip() @@ -87,6 +114,8 @@ self.archive = "http://archive.ubuntu.com/ubuntu" if "SUDO_USER" in os.environ: self.user = os.environ["SUDO_USER"] + elif "PKEXEC_UID" in os.environ: + self.user = pwd.getpwuid(int(os.environ["PKEXEC_UID"])).pw_name else: self.user = pwd.getpwuid(os.getuid()).pw_name self.dpkg_architecture = self._dpkg_architecture() @@ -127,8 +156,19 @@ return sources @property + def framework_base(self): + if self.framework in framework_base: + return framework_base[self.framework] + else: + return self.framework + + @property def full_name(self): - return "%s-%s-%s" % (self.name, self.framework, self.target_arch) + return "%s-%s-%s" % (self.name, self.framework_base, self.target_arch) + + @property + def full_session_name(self): + return "%s-%s" % (self.full_name, self.session) def exists(self): command = ["schroot", "-c", self.full_name, "-i"] @@ -136,6 +176,10 @@ return subprocess.call( command, stdout=devnull, stderr=devnull) == 0 + def _make_executable(self, path): + mode = stat.S_IMODE(os.stat(path).st_mode) + os.chmod(path, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + def create(self): if self.exists(): raise ClickChrootException( @@ -156,7 +200,7 @@ "pkg-config-%s" % target_tuple, "cmake", "dpkg-cross", "libc-dev:%s" % self.target_arch ] - for package in extra_packages.get(self.framework, []): + for package in extra_packages.get(self.framework_base, []): package = package.replace(":TARGET", ":%s" % self.target_arch) build_pkgs.append(package) os.makedirs(mount) @@ -205,6 +249,7 @@ print(" *) exit 101;;", file=policy) print(" esac", file=policy) print("done", file=policy) + self._make_executable(daemon_policy) os.remove("%s/sbin/initctl" % mount) os.symlink("%s/bin/true" % mount, "%s/sbin/initctl" % mount) finish_script = "%s/finish.sh" % mount @@ -235,6 +280,7 @@ debconf-communicate", file=finish) print("echo set debconf/priority critical | \ debconf-communicate", file=finish) + print("apt-get -y --force-yes dist-upgrade", file=finish) print("# Install basic build tool set to match buildd", file=finish) print("apt-get -y --force-yes install %s" @@ -249,7 +295,7 @@ print("# Clean up", file=finish) print("rm /finish.sh", file=finish) print("apt-get clean", file=finish) - os.chmod(finish_script, stat.S_IEXEC) + self._make_executable(finish_script) command = ["/finish.sh"] self.maint(*command) @@ -257,16 +303,24 @@ if not self.exists(): raise ClickChrootException( "Chroot %s does not exist" % self.full_name) - command = ["schroot", "-c", self.full_name, "--", "env"] + command = ["schroot", "-c"] + if self.session: + command.extend([self.full_session_name, "--run-session"]) + else: + command.append(self.full_name) + command.extend(["--", "env"]) for key, value in self.dpkg_architecture.items(): command.append("%s=%s" % (key, value)) command.extend(args) subprocess.check_call(command) def maint(self, *args): - command = [ - "schroot", "-c", "source:%s" % self.full_name, "-u", "root", "--", - ] + command = [ "schroot", "-u", "root", "-c" ] + if self.session: + command.extend([self.full_session_name, "--run-session"]) + else: + command.append("source:%s" % self.full_name) + command.append("--") command.extend(args) subprocess.check_call(command) @@ -305,3 +359,18 @@ os.remove(chroot_config) mount = "%s/%s" % (self.chroots_dir, self.full_name) shutil.rmtree(mount) + + def begin_session(self): + if not self.exists(): + raise ClickChrootException( + "Chroot %s does not exist" % self.full_name) + command = ["schroot", "-c", self.full_name, "--begin-session", + "--session-name", self.full_session_name] + subprocess.check_call(command) + + def end_session(self): + if not self.exists(): + raise ClickChrootException( + "Chroot %s does not exist" % self.full_name) + command = ["schroot", "-c", self.full_session_name, "--end-session"] + subprocess.check_call(command) diff -Nru click-0.4.18.3/click/commands/chroot.py click-0.4.19/click/commands/chroot.py --- click-0.4.18.3/click/commands/chroot.py 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/click/commands/chroot.py 2014-03-18 14:27:44.000000000 +0000 @@ -55,20 +55,28 @@ program = args.program if not program: program = ["/bin/bash"] - ClickChroot(args.architecture, args.framework).run(*program) + ClickChroot(args.architecture, args.framework, session=args.session).run(*program) def maint(parser, args): program = args.program if not program: program = ["/bin/bash"] - ClickChroot(args.architecture, args.framework).maint(*program) + ClickChroot(args.architecture, args.framework, session=args.session).maint(*program) def upgrade(parser, args): ClickChroot(args.architecture, args.framework).upgrade() +def begin_session(parser, args): + ClickChroot(args.architecture, args.framework, session=args.session).begin_session() + + +def end_session(parser, args): + ClickChroot(args.architecture, args.framework, session=args.session).end_session() + + def run(argv): parser = ArgumentParser("click chroot") subparsers = parser.add_subparsers( @@ -107,6 +115,10 @@ "run", help="run a program in the chroot") execute_parser.add_argument( + "-n", "--session-name", + dest='session', + help="persistent chroot session name to run a program in") + execute_parser.add_argument( "program", nargs=REMAINDER, help="program to run with arguments") execute_parser.set_defaults(func=execute) @@ -114,9 +126,27 @@ "maint", help="run a maintenance command in the chroot") maint_parser.add_argument( + "-n", "--session-name", + dest='session', + help="persistent chroot session name to run a maintenance command in") + maint_parser.add_argument( "program", nargs=REMAINDER, help="program to run with arguments") maint_parser.set_defaults(func=maint) + begin_parser = subparsers.add_parser( + "begin-session", + help="begin a persistent chroot session") + begin_parser.add_argument( + "session", + help="new session name") + begin_parser.set_defaults(func=begin_session) + end_parser = subparsers.add_parser( + "end-session", + help="end a persistent chroot session") + end_parser.add_argument( + "session", + help="session name to end") + end_parser.set_defaults(func=end_session) args = parser.parse_args(argv) if not hasattr(args, "func"): parser.print_help() diff -Nru click-0.4.18.3/click/commands/info.py click-0.4.19/click/commands/info.py --- click-0.4.18.3/click/commands/info.py 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/click/commands/info.py 2014-03-18 14:27:44.000000000 +0000 @@ -41,7 +41,12 @@ with closing(DebFile(filename=arg)) as package: with package.control.get_file( "manifest", encoding="UTF-8") as manifest_file: - return json.load(manifest_file) + manifest = json.load(manifest_file) + keys = list(manifest) + for key in keys: + if key.startswith("_"): + del manifest[key] + return manifest def run(argv): @@ -60,10 +65,6 @@ except Exception as e: print(e, file=sys.stderr) return 1 - keys = list(manifest) - for key in keys: - if key.startswith("_"): - del manifest[key] json.dump( manifest, sys.stdout, ensure_ascii=False, sort_keys=True, indent=4, separators=(",", ": ")) diff -Nru click-0.4.18.3/click/tests/test_user.py click-0.4.19/click/tests/test_user.py --- click-0.4.18.3/click/tests/test_user.py 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/click/tests/test_user.py 2014-03-18 14:27:44.000000000 +0000 @@ -311,6 +311,7 @@ json.dump(manifest_obj, manifest) manifest_obj["_directory"] = os.path.join( registry.get_overlay_db(), "a") + manifest_obj["_removable"] = 1 registry.set_version("a", "1.0") self.assertEqual( manifest_obj, json_object_to_python(registry.get_manifest("a"))) @@ -321,16 +322,19 @@ "name": "a", "version": "1.1", "_directory": os.path.join(user_dbs[1], "a"), + "_removable": 1, }, json_object_to_python(registry.get_manifest("a"))) self.assertEqual({ "name": "b", "version": "2.0", "_directory": os.path.join(user_dbs[0], "b"), + "_removable": 1, }, json_object_to_python(registry.get_manifest("b"))) self.assertEqual({ "name": "c", "version": "0.1", "_directory": os.path.join(user_dbs[1], "c"), + "_removable": 1, }, json_object_to_python(registry.get_manifest("c"))) self.assertRaisesUserError( Click.UserError.NO_SUCH_PACKAGE, registry.get_path, "d") diff -Nru click-0.4.18.3/debian/changelog click-0.4.19/debian/changelog --- click-0.4.18.3/debian/changelog 2014-03-12 12:02:47.000000000 +0000 +++ click-0.4.19/debian/changelog 2014-03-18 14:27:53.000000000 +0000 @@ -1,3 +1,33 @@ +click (0.4.19) trusty; urgency=medium + + [ Colin Watson ] + * Set Click.User.ensure_db visibility back to private, since it's no + longer used by Click.Hook. (The C ABI is unaffected.) + * Add brief documentation on Click's multiple-database scheme, based on my + recent mail to ubuntu-phone. + * Fix a few potential GLib critical messages from the PackageKit plugin. + * Make libclick-0.4-dev depend on libjson-glib-dev for + . + * Add Requires.private to click-0.4.pc, so that programs built against + libclick pick up the proper CFLAGS including glib and json-glib. + * chroot: Allow creating 14.04 chroots. + * Include _directory and _removable dynamic manifest keys in "click info" + output (LP: #1293788). + * Document -f and -s options to "click chroot" in click(1). + * chroot: Fix code to make /finish.sh executable. + * chroot: Make /usr/sbin/policy-rc.d executable in the chroot, as + otherwise it has no effect. + * chroot: Run apt-get dist-upgrade on the chroot before trying to install + the basic build tool set. Fixes chroot creation for saucy. + + [ Benjamin Zeller ] + * Take pkexec env vars into account when creating a chroot. + + [ Dimitri John Ledkov ] + * Add session management to click chroot. + + -- Ubuntu daily release Tue, 18 Mar 2014 14:27:53 +0000 + click (0.4.18.3) trusty; urgency=medium [ Colin Watson ] diff -Nru click-0.4.18.3/debian/control click-0.4.19/debian/control --- click-0.4.18.3/debian/control 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/debian/control 2014-03-18 14:27:44.000000000 +0000 @@ -66,7 +66,7 @@ Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} -Depends: ${shlibs:Depends}, ${misc:Depends}, libclick-0.4-0 (= ${binary:Version}), libglib2.0-dev +Depends: ${shlibs:Depends}, ${misc:Depends}, libclick-0.4-0 (= ${binary:Version}), libglib2.0-dev, libjson-glib-dev Description: development files for Click package management library Click is a simplified packaging format that installs in a separate part of the file system, suitable for third-party applications. diff -Nru click-0.4.18.3/doc/databases.rst click-0.4.19/doc/databases.rst --- click-0.4.18.3/doc/databases.rst 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.19/doc/databases.rst 2014-03-18 14:27:44.000000000 +0000 @@ -0,0 +1,65 @@ +========= +Databases +========= + +(This is a lightly-edited copy of a brain-dump sent by Colin Watson to the +ubuntu-phone mailing list, preserved here since it may be useful.) + +Click has multiple databases where packages may be unpacked: by default we +have the "core" database for core apps (``/usr/share/click/preinstalled/``), +the "custom" database for carrier/OEM customisations (``/custom/click/``), +and the "default" database for user-installed applications +(``/opt/click.ubuntu.com/``), although these are configurable in +``/etc/click/databases/``. Each database may have multiple unpacked +versions of any given package. + +Each database may also have user registrations, which live in +``.click/users/`` relative to the database root. Each user has a +subdirectory of that, which contains symlinks to the versions of each +package they have registered. This means that on a tablet, say, I can +install an app without it also showing up on my children's accounts; they'd +need to install it separately, although the disk space for the unpacked copy +of the app would be shared. + +There was an idea early on that we'd deal with preinstalled apps by going +round and registering them all for all active users on first boot. This +would have lots of problems for the packaging system, though. Most notably, +doing it that way makes it hard for a user to remove an app and make it +stick, because it would tend to reappear on system updates. You can +probably fudge your way around this somehow, but it gets very fiddly and +easy to get wrong. + +What we do instead is: we have an ``@all`` pseudo-user which you can +register packages for, typically in the core database (``click register +--root=/usr/share/click/preinstalled --all-users``). If a user wants to +remove a package, we do this by creating a deliberately broken symlink +pointing to ``@hidden`` in their user registration area in +``/opt/click.ubuntu.com/.click/users/$USERNAME/``. When click is asked to +list the set of packages for a given user, it walks its way down the list of +databases from top (default) to bottom (core). For each database, it checks +registrations for that user, followed by registrations for ``@all``. It +takes the first registration for any given package name that it finds. If +that registration is ``@hidden``, then it ignores the package, otherwise it +must be a link to the unpacked copy of the appropriate version of the +package. + +There are still some things that can't be done just with static files in the +image and instead have to be done at boot time and on session startup: we +have to make sure the right AppArmor profiles are loaded, do things to the +user's home directory like creating .desktop files, and that kind of thing. +We run ``click hook run-system`` at boot time and ``click hook run-user`` on +session startup, and these deal with running hooks for whatever packages are +visible in context, according to the rules above. + +The effect of all this is that we can hide a core app for a carrier by doing +this as root when preparing their custom overlay image:: + + click unregister --root=/custom/click --all-users PACKAGE-NAME + +This will create a symlink ``/custom/click/.click/users/@all/PACKAGE-NAME`` +pointing to ``@hidden``. Unless a user explicitly installs the app in +question, the effect of this will be that it's as if the app just isn't +there. It shouldn't incur any more than a negligible cost at startup +(basically just a readlink call); at the moment I think we might still +create an AppArmor profile for it, which isn't free, but that can be fixed +easily enough. diff -Nru click-0.4.18.3/doc/hooks.rst click-0.4.19/doc/hooks.rst --- click-0.4.18.3/doc/hooks.rst 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/doc/hooks.rst 2014-03-18 14:27:44.000000000 +0000 @@ -219,7 +219,7 @@ "hooks": { "example-app": { "apparmor": "apparmor/example-app.json", - "desktop": "example-app.desktop", + "desktop": "example-app.desktop" } } diff -Nru click-0.4.18.3/doc/index.rst click-0.4.19/doc/index.rst --- click-0.4.18.3/doc/index.rst 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/doc/index.rst 2014-03-18 14:27:44.000000000 +0000 @@ -84,6 +84,7 @@ file-format.rst constraints.rst hooks.rst + databases.rst todo.rst diff -Nru click-0.4.18.3/doc/manpage.rst click-0.4.19/doc/manpage.rst --- click-0.4.18.3/doc/manpage.rst 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/doc/manpage.rst 2014-03-18 14:27:44.000000000 +0000 @@ -97,27 +97,49 @@ Options: --a ARCH, --architecture ARCH Set the target architecture. +-a ARCH, --architecture ARCH Set the target architecture. +-f FRAMEWORK, --framework FRAMEWORK Set the target framework (default: + ubuntu-sdk-13.10). +-s SERIES, --series SERIES Set the target series for + newly-created chroots (default: a + series appropriate for the + framework). + This option is mainly for debugging; + use -f instead. Subcommands: +begin-session SESSION + Begin a persistent chroot session. + create Create a chroot. destroy Destroy a chroot. +end-session SESSION + End a persistent chroot session. + install PACKAGES Install packages in the chroot. -maint COMMAND ARGUMENTS +maint [-n SESSION] COMMAND ARGUMENTS Run a maintenance command in the chroot. Unlike ``run``, this runs its command as root inside the chroot, and its effects on the chroot will persist after ``click chroot maint`` exits. -run COMMAND ARGUMENTS + If a session name is given, run the command in that session. The + session must previously have been created by ``click chroot + begin-session``. + +run [-n SESSION] COMMAND ARGUMENTS Run a program in the chroot. + If a session name is given, run the command in that session. The + session must previously have been created by ``click chroot + begin-session``. + upgrade Upgrade the chroot. diff -Nru click-0.4.18.3/lib/click/click-0.4.pc.in click-0.4.19/lib/click/click-0.4.pc.in --- click-0.4.18.3/lib/click/click-0.4.pc.in 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/lib/click/click-0.4.pc.in 2014-03-18 14:27:44.000000000 +0000 @@ -23,5 +23,6 @@ Description: Click package manipulation library Version: @PACKAGE_VERSION@ URL: https://click.readthedocs.org/en/latest/ +Requires.private: glib-2.0 gobject-2.0 json-glib-1.0 Libs: -L${libdir} -lclick-0.4 Cflags: -I${includedir}/click-0.4 diff -Nru click-0.4.18.3/lib/click/user.vala click-0.4.19/lib/click/user.vala --- click-0.4.18.3/lib/click/user.vala 2014-03-12 12:02:37.000000000 +0000 +++ click-0.4.19/lib/click/user.vala 2014-03-18 14:27:44.000000000 +0000 @@ -301,7 +301,7 @@ return db_for_user (db.overlay, name); } - internal void + private void ensure_db () throws UserError { if (users == null) @@ -668,6 +668,12 @@ var obj = db.get_manifest (package, get_version (package)); /* Adjust _directory to point to the user registration path. */ obj.set_string_member ("_directory", get_path (package)); + /* This should really be a boolean, but it was mistakenly + * made an int when the "_removable" key was first created. + * We may change this in future. + */ + obj.set_int_member ("_removable", + is_removable (package) ? 1 : 0); return obj; } @@ -683,16 +689,8 @@ get_manifests () throws Error { var ret = new Json.Array (); - foreach (var package in get_package_names ()) { - var obj = get_manifest (package); - /* This should really be a boolean, but it was - * mistakenly made an int when the "_removable" key - * was first created. We may change this in future. - */ - obj.set_int_member ("_removable", - is_removable (package) ? 1 : 0); - ret.add_object_element (obj); - } + foreach (var package in get_package_names ()) + ret.add_object_element (get_manifest (package)); return ret; } diff -Nru click-0.4.18.3/pk-plugin/pk-plugin-click.c click-0.4.19/pk-plugin/pk-plugin-click.c --- click-0.4.18.3/pk-plugin/pk-plugin-click.c 2014-03-12 12:02:27.000000000 +0000 +++ click-0.4.19/pk-plugin/pk-plugin-click.c 2014-03-18 14:27:44.000000000 +0000 @@ -67,9 +67,8 @@ ret = TRUE; out: - if (info) - g_object_unref (info); - g_object_unref (file); + g_clear_object (&info); + g_clear_object (&file); return ret; } @@ -391,7 +390,7 @@ out: if (error) g_error_free (error); - g_object_unref (registry); + g_clear_object (®istry); g_free (username); return array; @@ -702,8 +701,8 @@ if (error) g_error_free (error); g_free (old_version); - g_object_unref (registry); - g_object_unref (db); + g_clear_object (®istry); + g_clear_object (&db); g_free (version); g_free (name); g_free (username);