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
.injector
import model
, download
, autopolicy
, namespaces
13 from zeroinstall
.injector
.iface_cache
import iface_cache
15 #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK)
17 #__main__.__builtins__.program_log = program_log
18 #program_log('0launch ' + ' '.join((sys.argv[1:])))
20 def _list_interfaces(args
):
22 matches
= iface_cache
.list_all_interfaces()
24 match
= args
[0].lower()
25 matches
= [i
for i
in iface_cache
.list_all_interfaces() if match
in i
.lower()]
33 def _import_feed(args
):
34 from zeroinstall
.support
import tasks
35 from zeroinstall
.injector
import gpg
, handler
, trust
36 from zeroinstall
.injector
.iface_cache
import PendingFeed
37 from xml
.dom
import minidom
39 if not os
.path
.isfile(x
):
40 raise model
.SafeException("File '%s' does not exist" % x
)
41 logging
.info("Importing from file '%s'", x
)
43 data
, sigs
= gpg
.check_stream(signed_data
)
44 doc
= minidom
.parseString(data
.read())
45 uri
= doc
.documentElement
.getAttribute('uri')
47 raise model
.SafeException("Missing 'uri' attribute on root element in '%s'" % x
)
48 iface
= iface_cache
.get_interface(uri
)
49 logging
.info("Importing information about interface %s", iface
)
52 pending
= PendingFeed(uri
, signed_data
)
54 handler
= handler
.Handler()
57 keys_downloaded
= tasks
.Task(pending
.download_keys(handler
), "download keys")
58 yield keys_downloaded
.finished
59 tasks
.check(keys_downloaded
.finished
)
60 if not iface_cache
.update_interface_if_trusted(iface
, pending
.sigs
, pending
.new_xml
):
61 blocker
= handler
.confirm_trust_keys(iface
, pending
.sigs
, pending
.new_xml
)
65 if not iface_cache
.update_interface_if_trusted(iface
, pending
.sigs
, pending
.new_xml
):
66 raise SafeException("No signing keys trusted; not importing")
68 task
= tasks
.Task(run(), "import feed")
70 errors
= handler
.wait_for_blocker(task
.finished
)
72 raise model
.SafeException("Errors during download: " + '\n'.join(errors
))
74 def _manage_feeds(options
, args
):
75 from zeroinstall
.injector
import writer
76 from zeroinstall
.injector
.handler
import Handler
77 from zeroinstall
.injector
.policy
import Policy
78 handler
= Handler(dry_run
= options
.dry_run
)
79 if not args
: raise UsageError()
81 print "Feed '%s':\n" % x
82 x
= model
.canonical_iface_uri(x
)
83 policy
= Policy(x
, handler
)
85 policy
.network_use
= model
.network_offline
87 feed
= iface_cache
.get_feed(x
)
88 if policy
.network_use
!= model
.network_offline
and policy
.is_stale(feed
):
89 blocker
= policy
.fetcher
.download_and_import_feed(x
, iface_cache
.iface_cache
)
90 print "Downloading feed; please wait..."
91 handler
.wait_for_blocker(blocker
)
94 interfaces
= policy
.get_feed_targets(x
)
95 for i
in range(len(interfaces
)):
96 feed
= interfaces
[i
].get_feed(x
)
98 print "%d) Remove as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
100 print "%d) Add as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
104 i
= raw_input('Enter a number, or CTRL-C to cancel [1]: ').strip()
105 except KeyboardInterrupt:
107 raise model
.SafeException("Aborted at user request.")
115 if i
> 0 and i
<= len(interfaces
):
117 print "Invalid number. Try again. (1 to %d)" % len(interfaces
)
118 iface
= interfaces
[i
- 1]
119 feed
= iface
.get_feed(x
)
121 iface
.extra_feeds
.remove(feed
)
123 iface
.extra_feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
124 writer
.save_interface(iface
)
125 print "\nFeed list for interface '%s' is now:" % iface
.get_name()
127 for f
in iface
.feeds
:
132 def _normal_mode(options
, args
):
134 # You can use -g on its own to edit the GUI's own policy
135 # Otherwise, failing to give an interface is an error
137 args
= [namespaces
.injector_gui_uri
]
138 options
.download_only
= True
142 iface_uri
= model
.canonical_iface_uri(args
[0])
143 root_iface
= iface_cache
.get_interface(iface_uri
)
145 policy
= autopolicy
.AutoPolicy(iface_uri
,
146 download_only
= bool(options
.download_only
),
147 dry_run
= options
.dry_run
,
148 src
= options
.source
)
150 if options
.before
or options
.not_before
:
151 policy
.solver
.extra_restrictions
[root_iface
] = [model
.VersionRangeRestriction(model
.parse_version(options
.before
),
152 model
.parse_version(options
.not_before
))]
155 policy
.network_use
= model
.network_offline
157 if options
.get_selections
:
159 raise model
.SafeException("Can't use arguments with --get-selections")
161 raise model
.SafeException("Can't use --main with --get-selections")
163 # Note that need_download() triggers a solve
164 if options
.refresh
or options
.gui
:
165 # We could run immediately, but the user asked us not to
166 can_run_immediately
= False
168 can_run_immediately
= (not policy
.need_download()) and policy
.ready
170 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if policy
.is_stale(iface_cache
.get_feed(feed
))]
172 if options
.download_only
and stale_feeds
:
173 can_run_immediately
= False
175 if can_run_immediately
:
177 if policy
.network_use
== model
.network_offline
:
178 logging
.debug("No doing background update because we are in off-line mode.")
180 # There are feeds we should update, but we can run without them.
181 # Do the update in the background while the program is running.
183 background
.spawn_background_update(policy
, options
.verbose
> 0)
184 if options
.get_selections
:
185 _get_selections(policy
)
187 if not options
.download_only
:
188 from zeroinstall
.injector
import run
189 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
191 logging
.info("Downloads done (download-only mode)")
192 assert options
.dry_run
or options
.download_only
195 # If the user didn't say whether to use the GUI, choose for them.
196 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
198 # If we need to download anything, we might as well
199 # refresh all the interfaces first. Also, this triggers
200 # the 'checking for updates' box, which is non-interactive
201 # when there are no changes to the selection.
202 options
.refresh
= True
203 logging
.info("Switching to GUI mode... (use --console to disable)")
209 from zeroinstall
.injector
import run
211 if options
.download_only
:
212 # Just changes the button's label
213 gui_args
.append('--download-only')
215 gui_args
.append('--refresh')
216 if options
.not_before
:
217 gui_args
.insert(0, options
.not_before
)
218 gui_args
.insert(0, '--not-before')
220 gui_args
.insert(0, options
.before
)
221 gui_args
.insert(0, '--before')
223 gui_args
.insert(0, '--source')
225 gui_args
.insert(0, '--verbose')
226 if options
.verbose
> 1:
227 gui_args
.insert(0, '--verbose')
228 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
230 sys
.exit(1) # Aborted
231 if options
.get_selections
:
233 doc
.writexml(sys
.stdout
)
234 sys
.stdout
.write('\n')
235 elif not options
.download_only
:
236 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
238 #program_log('download_and_execute ' + iface_uri)
239 policy
.download_and_execute(prog_args
, refresh
= bool(options
.refresh
), main
= options
.main
)
240 except autopolicy
.NeedDownload
, ex
:
241 # This only happens for dry runs
244 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
245 """Run the GUI to get the selections.
246 prog_args and options are used only if the GUI requests a test.
248 from zeroinstall
import helpers
249 def test_callback(sels
):
250 from zeroinstall
.injector
import run
251 return run
.test_selections(sels
, prog_args
,
252 bool(options
and options
.dry_run
),
253 options
and options
.main
)
254 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
256 def _download_missing_selections(options
, sels
):
257 from zeroinstall
.injector
import fetch
258 from zeroinstall
.injector
.handler
import Handler
259 handler
= Handler(dry_run
= options
.dry_run
)
260 fetcher
= fetch
.Fetcher(handler
)
261 blocker
= sels
.download_missing(iface_cache
, fetcher
)
263 logging
.info("Waiting for selected implementations to be downloaded...")
264 handler
.wait_for_blocker(blocker
)
266 def _get_selections(policy
):
268 doc
= selections
.Selections(policy
).toDOM()
269 doc
.writexml(sys
.stdout
)
270 sys
.stdout
.write('\n')
272 class UsageError(Exception): pass
274 def main(command_args
):
275 """Act as if 0launch was run with the given arguments.
276 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
277 @type command_args: [str]
279 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
280 for std
in (0, 1, 2):
284 fd
= os
.open('/dev/null', os
.O_RDONLY
)
289 parser
= OptionParser(usage
="usage: %prog [options] interface [args]\n"
290 " %prog --list [search-term]\n"
291 " %prog --import [signed-interface-files]\n"
292 " %prog --feed [interface]")
293 parser
.add_option("", "--before", help="choose a version before this", metavar
='VERSION')
294 parser
.add_option("-c", "--console", help="never use GUI", action
='store_false', dest
='gui')
295 parser
.add_option("-d", "--download-only", help="fetch but don't run", action
='store_true')
296 parser
.add_option("-D", "--dry-run", help="just print actions", action
='store_true')
297 parser
.add_option("-f", "--feed", help="add or remove a feed", action
='store_true')
298 parser
.add_option("", "--get-selections", help="write selected versions as XML", action
='store_true')
299 parser
.add_option("-g", "--gui", help="show graphical policy editor", action
='store_true')
300 parser
.add_option("-i", "--import", help="import from files, not from the network", action
='store_true')
301 parser
.add_option("-l", "--list", help="list all known interfaces", action
='store_true')
302 parser
.add_option("-m", "--main", help="name of the file to execute")
303 parser
.add_option("", "--not-before", help="minimum version to choose", metavar
='VERSION')
304 parser
.add_option("-o", "--offline", help="try to avoid using the network", action
='store_true')
305 parser
.add_option("-r", "--refresh", help="refresh all used interfaces", action
='store_true')
306 parser
.add_option("", "--set-selections", help="run versions specified in XML file", metavar
='FILE')
307 parser
.add_option("-s", "--source", help="select source code", action
='store_true')
308 parser
.add_option("-v", "--verbose", help="more verbose output", action
='count')
309 parser
.add_option("-V", "--version", help="display version information", action
='store_true')
310 parser
.add_option("-w", "--wrapper", help="execute program using a debugger, etc", metavar
='COMMAND')
311 parser
.disable_interspersed_args()
313 (options
, args
) = parser
.parse_args(command_args
)
316 logger
= logging
.getLogger()
317 if options
.verbose
== 1:
318 logger
.setLevel(logging
.INFO
)
320 logger
.setLevel(logging
.DEBUG
)
322 logging
.info("Running 0launch %s %s; Python %s", zeroinstall
.version
, repr(args
), sys
.version
)
326 _list_interfaces(args
)
327 elif options
.version
:
329 print "0launch (zero-install) " + zeroinstall
.version
330 print "Copyright (C) 2007 Thomas Leonard"
331 print "This program comes with ABSOLUTELY NO WARRANTY,"
332 print "to the extent permitted by law."
333 print "You may redistribute copies of this program"
334 print "under the terms of the GNU Lesser General Public License."
335 print "For more information about these matters, see the file named COPYING."
336 elif options
.set_selections
:
337 from zeroinstall
.injector
import selections
, qdom
, run
338 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
339 _download_missing_selections(options
, sels
)
340 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
341 elif getattr(options
, 'import'):
344 _manage_feeds(options
, args
)
346 _normal_mode(options
, args
)
350 except model
.SafeException
, ex
:
351 if options
.verbose
: raise
352 print >>sys
.stderr
, ex