Cope better if no GUI is available
[zeroinstall.git] / zeroinstall / cmd / select.py
blob7726b1b7e845f3f417cc7be603e3dc751e5bb800
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 __future__ import print_function
10 import os, sys
11 import logging
13 from zeroinstall import _
14 from zeroinstall.cmd import UsageError
15 from zeroinstall.injector import model, selections, requirements
16 from zeroinstall.injector.driver import Driver
17 from zeroinstall.support import tasks
19 syntax = "URI"
21 def add_generic_select_options(parser):
22 """All options for selecting."""
23 parser.add_option("", "--before", help=_("choose a version before this"), metavar='VERSION')
24 parser.add_option("", "--command", help=_("command to select"), metavar='COMMAND')
25 parser.add_option("", "--cpu", help=_("target CPU type"), metavar='CPU')
26 parser.add_option("", "--message", help=_("message to display when interacting with user"))
27 parser.add_option("", "--not-before", help=_("minimum version to choose"), metavar='VERSION')
28 parser.add_option("-o", "--offline", help=_("try to avoid using the network"), action='store_true')
29 parser.add_option("", "--os", help=_("target operation system type"), metavar='OS')
30 parser.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action='store_true')
31 parser.add_option("-s", "--source", help=_("select source code"), action='store_true')
33 def add_options(parser):
34 """Options for 'select' and 'download' (but not 'run')"""
35 add_generic_select_options(parser)
36 parser.add_option("", "--xml", help=_("write selected versions as XML"), action='store_true')
38 def get_selections(config, options, iface_uri, select_only, download_only, test_callback):
39 """Get selections for iface_uri, according to the options passed.
40 Will switch to GUI mode if necessary.
41 @param options: options from OptionParser
42 @param iface_uri: canonical URI of the interface
43 @param select_only: return immediately even if the selected versions aren't cached
44 @param download_only: wait for stale feeds, and display GUI button as Download, not Run
45 @return: the selected versions, or None if the user cancels
46 @rtype: L{selections.Selections} | None
47 """
48 if options.offline:
49 config.network_use = model.network_offline
51 iface_cache = config.iface_cache
53 # Try to load it as a feed. If it is a feed, it'll get cached. If not, it's a
54 # selections document and we return immediately.
55 maybe_selections = iface_cache.get_feed(iface_uri, selections_ok = True)
56 if isinstance(maybe_selections, selections.Selections):
57 if not select_only:
58 blocker = maybe_selections.download_missing(config)
59 if blocker:
60 logging.info(_("Waiting for selected implementations to be downloaded..."))
61 tasks.wait_for_blocker(blocker)
62 return maybe_selections
64 r = requirements.Requirements(iface_uri)
65 r.parse_options(options)
67 return get_selections_for(r, config, options, select_only, download_only, test_callback)
69 def get_selections_for(requirements, config, options, select_only, download_only, test_callback):
70 """Get selections for given requirements.
71 @since: 1.9"""
72 if options.offline:
73 config.network_use = model.network_offline
75 iface_cache = config.iface_cache
77 driver = Driver(config = config, requirements = requirements)
79 # Note that need_download() triggers a solve
80 if options.refresh or options.gui:
81 # We could run immediately, but the user asked us not to
82 can_run_immediately = False
83 else:
84 if select_only:
85 # --select-only: we only care that we've made a selection, not that we've cached the implementations
86 driver.need_download()
87 can_run_immediately = driver.solver.ready
88 else:
89 can_run_immediately = not driver.need_download()
91 stale_feeds = [feed for feed in driver.solver.feeds_used if
92 not feed.startswith('distribution:') and # Ignore (memory-only) PackageKit feeds
93 iface_cache.is_stale(feed, config.freshness)]
95 if download_only and stale_feeds:
96 can_run_immediately = False
98 if can_run_immediately:
99 if stale_feeds:
100 if config.network_use == model.network_offline:
101 logging.debug(_("No doing background update because we are in off-line mode."))
102 else:
103 # There are feeds we should update, but we can run without them.
104 # Do the update in the background while the program is running.
105 from zeroinstall.injector import background
106 background.spawn_background_update(driver, options.verbose)
107 return driver.solver.selections
109 # If we need to download anything, we might as well
110 # refresh all the feeds first.
111 options.refresh = True
113 if options.gui != False:
114 # If the user didn't say whether to use the GUI, choose for them.
115 gui_args = driver.requirements.get_as_options()
116 if download_only:
117 # Just changes the button's label
118 gui_args.append('--download-only')
119 if options.refresh:
120 gui_args.append('--refresh')
121 if options.verbose:
122 gui_args.insert(0, '--verbose')
123 if options.verbose > 1:
124 gui_args.insert(0, '--verbose')
125 if options.with_store:
126 for x in options.with_store:
127 gui_args += ['--with-store', x]
128 if select_only:
129 gui_args.append('--select-only')
131 from zeroinstall import helpers
132 sels = helpers.get_selections_gui(requirements.interface_uri, gui_args, test_callback, use_gui = options.gui)
134 if not sels:
135 return None # Aborted
136 elif sels is helpers.DontUseGUI:
137 sels = None
138 else:
139 sels = None
141 if sels is None:
142 # Note: --download-only also makes us stop and download stale feeds first.
143 downloaded = driver.solve_and_download_impls(refresh = options.refresh or download_only or False,
144 select_only = select_only)
145 if downloaded:
146 tasks.wait_for_blocker(downloaded)
147 sels = driver.solver.selections
149 return sels
151 def handle(config, options, args):
152 if len(args) != 1:
153 raise UsageError()
155 app = config.app_mgr.lookup_app(args[0], missing_ok = True)
156 if app is not None:
157 sels = app.get_selections()
159 r = app.get_requirements()
160 do_select = r.parse_update_options(options)
161 iface_uri = sels.interface
162 else:
163 iface_uri = model.canonical_iface_uri(args[0])
164 do_select = True
166 if do_select or options.gui:
167 sels = get_selections(config, options, iface_uri,
168 select_only = True, download_only = False, test_callback = None)
169 if not sels:
170 sys.exit(1) # Aborted by user
172 if options.xml:
173 show_xml(sels)
174 else:
175 show_human(sels, config.stores)
176 if app is not None and do_select:
177 print(_("(use '0install update' to save the new parameters)"))
179 def show_xml(sels):
180 doc = sels.toDOM()
181 doc.writexml(sys.stdout)
182 sys.stdout.write('\n')
184 def show_human(sels, stores):
185 done = set() # detect cycles
186 def print_node(uri, commands, indent):
187 if uri in done: return
188 done.add(uri)
189 impl = sels.selections.get(uri, None)
190 print(indent + "- URI:", uri)
191 if impl:
192 print(indent + " Version:", impl.version)
193 #print indent + " Command:", command
194 if impl.id.startswith('package:'):
195 path = "(" + impl.id + ")"
196 else:
197 path = impl.get_path(stores, missing_ok = True) or _("(not cached)")
198 print(indent + " Path:", path)
199 indent += " "
201 deps = impl.dependencies
202 for c in commands:
203 deps += impl.get_command(c).requires
204 for child in deps:
205 print_node(child.interface, child.get_required_commands(), indent)
206 else:
207 print(indent + " No selected version")
210 if sels.command:
211 print_node(sels.interface, [sels.command], "")
212 else:
213 print_node(sels.interface, [], "")