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 handler
.wait_for_downloads()
63 def _manage_feeds(options
, args
):
64 from zeroinstall
.injector
import iface_cache
, writer
65 from xml
.dom
import minidom
66 if not args
: raise UsageError()
68 print "Feed '%s':\n" % x
69 x
= model
.canonical_iface_uri(x
)
70 policy
= autopolicy
.AutoPolicy(x
, download_only
= True, dry_run
= options
.dry_run
)
72 policy
.network_use
= model
.network_offline
73 policy
.recalculate_with_dl()
74 interfaces
= policy
.get_feed_targets(policy
.root
)
75 for i
in range(len(interfaces
)):
76 feed
= interfaces
[i
].get_feed(x
)
78 print "%d) Remove as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
80 print "%d) Add as feed for '%s'" % (i
+ 1, interfaces
[i
].uri
)
84 i
= raw_input('Enter a number, or CTRL-C to cancel [1]: ').strip()
85 except KeyboardInterrupt:
87 raise model
.SafeException("Aborted at user request.")
95 if i
> 0 and i
<= len(interfaces
):
97 print "Invalid number. Try again. (1 to %d)" % len(interfaces
)
98 iface
= interfaces
[i
- 1]
99 feed
= iface
.get_feed(x
)
101 iface
.feeds
.remove(feed
)
103 iface
.feeds
.append(model
.Feed(x
, arch
= None, user_override
= True))
104 writer
.save_interface(iface
)
105 print "\nFeed list for interface '%s' is now:" % iface
.get_name()
107 for f
in iface
.feeds
:
112 def _normal_mode(options
, args
):
114 # You can use -g on its own to edit the GUI's own policy
115 # Otherwise, failing to give an interface is an error
117 args
= [namespaces
.injector_gui_uri
]
118 options
.download_only
= True
122 iface_uri
= model
.canonical_iface_uri(args
[0])
124 # Singleton instance used everywhere...
125 policy
= autopolicy
.AutoPolicy(iface_uri
,
126 download_only
= bool(options
.download_only
),
127 dry_run
= options
.dry_run
,
128 src
= options
.source
)
130 if options
.before
or options
.not_before
:
131 policy
.root_restrictions
.append(model
.Restriction(model
.parse_version(options
.before
),
132 model
.parse_version(options
.not_before
)))
135 policy
.network_use
= model
.network_offline
137 if options
.get_selections
:
139 raise model
.SafeException("Can't use arguments with --get-selections")
141 raise model
.SafeException("Can't use --main with --get-selections")
143 # Note that need_download() triggers a recalculate()
144 if options
.refresh
or options
.gui
:
145 # We could run immediately, but the user asked us not to
146 can_run_immediately
= False
148 can_run_immediately
= (not policy
.need_download()) and policy
.ready
150 if options
.download_only
and policy
.stale_feeds
:
151 can_run_immediately
= False
153 if can_run_immediately
:
154 if policy
.stale_feeds
:
155 if policy
.network_use
== model
.network_offline
:
156 logging
.debug("No doing background update because we are in off-line mode.")
158 # There are feeds we should update, but we can run without them.
159 # Do the update in the background while the program is running.
161 background
.spawn_background_update(policy
, options
.verbose
> 0)
162 if options
.get_selections
:
163 _get_selections(policy
)
165 policy
.execute(args
[1:], main
= options
.main
)
166 assert options
.dry_run
or options
.download_only
169 # If the user didn't say whether to use the GUI, choose for them.
170 if options
.gui
is None and os
.environ
.get('DISPLAY', None):
172 # If we need to download anything, we might as well
173 # refresh all the interfaces first. Also, this triggers
174 # the 'checking for updates' box, which is non-interactive
175 # when there are no changes to the selection.
176 options
.refresh
= True
177 logging
.info("Switching to GUI mode... (use --console to disable)")
183 from zeroinstall
.injector
import run
185 if options
.download_only
:
186 # Just changes the button's label
187 gui_args
.append('--download-only')
189 # Just changes the button's label
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
)
200 if not sels
: return # Aborted
201 if options
.get_selections
:
203 doc
.writexml(sys
.stdout
)
204 sys
.stdout
.write('\n')
205 elif not options
.download_only
:
206 run
.execute_selections(sels
, prog_args
, options
.dry_run
, options
.main
)
208 #program_log('download_and_execute ' + iface_uri)
209 policy
.download_and_execute(prog_args
, refresh
= bool(options
.refresh
), main
= options
.main
)
210 except autopolicy
.NeedDownload
, ex
:
211 # This only happens for dry runs
214 def _fork_gui(iface_uri
, gui_args
):
215 gui_policy
= autopolicy
.AutoPolicy(namespaces
.injector_gui_uri
)
216 # Try to start the GUI without using the network.
217 # The GUI can refresh itself if it wants to.
218 gui_policy
.freshness
= 0
219 gui_policy
.network_use
= model
.network_offline
220 gui_policy
.recalculate_with_dl()
221 assert gui_policy
.ready
# Should always be some version available
222 gui_policy
.start_downloading_impls()
223 gui_policy
.handler
.wait_for_downloads()
225 from zeroinstall
.injector
import run
227 cli
, gui
= socket
.socketpair()
235 os
.dup2(gui
.fileno(), 1)
236 run
.execute(gui_policy
, gui_args
+ ['--', iface_uri
])
239 traceback
.print_exc()
250 pid
, status
= os
.waitpid(child
, 0)
253 return None # Aborted
255 raise Exception("Error from GUI: code = %d" % status
)
257 if cli
is not None: cli
.close()
258 if gui
is not None: gui
.close()
260 from StringIO
import StringIO
261 from zeroinstall
.injector
import qdom
, selections
262 return selections
.Selections(qdom
.parse(StringIO(xml
)))
264 def _get_selections(policy
):
266 doc
= selections
.Selections(policy
).toDOM()
267 doc
.writexml(sys
.stdout
)
268 sys
.stdout
.write('\n')
270 class UsageError(Exception): pass
272 def main(command_args
):
273 """Act as if 0launch was run with the given arguments.
274 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
275 @type command_args: [str]
277 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
278 for std
in (0, 1, 2):
282 fd
= os
.open('/dev/null', os
.O_RDONLY
)
287 parser
= OptionParser(usage
="usage: %prog [options] interface [args]\n"
288 " %prog --list [search-term]\n"
289 " %prog --import [signed-interface-files]\n"
290 " %prog --feed [interface]")
291 parser
.add_option("", "--before", help="choose a version before this", metavar
='VERSION')
292 parser
.add_option("-c", "--console", help="never use GUI", action
='store_false', dest
='gui')
293 parser
.add_option("-d", "--download-only", help="fetch but don't run", action
='store_true')
294 parser
.add_option("-D", "--dry-run", help="just print actions", action
='store_true')
295 parser
.add_option("-f", "--feed", help="add or remove a feed", action
='store_true')
296 parser
.add_option("", "--get-selections", help="write selected versions as XML", action
='store_true')
297 parser
.add_option("-g", "--gui", help="show graphical policy editor", action
='store_true')
298 parser
.add_option("-i", "--import", help="import from files, not from the network", action
='store_true')
299 parser
.add_option("-l", "--list", help="list all known interfaces", action
='store_true')
300 parser
.add_option("-m", "--main", help="name of the file to execute")
301 parser
.add_option("", "--not-before", help="minimum version to choose", metavar
='VERSION')
302 parser
.add_option("-o", "--offline", help="try to avoid using the network", action
='store_true')
303 parser
.add_option("-r", "--refresh", help="refresh all used interfaces", action
='store_true')
304 parser
.add_option("", "--set-selections", help="run versions specified in XML file", metavar
='FILE')
305 parser
.add_option("-s", "--source", help="select source code", action
='store_true')
306 parser
.add_option("-v", "--verbose", help="more verbose output", action
='count')
307 parser
.add_option("-V", "--version", help="display version information", action
='store_true')
308 parser
.disable_interspersed_args()
310 (options
, args
) = parser
.parse_args(command_args
)
313 logger
= logging
.getLogger()
314 if options
.verbose
== 1:
315 logger
.setLevel(logging
.INFO
)
317 logger
.setLevel(logging
.DEBUG
)
319 logging
.info("Running 0launch %s %s; Python %s", zeroinstall
.version
, repr(args
), sys
.version
)
323 _list_interfaces(args
)
324 elif options
.version
:
326 print "0launch (zero-install) " + zeroinstall
.version
327 print "Copyright (C) 2006 Thomas Leonard"
328 print "This program comes with ABSOLUTELY NO WARRANTY,"
329 print "to the extent permitted by law."
330 print "You may redistribute copies of this program"
331 print "under the terms of the GNU General Public License."
332 print "For more information about these matters, see the file named COPYING."
333 elif options
.set_selections
:
334 from zeroinstall
.injector
import selections
, qdom
, run
335 sels
= selections
.Selections(qdom
.parse(file(options
.set_selections
)))
336 run
.execute_selections(sels
, args
, options
.dry_run
, options
.main
)
337 elif getattr(options
, 'import'):
338 _import_interface(args
)
340 _manage_feeds(options
, args
)
342 _normal_mode(options
, args
)
346 except model
.SafeException
, ex
:
347 if options
.verbose
: raise
348 print >>sys
.stderr
, ex