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
14 #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK)
16 #__main__.__builtins__.program_log = program_log
17 #program_log('0launch ' + ' '.join((sys.argv[1:])))
19 def _list_interfaces(args
):
20 from zeroinstall
.injector
.iface_cache
import iface_cache
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 iface_cache
, 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
)
53 iface_cache
.add_pending(pending
)
55 handler
= handler
.Handler()
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 model
.SafeException("Errors during download: " + '\n'.join(errors
))
75 def _manage_feeds(options
, args
):
76 from zeroinstall
.injector
import iface_cache
, 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
.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 model
.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])
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
.root_restrictions
.append(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 from zeroinstall
.injector
.iface_cache
import iface_cache
171 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if policy
.is_stale(iface_cache
.get_feed(feed
))]
173 if options
.download_only
and stale_feeds
:
174 can_run_immediately
= False
176 if can_run_immediately
:
178 if policy
.network_use
== model
.network_offline
:
179 logging
.debug("No doing background update because we are in off-line mode.")
181 # There are feeds we should update, but we can run without them.
182 # Do the update in the background while the program is running.
184 background
.spawn_background_update(policy
, options
.verbose
> 0)
185 if options
.get_selections
:
186 _get_selections(policy
)
188 if not options
.download_only
:
189 from zeroinstall
.injector
import run
190 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
192 logging
.info("Downloads done (download-only mode)")
193 assert options
.dry_run
or options
.download_only
196 # If the user didn't say whether to use the GUI, choose for them.
197 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
199 # If we need to download anything, we might as well
200 # refresh all the interfaces first. Also, this triggers
201 # the 'checking for updates' box, which is non-interactive
202 # when there are no changes to the selection.
203 options
.refresh
= True
204 logging
.info("Switching to GUI mode... (use --console to disable)")
210 from zeroinstall
.injector
import run
212 if options
.download_only
:
213 # Just changes the button's label
214 gui_args
.append('--download-only')
216 gui_args
.append('--refresh')
217 if options
.not_before
:
218 gui_args
.insert(0, options
.not_before
)
219 gui_args
.insert(0, '--not-before')
221 gui_args
.insert(0, options
.before
)
222 gui_args
.insert(0, '--before')
224 gui_args
.insert(0, '--source')
226 gui_args
.insert(0, '--verbose')
227 if options
.verbose
> 1:
228 gui_args
.insert(0, '--verbose')
229 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
231 sys
.exit(1) # Aborted
232 if options
.get_selections
:
234 doc
.writexml(sys
.stdout
)
235 sys
.stdout
.write('\n')
236 elif not options
.download_only
:
237 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
239 #program_log('download_and_execute ' + iface_uri)
240 policy
.download_and_execute(prog_args
, refresh
= bool(options
.refresh
), main
= options
.main
)
241 except autopolicy
.NeedDownload
, ex
:
242 # This only happens for dry runs
245 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
246 """Run the GUI to get the selections.
247 prog_args and options are used only if the GUI requests a test.
249 from zeroinstall
import helpers
250 def test_callback(sels
):
251 from zeroinstall
.injector
import run
252 return run
.test_selections(sels
, prog_args
,
253 bool(options
and options
.dry_run
),
254 options
and options
.main
)
255 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
257 def _get_selections(policy
):
259 doc
= selections
.Selections(policy
).toDOM()
260 doc
.writexml(sys
.stdout
)
261 sys
.stdout
.write('\n')
263 class UsageError(Exception): pass
265 def main(command_args
):
266 """Act as if 0launch was run with the given arguments.
267 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
268 @type command_args: [str]
270 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
271 for std
in (0, 1, 2):
275 fd
= os
.open('/dev/null', os
.O_RDONLY
)
280 parser
= OptionParser(usage
="usage: %prog [options] interface [args]\n"
281 " %prog --list [search-term]\n"
282 " %prog --import [signed-interface-files]\n"
283 " %prog --feed [interface]")
284 parser
.add_option("", "--before", help="choose a version before this", metavar
='VERSION')
285 parser
.add_option("-c", "--console", help="never use GUI", action
='store_false', dest
='gui')
286 parser
.add_option("-d", "--download-only", help="fetch but don't run", action
='store_true')
287 parser
.add_option("-D", "--dry-run", help="just print actions", action
='store_true')
288 parser
.add_option("-f", "--feed", help="add or remove a feed", action
='store_true')
289 parser
.add_option("", "--get-selections", help="write selected versions as XML", action
='store_true')
290 parser
.add_option("-g", "--gui", help="show graphical policy editor", action
='store_true')
291 parser
.add_option("-i", "--import", help="import from files, not from the network", action
='store_true')
292 parser
.add_option("-l", "--list", help="list all known interfaces", action
='store_true')
293 parser
.add_option("-m", "--main", help="name of the file to execute")
294 parser
.add_option("", "--not-before", help="minimum version to choose", metavar
='VERSION')
295 parser
.add_option("-o", "--offline", help="try to avoid using the network", action
='store_true')
296 parser
.add_option("-r", "--refresh", help="refresh all used interfaces", action
='store_true')
297 parser
.add_option("", "--set-selections", help="run versions specified in XML file", metavar
='FILE')
298 parser
.add_option("-s", "--source", help="select source code", action
='store_true')
299 parser
.add_option("-v", "--verbose", help="more verbose output", action
='count')
300 parser
.add_option("-V", "--version", help="display version information", action
='store_true')
301 parser
.add_option("-w", "--wrapper", help="execute program using a debugger, etc", metavar
='COMMAND')
302 parser
.disable_interspersed_args()
304 (options
, args
) = parser
.parse_args(command_args
)
307 logger
= logging
.getLogger()
308 if options
.verbose
== 1:
309 logger
.setLevel(logging
.INFO
)
311 logger
.setLevel(logging
.DEBUG
)
313 logging
.info("Running 0launch %s %s; Python %s", zeroinstall
.version
, repr(args
), sys
.version
)
317 _list_interfaces(args
)
318 elif options
.version
:
320 print "0launch (zero-install) " + zeroinstall
.version
321 print "Copyright (C) 2007 Thomas Leonard"
322 print "This program comes with ABSOLUTELY NO WARRANTY,"
323 print "to the extent permitted by law."
324 print "You may redistribute copies of this program"
325 print "under the terms of the GNU Lesser General Public License."
326 print "For more information about these matters, see the file named COPYING."
327 elif options
.set_selections
:
328 from zeroinstall
.injector
import selections
, qdom
, run
329 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
330 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
331 elif getattr(options
, 'import'):
334 _manage_feeds(options
, args
)
336 _normal_mode(options
, args
)
340 except model
.SafeException
, ex
:
341 if options
.verbose
: raise
342 print >>sys
.stderr
, ex