def process_group(group, group_attrs, base_depends, base_bindings, base_commands): for item in group.childNodes: if item.uri != XMLNS_IFACE: continue if item.name not in ('group', 'implementation', 'package-implementation'): continue # We've found a group or implementation. Scan for dependencies, # bindings and commands. Doing this here means that: # - We can share the code for groups and implementations here. # - The order doesn't matter, because these get processed first. # A side-effect is that the document root cannot contain # these. depends = base_depends[:] bindings = base_bindings[:] commands = base_commands.copy() for attr, command in [('main', 'run'), ('self-test', 'test')]: value = item.attrs.get(attr, None) if value is not None: commands[command] = Command(qdom.Element(XMLNS_IFACE, 'command', {'name': command, 'path': value}), None) for child in item.childNodes: if child.uri != XMLNS_IFACE: continue if child.name == 'requires': dep = process_depends(child, local_dir) if dep is not None: depends.append(dep) elif child.name == 'command': command_name = child.attrs.get('name', None) if not command_name: raise InvalidInterface('Missing name for <command>') commands[command_name] = Command(child, local_dir) elif child.name in binding_names: bindings.append(process_binding(child)) compile_command = item.attrs.get('http://zero-install.sourceforge.net/2006/namespaces/0compile command') if compile_command is not None: commands['compile'] = Command(qdom.Element(XMLNS_IFACE, 'command', {'name': 'compile', 'shell-command': compile_command}), None) item_attrs = _merge_attrs(group_attrs, item) if item.name == 'group': process_group(item, item_attrs, depends, bindings, commands) elif item.name == 'implementation': process_impl(item, item_attrs, depends, bindings, commands) elif item.name == 'package-implementation': if depends: warn("A <package-implementation> with dependencies in %s!", self.url) self._package_implementations.append((item, item_attrs)) else: assert 0
def build_feed(self): def child(parent, name, attrs=None): new = qdom.Element(XMLNS_IFACE, name, attrs or {}) parent.childNodes.append(new) return new root = qdom.Element(XMLNS_IFACE, 'interface', {'uri': uri_prefix + self.name}) child(root, 'name').content = self.name child(root, 'summary').content = self.name i = 0 for version in self.versions.values(): attrs = { 'id': str(i), 'version': str(version.n), 'main': 'dummy', } if version.arch: attrs['arch'] = version.arch impl = child(root, 'implementation', attrs) child(impl, 'manifest-digest', {'sha1new': '1234'}) for lib, min_v, max_v in version.requires: req = child(impl, 'requires', {'interface': uri_prefix + lib}) child(req, 'version', { 'before': str(int(max_v) + 1), 'not-before': min_v }) i += 1 feed = model.ZeroInstallFeed(root) feed.last_modified = 1 return feed
def _set_main(self, path): """"@deprecated: use commands["run"] instead""" if path is None: if "run" in self.commands: del self.commands["run"] else: self.commands["run"] = Command(qdom.Element(XMLNS_IFACE, 'command', {'path': path, 'name': 'run'}), None)
def get_feed(self, url, force=False): feed = iface_cache.IfaceCache.get_feed(self, url, force) if not feed: return None if feed not in self.done: self.done.add(feed) # For each source impl, add a corresponding binary # (the binary has no dependencies as we can't predict them here, # but they're not the same as the source's dependencies) srcs = [ x for x in feed.implementations.itervalues() if x.arch and x.arch.endswith('-src') ] for x in srcs: new_id = '0compile=' + x.id if not new_id in feed.implementations: new = NewBuildImplementation(feed, new_id, None) feed.implementations[new_id] = new new.set_arch(host_arch) new.version = x.version # Give it some dummy commands in case we're using it as a <runner>, etc (otherwise it can't be selected) for cmd_name in get_commands(x): cmd = qdom.Element(namespaces.XMLNS_IFACE, 'command', { 'path': 'new-build', 'name': cmd_name }) new.commands[cmd_name] = model.Command(cmd, None) # Find the <command name='compile'/> add_binary_deps(x, new) return feed
def installed_fixup(self, impl): # OpenSUSE uses _, Fedora uses . impl_id = impl.id.replace('_', '.') # Hack: If we added any Java implementations, find the corresponding JAVA_HOME... if impl_id.startswith('package:rpm:java-1.6.0-openjdk:'): java_version = '1.6.0-openjdk' elif impl_id.startswith('package:rpm:java-1.7.0-openjdk:'): java_version = '1.7.0-openjdk' else: return # On Fedora, unlike Debian, the arch is x86_64, not amd64 java_bin = '/usr/lib/jvm/jre-%s.%s/bin/java' % (java_version, impl.machine) if not os.path.exists(java_bin): # Try without the arch... java_bin = '/usr/lib/jvm/jre-%s/bin/java' % java_version if not os.path.exists(java_bin): logger.info("Java binary not found (%s)", java_bin) if impl.main is None: java_bin = '/usr/bin/java' else: return impl.commands["run"] = model.Command( qdom.Element(namespaces.XMLNS_IFACE, 'command', { 'path': java_bin, 'name': 'run' }), None)
def installed_fixup(self, impl): # Hack: If we added any Java implementations, find the corresponding JAVA_HOME... if impl.id.startswith('package:deb:openjdk-6-jre:'): java_version = '6-openjdk' elif impl.id.startswith('package:deb:openjdk-7-jre:'): java_version = '7-openjdk' else: return if impl.machine == 'x86_64': java_arch = 'amd64' else: java_arch = impl.machine java_bin = '/usr/lib/jvm/java-%s-%s/jre/bin/java' % (java_version, java_arch) if not os.path.exists(java_bin): # Try without the arch... java_bin = '/usr/lib/jvm/java-%s/jre/bin/java' % java_version if not os.path.exists(java_bin): logger.info("Java binary not found (%s)", java_bin) if impl.main is None: java_bin = '/usr/bin/java' else: return impl.commands["run"] = model.Command( qdom.Element(namespaces.XMLNS_IFACE, 'command', { 'path': java_bin, 'name': 'run' }), None)
def testDep(self): b = model.InterfaceDependency('http://foo', element=qdom.Element( namespaces.XMLNS_IFACE, 'requires', {})) assert not b.restrictions assert not b.bindings str(b)
def clone_command_for(command, arch): # This is a bit messy. We need to make a copy of the command, without the # unnecessary <requires> elements. all_dep_elems = set(dep.qdom for dep in command.requires) required_dep_elems = set(dep.qdom for dep in deps_in_use(command, arch)) if all_dep_elems == required_dep_elems: return command # No change dep_elems_to_remove = all_dep_elems - required_dep_elems old_root = command.qdom new_qdom = qdom.Element(old_root.uri, old_root.name, old_root.attrs) new_qdom.childNodes = [ node for node in command.qdom.childNodes if node not in dep_elems_to_remove ] return model.Command(new_qdom, command._local_dir)
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'
def child(parent, name, attrs=None): new = qdom.Element(XMLNS_IFACE, name, attrs or {}) parent.childNodes.append(new) return new
def __init__(self, feed_element, local_path = None, distro = None): """Create a feed object from a DOM. @param feed_element: the root element of a feed file @type feed_element: L{qdom.Element} @param local_path: the pathname of this local feed, or None for remote feeds""" self.local_path = local_path self.implementations = {} self.name = None self.summaries = {} # { lang: str } self.first_summary = None self.descriptions = {} # { lang: str } self.first_description = None self.last_modified = None self.feeds = [] self.feed_for = set() self.metadata = [] self.last_checked = None self._package_implementations = [] if distro is not None: import warnings warnings.warn("distro argument is now ignored", DeprecationWarning, 2) if feed_element is None: return # XXX subclass? assert feed_element.name in ('interface', 'feed'), "Root element should be <interface>, not %s" % feed_element assert feed_element.uri == XMLNS_IFACE, "Wrong namespace on root element: %s" % feed_element.uri main = feed_element.getAttribute('main') #if main: warn("Setting 'main' on the root element is deprecated. Put it on a <group> instead") if local_path: self.url = local_path local_dir = os.path.dirname(local_path) else: assert local_path is None self.url = feed_element.getAttribute('uri') if not self.url: raise InvalidInterface(_("<interface> uri attribute missing")) local_dir = None # Can't have relative paths min_injector_version = feed_element.getAttribute('min-injector-version') if min_injector_version: if parse_version(min_injector_version) > parse_version(version): raise InvalidInterface(_("This feed requires version %(min_version)s or later of " "Zero Install, but I am only version %(version)s. " "You can get a newer version from http://0install.net") % {'min_version': min_injector_version, 'version': version}) for x in feed_element.childNodes: if x.uri != XMLNS_IFACE: self.metadata.append(x) continue if x.name == 'name': self.name = x.content elif x.name == 'description': if self.first_description == None: self.first_description = x.content self.descriptions[x.attrs.get("http://www.w3.org/XML/1998/namespace lang", 'en')] = x.content elif x.name == 'summary': if self.first_summary == None: self.first_summary = x.content self.summaries[x.attrs.get("http://www.w3.org/XML/1998/namespace lang", 'en')] = x.content elif x.name == 'feed-for': feed_iface = x.getAttribute('interface') if not feed_iface: raise InvalidInterface(_('Missing "interface" attribute in <feed-for>')) self.feed_for.add(feed_iface) # Bug report from a Debian/stable user that --feed gets the wrong value. # Can't reproduce (even in a Debian/stable chroot), but add some logging here # in case it happens again. logger.debug(_("Is feed-for %s"), feed_iface) elif x.name == 'feed': feed_src = x.getAttribute('src') if not feed_src: raise InvalidInterface(_('Missing "src" attribute in <feed>')) if feed_src.startswith('http:') or feed_src.startswith('https:') or local_path: if feed_src.startswith('.'): feed_src = os.path.abspath(os.path.join(local_dir, feed_src)) langs = x.getAttribute('langs') if langs: langs = langs.replace('_', '-') self.feeds.append(Feed(feed_src, x.getAttribute('arch'), False, langs = langs)) else: raise InvalidInterface(_("Invalid feed URL '%s'") % feed_src) else: self.metadata.append(x) if not self.name: raise InvalidInterface(_("Missing <name> in feed")) if not self.summary: raise InvalidInterface(_("Missing <summary> in feed")) def process_group(group, group_attrs, base_depends, base_bindings, base_commands): for item in group.childNodes: if item.uri != XMLNS_IFACE: continue if item.name not in ('group', 'implementation', 'package-implementation'): continue # We've found a group or implementation. Scan for dependencies, # bindings and commands. Doing this here means that: # - We can share the code for groups and implementations here. # - The order doesn't matter, because these get processed first. # A side-effect is that the document root cannot contain # these. depends = base_depends[:] bindings = base_bindings[:] commands = base_commands.copy() for attr, command in [('main', 'run'), ('self-test', 'test')]: value = item.attrs.get(attr, None) if value is not None: commands[command] = Command(qdom.Element(XMLNS_IFACE, 'command', {'name': command, 'path': value}), None) for child in item.childNodes: if child.uri != XMLNS_IFACE: continue if child.name in _dependency_names: dep = process_depends(child, local_dir) depends.append(dep) elif child.name == 'command': command_name = child.attrs.get('name', None) if not command_name: raise InvalidInterface('Missing name for <command>') commands[command_name] = Command(child, local_dir) elif child.name in binding_names: bindings.append(process_binding(child)) compile_command = item.attrs.get('http://zero-install.sourceforge.net/2006/namespaces/0compile command') if compile_command is not None: commands['compile'] = Command(qdom.Element(XMLNS_IFACE, 'command', {'name': 'compile', 'shell-command': compile_command}), None) item_attrs = _merge_attrs(group_attrs, item) if item.name == 'group': process_group(item, item_attrs, depends, bindings, commands) elif item.name == 'implementation': process_impl(item, item_attrs, depends, bindings, commands) elif item.name == 'package-implementation': self._package_implementations.append((item, item_attrs, depends)) else: assert 0 def process_impl(item, item_attrs, depends, bindings, commands): id = item.getAttribute('id') if id is None: raise InvalidInterface(_("Missing 'id' attribute on %s") % item) local_path = item_attrs.get('local-path') if local_dir and local_path: abs_local_path = os.path.abspath(os.path.join(local_dir, local_path)) impl = ZeroInstallImplementation(self, id, abs_local_path) elif local_dir and (id.startswith('/') or id.startswith('.')): # For old feeds id = os.path.abspath(os.path.join(local_dir, id)) impl = ZeroInstallImplementation(self, id, id) else: impl = ZeroInstallImplementation(self, id, None) if '=' in id: # In older feeds, the ID was the (single) digest impl.digests.append(id) if id in self.implementations: logger.warn(_("Duplicate ID '%(id)s' in feed '%(feed)s'"), {'id': id, 'feed': self}) self.implementations[id] = impl impl.metadata = item_attrs try: version_mod = item_attrs.get('version-modifier', None) if version_mod: item_attrs['version'] += version_mod del item_attrs['version-modifier'] version = item_attrs['version'] except KeyError: raise InvalidInterface(_("Missing version attribute")) impl.version = parse_version(version) impl.commands = commands impl.released = item_attrs.get('released', None) impl.langs = item_attrs.get('langs', '').replace('_', '-') size = item.getAttribute('size') if size: impl.size = int(size) impl.arch = item_attrs.get('arch', None) try: stability = stability_levels[str(item_attrs['stability'])] except KeyError: stab = str(item_attrs['stability']) if stab != stab.lower(): raise InvalidInterface(_('Stability "%s" invalid - use lower case!') % item_attrs.stability) raise InvalidInterface(_('Stability "%s" invalid') % item_attrs['stability']) if stability >= preferred: raise InvalidInterface(_("Upstream can't set stability to preferred!")) impl.upstream_stability = stability impl.bindings = bindings impl.requires = depends for elem in item.childNodes: if elem.uri != XMLNS_IFACE: continue if elem.name == 'archive': url = elem.getAttribute('href') if not url: raise InvalidInterface(_("Missing href attribute on <archive>")) size = elem.getAttribute('size') if not size: raise InvalidInterface(_("Missing size attribute on <archive>")) impl.add_download_source(url = url, size = int(size), extract = elem.getAttribute('extract'), start_offset = _get_long(elem, 'start-offset'), type = elem.getAttribute('type')) elif elem.name == 'manifest-digest': for aname, avalue in elem.attrs.items(): if ' ' not in aname: impl.digests.append(zerostore.format_algorithm_digest_pair(aname, avalue)) elif elem.name == 'recipe': recipe = Recipe() for recipe_step in elem.childNodes: if recipe_step.uri == XMLNS_IFACE and recipe_step.name == 'archive': url = recipe_step.getAttribute('href') if not url: raise InvalidInterface(_("Missing href attribute on <archive>")) size = recipe_step.getAttribute('size') if not size: raise InvalidInterface(_("Missing size attribute on <archive>")) recipe.steps.append(DownloadSource(None, url = url, size = int(size), extract = recipe_step.getAttribute('extract'), start_offset = _get_long(recipe_step, 'start-offset'), type = recipe_step.getAttribute('type'))) elif recipe_step.uri == XMLNS_IFACE and recipe_step.name == 'rename': source = recipe_step.getAttribute('source') if not source: raise InvalidInterface(_("Missing source attribute on <rename>")) dest = recipe_step.getAttribute('dest') if not dest: raise InvalidInterface(_("Missing dest attribute on <rename>")) recipe.steps.append(RenameStep(source=source, dest=dest)) else: logger.info(_("Unknown step '%s' in recipe; skipping recipe"), recipe_step.name) break else: impl.download_sources.append(recipe) root_attrs = {'stability': 'testing'} root_commands = {} if main: logger.info("Note: @main on document element is deprecated in %s", self) root_commands['run'] = Command(qdom.Element(XMLNS_IFACE, 'command', {'path': main, 'name': 'run'}), None) process_group(feed_element, root_attrs, [], [], root_commands)
def execute_selections(selections, prog_args, dry_run=False, main=None, wrapper=None, stores=None): """Execute program. On success, doesn't return. On failure, raises an Exception. Returns normally only for a successful dry run. @param selections: the selected versions @type selections: L{selections.Selections} @param prog_args: arguments to pass to the program @type prog_args: [str] @param dry_run: if True, just print a message about what would have happened @type dry_run: bool @param main: the name of the binary to run, or None to use the default @type main: str @param wrapper: a command to use to actually run the binary, or None to run the binary directly @type wrapper: str @since: 0.27 @precondition: All implementations are in the cache. """ #assert stores is not None if stores is None: from zeroinstall import zerostore stores = zerostore.Stores() setup = Setup(stores, selections) commands = selections.commands if main is not None: # Replace first command with user's input if main.startswith('/'): main = main[ 1:] # User specified a path relative to the package root else: old_path = commands[0].path if commands else None if not old_path: raise SafeException( _("Can't use a relative replacement main when there is no original one!" )) main = os.path.join( os.path.dirname(old_path), main) # User main is relative to command's name # Copy all child nodes (e.g. <runner>) except for the arguments user_command_element = qdom.Element(namespaces.XMLNS_IFACE, 'command', {'path': main}) if commands: for child in commands[0].qdom.childNodes: if child.uri == namespaces.XMLNS_IFACE and child.name == 'arg': continue user_command_element.childNodes.append(child) user_command = Command(user_command_element, None) else: user_command = None setup.prepare_env() prog_args = setup.build_command(selections.interface, selections.command, user_command) + prog_args if wrapper: prog_args = ['/bin/sh', '-c', wrapper + ' "$@"', '-'] + list(prog_args) if dry_run: print(_("Would execute: %s") % ' '.join(prog_args)) else: logger.info(_("Executing: %s"), prog_args) sys.stdout.flush() sys.stderr.flush() try: env = os.environ.copy() for x in ['0install-runenv-ZEROINSTALL_GPG', 'ZEROINSTALL_GPG']: if x in env: del env[x] os.execve(prog_args[0], prog_args, env) except OSError as ex: raise SafeException( _("Failed to run '%(program_path)s': %(exception)s") % { 'program_path': prog_args[0], 'exception': str(ex) })