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
)
165 if options
.before
or options
.not_before
:
166 policy
.solver
.extra_restrictions
[root_iface
] = [model
.VersionRangeRestriction(model
.parse_version(options
.before
),
167 model
.parse_version(options
.not_before
))]
169 if options
.os
or options
.cpu
:
170 from zeroinstall
.injector
import arch
171 policy
.target_arch
= arch
.get_architecture(options
.os
, options
.cpu
)
174 policy
.network_use
= model
.network_offline
176 if options
.get_selections
:
178 raise SafeException(_("Can't use arguments with --get-selections"))
180 raise SafeException(_("Can't use --main with --get-selections"))
182 # Note that need_download() triggers a solve
183 if options
.refresh
or options
.gui
:
184 # We could run immediately, but the user asked us not to
185 can_run_immediately
= False
187 if options
.select_only
:
188 # --select-only: we only care that we've made a selection, not that we've cached the implementations
189 policy
.need_download()
190 can_run_immediately
= policy
.ready
192 can_run_immediately
= not policy
.need_download()
194 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if
195 not feed
.startswith('distribution:') and # Ignore (memory-only) PackageKit feeds
196 policy
.is_stale(iface_cache
.get_feed(feed
))]
198 if options
.download_only
and stale_feeds
:
199 can_run_immediately
= False
201 if can_run_immediately
:
203 if policy
.network_use
== model
.network_offline
:
204 logging
.debug(_("No doing background update because we are in off-line mode."))
206 # There are feeds we should update, but we can run without them.
207 # Do the update in the background while the program is running.
209 background
.spawn_background_update(policy
, options
.verbose
> 0)
210 if options
.get_selections
:
211 _get_selections(selections
.Selections(policy
), options
)
213 if not options
.download_only
:
214 from zeroinstall
.injector
import run
215 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
217 logging
.info(_("Downloads done (download-only mode)"))
218 assert options
.dry_run
or options
.download_only
221 # If the user didn't say whether to use the GUI, choose for them.
222 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
224 # If we need to download anything, we might as well
225 # refresh all the interfaces first. Also, this triggers
226 # the 'checking for updates' box, which is non-interactive
227 # when there are no changes to the selection.
228 options
.refresh
= True
229 logging
.info(_("Switching to GUI mode... (use --console to disable)"))
234 from zeroinstall
.injector
import run
237 if options
.download_only
:
238 # Just changes the button's label
239 gui_args
.append('--download-only')
241 gui_args
.append('--refresh')
243 gui_args
.append('--systray')
244 if options
.not_before
:
245 gui_args
.insert(0, options
.not_before
)
246 gui_args
.insert(0, '--not-before')
248 gui_args
.insert(0, options
.before
)
249 gui_args
.insert(0, '--before')
251 gui_args
.insert(0, '--source')
253 gui_args
.insert(0, options
.message
)
254 gui_args
.insert(0, '--message')
256 gui_args
.insert(0, '--verbose')
257 if options
.verbose
> 1:
258 gui_args
.insert(0, '--verbose')
260 gui_args
.insert(0, options
.cpu
)
261 gui_args
.insert(0, '--cpu')
263 gui_args
.insert(0, options
.os
)
264 gui_args
.insert(0, '--os')
265 if options
.with_store
:
266 for x
in options
.with_store
:
267 gui_args
+= ['--with-store', x
]
268 if options
.select_only
:
269 gui_args
.append('--select-only')
270 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
272 sys
.exit(1) # Aborted
274 # Note: --download-only also makes us stop and download stale feeds first.
275 downloaded
= policy
.solve_and_download_impls(refresh
= options
.refresh
or options
.download_only
or False,
276 select_only
= bool(options
.select_only
))
278 policy
.handler
.wait_for_blocker(downloaded
)
279 sels
= selections
.Selections(policy
)
281 if options
.get_selections
:
282 _get_selections(sels
, options
)
283 elif not options
.download_only
:
284 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
286 except NeedDownload
, ex
:
287 # This only happens for dry runs
290 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
291 """Run the GUI to get the selections.
292 prog_args and options are used only if the GUI requests a test.
294 from zeroinstall
import helpers
295 def test_callback(sels
):
296 from zeroinstall
.injector
import run
297 return run
.test_selections(sels
, prog_args
,
298 bool(options
and options
.dry_run
),
299 options
and options
.main
)
300 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
302 def _download_missing_selections(options
, sels
):
303 from zeroinstall
.injector
import fetch
304 from zeroinstall
.injector
.handler
import Handler
305 handler
= Handler(dry_run
= options
.dry_run
)
306 fetcher
= fetch
.Fetcher(handler
)
307 blocker
= sels
.download_missing(iface_cache
, fetcher
)
309 logging
.info(_("Waiting for selected implementations to be downloaded..."))
310 handler
.wait_for_blocker(blocker
)
312 def _get_selections(sels
, options
):
314 from zeroinstall
import zerostore
315 done
= set() # detect cycles
316 def print_node(uri
, indent
):
317 if uri
in done
: return
319 impl
= sels
.selections
.get(uri
, None)
320 print indent
+ "- URI:", uri
322 print indent
+ " Version:", impl
.version
324 if impl
.id.startswith('package:'):
325 path
= "(" + impl
.id + ")"
327 path
= impl
.local_path
or iface_cache
.stores
.lookup_any(impl
.digests
)
328 except zerostore
.NotStored
:
329 path
= "(not cached)"
330 print indent
+ " Path:", path
332 for child
in impl
.dependencies
:
333 if isinstance(child
, model
.InterfaceDependency
):
334 print_node(child
.interface
, indent
)
336 print indent
+ " No selected version"
339 print_node(sels
.interface
, "")
342 doc
.writexml(sys
.stdout
)
343 sys
.stdout
.write('\n')
345 class UsageError(Exception): pass
347 def main(command_args
):
348 """Act as if 0launch was run with the given arguments.
349 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
350 @type command_args: [str]
352 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
353 for std
in (0, 1, 2):
357 fd
= os
.open('/dev/null', os
.O_RDONLY
)
362 parser
= OptionParser(usage
=_("usage: %prog [options] interface [args]\n"
363 " %prog --list [search-term]\n"
364 " %prog --import [signed-interface-files]\n"
365 " %prog --feed [interface]"))
366 parser
.add_option("", "--before", help=_("choose a version before this"), metavar
='VERSION')
367 parser
.add_option("-c", "--console", help=_("never use GUI"), action
='store_false', dest
='gui')
368 parser
.add_option("", "--cpu", help=_("target CPU type"), metavar
='CPU')
369 parser
.add_option("-d", "--download-only", help=_("fetch but don't run"), action
='store_true')
370 parser
.add_option("-D", "--dry-run", help=_("just print actions"), action
='store_true')
371 parser
.add_option("-f", "--feed", help=_("add or remove a feed"), action
='store_true')
372 parser
.add_option("", "--get-selections", help=_("write selected versions as XML"), action
='store_true')
373 parser
.add_option("-g", "--gui", help=_("show graphical policy editor"), action
='store_true')
374 parser
.add_option("-i", "--import", help=_("import from files, not from the network"), action
='store_true')
375 parser
.add_option("-l", "--list", help=_("list all known interfaces"), action
='store_true')
376 parser
.add_option("-m", "--main", help=_("name of the file to execute"))
377 parser
.add_option("", "--message", help=_("message to display when interacting with user"))
378 parser
.add_option("", "--not-before", help=_("minimum version to choose"), metavar
='VERSION')
379 parser
.add_option("", "--os", help=_("target operation system type"), metavar
='OS')
380 parser
.add_option("-o", "--offline", help=_("try to avoid using the network"), action
='store_true')
381 parser
.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action
='store_true')
382 parser
.add_option("", "--select-only", help=_("only download the feeds"), action
='store_true')
383 parser
.add_option("", "--set-selections", help=_("run versions specified in XML file"), metavar
='FILE')
384 parser
.add_option("", "--show", help=_("show where components are installed"), action
='store_true')
385 parser
.add_option("-s", "--source", help=_("select source code"), action
='store_true')
386 parser
.add_option("", "--systray", help=_("download in the background"), action
='store_true')
387 parser
.add_option("-v", "--verbose", help=_("more verbose output"), action
='count')
388 parser
.add_option("-V", "--version", help=_("display version information"), action
='store_true')
389 parser
.add_option("", "--with-store", help=_("add an implementation cache"), action
='append', metavar
='DIR')
390 parser
.add_option("-w", "--wrapper", help=_("execute program using a debugger, etc"), metavar
='COMMAND')
391 parser
.disable_interspersed_args()
393 (options
, args
) = parser
.parse_args(command_args
)
396 logger
= logging
.getLogger()
397 if options
.verbose
== 1:
398 logger
.setLevel(logging
.INFO
)
400 logger
.setLevel(logging
.DEBUG
)
402 logging
.info(_("Running 0launch %(version)s %(args)s; Python %(python_version)s"), {'version': zeroinstall
.version
, 'args': repr(args
), 'python_version': sys
.version
})
404 if options
.select_only
or options
.show
:
405 options
.download_only
= True
408 options
.get_selections
= True
410 if options
.with_store
:
411 from zeroinstall
import zerostore
412 for x
in options
.with_store
:
413 iface_cache
.stores
.stores
.append(zerostore
.Store(os
.path
.abspath(x
)))
414 logging
.info(_("Stores search path is now %s"), iface_cache
.stores
.stores
)
418 _list_interfaces(args
)
419 elif options
.version
:
421 print "0launch (zero-install) " + zeroinstall
.version
422 print "Copyright (C) 2010 Thomas Leonard"
423 print _("This program comes with ABSOLUTELY NO WARRANTY,"
424 "\nto the extent permitted by law."
425 "\nYou may redistribute copies of this program"
426 "\nunder the terms of the GNU Lesser General Public License."
427 "\nFor more information about these matters, see the file named COPYING.")
428 elif options
.set_selections
:
429 from zeroinstall
.injector
import qdom
, run
430 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
431 _download_missing_selections(options
, sels
)
432 if options
.get_selections
:
433 _get_selections(sels
, options
)
434 elif not options
.download_only
:
435 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
436 elif getattr(options
, 'import'):
439 _manage_feeds(options
, args
)
441 _normal_mode(options
, args
)
445 except SafeException
, ex
:
446 if options
.verbose
: raise
448 print >>sys
.stderr
, unicode(ex
)
450 print >>sys
.stderr
, repr(ex
)