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_interface(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 xml
.dom
import minidom
78 if not args
: raise UsageError()
80 print "Feed '%s':\n" % x
81 x
= model
.canonical_iface_uri(x
)
82 policy
= autopolicy
.AutoPolicy(x
, download_only
= True, dry_run
= options
.dry_run
)
84 policy
.network_use
= model
.network_offline
85 policy
.recalculate_with_dl() # XXX
86 interfaces
= policy
.get_feed_targets(policy
.root
)
87 for i
in range(len(interfaces
)):
88 feed
= interfaces
[i
].get_feed(x
)
90 print "%d) Remove as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
92 print "%d) Add as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
96 i
= raw_input('Enter a number, or CTRL-C to cancel [1]: ').strip()
97 except KeyboardInterrupt:
99 raise model
.SafeException("Aborted at user request.")
107 if i
> 0 and i
<= len(interfaces
):
109 print "Invalid number. Try again. (1 to %d)" % len(interfaces
)
110 iface
= interfaces
[i
- 1]
111 feed
= iface
.get_feed(x
)
113 iface
.extra_feeds
.remove(feed
)
115 iface
.extra_feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
116 writer
.save_interface(iface
)
117 print "\nFeed list for interface '%s' is now:" % iface
.get_name()
119 for f
in iface
.feeds
:
124 def _normal_mode(options
, args
):
126 # You can use -g on its own to edit the GUI's own policy
127 # Otherwise, failing to give an interface is an error
129 args
= [namespaces
.injector_gui_uri
]
130 options
.download_only
= True
134 iface_uri
= model
.canonical_iface_uri(args
[0])
136 policy
= autopolicy
.AutoPolicy(iface_uri
,
137 download_only
= bool(options
.download_only
),
138 dry_run
= options
.dry_run
,
139 src
= options
.source
)
141 if options
.before
or options
.not_before
:
142 policy
.root_restrictions
.append(model
.Restriction(model
.parse_version(options
.before
),
143 model
.parse_version(options
.not_before
)))
146 policy
.network_use
= model
.network_offline
148 if options
.get_selections
:
150 raise model
.SafeException("Can't use arguments with --get-selections")
152 raise model
.SafeException("Can't use --main with --get-selections")
154 # Note that need_download() triggers a solve
155 if options
.refresh
or options
.gui
:
156 # We could run immediately, but the user asked us not to
157 can_run_immediately
= False
159 can_run_immediately
= (not policy
.need_download()) and policy
.ready
161 from zeroinstall
.injector
.iface_cache
import iface_cache
162 stale_feeds
= [feed
for feed
in policy
.solver
.feeds_used
if policy
.is_stale(iface_cache
.get_feed(feed
))]
164 if options
.download_only
and stale_feeds
:
165 can_run_immediately
= False
167 if can_run_immediately
:
169 if policy
.network_use
== model
.network_offline
:
170 logging
.debug("No doing background update because we are in off-line mode.")
172 # There are feeds we should update, but we can run without them.
173 # Do the update in the background while the program is running.
175 background
.spawn_background_update(policy
, options
.verbose
> 0)
176 if options
.get_selections
:
177 _get_selections(policy
)
179 if not options
.download_only
:
180 from zeroinstall
.injector
import run
181 run
.execute(policy
, args
[1:], dry_run
= options
.dry_run
, main
= options
.main
, wrapper
= options
.wrapper
)
183 logging
.info("Downloads done (download-only mode)")
184 assert options
.dry_run
or options
.download_only
187 # If the user didn't say whether to use the GUI, choose for them.
188 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
190 # If we need to download anything, we might as well
191 # refresh all the interfaces first. Also, this triggers
192 # the 'checking for updates' box, which is non-interactive
193 # when there are no changes to the selection.
194 options
.refresh
= True
195 logging
.info("Switching to GUI mode... (use --console to disable)")
201 from zeroinstall
.injector
import run
203 if options
.download_only
:
204 # Just changes the button's label
205 gui_args
.append('--download-only')
207 gui_args
.append('--refresh')
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, '--verbose')
218 if options
.verbose
> 1:
219 gui_args
.insert(0, '--verbose')
220 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
222 sys
.exit(1) # Aborted
223 if options
.get_selections
:
225 doc
.writexml(sys
.stdout
)
226 sys
.stdout
.write('\n')
227 elif not options
.download_only
:
228 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
230 #program_log('download_and_execute ' + iface_uri)
231 policy
.download_and_execute(prog_args
, refresh
= bool(options
.refresh
), main
= options
.main
)
232 except autopolicy
.NeedDownload
, ex
:
233 # This only happens for dry runs
236 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
237 """Run the GUI to get the selections.
238 prog_args and options are used only if the GUI requests a test.
240 from zeroinstall
import helpers
241 def test_callback(sels
):
242 from zeroinstall
.injector
import run
243 return run
.test_selections(sels
, prog_args
,
244 bool(options
and options
.dry_run
),
245 options
and options
.main
)
246 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
248 def _get_selections(policy
):
250 doc
= selections
.Selections(policy
).toDOM()
251 doc
.writexml(sys
.stdout
)
252 sys
.stdout
.write('\n')
254 class UsageError(Exception): pass
256 def main(command_args
):
257 """Act as if 0launch was run with the given arguments.
258 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
259 @type command_args: [str]
261 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
262 for std
in (0, 1, 2):
266 fd
= os
.open('/dev/null', os
.O_RDONLY
)
271 parser
= OptionParser(usage
="usage: %prog [options] interface [args]\n"
272 " %prog --list [search-term]\n"
273 " %prog --import [signed-interface-files]\n"
274 " %prog --feed [interface]")
275 parser
.add_option("", "--before", help="choose a version before this", metavar
='VERSION')
276 parser
.add_option("-c", "--console", help="never use GUI", action
='store_false', dest
='gui')
277 parser
.add_option("-d", "--download-only", help="fetch but don't run", action
='store_true')
278 parser
.add_option("-D", "--dry-run", help="just print actions", action
='store_true')
279 parser
.add_option("-f", "--feed", help="add or remove a feed", action
='store_true')
280 parser
.add_option("", "--get-selections", help="write selected versions as XML", action
='store_true')
281 parser
.add_option("-g", "--gui", help="show graphical policy editor", action
='store_true')
282 parser
.add_option("-i", "--import", help="import from files, not from the network", action
='store_true')
283 parser
.add_option("-l", "--list", help="list all known interfaces", action
='store_true')
284 parser
.add_option("-m", "--main", help="name of the file to execute")
285 parser
.add_option("", "--not-before", help="minimum version to choose", metavar
='VERSION')
286 parser
.add_option("-o", "--offline", help="try to avoid using the network", action
='store_true')
287 parser
.add_option("-r", "--refresh", help="refresh all used interfaces", action
='store_true')
288 parser
.add_option("", "--set-selections", help="run versions specified in XML file", metavar
='FILE')
289 parser
.add_option("-s", "--source", help="select source code", action
='store_true')
290 parser
.add_option("-v", "--verbose", help="more verbose output", action
='count')
291 parser
.add_option("-V", "--version", help="display version information", action
='store_true')
292 parser
.add_option("-w", "--wrapper", help="execute program using a debugger, etc", metavar
='COMMAND')
293 parser
.disable_interspersed_args()
295 (options
, args
) = parser
.parse_args(command_args
)
298 logger
= logging
.getLogger()
299 if options
.verbose
== 1:
300 logger
.setLevel(logging
.INFO
)
302 logger
.setLevel(logging
.DEBUG
)
304 logging
.info("Running 0launch %s %s; Python %s", zeroinstall
.version
, repr(args
), sys
.version
)
308 _list_interfaces(args
)
309 elif options
.version
:
311 print "0launch (zero-install) " + zeroinstall
.version
312 print "Copyright (C) 2007 Thomas Leonard"
313 print "This program comes with ABSOLUTELY NO WARRANTY,"
314 print "to the extent permitted by law."
315 print "You may redistribute copies of this program"
316 print "under the terms of the GNU Lesser General Public License."
317 print "For more information about these matters, see the file named COPYING."
318 elif options
.set_selections
:
319 from zeroinstall
.injector
import selections
, qdom
, run
320 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
321 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
322 elif getattr(options
, 'import'):
323 _import_interface(args
)
325 _manage_feeds(options
, args
)
327 _normal_mode(options
, args
)
331 except model
.SafeException
, ex
:
332 if options
.verbose
: raise
333 print >>sys
.stderr
, ex