def on_success():
			# A new local feed may have been registered, so reload it from the disk cache
			info(_("0compile command completed successfully. Reloading interface details."))
			reader.update_from_cache(interface)
			for feed in interface.extra_feeds:
				 self.policy.config.iface_cache.get_feed(feed.uri, force = True)
			self.policy.recalculate()
 def add_feed():
     # A new local feed may have been registered, so update the interface from the cache
     info(
         _("0compile command completed successfully. Reloading interface details."
           ))
     reader.update_from_cache(interface)
     policy.recalculate()
Example #3
0
	def action_run(self, uri):
		iface = self.iface_cache.get_interface(uri)
		reader.update_from_cache(iface)
		if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')):
			if gtk.pygtk_version >= (2,16,0) and gtk.gdk.WINDOWING == 'quartz':
				script = ['0launch', '--', uri]
				osascript = support.find_in_path('osascript')
				subprocess.Popen([osascript, '-e', 'tell app "Terminal"', '-e', 'activate',
							     '-e', 'do script "%s"' % ' '.join(script), '-e', 'end tell'])
				return
			for terminal in ['x-terminal-emulator', 'xterm', 'gnome-terminal', 'rxvt', 'konsole']:
				exe = support.find_in_path(terminal)
				if exe:
					if terminal == 'gnome-terminal':
						flag = '-x'
					else:
						flag = '-e'
					subprocess.Popen([terminal, flag, '0launch', '--', uri])
					break
			else:
				box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator"))
				box.run()
				box.destroy()
		else:
			subprocess.Popen(['0launch', '--', uri])
Example #4
0
 def action_run(self, uri):
     iface = self.iface_cache.get_interface(uri)
     reader.update_from_cache(iface)
     if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')):
         if gtk.pygtk_version >= (2, 16,
                                  0) and gtk.gdk.WINDOWING == 'quartz':
             script = ['0launch', '--', uri]
             osascript = support.find_in_path('osascript')
             subprocess.Popen([
                 osascript, '-e', 'tell app "Terminal"', '-e', 'activate',
                 '-e',
                 'do script "%s"' % ' '.join(script), '-e', 'end tell'
             ])
             return
         for terminal in [
                 'x-terminal-emulator', 'xterm', 'gnome-terminal', 'rxvt',
                 'konsole'
         ]:
             exe = support.find_in_path(terminal)
             if exe:
                 if terminal == 'gnome-terminal':
                     flag = '-x'
                 else:
                     flag = '-e'
                 subprocess.Popen([terminal, flag, '0launch', '--', uri])
                 break
         else:
             box = gtk.MessageDialog(
                 self.window, gtk.DIALOG_MODAL,
                 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
                 _("Can't find a suitable terminal emulator"))
             box.run()
             box.destroy()
     else:
         subprocess.Popen(['0launch', '--', uri])
Example #5
0
    def action_help(self, uri):
        p = policy.Policy(uri)
        policy.network_use = model.network_offline
        if p.need_download():
            child = subprocess.Popen(['0launch', '-d', '--', uri])
            child.wait()
            if child.returncode:
                return
        iface = self.iface_cache.get_interface(uri)
        reader.update_from_cache(iface)
        p.solve_with_downloads()
        impl = p.solver.selections[iface]
        assert impl, "Failed to choose an implementation of %s" % uri
        help_dir = impl.metadata.get('doc-dir')
        path = p.get_implementation_path(impl)
        assert path, "Chosen implementation is not cached!"
        if help_dir:
            path = os.path.join(path, help_dir)
        else:
            main = impl.main
            if main:
                # Hack for ROX applications. They should be updated to
                # set doc-dir.
                help_dir = os.path.join(path, os.path.dirname(main), 'Help')
                if os.path.isdir(help_dir):
                    path = help_dir

        # xdg-open has no "safe" mode, so check we're not "opening" an application.
        if os.path.exists(os.path.join(path, 'AppRun')):
            raise Exception(
                _("Documentation directory '%s' is an AppDir; refusing to open"
                  ) % path)

        subprocess.Popen(['xdg-open', path])
Example #6
0
	def action_help(self, uri):
		p = policy.Policy(uri)
		policy.network_use = model.network_offline
		if p.need_download():
			child = subprocess.Popen(['0launch', '-d', '--', uri])
			child.wait()
			if child.returncode:
				return
		iface = self.iface_cache.get_interface(uri)
		reader.update_from_cache(iface)
		p.solve_with_downloads()
		impl = p.solver.selections[iface]
		assert impl, "Failed to choose an implementation of %s" % uri
		help_dir = impl.metadata.get('doc-dir')
		path = p.get_implementation_path(impl)
		assert path, "Chosen implementation is not cached!"
		if help_dir:
			path = os.path.join(path, help_dir)
		else:
			main = impl.main
			if main:
				# Hack for ROX applications. They should be updated to
				# set doc-dir.
				help_dir = os.path.join(path, os.path.dirname(main), 'Help')
				if os.path.isdir(help_dir):
					path = help_dir

		# xdg-open has no "safe" mode, so check we're not "opening" an application.
		if os.path.exists(os.path.join(path, 'AppRun')):
			raise Exception(_("Documentation directory '%s' is an AppDir; refusing to open") % path)

		subprocess.Popen(['xdg-open', path])
    def ok(feed):
        from zeroinstall.injector import reader
        try:
            feed_targets = policy.get_feed_targets(feed)
            if interface not in feed_targets:
                raise Exception(
                    _("Not a valid feed for '%(uri)s'; this is a feed for:\n%(feed_for)s"
                      ) % {
                          'uri': interface.uri,
                          'feed_for': '\n'.join([f.uri for f in feed_targets])
                      })
            if interface.get_feed(feed):
                dialog.alert(None, _('This feed is already registered.'))
            else:
                interface.extra_feeds.append(
                    Feed(feed, user_override=True, arch=None))

            writer.save_interface(interface)
            chooser.destroy()
            reader.update_from_cache(interface)
            policy.recalculate()
        except Exception, ex:
            dialog.alert(
                None,
                _("Error in feed file '%(feed)s':\n\n%(exception)s") % {
                    'feed': feed,
                    'exception': str(ex)
                })
	def testSitePackages(self):
		# The old system (0install < 1.9):
		# - 0compile stores implementations to ~/.cache, and 
		# - adds to extra_feeds
		#
		# The middle system (0install 1.9..1.12)
		# - 0compile stores implementations to ~/.local/0install.net/site-packages
		#   but using an obsolete escaping scheme, and
		# - modern 0install finds them via extra_feeds
		#
		# The new system (0install >= 1.13):
		# - 0compile stores implementations to ~/.local/0install.net/site-packages, and
		# - 0install finds them automatically

		# For backwards compatibility, 0install >= 1.9:
		# - writes discovered feeds to extra_feeds
		# - skips such entries in extra_feeds when loading

		expected_escape = 'section__prog_5f_1.xml'

		meta_dir = basedir.save_data_path('0install.net', 'site-packages',
						   'http', 'example.com', expected_escape, '1.0', '0install')
		feed = os.path.join(meta_dir, 'feed.xml')
		shutil.copyfile(os.path.join(mydir, 'Local.xml'), feed)

		# Check that we find the feed without us having to register it
		iface = self.config.iface_cache.get_interface('http://example.com/section/prog_1.xml')
		self.assertEqual(1, len(iface.extra_feeds))
		site_feed, = iface.extra_feeds
		self.assertEqual(True, site_feed.site_package)

		# Check that we write it out, so that older 0installs can find it
		writer.save_interface(iface)

		config_file = basedir.load_first_config('0install.net', 'injector',
							'interfaces', 'http:##example.com#section#prog_1.xml')
		with open(config_file, 'rb') as s:
			doc = qdom.parse(s)

		feed_node = None
		for item in doc.childNodes:
			if item.name == 'feed':
				feed_node = item
		self.assertEqual('True', feed_node.getAttribute('is-site-package'))

		# Check we ignore this element
		iface.reset()
		self.assertEqual([], iface.extra_feeds)
		reader.update_user_overrides(iface)
		self.assertEqual([], iface.extra_feeds)

		# Check feeds are automatically removed again
		reader.update_from_cache(iface, iface_cache = self.config.iface_cache)
		self.assertEqual(1, len(iface.extra_feeds))
		shutil.rmtree(basedir.load_first_data('0install.net', 'site-packages',
							'http', 'example.com', expected_escape))

		reader.update_from_cache(iface, iface_cache = self.config.iface_cache)
		self.assertEqual(0, len(iface.extra_feeds))
Example #9
0
		def on_success():
			# A new local feed may have been registered, so reload it from the disk cache
			info(_("0compile command completed successfully. Reloading interface details."))
			reader.update_from_cache(interface)
			for feed in interface.extra_feeds:
				self.config.iface_cache.get_feed(feed.uri, force = True)
			import main
			main.recalculate()
    def _import_new_interface(self, interface, new_xml, modified_time):
        """Write new_xml into the cache.
		@param interface: updated once the new XML is written
		@param new_xml: the data to write
		@param modified_time: when new_xml was modified
		@raises ReplayAttack: if the new mtime is older than the current one
		"""
        assert modified_time

        upstream_dir = basedir.save_cache_path(config_site, 'interfaces')
        cached = os.path.join(upstream_dir, escape(interface.uri))

        if os.path.exists(cached):
            old_xml = file(cached).read()
            if old_xml == new_xml:
                debug(_("No change"))
                reader.update_from_cache(interface)
                return

        stream = file(cached + '.new', 'w')
        stream.write(new_xml)
        stream.close()
        os.utime(cached + '.new', (modified_time, modified_time))
        new_mtime = reader.check_readable(interface.uri, cached + '.new')
        assert new_mtime == modified_time

        old_modified = self._get_signature_date(interface.uri)
        if old_modified is None:
            old_modified = interface.last_modified

        if old_modified:
            if new_mtime < old_modified:
                os.unlink(cached + '.new')
                raise ReplayAttack(
                    _("New interface's modification time is "
                      "before old version!\nOld time: %(old_time)s\nNew time: %(new_time)s\n"
                      "Refusing update.") % {
                          'old_time': _pretty_time(old_modified),
                          'new_time': _pretty_time(new_mtime)
                      })
            if new_mtime == old_modified:
                # You used to have to update the modification time manually.
                # Now it comes from the signature, this check isn't useful
                # and often causes problems when the stored format changes
                # (e.g., when we stopped writing last-modified attributes)
                pass
                #raise SafeException("Interface has changed, but modification time "
                #		    "hasn't! Refusing update.")
        os.rename(cached + '.new', cached)
        debug(_("Saved as %s") % cached)

        reader.update_from_cache(interface)
Example #11
0
	def testSitePackages(self):
		# The old system (0install < 1.9):
		# - 0compile stores implementations to ~/.cache, and 
		# - adds to extra_feeds
		# The new system (0install >= 1.9):
		# - 0compile stores implementations to ~/.local/0install.net/site-packages, and
		# - 0install finds them automatically

		# For backwards compatibility, 0install >= 1.9:
		# - writes discovered feeds to extra_feeds
		# - skips such entries in extra_feeds when loading

		meta_dir = basedir.save_data_path('0install.net', 'site-packages',
						   'http:##example.com#prog.xml', '1.0', '0install')
		feed = os.path.join(meta_dir, 'feed.xml')
		shutil.copyfile(os.path.join(mydir, 'Local.xml'), feed)

		# Check that we find the feed without us having to register it
		iface = self.config.iface_cache.get_interface('http://example.com/prog.xml')
		self.assertEqual(1, len(iface.extra_feeds))
		site_feed, = iface.extra_feeds
		self.assertEqual(True, site_feed.site_package)

		# Check that we write it out, so that older 0installs can find it
		writer.save_interface(iface)

		config_file = basedir.load_first_config('0install.net', 'injector',
							'interfaces', 'http:##example.com#prog.xml')
		with open(config_file, 'rb') as s:
			doc = qdom.parse(s)

		feed_node = None
		for item in doc.childNodes:
			if item.name == 'feed':
				feed_node = item
		self.assertEqual('True', feed_node.getAttribute('site-package'))

		# Check we ignore this element
		iface.reset()
		self.assertEqual([], iface.extra_feeds)
		reader.update_user_overrides(iface)
		self.assertEqual([], iface.extra_feeds)

		# Check feeds are automatically removed again
		reader.update_from_cache(iface, iface_cache = self.config.iface_cache)
		self.assertEqual(1, len(iface.extra_feeds))
		shutil.rmtree(basedir.load_first_data('0install.net', 'site-packages',
							'http:##example.com#prog.xml'))

		reader.update_from_cache(iface, iface_cache = self.config.iface_cache)
		self.assertEqual(0, len(iface.extra_feeds))
    def get_interface(self, uri):
        """Get the interface for uri, creating a new one if required.
		New interfaces are initialised from the disk cache, but not from
		the network.
		@param uri: the URI of the interface to find
		@rtype: L{model.Interface}
		"""
        if type(uri) == str:
            uri = unicode(uri)
        assert isinstance(uri, unicode)

        if uri in self._interfaces:
            return self._interfaces[uri]

        debug(_("Initialising new interface object for %s"), uri)
        self._interfaces[uri] = Interface(uri)
        reader.update_from_cache(self._interfaces[uri])
        return self._interfaces[uri]
Example #13
0
	def get_interface(self, uri):
		"""Get the interface for uri, creating a new one if required.
		New interfaces are initialised from the disk cache, but not from
		the network.
		@param uri: the URI of the interface to find
		@type uri: str
		@rtype: L{model.Interface}"""
		if type(uri) == str:
			uri = unicode(uri)
		assert isinstance(uri, unicode)

		if uri in self._interfaces:
			return self._interfaces[uri]

		logger.debug(_("Initialising new interface object for %s"), uri)
		self._interfaces[uri] = Interface(uri)
		reader.update_from_cache(self._interfaces[uri], iface_cache = self)
		return self._interfaces[uri]
Example #14
0
	def action_run(self, uri):
		iface = self.iface_cache.get_interface(uri)
		reader.update_from_cache(iface)
		if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')):
			for terminal in ['xterm', 'gnome-terminal', 'rxvt', 'konsole']:
				exe = support.find_in_path(terminal)
				if exe:
					if terminal == 'gnome-terminal':
						flag = '-x'
					else:
						flag = '-e'
					subprocess.Popen([terminal, flag, '0launch', '--', uri])
					break
			else:
				box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator"))
				box.run()
				box.destroy()
		else:
			subprocess.Popen(['0launch', '--', uri])
	def action_run(self, uri):
		iface = self.iface_cache.get_interface(uri)
		reader.update_from_cache(iface)
		if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')):
			for terminal in ['xterm', 'gnome-terminal', 'rxvt', 'konsole']:
				exe = support.find_in_path(terminal)
				if exe:
					if terminal == 'gnome-terminal':
						flag = '-x'
					else:
						flag = '-e'
					subprocess.Popen([terminal, flag, '0launch', '--', uri])
					break
			else:
				box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator"))
				box.run()
				box.destroy()
		else:
			subprocess.Popen(['0launch', '--', uri])
Example #16
0
	def ok(feed):
		from zeroinstall.injector import reader
		try:
			feed_targets = policy.get_feed_targets(feed)
			if interface not in feed_targets:
				raise Exception(_("Not a valid feed for '%(uri)s'; this is a feed for:\n%(feed_for)s") %
						{'uri': interface.uri,
						'feed_for': '\n'.join([f.uri for f in feed_targets])})
			if feed in [f.uri for f in interface.extra_feeds]:
				dialog.alert(None, _('This feed is already registered.'))
			else:
				interface.extra_feeds.append(Feed(feed, user_override = True, arch = None))

			writer.save_interface(interface)
			chooser.destroy()
			reader.update_from_cache(interface)
			import main
			main.recalculate()
		except Exception as ex:
			dialog.alert(None, _("Error in feed file '%(feed)s':\n\n%(exception)s") % {'feed': feed, 'exception': str(ex)})
Example #17
0
		def response(box, resp):
			if resp == RESPONSE_SETUP:
				def done_setup():
					self.add_msg('Now use Build to compile the chosen source code.')
				self.run_command((sys.executable, main_path, 'setup'), done_setup)
			elif resp == RESPONSE_BUILD:
				def done_build():
					self.add_msg('\nBuild successful. Now register or publish the build.')
				def build_failed():
					self.add_msg('\nIf the messages displayed above indicate a missing dependency (e.g. no C compiler '
						     "or a library that isn't available through Zero Install) then install it using your "
						     'normal package manager and click on Build again. Note that for libraries you often '
						     'need the -dev version of the package. '
						     '\nOtherwise, please notify the developers of this problem (this will transmit '
						     'the contents of the build/build-failure.log file):')
					end = self.buffer.get_end_iter()
					anchor = self.buffer.create_child_anchor(end)
					align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
					button = ButtonMixed(gtk.STOCK_YES, 'Notify developers')
					align.add(button)
					align.set_padding(8, 8, 8, 8)
					align.show_all()
					self.tv.add_child_at_anchor(align, anchor)
					self.add_msg('\n')
					def report_bug(button):
						def done_notify():
							self.add_msg("\nReport sent. Thank you! (note: you won't get a reply, as "
								"no contact details were sent; write to the project's mailing "
								"list if you want to discuss the problem)")
						self.run_command((sys.executable, main_path, 'report-bug'), done_notify)
					button.connect('clicked', report_bug)
				buildenv = BuildEnv()
				changes = buildenv.get_build_changes()
				if changes:
					options = get_build_options(box, '\n'.join(changes) + '\n\nIt would be best to do a clean (full) build.')
				else:
					options = []
				if options is not None:
					box.run_command([sys.executable, main_path, 'build'] + options, done_build, build_failed)
			elif resp == RESPONSE_REGISTER:
				buildenv = BuildEnv()

				iface = iface_cache.get_interface(interface)
				reader.update_from_cache(iface)

				# Register using the feed-for, if available
				real_iface = iface
				for uri in iface.feed_for or []:
					real_iface = iface_cache.get_interface(uri)
					self.add_msg("Registering as a feed for %s" % real_iface.uri)
					break
				else:
					if os.path.isabs(iface.uri):
						self.add_msg("Warning: no <feed-for> in local feed %s!" % iface.uri)

				feed = buildenv.local_iface_file
				for f in real_iface.feeds or []:
					if f.uri == feed:
						self.add_msg("Feed '%s' is already registered for interface '%s'!\n" % (feed, real_iface.uri))
						return
				box.buffer.insert_at_cursor("Registering feed '%s'\n" % feed)
				real_iface.extra_feeds.append(model.Feed(feed, arch = None, user_override = True))
				writer.save_interface(real_iface)
				box.buffer.insert_at_cursor("Done. You can now close this window.\n")
			elif resp == RESPONSE_PUBLISH:
				buildenv = BuildEnv()
				box = PublishBox(self, buildenv)
				resp = box.run()
				box.destroy()
				if resp == gtk.RESPONSE_OK:
					def done_publish():
						self.add_msg("\nYou can use '0publish --local' to add this "
							"into the main feed. If you don't have a main feed then this "
							"will create one. See "
							"http://0install.net/injector-packagers.html for more information.")
					self.run_command((sys.executable, main_path,
						'publish', box.archive_dir.get_text()), done_publish)
			elif resp == gtk.RESPONSE_CANCEL or resp == gtk.RESPONSE_DELETE_EVENT:
				if self.kill_child(): return
				self.destroy()
			else:
				self.add_msg('Unknown response: %s' % resp)
Example #18
0
    def compile_and_register(self, sels, forced_iface_uri=None):
        """If forced_iface_uri, register as an implementation of this interface,
		ignoring the any <feed-for>, etc."""

        buildenv = BuildEnv(need_config=False)
        buildenv.config.set('compile', 'interface', sels.interface)
        buildenv.config.set('compile', 'selections', 'selections.xml')

        # Download any required packages now, so we can use the GUI to request confirmation, etc
        download_missing = sels.download_missing(self.config,
                                                 include_packages=True)
        if download_missing:
            yield download_missing
            tasks.check(download_missing)

        tmpdir = tempfile.mkdtemp(prefix='0compile-')
        try:
            os.chdir(tmpdir)

            # Write configuration for build...

            buildenv.save()

            sel_file = open('selections.xml', 'w')
            try:
                doc = sels.toDOM()
                doc.writexml(sel_file)
                sel_file.write('\n')
            finally:
                sel_file.close()

            # Do the build...

            build = self.spawn_build(buildenv.iface_name)
            if build:
                yield build
                tasks.check(build)

            # Register the result...
            dom = minidom.parse(buildenv.local_iface_file)

            feed_for_elem, = dom.getElementsByTagNameNS(
                namespaces.XMLNS_IFACE, 'feed-for')
            claimed_iface = feed_for_elem.getAttribute('interface')

            if forced_iface_uri is not None:
                if forced_iface_uri != claimed_iface:
                    self.note(
                        "WARNING: registering as feed for {forced}, though feed claims to be for {claimed}"
                        .format(forced=forced_iface_uri,
                                claimed=claimed_iface))
            else:
                forced_iface_uri = claimed_iface  # (the top-level interface being built)

            version = sels.selections[sels.interface].version

            site_package_versions_dir = basedir.save_data_path(
                '0install.net', 'site-packages',
                *model.escape_interface_uri(forced_iface_uri))
            leaf = '%s-%s' % (version, build_target_machine_type)
            site_package_dir = os.path.join(site_package_versions_dir, leaf)
            self.note("Storing build in %s" % site_package_dir)

            # 1. Copy new version in under a temporary name. Names starting with '.' are ignored by 0install.
            tmp_distdir = os.path.join(site_package_versions_dir,
                                       '.new-' + leaf)
            shutil.copytree(buildenv.distdir, tmp_distdir, symlinks=True)

            # 2. Rename the previous build to .old-VERSION (deleting that if it already existed)
            if os.path.exists(site_package_dir):
                self.note("(moving previous build out of the way)")
                previous_build_dir = os.path.join(site_package_versions_dir,
                                                  '.old-' + leaf)
                if os.path.exists(previous_build_dir):
                    shutil.rmtree(previous_build_dir)
                os.rename(site_package_dir, previous_build_dir)
            else:
                previous_build_dir = None

            # 3. Rename the new version immediately after renaming away the old one to minimise time when there's
            # no version.
            os.rename(tmp_distdir, site_package_dir)

            # 4. Delete the old version.
            if previous_build_dir:
                self.note("(deleting previous build)")
                shutil.rmtree(previous_build_dir)

            local_feed = os.path.join(site_package_dir, '0install', 'feed.xml')
            assert os.path.exists(
                local_feed), "Feed %s not found!" % local_feed

            # Reload - our 0install will detect the new feed automatically
            iface = self.config.iface_cache.get_interface(forced_iface_uri)
            reader.update_from_cache(iface,
                                     iface_cache=self.config.iface_cache)
            self.config.iface_cache.get_feed(local_feed, force=True)

            # Write it out - 0install will add the feed so that older 0install versions can find it
            writer.save_interface(iface)

            seen_key = (forced_iface_uri, sels.selections[sels.interface].id)
            assert seen_key not in self.seen, seen_key
            self.seen[seen_key] = site_package_dir
        except:
            self.note(
                "\nBuild failed: leaving build directory %s for inspection...\n"
                % tmpdir)
            raise
        else:
            # Can't delete current directory on Windows, so move to parent first
            os.chdir(os.path.join(tmpdir, os.path.pardir))

            ro_rmtree(tmpdir)
Example #19
0
	def compile_and_register(self, sels, forced_iface_uri = None):
		"""If forced_iface_uri, register as an implementation of this interface,
		ignoring the any <feed-for>, etc."""

		buildenv = BuildEnv(need_config = False)
		buildenv.config.set('compile', 'interface', sels.interface)
		buildenv.config.set('compile', 'selections', 'selections.xml')
		
		# Download any required packages now, so we can use the GUI to request confirmation, etc
		download_missing = sels.download_missing(self.config, include_packages = True)
		if download_missing:
			yield download_missing
			tasks.check(download_missing)

		tmpdir = tempfile.mkdtemp(prefix = '0compile-')
		try:
			os.chdir(tmpdir)

			# Write configuration for build...

			buildenv.save()

			sel_file = open('selections.xml', 'w')
			try:
				doc = sels.toDOM()
				doc.writexml(sel_file)
				sel_file.write('\n')
			finally:
				sel_file.close()

			# Do the build...

			build = self.spawn_build(buildenv.iface_name)
			if build:
				yield build
				tasks.check(build)

			# Register the result...
			dom = minidom.parse(buildenv.local_iface_file)

			feed_for_elem, = dom.getElementsByTagNameNS(namespaces.XMLNS_IFACE, 'feed-for')
			claimed_iface = feed_for_elem.getAttribute('interface')

			if forced_iface_uri is not None:
				if forced_iface_uri != claimed_iface:
					self.note("WARNING: registering as feed for {forced}, though feed claims to be for {claimed}".format(
						forced = forced_iface_uri,
						claimed = claimed_iface))
			else:
				forced_iface_uri = claimed_iface		# (the top-level interface being built)

			version = sels.selections[sels.interface].version

			site_package_versions_dir = basedir.save_data_path('0install.net', 'site-packages',
						*model.escape_interface_uri(forced_iface_uri))
			leaf =  '%s-%s' % (version, uname[4])
			site_package_dir = os.path.join(site_package_versions_dir, leaf)
			self.note("Storing build in %s" % site_package_dir)

			# 1. Copy new version in under a temporary name. Names starting with '.' are ignored by 0install.
			tmp_distdir = os.path.join(site_package_versions_dir, '.new-' + leaf)
			shutil.copytree(buildenv.distdir, tmp_distdir, symlinks = True)

			# 2. Rename the previous build to .old-VERSION (deleting that if it already existed)
			if os.path.exists(site_package_dir):
				self.note("(moving previous build out of the way)")
				previous_build_dir = os.path.join(site_package_versions_dir, '.old-' + leaf)
				if os.path.exists(previous_build_dir):
					shutil.rmtree(previous_build_dir)
				os.rename(site_package_dir, previous_build_dir)
			else:
				previous_build_dir = None

			# 3. Rename the new version immediately after renaming away the old one to minimise time when there's
			# no version.
			os.rename(tmp_distdir, site_package_dir)

			# 4. Delete the old version.
			if previous_build_dir:
				self.note("(deleting previous build)")
				shutil.rmtree(previous_build_dir)

			local_feed = os.path.join(site_package_dir, '0install', 'feed.xml')
			assert os.path.exists(local_feed), "Feed %s not found!" % local_feed

			# Reload - our 0install will detect the new feed automatically
			iface = self.config.iface_cache.get_interface(forced_iface_uri)
			reader.update_from_cache(iface, iface_cache = self.config.iface_cache)
			self.config.iface_cache.get_feed(local_feed, force = True)

			# Write it out - 0install will add the feed so that older 0install versions can find it
			writer.save_interface(iface)
		except:
			self.note("\nBuild failed: leaving build directory %s for inspection...\n" % tmpdir)
			raise
		else:
			# Can't delete current directory on Windows, so move to parent first
			os.chdir(os.path.join(tmpdir, os.path.pardir))

			ro_rmtree(tmpdir)