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 _manage_feeds(options
, args
):
36 from zeroinstall
.injector
import writer
37 from zeroinstall
.injector
.handler
import Handler
38 from zeroinstall
.injector
.policy
import Policy
40 def find_feed_import(iface
, feed_url
):
41 for f
in iface
.extra_feeds
:
46 handler
= Handler(dry_run
= options
.dry_run
)
47 if not args
: raise UsageError()
49 print _("Feed '%s':") % x
+ '\n'
50 x
= model
.canonical_iface_uri(x
)
51 policy
= Policy(x
, handler
)
53 policy
.network_use
= model
.network_offline
55 feed
= iface_cache
.get_feed(x
)
56 if policy
.network_use
!= model
.network_offline
and policy
.is_stale(feed
):
57 blocker
= policy
.fetcher
.download_and_import_feed(x
, iface_cache
.iface_cache
)
58 print _("Downloading feed; please wait...")
59 handler
.wait_for_blocker(blocker
)
62 interfaces
= policy
.get_feed_targets(x
)
63 for i
in range(len(interfaces
)):
64 if find_feed_import(interfaces
[i
], x
):
65 print _("%(index)d) Remove as feed for '%(uri)s'") % {'index': i
+ 1, 'uri': interfaces
[i
].uri
}
67 print _("%(index)d) Add as feed for '%(uri)s'") % {'index': i
+ 1, 'uri': interfaces
[i
].uri
}
71 i
= raw_input(_('Enter a number, or CTRL-C to cancel [1]: ')).strip()
72 except KeyboardInterrupt:
74 raise SafeException(_("Aborted at user request."))
82 if i
> 0 and i
<= len(interfaces
):
84 print _("Invalid number. Try again. (1 to %d)") % len(interfaces
)
85 iface
= interfaces
[i
- 1]
86 feed_import
= find_feed_import(iface
, x
)
88 iface
.extra_feeds
.remove(feed_import
)
90 iface
.extra_feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
91 writer
.save_interface(iface
)
92 print '\n' + _("Feed list for interface '%s' is now:") % iface
.get_name()
94 for f
in iface
.extra_feeds
:
99 def _normal_mode(options
, args
):
100 from zeroinstall
.injector
import handler
104 from zeroinstall
import helpers
105 return helpers
.get_selections_gui(None, [])
109 iface_uri
= model
.canonical_iface_uri(args
[0])
110 root_iface
= iface_cache
.get_interface(iface_uri
)
113 h
= handler
.ConsoleHandler()
115 h
= handler
.Handler()
116 h
.dry_run
= bool(options
.dry_run
)
118 command_name
= options
.command
119 if command_name
is None:
121 elif command_name
== '':
123 policy
= autopolicy
.AutoPolicy(iface_uri
,
125 download_only
= bool(options
.download_only
),
126 src
= options
.source
,
127 command
= command_name
)
129 if options
.before
or options
.not_before
:
130 policy
.solver
.extra_restrictions
[root_iface
] = [model
.VersionRangeRestriction(model
.parse_version(options
.before
),
131 model
.parse_version(options
.not_before
))]
133 if options
.os
or options
.cpu
:
134 from zeroinstall
.injector
import arch
135 policy
.target_arch
= arch
.get_architecture(options
.os
, options
.cpu
)
138 policy
.network_use
= model
.network_offline
140 if options
.get_selections
:
142 raise SafeException(_("Can't use arguments with --get-selections"))
144 raise SafeException(_("Can't use --main with --get-selections"))
146 # Note that need_download() triggers a solve
147 if options
.refresh
or options
.gui
:
148 # We could run immediately, but the user asked us not to
149 can_run_immediately
= False
151 if options
.select_only
:
152 # --select-only: we only care that we've made a selection, not that we've cached the implementations
153 policy
.need_download()
154 can_run_immediately
= policy
.ready
156 can_run_immediately
= not policy
.need_download()
158 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if
159 not feed
.startswith('distribution:') and # Ignore (memory-only) PackageKit feeds
160 policy
.is_stale(iface_cache
.get_feed(feed
))]
162 if options
.download_only
and stale_feeds
:
163 can_run_immediately
= False
165 if can_run_immediately
:
167 if policy
.network_use
== model
.network_offline
:
168 logging
.debug(_("No doing background update because we are in off-line mode."))
170 # There are feeds we should update, but we can run without them.
171 # Do the update in the background while the program is running.
173 background
.spawn_background_update(policy
, options
.verbose
> 0)
174 if options
.get_selections
:
175 _get_selections(selections
.Selections(policy
), options
)
177 if not options
.download_only
:
178 from zeroinstall
.injector
import run
179 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
181 logging
.info(_("Downloads done (download-only mode)"))
182 assert options
.dry_run
or options
.download_only
185 # If the user didn't say whether to use the GUI, choose for them.
186 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
188 # If we need to download anything, we might as well
189 # refresh all the interfaces first. Also, this triggers
190 # the 'checking for updates' box, which is non-interactive
191 # when there are no changes to the selection.
192 options
.refresh
= True
193 logging
.info(_("Switching to GUI mode... (use --console to disable)"))
198 from zeroinstall
.injector
import run
201 if options
.download_only
:
202 # Just changes the button's label
203 gui_args
.append('--download-only')
205 gui_args
.append('--refresh')
207 gui_args
.append('--systray')
208 if options
.not_before
:
209 gui_args
.insert(0, options
.not_before
)
210 gui_args
.insert(0, '--not-before')
212 gui_args
.insert(0, options
.before
)
213 gui_args
.insert(0, '--before')
215 gui_args
.insert(0, '--source')
217 gui_args
.insert(0, options
.message
)
218 gui_args
.insert(0, '--message')
220 gui_args
.insert(0, '--verbose')
221 if options
.verbose
> 1:
222 gui_args
.insert(0, '--verbose')
224 gui_args
.insert(0, options
.cpu
)
225 gui_args
.insert(0, '--cpu')
227 gui_args
.insert(0, options
.os
)
228 gui_args
.insert(0, '--os')
229 if options
.with_store
:
230 for x
in options
.with_store
:
231 gui_args
+= ['--with-store', x
]
232 if options
.select_only
:
233 gui_args
.append('--select-only')
234 if command_name
is not None:
235 gui_args
.append('--command')
236 gui_args
.append(command_name
)
237 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
239 sys
.exit(1) # Aborted
241 # Note: --download-only also makes us stop and download stale feeds first.
242 downloaded
= policy
.solve_and_download_impls(refresh
= options
.refresh
or options
.download_only
or False,
243 select_only
= bool(options
.select_only
))
245 policy
.handler
.wait_for_blocker(downloaded
)
246 sels
= selections
.Selections(policy
)
248 if options
.get_selections
:
249 _get_selections(sels
, options
)
250 elif not options
.download_only
:
251 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
253 except NeedDownload
, ex
:
254 # This only happens for dry runs
257 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
258 """Run the GUI to get the selections.
259 prog_args and options are used only if the GUI requests a test.
261 from zeroinstall
import helpers
262 def test_callback(sels
):
263 from zeroinstall
.injector
import run
264 return run
.test_selections(sels
, prog_args
,
265 bool(options
and options
.dry_run
),
266 options
and options
.main
)
267 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
269 def _download_missing_selections(options
, sels
):
270 from zeroinstall
.injector
import fetch
271 from zeroinstall
.injector
.handler
import Handler
272 handler
= Handler(dry_run
= options
.dry_run
)
273 fetcher
= fetch
.Fetcher(handler
)
274 blocker
= sels
.download_missing(iface_cache
, fetcher
)
276 logging
.info(_("Waiting for selected implementations to be downloaded..."))
277 handler
.wait_for_blocker(blocker
)
279 def _get_selections(sels
, options
):
281 from zeroinstall
import zerostore
282 done
= set() # detect cycles
283 def print_node(uri
, command
, indent
):
284 if uri
in done
: return
286 impl
= sels
.selections
.get(uri
, None)
287 print indent
+ "- URI:", uri
289 print indent
+ " Version:", impl
.version
291 if impl
.id.startswith('package:'):
292 path
= "(" + impl
.id + ")"
294 path
= impl
.local_path
or iface_cache
.stores
.lookup_any(impl
.digests
)
295 except zerostore
.NotStored
:
296 path
= "(not cached)"
297 print indent
+ " Path:", path
299 deps
= impl
.dependencies
300 if command
is not None:
301 deps
+= sels
.commands
[command
].requires
303 if isinstance(child
, model
.InterfaceDependency
):
304 if child
.qdom
.name
== 'runner':
305 child_command
= command
+ 1
308 print_node(child
.interface
, child_command
, indent
)
310 print indent
+ " No selected version"
314 print_node(sels
.interface
, 0, "")
316 print_node(sels
.interface
, None, "")
320 doc
.writexml(sys
.stdout
)
321 sys
.stdout
.write('\n')
323 class UsageError(Exception): pass
325 def main(command_args
):
326 """Act as if 0launch was run with the given arguments.
327 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
328 @type command_args: [str]
330 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
331 for std
in (0, 1, 2):
335 fd
= os
.open('/dev/null', os
.O_RDONLY
)
340 parser
= OptionParser(usage
=_("usage: %prog [options] interface [args]\n"
341 " %prog --list [search-term]\n"
342 " %prog --import [signed-interface-files]\n"
343 " %prog --feed [interface]"))
344 parser
.add_option("", "--before", help=_("choose a version before this"), metavar
='VERSION')
345 parser
.add_option("", "--command", help=_("command to select"), metavar
='COMMAND')
346 parser
.add_option("-c", "--console", help=_("never use GUI"), action
='store_false', dest
='gui')
347 parser
.add_option("", "--cpu", help=_("target CPU type"), metavar
='CPU')
348 parser
.add_option("-d", "--download-only", help=_("fetch but don't run"), action
='store_true')
349 parser
.add_option("-D", "--dry-run", help=_("just print actions"), action
='store_true')
350 parser
.add_option("-f", "--feed", help=_("add or remove a feed"), action
='store_true')
351 parser
.add_option("", "--get-selections", help=_("write selected versions as XML"), action
='store_true')
352 parser
.add_option("-g", "--gui", help=_("show graphical policy editor"), action
='store_true')
353 parser
.add_option("-i", "--import", help=_("import from files, not from the network"), action
='store_true')
354 parser
.add_option("-l", "--list", help=_("list all known interfaces"), action
='store_true')
355 parser
.add_option("-m", "--main", help=_("name of the file to execute"))
356 parser
.add_option("", "--message", help=_("message to display when interacting with user"))
357 parser
.add_option("", "--not-before", help=_("minimum version to choose"), metavar
='VERSION')
358 parser
.add_option("", "--os", help=_("target operation system type"), metavar
='OS')
359 parser
.add_option("-o", "--offline", help=_("try to avoid using the network"), action
='store_true')
360 parser
.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action
='store_true')
361 parser
.add_option("", "--select-only", help=_("only download the feeds"), action
='store_true')
362 parser
.add_option("", "--set-selections", help=_("run versions specified in XML file"), metavar
='FILE')
363 parser
.add_option("", "--show", help=_("show where components are installed"), action
='store_true')
364 parser
.add_option("-s", "--source", help=_("select source code"), action
='store_true')
365 parser
.add_option("", "--systray", help=_("download in the background"), action
='store_true')
366 parser
.add_option("-v", "--verbose", help=_("more verbose output"), action
='count')
367 parser
.add_option("-V", "--version", help=_("display version information"), action
='store_true')
368 parser
.add_option("", "--with-store", help=_("add an implementation cache"), action
='append', metavar
='DIR')
369 parser
.add_option("-w", "--wrapper", help=_("execute program using a debugger, etc"), metavar
='COMMAND')
370 parser
.disable_interspersed_args()
372 (options
, args
) = parser
.parse_args(command_args
)
375 logger
= logging
.getLogger()
376 if options
.verbose
== 1:
377 logger
.setLevel(logging
.INFO
)
379 logger
.setLevel(logging
.DEBUG
)
381 logging
.info(_("Running 0launch %(version)s %(args)s; Python %(python_version)s"), {'version': zeroinstall
.version
, 'args': repr(args
), 'python_version': sys
.version
})
383 if options
.select_only
or options
.show
:
384 options
.download_only
= True
387 options
.get_selections
= True
389 if options
.with_store
:
390 from zeroinstall
import zerostore
391 for x
in options
.with_store
:
392 iface_cache
.stores
.stores
.append(zerostore
.Store(os
.path
.abspath(x
)))
393 logging
.info(_("Stores search path is now %s"), iface_cache
.stores
.stores
)
397 _list_interfaces(args
)
398 elif options
.version
:
400 print "0launch (zero-install) " + zeroinstall
.version
401 print "Copyright (C) 2010 Thomas Leonard"
402 print _("This program comes with ABSOLUTELY NO WARRANTY,"
403 "\nto the extent permitted by law."
404 "\nYou may redistribute copies of this program"
405 "\nunder the terms of the GNU Lesser General Public License."
406 "\nFor more information about these matters, see the file named COPYING.")
407 elif options
.set_selections
:
408 from zeroinstall
.injector
import qdom
, run
409 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
410 _download_missing_selections(options
, sels
)
411 if options
.get_selections
:
412 _get_selections(sels
, options
)
413 elif not options
.download_only
:
414 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
415 elif getattr(options
, 'import'):
416 # (import is a keyword)
417 cmd
= __import__('zeroinstall.cmd.import', globals(), locals(), ["import"], 0)
418 cmd
.handle(options
, args
)
420 _manage_feeds(options
, args
)
422 _normal_mode(options
, args
)
426 except SafeException
, ex
:
427 if options
.verbose
: raise
429 print >>sys
.stderr
, unicode(ex
)
431 print >>sys
.stderr
, repr(ex
)