2 The B{0launch} command-line interface.
4 This code is here, rather than in B{0launch} itself, simply so that it gets byte-compiled at
8 from zeroinstall
import _
10 from optparse
import OptionParser
13 from zeroinstall
import SafeException
, NeedDownload
14 from zeroinstall
.injector
import model
, autopolicy
, selections
15 from zeroinstall
.injector
.iface_cache
import iface_cache
17 #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK)
19 #__main__.__builtins__.program_log = program_log
20 #program_log('0launch ' + ' '.join((sys.argv[1:])))
22 def _list_interfaces(args
):
24 matches
= iface_cache
.list_all_interfaces()
26 match
= args
[0].lower()
27 matches
= [i
for i
in iface_cache
.list_all_interfaces() if match
in i
.lower()]
35 def _import_feed(args
):
36 from zeroinstall
.support
import tasks
37 from zeroinstall
.injector
import gpg
, handler
38 from zeroinstall
.injector
.iface_cache
import PendingFeed
39 from xml
.dom
import minidom
40 handler
= handler
.Handler()
43 if not os
.path
.isfile(x
):
44 raise SafeException(_("File '%s' does not exist") % x
)
45 logging
.info(_("Importing from file '%s'"), x
)
47 data
, sigs
= gpg
.check_stream(signed_data
)
48 doc
= minidom
.parseString(data
.read())
49 uri
= doc
.documentElement
.getAttribute('uri')
51 raise SafeException(_("Missing 'uri' attribute on root element in '%s'") % x
)
52 logging
.info(_("Importing information about interface %s"), uri
)
55 pending
= PendingFeed(uri
, signed_data
)
58 keys_downloaded
= tasks
.Task(pending
.download_keys(handler
), "download keys")
59 yield keys_downloaded
.finished
60 tasks
.check(keys_downloaded
.finished
)
61 if not iface_cache
.update_feed_if_trusted(uri
, pending
.sigs
, pending
.new_xml
):
62 from zeroinstall
.injector
import fetch
63 fetcher
= fetch
.Fetcher(handler
)
64 blocker
= handler
.confirm_keys(pending
, fetcher
.fetch_key_info
)
68 if not iface_cache
.update_feed_if_trusted(uri
, pending
.sigs
, pending
.new_xml
):
69 raise SafeException(_("No signing keys trusted; not importing"))
71 task
= tasks
.Task(run(), "import feed")
73 errors
= handler
.wait_for_blocker(task
.finished
)
75 raise SafeException(_("Errors during download: ") + '\n'.join(errors
))
77 def _manage_feeds(options
, args
):
78 from zeroinstall
.injector
import writer
79 from zeroinstall
.injector
.handler
import Handler
80 from zeroinstall
.injector
.policy
import Policy
82 def find_feed_import(iface
, feed_url
):
83 for f
in iface
.extra_feeds
:
88 handler
= Handler(dry_run
= options
.dry_run
)
89 if not args
: raise UsageError()
91 print _("Feed '%s':") % x
+ '\n'
92 x
= model
.canonical_iface_uri(x
)
93 policy
= Policy(x
, handler
)
95 policy
.network_use
= model
.network_offline
97 feed
= iface_cache
.get_feed(x
)
98 if policy
.network_use
!= model
.network_offline
and policy
.is_stale(feed
):
99 blocker
= policy
.fetcher
.download_and_import_feed(x
, iface_cache
.iface_cache
)
100 print _("Downloading feed; please wait...")
101 handler
.wait_for_blocker(blocker
)
104 interfaces
= policy
.get_feed_targets(x
)
105 for i
in range(len(interfaces
)):
106 if find_feed_import(interfaces
[i
], x
):
107 print _("%(index)d) Remove as feed for '%(uri)s'") % {'index': i
+ 1, 'uri': interfaces
[i
].uri
}
109 print _("%(index)d) Add as feed for '%(uri)s'") % {'index': i
+ 1, 'uri': interfaces
[i
].uri
}
113 i
= raw_input(_('Enter a number, or CTRL-C to cancel [1]: ')).strip()
114 except KeyboardInterrupt:
116 raise SafeException(_("Aborted at user request."))
124 if i
> 0 and i
<= len(interfaces
):
126 print _("Invalid number. Try again. (1 to %d)") % len(interfaces
)
127 iface
= interfaces
[i
- 1]
128 feed_import
= find_feed_import(iface
, x
)
130 iface
.extra_feeds
.remove(feed_import
)
132 iface
.extra_feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
133 writer
.save_interface(iface
)
134 print '\n' + _("Feed list for interface '%s' is now:") % iface
.get_name()
135 if iface
.extra_feeds
:
136 for f
in iface
.extra_feeds
:
139 print _("(no feeds)")
141 def _normal_mode(options
, args
):
142 from zeroinstall
.injector
import handler
146 from zeroinstall
import helpers
147 return helpers
.get_selections_gui(None, [])
151 iface_uri
= model
.canonical_iface_uri(args
[0])
152 root_iface
= iface_cache
.get_interface(iface_uri
)
155 h
= handler
.ConsoleHandler()
157 h
= handler
.Handler()
158 h
.dry_run
= bool(options
.dry_run
)
160 policy
= autopolicy
.AutoPolicy(iface_uri
,
162 download_only
= bool(options
.download_only
),
163 src
= options
.source
,
164 command
= options
.command
)
166 if options
.before
or options
.not_before
:
167 policy
.solver
.extra_restrictions
[root_iface
] = [model
.VersionRangeRestriction(model
.parse_version(options
.before
),
168 model
.parse_version(options
.not_before
))]
170 if options
.os
or options
.cpu
:
171 from zeroinstall
.injector
import arch
172 policy
.target_arch
= arch
.get_architecture(options
.os
, options
.cpu
)
175 policy
.network_use
= model
.network_offline
177 if options
.get_selections
:
179 raise SafeException(_("Can't use arguments with --get-selections"))
181 raise SafeException(_("Can't use --main with --get-selections"))
183 # Note that need_download() triggers a solve
184 if options
.refresh
or options
.gui
:
185 # We could run immediately, but the user asked us not to
186 can_run_immediately
= False
188 if options
.select_only
:
189 # --select-only: we only care that we've made a selection, not that we've cached the implementations
190 policy
.need_download()
191 can_run_immediately
= policy
.ready
193 can_run_immediately
= not policy
.need_download()
195 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if
196 not feed
.startswith('distribution:') and # Ignore (memory-only) PackageKit feeds
197 policy
.is_stale(iface_cache
.get_feed(feed
))]
199 if options
.download_only
and stale_feeds
:
200 can_run_immediately
= False
202 if can_run_immediately
:
204 if policy
.network_use
== model
.network_offline
:
205 logging
.debug(_("No doing background update because we are in off-line mode."))
207 # There are feeds we should update, but we can run without them.
208 # Do the update in the background while the program is running.
210 background
.spawn_background_update(policy
, options
.verbose
> 0)
211 if options
.get_selections
:
212 _get_selections(selections
.Selections(policy
), options
)
214 if not options
.download_only
:
215 from zeroinstall
.injector
import run
216 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
218 logging
.info(_("Downloads done (download-only mode)"))
219 assert options
.dry_run
or options
.download_only
222 # If the user didn't say whether to use the GUI, choose for them.
223 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
225 # If we need to download anything, we might as well
226 # refresh all the interfaces first. Also, this triggers
227 # the 'checking for updates' box, which is non-interactive
228 # when there are no changes to the selection.
229 options
.refresh
= True
230 logging
.info(_("Switching to GUI mode... (use --console to disable)"))
235 from zeroinstall
.injector
import run
238 if options
.download_only
:
239 # Just changes the button's label
240 gui_args
.append('--download-only')
242 gui_args
.append('--refresh')
244 gui_args
.append('--systray')
245 if options
.not_before
:
246 gui_args
.insert(0, options
.not_before
)
247 gui_args
.insert(0, '--not-before')
249 gui_args
.insert(0, options
.before
)
250 gui_args
.insert(0, '--before')
252 gui_args
.insert(0, '--source')
254 gui_args
.insert(0, options
.message
)
255 gui_args
.insert(0, '--message')
257 gui_args
.insert(0, '--verbose')
258 if options
.verbose
> 1:
259 gui_args
.insert(0, '--verbose')
261 gui_args
.insert(0, options
.cpu
)
262 gui_args
.insert(0, '--cpu')
264 gui_args
.insert(0, options
.os
)
265 gui_args
.insert(0, '--os')
266 if options
.with_store
:
267 for x
in options
.with_store
:
268 gui_args
+= ['--with-store', x
]
269 if options
.select_only
:
270 gui_args
.append('--select-only')
272 gui_args
.append('--command')
273 gui_args
.append(options
.command
)
274 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
276 sys
.exit(1) # Aborted
278 # Note: --download-only also makes us stop and download stale feeds first.
279 downloaded
= policy
.solve_and_download_impls(refresh
= options
.refresh
or options
.download_only
or False,
280 select_only
= bool(options
.select_only
))
282 policy
.handler
.wait_for_blocker(downloaded
)
283 sels
= selections
.Selections(policy
)
285 if options
.get_selections
:
286 _get_selections(sels
, options
)
287 elif not options
.download_only
:
288 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
290 except NeedDownload
, ex
:
291 # This only happens for dry runs
294 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
295 """Run the GUI to get the selections.
296 prog_args and options are used only if the GUI requests a test.
298 from zeroinstall
import helpers
299 def test_callback(sels
):
300 from zeroinstall
.injector
import run
301 return run
.test_selections(sels
, prog_args
,
302 bool(options
and options
.dry_run
),
303 options
and options
.main
)
304 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
306 def _download_missing_selections(options
, sels
):
307 from zeroinstall
.injector
import fetch
308 from zeroinstall
.injector
.handler
import Handler
309 handler
= Handler(dry_run
= options
.dry_run
)
310 fetcher
= fetch
.Fetcher(handler
)
311 blocker
= sels
.download_missing(iface_cache
, fetcher
)
313 logging
.info(_("Waiting for selected implementations to be downloaded..."))
314 handler
.wait_for_blocker(blocker
)
316 def _get_selections(sels
, options
):
318 from zeroinstall
import zerostore
319 done
= set() # detect cycles
320 def print_node(uri
, command
, indent
):
321 if uri
in done
: return
323 impl
= sels
.selections
.get(uri
, None)
324 print indent
+ "- URI:", uri
326 print indent
+ " Version:", impl
.version
328 if impl
.id.startswith('package:'):
329 path
= "(" + impl
.id + ")"
331 path
= impl
.local_path
or iface_cache
.stores
.lookup_any(impl
.digests
)
332 except zerostore
.NotStored
:
333 path
= "(not cached)"
334 print indent
+ " Path:", path
336 deps
= impl
.dependencies
338 deps
+= command
.requires
340 if isinstance(child
, model
.InterfaceDependency
):
341 print_node(child
.interface
, None, indent
)
343 print indent
+ " No selected version"
347 for command
in sels
.commands
:
348 print_node(uri
, command
, "")
352 doc
.writexml(sys
.stdout
)
353 sys
.stdout
.write('\n')
355 class UsageError(Exception): pass
357 def main(command_args
):
358 """Act as if 0launch was run with the given arguments.
359 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
360 @type command_args: [str]
362 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
363 for std
in (0, 1, 2):
367 fd
= os
.open('/dev/null', os
.O_RDONLY
)
372 parser
= OptionParser(usage
=_("usage: %prog [options] interface [args]\n"
373 " %prog --list [search-term]\n"
374 " %prog --import [signed-interface-files]\n"
375 " %prog --feed [interface]"))
376 parser
.add_option("", "--before", help=_("choose a version before this"), metavar
='VERSION')
377 parser
.add_option("", "--command", help=_("command to select"), metavar
='COMMAND')
378 parser
.add_option("-c", "--console", help=_("never use GUI"), action
='store_false', dest
='gui')
379 parser
.add_option("", "--cpu", help=_("target CPU type"), metavar
='CPU')
380 parser
.add_option("-d", "--download-only", help=_("fetch but don't run"), action
='store_true')
381 parser
.add_option("-D", "--dry-run", help=_("just print actions"), action
='store_true')
382 parser
.add_option("-f", "--feed", help=_("add or remove a feed"), action
='store_true')
383 parser
.add_option("", "--get-selections", help=_("write selected versions as XML"), action
='store_true')
384 parser
.add_option("-g", "--gui", help=_("show graphical policy editor"), action
='store_true')
385 parser
.add_option("-i", "--import", help=_("import from files, not from the network"), action
='store_true')
386 parser
.add_option("-l", "--list", help=_("list all known interfaces"), action
='store_true')
387 parser
.add_option("-m", "--main", help=_("name of the file to execute"))
388 parser
.add_option("", "--message", help=_("message to display when interacting with user"))
389 parser
.add_option("", "--not-before", help=_("minimum version to choose"), metavar
='VERSION')
390 parser
.add_option("", "--os", help=_("target operation system type"), metavar
='OS')
391 parser
.add_option("-o", "--offline", help=_("try to avoid using the network"), action
='store_true')
392 parser
.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action
='store_true')
393 parser
.add_option("", "--select-only", help=_("only download the feeds"), action
='store_true')
394 parser
.add_option("", "--set-selections", help=_("run versions specified in XML file"), metavar
='FILE')
395 parser
.add_option("", "--show", help=_("show where components are installed"), action
='store_true')
396 parser
.add_option("-s", "--source", help=_("select source code"), action
='store_true')
397 parser
.add_option("", "--systray", help=_("download in the background"), action
='store_true')
398 parser
.add_option("-v", "--verbose", help=_("more verbose output"), action
='count')
399 parser
.add_option("-V", "--version", help=_("display version information"), action
='store_true')
400 parser
.add_option("", "--with-store", help=_("add an implementation cache"), action
='append', metavar
='DIR')
401 parser
.add_option("-w", "--wrapper", help=_("execute program using a debugger, etc"), metavar
='COMMAND')
402 parser
.disable_interspersed_args()
404 (options
, args
) = parser
.parse_args(command_args
)
407 logger
= logging
.getLogger()
408 if options
.verbose
== 1:
409 logger
.setLevel(logging
.INFO
)
411 logger
.setLevel(logging
.DEBUG
)
413 logging
.info(_("Running 0launch %(version)s %(args)s; Python %(python_version)s"), {'version': zeroinstall
.version
, 'args': repr(args
), 'python_version': sys
.version
})
415 if options
.select_only
or options
.show
:
416 options
.download_only
= True
419 options
.get_selections
= True
421 if options
.with_store
:
422 from zeroinstall
import zerostore
423 for x
in options
.with_store
:
424 iface_cache
.stores
.stores
.append(zerostore
.Store(os
.path
.abspath(x
)))
425 logging
.info(_("Stores search path is now %s"), iface_cache
.stores
.stores
)
429 _list_interfaces(args
)
430 elif options
.version
:
432 print "0launch (zero-install) " + zeroinstall
.version
433 print "Copyright (C) 2010 Thomas Leonard"
434 print _("This program comes with ABSOLUTELY NO WARRANTY,"
435 "\nto the extent permitted by law."
436 "\nYou may redistribute copies of this program"
437 "\nunder the terms of the GNU Lesser General Public License."
438 "\nFor more information about these matters, see the file named COPYING.")
439 elif options
.set_selections
:
440 from zeroinstall
.injector
import qdom
, run
441 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
442 _download_missing_selections(options
, sels
)
443 if options
.get_selections
:
444 _get_selections(sels
, options
)
445 elif not options
.download_only
:
446 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
447 elif getattr(options
, 'import'):
450 _manage_feeds(options
, args
)
452 _normal_mode(options
, args
)
456 except SafeException
, ex
:
457 if options
.verbose
: raise
459 print >>sys
.stderr
, unicode(ex
)
461 print >>sys
.stderr
, repr(ex
)