Python 3: the GUI now works well enough to run programs
[zeroinstall/solver.git] / zeroinstall / cmd / select.py
blob07159906a2d4d65a7a020d5a16a5c8a97df28137
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 the user didn't say whether to use the GUI, choose for them.
110 if options.gui is None and os.environ.get('DISPLAY', None):
111 options.gui = True
112 # If we need to download anything, we might as well
113 # refresh all the feeds first.
114 options.refresh = True
115 logging.info(_("Switching to GUI mode... (use --console to disable)"))
117 if options.gui:
118 gui_args = driver.requirements.get_as_options()
119 if download_only:
120 # Just changes the button's label
121 gui_args.append('--download-only')
122 if options.refresh:
123 gui_args.append('--refresh')
124 if options.verbose:
125 gui_args.insert(0, '--verbose')
126 if options.verbose > 1:
127 gui_args.insert(0, '--verbose')
128 if options.with_store:
129 for x in options.with_store:
130 gui_args += ['--with-store', x]
131 if select_only:
132 gui_args.append('--select-only')
134 from zeroinstall import helpers
135 sels = helpers.get_selections_gui(requirements.interface_uri, gui_args, test_callback)
137 if not sels:
138 return None # Aborted
139 else:
140 # Note: --download-only also makes us stop and download stale feeds first.
141 downloaded = driver.solve_and_download_impls(refresh = options.refresh or download_only or False,
142 select_only = select_only)
143 if downloaded:
144 tasks.wait_for_blocker(downloaded)
145 sels = driver.solver.selections
147 return sels
149 def handle(config, options, args):
150 if len(args) != 1:
151 raise UsageError()
153 app = config.app_mgr.lookup_app(args[0], missing_ok = True)
154 if app is not None:
155 sels = app.get_selections()
157 r = app.get_requirements()
158 do_select = r.parse_update_options(options)
159 iface_uri = sels.interface
160 else:
161 iface_uri = model.canonical_iface_uri(args[0])
162 do_select = True
164 if do_select or options.gui:
165 sels = get_selections(config, options, iface_uri,
166 select_only = True, download_only = False, test_callback = None)
167 if not sels:
168 sys.exit(1) # Aborted by user
170 if options.xml:
171 show_xml(sels)
172 else:
173 show_human(sels, config.stores)
174 if app is not None and do_select:
175 print(_("(use '0install update' to save the new parameters)"))
177 def show_xml(sels):
178 doc = sels.toDOM()
179 doc.writexml(sys.stdout)
180 sys.stdout.write('\n')
182 def show_human(sels, stores):
183 done = set() # detect cycles
184 def print_node(uri, commands, indent):
185 if uri in done: return
186 done.add(uri)
187 impl = sels.selections.get(uri, None)
188 print(indent + "- URI:", uri)
189 if impl:
190 print(indent + " Version:", impl.version)
191 #print indent + " Command:", command
192 if impl.id.startswith('package:'):
193 path = "(" + impl.id + ")"
194 else:
195 path = impl.get_path(stores, missing_ok = True) or _("(not cached)")
196 print(indent + " Path:", path)
197 indent += " "
199 deps = impl.dependencies
200 for c in commands:
201 deps += impl.get_command(c).requires
202 for child in deps:
203 print_node(child.interface, child.get_required_commands(), indent)
204 else:
205 print(indent + " No selected version")
208 if sels.command:
209 print_node(sels.interface, [sels.command], "")
210 else:
211 print_node(sels.interface, [], "")