Пример #1
0
def get_selections(config, options, iface_uri, select_only, download_only,
                   test_callback):
    """Get selections for iface_uri, according to the options passed.
	Will switch to GUI mode if necessary.
	@param options: options from OptionParser
	@param iface_uri: canonical URI of the interface
	@param select_only: return immediately even if the selected versions aren't cached
	@param download_only: wait for stale feeds, and display GUI button as Download, not Run
	@return: the selected versions, or None if the user cancels
	@rtype: L{selections.Selections} | None
	"""
    if options.offline:
        config.network_use = model.network_offline

    iface_cache = config.iface_cache

    # Try to load it as a feed. If it is a feed, it'll get cached. If not, it's a
    # selections document and we return immediately.
    maybe_selections = iface_cache.get_feed(iface_uri, selections_ok=True)
    if isinstance(maybe_selections, selections.Selections):
        if not select_only:
            blocker = maybe_selections.download_missing(config)
            if blocker:
                logger.info(
                    _("Waiting for selected implementations to be downloaded..."
                      ))
                tasks.wait_for_blocker(blocker)
        return maybe_selections

    r = requirements.Requirements(iface_uri)
    r.parse_options(options)

    return get_selections_for(r, config, options, select_only, download_only,
                              test_callback)
Пример #2
0
 def get_requirements(self):
     import json
     from zeroinstall.injector import requirements
     r = requirements.Requirements(None)
     reqs_file = os.path.join(self.path, 'requirements.json')
     with open(reqs_file, 'rt') as stream:
         values = json.load(stream)
     for k, v in values.items():
         setattr(r, k, v)
     return r
Пример #3
0
def handle(config, options, args):
	if len(args) != 2:
		raise UsageError()

	pet_name = args[0]
	iface_uri = model.canonical_iface_uri(args[1])

	sels = select.get_selections(config, options, iface_uri, select_only = False, download_only = True, test_callback = None)
	if not sels:
		sys.exit(1)	# Aborted by user

	r = requirements.Requirements(iface_uri)
	r.parse_options(options)

	app = config.app_mgr.create_app(pet_name, r)
	app.set_selections(sels)
	app.integrate_shell(pet_name)
Пример #4
0
    def testCommands(self):
        iface = os.path.join(mydir, "Command.xml")
        driver = Driver(requirements=Requirements(iface), config=self.config)
        driver.need_download()
        assert driver.solver.ready

        impl = driver.solver.selections[self.config.iface_cache.get_interface(
            iface)]
        assert impl.id == 'c'
        assert impl.main == 'test-gui'

        dep_impl_uri = impl.commands['run'].requires[0].interface
        dep_impl = driver.solver.selections[
            self.config.iface_cache.get_interface(dep_impl_uri)]
        assert dep_impl.id == 'sha1=256'

        s1 = driver.solver.selections
        assert s1.commands[0].path == 'test-gui'
        xml = s1.toDOM().toxml("utf-8")
        root = qdom.parse(BytesIO(xml))
        s2 = selections.Selections(root)

        assert s2.commands[0].path == 'test-gui'
        impl = s2.selections[iface]
        assert impl.id == 'c'

        assert s2.commands[0].qdom.attrs['http://custom attr'] == 'namespaced'
        custom_element = s2.commands[0].qdom.childNodes[0]
        assert custom_element.name == 'child'

        dep_impl = s2.selections[dep_impl_uri]
        assert dep_impl.id == 'sha1=256'

        d = Driver(self.config, requirements.Requirements(runexec))
        need_download = d.need_download()
        assert need_download == False

        xml = d.solver.selections.toDOM().toxml("utf-8")
        root = qdom.parse(BytesIO(xml))
        s3 = selections.Selections(root)
        runnable_impl = s3.selections[runnable]
        assert 'foo' in runnable_impl.commands
        assert 'run' in runnable_impl.commands
Пример #5
0
    def get_requirements(self):
        import json
        from zeroinstall.injector import requirements
        r = requirements.Requirements(None)
        reqs_file = os.path.join(self.path, 'requirements.json')
        with open(reqs_file, 'rt') as stream:
            values = json.load(stream)

        # Update old before/not-before values
        before = values.pop('before', None)
        not_before = values.pop('not_before', None)
        if before or not_before:
            assert not values.extra_restrictions
            expr = (not_before or '') + '..'
            if before:
                expr += '!' + before
            values['extra_restrictions'] = {values['interface_uri']: expr}

        for k, v in values.items():
            setattr(r, k, v)
        return r
Пример #6
0
def handle(config, options, args):
	"""@type args: [str]"""
	if len(args) != 2:
		raise UsageError()

	pet_name = args[0]
	iface_uri = model.canonical_iface_uri(args[1])

	sels = select.get_selections(config, options, iface_uri, select_only = False, download_only = True, test_callback = None)
	if not sels:
		sys.exit(1)	# Aborted by user

	root_feed = config.iface_cache.get_feed(iface_uri)
	if root_feed:
		target = root_feed.get_replaced_by()
		if target is not None:
			print(_("Warning: interface {old} has been replaced by {new}".format(old = iface_uri, new = target)))

	r = requirements.Requirements(iface_uri)
	r.parse_options(options)

	app = config.app_mgr.create_app(pet_name, r)
	app.set_selections(sels)
	app.integrate_shell(pet_name)
Пример #7
0
def handle(config, options, args):
    if len(args) != 1:
        raise UsageError()

    assert not options.offline

    old_gui = options.gui

    app = config.app_mgr.lookup_app(args[0], missing_ok=True)
    if app is not None:
        old_sels = app.get_selections()
        old_selections = old_sels.selections
        iface_uri = old_sels.interface
        r = app.get_requirements()
        r.parse_update_options(options)
    else:
        iface_uri = model.canonical_iface_uri(args[0])

        r = requirements.Requirements(iface_uri)
        r.parse_options(options)

        # Select once in offline console mode to get the old values
        options.offline = True
        options.gui = False
        options.refresh = False

        try:
            old_sels = select.get_selections_for(r,
                                                 config,
                                                 options,
                                                 select_only=True,
                                                 download_only=False,
                                                 test_callback=None)
        except SafeException:
            old_selections = {}
        else:
            if old_sels is None:
                old_selections = {}
            else:
                old_selections = old_sels.selections

    # Download in online mode to get the new values
    config.network_use = model.network_full
    options.offline = False
    options.gui = old_gui
    options.refresh = True

    sels = select.get_selections_for(r,
                                     config,
                                     options,
                                     select_only=False,
                                     download_only=True,
                                     test_callback=None)
    if not sels:
        sys.exit(1)  # Aborted by user

    root_feed = config.iface_cache.get_feed(iface_uri)
    if root_feed:
        target = root_feed.get_replaced_by()
        if target is not None:
            print(
                _("Warning: interface {old} has been replaced by {new}".format(
                    old=iface_uri, new=target)))

    from zeroinstall.cmd import whatchanged
    changes = whatchanged.show_changes(old_selections, sels.selections)
    root_sel = sels[iface_uri]

    if not changes:
        from zeroinstall.support import xmltools
        # No obvious changes, but check for more subtle updates.
        if not xmltools.nodes_equal(sels.toDOM(), old_sels.toDOM()):
            changes = True
            print(
                _("Updates to metadata found, but no change to version ({version})."
                  ).format(version=root_sel.version))

    root_iface = config.iface_cache.get_interface(iface_uri)
    # Force a reload, since we may have used the GUI to update it
    for feed in config.iface_cache.get_feeds(root_iface):
        config.iface_cache.get_feed(feed, force=True)

    root_impls = config.iface_cache.get_implementations(root_iface)

    latest = max((impl.version, impl) for impl in root_impls)[1]
    if latest.version > model.parse_version(sels[iface_uri].version):
        print(
            _("A later version ({name} {latest}) exists but was not selected. Using {version} instead."
              ).format(latest=latest.get_version(),
                       name=root_iface.get_name(),
                       version=root_sel.version))
        if not config.help_with_testing and latest.get_stability(
        ) < model.stable:
            print(
                _('To select "testing" versions, use:\n0install config help_with_testing True'
                  ))
    elif not changes:
        print(
            _("No updates found. Continuing with version {version}.").format(
                version=root_sel.version))

    if app is not None:
        if changes:
            app.set_selections(sels)
        app.set_requirements(r)
Пример #8
0
def run_test_combinations(config, spec):
    r = requirements.Requirements(spec.test_iface)
    r.command = spec.command

    d = driver.Driver(config=config, requirements=r)
    solver = d.solver

    # Explore all combinations...

    tested_iface = config.iface_cache.get_interface(spec.test_iface)
    results = Results(spec)
    for combo in spec.get_combos(spec.test_ifaces):
        key = set()
        restrictions = {}
        selections = {}
        for (uri, version) in combo.iteritems():
            iface = config.iface_cache.get_interface(uri)
            selections[iface] = version

            if version.startswith('%'):
                if version == '%nonlocal':
                    restrictions[iface] = [NonlocalRestriction()]
                else:
                    raise model.SafeException(
                        "Unknown special '{special}'".format(special=version))
            elif ',' in version:
                not_before, before = [
                    model.parse_version(v) if v != "" else None
                    for v in version.split(',')
                ]
                if (not_before and before) and not_before >= before:
                    raise model.SafeException(
                        "Low version >= high version in %s!" % version)
                restrictions[iface] = [
                    model.VersionRangeRestriction(before, not_before)
                ]
            else:
                restrictions[iface] = [
                    model.VersionExpressionRestriction(version)
                ]
            key.add((uri, version))

        solver.extra_restrictions = restrictions
        solve = d.solve_with_downloads()
        tasks.wait_for_blocker(solve)
        if not solver.ready:
            logging.info("Can't select combination %s: %s", combo,
                         solver.get_failure_reason())
            result = 'skipped'
            for uri, impl in solver.selections.iteritems():
                if impl is None:
                    selections[uri] = selections.get(uri, None) or '?'
                else:
                    selections[uri] = impl.get_version()
            if not selections:
                selections = solver.get_failure_reason()
        else:
            selections = {}
            for iface, impl in solver.selections.iteritems():
                if impl:
                    version = impl.get_version()
                else:
                    impl = None
                selections[iface] = version
            download = d.download_uncached_implementations()
            if download:
                config.handler.wait_for_blocker(download)

            print format_combo(selections)

            result = run_tests(config, tested_iface, solver.selections, spec)

        results.by_status[result].append(selections)
        results.by_combo[frozenset(key)] = (result, selections)

    return results
Пример #9
0
    def recursive_build(self, iface_uri, source_impl_id=None):
        """Build an implementation of iface_uri and register it as a feed.
		@param source_impl_id: the version to build, or None to build any version
		@type source_impl_id: str
		"""
        r = requirements.Requirements(iface_uri)
        r.source = True
        r.command = 'compile'

        d = driver.Driver(self.config, r)
        iface = self.config.iface_cache.get_interface(iface_uri)
        d.solver.record_details = True
        if source_impl_id is not None:
            d.solver.extra_restrictions[iface] = [
                ImplRestriction(source_impl_id)
            ]

        # For testing...
        #p.target_arch = arch.Architecture(os_ranks = {'FreeBSD': 0, None: 1}, machine_ranks = {'i386': 0, None: 1, 'newbuild': 2})

        while True:
            self.heading(iface_uri)
            self.note("\nSelecting versions for %s..." % iface.get_name())
            solved = d.solve_with_downloads()
            if solved:
                yield solved
                tasks.check(solved)

            if not d.solver.ready:
                self.print_details(d.solver)
                raise d.solver.get_failure_reason()
            self.note("Selection done.")

            self.note("\nPlan:\n")
            self.pretty_print_plan(d.solver, r.interface_uri)
            self.note('')

            needed = []
            for dep_iface_uri, dep_sel in d.solver.selections.selections.iteritems(
            ):
                if dep_sel.id.startswith('0compile='):
                    if not needed:
                        self.note(
                            "Build dependencies that need to be compiled first:\n"
                        )
                    self.note("- {iface} {version}".format(
                        iface=dep_iface_uri, version=dep_sel.version))
                    needed.append((dep_iface_uri, dep_sel))

            if not needed:
                self.note(
                    "No dependencies need compiling... compile %s itself..." %
                    iface.get_name())
                build = self.compile_and_register(
                    d.solver.selections,
                    # force the interface in the recursive case
                    iface_uri if iface_uri != self.iface_uri else None)
                yield build
                tasks.check(build)
                return

            # Compile the first missing build dependency...
            dep_iface_uri, dep_sel = needed[0]

            self.note("")

            #details = d.solver.details[self.config.iface_cache.get_interface(dep_iface.uri)]
            #for de in details:
            #	print de

            dep_source_id = dep_sel.id.split('=', 1)[1]
            seen_key = (dep_iface_uri, dep_source_id)
            if seen_key in self.seen:
                self.note_error(
                    "BUG: Stuck in an auto-compile loop: already built {key}!".
                    format(key=seen_key))
                # Try to find out why the previous build couldn't be used...
                dep_iface = self.config.iface_cache.get_interface(
                    dep_iface_uri)
                previous_build = self.seen[seen_key]
                previous_build_feed = os.path.join(previous_build, '0install',
                                                   'feed.xml')
                previous_feed = self.config.iface_cache.get_feed(
                    previous_build_feed)
                previous_binary_impl = previous_feed.implementations.values(
                )[0]
                raise SafeException(
                    "BUG: auto-compile loop: expected to select previously-build binary {binary}:\n\n{reason}"
                    .format(binary=previous_binary_impl,
                            reason=d.solver.justify_decision(
                                r, dep_iface, previous_binary_impl)))

            build = self.recursive_build(dep_iface_uri, dep_source_id)
            yield build
            tasks.check(build)

            assert seen_key in self.seen, (seen_key, self.seen
                                           )  # Must have been built by now
Пример #10
0
def get_selections(config, options, iface_uri, select_only, download_only, test_callback):
	"""Get selections for iface_uri, according to the options passed.
	Will switch to GUI mode if necessary.
	@param options: options from OptionParser
	@param iface_uri: canonical URI of the interface
	@param select_only: return immediately even if the selected versions aren't cached
	@param download_only: wait for stale feeds, and display GUI button as Download, not Run
	@return: the selected versions, or None if the user cancels
	@rtype: L{selections.Selections} | None
	"""
	if options.offline:
		config.network_use = model.network_offline

	iface_cache = config.iface_cache

	# Try to load it as a feed. If it is a feed, it'll get cached. If not, it's a
	# selections document and we return immediately.
	maybe_selections = iface_cache.get_feed(iface_uri, selections_ok = True)
	if isinstance(maybe_selections, selections.Selections):
		if not select_only:
			blocker = maybe_selections.download_missing(config)
			if blocker:
				logging.info(_("Waiting for selected implementations to be downloaded..."))
				tasks.wait_for_blocker(blocker)
		return maybe_selections

	r = requirements.Requirements(iface_uri)
	r.parse_options(options)

	policy = Policy(config = config, requirements = r)

	# Note that need_download() triggers a solve
	if options.refresh or options.gui:
		# We could run immediately, but the user asked us not to
		can_run_immediately = False
	else:
		if select_only:
			# --select-only: we only care that we've made a selection, not that we've cached the implementations
			policy.need_download()
			can_run_immediately = policy.ready
		else:
			can_run_immediately = not policy.need_download()

		stale_feeds = [feed for feed in policy.solver.feeds_used if
				not os.path.isabs(feed) and			# Ignore local feeds (note: file might be missing too)
				not feed.startswith('distribution:') and	# Ignore (memory-only) PackageKit feeds
				iface_cache.is_stale(iface_cache.get_feed(feed), config.freshness)]

		if download_only and stale_feeds:
			can_run_immediately = False

	if can_run_immediately:
		if stale_feeds:
			if policy.network_use == model.network_offline:
				logging.debug(_("No doing background update because we are in off-line mode."))
			else:
				# There are feeds we should update, but we can run without them.
				# Do the update in the background while the program is running.
				from zeroinstall.injector import background
				background.spawn_background_update(policy, options.verbose > 0)
		return policy.solver.selections

	# If the user didn't say whether to use the GUI, choose for them.
	if options.gui is None and os.environ.get('DISPLAY', None):
		options.gui = True
		# If we need to download anything, we might as well
		# refresh all the feeds first.
		options.refresh = True
		logging.info(_("Switching to GUI mode... (use --console to disable)"))

	if options.gui:
		gui_args = policy.requirements.get_as_options()
		if download_only:
			# Just changes the button's label
			gui_args.append('--download-only')
		if options.refresh:
			gui_args.append('--refresh')
		if options.verbose:
			gui_args.insert(0, '--verbose')
			if options.verbose > 1:
				gui_args.insert(0, '--verbose')
		if options.with_store:
			for x in options.with_store:
				gui_args += ['--with-store', x]
		if select_only:
			gui_args.append('--select-only')

		from zeroinstall import helpers
		sels = helpers.get_selections_gui(iface_uri, gui_args, test_callback)

		if not sels:
			return None		# Aborted
	else:
		# Note: --download-only also makes us stop and download stale feeds first.
		downloaded = policy.solve_and_download_impls(refresh = options.refresh or download_only or False,
							     select_only = select_only)
		if downloaded:
			tasks.wait_for_blocker(downloaded)
		sels = selections.Selections(policy)

	return sels
Пример #11
0
def run_gui(args):
    parser = OptionParser(usage=_("usage: %prog [options] interface"))
    parser.add_option("",
                      "--before",
                      help=_("choose a version before this"),
                      metavar='VERSION')
    parser.add_option("", "--cpu", help=_("target CPU type"), metavar='CPU')
    parser.add_option("",
                      "--command",
                      help=_("command to select"),
                      metavar='COMMAND')
    parser.add_option("-d",
                      "--download-only",
                      help=_("fetch but don't run"),
                      action='store_true')
    parser.add_option("",
                      "--message",
                      help=_("message to display when interacting with user"))
    parser.add_option("",
                      "--not-before",
                      help=_("minimum version to choose"),
                      metavar='VERSION')
    parser.add_option("",
                      "--os",
                      help=_("target operation system type"),
                      metavar='OS')
    parser.add_option("-r",
                      "--refresh",
                      help=_("check for updates of all interfaces"),
                      action='store_true')
    parser.add_option("",
                      "--select-only",
                      help=_("only download the feeds"),
                      action='store_true')
    parser.add_option("-s",
                      "--source",
                      help=_("select source code"),
                      action='store_true')
    parser.add_option("",
                      "--systray",
                      help=_("download in the background"),
                      action='store_true')
    parser.add_option("-v",
                      "--verbose",
                      help=_("more verbose output"),
                      action='count')
    parser.add_option("-V",
                      "--version",
                      help=_("display version information"),
                      action='store_true')
    parser.add_option("",
                      "--with-store",
                      help=_("add an implementation cache"),
                      action='append',
                      metavar='DIR')

    parser.disable_interspersed_args()

    (options, args) = parser.parse_args(args)

    if options.verbose:
        import logging
        logger = logging.getLogger()
        if options.verbose == 1:
            logger.setLevel(logging.INFO)
        else:
            logger.setLevel(logging.DEBUG)

    import gui

    if options.version:
        print "0launch-gui (zero-install) " + gui.version
        print "Copyright (C) 2010 Thomas Leonard"
        print _(
            "This program comes with ABSOLUTELY NO WARRANTY,"
            "\nto the extent permitted by law."
            "\nYou may redistribute copies of this program"
            "\nunder the terms of the GNU Lesser General Public License."
            "\nFor more information about these matters, see the file named COPYING."
        )
        sys.exit(0)

    import gtk
    if gtk.gdk.get_display() is None:
        print >> sys.stderr, "Failed to connect to display. Aborting."
        sys.exit(1)

    handler = gui.GUIHandler()

    config = load_config(handler)

    if options.with_store:
        from zeroinstall import zerostore
        for x in options.with_store:
            config.stores.stores.append(zerostore.Store(os.path.abspath(x)))

    if len(args) < 1:
        import preferences
        box = preferences.show_preferences(config)
        box.connect('destroy', gtk.main_quit)
        gtk.main()
        sys.exit(0)

    interface_uri = args[0]

    if len(args) > 1:
        parser.print_help()
        sys.exit(1)

    import mainwindow, dialog

    r = requirements.Requirements(interface_uri)
    r.parse_options(options)

    widgets = dialog.Template('main')

    policy = Policy(config=config, requirements=r)
    root_iface = config.iface_cache.get_interface(interface_uri)
    policy.solver.record_details = True

    window = mainwindow.MainWindow(policy,
                                   widgets,
                                   download_only=bool(options.download_only),
                                   select_only=bool(options.select_only))
    handler.mainwindow = window

    if options.message:
        window.set_message(options.message)

    root = config.iface_cache.get_interface(policy.root)
    window.browser.set_root(root)

    window.window.connect('destroy', lambda w: handler.abort_all_downloads())

    if options.systray:
        window.use_systray_icon()

    @tasks. async
    def main():
        force_refresh = bool(options.refresh)
        while True:
            window.refresh_button.set_sensitive(False)
            window.browser.set_update_icons(force_refresh)

            solved = policy.solve_with_downloads(force=force_refresh,
                                                 update_local=True)

            if not window.systray_icon:
                window.show()
            yield solved
            try:
                window.refresh_button.set_sensitive(True)
                window.browser.highlight_problems()
                tasks.check(solved)
            except Exception as ex:
                window.report_exception(ex)

            if window.systray_icon and window.systray_icon.get_visible() and \
               window.systray_icon.is_embedded():
                if policy.ready:
                    window.systray_icon.set_tooltip(
                        _('Downloading updates for %s') %
                        root_iface.get_name())
                    window.run_button.set_active(True)
                else:
                    # Should already be reporting an error, but
                    # blink it again just in case
                    window.systray_icon.set_blinking(True)

            refresh_clicked = dialog.ButtonClickedBlocker(
                window.refresh_button)
            yield refresh_clicked, _recalculate

            if refresh_clicked.happened:
                force_refresh = True

    tasks.wait_for_blocker(main())
Пример #12
0
def run_gui(args):
    parser = OptionParser(usage=_("usage: %prog [options] interface"))
    parser.add_option("",
                      "--before",
                      help=_("choose a version before this"),
                      metavar='VERSION')
    parser.add_option("", "--cpu", help=_("target CPU type"), metavar='CPU')
    parser.add_option("",
                      "--command",
                      help=_("command to select"),
                      metavar='COMMAND')
    parser.add_option("-d",
                      "--download-only",
                      help=_("fetch but don't run"),
                      action='store_true')
    parser.add_option("-g",
                      "--force-gui",
                      help=_("display an error if there's no GUI"),
                      action='store_true')
    parser.add_option("",
                      "--message",
                      help=_("message to display when interacting with user"))
    parser.add_option("",
                      "--not-before",
                      help=_("minimum version to choose"),
                      metavar='VERSION')
    parser.add_option("",
                      "--os",
                      help=_("target operation system type"),
                      metavar='OS')
    parser.add_option("-r",
                      "--refresh",
                      help=_("check for updates of all interfaces"),
                      action='store_true')
    parser.add_option("",
                      "--select-only",
                      help=_("only download the feeds"),
                      action='store_true')
    parser.add_option("-s",
                      "--source",
                      help=_("select source code"),
                      action='store_true')
    parser.add_option("",
                      "--systray",
                      help=_("download in the background"),
                      action='store_true')
    parser.add_option("-v",
                      "--verbose",
                      help=_("more verbose output"),
                      action='count')
    parser.add_option("-V",
                      "--version",
                      help=_("display version information"),
                      action='store_true')
    parser.add_option(
        "",
        "--version-for",
        help=_("set version constraints for a specific interface"),
        nargs=2,
        metavar='URI RANGE',
        action='append')
    parser.add_option("",
                      "--with-store",
                      help=_("add an implementation cache"),
                      action='append',
                      metavar='DIR')

    parser.disable_interspersed_args()

    (options, args) = parser.parse_args(args)

    if options.verbose:
        logger = logging.getLogger()
        if options.verbose == 1:
            logger.setLevel(logging.INFO)
        else:
            logger.setLevel(logging.DEBUG)

    if options.version:
        import gui
        print("0launch-gui (zero-install) " + gui.version)
        print("Copyright (C) 2010 Thomas Leonard")
        print(
            _("This program comes with ABSOLUTELY NO WARRANTY,"
              "\nto the extent permitted by law."
              "\nYou may redistribute copies of this program"
              "\nunder the terms of the GNU Lesser General Public License."
              "\nFor more information about these matters, see the file named COPYING."
              ))
        sys.exit(0)

    def nogui(ex):
        if options.force_gui:
            fn = logging.warn
        else:
            fn = logging.info
            fn("No GUI available", exc_info=ex)
        sys.exit(100)

    with warnings.catch_warnings():
        if not options.force_gui:
            warnings.filterwarnings("ignore")
        if sys.version_info[0] < 3:
            try:
                import pygtk
                pygtk.require('2.0')
            except ImportError as ex:
                nogui(ex)

        import gui

        try:
            if sys.version_info[0] > 2:
                from zeroinstall.gtkui import pygtkcompat
                pygtkcompat.enable()
                pygtkcompat.enable_gtk(version='3.0')
            import gtk
        except (ImportError, ValueError, RuntimeError) as ex:
            nogui(ex)

        if gtk.gdk.get_display() is None:
            try:
                raise SafeException("Failed to connect to display.")
            except SafeException as ex:
                nogui(ex)  # logging needs this as a raised exception

    handler = gui.GUIHandler()

    config = load_config(handler)

    if options.with_store:
        from zeroinstall import zerostore
        for x in options.with_store:
            config.stores.stores.append(zerostore.Store(os.path.abspath(x)))

    if len(args) < 1:

        @tasks. async
        def prefs_main():
            import preferences
            box = preferences.show_preferences(config)
            done = tasks.Blocker('close preferences')
            box.connect('destroy', lambda w: done.trigger())
            yield done

        tasks.wait_for_blocker(prefs_main())
        sys.exit(0)

    interface_uri = args[0]

    if len(args) > 1:
        parser.print_help()
        sys.exit(1)

    import mainwindow, dialog

    r = requirements.Requirements(interface_uri)
    r.parse_options(options)

    widgets = dialog.Template('main')

    driver = Driver(config=config, requirements=r)
    root_iface = config.iface_cache.get_interface(interface_uri)
    driver.solver.record_details = True

    window = mainwindow.MainWindow(driver,
                                   widgets,
                                   download_only=bool(options.download_only),
                                   select_only=bool(options.select_only))
    handler.mainwindow = window

    if options.message:
        window.set_message(options.message)

    root = config.iface_cache.get_interface(r.interface_uri)
    window.browser.set_root(root)

    window.window.connect('destroy', lambda w: handler.abort_all_downloads())

    if options.systray:
        window.use_systray_icon()

    @tasks. async
    def main():
        force_refresh = bool(options.refresh)
        while True:
            window.refresh_button.set_sensitive(False)
            window.browser.set_update_icons(force_refresh)

            solved = driver.solve_with_downloads(force=force_refresh,
                                                 update_local=True)

            if not window.systray_icon:
                window.show()
            yield solved
            try:
                window.refresh_button.set_sensitive(True)
                window.browser.highlight_problems()
                tasks.check(solved)
            except Exception as ex:
                window.report_exception(ex)

            if window.systray_icon and window.systray_icon.get_visible() and \
               window.systray_icon.is_embedded():
                if driver.solver.ready:
                    window.systray_icon.set_tooltip(
                        _('Downloading updates for %s') %
                        root_iface.get_name())
                    window.run_button.set_active(True)
                else:
                    # Should already be reporting an error, but
                    # blink it again just in case
                    window.systray_icon.set_blinking(True)

            refresh_clicked = dialog.ButtonClickedBlocker(
                window.refresh_button)
            yield refresh_clicked, _recalculate

            if refresh_clicked.happened:
                force_refresh = True

    tasks.wait_for_blocker(main())