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 command_name
= options
.command
161 if command_name
is None:
163 elif command_name
== '':
165 policy
= autopolicy
.AutoPolicy(iface_uri
,
167 download_only
= bool(options
.download_only
),
168 src
= options
.source
,
169 command
= command_name
)
171 if options
.before
or options
.not_before
:
172 policy
.solver
.extra_restrictions
[root_iface
] = [model
.VersionRangeRestriction(model
.parse_version(options
.before
),
173 model
.parse_version(options
.not_before
))]
175 if options
.os
or options
.cpu
:
176 from zeroinstall
.injector
import arch
177 policy
.target_arch
= arch
.get_architecture(options
.os
, options
.cpu
)
180 policy
.network_use
= model
.network_offline
182 if options
.get_selections
:
184 raise SafeException(_("Can't use arguments with --get-selections"))
186 raise SafeException(_("Can't use --main with --get-selections"))
188 # Note that need_download() triggers a solve
189 if options
.refresh
or options
.gui
:
190 # We could run immediately, but the user asked us not to
191 can_run_immediately
= False
193 if options
.select_only
:
194 # --select-only: we only care that we've made a selection, not that we've cached the implementations
195 policy
.need_download()
196 can_run_immediately
= policy
.ready
198 can_run_immediately
= not policy
.need_download()
200 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if
201 not feed
.startswith('distribution:') and # Ignore (memory-only) PackageKit feeds
202 policy
.is_stale(iface_cache
.get_feed(feed
))]
204 if options
.download_only
and stale_feeds
:
205 can_run_immediately
= False
207 if can_run_immediately
:
209 if policy
.network_use
== model
.network_offline
:
210 logging
.debug(_("No doing background update because we are in off-line mode."))
212 # There are feeds we should update, but we can run without them.
213 # Do the update in the background while the program is running.
215 background
.spawn_background_update(policy
, options
.verbose
> 0)
216 if options
.get_selections
:
217 _get_selections(selections
.Selections(policy
), options
)
219 if not options
.download_only
:
220 from zeroinstall
.injector
import run
221 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
223 logging
.info(_("Downloads done (download-only mode)"))
224 assert options
.dry_run
or options
.download_only
227 # If the user didn't say whether to use the GUI, choose for them.
228 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
230 # If we need to download anything, we might as well
231 # refresh all the interfaces first. Also, this triggers
232 # the 'checking for updates' box, which is non-interactive
233 # when there are no changes to the selection.
234 options
.refresh
= True
235 logging
.info(_("Switching to GUI mode... (use --console to disable)"))
240 from zeroinstall
.injector
import run
243 if options
.download_only
:
244 # Just changes the button's label
245 gui_args
.append('--download-only')
247 gui_args
.append('--refresh')
249 gui_args
.append('--systray')
250 if options
.not_before
:
251 gui_args
.insert(0, options
.not_before
)
252 gui_args
.insert(0, '--not-before')
254 gui_args
.insert(0, options
.before
)
255 gui_args
.insert(0, '--before')
257 gui_args
.insert(0, '--source')
259 gui_args
.insert(0, options
.message
)
260 gui_args
.insert(0, '--message')
262 gui_args
.insert(0, '--verbose')
263 if options
.verbose
> 1:
264 gui_args
.insert(0, '--verbose')
266 gui_args
.insert(0, options
.cpu
)
267 gui_args
.insert(0, '--cpu')
269 gui_args
.insert(0, options
.os
)
270 gui_args
.insert(0, '--os')
271 if options
.with_store
:
272 for x
in options
.with_store
:
273 gui_args
+= ['--with-store', x
]
274 if options
.select_only
:
275 gui_args
.append('--select-only')
276 if command_name
is not None:
277 gui_args
.append('--command')
278 gui_args
.append(command_name
)
279 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
281 sys
.exit(1) # Aborted
283 # Note: --download-only also makes us stop and download stale feeds first.
284 downloaded
= policy
.solve_and_download_impls(refresh
= options
.refresh
or options
.download_only
or False,
285 select_only
= bool(options
.select_only
))
287 policy
.handler
.wait_for_blocker(downloaded
)
288 sels
= selections
.Selections(policy
)
290 if options
.get_selections
:
291 _get_selections(sels
, options
)
292 elif not options
.download_only
:
293 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
295 except NeedDownload
, ex
:
296 # This only happens for dry runs
299 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
300 """Run the GUI to get the selections.
301 prog_args and options are used only if the GUI requests a test.
303 from zeroinstall
import helpers
304 def test_callback(sels
):
305 from zeroinstall
.injector
import run
306 return run
.test_selections(sels
, prog_args
,
307 bool(options
and options
.dry_run
),
308 options
and options
.main
)
309 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
311 def _download_missing_selections(options
, sels
):
312 from zeroinstall
.injector
import fetch
313 from zeroinstall
.injector
.handler
import Handler
314 handler
= Handler(dry_run
= options
.dry_run
)
315 fetcher
= fetch
.Fetcher(handler
)
316 blocker
= sels
.download_missing(iface_cache
, fetcher
)
318 logging
.info(_("Waiting for selected implementations to be downloaded..."))
319 handler
.wait_for_blocker(blocker
)
321 def _get_selections(sels
, options
):
323 from zeroinstall
import zerostore
324 done
= set() # detect cycles
325 def print_node(uri
, command
, indent
):
326 if uri
in done
: return
328 impl
= sels
.selections
.get(uri
, None)
329 print indent
+ "- URI:", uri
331 print indent
+ " Version:", impl
.version
333 if impl
.id.startswith('package:'):
334 path
= "(" + impl
.id + ")"
336 path
= impl
.local_path
or iface_cache
.stores
.lookup_any(impl
.digests
)
337 except zerostore
.NotStored
:
338 path
= "(not cached)"
339 print indent
+ " Path:", path
341 deps
= impl
.dependencies
343 deps
+= command
.requires
345 if isinstance(child
, model
.InterfaceDependency
):
346 print_node(child
.interface
, None, indent
)
348 print indent
+ " No selected version"
352 print_node(sels
.interface
, sels
.commands
[0], "")
354 print_node(sels
.interface
, None, "")
358 doc
.writexml(sys
.stdout
)
359 sys
.stdout
.write('\n')
361 class UsageError(Exception): pass
363 def main(command_args
):
364 """Act as if 0launch was run with the given arguments.
365 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
366 @type command_args: [str]
368 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
369 for std
in (0, 1, 2):
373 fd
= os
.open('/dev/null', os
.O_RDONLY
)
378 parser
= OptionParser(usage
=_("usage: %prog [options] interface [args]\n"
379 " %prog --list [search-term]\n"
380 " %prog --import [signed-interface-files]\n"
381 " %prog --feed [interface]"))
382 parser
.add_option("", "--before", help=_("choose a version before this"), metavar
='VERSION')
383 parser
.add_option("", "--command", help=_("command to select"), metavar
='COMMAND')
384 parser
.add_option("-c", "--console", help=_("never use GUI"), action
='store_false', dest
='gui')
385 parser
.add_option("", "--cpu", help=_("target CPU type"), metavar
='CPU')
386 parser
.add_option("-d", "--download-only", help=_("fetch but don't run"), action
='store_true')
387 parser
.add_option("-D", "--dry-run", help=_("just print actions"), action
='store_true')
388 parser
.add_option("-f", "--feed", help=_("add or remove a feed"), action
='store_true')
389 parser
.add_option("", "--get-selections", help=_("write selected versions as XML"), action
='store_true')
390 parser
.add_option("-g", "--gui", help=_("show graphical policy editor"), action
='store_true')
391 parser
.add_option("-i", "--import", help=_("import from files, not from the network"), action
='store_true')
392 parser
.add_option("-l", "--list", help=_("list all known interfaces"), action
='store_true')
393 parser
.add_option("-m", "--main", help=_("name of the file to execute"))
394 parser
.add_option("", "--message", help=_("message to display when interacting with user"))
395 parser
.add_option("", "--not-before", help=_("minimum version to choose"), metavar
='VERSION')
396 parser
.add_option("", "--os", help=_("target operation system type"), metavar
='OS')
397 parser
.add_option("-o", "--offline", help=_("try to avoid using the network"), action
='store_true')
398 parser
.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action
='store_true')
399 parser
.add_option("", "--select-only", help=_("only download the feeds"), action
='store_true')
400 parser
.add_option("", "--set-selections", help=_("run versions specified in XML file"), metavar
='FILE')
401 parser
.add_option("", "--show", help=_("show where components are installed"), action
='store_true')
402 parser
.add_option("-s", "--source", help=_("select source code"), action
='store_true')
403 parser
.add_option("", "--systray", help=_("download in the background"), action
='store_true')
404 parser
.add_option("-v", "--verbose", help=_("more verbose output"), action
='count')
405 parser
.add_option("-V", "--version", help=_("display version information"), action
='store_true')
406 parser
.add_option("", "--with-store", help=_("add an implementation cache"), action
='append', metavar
='DIR')
407 parser
.add_option("-w", "--wrapper", help=_("execute program using a debugger, etc"), metavar
='COMMAND')
408 parser
.disable_interspersed_args()
410 (options
, args
) = parser
.parse_args(command_args
)
413 logger
= logging
.getLogger()
414 if options
.verbose
== 1:
415 logger
.setLevel(logging
.INFO
)
417 logger
.setLevel(logging
.DEBUG
)
419 logging
.info(_("Running 0launch %(version)s %(args)s; Python %(python_version)s"), {'version': zeroinstall
.version
, 'args': repr(args
), 'python_version': sys
.version
})
421 if options
.select_only
or options
.show
:
422 options
.download_only
= True
425 options
.get_selections
= True
427 if options
.with_store
:
428 from zeroinstall
import zerostore
429 for x
in options
.with_store
:
430 iface_cache
.stores
.stores
.append(zerostore
.Store(os
.path
.abspath(x
)))
431 logging
.info(_("Stores search path is now %s"), iface_cache
.stores
.stores
)
435 _list_interfaces(args
)
436 elif options
.version
:
438 print "0launch (zero-install) " + zeroinstall
.version
439 print "Copyright (C) 2010 Thomas Leonard"
440 print _("This program comes with ABSOLUTELY NO WARRANTY,"
441 "\nto the extent permitted by law."
442 "\nYou may redistribute copies of this program"
443 "\nunder the terms of the GNU Lesser General Public License."
444 "\nFor more information about these matters, see the file named COPYING.")
445 elif options
.set_selections
:
446 from zeroinstall
.injector
import qdom
, run
447 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
448 _download_missing_selections(options
, sels
)
449 if options
.get_selections
:
450 _get_selections(sels
, options
)
451 elif not options
.download_only
:
452 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
453 elif getattr(options
, 'import'):
456 _manage_feeds(options
, args
)
458 _normal_mode(options
, args
)
462 except SafeException
, ex
:
463 if options
.verbose
: raise
465 print >>sys
.stderr
, unicode(ex
)
467 print >>sys
.stderr
, repr(ex
)