def testCommands(self):
        iface = os.path.join(mydir, "Command.xml")
        p = policy.Policy(iface, config=self.config)
        p.need_download()
        assert p.ready

        impl = p.solver.selections[self.config.iface_cache.get_interface(
            iface)]
        assert impl.id == 'c'
        assert impl.main == 'runnable/missing'

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

        s1 = selections.Selections(p)
        assert s1.commands[0].path == 'runnable/missing'
        xml = s1.toDOM().toxml("utf-8")
        root = qdom.parse(StringIO(xml))
        s2 = selections.Selections(root)

        assert s2.commands[0].path == 'runnable/missing'
        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'
Exemple #2
0
	def get_selections(self, prompt = False):
		if self._selections:
			assert not prompt
			return self._selections

		selections_file = self.config.get('compile', 'selections')
		if selections_file:
			if prompt:
				raise SafeException("Selections are fixed by %s" % selections_file)
			stream = file(selections_file)
			try:
				self._selections = selections.Selections(qdom.parse(stream))
			finally:
				stream.close()
			from zeroinstall.injector import handler
			from zeroinstall.injector.config import load_config
			if os.isatty(1):
				h = handler.ConsoleHandler()
			else:
				h = handler.Handler()
			config = load_config(h)
			blocker = self._selections.download_missing(config)
			if blocker:
				print "Waiting for selected implementations to be downloaded..."
				h.wait_for_blocker(blocker)
		else:
			command = install_prog + ['download', '--source', '--xml']
			if prompt and '--console' not in install_prog:
				if os.name == 'nt':
					command[0] += '-win'
				command.append('--gui')
			command.append(self.interface)
			child = subprocess.Popen(command, stdout = subprocess.PIPE)
			try:
				self._selections = selections.Selections(qdom.parse(child.stdout))
			finally:
				if child.wait():
					raise SafeException(' '.join(repr(x) for x in command) + " failed (exit code %d)" % child.returncode)

		self.root_impl = self._selections.selections[self.interface]

		self.orig_srcdir = os.path.realpath(lookup(self.root_impl))
		self.user_srcdir = None

		if os.path.isdir('src'):
			self.user_srcdir = os.path.realpath('src')
			if self.user_srcdir == self.orig_srcdir or \
			   self.user_srcdir.startswith(os.path.join(self.orig_srcdir, '')) or \
			   self.orig_srcdir.startswith(os.path.join(self.user_srcdir, '')):
				info("Ignoring 'src' directory because it coincides with %s",
					self.orig_srcdir)
				self.user_srcdir = None

		return self._selections
Exemple #3
0
    def testOldCommands(self):
        command_feed = os.path.join(mydir, 'old-selections.xml')
        with open(command_feed, 'rb') as stream:
            s1 = selections.Selections(qdom.parse(stream))
        self.assertEqual("run", s1.command)
        self.assertEqual(2, len(s1.commands))
        self.assertEqual("bin/java", s1.commands[1].path)

        xml = s1.toDOM().toxml("utf-8")
        root = qdom.parse(BytesIO(xml))
        s2 = selections.Selections(root)

        self.assertEqual("run", s2.command)
        self.assertEqual(2, len(s2.commands))
        self.assertEqual("bin/java", s2.commands[1].path)
Exemple #4
0
    def get_selections(self,
                       snapshot_date=None,
                       may_update=False,
                       use_gui=None):
        """Load the selections.
		If may_update is True then the returned selections will be cached and available.
		@param snapshot_date: get a historical snapshot
		@type snapshot_date: (as returned by L{get_history}) | None
		@param may_update: whether to check for updates
		@type may_update: bool
		@param use_gui: whether to use the GUI for foreground updates
		@type use_gui: bool | None (never/always/if possible)
		@return: the selections
		@rtype: L{selections.Selections}"""
        if snapshot_date:
            assert may_update is False, "Can't update a snapshot!"
            sels_file = os.path.join(self.path,
                                     'selections-' + snapshot_date + '.xml')
        else:
            sels_file = os.path.join(self.path, 'selections.xml')

        try:
            with open(sels_file, 'rb') as stream:
                sels = selections.Selections(qdom.parse(stream))
        except IOError as ex:
            if may_update and ex.errno == errno.ENOENT:
                logger.info("App selections missing: %s", ex)
                sels = None
            else:
                raise

        if may_update:
            sels = self._check_for_updates(sels, use_gui)

        return sels
Exemple #5
0
def handle(config, options, args):
    if len(args) != 1:
        raise UsageError()

    app = config.app_mgr.lookup_app(args[0], missing_ok=True)
    if app is not None:
        sels = app.get_selections()

        r = app.get_requirements()

        if r.extra_restrictions and not options.xml:
            print("User-provided restrictions in force:")
            for uri, expr in r.extra_restrictions.items():
                print("  {uri}: {expr}".format(uri=uri, expr=expr))
            print()
    elif os.path.exists(args[0]):
        with open(args[0], 'rb') as stream:
            sels = selections.Selections(qdom.parse(stream))
    else:
        raise SafeException(_("Neither an app nor a file: '%s'") % args[0])

    if options.root_uri:
        print(sels.interface)
    elif options.xml:
        select.show_xml(sels)
    else:
        select.show_human(sels, config.stores)
def ensure_cached(uri, command='run', config=None):
    """Ensure that an implementation of uri is cached.
	If not, it downloads one. It uses the GUI if a display is
	available, or the console otherwise.
	@param uri: the required interface
	@type uri: str
	@return: the selected implementations, or None if the user cancelled
	@rtype: L{zeroinstall.injector.selections.Selections}
	"""
    from zeroinstall.injector import policy, selections

    if config is None:
        from zeroinstall.injector.config import load_config
        config = load_config()
    p = policy.Policy(uri, command=command, config=config)
    p.freshness = 0  # Don't check for updates

    if p.need_download() or not p.ready:
        if os.environ.get('DISPLAY', None):
            return get_selections_gui(uri, ['--command', command])
        else:
            done = p.solve_and_download_impls()
            tasks.wait_for_blocker(done)

    return selections.Selections(p)
def load_feed(source, local=False, selections_ok=False):
    """Load a feed from a local file.
	@param source: the name of the file to read
	@type source: str
	@param local: this is a local feed
	@type local: bool
	@param selections_ok: if it turns out to be a local selections document, return that instead
	@type selections_ok: bool
	@raise InvalidInterface: if the source's syntax is incorrect
	@return: the new feed
	@since: 0.48
	@see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
    try:
        root = qdom.parse(open(source))
    except IOError as ex:
        if ex.errno == errno.ENOENT and local:
            raise MissingLocalFeed(
                _("Feed not found. Perhaps this is a local feed that no longer exists? You can remove it from the list of feeds in that case."
                  ))
        raise InvalidInterface(_("Can't read file"), ex)
    except Exception as ex:
        raise InvalidInterface(_("Invalid XML"), ex)

    if local:
        if selections_ok and root.uri == XMLNS_IFACE and root.name == 'selections':
            from zeroinstall.injector import selections
            return selections.Selections(root)
        local_path = source
    else:
        local_path = None
    feed = ZeroInstallFeed(root, local_path)
    feed.last_modified = int(os.stat(source).st_mtime)
    return feed
Exemple #8
0
    def testSelect(self):
        out, err = self.run_0install(['select'])
        assert out.lower().startswith("usage:")
        assert '--xml' in out

        out, err = self.run_0install(['select', 'Local.xml'])
        assert not err, err
        assert 'Version: 0.1' in out

        out, err = self.run_0install(['select', 'Local.xml', '--command='])
        assert not err, err
        assert 'Version: 0.1' in out

        local_uri = model.canonical_iface_uri('Local.xml')
        out, err = self.run_0install(['select', 'Local.xml'])
        assert not err, err
        assert 'Version: 0.1' in out

        out, err = self.run_0install(['select', 'Local.xml', '--xml'])
        sels = selections.Selections(
            qdom.parse(BytesIO(str(out).encode('utf-8'))))
        assert sels.selections[local_uri].version == '0.1'

        out, err = self.run_0install(['select', 'selections.xml'])
        assert not err, err
        assert 'Version: 1\n' in out
        assert '(not cached)' in out

        out, err = self.run_0install(['select', 'runnable/RunExec.xml'])
        assert not err, err
        assert 'Runner' in out, out
    def testSelections(self):
        from zeroinstall.injector import cli
        root = qdom.parse(file("selections.xml"))
        sels = selections.Selections(root)

        class Options:
            dry_run = False

        with output_suppressed():
            self.child = server.handle_requests(
                'Hello.xml', '6FCF121BE2390E0B.gpg',
                '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B',
                'HelloWorld.tgz')
            sys.stdin = Reply("Y\n")
            try:
                self.config.stores.lookup_any(
                    sels.selections['http://example.com:8000/Hello.xml'].
                    digests)
                assert False
            except NotStored:
                pass
            cli.main(['--download-only', 'selections.xml'], config=self.config)
            path = self.config.stores.lookup_any(
                sels.selections['http://example.com:8000/Hello.xml'].digests)
            assert os.path.exists(os.path.join(path, 'HelloWorld', 'main'))

            assert sels.download_missing(self.config) is None
Exemple #10
0
def report_bug(policy, iface):
    assert iface

    # TODO: Check the interface to decide where to send bug reports

    issue_file = '/etc/issue'
    if os.path.exists(issue_file):
        issue = file(issue_file).read().strip()
    else:
        issue = "(file '%s' not found)" % issue_file

    text = 'Problem with %s\n' % iface.uri
    if iface.uri != policy.root:
        text = '  (while attempting to run %s)\n' % policy.root
    text += '\n'

    text += 'Zero Install: Version %s, with Python %s\n' % (
        zeroinstall.version, sys.version)

    text += '\nChosen implementations:\n'

    if not policy.ready:
        text += '  Failed to select all required implementations\n'

    for chosen_iface_uri, impl in policy.solver.selections.selections.iteritems(
    ):
        text += '\n  Interface: %s\n' % chosen_iface_uri
        if impl:
            text += '    Version: %s\n' % impl.version
            feed_url = impl.attrs['from-feed']
            if feed_url != chosen_iface_uri:
                text += '  From feed: %s\n' % feed_url
            text += '         ID: %s\n' % impl.id
        else:
            chosen_iface = policy.config.iface_cache.get_interface(
                chosen_iface_uri)
            impls = policy.solver.details.get(chosen_iface, None)
            if impls:
                best, reason = impls[0]
                note = 'best was %s, but: %s' % (best, reason)
            else:
                note = 'not considered; %d available' % len(
                    chosen_iface.implementations)

            text += '    No implementation selected (%s)\n' % note

    if hasattr(os, 'uname'):
        text += '\nSystem:\n  %s\n\nIssue:\n  %s\n' % ('\n  '.join(
            os.uname()), issue)
    else:
        text += '\nSystem without uname()\n'

    if policy.solver.ready:
        sels = selections.Selections(policy)
        text += "\n" + sels.toDOM().toprettyxml(encoding='utf-8')

    reporter = BugReporter(policy, iface, text)
    reporter.show()
Exemple #11
0
	def load_built_selections(self):
		path = join(self.metadir, 'build-environment.xml')
		if os.path.exists(path):
			stream = file(path)
			try:
				return selections.Selections(qdom.parse(stream))
			finally:
				stream.close()
		return None
Exemple #12
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
Exemple #13
0
    def testLocalPath(self):
        # 0launch --get-selections Local.xml
        iface = os.path.join(mydir, "Local.xml")
        driver = Driver(requirements=Requirements(iface), config=self.config)
        driver.need_download()
        assert driver.solver.ready
        s1 = driver.solver.selections
        xml = s1.toDOM().toxml("utf-8")

        # Reload selections and check they're the same
        root = qdom.parse(BytesIO(xml))
        s2 = selections.Selections(root)
        local_path = s2.selections[iface].local_path
        assert os.path.isdir(local_path), local_path
        assert not s2.selections[iface].digests, s2.selections[iface].digests

        # Add a newer implementation and try again
        feed = self.config.iface_cache.get_feed(iface)
        impl = model.ZeroInstallImplementation(feed,
                                               "foo bar=123",
                                               local_path=None)
        impl.version = model.parse_version('1.0')
        impl.commands["run"] = model.Command(
            qdom.Element(namespaces.XMLNS_IFACE, 'command', {
                'path': 'dummy',
                'name': 'run'
            }), None)
        impl.add_download_source('http://localhost/bar.tgz', 1000, None)
        feed.implementations = {impl.id: impl}
        assert driver.need_download()
        assert driver.solver.ready, driver.solver.get_failure_reason()
        s1 = driver.solver.selections
        xml = s1.toDOM().toxml("utf-8")
        root = qdom.parse(BytesIO(xml))
        s2 = selections.Selections(root)
        xml = s2.toDOM().toxml("utf-8")
        qdom.parse(BytesIO(xml))
        assert s2.selections[iface].local_path is None
        assert not s2.selections[iface].digests, s2.selections[iface].digests
        assert s2.selections[iface].id == 'foo bar=123'
Exemple #14
0
    def collect_output(self, buffer):
        iter = buffer.get_end_iter()
        buffer.place_cursor(iter)

        if not self.policy.ready:
            missing = [
                iface.uri for iface in self.policy.implementation
                if self.policy.implementation[iface] is None
            ]
            buffer.insert_at_cursor(
                "Can't run: no version has been selected for:\n- " +
                "\n- ".join(missing))
            return
        uncached = self.policy.get_uncached_implementations()
        if uncached:
            buffer.insert_at_cursor(
                "Can't run: the chosen versions have not been downloaded yet. I need:\n\n- "
                + "\n\n- ".join([
                    '%s version %s\n  (%s)' %
                    (x[0].uri, x[1].get_version(), x[1].id) for x in uncached
                ]))
            return

        from zeroinstall.injector import selections
        sels = selections.Selections(self.policy)
        doc = sels.toDOM()

        self.hide()
        try:
            gtk.gdk.flush()
            iter = buffer.get_end_iter()
            buffer.place_cursor(iter)

            # Tell 0launch to run the program
            doc.documentElement.setAttribute('run-test', 'true')
            payload = doc.toxml('utf-8')
            sys.stdout.write(('Length:%8x\n' % len(payload)) + payload)
            sys.stdout.flush()

            reply = support.read_bytes(0, len('Length:') + 9)
            assert reply.startswith('Length:')
            test_output = support.read_bytes(0,
                                             int(reply.split(':', 1)[1], 16))

            # Cope with invalid UTF-8
            import codecs
            decoder = codecs.getdecoder('utf-8')
            data = decoder(test_output, 'replace')[0]

            buffer.insert_at_cursor(data)
        finally:
            self.show()
Exemple #15
0
    def get_selections(self, snapshot_date=None):
        """Load the selections. Does not check whether they are cached, nor trigger updates.
		@param snapshot_date: get a historical snapshot
		@type snapshot_date: (as returned by L{get_history}) | None
		@return: the selections
		@rtype: L{selections.Selections}"""
        if snapshot_date:
            sels_file = os.path.join(self.path,
                                     'selections-' + snapshot_date + '.xml')
        else:
            sels_file = os.path.join(self.path, 'selections.xml')
        with open(sels_file, 'rb') as stream:
            return selections.Selections(qdom.parse(stream))
Exemple #16
0
    def testSelectOnly(self):
        os.environ['DISPLAY'] = ':foo'
        out, err = self.run_0install(['select', '--xml', 'Hello.xml'])
        self.assertEqual("", err)

        assert out.endswith("Finished\n"), out
        out = out[:-len("Finished\n")]

        root = qdom.parse(BytesIO(str(out).encode('utf-8')))
        self.assertEqual(namespaces.XMLNS_IFACE, root.uri)
        sels = selections.Selections(root)
        sel, = sels.selections.values()
        self.assertEqual("sha1=3ce644dc725f1d21cfcf02562c76f375944b266a",
                         sel.id)
    def testSelectOnly(self):
        os.environ['DISPLAY'] = ':foo'
        out, err = self.run_0launch(
            ['--get-selections', '--select-only', 'Hello.xml'])
        self.assertEquals("", err)

        assert out.endswith("Finished\n")
        out = out[:-len("Finished\n")]

        root = qdom.parse(StringIO(str(out)))
        self.assertEquals(namespaces.XMLNS_IFACE, root.uri)
        sels = selections.Selections(root)
        sel, = sels.selections.values()
        self.assertEquals("sha1=3ce644dc725f1d21cfcf02562c76f375944b266a",
                          sel.id)
Exemple #18
0
	def testSelectionsWithFeed(self):
		with open("selections.xml", 'rb') as stream:
			root = qdom.parse(stream)
		sels = selections.Selections(root)

		with output_suppressed():
			run_server('Hello.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz')
			sys.stdin = Reply("Y\n")

			tasks.wait_for_blocker(self.config.fetcher.download_and_import_feed('http://example.com:8000/Hello.xml', self.config.iface_cache))

			out, err = self.run_ocaml(['download', 'selections.xml'])
			assert not out, out
			assert not err, err
			path = self.config.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests)
			assert os.path.exists(os.path.join(path, 'HelloWorld', 'main'))

			assert sels.download_missing(self.config) is None
Exemple #19
0
	def build(selections_xml):
		# Get the chosen versions
		sels = selections.Selections(qdom.parse(BytesIO(selections_xml)))

		impl = sels.selections[interface_uri]

		min_version = impl.attrs.get(XMLNS_0COMPILE + ' min-version', our_min_version)
		# Check the syntax is valid and the version is high enough
		if model.parse_version(min_version) < model.parse_version(our_min_version):
			min_version = our_min_version

		# Do the whole build-and-register-feed
		c = Command()
		c.run(("0launch",
			'--message', _('Download the 0compile tool, to compile the source code'),
			'--not-before=' + min_version,
			"http://0install.net/2006/interfaces/0compile.xml",
			'gui',
			interface_uri), lambda unused: on_success())
Exemple #20
0
def load_feed(source, local = False, selections_ok = False, generate_sizes = False, implementation_id_alg=None, config=None):
	"""Load a feed from a local file.
	@param source: the name of the file to read
	@type source: str
	@param local: this is a local feed
	@type local: bool
	@param selections_ok: if it turns out to be a local selections document, return that instead
	@type selections_ok: bool
	@param generate_sizes: if True, sizes of archives with missing size attributes will be generated
	@type generate_sizes: bool
	@param implementation_id_alg: if specified, missing impl ids will be generated with this alg
	@type implementation_id_alg: L{Algorithm}
	@raise InvalidInterface: if the source's syntax is incorrect
	@return: the new feed
	@since: 0.48
	@see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
	try:
		root = qdom.parse(file(source))
	except IOError as ex:
		if ex.errno == 2 and local:
			raise MissingLocalFeed(_("Feed not found. Perhaps this is a local feed that no longer exists? You can remove it from the list of feeds in that case."))
		raise InvalidInterface(_("Can't read file"), ex)
	except Exception as ex:
		raise InvalidInterface(_("Invalid XML"), ex)

	if local:
		if selections_ok and root.uri == XMLNS_IFACE and root.name == 'selections':
			from zeroinstall.injector import selections
			return selections.Selections(root)
		local_path = source
	else:
		local_path = None
	if implementation_id_alg or generate_sizes:
		from zeroinstall.injector.config import load_config
		if config is None:
			config = load_config()
		feed = ZeroInstallFeed(root, local_path, None, generate_sizes, implementation_id_alg, config.fetcher, config.stores)
	else:
		feed = ZeroInstallFeed(root, local_path)
	feed.last_modified = int(os.stat(source).st_mtime)
	
	return feed
Exemple #21
0
    def get_selections(self, snapshot_date=None, may_update=False):
        """Load the selections.
		@param may_update: whether to check for updates
		@type may_update: bool
		@param snapshot_date: get a historical snapshot
		@type snapshot_date: (as returned by L{get_history}) | None
		@return: the selections
		@rtype: L{selections.Selections}"""
        if snapshot_date:
            sels_file = os.path.join(self.path,
                                     'selections-' + snapshot_date + '.xml')
        else:
            sels_file = os.path.join(self.path, 'selections.xml')
        with open(sels_file, 'rb') as stream:
            sels = selections.Selections(qdom.parse(stream))

        if may_update:
            sels = self._check_for_updates(sels)

        return sels
Exemple #22
0
	def testSelections(self):
		with open("selections.xml", 'rb') as stream:
			root = qdom.parse(stream)
		sels = selections.Selections(root)
		class Options: dry_run = False

		with output_suppressed():
			run_server('Hello.xml', '6FCF121BE2390E0B.gpg', '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B', 'HelloWorld.tgz')
			try:
				self.config.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests)
				assert False
			except NotStored:
				pass
			out, err = self.run_ocaml(['download', 'selections.xml'], stdin = "Y\n")
			assert not out, out
			assert not err, err
			path = self.config.stores.lookup_any(sels.selections['http://example.com:8000/Hello.xml'].digests)
			assert os.path.exists(os.path.join(path, 'HelloWorld', 'main'))

			assert sels.download_missing(self.config) is None
Exemple #23
0
    def testDownload(self):
        out, err = self.run_0install(['download'])
        assert out.lower().startswith("usage:")
        assert '--show' in out

        assert 'file\n' in self.complete(["download", ""], 2)

        out, err = self.run_0install(['download', 'Local.xml', '--show'])
        assert not err, err
        assert 'Version: 0.1' in out

        local_uri = model.canonical_iface_uri('Local.xml')
        out, err = self.run_0install(['download', 'Local.xml', '--xml'])
        assert not err, err
        sels = selections.Selections(
            qdom.parse(BytesIO(str(out).encode('utf-8'))))
        assert sels.selections[local_uri].version == '0.1'

        out, err = self.run_0install(
            ['download', 'Local.xml', '--show', '--with-store=/foo'])
        assert not err, err
        assert self.config.stores.stores[-1].dir == '/foo'

        out, err = self.run_0install(
            ['download', '--offline', 'selections.xml'])
        assert 'Would download' in err
        self.config.network_use = model.network_full

        self.config.stores = TestStores()
        digest = 'sha1=3ce644dc725f1d21cfcf02562c76f375944b266a'
        self.config.fetcher.allow_download(digest)
        out, err = self.run_0install(['download', 'Hello.xml', '--show'])
        assert not err, err
        assert self.config.stores.lookup_any([digest]).startswith('/fake')
        assert 'Version: 1\n' in out

        out, err = self.run_0install(
            ['download', '--offline', 'selections.xml', '--show'])
        assert '/fake_store' in out
        self.config.network_use = model.network_full
Exemple #24
0
    def testSelect(self):
        out, err = self.run_0install(['select'])
        assert out.lower().startswith("usage:")
        assert '--xml' in out

        out, err = self.run_0install(['select', 'Local.xml'])
        assert not err, err
        assert 'Version: 0.1' in out

        local_uri = model.canonical_iface_uri('Local.xml')
        out, err = self.run_0install(['select', 'Local.xml'])
        assert not err, err
        assert 'Version: 0.1' in out

        out, err = self.run_0install(['select', 'Local.xml', '--xml'])
        sels = selections.Selections(qdom.parse(StringIO(str(out))))
        assert sels.selections[local_uri].version == '0.1'

        out, err = self.run_0install(['select', 'selections.xml'])
        assert not err, err
        assert 'Version: 1\n' in out
        assert '(not cached)' in out
Exemple #25
0
def ensure_cached(uri):
    """Ensure that an implementation of uri is cached.
	If not, it downloads one. It uses the GUI if a display is
	available, or the console otherwise.
	@param uri: the required interface
	@type uri: str
	@return: a new policy for this program, or None if the user cancelled
	@rtype: L{zeroinstall.injector.selections.Selections}
	"""
    from zeroinstall.injector import autopolicy, selections

    p = autopolicy.AutoPolicy(uri, download_only=True)
    p.freshness = 0  # Don't check for updates

    if p.need_download() or not p.ready:
        if os.environ.get('DISPLAY', None):
            return get_selections_gui(uri, [])
        else:
            p.recalculate_with_dl()
            p.start_downloading_impls()
            p.handler.wait_for_downloads()

    return selections.Selections(p)
    def testSelectionsWithFeed(self):
        from zeroinstall.injector import cli
        root = qdom.parse(file("selections.xml"))
        sels = selections.Selections(root)

        with output_suppressed():
            self.child = server.handle_requests(
                'Hello.xml', '6FCF121BE2390E0B.gpg',
                '/key-info/key/DE937DD411906ACF7C263B396FCF121BE2390E0B',
                'HelloWorld.tgz')
            sys.stdin = Reply("Y\n")

            self.config.handler.wait_for_blocker(
                self.config.fetcher.download_and_import_feed(
                    'http://example.com:8000/Hello.xml',
                    self.config.iface_cache))

            cli.main(['--download-only', 'selections.xml'], config=self.config)
            path = self.config.stores.lookup_any(
                sels.selections['http://example.com:8000/Hello.xml'].digests)
            assert os.path.exists(os.path.join(path, 'HelloWorld', 'main'))

            assert sels.download_missing(self.config) is None
	def download_and_run(self, run_button, cancelled):
		try:
			if not self.select_only:
				downloaded = self.policy.download_uncached_implementations()

				if downloaded:
					# We need to wait until everything is downloaded...
					blockers = [downloaded, cancelled]
					yield blockers
					tasks.check(blockers)

					if cancelled.happened:
						return

				uncached = self.policy.get_uncached_implementations()
			else:
				uncached = None		# (we don't care)

			if uncached:
				missing = '\n- '.join([_('%(iface_name)s %(impl_version)s') % {'iface_name': iface.get_name(), 'impl_version': impl.get_version()} for iface, impl in uncached])
				dialog.alert(self.window, _('Not all downloads succeeded; cannot run program.\n\nFailed to get:') + '\n- ' + missing)
			else:
				from zeroinstall.injector import selections
				sels = selections.Selections(self.policy)
				doc = sels.toDOM()
				reply = doc.toxml('utf-8')
				sys.stdout.write(('Length:%8x\n' % len(reply)) + reply)
				self.window.destroy()
				sys.exit(0)			# Success
		except SystemExit:
			raise
		except download.DownloadAborted as ex:
			run_button.set_active(False)
			# Don't bother reporting this to the user
		except Exception as ex:
			run_button.set_active(False)
			self.report_exception(ex)
Exemple #28
0
def get_selections_gui(iface_uri, gui_args, test_callback = None, use_gui = True):
	"""Run the GUI to choose and download a set of implementations.
	The user may ask the GUI to submit a bug report about the program. In that case,
	the GUI may ask us to test it. test_callback is called in that case with the implementations
	to be tested; the callback will typically call L{zeroinstall.injector.run.test_selections} and return the result of that.
	@param iface_uri: the required program, or None to show just the preferences dialog
	@type iface_uri: str
	@param gui_args: any additional arguments for the GUI itself
	@type gui_args: [str]
	@param test_callback: function to use to try running the program
	@type test_callback: L{zeroinstall.injector.selections.Selections} -> str
	@param use_gui: if True, raise a SafeException if the GUI is not available. If None, returns DontUseGUI if the GUI cannot be started. If False, returns DontUseGUI always. (since 1.11)
	@param use_gui: bool | None
	@return: the selected implementations
	@rtype: L{zeroinstall.injector.selections.Selections}
	@since: 0.28
	"""
	if use_gui is False:
		return DontUseGUI

	if 'DISPLAY' not in os.environ:
		if use_gui is None:
			return DontUseGUI
		else:
			raise SafeException("Can't use GUI because $DISPLAY is not set")

	from zeroinstall.injector import selections, qdom
	from io import BytesIO

	from os.path import join, dirname
	gui_exe = join(dirname(__file__), '0launch-gui', '0launch-gui')

	import socket
	cli, gui = socket.socketpair()

	try:
		child = os.fork()
		if child == 0:
			# We are the child (GUI)
			try:
				try:
					cli.close()
					# We used to use pipes to support Python2.3...
					os.dup2(gui.fileno(), 1)
					os.dup2(gui.fileno(), 0)
					if use_gui is True:
						gui_args = ['-g'] + gui_args
					if iface_uri is not None:
						gui_args = gui_args + ['--', iface_uri]
					os.execvp(sys.executable, [sys.executable, gui_exe] + gui_args)
				except:
					import traceback
					traceback.print_exc(file = sys.stderr)
			finally:
				sys.stderr.flush()
				os._exit(1)
		# We are the parent (CLI)
		gui.close()
		gui = None

		while True:
			logger.info("Waiting for selections from GUI...")

			reply = support.read_bytes(cli.fileno(), len('Length:') + 9, null_ok = True)
			if reply:
				if not reply.startswith(b'Length:'):
					raise Exception("Expected Length:, but got %s" % repr(reply))
				reply = reply.decode('ascii')
				xml = support.read_bytes(cli.fileno(), int(reply.split(':', 1)[1], 16))

				dom = qdom.parse(BytesIO(xml))
				sels = selections.Selections(dom)

				if dom.getAttribute('run-test'):
					logger.info("Testing program, as requested by GUI...")
					if test_callback is None:
						output = b"Can't test: no test_callback was passed to get_selections_gui()\n"
					else:
						output = test_callback(sels)
					logger.info("Sending results to GUI...")
					output = ('Length:%8x\n' % len(output)).encode('utf-8') + output
					logger.debug("Sending: %s", repr(output))
					while output:
						sent = cli.send(output)
						output = output[sent:]
					continue
			else:
				sels = None

			pid, status = os.waitpid(child, 0)
			assert pid == child
			if status == 1 << 8:
				logger.info("User cancelled the GUI; aborting")
				return None		# Aborted
			elif status == 100 << 8:
				if use_gui is None:
					return DontUseGUI
				else:
					raise SafeException("No GUI available")
			if status != 0:
				raise Exception("Error from GUI: code = %d" % status)
			break
	finally:
		for sock in [cli, gui]:
			if sock is not None: sock.close()
	
	return sels
Exemple #29
0
    def solve(self,
              root_interface,
              root_arch,
              command_name='run',
              closest_match=False):
        # closest_match is used internally. It adds a lowest-ranked
        # by valid implementation to every interface, so we can always
        # select something. Useful for diagnostics.

        # The basic plan is this:
        # 1. Scan the root interface and all dependencies recursively, building up a SAT problem.
        # 2. Solve the SAT problem. Whenever there are multiple options, try the most preferred one first.
        # 3. Create a Selections object from the results.
        #
        # All three involve recursively walking the tree in a similar way:
        # 1) we follow every dependency of every implementation (order not important)
        # 2) we follow every dependency of every selected implementation (better versions first)
        # 3) we follow every dependency of every selected implementation (order doesn't matter)
        #
        # In all cases, a dependency may be on an <implementation> or on a specific <command>.

        # TODO: We need some way to figure out which feeds to include.
        # Currently, we include any feed referenced from anywhere but
        # this is probably too much. We could insert a dummy optimial
        # implementation in stale/uncached feeds and see whether it
        # selects that.
        iface_cache = self.config.iface_cache

        problem = sat.SATProblem()

        impl_to_var = {}  # Impl -> sat var
        self.feeds_used = set()
        self.requires = {}
        self.ready = False
        self.details = self.record_details and {}

        self.selections = None
        self._failure_reason = None

        ifaces_processed = set()

        impls_for_machine_group = {
            0: []
        }  # Machine group (e.g. "64") to [impl] in that group
        for machine_group in machine_groups.values():
            impls_for_machine_group[machine_group] = []

        impls_for_iface = {}  # Iface -> [impl]

        group_clause_for = {}  # Iface URI -> AtMostOneClause | bool
        group_clause_for_command = {
        }  # (Iface URI, command name) -> AtMostOneClause | bool

        # Return the dependencies of impl that we should consider.
        # Skips dependencies if the use flag isn't what we need.
        # (note: impl may also be a model.Command)
        def deps_in_use(impl, arch):
            for dep in impl.requires:
                use = dep.metadata.get("use", None)
                if use not in arch.use:
                    continue
                yield dep

        # Must have already done add_iface on dependency.interface.
        # If dependency is essential:
        #   Add a clause so that if requiring_impl_var is True then an implementation
        #   matching 'dependency' must also be selected.
        # If dependency is optional:
        #   Require that no incompatible version is selected.
        # This ignores any 'command' required. Handle that separately.
        def find_dependency_candidates(requiring_impl_var, dependency):
            def meets_restrictions(candidate):
                for r in dependency.restrictions:
                    if not r.meets_restriction(candidate):
                        #warn("%s rejected due to %s", candidate.get_version(), r)
                        return False
                return True

            essential = dependency.importance == model.Dependency.Essential

            dep_iface = iface_cache.get_interface(dependency.interface)
            dep_union = [sat.neg(requiring_impl_var)
                         ]  # Either requiring_impl_var is False, or ...
            for candidate in impls_for_iface[dep_iface]:
                if (candidate.__class__ is
                        _DummyImpl) or meets_restrictions(candidate):
                    if essential:
                        c_var = impl_to_var.get(candidate, None)
                        if c_var is not None:
                            dep_union.append(c_var)
                        # else we filtered that version out, so ignore it
                else:
                    # Candidate doesn't meet our requirements
                    # If the dependency is optional add a rule to make sure we don't
                    # select this candidate.
                    # (for essential dependencies this isn't necessary because we must
                    # select a good version and we can't select two)
                    if not essential:
                        c_var = impl_to_var.get(candidate, None)
                        if c_var is not None:
                            problem.add_clause(dep_union + [sat.neg(c_var)])
                        # else we filtered that version out, so ignore it

            if essential:
                problem.add_clause(dep_union)

        def is_unusable(impl, restrictions, arch):
            """@return: whether this implementation is unusable.
			@rtype: bool"""
            return get_unusable_reason(impl, restrictions, arch) != None

        def get_unusable_reason(impl, restrictions, arch):
            """
			@param impl: Implementation to test.
			@type restrictions: [L{model.Restriction}]
			@return: The reason why this impl is unusable, or None if it's OK.
			@rtype: str
			@note: The restrictions are for the interface being requested, not the feed
			of the implementation; they may be different when feeds are being used."""
            for r in restrictions:
                if not r.meets_restriction(impl):
                    return _(
                        "Incompatible with another selected implementation")
            stability = impl.get_stability()
            if stability <= model.buggy:
                return stability.name
            if (self.config.network_use == model.network_offline
                    or not impl.download_sources) and not impl.is_available(
                        self.config.stores):
                if not impl.download_sources:
                    return _("No retrieval methods")
                return _("Not cached and we are off-line")
            if impl.os not in arch.os_ranks:
                return _("Unsupported OS")
            if impl.machine not in arch.machine_ranks:
                if impl.machine == 'src':
                    return _("Source code")
                return _("Unsupported machine type")
            return None

        def usable_feeds(iface, arch):
            """Return all feeds for iface that support arch.
			@rtype: generator(ZeroInstallFeed)"""
            yield iface.uri

            for f in iface_cache.get_feed_imports(iface):
                # Note: when searching for src, None is not in machine_ranks
                if f.os in arch.os_ranks and \
                   (f.machine is None or f.machine in arch.machine_ranks):
                    yield f.uri
                else:
                    debug(
                        _("Skipping '%(feed)s'; unsupported architecture %(os)s-%(machine)s"
                          ), {
                              'feed': f,
                              'os': f.os,
                              'machine': f.machine
                          })

        # If requiring_var is True then all of requirer's dependencies must be satisfied.
        # requirer can be a <command> or an <implementation>
        def process_dependencies(requiring_var, requirer, arch):
            for d in deps_in_use(requirer, arch):
                debug(_("Considering command dependency %s"), d)

                add_iface(d.interface, arch.child_arch)

                for c in d.get_required_commands():
                    # We depend on a specific command within the implementation.
                    command_vars = add_command_iface(d.interface,
                                                     arch.child_arch, c)

                    # If the parent command/impl is chosen, one of the candidate commands
                    # must be too. If there aren't any, then this command is unselectable.
                    problem.add_clause([sat.neg(requiring_var)] + command_vars)

                # Must choose one version of d if impl is selected
                find_dependency_candidates(requiring_var, d)

        def add_iface(uri, arch):
            """Name implementations from feed and assert that only one can be selected."""
            if uri in ifaces_processed: return
            ifaces_processed.add(uri)

            iface = iface_cache.get_interface(uri)

            impls = []
            for f in usable_feeds(iface, arch):
                self.feeds_used.add(f)
                debug(_("Processing feed %s"), f)

                try:
                    feed = iface_cache.get_feed(f)
                    if feed is None: continue
                    #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for:
                    #	info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f})

                    if feed.implementations:
                        impls.extend(feed.implementations.values())

                    distro_feed_url = feed.get_distro_feed()
                    if distro_feed_url:
                        self.feeds_used.add(distro_feed_url)
                        distro_feed = iface_cache.get_feed(distro_feed_url)
                        if distro_feed.implementations:
                            impls.extend(distro_feed.implementations.values())
                except MissingLocalFeed as ex:
                    warn(
                        _("Missing local feed; if it's no longer required, remove it with:"
                          ) + '\n0install remove-feed ' + iface.uri + ' ' + f,
                        {
                            'feed': f,
                            'interface': iface,
                            'exception': ex
                        })
                except Exception as ex:
                    warn(
                        _("Failed to load feed %(feed)s for %(interface)s: %(exception)s"
                          ), {
                              'feed': f,
                              'interface': iface,
                              'exception': ex
                          })
                    #raise

            impls.sort(key=lambda impl: self.get_rating(iface, impl, arch),
                       reverse=True)

            impls_for_iface[iface] = filtered_impls = []

            my_extra_restrictions = self.extra_restrictions.get(iface, [])

            if self.record_details:
                self.details[iface] = [
                    (impl,
                     get_unusable_reason(impl, my_extra_restrictions, arch))
                    for impl in impls
                ]

            var_names = []
            for impl in impls:
                if is_unusable(impl, my_extra_restrictions, arch):
                    continue

                filtered_impls.append(impl)

                if impl in impl_to_var:
                    # TODO If the same impl comes from original feed and
                    # the feed with "feed" tag
                    continue
                v = problem.add_variable(ImplInfo(iface, impl, arch))
                impl_to_var[impl] = v
                var_names.append(v)

                if impl.machine and impl.machine != 'src':
                    impls_for_machine_group[machine_groups.get(
                        impl.machine, 0)].append(v)

                process_dependencies(v, impl, arch)

            if closest_match:
                dummy_impl = _DummyImpl()
                dummy_var = problem.add_variable(
                    ImplInfo(iface, dummy_impl, arch, dummy=True))
                var_names.append(dummy_var)
                impl_to_var[dummy_impl] = dummy_var
                filtered_impls.append(dummy_impl)

            # Only one implementation of this interface can be selected
            if uri == root_interface:
                if var_names:
                    clause = problem.at_most_one(var_names)
                    problem.add_clause(var_names)  # at least one
                else:
                    problem.impossible()
                    clause = False
            elif var_names:
                clause = problem.at_most_one(var_names)
            else:
                # Don't need to add to group_clause_for because we should
                # never get a possible selection involving this.
                return

            assert clause is not True
            assert clause is not None
            if clause is not False:
                group_clause_for[uri] = clause

        def add_command_iface(uri, arch, command_name):
            """Add every <command> in interface 'uri' with this name.
			Each one depends on the corresponding implementation and only
			one can be selected. If closest_match is on, include a dummy
			command that can always be selected."""

            # Check whether we've already processed this (interface,command) pair
            existing = group_clause_for_command.get((uri, command_name), None)
            if existing is not None:
                return existing.lits

            # First ensure that the interface itself has been processed
            # We'll reuse the ordering of the implementations to order
            # the commands too.
            add_iface(uri, arch)

            iface = iface_cache.get_interface(uri)
            filtered_impls = impls_for_iface[iface]

            var_names = []
            for impl in filtered_impls:
                command = impl.commands.get(command_name, None)
                if not command:
                    if not isinstance(impl, _DummyImpl):
                        # Mark implementation as unselectable
                        problem.add_clause([sat.neg(impl_to_var[impl])])
                    continue

                # We have a candidate <command>. Require that if it's selected
                # then we select the corresponding <implementation> too.
                command_var = problem.add_variable(
                    CommandInfo(command_name, command, impl, arch))
                problem.add_clause([impl_to_var[impl], sat.neg(command_var)])

                var_names.append(command_var)

                process_dependencies(command_var, command, arch)

            # Tell the user why we couldn't use this version
            if self.record_details:

                def new_reason(impl, old_reason):
                    if command_name in impl.commands:
                        return old_reason
                    return old_reason or (_('No %s command') % command_name)

                self.details[iface] = [(impl, new_reason(impl, reason))
                                       for (impl,
                                            reason) in self.details[iface]]

            if closest_match:
                dummy_command = problem.add_variable(None)
                var_names.append(dummy_command)

            if var_names:
                # Can't select more than one of them.
                assert (uri, command_name) not in group_clause_for_command
                group_clause_for_command[(
                    uri, command_name)] = problem.at_most_one(var_names)

            return var_names

        if command_name is None:
            add_iface(root_interface, root_arch)
        else:
            commands = add_command_iface(root_interface, root_arch,
                                         command_name)
            if len(commands) > int(closest_match):
                # (we have at least one non-dummy command)
                problem.add_clause(commands)  # At least one
            else:
                # (note: might be because we haven't cached it yet)
                info("No %s <command> in %s", command_name, root_interface)

                impls = impls_for_iface[iface_cache.get_interface(
                    root_interface)]
                if impls == [] or (len(impls) == 1
                                   and isinstance(impls[0], _DummyImpl)):
                    # There were no candidates at all.
                    if self.config.network_use == model.network_offline:
                        self._failure_reason = _(
                            "Interface '%s' has no usable implementations in the cache (and 0install is in off-line mode)"
                        ) % root_interface
                    else:
                        self._failure_reason = _(
                            "Interface '%s' has no usable implementations"
                        ) % root_interface
                else:
                    # We had some candidates implementations, but none for the command we need
                    self._failure_reason = _(
                        "Interface '%s' cannot be executed directly; it is just a library "
                        "to be used by other programs (or missing '%s' command)"
                    ) % (root_interface, command_name)

                problem.impossible()

        # Require m<group> to be true if we select an implementation in that group
        m_groups = []
        for machine_group, impls in impls_for_machine_group.iteritems():
            m_group = 'm%d' % machine_group
            group_var = problem.add_variable(m_group)
            if impls:
                for impl in impls:
                    problem.add_clause([group_var, sat.neg(impl)])
            m_groups.append(group_var)
        if m_groups:
            m_groups_clause = problem.at_most_one(m_groups)
        else:
            m_groups_clause = None

        def decide():
            """Recurse through the current selections until we get to an interface with
			no chosen version, then tell the solver to try the best version from that."""
            def find_undecided_dep(impl_or_command, arch):
                # Check for undecided dependencies of impl_or_command
                for dep in deps_in_use(impl_or_command, arch):
                    for c in dep.get_required_commands():
                        dep_lit = find_undecided_command(dep.interface, c)
                        if dep_lit is not None:
                            return dep_lit
                    dep_lit = find_undecided(dep.interface)
                    if dep_lit is not None:
                        return dep_lit
                return None

            seen = set()

            def find_undecided(uri):
                if uri in seen:
                    return  # Break cycles
                seen.add(uri)

                if uri not in group_clause_for:
                    # TODO Looks like Sweets patch introduced this issue
                    return

                group = group_clause_for[uri]
                #print "Group for %s = %s" % (uri, group)
                lit = group.current
                if lit is None:
                    return group.best_undecided()
                # else there was only one choice anyway

                # Check for undecided dependencies
                lit_info = problem.get_varinfo_for_lit(lit).obj
                return find_undecided_dep(lit_info.impl, lit_info.arch)

            def find_undecided_command(uri, name):
                if name is None: return find_undecided(uri)

                group = group_clause_for_command[(uri, name)]
                lit = group.current
                if lit is None:
                    return group.best_undecided()
                # else we've already chosen which <command> to use

                # Check for undecided command-specific dependencies, and then for
                # implementation dependencies.
                lit_info = problem.get_varinfo_for_lit(lit).obj
                if lit_info is None:
                    assert closest_match
                    return None  # (a dummy command added for better diagnostics; has no dependencies)
                return find_undecided_dep(lit_info.command, lit_info.arch) or \
                       find_undecided_dep(lit_info.impl, lit_info.arch)

            best = find_undecided_command(root_interface, command_name)
            if best is not None:
                return best

            # If we're chosen everything we need, we can probably
            # set everything else to False.
            for group in group_clause_for.values(
            ) + group_clause_for_command.values() + [m_groups_clause]:
                if group.current is None:
                    best = group.best_undecided()
                    if best is not None:
                        return sat.neg(best)

            return None  # Failed to find any valid combination

        ready = problem.run_solver(decide) is True

        if not ready and not closest_match:
            # We failed while trying to do a real solve.
            # Try a closest match solve to get a better
            # error report for the user.
            self.solve(root_interface,
                       root_arch,
                       command_name=command_name,
                       closest_match=True)
        else:
            self.ready = ready and not closest_match
            self.selections = selections.Selections(None)
            self.selections.interface = root_interface

            sels = self.selections.selections

            commands_needed = []

            # Popular sels with the selected implementations.
            # Also, note down all the commands we need.
            for uri, group in group_clause_for.iteritems():
                if group.current is not None:
                    lit_info = problem.get_varinfo_for_lit(group.current).obj
                    if lit_info.is_dummy:
                        sels[lit_info.iface.uri] = None
                    else:
                        # We selected an implementation for interface 'uri'
                        impl = lit_info.impl

                        for b in impl.bindings:
                            c = b.command
                            if c is not None:
                                commands.append((uri, c))

                        deps = self.requires[lit_info.iface] = []
                        for dep in deps_in_use(lit_info.impl, lit_info.arch):
                            deps.append(dep)
                            for c in dep.get_required_commands():
                                commands_needed.append((dep.interface, c))

                        sels[lit_info.iface.uri] = selections.ImplSelection(
                            lit_info.iface.uri, impl, deps)

            # Now all all the commands in too.
            def add_command(iface, name):
                sel = sels.get(iface, None)
                if sel:
                    command = sel.impl.commands[name]
                    if name in sel._used_commands:
                        return  # Already added
                    sel._used_commands.add(name)

                    for dep in command.requires:
                        for dep_command_name in dep.get_required_commands():
                            add_command(dep.interface, dep_command_name)

                    # A <command> can depend on another <command> in the same interface
                    # (e.g. the tests depending on the main program)
                    for b in command.bindings:
                        c = b.command
                        if c is not None:
                            add_command(iface, c)

            for iface, command in commands_needed:
                add_command(iface, command)

            if command_name is not None:
                self.selections.command = command_name
                add_command(root_interface, command_name)
Exemple #30
0
	def testBackgroundUnsolvable(self):
		my_dbus.system_services = {"org.freedesktop.NetworkManager": {"/org/freedesktop/NetworkManager": NetworkManager()}}

		trust.trust_db.trust_key('DE937DD411906ACF7C263B396FCF121BE2390E0B', 'example.com:8000')

		# Create new app
		run_server('Hello.xml', '6FCF121BE2390E0B.gpg', 'HelloWorld.tgz')
		out, err = self.run_ocaml(['add', 'test-app', 'http://example.com:8000/Hello.xml'])
		kill_server_process()
		assert not out, out
		assert not err, err

		# Delete cached implementation so we need to download it again
		out, err = self.run_ocaml(['select', '--xml', 'test-app'], binary = True)
		sels = selections.Selections(qdom.parse(BytesIO(out)))
		stored = sels.selections['http://example.com:8000/Hello.xml'].get_path(self.config.stores)
		assert os.path.basename(stored).startswith('sha1')
		ro_rmtree(stored)

		out, err = self.run_ocaml(['select', '--xml', 'test-app'], binary = True)
		assert not err, err
		sels = selections.Selections(qdom.parse(BytesIO(out)))
		# Replace the selection with a bogus and unusable <package-implementation>
		sel, = sels.selections.values()
		sel.attrs['id'] = "package:dummy:badpackage"
		sel.attrs['from-feed'] = "distribution:http://example.com:8000/Hello.xml"
		sel.attrs['package'] = "badpackage"
		sel.get_command('run').qdom.attrs['path'] = '/i/dont/exist'

		app = basedir.load_first_config(namespaces.config_site, "apps", 'test-app')

		with open(os.path.join(app, 'selections.xml'), 'wt') as stream:
			doc = sels.toDOM()
			doc.writexml(stream, addindent="  ", newl="\n", encoding = 'utf-8')

		# Not time for a background update yet, but the missing binary should trigger
		# an update anyway.
		self.config.freshness = 0

		# Check we try to launch the GUI...
		os.environ['DISPLAY'] = 'dummy'
		run_server('Hello.xml', 'HelloWorld.tgz')
		out, err = self.run_ocaml(['download', '--xml', '-v', 'test-app'], binary = True)
		kill_server_process()
		err = err.decode('utf-8')
		assert 'get new selections; current ones are not usable' in err, err
		assert 'get-selections-gui' in err, err
		sels = selections.Selections(qdom.parse(BytesIO(out)))

		# Check we can also work without the GUI...
		del os.environ['DISPLAY']

		# Delete cached implementation so we need to download it again
		out, err = self.run_ocaml(['select', '--xml', 'test-app'], binary = True)
		sels = selections.Selections(qdom.parse(BytesIO(out)))
		stored = sels.selections['http://example.com:8000/Hello.xml'].get_path(self.config.stores)
		assert os.path.basename(stored).startswith('sha1')
		ro_rmtree(stored)

		run_server('Hello.xml', 'HelloWorld.tgz')
		out, err = self.run_ocaml(['download', '--xml', '-v', 'test-app'], binary = True)
		kill_server_process()
		err = err.decode('utf-8')
		assert 'get new selections; current ones are not usable' in err, err
		assert 'get-selections-gui' not in err, err
		sels = selections.Selections(qdom.parse(BytesIO(out)))

		# Now trigger a background update which discovers that no solution is possible
		timestamp = os.path.join(app, 'last-checked')
		last_check_attempt = os.path.join(app, 'last-check-attempt')
		selections_path = os.path.join(app, 'selections.xml')
		def reset_timestamps():
			global ran_gui
			ran_gui = False
			os.utime(timestamp, (1, 1))		# 1970
			os.utime(selections_path, (1, 1))
			if os.path.exists(last_check_attempt):
				os.unlink(last_check_attempt)
		reset_timestamps()

		out, err = self.run_ocaml(['destroy', 'test-app'])
		assert not out, out
		assert not err, err

		run_server('Hello.xml')
		out, err = self.run_ocaml(['add', '--source', 'test-app', 'http://example.com:8000/Hello.xml'])
		assert not out, out
		assert 'We want source and this is a binary' in err, err