blob: 2487f6dab6363443e00f4ede2e375619106995fb [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 ():
16 """Return a string (suitable for tacking onto directory names) that
Greg Wardb75c4852000-06-18 15:45:55 +000017 identifies the current platform. Currently, this is just
18 'sys.platform'.
19 """
20 return sys.platform
Greg Ward50919292000-03-07 03:27:08 +000021
22
Greg Wardd8dfb4c2000-05-31 02:32:10 +000023def convert_path (pathname):
Greg Ward50919292000-03-07 03:27:08 +000024 """Return 'pathname' as a name that will work on the native
25 filesystem, i.e. split it on '/' and put it back together again
26 using the current directory separator. Needed because filenames in
27 the setup script are always supplied in Unix style, and have to be
28 converted to the local convention before we can actually use them in
Greg Ward02a1a2b2000-04-15 22:15:07 +000029 the filesystem. Raises ValueError if 'pathname' is
Greg Ward50919292000-03-07 03:27:08 +000030 absolute (starts with '/') or contains local directory separators
31 (unless the local separator is '/', of course)."""
32
33 if pathname[0] == '/':
Greg Ward02a1a2b2000-04-15 22:15:07 +000034 raise ValueError, "path '%s' cannot be absolute" % pathname
Greg Ward50919292000-03-07 03:27:08 +000035 if pathname[-1] == '/':
Greg Ward02a1a2b2000-04-15 22:15:07 +000036 raise ValueError, "path '%s' cannot end with '/'" % pathname
Greg Ward464023f2000-04-25 01:33:11 +000037 if os.sep != '/':
Greg Wardd8dfb4c2000-05-31 02:32:10 +000038 paths = string.split (pathname, '/')
39 return apply (os.path.join, paths)
Greg Ward50919292000-03-07 03:27:08 +000040 else:
41 return pathname
42
Greg Wardd8dfb4c2000-05-31 02:32:10 +000043# convert_path ()
Greg Ward1b4ede52000-03-22 00:22:44 +000044
45
Greg Ward67f75d42000-04-27 01:53:46 +000046def change_root (new_root, pathname):
Greg Ward67f75d42000-04-27 01:53:46 +000047 """Return 'pathname' with 'new_root' prepended. If 'pathname' is
48 relative, this is equivalent to "os.path.join(new_root,pathname)".
49 Otherwise, it requires making 'pathname' relative and then joining the
Greg Ward4b46ef92000-05-31 02:14:32 +000050 two, which is tricky on DOS/Windows and Mac OS.
51 """
52 if os.name == 'posix':
53 if not os.path.isabs (pathname):
54 return os.path.join (new_root, pathname)
55 else:
56 return os.path.join (new_root, pathname[1:])
Greg Ward67f75d42000-04-27 01:53:46 +000057
58 elif os.name == 'nt':
Greg Ward67f75d42000-04-27 01:53:46 +000059 (drive, path) = os.path.splitdrive (pathname)
Greg Ward4b46ef92000-05-31 02:14:32 +000060 if path[0] == '\\':
61 path = path[1:]
62 return os.path.join (new_root, path)
Greg Ward67f75d42000-04-27 01:53:46 +000063
64 elif os.name == 'mac':
65 raise RuntimeError, "no clue how to do this on Mac OS"
66
67 else:
68 raise DistutilsPlatformError, \
69 "nothing known about platform '%s'" % os.name
70
71
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +000072_environ_checked = 0
73def check_environ ():
Greg Ward1b4ede52000-03-22 00:22:44 +000074 """Ensure that 'os.environ' has all the environment variables we
75 guarantee that users can use in config files, command-line
76 options, etc. Currently this includes:
77 HOME - user's home directory (Unix only)
Greg Ward612eb9f2000-07-27 02:13:20 +000078 PLAT - description of the current platform, including hardware
Greg Ward1b4ede52000-03-22 00:22:44 +000079 and OS (see 'get_platform()')
80 """
81
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +000082 global _environ_checked
83 if _environ_checked:
84 return
85
Greg Ward1b4ede52000-03-22 00:22:44 +000086 if os.name == 'posix' and not os.environ.has_key('HOME'):
87 import pwd
88 os.environ['HOME'] = pwd.getpwuid (os.getuid())[5]
89
90 if not os.environ.has_key('PLAT'):
91 os.environ['PLAT'] = get_platform ()
92
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +000093 _environ_checked = 1
94
Greg Ward1b4ede52000-03-22 00:22:44 +000095
96def subst_vars (str, local_vars):
97 """Perform shell/Perl-style variable substitution on 'string'.
Greg Ward612eb9f2000-07-27 02:13:20 +000098 Every occurrence of '$' followed by a name, or a name enclosed in
Greg Ward1b4ede52000-03-22 00:22:44 +000099 braces, is considered a variable. Every variable is substituted by
100 the value found in the 'local_vars' dictionary, or in 'os.environ'
101 if it's not in 'local_vars'. 'os.environ' is first checked/
102 augmented to guarantee that it contains certain values: see
103 '_check_environ()'. Raise ValueError for any variables not found in
104 either 'local_vars' or 'os.environ'."""
105
Gregory P. Smithe7e35ac2000-05-12 00:40:00 +0000106 check_environ ()
Greg Ward1b4ede52000-03-22 00:22:44 +0000107 def _subst (match, local_vars=local_vars):
108 var_name = match.group(1)
109 if local_vars.has_key (var_name):
110 return str (local_vars[var_name])
111 else:
112 return os.environ[var_name]
113
114 return re.sub (r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, str)
115
116# subst_vars ()
Greg Ward7c1a6d42000-03-29 02:48:40 +0000117
118
Greg Warde9055132000-06-17 02:16:46 +0000119def grok_environment_error (exc, prefix="error: "):
120 """Generate a useful error message from an EnvironmentError (IOError or
121 OSError) exception object. Handles Python 1.5.1 and 1.5.2 styles, and
122 does what it can to deal with exception objects that don't have a
123 filename (which happens when the error is due to a two-file operation,
124 such as 'rename()' or 'link()'. Returns the error message as a string
125 prefixed with 'prefix'.
126 """
127 # check for Python 1.5.2-style {IO,OS}Error exception objects
128 if hasattr (exc, 'filename') and hasattr (exc, 'strerror'):
129 if exc.filename:
130 error = prefix + "%s: %s" % (exc.filename, exc.strerror)
131 else:
132 # two-argument functions in posix module don't
133 # include the filename in the exception object!
134 error = prefix + "%s" % exc.strerror
135 else:
136 error = prefix + str(exc[-1])
137
138 return error
Greg Ward6a2a3db2000-06-24 20:40:02 +0000139
140
141# Needed by 'split_quoted()'
Greg Ward2b042de2000-08-08 14:38:13 +0000142_wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
Greg Ward6a2a3db2000-06-24 20:40:02 +0000143_squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
144_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
145
146def split_quoted (s):
147 """Split a string up according to Unix shell-like rules for quotes and
148 backslashes. In short: words are delimited by spaces, as long as those
149 spaces are not escaped by a backslash, or inside a quoted string.
150 Single and double quotes are equivalent, and the quote characters can
151 be backslash-escaped. The backslash is stripped from any two-character
152 escape sequence, leaving only the escaped character. The quote
153 characters are stripped from any quoted string. Returns a list of
154 words.
155 """
156
157 # This is a nice algorithm for splitting up a single string, since it
158 # doesn't require character-by-character examination. It was a little
159 # bit of a brain-bender to get it working right, though...
160
161 s = string.strip(s)
162 words = []
163 pos = 0
164
165 while s:
166 m = _wordchars_re.match(s, pos)
167 end = m.end()
168 if end == len(s):
169 words.append(s[:end])
170 break
171
Greg Ward2b042de2000-08-08 14:38:13 +0000172 if s[end] in string.whitespace: # unescaped, unquoted whitespace: now
Greg Ward6a2a3db2000-06-24 20:40:02 +0000173 words.append(s[:end]) # we definitely have a word delimiter
174 s = string.lstrip(s[end:])
175 pos = 0
176
177 elif s[end] == '\\': # preserve whatever is being escaped;
178 # will become part of the current word
179 s = s[:end] + s[end+1:]
180 pos = end+1
181
182 else:
183 if s[end] == "'": # slurp singly-quoted string
184 m = _squote_re.match(s, end)
185 elif s[end] == '"': # slurp doubly-quoted string
186 m = _dquote_re.match(s, end)
187 else:
188 raise RuntimeError, \
189 "this can't happen (bad char '%c')" % s[end]
190
191 if m is None:
192 raise ValueError, \
193 "bad string (mismatched %s quotes?)" % s[end]
194
195 (beg, end) = m.span()
196 s = s[:beg] + s[beg+1:end-1] + s[end:]
197 pos = m.end() - 2
198
199 if pos >= len(s):
200 words.append(s)
201 break
202
203 return words
204
205# split_quoted ()
Greg Ward1c16ac32000-08-02 01:37:30 +0000206
207
208def execute (func, args, msg=None, verbose=0, dry_run=0):
209 """Perform some action that affects the outside world (eg. by writing
210 to the filesystem). Such actions are special because they are disabled
211 by the 'dry_run' flag, and announce themselves if 'verbose' is true.
212 This method takes care of all that bureaucracy for you; all you have to
213 do is supply the function to call and an argument tuple for it (to
214 embody the "external action" being performed), and an optional message
215 to print.
216 """
217 # Generate a message if we weren't passed one
218 if msg is None:
219 msg = "%s%s" % (func.__name__, `args`)
220 if msg[-2:] == ',)': # correct for singleton tuple
221 msg = msg[0:-2] + ')'
222
223 # Print it if verbosity level is high enough
224 if verbose:
225 print msg
226
227 # And do it, as long as we're not in dry-run mode
228 if not dry_run:
229 apply(func, args)
230
231# execute()