source: python/trunk/Lib/distutils/util.py@ 560

Last change on this file since 560 was 391, checked in by dmik, 11 years ago

python: Merge vendor 2.7.6 to trunk.

  • Property svn:eol-style set to native
File size: 18.4 KB
Line 
1"""distutils.util
2
3Miscellaneous utility functions -- anything that doesn't fit into
4one of the other *util.py modules.
5"""
6
7__revision__ = "$Id$"
8
9import sys, os, string, re
10from distutils.errors import DistutilsPlatformError
11from distutils.dep_util import newer
12from distutils.spawn import spawn
13from distutils import log
14from distutils.errors import DistutilsByteCompileError
15
16def get_platform ():
17 """Return a string that identifies the current platform. This is used
18 mainly to distinguish platform-specific build directories and
19 platform-specific built distributions. Typically includes the OS name
20 and version and the architecture (as supplied by 'os.uname()'),
21 although the exact information included depends on the OS; eg. for IRIX
22 the architecture isn't particularly important (IRIX only runs on SGI
23 hardware), but for Linux the kernel version isn't particularly
24 important.
25
26 Examples of returned values:
27 linux-i586
28 linux-alpha (?)
29 solaris-2.6-sun4u
30 irix-5.3
31 irix64-6.2
32
33 Windows will return one of:
34 win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
35 win-ia64 (64bit Windows on Itanium)
36 win32 (all others - specifically, sys.platform is returned)
37
38 For other non-POSIX platforms, currently just returns 'sys.platform'.
39 """
40 if os.name == 'nt':
41 # sniff sys.version for architecture.
42 prefix = " bit ("
43 i = string.find(sys.version, prefix)
44 if i == -1:
45 return sys.platform
46 j = string.find(sys.version, ")", i)
47 look = sys.version[i+len(prefix):j].lower()
48 if look=='amd64':
49 return 'win-amd64'
50 if look=='itanium':
51 return 'win-ia64'
52 return sys.platform
53
54 # Set for cross builds explicitly
55 if "_PYTHON_HOST_PLATFORM" in os.environ:
56 return os.environ["_PYTHON_HOST_PLATFORM"]
57
58 if os.name != "posix" or not hasattr(os, 'uname'):
59 # XXX what about the architecture? NT is Intel or Alpha,
60 # Mac OS is M68k or PPC, etc.
61 return sys.platform
62
63 # Try to distinguish various flavours of Unix
64
65 (osname, host, release, version, machine) = os.uname()
66
67 # Convert the OS name to lowercase, remove '/' characters
68 # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
69 osname = string.lower(osname)
70 osname = string.replace(osname, '/', '')
71 machine = string.replace(machine, ' ', '_')
72 machine = string.replace(machine, '/', '-')
73
74 if osname[:5] == "linux":
75 # At least on Linux/Intel, 'machine' is the processor --
76 # i386, etc.
77 # XXX what about Alpha, SPARC, etc?
78 return "%s-%s" % (osname, machine)
79 elif osname[:5] == "sunos":
80 if release[0] >= "5": # SunOS 5 == Solaris 2
81 osname = "solaris"
82 release = "%d.%s" % (int(release[0]) - 3, release[2:])
83 # We can't use "platform.architecture()[0]" because a
84 # bootstrap problem. We use a dict to get an error
85 # if some suspicious happens.
86 bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}
87 machine += ".%s" % bitness[sys.maxint]
88 # fall through to standard osname-release-machine representation
89 elif osname[:4] == "irix": # could be "irix64"!
90 return "%s-%s" % (osname, release)
91 elif osname[:3] == "aix":
92 return "%s-%s.%s" % (osname, version, release)
93 elif osname[:6] == "cygwin":
94 osname = "cygwin"
95 rel_re = re.compile (r'[\d.]+')
96 m = rel_re.match(release)
97 if m:
98 release = m.group()
99 elif osname[:6] == "darwin":
100 import _osx_support, distutils.sysconfig
101 osname, release, machine = _osx_support.get_platform_osx(
102 distutils.sysconfig.get_config_vars(),
103 osname, release, machine)
104
105 return "%s-%s-%s" % (osname, release, machine)
106
107# get_platform ()
108
109
110def convert_path (pathname):
111 """Return 'pathname' as a name that will work on the native filesystem,
112 i.e. split it on '/' and put it back together again using the current
113 directory separator. Needed because filenames in the setup script are
114 always supplied in Unix style, and have to be converted to the local
115 convention before we can actually use them in the filesystem. Raises
116 ValueError on non-Unix-ish systems if 'pathname' either starts or
117 ends with a slash.
118 """
119 if os.sep == '/':
120 return pathname
121 if not pathname:
122 return pathname
123 if pathname[0] == '/':
124 raise ValueError, "path '%s' cannot be absolute" % pathname
125 if pathname[-1] == '/':
126 raise ValueError, "path '%s' cannot end with '/'" % pathname
127
128 paths = string.split(pathname, '/')
129 while '.' in paths:
130 paths.remove('.')
131 if not paths:
132 return os.curdir
133 return os.path.join(*paths)
134
135# convert_path ()
136
137
138def change_root (new_root, pathname):
139 """Return 'pathname' with 'new_root' prepended. If 'pathname' is
140 relative, this is equivalent to "os.path.join(new_root,pathname)".
141 Otherwise, it requires making 'pathname' relative and then joining the
142 two, which is tricky on DOS/Windows and Mac OS.
143 """
144 if new_root is None or new_root == '':
145 return pathname
146
147 if os.name == 'posix':
148 if not os.path.isabs(pathname):
149 return os.path.join(new_root, pathname)
150 else:
151 return os.path.join(new_root, pathname[1:])
152
153 elif os.name == 'nt':
154 (drive, path) = os.path.splitdrive(pathname)
155 if path[0] == '\\':
156 path = path[1:]
157 return os.path.join(new_root, path)
158
159 elif os.name == 'os2':
160 (drive, path) = os.path.splitdrive(pathname)
161 if path[0] == os.sep:
162 path = path[1:]
163 return os.path.join(new_root, path)
164
165 else:
166 raise DistutilsPlatformError, \
167 "nothing known about platform '%s'" % os.name
168
169
170_environ_checked = 0
171def check_environ ():
172 """Ensure that 'os.environ' has all the environment variables we
173 guarantee that users can use in config files, command-line options,
174 etc. Currently this includes:
175 HOME - user's home directory (Unix only)
176 PLAT - description of the current platform, including hardware
177 and OS (see 'get_platform()')
178 """
179 global _environ_checked
180 if _environ_checked:
181 return
182
183 if os.name == 'posix' and 'HOME' not in os.environ:
184 import pwd
185 os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
186
187 if 'PLAT' not in os.environ:
188 os.environ['PLAT'] = get_platform()
189
190 _environ_checked = 1
191
192
193def subst_vars (s, local_vars):
194 """Perform shell/Perl-style variable substitution on 'string'. Every
195 occurrence of '$' followed by a name is considered a variable, and
196 variable is substituted by the value found in the 'local_vars'
197 dictionary, or in 'os.environ' if it's not in 'local_vars'.
198 'os.environ' is first checked/augmented to guarantee that it contains
199 certain values: see 'check_environ()'. Raise ValueError for any
200 variables not found in either 'local_vars' or 'os.environ'.
201 """
202 check_environ()
203 def _subst (match, local_vars=local_vars):
204 var_name = match.group(1)
205 if var_name in local_vars:
206 return str(local_vars[var_name])
207 else:
208 return os.environ[var_name]
209
210 try:
211 return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
212 except KeyError, var:
213 raise ValueError, "invalid variable '$%s'" % var
214
215# subst_vars ()
216
217
218def grok_environment_error (exc, prefix="error: "):
219 """Generate a useful error message from an EnvironmentError (IOError or
220 OSError) exception object. Handles Python 1.5.1 and 1.5.2 styles, and
221 does what it can to deal with exception objects that don't have a
222 filename (which happens when the error is due to a two-file operation,
223 such as 'rename()' or 'link()'. Returns the error message as a string
224 prefixed with 'prefix'.
225 """
226 # check for Python 1.5.2-style {IO,OS}Error exception objects
227 if hasattr(exc, 'filename') and hasattr(exc, 'strerror'):
228 if exc.filename:
229 error = prefix + "%s: %s" % (exc.filename, exc.strerror)
230 else:
231 # two-argument functions in posix module don't
232 # include the filename in the exception object!
233 error = prefix + "%s" % exc.strerror
234 else:
235 error = prefix + str(exc[-1])
236
237 return error
238
239
240# Needed by 'split_quoted()'
241_wordchars_re = _squote_re = _dquote_re = None
242def _init_regex():
243 global _wordchars_re, _squote_re, _dquote_re
244 _wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
245 _squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
246 _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
247
248def split_quoted (s):
249 """Split a string up according to Unix shell-like rules for quotes and
250 backslashes. In short: words are delimited by spaces, as long as those
251 spaces are not escaped by a backslash, or inside a quoted string.
252 Single and double quotes are equivalent, and the quote characters can
253 be backslash-escaped. The backslash is stripped from any two-character
254 escape sequence, leaving only the escaped character. The quote
255 characters are stripped from any quoted string. Returns a list of
256 words.
257 """
258
259 # This is a nice algorithm for splitting up a single string, since it
260 # doesn't require character-by-character examination. It was a little
261 # bit of a brain-bender to get it working right, though...
262 if _wordchars_re is None: _init_regex()
263
264 s = string.strip(s)
265 words = []
266 pos = 0
267
268 while s:
269 m = _wordchars_re.match(s, pos)
270 end = m.end()
271 if end == len(s):
272 words.append(s[:end])
273 break
274
275 if s[end] in string.whitespace: # unescaped, unquoted whitespace: now
276 words.append(s[:end]) # we definitely have a word delimiter
277 s = string.lstrip(s[end:])
278 pos = 0
279
280 elif s[end] == '\\': # preserve whatever is being escaped;
281 # will become part of the current word
282 s = s[:end] + s[end+1:]
283 pos = end+1
284
285 else:
286 if s[end] == "'": # slurp singly-quoted string
287 m = _squote_re.match(s, end)
288 elif s[end] == '"': # slurp doubly-quoted string
289 m = _dquote_re.match(s, end)
290 else:
291 raise RuntimeError, \
292 "this can't happen (bad char '%c')" % s[end]
293
294 if m is None:
295 raise ValueError, \
296 "bad string (mismatched %s quotes?)" % s[end]
297
298 (beg, end) = m.span()
299 s = s[:beg] + s[beg+1:end-1] + s[end:]
300 pos = m.end() - 2
301
302 if pos >= len(s):
303 words.append(s)
304 break
305
306 return words
307
308# split_quoted ()
309
310
311def execute (func, args, msg=None, verbose=0, dry_run=0):
312 """Perform some action that affects the outside world (eg. by
313 writing to the filesystem). Such actions are special because they
314 are disabled by the 'dry_run' flag. This method takes care of all
315 that bureaucracy for you; all you have to do is supply the
316 function to call and an argument tuple for it (to embody the
317 "external action" being performed), and an optional message to
318 print.
319 """
320 if msg is None:
321 msg = "%s%r" % (func.__name__, args)
322 if msg[-2:] == ',)': # correct for singleton tuple
323 msg = msg[0:-2] + ')'
324
325 log.info(msg)
326 if not dry_run:
327 func(*args)
328
329
330def strtobool (val):
331 """Convert a string representation of truth to true (1) or false (0).
332
333 True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
334 are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
335 'val' is anything else.
336 """
337 val = string.lower(val)
338 if val in ('y', 'yes', 't', 'true', 'on', '1'):
339 return 1
340 elif val in ('n', 'no', 'f', 'false', 'off', '0'):
341 return 0
342 else:
343 raise ValueError, "invalid truth value %r" % (val,)
344
345
346def byte_compile (py_files,
347 optimize=0, force=0,
348 prefix=None, base_dir=None,
349 verbose=1, dry_run=0,
350 direct=None):
351 """Byte-compile a collection of Python source files to either .pyc
352 or .pyo files in the same directory. 'py_files' is a list of files
353 to compile; any files that don't end in ".py" are silently skipped.
354 'optimize' must be one of the following:
355 0 - don't optimize (generate .pyc)
356 1 - normal optimization (like "python -O")
357 2 - extra optimization (like "python -OO")
358 If 'force' is true, all files are recompiled regardless of
359 timestamps.
360
361 The source filename encoded in each bytecode file defaults to the
362 filenames listed in 'py_files'; you can modify these with 'prefix' and
363 'basedir'. 'prefix' is a string that will be stripped off of each
364 source filename, and 'base_dir' is a directory name that will be
365 prepended (after 'prefix' is stripped). You can supply either or both
366 (or neither) of 'prefix' and 'base_dir', as you wish.
367
368 If 'dry_run' is true, doesn't actually do anything that would
369 affect the filesystem.
370
371 Byte-compilation is either done directly in this interpreter process
372 with the standard py_compile module, or indirectly by writing a
373 temporary script and executing it. Normally, you should let
374 'byte_compile()' figure out to use direct compilation or not (see
375 the source for details). The 'direct' flag is used by the script
376 generated in indirect mode; unless you know what you're doing, leave
377 it set to None.
378 """
379 # nothing is done if sys.dont_write_bytecode is True
380 if sys.dont_write_bytecode:
381 raise DistutilsByteCompileError('byte-compiling is disabled.')
382
383 # First, if the caller didn't force us into direct or indirect mode,
384 # figure out which mode we should be in. We take a conservative
385 # approach: choose direct mode *only* if the current interpreter is
386 # in debug mode and optimize is 0. If we're not in debug mode (-O
387 # or -OO), we don't know which level of optimization this
388 # interpreter is running with, so we can't do direct
389 # byte-compilation and be certain that it's the right thing. Thus,
390 # always compile indirectly if the current interpreter is in either
391 # optimize mode, or if either optimization level was requested by
392 # the caller.
393 if direct is None:
394 direct = (__debug__ and optimize == 0)
395
396 # "Indirect" byte-compilation: write a temporary script and then
397 # run it with the appropriate flags.
398 if not direct:
399 try:
400 from tempfile import mkstemp
401 (script_fd, script_name) = mkstemp(".py")
402 except ImportError:
403 from tempfile import mktemp
404 (script_fd, script_name) = None, mktemp(".py")
405 log.info("writing byte-compilation script '%s'", script_name)
406 if not dry_run:
407 if script_fd is not None:
408 script = os.fdopen(script_fd, "w")
409 else:
410 script = open(script_name, "w")
411
412 script.write("""\
413from distutils.util import byte_compile
414files = [
415""")
416
417 # XXX would be nice to write absolute filenames, just for
418 # safety's sake (script should be more robust in the face of
419 # chdir'ing before running it). But this requires abspath'ing
420 # 'prefix' as well, and that breaks the hack in build_lib's
421 # 'byte_compile()' method that carefully tacks on a trailing
422 # slash (os.sep really) to make sure the prefix here is "just
423 # right". This whole prefix business is rather delicate -- the
424 # problem is that it's really a directory, but I'm treating it
425 # as a dumb string, so trailing slashes and so forth matter.
426
427 #py_files = map(os.path.abspath, py_files)
428 #if prefix:
429 # prefix = os.path.abspath(prefix)
430
431 script.write(string.join(map(repr, py_files), ",\n") + "]\n")
432 script.write("""
433byte_compile(files, optimize=%r, force=%r,
434 prefix=%r, base_dir=%r,
435 verbose=%r, dry_run=0,
436 direct=1)
437""" % (optimize, force, prefix, base_dir, verbose))
438
439 script.close()
440
441 cmd = [sys.executable, script_name]
442 if optimize == 1:
443 cmd.insert(1, "-O")
444 elif optimize == 2:
445 cmd.insert(1, "-OO")
446 spawn(cmd, dry_run=dry_run)
447 execute(os.remove, (script_name,), "removing %s" % script_name,
448 dry_run=dry_run)
449
450 # "Direct" byte-compilation: use the py_compile module to compile
451 # right here, right now. Note that the script generated in indirect
452 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
453 # cross-process recursion. Hey, it works!
454 else:
455 from py_compile import compile
456
457 for file in py_files:
458 if file[-3:] != ".py":
459 # This lets us be lazy and not filter filenames in
460 # the "install_lib" command.
461 continue
462
463 # Terminology from the py_compile module:
464 # cfile - byte-compiled file
465 # dfile - purported source filename (same as 'file' by default)
466 cfile = file + (__debug__ and "c" or "o")
467 dfile = file
468 if prefix:
469 if file[:len(prefix)] != prefix:
470 raise ValueError, \
471 ("invalid prefix: filename %r doesn't start with %r"
472 % (file, prefix))
473 dfile = dfile[len(prefix):]
474 if base_dir:
475 dfile = os.path.join(base_dir, dfile)
476
477 cfile_base = os.path.basename(cfile)
478 if direct:
479 if force or newer(file, cfile):
480 log.info("byte-compiling %s to %s", file, cfile_base)
481 if not dry_run:
482 compile(file, cfile, dfile)
483 else:
484 log.debug("skipping byte-compilation of %s to %s",
485 file, cfile_base)
486
487# byte_compile ()
488
489def rfc822_escape (header):
490 """Return a version of the string escaped for inclusion in an
491 RFC-822 header, by ensuring there are 8 spaces space after each newline.
492 """
493 lines = string.split(header, '\n')
494 header = string.join(lines, '\n' + 8*' ')
495 return header
Note: See TracBrowser for help on using the repository browser.