Fixed support for setting --main with 0alias
[zeroinstall.git] / 0alias
blob148c8b53603ae78c97caea44634a08e3f253028b
1 #!/usr/bin/env python
2 # Copyright (C) 2010, Thomas Leonard
3 # See the README file for details, or visit http://0install.net.
5 import locale
6 from logging import warn
7 try:
8 locale.setlocale(locale.LC_ALL, '')
9 except locale.Error:
10 warn('Error setting locale (eg. Invalid locale)')
12 import os, sys
14 ## PATH ##
16 from optparse import OptionParser
18 from zeroinstall.injector import reader, model
19 from zeroinstall import support, alias, helpers
20 from zeroinstall.support import basedir
22 def export(name, value):
23 """Try to guess the command to set an environment variable."""
24 shell = os.environ.get('SHELL', '?')
25 if 'csh' in shell:
26 return "setenv %s %s" % (name, value)
27 return "export %s=%s" % (name, value)
29 def find_path(paths):
30 """Find the first writable path in : separated list."""
31 for path in paths:
32 if os.path.realpath(path).startswith(basedir.xdg_cache_home):
33 pass # print "Skipping cache", first_path
34 elif not os.access(path, os.W_OK):
35 pass # print "No access", first_path
36 else:
37 break
38 else:
39 return None
41 return path
43 # Do this here so we can include it in the help message.
44 # But, don't abort if there isn't one because we might
45 # be doing something else (e.g. --manpage)
46 first_path = find_path(os.environ['PATH'].split(':'))
47 in_path = first_path is not None
48 if not in_path:
49 first_path = os.path.expanduser('~/bin/')
51 parser = OptionParser(usage="usage: %%prog [options] alias [interface [main]]\n\n"
52 "Creates a script to run 'interface' (will be created in\n"
53 "%s unless overridden by --dir).\n\n"
54 "If no interface is given, edits the policy for an existing alias.\n"
55 "For interfaces providing more than one executable, the desired\n"
56 "'main' binary or command may also be given." % first_path)
57 parser.add_option("-c", "--command", help="the command the alias will run (default 'run')", metavar='COMMNAD')
58 parser.add_option("-m", "--manpage", help="show the manual page for an existing alias", action='store_true')
59 parser.add_option("-r", "--resolve", help="show the URI for an alias", action='store_true')
60 parser.add_option("-V", "--version", help="display version information", action='store_true')
61 parser.add_option("-d", "--dir", help="install in DIR", dest="user_path", metavar="DIR")
62 parser.disable_interspersed_args()
64 (options, args) = parser.parse_args()
66 if options.version:
67 import zeroinstall
68 print "0alias (zero-install) " + zeroinstall.version
69 print "Copyright (C) 2010 Thomas Leonard"
70 print "This program comes with ABSOLUTELY NO WARRANTY,"
71 print "to the extent permitted by law."
72 print "You may redistribute copies of this program"
73 print "under the terms of the GNU Lesser General Public License."
74 print "For more information about these matters, see the file named COPYING."
75 sys.exit(0)
77 if options.manpage:
78 if len(args) != 1:
79 os.execlp('man', 'man', *args)
80 sys.exit(1)
82 if len(args) < 1 or len(args) > 3:
83 parser.print_help()
84 sys.exit(1)
85 alias_prog, interface_uri, main = (list(args) + [None, None])[:3]
86 command = options.command
88 if options.resolve or options.manpage:
89 if interface_uri is not None:
90 parser.print_help()
91 sys.exit(1)
93 if options.user_path:
94 first_path = options.user_path
96 if interface_uri is None:
97 if options.command:
98 print >>sys.stderr, "Can't use --command when editing an existing alias"
99 sys.exit(1)
100 try:
101 if not os.path.isabs(alias_prog):
102 full_path = support.find_in_path(alias_prog)
103 if not full_path:
104 raise alias.NotAnAliasScript("Not found in $PATH: " + alias_prog)
105 else:
106 full_path = alias_prog
108 alias_info = alias.parse_script(full_path)
109 interface_uri = alias_info.uri
110 main = alias_info.main
111 command = alias_info.command
112 except alias.NotAnAliasScript as ex:
113 if options.manpage:
114 os.execlp('man', 'man', *args)
115 print >>sys.stderr, str(ex)
116 sys.exit(1)
118 interface_uri = model.canonical_iface_uri(interface_uri)
120 if options.resolve:
121 print interface_uri
122 sys.exit(0)
124 if options.manpage:
125 sels = helpers.ensure_cached(interface_uri, command = command)
126 if not sels:
127 # Cancelled by user
128 sys.exit(1)
130 if sels.commands:
131 selected_command = sels.commands[0]
132 else:
133 print >>sys.stderr, "No <command> in selections!"
134 selected_impl = sels.selections[interface_uri]
136 from zeroinstall.injector.iface_cache import iface_cache
137 impl_path = selected_impl.local_path or iface_cache.stores.lookup_any(selected_impl.digests)
139 if main is None:
140 main = selected_command.path
141 if main is None:
142 print >>sys.stderr, "No main program for interface '%s'" % interface_uri
143 sys.exit(1)
145 prog_name = os.path.basename(main)
146 alias_name = os.path.basename(args[0])
148 assert impl_path
150 # TODO: the feed should say where the man-pages are, but for now we'll accept
151 # a directory called man in some common locations...
152 for mandir in ['man', 'share/man', 'usr/man', 'usr/share/man']:
153 manpath = os.path.join(impl_path, mandir)
154 if os.path.isdir(manpath):
155 # Note: unlike "man -M", this also copes with LANG settings...
156 os.environ['MANPATH'] = manpath
157 os.execlp('man', 'man', prog_name)
158 sys.exit(1)
160 # No man directory given or found, so try searching for man files
162 manpages = []
163 for root, dirs, files in os.walk(impl_path):
164 for f in files:
165 if f.endswith('.gz'):
166 manpage_file = f[:-3]
167 else:
168 manpage_file = f
169 if manpage_file.endswith('.1') or \
170 manpage_file.endswith('.6') or \
171 manpage_file.endswith('.8'):
172 manpage_prog = manpage_file[:-2]
173 if manpage_prog == prog_name or manpage_prog == alias_name:
174 os.execlp('man', 'man', os.path.join(root, f))
175 sys.exit(1)
176 else:
177 manpages.append((root, f))
179 print "No matching manpage was found for '%s' (%s)" % (alias_name, interface_uri)
180 if manpages:
181 print "These non-matching man-pages were found, however:"
182 for root, file in manpages:
183 print os.path.join(root, file)
184 sys.exit(1)
186 if not os.path.isdir(first_path):
187 print "(creating directory %s)" % first_path
188 os.makedirs(first_path)
190 if len(args) == 1:
191 os.execlp('0launch', '0launch', '-gd', '--', interface_uri)
192 sys.exit(1)
194 try:
195 interface = model.Interface(interface_uri)
196 if not reader.update_from_cache(interface):
197 print >>sys.stderr, "Interface '%s' not currently in cache. Fetching..." % interface_uri
198 if os.spawnlp(os.P_WAIT, '0launch', '0launch', '-d', interface_uri):
199 raise model.SafeException("0launch failed")
200 if not reader.update_from_cache(interface):
201 raise model.SafeException("Interface still not in cache. Aborting.")
203 script = os.path.join(first_path, alias_prog)
204 if os.path.exists(script):
205 raise model.SafeException("File '%s' already exists. Delete it first." % script)
206 sys.exit(1)
207 except model.SafeException as ex:
208 print >>sys.stderr, ex
209 sys.exit(1)
211 wrapper = file(script, 'w')
212 alias.write_script(wrapper, interface_uri, main, command = command)
214 # Make new script executable
215 os.chmod(script, 0o111 | os.fstat(wrapper.fileno()).st_mode)
216 wrapper.close()
218 #print "Created script '%s'." % script
219 #print "To edit policy: 0alias %s" % alias_prog
220 if options.user_path:
221 pass # Assume user knows what they're doing
222 elif not in_path:
223 print >>sys.stderr, 'Warning: %s is not in $PATH. Add it with:\n%s' % (first_path, export('PATH', first_path + ':$PATH'))
224 else:
225 shell = os.environ.get('SHELL', '?')
226 if not (shell.endswith('/zsh') or shell.endswith('/bash')):
227 print "(note: some shells require you to type 'rehash' now)"