From 25400d4540cb089340e5cbf2b49fd78a987ec19a Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Tue, 5 Jul 2011 10:47:21 +0100 Subject: [PATCH] In selections, store elements inside elements This is needed to support executable dependencies. --- tests/old-selections.xml | 26 +++++++++ tests/testselections.py | 17 ++++++ zeroinstall/injector/model.py | 7 ++- zeroinstall/injector/selections.py | 116 +++++++++++++++++++++++++++++-------- zeroinstall/injector/solver.py | 3 +- 5 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 tests/old-selections.xml diff --git a/tests/old-selections.xml b/tests/old-selections.xml new file mode 100644 index 0000000..f4fc9a4 --- /dev/null +++ b/tests/old-selections.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + eu.serscis.Eval + + + diff --git a/tests/testselections.py b/tests/testselections.py index 7fbf4cc..b0a3492 100755 --- a/tests/testselections.py +++ b/tests/testselections.py @@ -145,5 +145,22 @@ class TestSelections(BaseTest): dep_impl = s2.selections[dep_impl_uri] assert dep_impl.id == 'sha1=256' + def testOldCommands(self): + command_feed = os.path.join(mydir, 'old-selections.xml') + with open(command_feed) as stream: + s1 = selections.Selections(qdom.parse(stream)) + self.assertEquals("run", s1.command) + self.assertEquals(2, len(s1.commands)) + self.assertEquals("bin/java", s1.commands[1].path) + + xml = s1.toDOM().toxml("utf-8") + root = qdom.parse(StringIO(xml)) + s2 = selections.Selections(root) + + self.assertEquals("run", s2.command) + self.assertEquals(2, len(s2.commands)) + self.assertEquals("bin/java", s2.commands[1].path) + + if __name__ == '__main__': unittest.main() diff --git a/zeroinstall/injector/model.py b/zeroinstall/injector/model.py index 67d670f..147911e 100644 --- a/zeroinstall/injector/model.py +++ b/zeroinstall/injector/model.py @@ -497,6 +497,9 @@ class Command(object): self.requires # (sets _runner) return self._runner + def __str__(self): + return str(self.qdom) + class Implementation(object): """An Implementation is a package which implements an Interface. @ivar download_sources: list of methods of getting this implementation @@ -887,7 +890,7 @@ class ZeroInstallFeed(object): ('self-test', 'test')]: value = item.attrs.get(attr, None) if value is not None: - commands[command] = Command(qdom.Element(XMLNS_IFACE, 'command', {'path': value}), 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 @@ -904,7 +907,7 @@ class ZeroInstallFeed(object): 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', {'shell-command': compile_command}), None) + commands['compile'] = Command(qdom.Element(XMLNS_IFACE, 'command', {'name': 'compile', 'shell-command': compile_command}), None) item_attrs = _merge_attrs(group_attrs, item) diff --git a/zeroinstall/injector/selections.py b/zeroinstall/injector/selections.py index 7c02f43..1958845 100644 --- a/zeroinstall/injector/selections.py +++ b/zeroinstall/injector/selections.py @@ -56,12 +56,13 @@ class Selection(object): class ImplSelection(Selection): """A Selection created from an Implementation""" - __slots__ = ['impl', 'dependencies', 'attrs'] + __slots__ = ['impl', 'dependencies', 'attrs', '_used_commands'] def __init__(self, iface_uri, impl, dependencies): assert impl self.impl = impl self.dependencies = dependencies + self._used_commands = set() attrs = impl.metadata.copy() attrs['id'] = impl.id @@ -78,37 +79,54 @@ class ImplSelection(Selection): @property def digests(self): return self.impl.digests + def get_command(self, name): + assert name in self._used_commands + return self.impl.commands[name] + + def get_commands(self): + commands = {} + for c in self._used_commands: + commands[c] = self.impl.commands[c] + return commands + class XMLSelection(Selection): """A Selection created by reading an XML selections document. @ivar digests: a list of manifest digests @type digests: [str] """ - __slots__ = ['bindings', 'dependencies', 'attrs', 'digests'] + __slots__ = ['bindings', 'dependencies', 'attrs', 'digests', 'commands'] - def __init__(self, dependencies, bindings = None, attrs = None, digests = None): + def __init__(self, dependencies, bindings = None, attrs = None, digests = None, commands = None): if bindings is None: bindings = [] if digests is None: digests = [] self.dependencies = dependencies self.bindings = bindings self.attrs = attrs self.digests = digests + self.commands = commands assert self.interface assert self.id assert self.version assert self.feed + def get_command(self, name): + return self.commands[name] + + def get_commands(self): + return self.commands + class Selections(object): """ A selected set of components which will make up a complete program. @ivar interface: the interface of the program @type interface: str - @ivar commands: how to run this selection (will contain more than one item if runners are used) - @type commands: [{L{Command}}] + @ivar command: the command to run on 'interface' + @type command: str @ivar selections: the selected implementations @type selections: {str: L{Selection}} """ - __slots__ = ['interface', 'selections', 'commands'] + __slots__ = ['interface', 'selections', 'command'] def __init__(self, source): """Constructor. @@ -116,10 +134,11 @@ class Selections(object): @type source: L{Element} """ self.selections = {} + self.command = None if source is None: - self.commands = [] # (Solver will fill everything in) + pass elif isinstance(source, Policy): import warnings warnings.warn("Use policy.solver.selections instead", DeprecationWarning, 2) @@ -141,31 +160,35 @@ class Selections(object): """Parse and load a selections document. @param root: a saved set of selections.""" self.interface = root.getAttribute('interface') + self.command = root.getAttribute('command') assert self.interface - self.commands = [] + old_commands = [] for selection in root.childNodes: if selection.uri != XMLNS_IFACE: continue if selection.name != 'selection': if selection.name == 'command': - self.commands.append(Command(selection, None)) + old_commands.append(Command(selection, None)) continue requires = [] bindings = [] digests = [] - for dep_elem in selection.childNodes: - if dep_elem.uri != XMLNS_IFACE: + commands = {} + for elem in selection.childNodes: + if elem.uri != XMLNS_IFACE: continue - if dep_elem.name in binding_names: - bindings.append(process_binding(dep_elem)) - elif dep_elem.name == 'requires': - dep = process_depends(dep_elem, None) + if elem.name in binding_names: + bindings.append(process_binding(elem)) + elif elem.name == 'requires': + dep = process_depends(elem, None) requires.append(dep) - elif dep_elem.name == 'manifest-digest': - for aname, avalue in dep_elem.attrs.iteritems(): + elif elem.name == 'manifest-digest': + for aname, avalue in elem.attrs.iteritems(): digests.append('%s=%s' % (aname, avalue)) + elif elem.name == 'command': + commands[elem.getAttribute('name')] = Command(elem, None) # For backwards compatibility, allow getting the digest from the ID sel_id = selection.attrs['id'] @@ -177,17 +200,38 @@ class Selections(object): iface_uri = selection.attrs['interface'] - s = XMLSelection(requires, bindings, selection.attrs, digests) + s = XMLSelection(requires, bindings, selection.attrs, digests, commands) self.selections[iface_uri] = s - if not self.commands: - # Old-style selections document; use the main attribute - if iface_uri == self.interface: + if self.command is None: + # Old style selections document + if old_commands: + # 0launch 0.52 to 1.1 + self.command = 'run' + iface = self.interface + last_command = None + + for command in old_commands: + command.qdom.attrs['name'] = 'run' + self.selections[iface].commands['run'] = command + runner = command.get_runner() + if runner: + iface = runner.interface + else: + iface = None + else: + # 0launch < 0.51 root_sel = self.selections[self.interface] main = root_sel.attrs.get('main', None) if main is not None: - self.commands = [Command(Element(XMLNS_IFACE, 'command', {'path': main}), None)] - + root_sel.commands['run'] = Command(Element(XMLNS_IFACE, 'command', {'path': main}), None) + self.command = 'run' + + elif self.command == '': + # New style, but no command requested + self.command = None + assert not old_commands, " list in new-style selections document!" + def toDOM(self): """Create a DOM document for the selected implementations. The document gives the URI of the root, plus each selected implementation. @@ -207,6 +251,8 @@ class Selections(object): root.setAttributeNS(None, 'interface', self.interface) + root.setAttributeNS(None, 'command', self.command or "") + prefixes = Prefixes(XMLNS_IFACE) for iface, selection in sorted(self.selections.items()): @@ -256,8 +302,8 @@ class Selections(object): for b in dep.bindings: dep_elem.appendChild(b._toxml(doc)) - for command in self.commands: - root.appendChild(command._toxml(doc, prefixes)) + for command in selection.get_commands().values(): + selection_elem.appendChild(command._toxml(doc, prefixes)) for ns, prefix in prefixes.prefixes.items(): root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + prefix, ns) @@ -365,3 +411,23 @@ class Selections(object): def items(self): # Deprecated return list(self.iteritems()) + + @property + def commands(self): + i = self.interface + c = self.command + commands = [] + while c is not None: + sel = self.selections[i] + command = sel.get_command(c) + + commands.append(command) + + runner = command.get_runner() + if not runner: + break + + i = runner.metadata['interface'] + c = runner.qdom.attrs.get('command', 'run') + + return commands diff --git a/zeroinstall/injector/solver.py b/zeroinstall/injector/solver.py index 4985537..5fc67dd 100644 --- a/zeroinstall/injector/solver.py +++ b/zeroinstall/injector/solver.py @@ -654,12 +654,13 @@ class SATSolver(Solver): sel = sels.get(iface, None) if sel: command = sel.impl.commands[name] - self.selections.commands.append(command) + sel._used_commands.add(name) runner = command.get_runner() if runner: add_command(runner.metadata['interface'], _get_command_name(runner)) if command_name is not None: + self.selections.command = command_name add_command(root_interface, command_name) def get_failure_reason(self): -- 2.11.4.GIT