[Jython-checkins] jython (merge default -> default): Merged trunk
jim.baker
jython-checkins at python.org
Thu Mar 15 05:23:13 CET 2012
http://hg.python.org/jython/rev/705ac19d2fb0
changeset: 6389:705ac19d2fb0
parent: 6388:3572914af35c
parent: 6385:2b0f406ce73a
user: Jim Baker <jbaker at zyasoft.com>
date: Wed Mar 14 21:23:03 2012 -0700
summary:
Merged trunk
files:
Lib/gzip.py | 208 +++++----
Lib/subprocess.py | 751 ++++++++++++++++++++++-----------
2 files changed, 605 insertions(+), 354 deletions(-)
diff --git a/Lib/gzip.py b/Lib/gzip.py
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -5,8 +5,9 @@
# based on Andrew Kuchling's minigzip.py distributed with the zlib module
-import struct, sys, time
+import struct, sys, time, os
import zlib
+import io
import __builtin__
__all__ = ["GzipFile","open"]
@@ -15,29 +16,13 @@
READ, WRITE = 1, 2
-def U32(i):
- """Return i as an unsigned integer, assuming it fits in 32 bits.
-
- If it's >= 2GB when viewed as a 32-bit unsigned int, return a long.
- """
- if i < 0:
- i += 1L << 32
- return i
-
-def LOWU32(i):
- """Return the low-order 32 bits of an int, as a non-negative int."""
- return i & 0xFFFFFFFFL
-
-def write32(output, value):
- output.write(struct.pack("<l", value))
-
def write32u(output, value):
# The L format writes the bit pattern correctly whether signed
# or unsigned.
output.write(struct.pack("<L", value))
def read32(input):
- return struct.unpack("<l", input.read(4))[0]
+ return struct.unpack("<I", input.read(4))[0]
def open(filename, mode="rb", compresslevel=9):
"""Shorthand for GzipFile(filename, mode, compresslevel).
@@ -48,7 +33,7 @@
"""
return GzipFile(filename, mode, compresslevel)
-class GzipFile:
+class GzipFile(io.BufferedIOBase):
"""The GzipFile class simulates most of the methods of a file object with
the exception of the readinto() and truncate() methods.
@@ -62,7 +47,7 @@
max_read_chunk = 256 * 1024 # 256kb
def __init__(self, filename=None, mode=None,
- compresslevel=9, fileobj=None):
+ compresslevel=9, fileobj=None, mtime=None):
"""Constructor for the GzipFile class.
At least one of fileobj and filename must be given a
@@ -89,6 +74,15 @@
level of compression; 1 is fastest and produces the least compression,
and 9 is slowest and produces the most compression. The default is 9.
+ The mtime argument is an optional numeric timestamp to be written
+ to the stream when compressing. All gzip compressed streams
+ are required to contain a timestamp. If omitted or None, the
+ current time is used. This module ignores the timestamp when
+ decompressing; however, some programs, such as gunzip, make use
+ of it. The format of the timestamp is the same as that of the
+ return value of time.time() and of the st_mtime member of the
+ object returned by os.stat().
+
"""
# guarantee the file is opened in binary mode on platforms
@@ -108,9 +102,13 @@
self.mode = READ
# Set flag indicating start of a new member
self._new_member = True
+ # Buffer data read from gzip file. extrastart is offset in
+ # stream where buffer starts. extrasize is number of
+ # bytes remaining in buffer from current stream position.
self.extrabuf = ""
self.extrasize = 0
- self.filename = filename
+ self.extrastart = 0
+ self.name = filename
# Starts small, scales exponentially
self.min_readsize = 100
@@ -127,19 +125,33 @@
self.fileobj = fileobj
self.offset = 0
+ self.mtime = mtime
if self.mode == WRITE:
self._write_gzip_header()
+ @property
+ def filename(self):
+ import warnings
+ warnings.warn("use the name attribute", DeprecationWarning, 2)
+ if self.mode == WRITE and self.name[-3:] != ".gz":
+ return self.name + ".gz"
+ return self.name
+
def __repr__(self):
s = repr(self.fileobj)
return '<gzip ' + s[1:-1] + ' ' + hex(id(self)) + '>'
+ def _check_closed(self):
+ """Raises a ValueError if the underlying file object has been closed.
+
+ """
+ if self.closed:
+ raise ValueError('I/O operation on closed file.')
+
def _init_write(self, filename):
- if filename[-3:] != '.gz':
- filename = filename + '.gz'
- self.filename = filename
- self.crc = zlib.crc32("")
+ self.name = filename
+ self.crc = zlib.crc32("") & 0xffffffffL
self.size = 0
self.writebuf = []
self.bufsize = 0
@@ -147,19 +159,24 @@
def _write_gzip_header(self):
self.fileobj.write('\037\213') # magic header
self.fileobj.write('\010') # compression method
- fname = self.filename[:-3]
+ fname = os.path.basename(self.name)
+ if fname.endswith(".gz"):
+ fname = fname[:-3]
flags = 0
if fname:
flags = FNAME
self.fileobj.write(chr(flags))
- write32u(self.fileobj, long(time.time()))
+ mtime = self.mtime
+ if mtime is None:
+ mtime = time.time()
+ write32u(self.fileobj, long(mtime))
self.fileobj.write('\002')
self.fileobj.write('\377')
if fname:
self.fileobj.write(fname + '\000')
def _init_read(self):
- self.crc = zlib.crc32("")
+ self.crc = zlib.crc32("") & 0xffffffffL
self.size = 0
def _read_gzip_header(self):
@@ -170,10 +187,10 @@
if method != 8:
raise IOError, 'Unknown compression method'
flag = ord( self.fileobj.read(1) )
- # modtime = self.fileobj.read(4)
+ self.mtime = read32(self.fileobj)
# extraflag = self.fileobj.read(1)
# os = self.fileobj.read(1)
- self.fileobj.read(6)
+ self.fileobj.read(2)
if flag & FEXTRA:
# Read & discard the extra field, if present
@@ -195,21 +212,29 @@
if flag & FHCRC:
self.fileobj.read(2) # Read & discard the 16-bit header CRC
-
def write(self,data):
+ self._check_closed()
if self.mode != WRITE:
import errno
raise IOError(errno.EBADF, "write() on read-only GzipFile object")
if self.fileobj is None:
raise ValueError, "write() on closed GzipFile object"
+
+ # Convert data type if called by io.BufferedWriter.
+ if isinstance(data, memoryview):
+ data = data.tobytes()
+
if len(data) > 0:
self.size = self.size + len(data)
- self.crc = zlib.crc32(data, self.crc)
+ self.crc = zlib.crc32(data, self.crc) & 0xffffffffL
self.fileobj.write( self.compress.compress(data) )
self.offset += len(data)
+ return len(data)
+
def read(self, size=-1):
+ self._check_closed()
if self.mode != READ:
import errno
raise IOError(errno.EBADF, "read() on write-only GzipFile object")
@@ -234,15 +259,14 @@
if size > self.extrasize:
size = self.extrasize
- chunk = self.extrabuf[:size]
- self.extrabuf = self.extrabuf[size:]
+ offset = self.offset - self.extrastart
+ chunk = self.extrabuf[offset: offset + size]
self.extrasize = self.extrasize - size
self.offset += size
return chunk
def _unread(self, buf):
- self.extrabuf = buf + self.extrabuf
self.extrasize = len(buf) + self.extrasize
self.offset -= len(buf)
@@ -297,9 +321,11 @@
self._new_member = True
def _add_read_data(self, data):
- self.crc = zlib.crc32(data, self.crc)
- self.extrabuf = self.extrabuf + data
+ self.crc = zlib.crc32(data, self.crc) & 0xffffffffL
+ offset = self.offset - self.extrastart
+ self.extrabuf = self.extrabuf[offset:] + data
self.extrasize = self.extrasize + len(data)
+ self.extrastart = self.offset
self.size = self.size + len(data)
def _read_eof(self):
@@ -310,24 +336,34 @@
# stored is the true file size mod 2**32.
self.fileobj.seek(-8, 1)
crc32 = read32(self.fileobj)
- isize = U32(read32(self.fileobj)) # may exceed 2GB
- if U32(crc32) != U32(self.crc):
- raise IOError, "CRC check failed"
- elif isize != LOWU32(self.size):
+ isize = read32(self.fileobj) # may exceed 2GB
+ if crc32 != self.crc:
+ raise IOError("CRC check failed %s != %s" % (hex(crc32),
+ hex(self.crc)))
+ elif isize != (self.size & 0xffffffffL):
raise IOError, "Incorrect length of data produced"
+ # Gzip files can be padded with zeroes and still have archives.
+ # Consume all zero bytes and set the file position to the first
+ # non-zero byte. See http://www.gzip.org/#faq8
+ c = "\x00"
+ while c == "\x00":
+ c = self.fileobj.read(1)
+ if c:
+ self.fileobj.seek(-1, 1)
+
+ @property
+ def closed(self):
+ return self.fileobj is None
+
def close(self):
+ if self.fileobj is None:
+ return
if self.mode == WRITE:
self.fileobj.write(self.compress.flush())
- # The native zlib crc is an unsigned 32-bit integer, but
- # the Python wrapper implicitly casts that to a signed C
- # long. So, on a 32-bit box self.crc may "look negative",
- # while the same crc on a 64-bit box may "look positive".
- # To avoid irksome warnings from the `struct` module, force
- # it to look positive on all boxes.
- write32u(self.fileobj, LOWU32(self.crc))
+ write32u(self.fileobj, self.crc)
# self.size may exceed 2GB, or even 4GB
- write32u(self.fileobj, LOWU32(self.size))
+ write32u(self.fileobj, self.size & 0xffffffffL)
self.fileobj = None
elif self.mode == READ:
self.fileobj = None
@@ -335,25 +371,18 @@
self.myfileobj.close()
self.myfileobj = None
- def __del__(self):
- try:
- if (self.myfileobj is None and
- self.fileobj is None):
- return
- except AttributeError:
- return
- self.close()
-
if not sys.platform.startswith('java'):
def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
+ self._check_closed()
if self.mode == WRITE:
# Ensure the compressor's buffer is flushed
self.fileobj.write(self.compress.flush(zlib_mode))
- self.fileobj.flush()
+ self.fileobj.flush()
else:
# Java lacks Z_SYNC_FLUSH; thus Jython can't flush the
# compressobj until EOF
def flush(self,zlib_mode=None):
+ self._check_closed()
self.fileobj.flush()
def fileno(self):
@@ -364,12 +393,6 @@
"""
return self.fileobj.fileno()
- def isatty(self):
- return False
-
- def tell(self):
- return self.offset
-
def rewind(self):
'''Return the uncompressed stream file position indicator to the
beginning of the file'''
@@ -379,9 +402,24 @@
self._new_member = True
self.extrabuf = ""
self.extrasize = 0
+ self.extrastart = 0
self.offset = 0
- def seek(self, offset):
+ def readable(self):
+ return self.mode == READ
+
+ def writable(self):
+ return self.mode == WRITE
+
+ def seekable(self):
+ return True
+
+ def seek(self, offset, whence=0):
+ if whence:
+ if whence == 1:
+ offset = self.offset + offset
+ else:
+ raise ValueError('Seek from end not supported')
if self.mode == WRITE:
if offset < self.offset:
raise IOError('Negative seek in write mode')
@@ -398,8 +436,18 @@
self.read(1024)
self.read(count % 1024)
+ return self.offset
+
def readline(self, size=-1):
if size < 0:
+ # Shortcut common case - newline found in buffer.
+ offset = self.offset - self.extrastart
+ i = self.extrabuf.find('\n', offset) + 1
+ if i > 0:
+ self.extrasize -= i - offset
+ self.offset += i - offset
+ return self.extrabuf[offset: i]
+
size = sys.maxint
readsize = self.min_readsize
else:
@@ -429,34 +477,6 @@
self.min_readsize = min(readsize, self.min_readsize * 2, 512)
return ''.join(bufs) # Return resulting line
- def readlines(self, sizehint=0):
- # Negative numbers result in reading all the lines
- if sizehint <= 0:
- sizehint = sys.maxint
- L = []
- while sizehint > 0:
- line = self.readline()
- if line == "":
- break
- L.append(line)
- sizehint = sizehint - len(line)
-
- return L
-
- def writelines(self, L):
- for line in L:
- self.write(line)
-
- def __iter__(self):
- return self
-
- def next(self):
- line = self.readline()
- if line:
- return line
- else:
- raise StopIteration
-
def _test():
# Act like gzip; with -d, act like gunzip.
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -107,7 +107,7 @@
(Windows only)
-This module also defines two shortcut functions:
+This module also defines some shortcut functions:
call(*popenargs, **kwargs):
Run command with arguments. Wait for command to complete, then
@@ -127,6 +127,18 @@
check_call(["ls", "-l"])
+check_output(*popenargs, **kwargs):
+ Run command with arguments and return its output as a byte string.
+
+ If the exit code was non-zero it raises a CalledProcessError. The
+ CalledProcessError object will have the return code in the returncode
+ attribute and output in the output attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ output = check_output(["ls", "-l", "/dev/null"])
+
+
Exceptions
----------
Exceptions raised in the child process, before the new program has
@@ -141,8 +153,8 @@
A ValueError will be raised if Popen is called with invalid arguments.
-check_call() will raise CalledProcessError, if the called process
-returns a non-zero return code.
+check_call() and check_output() will raise CalledProcessError, if the
+called process returns a non-zero return code.
Security
@@ -287,54 +299,80 @@
Replacing os.popen*
-------------------
-pipe = os.popen(cmd, mode='r', bufsize)
+pipe = os.popen("cmd", mode='r', bufsize)
==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
+pipe = Popen("cmd", shell=True, bufsize=bufsize, stdout=PIPE).stdout
-pipe = os.popen(cmd, mode='w', bufsize)
+pipe = os.popen("cmd", mode='w', bufsize)
==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
+pipe = Popen("cmd", shell=True, bufsize=bufsize, stdin=PIPE).stdin
-(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize)
+(child_stdin, child_stdout) = os.popen2("cmd", mode, bufsize)
==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
+p = Popen("cmd", shell=True, bufsize=bufsize,
stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdin, child_stdout) = (p.stdin, p.stdout)
(child_stdin,
child_stdout,
- child_stderr) = os.popen3(cmd, mode, bufsize)
+ child_stderr) = os.popen3("cmd", mode, bufsize)
==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
+p = Popen("cmd", shell=True, bufsize=bufsize,
stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
(child_stdin,
child_stdout,
child_stderr) = (p.stdin, p.stdout, p.stderr)
-(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize)
+(child_stdin, child_stdout_and_stderr) = os.popen4("cmd", mode,
+ bufsize)
==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
+p = Popen("cmd", shell=True, bufsize=bufsize,
stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
+On Unix, os.popen2, os.popen3 and os.popen4 also accept a sequence as
+the command to execute, in which case arguments will be passed
+directly to the program without shell intervention. This usage can be
+replaced as follows:
+
+(child_stdin, child_stdout) = os.popen2(["/bin/ls", "-l"], mode,
+ bufsize)
+==>
+p = Popen(["/bin/ls", "-l"], bufsize=bufsize, stdin=PIPE, stdout=PIPE)
+(child_stdin, child_stdout) = (p.stdin, p.stdout)
+
+Return code handling translates as follows:
+
+pipe = os.popen("cmd", 'w')
+...
+rc = pipe.close()
+if rc is not None and rc % 256:
+ print "There were some errors"
+==>
+process = Popen("cmd", 'w', shell=True, stdin=PIPE)
+...
+process.stdin.close()
+if process.wait() != 0:
+ print "There were some errors"
+
Replacing popen2.*
------------------
-Note: If the cmd argument to popen2 functions is a string, the command
-is executed through /bin/sh. If it is a list, the command is directly
-executed.
-
(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode)
==>
p = Popen(["somestring"], shell=True, bufsize=bufsize
stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdout, child_stdin) = (p.stdout, p.stdin)
+On Unix, popen2 also accepts a sequence as the command to execute, in
+which case arguments will be passed directly to the program without
+shell intervention. This usage can be replaced as follows:
-(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode)
+(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize,
+ mode)
==>
p = Popen(["mycmd", "myarg"], bufsize=bufsize,
stdin=PIPE, stdout=PIPE, close_fds=True)
@@ -357,15 +395,19 @@
import os
import types
import traceback
+import signal
# Exception classes used by this module.
class CalledProcessError(Exception):
- """This exception is raised when a process run by check_call() returns
- a non-zero exit status. The exit status will be stored in the
- returncode attribute."""
- def __init__(self, returncode, cmd):
+ """This exception is raised when a process run by check_call() or
+ check_output() returns a non-zero exit status.
+ The exit status will be stored in the returncode attribute;
+ check_output() will also store the output in the output attribute.
+ """
+ def __init__(self, returncode, cmd, output=None):
self.returncode = returncode
self.cmd = cmd
+ self.output = output
def __str__(self):
return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
@@ -373,28 +415,15 @@
if mswindows:
import threading
import msvcrt
- if 0: # <-- change this to use pywin32 instead of the _subprocess driver
- import pywintypes
- from win32api import GetStdHandle, STD_INPUT_HANDLE, \
- STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
- from win32api import GetCurrentProcess, DuplicateHandle, \
- GetModuleFileName, GetVersion
- from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE
- from win32pipe import CreatePipe
- from win32process import CreateProcess, STARTUPINFO, \
- GetExitCodeProcess, STARTF_USESTDHANDLES, \
- STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
- from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
- else:
- from _subprocess import *
- class STARTUPINFO:
- dwFlags = 0
- hStdInput = None
- hStdOutput = None
- hStdError = None
- wShowWindow = 0
- class pywintypes:
- error = IOError
+ import _subprocess
+ class STARTUPINFO:
+ dwFlags = 0
+ hStdInput = None
+ hStdOutput = None
+ hStdError = None
+ wShowWindow = 0
+ class pywintypes:
+ error = IOError
elif jython:
import errno
import threading
@@ -410,30 +439,35 @@
import org.python.core.io.StreamIO
else:
import select
+ _has_poll = hasattr(select, 'poll')
import errno
import fcntl
import gc
import pickle
-__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"]
+ # When select or poll has indicated that the file is writable,
+ # we can write up to _PIPE_BUF bytes without risk of blocking.
+ # POSIX defines PIPE_BUF as >= 512.
+ _PIPE_BUF = getattr(select, 'PIPE_BUF', 512)
+
+__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call",
+ "check_output", "CalledProcessError"]
+
+if mswindows:
+ from _subprocess import CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP
+ __all__.extend(["CREATE_NEW_CONSOLE", "CREATE_NEW_PROCESS_GROUP"])
try:
MAXFD = os.sysconf("SC_OPEN_MAX")
except:
MAXFD = 256
-# True/False does not exist on 2.2.0
-try:
- False
-except NameError:
- False = 0
- True = 1
-
_active = []
def _cleanup():
for inst in _active[:]:
- if inst.poll(_deadstate=sys.maxint) >= 0:
+ res = inst._internal_poll(_deadstate=sys.maxint)
+ if res is not None and res >= 0:
try:
_active.remove(inst)
except ValueError:
@@ -445,6 +479,16 @@
STDOUT = -2
+def _eintr_retry_call(func, *args):
+ while True:
+ try:
+ return func(*args)
+ except OSError, e:
+ if e.errno == errno.EINTR:
+ continue
+ raise
+
+
def call(*popenargs, **kwargs):
"""Run command with arguments. Wait for command to complete, then
return the returncode attribute.
@@ -467,12 +511,45 @@
check_call(["ls", "-l"])
"""
retcode = call(*popenargs, **kwargs)
- cmd = kwargs.get("args")
- if cmd is None:
- cmd = popenargs[0]
if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
raise CalledProcessError(retcode, cmd)
- return retcode
+ return 0
+
+
+def check_output(*popenargs, **kwargs):
+ r"""Run command with arguments and return its output as a byte string.
+
+ If the exit code was non-zero it raises a CalledProcessError. The
+ CalledProcessError object will have the return code in the returncode
+ attribute and output in the output attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ >>> check_output(["ls", "-l", "/dev/null"])
+ 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
+
+ The stdout argument is not allowed as it is used internally.
+ To capture standard error in the result, use stderr=STDOUT.
+
+ >>> check_output(["/bin/sh", "-c",
+ ... "ls -l non_existent_file ; exit 0"],
+ ... stderr=STDOUT)
+ 'ls: non_existent_file: No such file or directory\n'
+ """
+ if 'stdout' in kwargs:
+ raise ValueError('stdout argument not allowed, it will be overridden.')
+ process = Popen(stdout=PIPE, *popenargs, **kwargs)
+ output, unused_err = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ raise CalledProcessError(retcode, cmd, output=output)
+ return output
def list2cmdline(seq):
@@ -485,8 +562,8 @@
2) A string surrounded by double quotation marks is
interpreted as a single argument, regardless of white space
- or pipe characters contained within. A quoted string can be
- embedded in an argument.
+ contained within. A quoted string can be embedded in an
+ argument.
3) A double quotation mark preceded by a backslash is
interpreted as a literal double quotation mark.
@@ -502,7 +579,9 @@
"""
# See
- # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
+ # http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
+ # or search http://msdn.microsoft.com for
+ # "Parsing C++ Command-Line Arguments"
result = []
needquote = False
for arg in seq:
@@ -512,7 +591,7 @@
if result:
result.append(' ')
- needquote = (" " in arg) or ("\t" in arg) or ("|" in arg) or not arg
+ needquote = (" " in arg) or ("\t" in arg) or not arg
if needquote:
result.append('"')
@@ -755,21 +834,13 @@
c2pread, c2pwrite,
errread, errwrite)
- # On Windows, you cannot just redirect one or two handles: You
- # either have to redirect all three or none. If the subprocess
- # user has only redirected one or two handles, we are
- # automatically creating PIPEs for the rest. We should close
- # these after the process is started. See bug #1124861.
if mswindows:
- if stdin is None and p2cwrite is not None:
- os.close(p2cwrite)
- p2cwrite = None
- if stdout is None and c2pread is not None:
- os.close(c2pread)
- c2pread = None
- if stderr is None and errread is not None:
- os.close(errread)
- errread = None
+ if p2cwrite is not None:
+ p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0)
+ if c2pread is not None:
+ c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0)
+ if errread is not None:
+ errread = msvcrt.open_osfhandle(errread.Detach(), 0)
if jython:
self._stdin_thread = None
@@ -846,12 +917,12 @@
return data
- def __del__(self, sys=sys):
+ def __del__(self, _maxint=sys.maxint, _active=_active):
if not self._child_created:
# We didn't get to successfully create a child process.
return
# In case the child hasn't been waited on, check if it's done.
- self.poll(_deadstate=sys.maxint)
+ self._internal_poll(_deadstate=_maxint)
if self.returncode is None and _active is not None:
# Child is still running, keep us alive until we can wait on it.
_active.append(self)
@@ -877,14 +948,20 @@
self.stdin.close()
elif self.stdout:
stdout = self.stdout.read()
+ self.stdout.close()
elif self.stderr:
stderr = self.stderr.read()
+ self.stderr.close()
self.wait()
return (stdout, stderr)
return self._communicate(input)
+ def poll(self):
+ return self._internal_poll()
+
+
if mswindows or jython:
#
# Windows and Jython shared methods
@@ -945,7 +1022,7 @@
# Windows methods
#
def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tupel with IO objects:
+ """Construct and return tuple with IO objects:
p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
"""
if stdin is None and stdout is None and stderr is None:
@@ -956,14 +1033,11 @@
errread, errwrite = None, None
if stdin is None:
- p2cread = GetStdHandle(STD_INPUT_HANDLE)
- if p2cread is not None:
- pass
- elif stdin is None or stdin == PIPE:
- p2cread, p2cwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- p2cwrite = p2cwrite.Detach()
- p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0)
+ p2cread = _subprocess.GetStdHandle(_subprocess.STD_INPUT_HANDLE)
+ if p2cread is None:
+ p2cread, _ = _subprocess.CreatePipe(None, 0)
+ elif stdin == PIPE:
+ p2cread, p2cwrite = _subprocess.CreatePipe(None, 0)
elif isinstance(stdin, int):
p2cread = msvcrt.get_osfhandle(stdin)
else:
@@ -972,14 +1046,11 @@
p2cread = self._make_inheritable(p2cread)
if stdout is None:
- c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE)
- if c2pwrite is not None:
- pass
- elif stdout is None or stdout == PIPE:
- c2pread, c2pwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- c2pread = c2pread.Detach()
- c2pread = msvcrt.open_osfhandle(c2pread, 0)
+ c2pwrite = _subprocess.GetStdHandle(_subprocess.STD_OUTPUT_HANDLE)
+ if c2pwrite is None:
+ _, c2pwrite = _subprocess.CreatePipe(None, 0)
+ elif stdout == PIPE:
+ c2pread, c2pwrite = _subprocess.CreatePipe(None, 0)
elif isinstance(stdout, int):
c2pwrite = msvcrt.get_osfhandle(stdout)
else:
@@ -988,14 +1059,11 @@
c2pwrite = self._make_inheritable(c2pwrite)
if stderr is None:
- errwrite = GetStdHandle(STD_ERROR_HANDLE)
- if errwrite is not None:
- pass
- elif stderr is None or stderr == PIPE:
- errread, errwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- errread = errread.Detach()
- errread = msvcrt.open_osfhandle(errread, 0)
+ errwrite = _subprocess.GetStdHandle(_subprocess.STD_ERROR_HANDLE)
+ if errwrite is None:
+ _, errwrite = _subprocess.CreatePipe(None, 0)
+ elif stderr == PIPE:
+ errread, errwrite = _subprocess.CreatePipe(None, 0)
elif stderr == STDOUT:
errwrite = c2pwrite
elif isinstance(stderr, int):
@@ -1012,14 +1080,15 @@
def _make_inheritable(self, handle):
"""Return a duplicate of handle, which is inheritable"""
- return DuplicateHandle(GetCurrentProcess(), handle,
- GetCurrentProcess(), 0, 1,
- DUPLICATE_SAME_ACCESS)
+ return _subprocess.DuplicateHandle(_subprocess.GetCurrentProcess(),
+ handle, _subprocess.GetCurrentProcess(), 0, 1,
+ _subprocess.DUPLICATE_SAME_ACCESS)
def _find_w9xpopen(self):
"""Find and return absolut path to w9xpopen.exe"""
- w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)),
+ w9xpopen = os.path.join(
+ os.path.dirname(_subprocess.GetModuleFileName(0)),
"w9xpopen.exe")
if not os.path.exists(w9xpopen):
# Eeek - file-not-found - possibly an embedding
@@ -1048,17 +1117,17 @@
if startupinfo is None:
startupinfo = STARTUPINFO()
if None not in (p2cread, c2pwrite, errwrite):
- startupinfo.dwFlags |= STARTF_USESTDHANDLES
+ startupinfo.dwFlags |= _subprocess.STARTF_USESTDHANDLES
startupinfo.hStdInput = p2cread
startupinfo.hStdOutput = c2pwrite
startupinfo.hStdError = errwrite
if shell:
- startupinfo.dwFlags |= STARTF_USESHOWWINDOW
- startupinfo.wShowWindow = SW_HIDE
+ startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
+ startupinfo.wShowWindow = _subprocess.SW_HIDE
comspec = os.environ.get("COMSPEC", "cmd.exe")
- args = comspec + " /c " + args
- if (GetVersion() >= 0x80000000L or
+ args = '{} /c "{}"'.format (comspec, args)
+ if (_subprocess.GetVersion() >= 0x80000000 or
os.path.basename(comspec).lower() == "command.com"):
# Win9x, or using command.com on NT. We need to
# use the w9xpopen intermediate program. For more
@@ -1072,11 +1141,11 @@
# use at xxx" and a hopeful warning about the
# stability of your system. Cost is Ctrl+C wont
# kill children.
- creationflags |= CREATE_NEW_CONSOLE
+ creationflags |= _subprocess.CREATE_NEW_CONSOLE
# Start the process
try:
- hp, ht, pid, tid = CreateProcess(executable, args,
+ hp, ht, pid, tid = _subprocess.CreateProcess(executable, args,
# no special security
None, None,
int(not close_fds),
@@ -1090,6 +1159,19 @@
# translate errno using _sys_errlist (or simliar), but
# how can this be done from Python?
raise WindowsError(*e.args)
+ finally:
+ # Child is launched. Close the parent's copy of those pipe
+ # handles that only the child should have open. You need
+ # to make sure that no handles to the write end of the
+ # output pipe are maintained in this process or else the
+ # pipe will not close when the child process exits and the
+ # ReadFile will hang.
+ if p2cread is not None:
+ p2cread.Close()
+ if c2pwrite is not None:
+ c2pwrite.Close()
+ if errwrite is not None:
+ errwrite.Close()
# Retain the process handle, but close the thread handle
self._child_created = True
@@ -1097,26 +1179,20 @@
self.pid = pid
ht.Close()
- # Child is launched. Close the parent's copy of those pipe
- # handles that only the child should have open. You need
- # to make sure that no handles to the write end of the
- # output pipe are maintained in this process or else the
- # pipe will not close when the child process exits and the
- # ReadFile will hang.
- if p2cread is not None:
- p2cread.Close()
- if c2pwrite is not None:
- c2pwrite.Close()
- if errwrite is not None:
- errwrite.Close()
+ def _internal_poll(self, _deadstate=None,
+ _WaitForSingleObject=_subprocess.WaitForSingleObject,
+ _WAIT_OBJECT_0=_subprocess.WAIT_OBJECT_0,
+ _GetExitCodeProcess=_subprocess.GetExitCodeProcess):
+ """Check if child process has terminated. Returns returncode
+ attribute.
+ This method is called by __del__, so it can only refer to objects
+ in its local scope.
- def poll(self, _deadstate=None):
- """Check if child process has terminated. Returns returncode
- attribute."""
+ """
if self.returncode is None:
- if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
- self.returncode = GetExitCodeProcess(self._handle)
+ if _WaitForSingleObject(self._handle, 0) == _WAIT_OBJECT_0:
+ self.returncode = _GetExitCodeProcess(self._handle)
return self.returncode
@@ -1124,8 +1200,9 @@
"""Wait for child process to terminate. Returns returncode
attribute."""
if self.returncode is None:
- obj = WaitForSingleObject(self._handle, INFINITE)
- self.returncode = GetExitCodeProcess(self._handle)
+ _subprocess.WaitForSingleObject(self._handle,
+ _subprocess.INFINITE)
+ self.returncode = _subprocess.GetExitCodeProcess(self._handle)
return self.returncode
elif jython:
@@ -1291,12 +1368,31 @@
self._stdin_thread.interrupt()
return self.returncode
+ def send_signal(self, sig):
+ """Send a signal to the process
+ """
+ if sig == signal.SIGTERM:
+ self.terminate()
+ elif sig == signal.CTRL_C_EVENT:
+ os.kill(self.pid, signal.CTRL_C_EVENT)
+ elif sig == signal.CTRL_BREAK_EVENT:
+ os.kill(self.pid, signal.CTRL_BREAK_EVENT)
+ else:
+ raise ValueError("Unsupported signal: {}".format(sig))
+
+ def terminate(self):
+ """Terminates the process
+ """
+ _subprocess.TerminateProcess(self._handle, 1)
+
+ kill = terminate
+
else:
#
# POSIX methods
#
def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tupel with IO objects:
+ """Construct and return tuple with IO objects:
p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
"""
p2cread, p2cwrite = None, None
@@ -1340,19 +1436,31 @@
errread, errwrite)
- def _set_cloexec_flag(self, fd):
+ def _set_cloexec_flag(self, fd, cloexec=True):
try:
cloexec_flag = fcntl.FD_CLOEXEC
except AttributeError:
cloexec_flag = 1
old = fcntl.fcntl(fd, fcntl.F_GETFD)
- fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
+ if cloexec:
+ fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
+ else:
+ fcntl.fcntl(fd, fcntl.F_SETFD, old & ~cloexec_flag)
def _close_fds(self, but):
- os.closerange(3, but)
- os.closerange(but + 1, MAXFD)
+ if hasattr(os, 'closerange'):
+ os.closerange(3, but)
+ os.closerange(but + 1, MAXFD)
+ else:
+ for i in xrange(3, MAXFD):
+ if i == but:
+ continue
+ try:
+ os.close(i)
+ except:
+ pass
def _execute_child(self, args, executable, preexec_fn, close_fds,
@@ -1370,6 +1478,8 @@
if shell:
args = ["/bin/sh", "-c"] + args
+ if executable:
+ args[0] = executable
if executable is None:
executable = args[0]
@@ -1378,115 +1488,145 @@
# The first char specifies the exception type: 0 means
# OSError, 1 means some other error.
errpipe_read, errpipe_write = os.pipe()
- self._set_cloexec_flag(errpipe_write)
+ try:
+ try:
+ self._set_cloexec_flag(errpipe_write)
- gc_was_enabled = gc.isenabled()
- # Disable gc to avoid bug where gc -> file_dealloc ->
- # write to stderr -> hang. http://bugs.python.org/issue1336
- gc.disable()
- try:
- self.pid = os.fork()
- except:
- if gc_was_enabled:
- gc.enable()
- raise
- self._child_created = True
- if self.pid == 0:
- # Child
+ gc_was_enabled = gc.isenabled()
+ # Disable gc to avoid bug where gc -> file_dealloc ->
+ # write to stderr -> hang. http://bugs.python.org/issue1336
+ gc.disable()
+ try:
+ self.pid = os.fork()
+ except:
+ if gc_was_enabled:
+ gc.enable()
+ raise
+ self._child_created = True
+ if self.pid == 0:
+ # Child
+ try:
+ # Close parent's pipe ends
+ if p2cwrite is not None:
+ os.close(p2cwrite)
+ if c2pread is not None:
+ os.close(c2pread)
+ if errread is not None:
+ os.close(errread)
+ os.close(errpipe_read)
+
+ # Dup fds for child
+ def _dup2(a, b):
+ # dup2() removes the CLOEXEC flag but
+ # we must do it ourselves if dup2()
+ # would be a no-op (issue #10806).
+ if a == b:
+ self._set_cloexec_flag(a, False)
+ elif a is not None:
+ os.dup2(a, b)
+ _dup2(p2cread, 0)
+ _dup2(c2pwrite, 1)
+ _dup2(errwrite, 2)
+
+ # Close pipe fds. Make sure we don't close the
+ # same fd more than once, or standard fds.
+ closed = { None }
+ for fd in [p2cread, c2pwrite, errwrite]:
+ if fd not in closed and fd > 2:
+ os.close(fd)
+ closed.add(fd)
+
+ # Close all other fds, if asked for
+ if close_fds:
+ self._close_fds(but=errpipe_write)
+
+ if cwd is not None:
+ os.chdir(cwd)
+
+ if preexec_fn:
+ preexec_fn()
+
+ if env is None:
+ os.execvp(executable, args)
+ else:
+ os.execvpe(executable, args, env)
+
+ except:
+ exc_type, exc_value, tb = sys.exc_info()
+ # Save the traceback and attach it to the exception object
+ exc_lines = traceback.format_exception(exc_type,
+ exc_value,
+ tb)
+ exc_value.child_traceback = ''.join(exc_lines)
+ os.write(errpipe_write, pickle.dumps(exc_value))
+
+ # This exitcode won't be reported to applications, so it
+ # really doesn't matter what we return.
+ os._exit(255)
+
+ # Parent
+ if gc_was_enabled:
+ gc.enable()
+ finally:
+ # be sure the FD is closed no matter what
+ os.close(errpipe_write)
+
+ if p2cread is not None and p2cwrite is not None:
+ os.close(p2cread)
+ if c2pwrite is not None and c2pread is not None:
+ os.close(c2pwrite)
+ if errwrite is not None and errread is not None:
+ os.close(errwrite)
+
+ # Wait for exec to fail or succeed; possibly raising exception
+ # Exception limited to 1M
+ data = _eintr_retry_call(os.read, errpipe_read, 1048576)
+ finally:
+ # be sure the FD is closed no matter what
+ os.close(errpipe_read)
+
+ if data != "":
try:
- # Close parent's pipe ends
- if p2cwrite is not None:
- os.close(p2cwrite)
- if c2pread is not None:
- os.close(c2pread)
- if errread is not None:
- os.close(errread)
- os.close(errpipe_read)
-
- # Dup fds for child
- if p2cread is not None:
- os.dup2(p2cread, 0)
- if c2pwrite is not None:
- os.dup2(c2pwrite, 1)
- if errwrite is not None:
- os.dup2(errwrite, 2)
-
- # Close pipe fds. Make sure we don't close the same
- # fd more than once, or standard fds.
- if p2cread is not None and p2cread not in (0,):
- os.close(p2cread)
- if c2pwrite is not None and c2pwrite not in (p2cread, 1):
- os.close(c2pwrite)
- if errwrite is not None and errwrite not in (p2cread, c2pwrite, 2):
- os.close(errwrite)
-
- # Close all other fds, if asked for
- if close_fds:
- self._close_fds(but=errpipe_write)
-
- if cwd is not None:
- os.chdir(cwd)
-
- if preexec_fn:
- apply(preexec_fn)
-
- if env is None:
- os.execvp(executable, args)
- else:
- os.execvpe(executable, args, env)
-
- except:
- exc_type, exc_value, tb = sys.exc_info()
- # Save the traceback and attach it to the exception object
- exc_lines = traceback.format_exception(exc_type,
- exc_value,
- tb)
- exc_value.child_traceback = ''.join(exc_lines)
- os.write(errpipe_write, pickle.dumps(exc_value))
-
- # This exitcode won't be reported to applications, so it
- # really doesn't matter what we return.
- os._exit(255)
-
- # Parent
- if gc_was_enabled:
- gc.enable()
- os.close(errpipe_write)
- if p2cread is not None and p2cwrite is not None:
- os.close(p2cread)
- if c2pwrite is not None and c2pread is not None:
- os.close(c2pwrite)
- if errwrite is not None and errread is not None:
- os.close(errwrite)
-
- # Wait for exec to fail or succeed; possibly raising exception
- data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
- os.close(errpipe_read)
- if data != "":
- os.waitpid(self.pid, 0)
+ _eintr_retry_call(os.waitpid, self.pid, 0)
+ except OSError as e:
+ if e.errno != errno.ECHILD:
+ raise
child_exception = pickle.loads(data)
+ for fd in (p2cwrite, c2pread, errread):
+ if fd is not None:
+ os.close(fd)
raise child_exception
- def _handle_exitstatus(self, sts):
- if os.WIFSIGNALED(sts):
- self.returncode = -os.WTERMSIG(sts)
- elif os.WIFEXITED(sts):
- self.returncode = os.WEXITSTATUS(sts)
+ def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED,
+ _WTERMSIG=os.WTERMSIG, _WIFEXITED=os.WIFEXITED,
+ _WEXITSTATUS=os.WEXITSTATUS):
+ # This method is called (indirectly) by __del__, so it cannot
+ # refer to anything outside of its local scope."""
+ if _WIFSIGNALED(sts):
+ self.returncode = -_WTERMSIG(sts)
+ elif _WIFEXITED(sts):
+ self.returncode = _WEXITSTATUS(sts)
else:
# Should never happen
raise RuntimeError("Unknown child exit status!")
- def poll(self, _deadstate=None):
+ def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
+ _WNOHANG=os.WNOHANG, _os_error=os.error):
"""Check if child process has terminated. Returns returncode
- attribute."""
+ attribute.
+
+ This method is called by __del__, so it cannot reference anything
+ outside of the local scope (nor can any methods it calls).
+
+ """
if self.returncode is None:
try:
- pid, sts = os.waitpid(self.pid, os.WNOHANG)
+ pid, sts = _waitpid(self.pid, _WNOHANG)
if pid == self.pid:
self._handle_exitstatus(sts)
- except os.error:
+ except _os_error:
if _deadstate is not None:
self.returncode = _deadstate
return self.returncode
@@ -1496,25 +1636,114 @@
"""Wait for child process to terminate. Returns returncode
attribute."""
if self.returncode is None:
- pid, sts = os.waitpid(self.pid, 0)
+ try:
+ pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
+ except OSError as e:
+ if e.errno != errno.ECHILD:
+ raise
+ # This happens if SIGCLD is set to be ignored or waiting
+ # for child processes has otherwise been disabled for our
+ # process. This child is dead, we can't get the status.
+ sts = 0
self._handle_exitstatus(sts)
return self.returncode
def _communicate(self, input):
+ if self.stdin:
+ # Flush stdio buffer. This might block, if the user has
+ # been writing to .stdin in an uncontrolled fashion.
+ self.stdin.flush()
+ if not input:
+ self.stdin.close()
+
+ if _has_poll:
+ stdout, stderr = self._communicate_with_poll(input)
+ else:
+ stdout, stderr = self._communicate_with_select(input)
+
+ # All data exchanged. Translate lists into strings.
+ if stdout is not None:
+ stdout = ''.join(stdout)
+ if stderr is not None:
+ stderr = ''.join(stderr)
+
+ # Translate newlines, if requested. We cannot let the file
+ # object do the translation: It is based on stdio, which is
+ # impossible to combine with select (unless forcing no
+ # buffering).
+ if self.universal_newlines and hasattr(file, 'newlines'):
+ if stdout:
+ stdout = self._translate_newlines(stdout)
+ if stderr:
+ stderr = self._translate_newlines(stderr)
+
+ self.wait()
+ return (stdout, stderr)
+
+
+ def _communicate_with_poll(self, input):
+ stdout = None # Return
+ stderr = None # Return
+ fd2file = {}
+ fd2output = {}
+
+ poller = select.poll()
+ def register_and_append(file_obj, eventmask):
+ poller.register(file_obj.fileno(), eventmask)
+ fd2file[file_obj.fileno()] = file_obj
+
+ def close_unregister_and_remove(fd):
+ poller.unregister(fd)
+ fd2file[fd].close()
+ fd2file.pop(fd)
+
+ if self.stdin and input:
+ register_and_append(self.stdin, select.POLLOUT)
+
+ select_POLLIN_POLLPRI = select.POLLIN | select.POLLPRI
+ if self.stdout:
+ register_and_append(self.stdout, select_POLLIN_POLLPRI)
+ fd2output[self.stdout.fileno()] = stdout = []
+ if self.stderr:
+ register_and_append(self.stderr, select_POLLIN_POLLPRI)
+ fd2output[self.stderr.fileno()] = stderr = []
+
+ input_offset = 0
+ while fd2file:
+ try:
+ ready = poller.poll()
+ except select.error, e:
+ if e.args[0] == errno.EINTR:
+ continue
+ raise
+
+ for fd, mode in ready:
+ if mode & select.POLLOUT:
+ chunk = input[input_offset : input_offset + _PIPE_BUF]
+ input_offset += os.write(fd, chunk)
+ if input_offset >= len(input):
+ close_unregister_and_remove(fd)
+ elif mode & select_POLLIN_POLLPRI:
+ data = os.read(fd, 4096)
+ if not data:
+ close_unregister_and_remove(fd)
+ fd2output[fd].append(data)
+ else:
+ # Ignore hang up or errors.
+ close_unregister_and_remove(fd)
+
+ return (stdout, stderr)
+
+
+ def _communicate_with_select(self, input):
read_set = []
write_set = []
stdout = None # Return
stderr = None # Return
- if self.stdin:
- # Flush stdio buffer. This might block, if the user has
- # been writing to .stdin in an uncontrolled fashion.
- self.stdin.flush()
- if input:
- write_set.append(self.stdin)
- else:
- self.stdin.close()
+ if self.stdin and input:
+ write_set.append(self.stdin)
if self.stdout:
read_set.append(self.stdout)
stdout = []
@@ -1524,13 +1753,16 @@
input_offset = 0
while read_set or write_set:
- rlist, wlist, xlist = select.select(read_set, write_set, [])
+ try:
+ rlist, wlist, xlist = select.select(read_set, write_set, [])
+ except select.error, e:
+ if e.args[0] == errno.EINTR:
+ continue
+ raise
if self.stdin in wlist:
- # When select has indicated that the file is writable,
- # we can write up to PIPE_BUF bytes without risk
- # blocking. POSIX defines PIPE_BUF >= 512
- bytes_written = os.write(self.stdin.fileno(), buffer(input, input_offset, 512))
+ chunk = input[input_offset : input_offset + _PIPE_BUF]
+ bytes_written = os.write(self.stdin.fileno(), chunk)
input_offset += bytes_written
if input_offset >= len(input):
self.stdin.close()
@@ -1550,24 +1782,23 @@
read_set.remove(self.stderr)
stderr.append(data)
- # All data exchanged. Translate lists into strings.
- if stdout is not None:
- stdout = ''.join(stdout)
- if stderr is not None:
- stderr = ''.join(stderr)
+ return (stdout, stderr)
- # Translate newlines, if requested. We cannot let the file
- # object do the translation: It is based on stdio, which is
- # impossible to combine with select (unless forcing no
- # buffering).
- if self.universal_newlines and hasattr(file, 'newlines'):
- if stdout:
- stdout = self._translate_newlines(stdout)
- if stderr:
- stderr = self._translate_newlines(stderr)
- self.wait()
- return (stdout, stderr)
+ def send_signal(self, sig):
+ """Send a signal to the process
+ """
+ os.kill(self.pid, sig)
+
+ def terminate(self):
+ """Terminate the process with SIGTERM
+ """
+ self.send_signal(signal.SIGTERM)
+
+ def kill(self):
+ """Kill the process with SIGKILL
+ """
+ self.send_signal(signal.SIGKILL)
def _demo_posix():
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list