Allow passing a selections document in place of a feed
[zeroinstall/zeroinstall-limyreth.git] / zeroinstall / cmd / select.py
blob33e71f60864060d8e4d2708d75e9df61ad9e5262
1 """
2 The B{0install select} command-line interface.
3 """
5 # Copyright (C) 2011, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from optparse import OptionParser
9 import os, sys
10 import logging
12 from zeroinstall import cmd, SafeException, _
13 from zeroinstall.cmd import UsageError
14 from zeroinstall.injector import model, autopolicy, selections, handler
15 from zeroinstall.injector.iface_cache import iface_cache
17 syntax = "URI"
19 def add_generic_select_options(parser):
20 """All options for selecting."""
21 parser.add_option("", "--before", help=_("choose a version before this"), metavar='VERSION')
22 parser.add_option("", "--command", help=_("command to select"), metavar='COMMAND')
23 parser.add_option("", "--cpu", help=_("target CPU type"), metavar='CPU')
24 parser.add_option("", "--message", help=_("message to display when interacting with user"))
25 parser.add_option("", "--not-before", help=_("minimum version to choose"), metavar='VERSION')
26 parser.add_option("-o", "--offline", help=_("try to avoid using the network"), action='store_true')
27 parser.add_option("", "--os", help=_("target operation system type"), metavar='OS')
28 parser.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action='store_true')
29 parser.add_option("-s", "--source", help=_("select source code"), action='store_true')
31 def add_options(parser):
32 """Options for 'select' and 'download' (but not 'run')"""
33 add_generic_select_options(parser)
34 parser.add_option("", "--xml", help=_("write selected versions as XML"), action='store_true')
36 def get_selections(options, iface_uri, select_only, download_only, test_callback):
37 """Get selections for iface_uri, according to the options passed.
38 Will switch to GUI mode if necessary.
39 @param options: options from OptionParser
40 @param iface_uri: canonical URI of the interface
41 @param select_only: return immediately even if the selected versions aren't cached
42 @param download_only: wait for stale feeds, and display GUI button as Download, not Run
43 @return: the selected versions, or None if the user cancels
44 @rtype: L{selections.Selections} | None
45 """
47 # Try to load it as a feed. If it is a feed, it'll get cached. If not, it's a
48 # selections document and we return immediately.
49 maybe_selections = iface_cache.get_feed(iface_uri, selections_ok = True)
50 if isinstance(maybe_selections, selections.Selections):
51 return maybe_selections
53 root_iface = iface_cache.get_interface(iface_uri)
55 if os.isatty(1):
56 h = handler.ConsoleHandler()
57 else:
58 h = handler.Handler()
59 h.dry_run = bool(options.dry_run)
61 command_name = options.command
62 if command_name is None:
63 command_name = 'run'
64 elif command_name == '':
65 command_name = None
66 policy = autopolicy.AutoPolicy(iface_uri,
67 handler = h,
68 download_only = True, # unused?
69 src = options.source,
70 command = command_name)
72 if options.before or options.not_before:
73 policy.solver.extra_restrictions[root_iface] = [
74 model.VersionRangeRestriction(model.parse_version(options.before),
75 model.parse_version(options.not_before))]
77 if options.os or options.cpu:
78 from zeroinstall.injector import arch
79 policy.target_arch = arch.get_architecture(options.os, options.cpu)
81 if options.offline:
82 policy.network_use = model.network_offline
84 # Note that need_download() triggers a solve
85 if options.refresh or options.gui:
86 # We could run immediately, but the user asked us not to
87 can_run_immediately = False
88 else:
89 if select_only:
90 # --select-only: we only care that we've made a selection, not that we've cached the implementations
91 policy.need_download()
92 can_run_immediately = policy.ready
93 else:
94 can_run_immediately = not policy.need_download()
96 stale_feeds = [feed for feed in policy.solver.feeds_used if
97 not feed.startswith('distribution:') and # Ignore (memory-only) PackageKit feeds
98 policy.is_stale(iface_cache.get_feed(feed))]
100 if download_only and stale_feeds:
101 can_run_immediately = False
103 if can_run_immediately:
104 if stale_feeds:
105 if policy.network_use == model.network_offline:
106 logging.debug(_("No doing background update because we are in off-line mode."))
107 else:
108 # There are feeds we should update, but we can run without them.
109 # Do the update in the background while the program is running.
110 from zeroinstall.injector import background
111 background.spawn_background_update(policy, options.verbose > 0)
112 return policy.solver.selections
114 # If the user didn't say whether to use the GUI, choose for them.
115 if options.gui is None and os.environ.get('DISPLAY', None):
116 options.gui = True
117 # If we need to download anything, we might as well
118 # refresh all the feeds first.
119 options.refresh = True
120 logging.info(_("Switching to GUI mode... (use --console to disable)"))
122 if options.gui:
123 gui_args = []
124 if download_only:
125 # Just changes the button's label
126 gui_args.append('--download-only')
127 if options.refresh:
128 gui_args.append('--refresh')
129 if options.not_before:
130 gui_args.insert(0, options.not_before)
131 gui_args.insert(0, '--not-before')
132 if options.before:
133 gui_args.insert(0, options.before)
134 gui_args.insert(0, '--before')
135 if options.source:
136 gui_args.insert(0, '--source')
137 if options.message:
138 gui_args.insert(0, options.message)
139 gui_args.insert(0, '--message')
140 if options.verbose:
141 gui_args.insert(0, '--verbose')
142 if options.verbose > 1:
143 gui_args.insert(0, '--verbose')
144 if options.cpu:
145 gui_args.insert(0, options.cpu)
146 gui_args.insert(0, '--cpu')
147 if options.os:
148 gui_args.insert(0, options.os)
149 gui_args.insert(0, '--os')
150 if options.with_store:
151 for x in options.with_store:
152 gui_args += ['--with-store', x]
153 if select_only:
154 gui_args.append('--select-only')
155 if command_name is not None:
156 gui_args.append('--command')
157 gui_args.append(command_name)
159 from zeroinstall import helpers
160 sels = helpers.get_selections_gui(iface_uri, gui_args, test_callback)
162 if not sels:
163 return None # Aborted
164 else:
165 # Note: --download-only also makes us stop and download stale feeds first.
166 downloaded = policy.solve_and_download_impls(refresh = options.refresh or download_only or False,
167 select_only = select_only)
168 if downloaded:
169 policy.handler.wait_for_blocker(downloaded)
170 sels = selections.Selections(policy)
172 return sels
174 def handle(options, args):
175 if len(args) != 1:
176 raise UsageError()
177 iface_uri = model.canonical_iface_uri(args[0])
179 sels = get_selections(options, iface_uri,
180 select_only = True, download_only = False, test_callback = None)
181 if not sels:
182 sys.exit(1) # Aborted by user
184 if options.xml:
185 show_xml(sels)
186 else:
187 show_human(sels)
189 def show_xml(sels):
190 doc = sels.toDOM()
191 doc.writexml(sys.stdout)
192 sys.stdout.write('\n')
194 def show_human(sels):
195 from zeroinstall import zerostore
196 done = set() # detect cycles
197 def print_node(uri, command, indent):
198 if uri in done: return
199 done.add(uri)
200 impl = sels.selections.get(uri, None)
201 print indent + "- URI:", uri
202 if impl:
203 print indent + " Version:", impl.version
204 try:
205 if impl.id.startswith('package:'):
206 path = "(" + impl.id + ")"
207 else:
208 path = impl.local_path or iface_cache.stores.lookup_any(impl.digests)
209 except zerostore.NotStored:
210 path = "(not cached)"
211 print indent + " Path:", path
212 indent += " "
213 deps = impl.dependencies
214 if command is not None:
215 deps += sels.commands[command].requires
216 for child in deps:
217 if isinstance(child, model.InterfaceDependency):
218 if child.qdom.name == 'runner':
219 child_command = command + 1
220 else:
221 child_command = None
222 print_node(child.interface, child_command, indent)
223 else:
224 print indent + " No selected version"
227 if sels.commands:
228 print_node(sels.interface, 0, "")
229 else:
230 print_node(sels.interface, None, "")