When run with -v, log the 0launch version, arguments and Python version.
[zeroinstall.git] / zeroinstall / injector / cli.py
blobfe2fab0aa7d2e1e53c593009f633371de0e57fbc
1 """
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
5 install time.
6 """
8 import os, sys
9 from optparse import OptionParser
10 import logging
12 #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK)
13 #import __main__
14 #__main__.__builtins__.program_log = program_log
15 #program_log('0launch ' + ' '.join((sys.argv[1:])))
17 def main(command_args):
18 """Act as if 0launch was run with the given arguments.
19 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
20 @type command_args: [str]
21 """
22 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
23 for std in (0, 1, 2):
24 try:
25 os.fstat(std)
26 except OSError:
27 fd = os.open('/dev/null', os.O_RDONLY)
28 if fd != std:
29 os.dup2(fd, std)
30 os.close(fd)
32 parser = OptionParser(usage="usage: %prog [options] interface [args]\n"
33 " %prog --list [search-term]\n"
34 " %prog --import [signed-interface-files]\n"
35 " %prog --feed [interface]")
36 parser.add_option("", "--before", help="choose a version before this", metavar='VERSION')
37 parser.add_option("-c", "--console", help="never use GUI", action='store_false', dest='gui')
38 parser.add_option("-d", "--download-only", help="fetch but don't run", action='store_true')
39 parser.add_option("-D", "--dry-run", help="just print actions", action='store_true')
40 parser.add_option("-f", "--feed", help="add or remove a feed", action='store_true')
41 parser.add_option("-g", "--gui", help="show graphical policy editor", action='store_true')
42 parser.add_option("-i", "--import", help="import from files, not from the network", action='store_true')
43 parser.add_option("-l", "--list", help="list all known interfaces", action='store_true')
44 parser.add_option("-m", "--main", help="name of the file to execute")
45 parser.add_option("", "--not-before", help="minimum version to choose", metavar='VERSION')
46 parser.add_option("-o", "--offline", help="try to avoid using the network", action='store_true')
47 parser.add_option("-r", "--refresh", help="refresh all used interfaces", action='store_true')
48 parser.add_option("-s", "--source", help="select source code", action='store_true')
49 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
50 parser.add_option("-V", "--version", help="display version information", action='store_true')
51 parser.disable_interspersed_args()
53 (options, args) = parser.parse_args(command_args)
55 if options.verbose:
56 logger = logging.getLogger()
57 if options.verbose == 1:
58 logger.setLevel(logging.INFO)
59 else:
60 logger.setLevel(logging.DEBUG)
61 import zeroinstall
62 logging.info("Running 0launch %s %s; Python %s", zeroinstall.version, repr(args), sys.version)
64 from zeroinstall.injector import model, download, autopolicy, namespaces
66 if options.list:
67 from zeroinstall.injector.iface_cache import iface_cache
68 if len(args) == 0:
69 match = None
70 matches = iface_cache.list_all_interfaces()
71 elif len(args) == 1:
72 match = args[0].lower()
73 matches = [i for i in iface_cache.list_all_interfaces() if match in i.lower()]
74 else:
75 parser.print_help()
76 sys.exit(1)
78 matches.sort()
79 for i in matches:
80 print i
81 sys.exit(0)
83 if options.version:
84 import zeroinstall
85 print "0launch (zero-install) " + zeroinstall.version
86 print "Copyright (C) 2006 Thomas Leonard"
87 print "This program comes with ABSOLUTELY NO WARRANTY,"
88 print "to the extent permitted by law."
89 print "You may redistribute copies of this program"
90 print "under the terms of the GNU General Public License."
91 print "For more information about these matters, see the file named COPYING."
92 sys.exit(0)
94 if len(args) < 1:
95 if options.gui:
96 args = [namespaces.injector_gui_uri]
97 options.download_only = True
98 else:
99 parser.print_help()
100 sys.exit(1)
102 try:
103 if getattr(options, 'import'):
104 from zeroinstall.injector import gpg, handler, trust
105 from zeroinstall.injector.iface_cache import iface_cache, PendingFeed
106 from xml.dom import minidom
107 for x in args:
108 if not os.path.isfile(x):
109 raise model.SafeException("File '%s' does not exist" % x)
110 logging.info("Importing from file '%s'", x)
111 signed_data = file(x)
112 data, sigs = gpg.check_stream(signed_data)
113 doc = minidom.parseString(data.read())
114 uri = doc.documentElement.getAttribute('uri')
115 if not uri:
116 raise model.SafeException("Missing 'uri' attribute on root element in '%s'" % x)
117 iface = iface_cache.get_interface(uri)
118 logging.info("Importing information about interface %s", iface)
119 signed_data.seek(0)
121 def keys_ready():
122 if not iface_cache.update_interface_if_trusted(iface, pending.sigs, pending.new_xml):
123 handler.confirm_trust_keys(iface, pending.sigs, pending.new_xml)
124 trust.trust_db.watchers.append(lambda: keys_ready())
126 pending = PendingFeed(uri, signed_data)
127 iface_cache.add_pending(pending)
129 handler = handler.Handler()
130 pending.begin_key_downloads(handler, keys_ready)
131 handler.wait_for_downloads()
133 sys.exit(0)
135 if getattr(options, 'feed'):
136 from zeroinstall.injector import iface_cache, writer
137 from xml.dom import minidom
138 for x in args:
139 print "Feed '%s':\n" % x
140 x = model.canonical_iface_uri(x)
141 policy = autopolicy.AutoPolicy(x, download_only = True, dry_run = options.dry_run)
142 if options.offline:
143 policy.network_use = model.network_offline
144 policy.recalculate_with_dl()
145 interfaces = policy.get_feed_targets(policy.root)
146 for i in range(len(interfaces)):
147 feed = interfaces[i].get_feed(x)
148 if feed:
149 print "%d) Remove as feed for '%s'" % (i + 1, interfaces[i].uri)
150 else:
151 print "%d) Add as feed for '%s'" % (i + 1, interfaces[i].uri)
152 print
153 while True:
154 try:
155 i = raw_input('Enter a number, or CTRL-C to cancel [1]: ').strip()
156 except KeyboardInterrupt:
157 print
158 raise model.SafeException("Aborted at user request.")
159 if i == '':
160 i = 1
161 else:
162 try:
163 i = int(i)
164 except ValueError:
165 i = 0
166 if i > 0 and i <= len(interfaces):
167 break
168 print "Invalid number. Try again. (1 to %d)" % len(interfaces)
169 iface = interfaces[i - 1]
170 feed = iface.get_feed(x)
171 if feed:
172 iface.feeds.remove(feed)
173 else:
174 iface.feeds.append(model.Feed(x, arch = None, user_override = True))
175 writer.save_interface(iface)
176 print "\nFeed list for interface '%s' is now:" % iface.get_name()
177 if iface.feeds:
178 for f in iface.feeds:
179 print "- " + f.uri
180 else:
181 print "(no feeds)"
182 sys.exit(0)
184 iface_uri = model.canonical_iface_uri(args[0])
186 # Singleton instance used everywhere...
187 policy = autopolicy.AutoPolicy(iface_uri,
188 download_only = bool(options.download_only),
189 dry_run = options.dry_run,
190 src = options.source)
192 if options.before or options.not_before:
193 policy.root_restrictions.append(model.Restriction(model.parse_version(options.before),
194 model.parse_version(options.not_before)))
196 if options.offline:
197 policy.network_use = model.network_offline
199 # Note that need_download() triggers a recalculate()
200 if options.refresh or options.gui:
201 # We could run immediately, but the user asked us not to
202 can_run_immediately = False
203 else:
204 can_run_immediately = (not policy.need_download()) and policy.ready
206 if options.download_only and policy.stale_feeds:
207 can_run_immediately = False
209 if can_run_immediately:
210 if policy.stale_feeds:
211 # There are feeds we should update, but we can run without them.
212 # Do the update in the background while the program is running.
213 import background
214 background.spawn_background_update(policy, options.verbose > 0)
215 policy.execute(args[1:], main = options.main)
216 assert options.dry_run or options.download_only
217 return
219 # If the user didn't say whether to use the GUI, choose for them.
220 if options.gui is None and os.environ.get('DISPLAY', None):
221 options.gui = True
222 # If we need to download anything, we might as well
223 # refresh all the interfaces first. Also, this triggers
224 # the 'checking for updates' box, which is non-interactive
225 # when there are no changes to the selection.
226 options.refresh = True
227 logging.info("Switching to GUI mode... (use --console to disable)")
228 except model.SafeException, ex:
229 if options.verbose: raise
230 print >>sys.stderr, ex
231 sys.exit(1)
233 if options.gui:
234 policy.set_root(namespaces.injector_gui_uri)
235 policy.src = False
237 # Try to start the GUI without using the network.
238 # The GUI can refresh itself if it wants to.
239 policy.freshness = 0
240 policy.network_use = model.network_offline
242 prog_args = [iface_uri] + args[1:]
243 # Options apply to actual program, not GUI
244 if options.download_only:
245 policy.download_only = False
246 prog_args.insert(0, '--download-only')
247 if options.refresh:
248 options.refresh = False
249 prog_args.insert(0, '--refresh')
250 if options.not_before:
251 prog_args.insert(0, options.not_before)
252 prog_args.insert(0, '--not-before')
253 if options.before:
254 prog_args.insert(0, options.before)
255 prog_args.insert(0, '--before')
256 if options.source:
257 prog_args.insert(0, '--source')
258 if options.main:
259 prog_args = ['--main', options.main] + prog_args
260 options.main = None
261 del policy.root_restrictions[:]
262 else:
263 prog_args = args[1:]
265 try:
266 #program_log('download_and_execute ' + iface_uri)
267 policy.download_and_execute(prog_args, refresh = bool(options.refresh), main = options.main)
268 except autopolicy.NeedDownload, ex:
269 print ex
270 sys.exit(0)
271 except model.SafeException, ex:
272 if options.verbose: raise
273 print >>sys.stderr, ex
274 sys.exit(1)