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
.injector
import gpg
, handler
, trust
35 from zeroinstall
.injector
.iface_cache
import iface_cache
, PendingFeed
36 from xml
.dom
import minidom
38 if not os
.path
.isfile(x
):
39 raise model
.SafeException("File '%s' does not exist" % x
)
40 logging
.info("Importing from file '%s'", x
)
42 data
, sigs
= gpg
.check_stream(signed_data
)
43 doc
= minidom
.parseString(data
.read())
44 uri
= doc
.documentElement
.getAttribute('uri')
46 raise model
.SafeException("Missing 'uri' attribute on root element in '%s'" % x
)
47 iface
= iface_cache
.get_interface(uri
)
48 logging
.info("Importing information about interface %s", iface
)
52 if not iface_cache
.update_interface_if_trusted(iface
, pending
.sigs
, pending
.new_xml
):
53 handler
.confirm_trust_keys(iface
, pending
.sigs
, pending
.new_xml
)
54 trust
.trust_db
.watchers
.append(lambda: keys_ready())
56 pending
= PendingFeed(uri
, signed_data
)
57 iface_cache
.add_pending(pending
)
59 handler
= handler
.Handler()
60 pending
.begin_key_downloads(handler
, keys_ready
)
61 errors
= handler
.wait_for_downloads()
63 raise model
.SafeException("Errors during download: " + '\n'.join(errors
))
65 def _manage_feeds(options
, args
):
66 from zeroinstall
.injector
import iface_cache
, writer
67 from xml
.dom
import minidom
68 if not args
: raise UsageError()
70 print "Feed '%s':\n" % x
71 x
= model
.canonical_iface_uri(x
)
72 policy
= autopolicy
.AutoPolicy(x
, download_only
= True, dry_run
= options
.dry_run
)
74 policy
.network_use
= model
.network_offline
75 policy
.recalculate_with_dl()
76 interfaces
= policy
.get_feed_targets(policy
.root
)
77 for i
in range(len(interfaces
)):
78 feed
= interfaces
[i
].get_feed(x
)
80 print "%d) Remove as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
82 print "%d) Add as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
86 i
= raw_input('Enter a number, or CTRL-C to cancel [1]: ').strip()
87 except KeyboardInterrupt:
89 raise model
.SafeException("Aborted at user request.")
97 if i
> 0 and i
<= len(interfaces
):
99 print "Invalid number. Try again. (1 to %d)" % len(interfaces
)
100 iface
= interfaces
[i
- 1]
101 feed
= iface
.get_feed(x
)
103 iface
.extra_feeds
.remove(feed
)
105 iface
.extra_feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
106 writer
.save_interface(iface
)
107 print "\nFeed list for interface '%s' is now:" % iface
.get_name()
109 for f
in iface
.feeds
:
114 def _normal_mode(options
, args
):
116 # You can use -g on its own to edit the GUI's own policy
117 # Otherwise, failing to give an interface is an error
119 args
= [namespaces
.injector_gui_uri
]
120 options
.download_only
= True
124 iface_uri
= model
.canonical_iface_uri(args
[0])
126 policy
= autopolicy
.AutoPolicy(iface_uri
,
127 download_only
= bool(options
.download_only
),
128 dry_run
= options
.dry_run
,
129 src
= options
.source
)
131 if options
.before
or options
.not_before
:
132 policy
.root_restrictions
.append(model
.Restriction(model
.parse_version(options
.before
),
133 model
.parse_version(options
.not_before
)))
136 policy
.network_use
= model
.network_offline
138 if options
.get_selections
:
140 raise model
.SafeException("Can't use arguments with --get-selections")
142 raise model
.SafeException("Can't use --main with --get-selections")
144 # Note that need_download() triggers a recalculate()
145 if options
.refresh
or options
.gui
:
146 # We could run immediately, but the user asked us not to
147 can_run_immediately
= False
149 can_run_immediately
= (not policy
.need_download()) and policy
.ready
151 if options
.download_only
and policy
.stale_feeds
:
152 can_run_immediately
= False
154 if can_run_immediately
:
155 if policy
.stale_feeds
:
156 if policy
.network_use
== model
.network_offline
:
157 logging
.debug("No doing background update because we are in off-line mode.")
159 # There are feeds we should update, but we can run without them.
160 # Do the update in the background while the program is running.
162 background
.spawn_background_update(policy
, options
.verbose
> 0)
163 if options
.get_selections
:
164 _get_selections(policy
)
166 policy
.execute(args
[1:], main
= options
.main
, wrapper
= options
.wrapper
)
167 assert options
.dry_run
or options
.download_only
170 # If the user didn't say whether to use the GUI, choose for them.
171 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
173 # If we need to download anything, we might as well
174 # refresh all the interfaces first. Also, this triggers
175 # the 'checking for updates' box, which is non-interactive
176 # when there are no changes to the selection.
177 options
.refresh
= True
178 logging
.info("Switching to GUI mode... (use --console to disable)")
184 from zeroinstall
.injector
import run
186 if options
.download_only
:
187 # Just changes the button's label
188 gui_args
.append('--download-only')
190 gui_args
.append('--refresh')
191 if options
.not_before
:
192 gui_args
.insert(0, options
.not_before
)
193 gui_args
.insert(0, '--not-before')
195 gui_args
.insert(0, options
.before
)
196 gui_args
.insert(0, '--before')
198 gui_args
.insert(0, '--source')
199 sels
= _fork_gui(iface_uri
, gui_args
, prog_args
, options
)
201 sys
.exit(1) # Aborted
202 if options
.get_selections
:
204 doc
.writexml(sys
.stdout
)
205 sys
.stdout
.write('\n')
206 elif not options
.download_only
:
207 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
, options
.wrapper
)
209 #program_log('download_and_execute ' + iface_uri)
210 policy
.download_and_execute(prog_args
, refresh
= bool(options
.refresh
), main
= options
.main
)
211 except autopolicy
.NeedDownload
, ex
:
212 # This only happens for dry runs
215 def _fork_gui(iface_uri
, gui_args
, prog_args
, options
= None):
216 """Run the GUI to get the selections.
217 prog_args and options are used only if the GUI requests a test.
219 from zeroinstall
import helpers
220 def test_callback(sels
):
221 from zeroinstall
.injector
import run
222 return run
.test_selections(sels
, prog_args
,
223 bool(options
and options
.dry_run
),
224 options
and options
.main
)
225 return helpers
.get_selections_gui(iface_uri
, gui_args
, test_callback
)
227 def _get_selections(policy
):
229 doc
= selections
.Selections(policy
).toDOM()
230 doc
.writexml(sys
.stdout
)
231 sys
.stdout
.write('\n')
233 class UsageError(Exception): pass
235 def main(command_args
):
236 """Act as if 0launch was run with the given arguments.
237 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
238 @type command_args: [str]
240 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
241 for std
in (0, 1, 2):
245 fd
= os
.open('/dev/null', os
.O_RDONLY
)
250 parser
= OptionParser(usage
="usage: %prog [options] interface [args]\n"
251 " %prog --list [search-term]\n"
252 " %prog --import [signed-interface-files]\n"
253 " %prog --feed [interface]")
254 parser
.add_option("", "--before", help="choose a version before this", metavar
='VERSION')
255 parser
.add_option("-c", "--console", help="never use GUI", action
='store_false', dest
='gui')
256 parser
.add_option("-d", "--download-only", help="fetch but don't run", action
='store_true')
257 parser
.add_option("-D", "--dry-run", help="just print actions", action
='store_true')
258 parser
.add_option("-f", "--feed", help="add or remove a feed", action
='store_true')
259 parser
.add_option("", "--get-selections", help="write selected versions as XML", action
='store_true')
260 parser
.add_option("-g", "--gui", help="show graphical policy editor", action
='store_true')
261 parser
.add_option("-i", "--import", help="import from files, not from the network", action
='store_true')
262 parser
.add_option("-l", "--list", help="list all known interfaces", action
='store_true')
263 parser
.add_option("-m", "--main", help="name of the file to execute")
264 parser
.add_option("", "--not-before", help="minimum version to choose", metavar
='VERSION')
265 parser
.add_option("-o", "--offline", help="try to avoid using the network", action
='store_true')
266 parser
.add_option("-r", "--refresh", help="refresh all used interfaces", action
='store_true')
267 parser
.add_option("", "--set-selections", help="run versions specified in XML file", metavar
='FILE')
268 parser
.add_option("-s", "--source", help="select source code", action
='store_true')
269 parser
.add_option("-v", "--verbose", help="more verbose output", action
='count')
270 parser
.add_option("-V", "--version", help="display version information", action
='store_true')
271 parser
.add_option("-w", "--wrapper", help="execute program using a debugger, etc", metavar
='COMMAND')
272 parser
.disable_interspersed_args()
274 (options
, args
) = parser
.parse_args(command_args
)
277 logger
= logging
.getLogger()
278 if options
.verbose
== 1:
279 logger
.setLevel(logging
.INFO
)
281 logger
.setLevel(logging
.DEBUG
)
283 logging
.info("Running 0launch %s %s; Python %s", zeroinstall
.version
, repr(args
), sys
.version
)
287 _list_interfaces(args
)
288 elif options
.version
:
290 print "0launch (zero-install) " + zeroinstall
.version
291 print "Copyright (C) 2007 Thomas Leonard"
292 print "This program comes with ABSOLUTELY NO WARRANTY,"
293 print "to the extent permitted by law."
294 print "You may redistribute copies of this program"
295 print "under the terms of the GNU Lesser General Public License."
296 print "For more information about these matters, see the file named COPYING."
297 elif options
.set_selections
:
298 from zeroinstall
.injector
import selections
, qdom
, run
299 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
300 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
, options
.wrapper
)
301 elif getattr(options
, 'import'):
302 _import_interface(args
)
304 _manage_feeds(options
, args
)
306 _normal_mode(options
, args
)
310 except model
.SafeException
, ex
:
311 if options
.verbose
: raise
312 print >>sys
.stderr
, ex