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
, namespaces
, 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 iface
= iface_cache
.get_interface(uri
)
53 logging
.info(_("Importing information about interface %s"), iface
)
56 pending
= PendingFeed(uri
, signed_data
)
59 keys_downloaded
= tasks
.Task(pending
.download_keys(handler
), "download keys")
60 yield keys_downloaded
.finished
61 tasks
.check(keys_downloaded
.finished
)
62 if not iface_cache
.update_interface_if_trusted(iface
, pending
.sigs
, pending
.new_xml
):
63 blocker
= handler
.confirm_trust_keys(iface
, pending
.sigs
, pending
.new_xml
)
67 if not iface_cache
.update_interface_if_trusted(iface
, pending
.sigs
, pending
.new_xml
):
68 raise SafeException(_("No signing keys trusted; not importing"))
70 task
= tasks
.Task(run(), "import feed")
72 errors
= handler
.wait_for_blocker(task
.finished
)
74 raise SafeException(_("Errors during download: ") + '\n'.join(errors
))
76 def _manage_feeds(options
, args
):
77 from zeroinstall
.injector
import writer
78 from zeroinstall
.injector
.handler
import Handler
79 from zeroinstall
.injector
.policy
import Policy
80 handler
= Handler(dry_run
= options
.dry_run
)
81 if not args
: raise UsageError()
83 print _("Feed '%s':\n") % x
84 x
= model
.canonical_iface_uri(x
)
85 policy
= Policy(x
, handler
)
87 policy
.network_use
= model
.network_offline
89 feed
= iface_cache
.get_feed(x
)
90 if policy
.network_use
!= model
.network_offline
and policy
.is_stale(feed
):
91 blocker
= policy
.fetcher
.download_and_import_feed(x
, iface_cache
.iface_cache
)
92 print _("Downloading feed; please wait...")
93 handler
.wait_for_blocker(blocker
)
96 interfaces
= policy
.get_feed_targets(x
)
97 for i
in range(len(interfaces
)):
98 feed
= interfaces
[i
].get_feed(x
)
100 print _("%(index)d) Remove as feed for '%(uri)s'") % {'index': i
+ 1, 'uri': interfaces
[i
].uri
}
102 print _("%(index)d) Add as feed for '%(uri)s'") % {'index': i
+ 1, 'uri': interfaces
[i
].uri
}
106 i
= raw_input(_('Enter a number, or CTRL-C to cancel [1]: ')).strip()
107 except KeyboardInterrupt:
109 raise SafeException(_("Aborted at user request."))
117 if i
> 0 and i
<= len(interfaces
):
119 print _("Invalid number. Try again. (1 to %d)") % len(interfaces
)
120 iface
= interfaces
[i
- 1]
121 feed
= iface
.get_feed(x
)
123 iface
.extra_feeds
.remove(feed
)
125 iface
.extra_feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
126 writer
.save_interface(iface
)
127 print '\n' + _("Feed list for interface '%s' is now:") % iface
.get_name()
129 for f
in iface
.feeds
:
132 print _("(no feeds)")
134 def _normal_mode(options
, args
):
136 # You can use -g on its own to edit the GUI's own policy
137 # Otherwise, failing to give an interface is an error
139 args
= [namespaces
.injector_gui_uri
]
140 options
.download_only
= True
144 iface_uri
= model
.canonical_iface_uri(args
[0])
145 root_iface
= iface_cache
.get_interface(iface_uri
)
147 policy
= autopolicy
.AutoPolicy(iface_uri
,
148 download_only
= bool(options
.download_only
),
149 dry_run
= options
.dry_run
,
150 src
= options
.source
)
152 if options
.before
or options
.not_before
:
153 policy
.solver
.extra_restrictions
[root_iface
] = [model
.VersionRangeRestriction(model
.parse_version(options
.before
),
154 model
.parse_version(options
.not_before
))]
156 if options
.os
or options
.cpu
:
157 from zeroinstall
.injector
import arch
158 policy
.target_arch
= arch
.get_architecture(options
.os
, options
.cpu
)
161 policy
.network_use
= model
.network_offline
163 if options
.get_selections
:
165 raise SafeException(_("Can't use arguments with --get-selections"))
167 raise SafeException(_("Can't use --main with --get-selections"))
169 # Note that need_download() triggers a solve
170 if options
.refresh
or options
.gui
:
171 # We could run immediately, but the user asked us not to
172 can_run_immediately
= False
174 can_run_immediately
= (not policy
.need_download()) and policy
.ready
176 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if policy
.is_stale(iface_cache
.get_feed(feed
))]
178 if options
.download_only
and stale_feeds
:
179 can_run_immediately
= False
181 if can_run_immediately
:
183 if policy
.network_use
== model
.network_offline
:
184 logging
.debug(_("No doing background update because we are in off-line mode."))
186 # There are feeds we should update, but we can run without them.
187 # Do the update in the background while the program is running.
189 background
.spawn_background_update(policy
, options
.verbose
> 0)
190 if options
.get_selections
:
191 _get_selections(policy
)
193 if not options
.download_only
:
194 from zeroinstall
.injector
import run
195 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
197 logging
.info(_("Downloads done (download-only mode)"))
198 assert options
.dry_run
or options
.download_only
201 # If the user didn't say whether to use the GUI, choose for them.
202 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
204 # If we need to download anything, we might as well
205 # refresh all the interfaces first. Also, this triggers
206 # the 'checking for updates' box, which is non-interactive
207 # when there are no changes to the selection.
208 options
.refresh
= True
209 logging
.info(_("Switching to GUI mode... (use --console to disable)"))
214 from zeroinstall
.injector
import run
217 if options
.download_only
:
218 # Just changes the button's label
219 gui_args
.append('--download-only')
221 gui_args
.append('--refresh')
223 gui_args
.append('--systray')
224 if options
.not_before
:
225 gui_args
.insert(0, options
.not_before
)
226 gui_args
.insert(0, '--not-before')
228 gui_args
.insert(0, options
.before
)
229 gui_args
.insert(0, '--before')
231 gui_args
.insert(0, '--source')
233 gui_args
.insert(0, options
.message
)
234 gui_args
.insert(0, '--message')
236 gui_args
.insert(0, '--verbose')
237 if options
.verbose
> 1:
238 gui_args
.insert(0, '--verbose')
240 gui_args
.insert(0, options
.cpu
)
241 gui_args
.insert(0, '--cpu')
243 gui_args
.insert(0, options
.os
)
244 gui_args
.insert(0, '--os')
245 if options
.with_store
:
246 for x
in options
.with_store
:
247 gui_args
+= ['--with-store', x
]
248 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
250 sys
.exit(1) # Aborted
252 #program_log('download_and_execute ' + iface_uri)
253 downloaded
= policy
.solve_and_download_impls(refresh
= bool(options
.refresh
))
255 policy
.handler
.wait_for_blocker(downloaded
)
256 sels
= selections
.Selections(policy
)
258 if options
.get_selections
:
260 doc
.writexml(sys
.stdout
)
261 sys
.stdout
.write('\n')
262 elif not options
.download_only
:
263 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
265 except NeedDownload
, ex
:
266 # This only happens for dry runs
269 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
270 """Run the GUI to get the selections.
271 prog_args and options are used only if the GUI requests a test.
273 from zeroinstall
import helpers
274 def test_callback(sels
):
275 from zeroinstall
.injector
import run
276 return run
.test_selections(sels
, prog_args
,
277 bool(options
and options
.dry_run
),
278 options
and options
.main
)
279 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
281 def _download_missing_selections(options
, sels
):
282 from zeroinstall
.injector
import fetch
283 from zeroinstall
.injector
.handler
import Handler
284 handler
= Handler(dry_run
= options
.dry_run
)
285 fetcher
= fetch
.Fetcher(handler
)
286 blocker
= sels
.download_missing(iface_cache
, fetcher
)
288 logging
.info(_("Waiting for selected implementations to be downloaded..."))
289 handler
.wait_for_blocker(blocker
)
291 def _get_selections(policy
):
292 doc
= selections
.Selections(policy
).toDOM()
293 doc
.writexml(sys
.stdout
)
294 sys
.stdout
.write('\n')
296 class UsageError(Exception): pass
298 def main(command_args
):
299 """Act as if 0launch was run with the given arguments.
300 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
301 @type command_args: [str]
303 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
304 for std
in (0, 1, 2):
308 fd
= os
.open('/dev/null', os
.O_RDONLY
)
313 parser
= OptionParser(usage
=_("usage: %prog [options] interface [args]\n"
314 " %prog --list [search-term]\n"
315 " %prog --import [signed-interface-files]\n"
316 " %prog --feed [interface]"))
317 parser
.add_option("", "--before", help=_("choose a version before this"), metavar
='VERSION')
318 parser
.add_option("-c", "--console", help=_("never use GUI"), action
='store_false', dest
='gui')
319 parser
.add_option("", "--cpu", help=_("target CPU type"), metavar
='CPU')
320 parser
.add_option("-d", "--download-only", help=_("fetch but don't run"), action
='store_true')
321 parser
.add_option("-D", "--dry-run", help=_("just print actions"), action
='store_true')
322 parser
.add_option("-f", "--feed", help=_("add or remove a feed"), action
='store_true')
323 parser
.add_option("", "--get-selections", help=_("write selected versions as XML"), action
='store_true')
324 parser
.add_option("-g", "--gui", help=_("show graphical policy editor"), action
='store_true')
325 parser
.add_option("-i", "--import", help=_("import from files, not from the network"), action
='store_true')
326 parser
.add_option("-l", "--list", help=_("list all known interfaces"), action
='store_true')
327 parser
.add_option("-m", "--main", help=_("name of the file to execute"))
328 parser
.add_option("", "--message", help=_("message to display when interacting with user"))
329 parser
.add_option("", "--not-before", help=_("minimum version to choose"), metavar
='VERSION')
330 parser
.add_option("", "--os", help=_("target operation system type"), metavar
='OS')
331 parser
.add_option("-o", "--offline", help=_("try to avoid using the network"), action
='store_true')
332 parser
.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action
='store_true')
333 parser
.add_option("", "--set-selections", help=_("run versions specified in XML file"), metavar
='FILE')
334 parser
.add_option("-s", "--source", help=_("select source code"), action
='store_true')
335 parser
.add_option("", "--systray", help=_("download in the background"), action
='store_true')
336 parser
.add_option("-v", "--verbose", help=_("more verbose output"), action
='count')
337 parser
.add_option("-V", "--version", help=_("display version information"), action
='store_true')
338 parser
.add_option("", "--with-store", help=_("add an implementation cache"), action
='append', metavar
='DIR')
339 parser
.add_option("-w", "--wrapper", help=_("execute program using a debugger, etc"), metavar
='COMMAND')
340 parser
.disable_interspersed_args()
342 (options
, args
) = parser
.parse_args(command_args
)
345 logger
= logging
.getLogger()
346 if options
.verbose
== 1:
347 logger
.setLevel(logging
.INFO
)
349 logger
.setLevel(logging
.DEBUG
)
351 logging
.info(_("Running 0launch %(version)s %(args)s; Python %(python_version)s"), {'version': zeroinstall
.version
, 'args': repr(args
), 'python_version': sys
.version
})
353 if options
.with_store
:
354 from zeroinstall
import zerostore
355 for x
in options
.with_store
:
356 iface_cache
.stores
.stores
.append(zerostore
.Store(os
.path
.abspath(x
)))
357 logging
.info(_("Stores search path is now %s"), iface_cache
.stores
.stores
)
361 _list_interfaces(args
)
362 elif options
.version
:
364 print "0launch (zero-install) " + zeroinstall
.version
365 print "Copyright (C) 2009 Thomas Leonard"
366 print _("This program comes with ABSOLUTELY NO WARRANTY,"
367 "\nto the extent permitted by law."
368 "\nYou may redistribute copies of this program"
369 "\nunder the terms of the GNU Lesser General Public License."
370 "\nFor more information about these matters, see the file named COPYING.")
371 elif options
.set_selections
:
372 from zeroinstall
.injector
import qdom
, run
373 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
374 _download_missing_selections(options
, sels
)
375 if not options
.download_only
:
376 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
377 elif getattr(options
, 'import'):
380 _manage_feeds(options
, args
)
382 _normal_mode(options
, args
)
386 except SafeException
, ex
:
387 if options
.verbose
: raise
388 print >>sys
.stderr
, ex