Large-scale API cleanup
[zeroinstall/zeroinstall-afb.git] / zeroinstall / cmd / select.py
blobff1d7b44bd67517f863ccf0fa4f6304564fc4b05
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 import os, sys
9 import logging
11 from zeroinstall import _
12 from zeroinstall.cmd import UsageError
13 from zeroinstall.injector import model, selections, requirements
14 from zeroinstall.injector.policy import Policy
16 syntax = "URI"
18 def add_generic_select_options(parser):
19 """All options for selecting."""
20 parser.add_option("", "--before", help=_("choose a version before this"), metavar='VERSION')
21 parser.add_option("", "--command", help=_("command to select"), metavar='COMMAND')
22 parser.add_option("", "--cpu", help=_("target CPU type"), metavar='CPU')
23 parser.add_option("", "--message", help=_("message to display when interacting with user"))
24 parser.add_option("", "--not-before", help=_("minimum version to choose"), metavar='VERSION')
25 parser.add_option("-o", "--offline", help=_("try to avoid using the network"), action='store_true')
26 parser.add_option("", "--os", help=_("target operation system type"), metavar='OS')
27 parser.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action='store_true')
28 parser.add_option("-s", "--source", help=_("select source code"), action='store_true')
29 parser.add_option("", "--systray", help=_("download in the background"), 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(config, 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 """
46 if options.offline:
47 config.network_use = model.network_offline
49 # Try to load it as a feed. If it is a feed, it'll get cached. If not, it's a
50 # selections document and we return immediately.
51 maybe_selections = config.iface_cache.get_feed(iface_uri, selections_ok = True)
52 if isinstance(maybe_selections, selections.Selections):
53 if not select_only:
54 blocker = maybe_selections.download_missing(config)
55 if blocker:
56 logging.info(_("Waiting for selected implementations to be downloaded..."))
57 config.handler.wait_for_blocker(blocker)
58 return maybe_selections
60 r = requirements.Requirements(iface_uri)
61 r.parse_options(options)
63 policy = Policy(config = config, requirements = r)
65 # Note that need_download() triggers a solve
66 if options.refresh or options.gui:
67 # We could run immediately, but the user asked us not to
68 can_run_immediately = False
69 else:
70 if select_only:
71 # --select-only: we only care that we've made a selection, not that we've cached the implementations
72 policy.need_download()
73 can_run_immediately = policy.ready
74 else:
75 can_run_immediately = not policy.need_download()
77 stale_feeds = [feed for feed in policy.solver.feeds_used if
78 not feed.startswith('distribution:') and # Ignore (memory-only) PackageKit feeds
79 policy.is_stale(config.iface_cache.get_feed(feed))]
81 if download_only and stale_feeds:
82 can_run_immediately = False
84 if can_run_immediately:
85 if stale_feeds:
86 if policy.network_use == model.network_offline:
87 logging.debug(_("No doing background update because we are in off-line mode."))
88 else:
89 # There are feeds we should update, but we can run without them.
90 # Do the update in the background while the program is running.
91 from zeroinstall.injector import background
92 background.spawn_background_update(policy, options.verbose > 0)
93 return policy.solver.selections
95 # If the user didn't say whether to use the GUI, choose for them.
96 if options.gui is None and os.environ.get('DISPLAY', None):
97 options.gui = True
98 # If we need to download anything, we might as well
99 # refresh all the feeds first.
100 options.refresh = True
101 logging.info(_("Switching to GUI mode... (use --console to disable)"))
103 if options.gui:
104 gui_args = policy.requirements.get_as_options()
105 if download_only:
106 # Just changes the button's label
107 gui_args.append('--download-only')
108 if options.refresh:
109 gui_args.append('--refresh')
110 if options.verbose:
111 gui_args.insert(0, '--verbose')
112 if options.verbose > 1:
113 gui_args.insert(0, '--verbose')
114 if options.with_store:
115 for x in options.with_store:
116 gui_args += ['--with-store', x]
117 if select_only:
118 gui_args.append('--select-only')
119 if options.systray:
120 gui_args.append('--systray')
122 from zeroinstall import helpers
123 sels = helpers.get_selections_gui(iface_uri, gui_args, test_callback)
125 if not sels:
126 return None # Aborted
127 else:
128 # Note: --download-only also makes us stop and download stale feeds first.
129 downloaded = policy.solve_and_download_impls(refresh = options.refresh or download_only or False,
130 select_only = select_only)
131 if downloaded:
132 config.handler.wait_for_blocker(downloaded)
133 sels = selections.Selections(policy)
135 return sels
137 def handle(config, options, args):
138 if len(args) != 1:
139 raise UsageError()
140 iface_uri = model.canonical_iface_uri(args[0])
142 sels = get_selections(config, options, iface_uri,
143 select_only = True, download_only = False, test_callback = None)
144 if not sels:
145 sys.exit(1) # Aborted by user
147 if options.xml:
148 show_xml(sels)
149 else:
150 show_human(sels, config.stores)
152 def show_xml(sels):
153 doc = sels.toDOM()
154 doc.writexml(sys.stdout)
155 sys.stdout.write('\n')
157 def show_human(sels, stores):
158 from zeroinstall import zerostore
159 done = set() # detect cycles
160 def print_node(uri, command, indent):
161 if uri in done: return
162 done.add(uri)
163 impl = sels.selections.get(uri, None)
164 print indent + "- URI:", uri
165 if impl:
166 print indent + " Version:", impl.version
167 try:
168 if impl.id.startswith('package:'):
169 path = "(" + impl.id + ")"
170 else:
171 path = impl.local_path or stores.lookup_any(impl.digests)
172 except zerostore.NotStored:
173 path = "(not cached)"
174 print indent + " Path:", path
175 indent += " "
176 deps = impl.dependencies
177 if command is not None:
178 deps += sels.commands[command].requires
179 for child in deps:
180 if isinstance(child, model.InterfaceDependency):
181 if child.qdom.name == 'runner':
182 child_command = command + 1
183 else:
184 child_command = None
185 print_node(child.interface, child_command, indent)
186 else:
187 print indent + " No selected version"
190 if sels.commands:
191 print_node(sels.interface, 0, "")
192 else:
193 print_node(sels.interface, None, "")