blob: 3f068078548bea54038b8944fd67de5cdc4a15b6 [file] [log] [blame]
Greg Ward2689e3d1999-03-22 14:52:19 +00001"""distutils.util
2
Greg Wardaebf7062000-04-04 02:05:59 +00003Miscellaneous utility functions -- anything that doesn't fit into
4one of the other *util.py modules."""
Greg Ward2689e3d1999-03-22 14:52:19 +00005
6# created 1999/03/08, Greg Ward
7
Greg Ward3ce77fd2000-03-02 01:49:45 +00008__revision__ = "$Id$"
Greg Ward2689e3d1999-03-22 14:52:19 +00009
Greg Warda7540bd2000-03-23 04:39:16 +000010import sys, os, string, re, shutil
Greg Ward2689e3d1999-03-22 14:52:19 +000011from distutils.errors import *
Greg Ward7c1a6d42000-03-29 02:48:40 +000012from distutils.spawn import spawn
Greg Ward2689e3d1999-03-22 14:52:19 +000013
Greg Wardaa458bc2000-04-22 15:14:58 +000014
Greg Ward585df892000-03-01 14:40:15 +000015def get_platform ():
Greg Ward59399bb2000-09-15 01:16:14 +000016 """Return a string that identifies the current platform. This is used
17 mainly to distinguish platform-specific build directories and
18 platform-specific built distributions. Typically includes the OS name
19 and version and the architecture (as supplied by 'os.uname()'),
20 although the exact information included depends on the OS; eg. for IRIX
21 the architecture isn't particularly important (IRIX only runs on SGI
22 hardware), but for Linux the kernel version isn't particularly
23 important.
24
25 Examples of returned values:
26 linux-i586
27 linux-alpha (?)
28 solaris-2.6-sun4u
29 irix-5.3
30 irix64-6.2
31
32 For non-POSIX platforms, currently just returns 'sys.platform'.
Greg Wardb75c4852000-06-18 15:45:55 +000033 """
Greg Ward59399bb2000-09-15 01:16:14 +000034 if os.name != "posix":
35 # XXX what about the architecture? NT is Intel or Alpha,
36 # Mac OS is M68k or PPC, etc.
37 return sys.platform
38
39 # Try to distinguish various flavours of Unix
40
41 (osname, host, release, version, machine) = os.uname()
42 osname = string.lower(osname)
43 if osname[:5] == "linux":
44 # At least on Linux/Intel, 'machine' is the processor --
45 # i386, etc.
46 # XXX what about Alpha, SPARC, etc?
47 return "%s-%s" % (osname, machine)
48 elif osname[:5] == "sunos":
49 if release[0] >= "5": # SunOS 5 == Solaris 2
50 osname = "solaris"
51 release = "%d.%s" % (int(release[0]) - 3, release[2:])
52 # fall through to standard osname-release-machine representation
53 elif osname[:4] == "irix": # could be "irix64"!
54 return "%s-%s" % (osname, release)
55
56 return "%s-%s-%s" % (osname, release, machine)
57
58# get_platform ()
Greg Ward50919292000-03-07 03:27:08 +000059
60
Greg Wardd8dfb4c2000-05-31 02:32:10 +000061def convert_path (pathname):
Greg Ward50919292000-03-07 03:27:08 +000062 """Return 'pathname' as a name that will work on the native
63 filesystem, i.e. split it on '/' and put it back together again
64 using the current directory separator. Needed because filenames in
65 the setup script are always supplied in Unix style, and have to be
66 converted to the local convention before we can actually use them in
Greg Ward02a1a2b2000-04-15 22:15:07 +000067 the filesystem. Raises ValueError if 'pathname' is
Greg Ward50919292000-03-07 03:27:08 +000068 absolute (starts with '/') or contains local directory separators
69 (unless the local separator is '/', of course)."""
70
Greg Ward7ec05352000-09-22 01:05:43 +000071 if os.sep == '/':
72 return pathname
Greg Ward50919292000-03-07 03:27:08 +000073 if pathname[0] == '/':
Greg Ward02a1a2b2000-04-15 22:15:07 +000074 raise ValueError, "path '%s' cannot be absolute" % pathname
Greg Ward50919292000-03-07 03:27:08 +000075 if pathname[-1] == '/':
Greg Ward02a1a2b2000-04-15 22:15:07 +000076 raise ValueError, "path '%s' cannot end with '/'" % pathname
Greg Ward7ec05352000-09-22 01:05:43 +000077
78 paths = string.split(pathname, '/')
79 return apply(os.path.join, paths)
Greg Ward50919292000-03-07 03:27:08 +000080
Greg Wardd8dfb4c2000-05-31 02:32:10 +000081# convert_path ()
Greg Ward1b4ede52000-03-22 00:22:44 +000082
83
Greg Ward67f75d42000-04-27 01:53:46 +000084def change_root (new_root, pathname):
Greg Ward67f75d42000-04-27 01:53:46 +000085 """Return 'pathname' with 'new_root' prepended. If 'pathname' is
86 relative, this is equivalent to "os.path.join(new_root,pathname)".
87 Otherwise, it requires making 'pathname' relative and then joining the
Greg Ward4b46ef92000-05-31 02:14:32 +000088 two, which is tricky on DOS/Windows and Mac OS.
89 """
90 if os.name == 'posix':
91 if not os.path.isabs (pathname):
92 return os.path.join (new_root, pathname)
93 else:
94 return os.path.join (new_root, pathname[1:])
Greg Ward67f75d42000-04-27 01:53:46 +000095
96 elif os.name == 'nt':
Greg Ward67f75d42000-04-27 01:53:46 +000097 (drive, path) = os.path.splitdrive (pathname)
Greg Ward4b46ef92000-05-31 02:14:32 +000098 if path[0] == '\\':
99 path = path[1:]
100 return os.path.join (new_root, path)
Greg Ward67f75d42000-04-27 01:53:46 +0000101
102 elif os.name == 'mac':
Greg Wardf5855742000-09-21 01:23:35 +0000103 if not os.path.isabs(pathname):
104 return os.path.join(new_root, pathname)
105 else:
106 # Chop off volume name from start of path
107 elements = string.split(pathname, ":", 1)
108 pathname = ":" + elements[1]
109 return os.path.join(new_root, pathname)
Greg Ward67f75d42000-04-27 01:53:46 +0000110
111 else:
112 raise DistutilsPlatformError, \
113 "nothing known about platform '%s'" % os.name
114
115
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +0000116_environ_checked = 0
117def check_environ ():
Greg Ward1b4ede52000-03-22 00:22:44 +0000118 """Ensure that 'os.environ' has all the environment variables we
119 guarantee that users can use in config files, command-line
120 options, etc. Currently this includes:
121 HOME - user's home directory (Unix only)
Greg Ward612eb9f2000-07-27 02:13:20 +0000122 PLAT - description of the current platform, including hardware
Greg Ward1b4ede52000-03-22 00:22:44 +0000123 and OS (see 'get_platform()')
124 """
125
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +0000126 global _environ_checked
127 if _environ_checked:
128 return
129
Greg Ward1b4ede52000-03-22 00:22:44 +0000130 if os.name == 'posix' and not os.environ.has_key('HOME'):
131 import pwd
132 os.environ['HOME'] = pwd.getpwuid (os.getuid())[5]
133
134 if not os.environ.has_key('PLAT'):
135 os.environ['PLAT'] = get_platform ()
136
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +0000137 _environ_checked = 1
138
Greg Ward1b4ede52000-03-22 00:22:44 +0000139
140def subst_vars (str, local_vars):
141 """Perform shell/Perl-style variable substitution on 'string'.
Greg Ward612eb9f2000-07-27 02:13:20 +0000142 Every occurrence of '$' followed by a name, or a name enclosed in
Greg Ward1b4ede52000-03-22 00:22:44 +0000143 braces, is considered a variable. Every variable is substituted by
144 the value found in the 'local_vars' dictionary, or in 'os.environ'
145 if it's not in 'local_vars'. 'os.environ' is first checked/
146 augmented to guarantee that it contains certain values: see
147 '_check_environ()'. Raise ValueError for any variables not found in
148 either 'local_vars' or 'os.environ'."""
149
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +0000150 check_environ ()
Greg Ward1b4ede52000-03-22 00:22:44 +0000151 def _subst (match, local_vars=local_vars):
152 var_name = match.group(1)
153 if local_vars.has_key (var_name):
154 return str (local_vars[var_name])
155 else:
156 return os.environ[var_name]
157
158 return re.sub (r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, str)
159
160# subst_vars ()
Greg Ward7c1a6d42000-03-29 02:48:40 +0000161
162
Greg Warde9055132000-06-17 02:16:46 +0000163def grok_environment_error (exc, prefix="error: "):
164 """Generate a useful error message from an EnvironmentError (IOError or
165 OSError) exception object. Handles Python 1.5.1 and 1.5.2 styles, and
166 does what it can to deal with exception objects that don't have a
167 filename (which happens when the error is due to a two-file operation,
168 such as 'rename()' or 'link()'. Returns the error message as a string
169 prefixed with 'prefix'.
170 """
171 # check for Python 1.5.2-style {IO,OS}Error exception objects
172 if hasattr (exc, 'filename') and hasattr (exc, 'strerror'):
173 if exc.filename:
174 error = prefix + "%s: %s" % (exc.filename, exc.strerror)
175 else:
176 # two-argument functions in posix module don't
177 # include the filename in the exception object!
178 error = prefix + "%s" % exc.strerror
179 else:
180 error = prefix + str(exc[-1])
181
182 return error
Greg Ward6a2a3db2000-06-24 20:40:02 +0000183
184
185# Needed by 'split_quoted()'
Greg Ward2b042de2000-08-08 14:38:13 +0000186_wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
Greg Ward6a2a3db2000-06-24 20:40:02 +0000187_squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
188_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
189
190def split_quoted (s):
191 """Split a string up according to Unix shell-like rules for quotes and
192 backslashes. In short: words are delimited by spaces, as long as those
193 spaces are not escaped by a backslash, or inside a quoted string.
194 Single and double quotes are equivalent, and the quote characters can
195 be backslash-escaped. The backslash is stripped from any two-character
196 escape sequence, leaving only the escaped character. The quote
197 characters are stripped from any quoted string. Returns a list of
198 words.
199 """
200
201 # This is a nice algorithm for splitting up a single string, since it
202 # doesn't require character-by-character examination. It was a little
203 # bit of a brain-bender to get it working right, though...
204
205 s = string.strip(s)
206 words = []
207 pos = 0
208
209 while s:
210 m = _wordchars_re.match(s, pos)
211 end = m.end()
212 if end == len(s):
213 words.append(s[:end])
214 break
215
Greg Ward2b042de2000-08-08 14:38:13 +0000216 if s[end] in string.whitespace: # unescaped, unquoted whitespace: now
Greg Ward6a2a3db2000-06-24 20:40:02 +0000217 words.append(s[:end]) # we definitely have a word delimiter
218 s = string.lstrip(s[end:])
219 pos = 0
220
221 elif s[end] == '\\': # preserve whatever is being escaped;
222 # will become part of the current word
223 s = s[:end] + s[end+1:]
224 pos = end+1
225
226 else:
227 if s[end] == "'": # slurp singly-quoted string
228 m = _squote_re.match(s, end)
229 elif s[end] == '"': # slurp doubly-quoted string
230 m = _dquote_re.match(s, end)
231 else:
232 raise RuntimeError, \
233 "this can't happen (bad char '%c')" % s[end]
234
235 if m is None:
236 raise ValueError, \
237 "bad string (mismatched %s quotes?)" % s[end]
238
239 (beg, end) = m.span()
240 s = s[:beg] + s[beg+1:end-1] + s[end:]
241 pos = m.end() - 2
242
243 if pos >= len(s):
244 words.append(s)
245 break
246
247 return words
248
249# split_quoted ()
Greg Ward1c16ac32000-08-02 01:37:30 +0000250
251
252def execute (func, args, msg=None, verbose=0, dry_run=0):
253 """Perform some action that affects the outside world (eg. by writing
254 to the filesystem). Such actions are special because they are disabled
255 by the 'dry_run' flag, and announce themselves if 'verbose' is true.
256 This method takes care of all that bureaucracy for you; all you have to
257 do is supply the function to call and an argument tuple for it (to
258 embody the "external action" being performed), and an optional message
259 to print.
260 """
261 # Generate a message if we weren't passed one
262 if msg is None:
263 msg = "%s%s" % (func.__name__, `args`)
264 if msg[-2:] == ',)': # correct for singleton tuple
265 msg = msg[0:-2] + ')'
266
267 # Print it if verbosity level is high enough
268 if verbose:
269 print msg
270
271 # And do it, as long as we're not in dry-run mode
272 if not dry_run:
273 apply(func, args)
274
275# execute()