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
9 from optparse
import OptionParser
12 from zeroinstall
import SafeException
, NeedDownload
13 from zeroinstall
.injector
import model
, autopolicy
, namespaces
14 from zeroinstall
.injector
.iface_cache
import iface_cache
16 #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK)
18 #__main__.__builtins__.program_log = program_log
19 #program_log('0launch ' + ' '.join((sys.argv[1:])))
21 def _list_interfaces(args
):
23 matches
= iface_cache
.list_all_interfaces()
25 match
= args
[0].lower()
26 matches
= [i
for i
in iface_cache
.list_all_interfaces() if match
in i
.lower()]
34 def _import_feed(args
):
35 from zeroinstall
.support
import tasks
36 from zeroinstall
.injector
import gpg
, handler
37 from zeroinstall
.injector
.iface_cache
import PendingFeed
38 from xml
.dom
import minidom
39 handler
= handler
.Handler()
42 if not os
.path
.isfile(x
):
43 raise SafeException("File '%s' does not exist" % x
)
44 logging
.info("Importing from file '%s'", x
)
46 data
, sigs
= gpg
.check_stream(signed_data
)
47 doc
= minidom
.parseString(data
.read())
48 uri
= doc
.documentElement
.getAttribute('uri')
50 raise SafeException("Missing 'uri' attribute on root element in '%s'" % x
)
51 iface
= iface_cache
.get_interface(uri
)
52 logging
.info("Importing information about interface %s", iface
)
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_interface_if_trusted(iface
, pending
.sigs
, pending
.new_xml
):
62 blocker
= handler
.confirm_trust_keys(iface
, pending
.sigs
, pending
.new_xml
)
66 if not iface_cache
.update_interface_if_trusted(iface
, pending
.sigs
, pending
.new_xml
):
67 raise SafeException("No signing keys trusted; not importing")
69 task
= tasks
.Task(run(), "import feed")
71 errors
= handler
.wait_for_blocker(task
.finished
)
73 raise SafeException("Errors during download: " + '\n'.join(errors
))
75 def _manage_feeds(options
, args
):
76 from zeroinstall
.injector
import writer
77 from zeroinstall
.injector
.handler
import Handler
78 from zeroinstall
.injector
.policy
import Policy
79 handler
= Handler(dry_run
= options
.dry_run
)
80 if not args
: raise UsageError()
82 print "Feed '%s':\n" % x
83 x
= model
.canonical_iface_uri(x
)
84 policy
= Policy(x
, handler
)
86 policy
.network_use
= model
.network_offline
88 feed
= iface_cache
.get_feed(x
)
89 if policy
.network_use
!= model
.network_offline
and policy
.is_stale(feed
):
90 blocker
= policy
.fetcher
.download_and_import_feed(x
, iface_cache
.iface_cache
)
91 print "Downloading feed; please wait..."
92 handler
.wait_for_blocker(blocker
)
95 interfaces
= policy
.get_feed_targets(x
)
96 for i
in range(len(interfaces
)):
97 feed
= interfaces
[i
].get_feed(x
)
99 print "%d) Remove as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
101 print "%d) Add as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
105 i
= raw_input('Enter a number, or CTRL-C to cancel [1]: ').strip()
106 except KeyboardInterrupt:
108 raise SafeException("Aborted at user request.")
116 if i
> 0 and i
<= len(interfaces
):
118 print "Invalid number. Try again. (1 to %d)" % len(interfaces
)
119 iface
= interfaces
[i
- 1]
120 feed
= iface
.get_feed(x
)
122 iface
.extra_feeds
.remove(feed
)
124 iface
.extra_feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
125 writer
.save_interface(iface
)
126 print "\nFeed list for interface '%s' is now:" % iface
.get_name()
128 for f
in iface
.feeds
:
133 def _normal_mode(options
, args
):
135 # You can use -g on its own to edit the GUI's own policy
136 # Otherwise, failing to give an interface is an error
138 args
= [namespaces
.injector_gui_uri
]
139 options
.download_only
= True
143 iface_uri
= model
.canonical_iface_uri(args
[0])
144 root_iface
= iface_cache
.get_interface(iface_uri
)
146 policy
= autopolicy
.AutoPolicy(iface_uri
,
147 download_only
= bool(options
.download_only
),
148 dry_run
= options
.dry_run
,
149 src
= options
.source
)
151 if options
.before
or options
.not_before
:
152 policy
.solver
.extra_restrictions
[root_iface
] = [model
.VersionRangeRestriction(model
.parse_version(options
.before
),
153 model
.parse_version(options
.not_before
))]
155 if options
.os
or options
.cpu
:
156 from zeroinstall
.injector
import arch
157 policy
.target_arch
= arch
.get_architecture(options
.os
, options
.cpu
)
160 policy
.network_use
= model
.network_offline
162 if options
.get_selections
:
164 raise SafeException("Can't use arguments with --get-selections")
166 raise SafeException("Can't use --main with --get-selections")
168 # Note that need_download() triggers a solve
169 if options
.refresh
or options
.gui
:
170 # We could run immediately, but the user asked us not to
171 can_run_immediately
= False
173 can_run_immediately
= (not policy
.need_download()) and policy
.ready
175 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if policy
.is_stale(iface_cache
.get_feed(feed
))]
177 if options
.download_only
and stale_feeds
:
178 can_run_immediately
= False
180 if can_run_immediately
:
182 if policy
.network_use
== model
.network_offline
:
183 logging
.debug("No doing background update because we are in off-line mode.")
185 # There are feeds we should update, but we can run without them.
186 # Do the update in the background while the program is running.
188 background
.spawn_background_update(policy
, options
.verbose
> 0)
189 if options
.get_selections
:
190 _get_selections(policy
)
192 if not options
.download_only
:
193 from zeroinstall
.injector
import run
194 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
196 logging
.info("Downloads done (download-only mode)")
197 assert options
.dry_run
or options
.download_only
200 # If the user didn't say whether to use the GUI, choose for them.
201 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
203 # If we need to download anything, we might as well
204 # refresh all the interfaces first. Also, this triggers
205 # the 'checking for updates' box, which is non-interactive
206 # when there are no changes to the selection.
207 options
.refresh
= True
208 logging
.info("Switching to GUI mode... (use --console to disable)")
214 from zeroinstall
.injector
import run
216 if options
.download_only
:
217 # Just changes the button's label
218 gui_args
.append('--download-only')
220 gui_args
.append('--refresh')
221 if options
.not_before
:
222 gui_args
.insert(0, options
.not_before
)
223 gui_args
.insert(0, '--not-before')
225 gui_args
.insert(0, options
.before
)
226 gui_args
.insert(0, '--before')
228 gui_args
.insert(0, '--source')
230 gui_args
.insert(0, options
.message
)
231 gui_args
.insert(0, '--message')
233 gui_args
.insert(0, '--verbose')
234 if options
.verbose
> 1:
235 gui_args
.insert(0, '--verbose')
237 gui_args
.insert(0, options
.cpu
)
238 gui_args
.insert(0, '--cpu')
240 gui_args
.insert(0, options
.os
)
241 gui_args
.insert(0, '--os')
242 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
244 sys
.exit(1) # Aborted
245 if options
.get_selections
:
247 doc
.writexml(sys
.stdout
)
248 sys
.stdout
.write('\n')
249 elif not options
.download_only
:
250 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
252 #program_log('download_and_execute ' + iface_uri)
253 policy
.download_and_execute(prog_args
, refresh
= bool(options
.refresh
), main
= options
.main
)
254 except NeedDownload
, ex
:
255 # This only happens for dry runs
258 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
259 """Run the GUI to get the selections.
260 prog_args and options are used only if the GUI requests a test.
262 from zeroinstall
import helpers
263 def test_callback(sels
):
264 from zeroinstall
.injector
import run
265 return run
.test_selections(sels
, prog_args
,
266 bool(options
and options
.dry_run
),
267 options
and options
.main
)
268 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
270 def _download_missing_selections(options
, sels
):
271 from zeroinstall
.injector
import fetch
272 from zeroinstall
.injector
.handler
import Handler
273 handler
= Handler(dry_run
= options
.dry_run
)
274 fetcher
= fetch
.Fetcher(handler
)
275 blocker
= sels
.download_missing(iface_cache
, fetcher
)
277 logging
.info("Waiting for selected implementations to be downloaded...")
278 handler
.wait_for_blocker(blocker
)
280 def _get_selections(policy
):
282 doc
= selections
.Selections(policy
).toDOM()
283 doc
.writexml(sys
.stdout
)
284 sys
.stdout
.write('\n')
286 class UsageError(Exception): pass
288 def main(command_args
):
289 """Act as if 0launch was run with the given arguments.
290 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
291 @type command_args: [str]
293 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
294 for std
in (0, 1, 2):
298 fd
= os
.open('/dev/null', os
.O_RDONLY
)
303 parser
= OptionParser(usage
="usage: %prog [options] interface [args]\n"
304 " %prog --list [search-term]\n"
305 " %prog --import [signed-interface-files]\n"
306 " %prog --feed [interface]")
307 parser
.add_option("", "--before", help="choose a version before this", metavar
='VERSION')
308 parser
.add_option("-c", "--console", help="never use GUI", action
='store_false', dest
='gui')
309 parser
.add_option("", "--cpu", help="target CPU type", metavar
='CPU')
310 parser
.add_option("-d", "--download-only", help="fetch but don't run", action
='store_true')
311 parser
.add_option("-D", "--dry-run", help="just print actions", action
='store_true')
312 parser
.add_option("-f", "--feed", help="add or remove a feed", action
='store_true')
313 parser
.add_option("", "--get-selections", help="write selected versions as XML", action
='store_true')
314 parser
.add_option("-g", "--gui", help="show graphical policy editor", action
='store_true')
315 parser
.add_option("-i", "--import", help="import from files, not from the network", action
='store_true')
316 parser
.add_option("-l", "--list", help="list all known interfaces", action
='store_true')
317 parser
.add_option("-m", "--main", help="name of the file to execute")
318 parser
.add_option("", "--message", help="message to display when interacting with user")
319 parser
.add_option("", "--not-before", help="minimum version to choose", metavar
='VERSION')
320 parser
.add_option("", "--os", help="target operation system type", metavar
='OS')
321 parser
.add_option("-o", "--offline", help="try to avoid using the network", action
='store_true')
322 parser
.add_option("-r", "--refresh", help="refresh all used interfaces", action
='store_true')
323 parser
.add_option("", "--set-selections", help="run versions specified in XML file", metavar
='FILE')
324 parser
.add_option("-s", "--source", help="select source code", action
='store_true')
325 parser
.add_option("-v", "--verbose", help="more verbose output", action
='count')
326 parser
.add_option("-V", "--version", help="display version information", action
='store_true')
327 parser
.add_option("-w", "--wrapper", help="execute program using a debugger, etc", metavar
='COMMAND')
328 parser
.disable_interspersed_args()
330 (options
, args
) = parser
.parse_args(command_args
)
333 logger
= logging
.getLogger()
334 if options
.verbose
== 1:
335 logger
.setLevel(logging
.INFO
)
337 logger
.setLevel(logging
.DEBUG
)
339 logging
.info("Running 0launch %s %s; Python %s", zeroinstall
.version
, repr(args
), sys
.version
)
343 _list_interfaces(args
)
344 elif options
.version
:
346 print "0launch (zero-install) " + zeroinstall
.version
347 print "Copyright (C) 2009 Thomas Leonard"
348 print "This program comes with ABSOLUTELY NO WARRANTY,"
349 print "to the extent permitted by law."
350 print "You may redistribute copies of this program"
351 print "under the terms of the GNU Lesser General Public License."
352 print "For more information about these matters, see the file named COPYING."
353 elif options
.set_selections
:
354 from zeroinstall
.injector
import selections
, qdom
, run
355 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
356 _download_missing_selections(options
, sels
)
357 if not options
.download_only
:
358 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
359 elif getattr(options
, 'import'):
362 _manage_feeds(options
, args
)
364 _normal_mode(options
, args
)
368 except SafeException
, ex
:
369 if options
.verbose
: raise
370 print >>sys
.stderr
, ex