Fixed some file descriptor leaks
[zeroinstall/solver.git] / 0alias
blob2922a9f9cad18ee86e17c4cf0d4f1d16a642b99d
1 #!/usr/bin/env python
2 # Copyright (C) 2010, Thomas Leonard
3 # See the README file for details, or visit http://0install.net.
5 from __future__ import print_function
7 import locale
8 import logging
9 from logging import warn
10 try:
11 locale.setlocale(locale.LC_ALL, '')
12 except locale.Error:
13 warn('Error setting locale (eg. Invalid locale)')
15 import os, sys
17 ## PATH ##
19 from optparse import OptionParser
21 from zeroinstall.injector import reader, model
22 from zeroinstall import support, alias, helpers, _
23 from zeroinstall.support import basedir
25 def export(name, value):
26 """Try to guess the command to set an environment variable."""
27 shell = os.environ.get('SHELL', '?')
28 if 'csh' in shell:
29 return "setenv %s %s" % (name, value)
30 return "export %s=%s" % (name, value)
32 def find_path(paths):
33 """Find the first writable path in the list,
34 skipping /bin, /sbin and everything under /usr except /usr/local/bin"""
35 for path in paths:
36 if path.startswith('/usr/') and not path.startswith('/usr/local/bin'):
37 # (/usr/local/bin is OK if we're running as root)
38 pass
39 elif path.startswith('/bin') or path.startswith('/sbin'):
40 pass
41 elif os.path.realpath(path).startswith(basedir.xdg_cache_home):
42 pass # print "Skipping cache", first_path
43 elif not os.access(path, os.W_OK):
44 pass # print "No access", first_path
45 else:
46 break
47 else:
48 return None
50 return path
52 # Do this here so we can include it in the help message.
53 # But, don't abort if there isn't one because we might
54 # be doing something else (e.g. --manpage)
55 first_path = find_path(os.environ['PATH'].split(':'))
56 in_path = first_path is not None
57 if not in_path:
58 first_path = os.path.expanduser('~/bin/')
60 parser = OptionParser(usage="usage: %%prog [options] alias [interface [main]]\n\n"
61 "Creates a script to run 'interface' (will be created in\n"
62 "%s unless overridden by --dir).\n\n"
63 "If no interface is given, edits the policy for an existing alias.\n"
64 "For interfaces providing more than one executable, the desired\n"
65 "'main' binary or command may also be given." % first_path)
66 parser.add_option("-c", "--command", help="the command the alias will run (default 'run')", metavar='COMMNAD')
67 parser.add_option("-m", "--manpage", help="show the manual page for an existing alias", action='store_true')
68 parser.add_option("-r", "--resolve", help="show the URI for an alias", action='store_true')
69 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
70 parser.add_option("-V", "--version", help="display version information", action='store_true')
71 parser.add_option("-d", "--dir", help="install in DIR", dest="user_path", metavar="DIR")
73 (options, args) = parser.parse_args()
75 if options.verbose:
76 logger = logging.getLogger()
77 if options.verbose == 1:
78 logger.setLevel(logging.INFO)
79 else:
80 logger.setLevel(logging.DEBUG)
81 hdlr = logging.StreamHandler()
82 fmt = logging.Formatter("%(levelname)s:%(message)s")
83 hdlr.setFormatter(fmt)
84 logger.addHandler(hdlr)
86 if options.version:
87 import zeroinstall
88 print("0alias (zero-install) " + zeroinstall.version)
89 print("Copyright (C) 2010 Thomas Leonard")
90 print("This program comes with ABSOLUTELY NO WARRANTY,")
91 print("to the extent permitted by law.")
92 print("You may redistribute copies of this program")
93 print("under the terms of the GNU Lesser General Public License.")
94 print("For more information about these matters, see the file named COPYING.")
95 sys.exit(0)
97 if options.manpage:
98 if len(args) != 1:
99 os.execlp('man', 'man', *args)
100 sys.exit(1)
102 if len(args) < 1 or len(args) > 3:
103 parser.print_help()
104 sys.exit(1)
105 alias_prog, interface_uri, main = (list(args) + [None, None])[:3]
106 command = options.command
108 if options.resolve or options.manpage:
109 if interface_uri is not None:
110 parser.print_help()
111 sys.exit(1)
113 if options.user_path:
114 first_path = options.user_path
116 if interface_uri is None:
117 if options.command:
118 print("Can't use --command when editing an existing alias", file=sys.stderr)
119 sys.exit(1)
120 try:
121 if not os.path.isabs(alias_prog):
122 full_path = support.find_in_path(alias_prog)
123 if not full_path:
124 raise alias.NotAnAliasScript("Not found in $PATH: " + alias_prog)
125 else:
126 full_path = alias_prog
128 alias_info = alias.parse_script(full_path)
129 interface_uri = alias_info.uri
130 main = alias_info.main
131 command = alias_info.command
132 except (alias.NotAnAliasScript, IOError) as ex:
133 # (we get IOError if e.g. the script isn't readable)
134 if options.manpage:
135 logging.debug("not a 0alias script '%s': %s", alias_prog, ex)
136 os.execlp('man', 'man', *args)
137 print(str(ex), file=sys.stderr)
138 sys.exit(1)
140 interface_uri = model.canonical_iface_uri(interface_uri)
142 if options.resolve:
143 print(interface_uri)
144 sys.exit(0)
146 if options.manpage:
147 sels = helpers.ensure_cached(interface_uri, command = command or 'run')
148 if not sels:
149 # Cancelled by user
150 sys.exit(1)
152 if sels.commands:
153 selected_command = sels.commands[0]
154 else:
155 print("No <command> in selections!", file=sys.stderr)
156 selected_impl = sels.selections[interface_uri]
158 from zeroinstall.injector.iface_cache import iface_cache
159 impl_path = selected_impl.local_path or iface_cache.stores.lookup_any(selected_impl.digests)
161 if main is None:
162 main = selected_command.path
163 if main is None:
164 print("No main program for interface '%s'" % interface_uri, file=sys.stderr)
165 sys.exit(1)
167 prog_name = os.path.basename(main)
168 alias_name = os.path.basename(args[0])
170 assert impl_path
172 # TODO: the feed should say where the man-pages are, but for now we'll accept
173 # a directory called man in some common locations...
174 for mandir in ['man', 'share/man', 'usr/man', 'usr/share/man']:
175 manpath = os.path.join(impl_path, mandir)
176 if os.path.isdir(manpath):
177 # Note: unlike "man -M", this also copes with LANG settings...
178 os.environ['MANPATH'] = manpath
179 os.execlp('man', 'man', prog_name)
180 sys.exit(1)
182 # No man directory given or found, so try searching for man files
184 manpages = []
185 for root, dirs, files in os.walk(impl_path):
186 for f in files:
187 if f.endswith('.gz'):
188 manpage_file = f[:-3]
189 else:
190 manpage_file = f
191 if manpage_file.endswith('.1') or \
192 manpage_file.endswith('.6') or \
193 manpage_file.endswith('.8'):
194 manpage_prog = manpage_file[:-2]
195 if manpage_prog == prog_name or manpage_prog == alias_name:
196 os.execlp('man', 'man', os.path.join(root, f))
197 sys.exit(1)
198 else:
199 manpages.append((root, f))
201 print("No matching manpage was found for '%s' (%s)" % (alias_name, interface_uri))
202 if manpages:
203 print("These non-matching man-pages were found, however:")
204 for root, file in manpages:
205 print(os.path.join(root, file))
206 sys.exit(1)
208 if not os.path.isdir(first_path):
209 print("(creating directory %s)" % first_path)
210 os.makedirs(first_path)
212 if len(args) == 1:
213 os.execlp('0launch', '0launch', '-gd', '--', interface_uri)
214 sys.exit(1)
216 try:
217 if not options.user_path:
218 if alias_prog == '0launch':
219 raise model.SafeException(_('Refusing to create an alias named "0launch" (to avoid an infinite loop)'))
221 interface = model.Interface(interface_uri)
222 if not reader.update_from_cache(interface):
223 print("Interface '%s' not currently in cache. Fetching..." % interface_uri, file=sys.stderr)
224 if os.spawnlp(os.P_WAIT, '0launch', '0launch', '-d', interface_uri):
225 raise model.SafeException("0launch failed")
226 if not reader.update_from_cache(interface):
227 raise model.SafeException("Interface still not in cache. Aborting.")
229 script = os.path.join(first_path, alias_prog)
230 if os.path.exists(script):
231 raise model.SafeException("File '%s' already exists. Delete it first." % script)
232 sys.exit(1)
233 except model.SafeException as ex:
234 print(ex, file=sys.stderr)
235 sys.exit(1)
237 wrapper = open(script, 'w')
238 alias.write_script(wrapper, interface_uri, main, command = command)
240 # Make new script executable
241 os.chmod(script, 0o111 | os.fstat(wrapper.fileno()).st_mode)
242 wrapper.close()
244 #print "Created script '%s'." % script
245 #print "To edit policy: 0alias %s" % alias_prog
246 if options.user_path:
247 pass # Assume user knows what they're doing
248 elif not in_path:
249 print('Warning: %s is not in $PATH. Add it with:\n%s' % (first_path, export('PATH', first_path + ':$PATH')), file=sys.stderr)
250 else:
251 shell = os.environ.get('SHELL', '?')
252 if not (shell.endswith('/zsh') or shell.endswith('/bash')):
253 print("(note: some shells require you to type 'rehash' now)")