Lenard Lindstrom avatar Lenard Lindstrom committed 8a7d9b2

MinGW/MSYS build process more automated and DLLs Win32 GUI by default

Comments (0)

Files changed (17)

 # BUG	= fixed a bug that was (or could have been) crashing
 #
 #
+Feb 12, 2008 (svn 1107)
+    Further Windows build updates:
+      For MinGW/MSYS the build process now finds the MSYS and MinGW
+      directories automatically. gcc links to msvcr71.dll without
+      requiring changes to the gcc specs file. This makes the build
+      process GCC Version 4 ready. By default all DLLs are Win32 GUI,
+      but can be console. The build progams will run from either the
+      Windows or MSYS terminal.
+
+      Fixed a bug where DLLs were not installed in the package directory.
+      For Windows, everything now goes into the package directory.
+
+      build_deps.py renamed to msys_build_deps.py to avoid confusion.
+
 Jan 26, 2008
     pygame.sndarray noew can change between Numeric and numpy using the
       new methods pygame.sndarray.use_array () and

build_deps.py

-# Program build_deps.py
-
-"""Build Pygame dependencies
-
-Configured for Pygame 1.8 and Python 2.4 and up.
-
-The libraries are installed in /usr/local of the MSYS directory structure.
-
-This program can be run from a cmd or MSYS prompt. The current directory
-and its outer directory are searched for the library source directories.
-
-For the build to work the MSYS /usr/local header and library directories
-must be part of the MinGW default search path. The necessary changes
-to the mingw\\lib\\gcc\\mingw32\\<gcc verion #>\\specs file will be
-made if the --prepare-mingw command line option is chosen. The specs file
-is backed up as specs-original so the changes can be easily undone.
-
-Python 2.4 and later are linked against msvcr71.dll. By default MinGW links
-against the older msvcrt.dll. Unless the --no-msvcr71 option is closen the
---prepare-mingw option updates the MinGW specs file so MingGW links against
-msvcr71.dll. Restoring the specs file undoes the changes.
-
-Useful environment variables are "SHELL", the MSYS shell program (already defined
-in the MSYS console), and "MINGW_ROOT_DIRECTORY". The program will prompt for any
-missing information.
-
-To get a list of command line options run
-
-python build_deps.py --help
-
-This program has been tested against the following libraries:
-
-SDL 1.2.13
-SDL_image 1.2.6
-SDL_mixer 1.2.8
-SDL_ttf 2.0.9
-smpeg revision 370 from SVN
-freetype 2.3.5
-libogg 1.1.3
-libvorbis 1.2.0
-tiff 3.8.2
-libpng 1.2.24
-jpeg 6b
-zlib 1.2.3
-
-The build environment used:
-
-gcc-core-3.4.5
-binutils-2.17.50
-mingw-runtime-3.14
-win32api-3.11
-mingw32-make-3.81.2
-gcc-g++3.4.5
-MSYS-1.0.11
-MSYS-DTK-1.0.1 (for smpeg)
-nasm (from www.libsdl.org)
-"""
-
-from optparse import OptionParser
-import os
-import sys
-import subprocess
-from glob import glob
-import time
-
-#
-#   Generic declarations
-#
-hunt_paths = ['.', '..']
-
-# These functions are created by init()
-#   windows_to_msys(path) => MSYS path
-#   run_shell_script(script) => exit code
-
-# This variable is set by init()
-#   msys_root
-
-def msys_raw_input(prompt=None):
-    if prompt is not None:
-        sys.stdout.flush()
-        sys.stdout.write(prompt)
-    sys.stdout.flush()
-    return raw_input()
-
-def confirm(message):
-    "ask a yes/no question, return result"
-    reply = msys_raw_input("\n%s [Y/n]:" % message)
-    if reply and reply[0].lower() == 'n':
-        return 0
-    return 1
-
-def as_flag(b):
-    """Return bool b as a shell script flag '1' or '0'"""
-    if b:
-        return '1'
-    return '0'
-
-class Dependency(object):
-    def __init__(self, name, wildcards, dlls, shell_script):
-        self.name = name
-        self.wildcards = wildcards
-        self.shell_script = shell_script
-        self.dlls = dlls
-
-    def configure(self, hunt_paths):
-        self.path = None
-        self.paths = []
-        self.hunt(hunt_paths)
-        self.choosepath()
-
-    def hunt(self, hunt_paths):
-        parent = os.path.abspath('..')
-        for p in hunt_paths:
-            for w in self.wildcards:
-                found = glob(os.path.join(p, w))
-                found.sort() or found.reverse()  #reverse sort
-                for f in found:
-                    if f[:5] == '..'+os.sep+'..' and \
-                        os.path.abspath(f)[:len(parent)] == parent:
-                        continue
-                    if os.path.isdir(f):
-                        self.paths.append(f)
-
-    def choosepath(self):
-        path = None
-        if not self.paths:
-            print "Path for %s: not found" % self.name
-        elif len(self.paths) == 1:
-            path = self.paths[0]
-        else:
-            print "Select path for %s:" % self.name
-            for i in range(len(self.paths)):
-                print "  %d = %s" % (i+1, self.paths[i])
-            print "  0 = <Nothing>"
-            choice = msys_raw_input("Select 0-%d (1=default):" % len(self.paths))
-            if not choice:
-                choice = 1
-            else:
-                choice = int(choice)
-            if choice > 0:
-                path = self.paths[choice-1]
-        if path is not None:
-            self.path = os.path.abspath(path)
-            print "Path for %s: %s" % (self.name, self.path)
-
-    def build(self):
-        if self.path is not None:
-            os.environ['BDWD'] = windows_to_msys(self.path)  # Is the path conversion overkill?
-            return run_shell_script(self.shell_script)
-        else:
-            return None
-
-class Preparation(object):
-    def __init__(self, name, shell_script):
-        self.name = name
-        self.path = 'n/a'
-        self.paths = []
-        self.path = None
-        self.dlls = []
-        self.shell_script = shell_script
-
-    def configure(self, hunt_paths):
-        pass
-
-    def build(self):
-        return run_shell_script(self.shell_script)
-
-def configure(dependencies):
-    for dep in dependencies:
-        dep.configure(hunt_paths)
-
-def build(dependencies):
-    results = []
-    for dep in dependencies:
-        results.append((dep.name, dep.build()))
-        time.sleep(3)  # Give the subshells time to disappear
-    return results
-
-def input_shell():
-    while 1:
-        dir_path = msys_raw_input("Enter the MSYS directory path,\n"
-                                  "(or press [Enter] to quit):")
-        if not dir_path:
-            return None
-        roots = glob(os.path.join(dir_path, '[1-9].[0-9]'))
-        roots.sort()
-        roots.reverse()
-        if not roots:
-            print "\nNo msys versions found.\n"
-        else:
-            if len(roots) == 1:
-                root = roots[0]
-            else:
-                print "Select an Msys version:"
-                for i, path in enumerate(roots):
-                    print "  %d = %s" % (i+1, os.path.split(path)[1])
-                choice = msys_raw_input("Select 1-%d (1 = default):")
-                if not choice:
-                    root = roots[0]
-                else:
-                    root = roots[int(choice)-1]
-            shell = os.path.join(os.path.abspath(root), 'bin', 'sh.exe')
-            if os.path.isfile(shell):
-                return shell
-            else:
-                print "\nThe specified Msys version has shell.\n"
-    
-def init(msys_directory=None, mingw_directory=None):
-    """Set up environment"""
-    # This function may terminate execution.
-    global windows_to_msys, run_shell_script, msys_root
-
-    def windows_to_msys(path):
-        # Assumption: The path is absolute and starts with a drive letter.
-        path_lower = path.lower()
-        if path_lower.startswith(msys_root):
-            return '/usr' + path[len(msys_root):].replace(os.sep, '/')
-        if path_lower.startswith(mingw_root):
-            return '/mingw' + path[len(mingw_root):].replace(os.sep, '/')
-        drive, tail = os.path.splitdrive(path)
-        return '/%s%s' % (drive[0], tail.replace(os.sep, '/'))
-
-    def run_shell_script(script):
-        cmd = [shell]
-        if 'MSYSTEM' not in os.environ:
-            cmd.append('--login')
-        previous_cwd = os.getcwd()
-        try:
-            p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
-            p.communicate(script)
-            return not bool(p.returncode)
-        finally:
-            os.chdir(previous_cwd)
-
-    if mingw_directory is None:
-        mingw_directory = '\\mingw'  # Default
-
-    # Find the MSYS shell.
-    try:
-        file_path = os.environ['SHELL']
-    except KeyError:
-        if msys_directory is not None:
-            file_path = os.path.join(msys_directory, 'bin', 'sh.exe')
-        else:
-            file_path = input_shell()
-    shell = os.path.abspath(file_path)
-    if not os.path.isfile(shell):
-        shell = input_shell()
-        if shell is None:
-            sys.exit()
-    msys_root = os.path.split(os.path.split(shell)[0])[0].lower()
-
-    # Ensure MINGW_ROOT_DIRECTORY environment variable defined.
-    try:
-        dir_path = os.environ['MINGW_ROOT_DIRECTORY']
-    except KeyError:
-        dir_path = mingw_directory
-    while 1:
-        dir_path = os.path.abspath(dir_path)
-        if os.path.isdir(dir_path):
-            break
-        dir_path = msys_raw_input("Enter the MINGW directory path,\n(or press [Enter] to quit):")
-        if not dir_path:
-            sys.exit()
-    os.environ['MINGW_ROOT_DIRECTORY'] = dir_path
-    mingw_root = dir_path.lower()
-
-def main(dependencies, mingw_preparation, msys_preparation):
-    names = [d.name for d in dependencies]
-    usage = ("usage: %prog [options] --all\n"
-             "       %prog [options] [args]\n"
-             "\n"
-             "Build the Pygame dependencies.  The args, if given, are\n"
-             "libraries to include or exclude.\n"
-             "\n"
-             "At startup this program may prompt for missing information.\n"
-             "Be aware of this before redirecting output or leaving the\n"
-             "program unattended. Once the 'Starting build' message appears\n"
-             "no more user input is required.\n"
-             "\n"
-             "Note that MSYS is unstable. A full build may hang.\n"
-             "See --include and --help-args. Warning, the build\n"
-             "will take awhile."
-             "\n"
-             "See the doc string at the beginning of the program for more details\n")
-    
-    parser = OptionParser(usage)
-    parser.add_option('-a', '--all', action='store_true', dest='build_all',
-                      help="Include all libraries in the build")
-    parser.set_defaults(build_all=False)
-    parser.add_option('-p', '--prepare-mingw', action='store_true', dest='prepare_mingw',
-                      help="Make necessary changes to the MinGW specs file.\n"
-                           "This only needs to be run once")
-    parser.set_defaults(prepare_mingw=False)
-    parser.add_option('--no-msvcr71', action='store_true', dest='msvcrt',
-                      help="Do not link to msvcr71.dll; see --prepare-mingw")
-    parser.set_defaults(msvcrt=False)
-    parser.add_option('--no-configure', action='store_false', dest='configure',
-                      help="Do not prepare the makefiles")
-    parser.set_defaults(configure=True)
-    parser.add_option('--no-compile', action='store_false', dest='compile',
-                      help="Do not compile or install the libraries")
-    parser.set_defaults(compile=True)
-    parser.add_option('--no-install', action='store_false', dest='install',
-                      help="Do not install the libraries")
-    parser.set_defaults(install=True)
-    parser.add_option('--clean', action='store_true', dest='clean',
-                      help="Remove generated files (make clean) as a last step")
-    parser.set_defaults(clean=False)
-    parser.add_option('--clean-only', action='store_true', dest='clean_only',
-                      help="Perform only a clean")
-    parser.set_defaults(clean_only=False)
-    parser.add_option('-e', '--exclude', action='store_true', dest='exclude',
-                      help="Exclude the specified libraries")
-    parser.set_defaults(exclude=False)
-    parser.add_option('-m', '--msys-root', action='store', dest='msys_directory',
-                      help="MSYS root directory path (which includes the 1.x subdirectory)")
-    parser.set_defaults(msys_directory='')
-    parser.add_option('-g', '--mingw-root', action='store', dest='mingw_directory',
-                      help="MinGW root directory path")
-    parser.set_defaults(mingw_directory='')
-    parser.add_option('--help-args', action='store_true', dest='arg_help',
-                      help="Show a list of recognised libraries, in build order, and exit")
-    parser.set_defaults(arg_help=False)
-    options, args = parser.parse_args()
-    if options.arg_help:
-        print "These are the Pygame library dependencies:"
-        for n in names:
-            print " ", n
-        return 0
-    if options.build_all:
-        if args:
-            print "No library names are accepted for the --all option."
-            return 1
-        if options.exclude:
-            print "All libraries excluded"
-            deps = []
-        else:
-            deps = dependencies
-    elif args:
-        args = [a.upper() for a in args]
-        for a in args:
-            if a not in names:
-                print "%s is an unknown library; valid choices are:" % a
-                for n in names:
-                    print " ", n
-                return 1
-        if options.exclude:
-            deps = [d for d in dependencies if d.name not in args]
-        else:
-            deps = [d for d in dependencies if d.name in args]
-    else:
-        print "No libraries specified."
-        deps = []
-    if options.prepare_mingw:
-        deps.insert(0, mingw_preparation)
-    if deps:
-        deps.insert(0, msys_preparation)
-
-    init(options.msys_directory, options.mingw_directory)
-    configure(deps)
-    print "=== Starting build ==="
-    sys.stdout.flush()  # Make sure everything is displayed before scripts start.
-    os.environ['BDCONF'] = as_flag(options.configure and not options.clean_only)
-    os.environ['BDCOMP'] = as_flag(options.compile and not options.clean_only)
-    os.environ['BDINST'] = as_flag(options.install and options.compile and not options.clean_only)
-    os.environ['BDCLEAN'] = as_flag(options.clean or options.clean_only)
-    os.environ['BDMSVCR71'] = as_flag(not options.msvcrt)
-    start_time = time.time()
-    results = build(deps)
-    print "\n\n=== Summary ==="
-    for name, result in results:
-        if result is not None and not result:
-            print "  %s reported errors" % name
-    bin_dir = os.path.join(msys_root, 'local', 'bin')
-    print
-    for d in dependencies:
-        name = d.name
-        dlls = d.dlls
-        for dll in dlls:
-            dll_path = os.path.join(bin_dir, dll)
-            try:
-                mod_time = os.path.getmtime(dll_path)
-            except:
-                msg = "No DLL"
-            else:
-                if mod_time >= start_time:
-                    msg = "Installed new DLL %s" % dll_path
-                else:
-                    msg = "-- (old DLL %s)" % dll_path
-            print "  %-10s: %s" % (name, msg)
-    
-#
-#   Build specific code
-#
-
-# This list includes the MSYS shell scripts to build each library.
-# Each script runs in an environment where MINGW_ROOT_DIRECTORY is
-# defined and the MinGW bin directory is in PATH. Three other environment
-# variables are defined: BDCONF, BDCOMP, BDINST and BDCLEAN. They are either
-# '0' or '1'. They represent configure, compile, install and clean respectively.
-# When '1' the corresponding action is performed. When '0' it is skipped.
-# A final variable, DBWD, is the root directory of the source code. A
-# script will cd to it before doing anything else.
-# 
-# The list order corresponds to build order. It is critical.
-dependencies = [
-    Dependency('SDL', ['SDL-[1-9].*'], ['SDL.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # Remove NONAMELESSUNION from directx.h headers.
-  for d in video audio; do
-    BDDXHDR=src/$d/windx5/directx.h
-    cp -f $BDDXHDR $BDDXHDR'_'
-    sed 's/^\\(\\#define NONAMELESSUNION\\)/\\/*\\1*\\//' $BDDXHDR'_' >$BDDXHDR
-    if [ x$? != x0 ]; then exit $?; fi
-    rm $BDDXHDR'_'
-    BDDXHDR=
-  done
-  BDDXHDR=
-
-  # Build and install as win32 gui.
-  export LDFLAGS='-mwindows'
-  ./configure
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('Z', ['zlib-[1-9].*'], ['zlib1.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # Use the existing gcc makefile, modified to build a win32 gui.
-  sed 's/dllwrap/dllwrap -mwindows/' win32/Makefile.gcc >Makefile.gcc
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  # Build with the import library renamed.
-  make IMPLIB='libz.dll.a' -fMakefile.gcc
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  # Have to do own install.
-  cp -fp *.a /usr/local/lib
-  cp -fp zlib.h /usr/local/include
-  cp -fp zconf.h /usr/local/include
-  cp -fp zlib1.dll /usr/local/bin
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('FREETYPE', ['freetype-[2-9].*'], [], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # Will only install a static library, but that is all that is needed.
-  ./configure --disable-shared
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make
-  if [ ! -f objs/.libs/libfreetype.a ]; then exit 1; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('FONT', ['SDL_ttf-[2-9].*'], ['SDL_ttf.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  ./configure
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('PNG', ['libpng-[1-9].*'], ['libpng13.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # This will only build a static library.
-  ./configure --with-libpng-compat=no --disable-shared
-  if [ x$? != x0 ]; then exit $?; fi
-
-  # Remove a duplicate entry in the def file.
-  sed '222 d' scripts/pngw32.def >in.def
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  # Build the DLL as win32 gui.
-  make
-  if [ x$? != x0 ]; then exit $?; fi
-  dlltool -D libpng13.dll -d in.def -l libpng.dll.a
-  if [ x$? != x0 ]; then exit $?; fi
-  ranlib libpng.dll.a
-  if [ x$? != x0 ]; then exit $?; fi
-  gcc -shared -s -mwindows -def in.def -o libpng13.dll .libs/libpng12.a -lz
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  # Only install the headers and import library, otherwise SDL_image will
-  # statically link to png.
-  make install-pkgincludeHEADERS
-  cp -fp png.h /usr/local/include
-  cp -fp pngconf.h /usr/local/include
-  cp -fp libpng.dll.a /usr/local/lib
-  cp -fp libpng13.dll /usr/local/bin
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-  rm -f in.def
-  rm -f libpng.dll.a
-  rm -f libpng13.dll
-fi
-"""),
-    Dependency('JPEG', ['jpeg-[6-9]*'], ['jpeg.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # This will only build a static library.
-  ./configure --disable-shared
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  # Build the DLL as a win32 gui.
-  make CFLAGS='-O2'
-  if [ x$? != x0 ]; then exit $?; fi
-  dlltool --export-all-symbols -D jpeg.dll -l libjpeg.dll.a -z in.def libjpeg.a
-  if [ x$? != x0 ]; then exit $?; fi
-  ranlib libjpeg.dll.a
-  if [ x$? != x0 ]; then exit $?; fi
-  gcc -shared -s -mwindows -def in.def -o jpeg.dll libjpeg.a
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  # Only install the headers and import library, otherwise SDL_image will
-  # statically link to jpeg.
-  make install-headers
-  cp -fp libjpeg.dll.a /usr/local/lib
-  cp -fp jpeg.dll /usr/local/bin
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-  rm -f in.def
-  rm -f libjpeg.dll.a
-  rm -f jpeg.dll
-fi
-"""),
-    Dependency('TIFF', ['tiff-[3-9].*'], ['libtiff.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # The shared library build does not work
-  ./configure --disable-cxx --prefix=/usr/local --disable-shared
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make
-  if [ x$? != x0 ]; then exit $?; fi
-
-  # Build the DLL as a win32 gui
-  cd libtiff
-  gcc -shared -s -mwindows -def libtiff.def -o libtiff.dll .libs/libtiff.a -ljpeg -lz
-  if [ x$? != x0 ]; then exit $?; fi
-  dlltool -D libtiff.dll -d libtiff.def -l libtiff.dll.a
-  if [ x$? != x0 ]; then exit $?; fi
-  ranlib libtiff.dll.a
-  if [ x$? != x0 ]; then exit $?; fi
-  cd ..
-fi
-
-if [ x$BDINST == x1 ]; then
-  # Only install the headers and import library, otherwise SDL_image will
-  # statically link to jpeg.
-  cd libtiff
-  make install-data-am
-  cp -fp libtiff.dll.a /usr/local/lib
-  cp -fp libtiff.dll /usr/local/bin
-  if [ x$? != x0 ]; then exit $?; fi
-  cd ..
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-  rm -f libtiff/libtiff.dll.a
-  rm -f libtiff/libtiff.dll
-fi
-"""),
-    Dependency('IMAGE', ['SDL_image-[1-9].*'], ['SDL_image.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # Disable dynamic loading of image libraries as that uses wrong DLL search path
-  ./configure --disable-jpeg-shared --disable-png-shared --disable-tif-shared
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('SMPEG', ['smpeg-[0-9].*', 'smpeg'], ['smpeg.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # This comes straight from SVN so has no configure script
-  ./autogen.sh
-  if [ x$? != x0 ]; then exit $?; fi
-  ./configure --disable-gtk-player --disable-opengl-player --disable-gtktest
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make CXXLD='$(CXX) -no-undefined'
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('OGG', ['libogg-[1-9].*'], ['libogg-0.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # Build as win32 gui.
-  export LDFLAGS='-mwindows'
-  ./configure
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('VORBIS', ['libvorbis-[1-9].*'], ['libvorbis-0.dll', 'libvorbisfile-3.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # Build as win32 gui.
-  export LDFLAGS='-mwindows'
-  ./configure
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make LIBS='-logg'
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    Dependency('MIXER', ['SDL_mixer-[1-9].*'], ['SDL_mixer.dll'], """
-
-cd $BDWD
-
-if [ x$BDCONF == x1 ]; then
-  # Remove INCLUDE or configure script fails
-  export INCLUDE=''
-  ./configure --disable-music-ogg-shared --disable-music-mp3-shared
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCOMP == x1 ]; then
-  make
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDINST == x1 ]; then
-  make install
-  if [ x$? != x0 ]; then exit $?; fi
-fi
-
-if [ x$BDCLEAN == x1 ]; then
-  make clean
-fi
-"""),
-    ]  # End dependencies = [.
-
-
-msys_prep = Preparation('/usr/local', """
-
-# Ensure /usr/local and its subdirectories exist.
-mkdir -p /usr/local/lib
-mkdir -p /usr/local/include
-mkdir -p /usr/local/bin
-mkdir -p /usr/local/doc
-mkdir -p /usr/local/man
-mkdir -p /usr/local/share
-""")
-    
-mingw_prep = Preparation('MinGW Preparation', r"""
-
-set -e
-
-#
-#   msvcr71.dll support
-#
-if [ x$BDMSVCR71 == x1 ]; then
-  OBJS='isascii.o iscsym.o iscsymf.o toascii.o
-        strcasecmp.o strncasecmp.o wcscmpi.o'
-  if [ ! -d /tmp/build_deps ]; then mkdir /tmp/build_deps; fi
-  cd /tmp/build_deps
-  # These definitions are taken from mingw-runtime-3.12 .
-  # The file was generated with the following command:
-  #
-  # gcc -DRUNTIME=msvcrt -D__FILENAME__=moldname-msvcrt.def
-  #   -D__MSVCRT__ -C -E -P -xc-header moldname.def.in >moldname-msvcrt.def
-  cat > moldname-msvcrt.def << 'THE_END'
-EXPORTS
-access
-chdir
-chmod
-chsize
-close
-creat
-cwait
-
-daylight DATA
-
-dup
-dup2
-ecvt
-eof
-execl
-execle
-execlp
-execlpe
-execv
-execve
-execvp
-execvpe
-fcvt
-fdopen
-fgetchar
-fgetwchar
-filelength
-fileno
-; Alias fpreset is set in CRT_fp10,c and CRT_fp8.c.
-; fpreset
-fputchar
-fputwchar
-fstat
-ftime
-gcvt
-getch
-getche
-getcwd
-getpid
-getw
-heapwalk
-isatty
-itoa
-kbhit
-lfind
-lsearch
-lseek
-ltoa
-memccpy
-memicmp
-mkdir
-mktemp
-open
-pclose
-popen
-putch
-putenv
-putw
-read
-rmdir
-rmtmp
-searchenv
-setmode
-sopen
-spawnl
-spawnle
-spawnlp
-spawnlpe
-spawnv
-spawnve
-spawnvp
-spawnvpe
-stat
-strcmpi
-strdup
-stricmp
-stricoll
-strlwr
-strnicmp
-strnset
-strrev
-strset
-strupr
-swab
-tell
-tempnam
-
-timezone DATA
-
-; export tzname for both. See <time.h>
-tzname DATA
-tzset
-umask
-ungetch
-unlink
-utime
-wcsdup
-wcsicmp
-wcsicoll
-wcslwr
-wcsnicmp
-wcsnset
-wcsrev
-wcsset
-wcsupr
-
-wpopen
-
-write
-; non-ANSI functions declared in math.h
-j0
-j1
-jn
-y0
-y1
-yn
-chgsign
-scalb
-finite
-fpclass
-; C99 functions
-cabs
-hypot
-logb
-nextafter
-THE_END
-
-  ar x /mingw/lib/libmoldname.a $OBJS
-  dlltool --as as -k -U \
-     --dllname msvcr71.dll \
-     --def moldname-msvcrt.def \
-     --output-lib libmoldname71.a
-  ar rc libmoldname71.a $OBJS
-  ranlib libmoldname71.a
-  cp -fp libmoldname71.a /mingw/lib
-  # In the default libraries list replace msvcrt libs with msvcrt71 versions
-  SEDOPTSVC="
-    s/-lmsvcrt/-lmsvcr71/;
-    s/-lmoldname /-lmoldname71 /;"
-  rm -f ./*
-  cd $OLDPWD
-  rmdir /tmp/build_deps
-else
-  SEDOPTSVC=
-fi
-
-#
-#   Add /usr/local to MinGW searches.
-#
-# Get /usr/local as a Windows path with Unix path separators
-# and properly escaped for sed
-LOCALDIR=`python -c 'import sys; print sys.argv[1].replace("/", "\/")' /usr/local`
-# Append include and library paths to the appropriate gcc options lists
-SEDOPTSLD="
-  /^\*cpp:\$/{N; s/\(.\)$/\1 -I$LOCALDIR\/include/;};
-  /^\*link:\$/{N; s/\(.\)$/\1 -L$LOCALDIR\/lib/;};"
-
-#
-#   Modify the specs file
-#
-GCCVER=`gcc -dumpversion`
-SPECDIR="/mingw/lib/gcc/mingw32/$GCCVER"
-
-# Make a backup if one does not exist, else restore specs.
-if [ ! -f $SPECDIR/specs-original ]; then
-  cp $SPECDIR/specs $SPECDIR/specs-original
-else
-  cp $SPECDIR/specs-original $SPECDIR/specs
-fi
-
-SEDOPTS="$SEDOPTSLD $SEDOPTSVC"
-SEDADDCR=$'s/\\(.*\\)$/\\1\r/;'
-tr -d \\r <$SPECDIR/specs-original | sed "$SEDOPTS" | sed "$SEDADDCR" >$SPECDIR/specs
-""")
-
-if __name__ == '__main__':
-    sys.exit(main(dependencies, mingw_prep, msys_prep))
 #!/usr/bin/env python
+# For MinGW build requires Python 2.4 or better and win32api.
 
 """Quick tool to help setup the needed paths and flags
 in your Setup file. This will call the appropriate sub-config
 cflags: extra compile flags
 """
 
+import msysio
+import mingwcfg
 import sys, os, shutil, string
 
-def is_msys_mingw():
-    if os.environ.has_key("MSYSTEM"):
-        if os.environ["MSYSTEM"] == "MINGW32":
-            return 1
-    return 0
-
-if sys.platform == 'win32' and not is_msys_mingw():
-    print 'Using WINDOWS configuration...\n'
-    import config_win
-    CFG = config_win
-elif sys.platform == 'win32' and is_msys_mingw():
-    print 'Using WINDOWS mingw/msys configuration...\n'
-    import config_msys
-    CFG = config_msys
-elif sys.platform == 'darwin':
-    print 'Using Darwin configuration...\n'
-    import config_darwin
-    CFG = config_darwin
-else:
-    print 'Using UNIX configuration...\n'
-    import config_unix
-    CFG = config_unix
+def print_(*args, **kwds):
+    """Simular to the Python 3.0 print function"""
+    # This not only supports MSYS but is also a head start on the move to
+    # Python 3.0. Also, this function can be overridden for testing.
+    msysio.print_(*args, **kwds)
 
 def confirm(message):
     "ask a yes/no question, return result"
-    #The output must be flushed for the prompt to be visible on MSYS bash
-    sys.stdout.write("\n%s [Y/n]:" % message)
-    sys.stdout.flush()
-    reply = raw_input()
+    reply = msysio.raw_input_("\n%s [Y/n]:" % message)
     if reply and string.lower(reply[0]) == 'n':
         return 0
     return 1
 
+def is_msys_mingw():
+    """Return true if this in an MinGW/MSYS build
+
+    The user may prompted for confirmation so only call this function
+    once.
+    """
+    if msysio.is_msys():
+        return 1
+    if ('MINGW_ROOT_DIRECTORY' in os.environ or
+        os.path.isfile(mingwcfg.path)):
+        return confirm("Is this an mingw/msys build")
+    return 0
+
 def prepdep(dep, basepath):
     "add some vars to a dep"
     if dep.libs:
             newsetup.write(line)
 
 def main():
+    if sys.platform == 'win32' and not is_msys_mingw():
+        print_('Using WINDOWS configuration...\n')
+        import config_win as CFG
+    elif sys.platform == 'win32':
+        print_('Using WINDOWS mingw/msys configuration...\n')
+        import config_msys as CFG
+    elif sys.platform == 'darwin':
+        print_('Using Darwin configuration...\n')
+        import config_darwin as CFG
+    else:
+        print_('Using UNIX configuration...\n')
+        import config_unix as CFG
+    
     if os.path.isfile('Setup'):
         if "-auto" in sys.argv or confirm('Backup existing "Setup" file'):
             shutil.copyfile('Setup', 'Setup.bak')
         for d in deps:
             prepdep(d, basepath)
         writesetupfile(deps, basepath)
-        print """\nIf you get compiler errors during install, doublecheck
-the compiler flags in the "Setup" file.\n"""
+        print_("""\nIf you get compiler errors during install, doublecheck
+the compiler flags in the "Setup" file.\n""")
     else:
-        print """\nThere was an error creating the Setup file, check for errors
-or make a copy of "Setup.in" and edit by hand."""
+        print_("""\nThere was an error creating the Setup file, check for errors
+or make a copy of "Setup.in" and edit by hand.""")
 
 if __name__ == '__main__': main()
+# Requires Python 2.4 or better and win32api.
+
 """Config on Msys mingw"""
 
 import dll
+import msys
 import os, sys, string
-import subprocess
-import re
 from glob import glob
 from distutils.sysconfig import get_python_inc
-import itertools
 
 configcommand = os.environ.get('SDL_CONFIG', 'sdl-config',)
 configcommand = configcommand + ' --version --cflags --libs'
 class ConfigError(Exception):
     pass
 
-msys_root = os.path.split(os.path.split(os.environ['SHELL'])[0])[0]  # Get msys root directory
-try:
-    mingw_root = os.environ['MINGW_ROOT_DIRECTORY']
-except KeyError:
-    pass
-check_for_drive = re.compile('/[A-Z]/', re.I).match
-def has_drive(path):
-    return check_for_drive(path) is not None
-
-class ConvertionError(ConfigError):
-    pass
-
-def msys_to_windows(path):
-    """Return a Windows translation of an MSYS path
-
-    The Unix path separator is uses as it survives the distutils setup file read process.
-    """
-    if path.startswith('/usr'):
-        path =  msys_root + path[4:]
-    elif path.startswith('/mingw'):
-        try:
-            path =  mingw_root + path[6:]
-        except NameError:
-            raise ConversionError('MINGW_ROOT_DIRECTORY environment variable undefined')
-    elif has_drive(path):
-        path =  path[1] + ":" + path[2:]
-    elif path == '/':
-        path = msys_root
-    elif path.startswith('/'):
-        path =  msys_root + path
-    return path.replace(os.sep, '/')
-
 def path_join(a, *p):
     return os.path.join(a, *p).replace(os.sep, '/')
 path_split = os.path.split
 
+def print_(*args, **kwds):
+    return msys.msys_print(*args, **kwds)
+
 def confirm(message):
     "ask a yes/no question, return result"
-    #The output must be flushed for the prompt to be visible on MSYS bash
-    sys.stdout.write("\n%s [Y/n]:" % message)
-    sys.stdout.flush()
-    reply = raw_input()
+    reply = msys.msys_raw_input("\n%s [Y/n]:" % message)
     if reply and string.lower(reply[0]) == 'n':
         return 0
     return 1
 
 class DependencyProg:
     needs_dll = True
-    def __init__(self, name, envname, exename, minver, defaultlibs=None):
+    def __init__(self, name, envname, exename, minver, msys, defaultlibs=None):
         if defaultlibs is None:
             defaultlibs = [dll.name_to_root(name)]
         self.name = name
-        command = os.environ.get(envname, exename)
-        drv, pth = os.path.splitdrive(command)
-        if drv:
-            command = '/' + drv[0] + pth
-        shell = os.environ['SHELL']
+        try:
+            command = os.environ[envname]
+        except KeyError:
+            command = exename
+        else:
+            drv, pth = os.path.splitdrive(command)
+            if drv:
+                command = '/' + drv[0] + pth
         self.lib_dir = ''
         self.inc_dir = ''
         self.libs = []
         self.cflags = ''
         try:
-            config = subprocess.Popen([shell, command, '--version', '--cflags', '--libs'],
-                                      stdout=subprocess.PIPE
-                                      ).communicate()[0]
+            config = msys.run_shell_command([command, '--version', '--cflags', '--libs'])
             ver, flags = config.split('\n', 1)
             self.ver = ver.strip()
             flags = flags.split()
             self.cflags = ''
             for f in flags:
                 if f[:2] in ('-I', '-L'):
-                    self.cflags += f[:2] + msys_to_windows(f[2:]) + ' '
+                    self.cflags += f[:2] + msys.msys_to_windows(f[2:]) + ' '
                 elif f[:2] in ('-l', '-D'):
                     self.cflags += f + ' '
                 elif f[:3] == '-Wl':
                     self.cflags += '-Xlinker ' + f + ' '
         except:
-            print 'WARNING: "%s" failed!' % command    
+            print_('WARNING: "%s" failed!' % command)
             self.found = 0
             self.ver = '0'
             self.libs = defaultlibs
 
     def configure(self, incdirs, libdir):
         if self.found:
-            print self.name + '        '[len(self.name):] + ': found ' + self.ver
+            print_(self.name + '        '[len(self.name):] + ': found ' + self.ver)
             self.found = 1
         else:
-            print self.name + '        '[len(self.name):] + ': not found'
+            print_(self.name + '        '[len(self.name):] + ': not found')
 
 class Dependency:
     needs_dll = True
         self.find_lib_dir(libdirs)
         
         if self.lib_dir and self.inc_dir:
-            print self.name + '        '[len(self.name):] + ': found'
+            print_(self.name + '        '[len(self.name):] + ': found')
             self.found = 1
         else:
-            print self.name + '        '[len(self.name):] + ': not found'
+            print_(self.name + '        '[len(self.name):] + ': not found')
 
     def find_inc_dir(self, incdirs):
         incname = self.checkhead
             else:
                 self.inc_dir = os.path.split(fullpath)[0]
         if self.found:
-            print self.name + '        '[len(self.name):] + ': found', self.ver
+            print_(self.name + '        '[len(self.name):] + ': found', self.ver)
         else:
-            print self.name + '        '[len(self.name):] + ': not found'
+            print_(self.name + '        '[len(self.name):] + ': not found')
 
 class DependencyWin:
     needs_dll = False
 
 
 def main():
-    print '\nHunting dependencies...'
+    m = msys.Msys(require_mingw=False)
+    print_('\nHunting dependencies...')
     DEPS = [
-        DependencyProg('SDL', 'SDL_CONFIG', 'sdl-config', '1.2'),
+        DependencyProg('SDL', 'SDL_CONFIG', 'sdl-config', '1.2', m),
         Dependency('FONT', 'SDL_ttf.h', 'libSDL_ttf.dll.a'),
         Dependency('IMAGE', 'SDL_image.h', 'libSDL_image.dll.a'),
         Dependency('MIXER', 'SDL_mixer.h', 'libSDL_mixer.dll.a'),
-        DependencyProg('SMPEG', 'SMPEG_CONFIG', 'smpeg-config', '0.4.3'),
+        DependencyProg('SMPEG', 'SMPEG_CONFIG', 'smpeg-config', '0.4.3', m),
         Dependency('PNG', 'png.h', 'libpng.dll.a'),
         Dependency('JPEG', 'jpeglib.h', 'libjpeg.dll.a'),
         DependencyWin('SCRAP', ['user32', 'gdi32']),
     ]
 
     if not DEPS[0].found:
-        print 'Unable to run "sdl-config". Please make sure a development version of SDL is installed.'
-        raise SystemExit
+        print_('Unable to run "sdl-config". Please make sure a development version of SDL is installed.')
+        sys.exit(1)
 
     if localbase:
         incdirs = [localbase+d for d in origincdirs]
     else:
         incdirs = []
         libdirs = []
-    incdirs += [msys_to_windows("/usr/local"+d) for d in origincdirs]
-    libdirs += [msys_to_windows("/usr/local"+d) for d in origlibdirs]
-    incdirs += [msys_to_windows("/mingw"+d) for d in origincdirs]
-    libdirs += [msys_to_windows("/mingw"+d) for d in origlibdirs]
+    incdirs += [m.msys_to_windows("/usr/local"+d) for d in origincdirs]
+    libdirs += [m.msys_to_windows("/usr/local"+d) for d in origlibdirs]
+    if m.mingw_root is not None:
+        incdirs += [m.msys_to_windows("/mingw"+d) for d in origincdirs]
+        libdirs += [m.msys_to_windows("/mingw"+d) for d in origlibdirs]
     for arg in string.split(DEPS[0].cflags):
         if arg[:2] == '-I':
             incdirs.append(arg[2:])
     for d in DEPS:
         if isinstance(d, DependencyDLL):
             if d.lib_dir == '':
-                print "DLL for %-12s: not found" % d.lib_name
+                print_("DLL for %-12s: not found" % d.lib_name)
             else:
-                print "DLL for %-12s: %s" % (d.lib_name, d.lib_dir)
+                print_("DLL for %-12s: %s" % (d.lib_name, d.lib_dir))
     
     for d in DEPS[1:]:
         if not d.found:
     return DEPS
 
 if __name__ == '__main__':
-    print """This is the configuration subscript for MSYS.
-Please run "config.py" for full configuration."""
+    print_("""This is the configuration subscript for MSYS.
+Please run "config.py" for full configuration.""")
 
     for line in open('Setup.in').readlines():
         if line[:3] == '#--': continue
         if line[:6] == 'SDL = ':
-            line = 'SDL = -Iprebuilt/include -Lprebuilt/lib -lSDL\n'
+            line = 'SDL = -Iprebuilt/include -Iprebuilt/include/SDL -Lprebuilt/lib -lSDL\n'
+        if line[:8] == 'SMPEG = ':
+            line = 'SMPEG = -Iprebuilt/include/smpeg -lsmpeg\n'
         if line[:8] == 'SCRAP = ':
             line = 'SCRAP = -luser32 -lgdi32\n'
         setup.write(line)

configtest/readme.txt

 must be a subdirectory of the python scripts they test.
 
 test_congif_msys.py:
-    Test dependency search. Requires testdir directory. Must
-    be run from the MSYS console.
-
-test_config_msys_i.py:
-    Internals test. Check MSYS to path conversion.
+    Test dependency search. Requires testdir directory.
 
 test_config_win.py
     Test dependency search. Requires testdir directroy.
 test_dll.py
     Test the shared DLL information.
 
+test_msys.py
+    Test the MSYS support module. Verifies that path name converion
+    and MSYS bash shell can be calls. Requires MSYS to be installed
+    and configured for MinGW.
 

configtest/test_config_msys.py

 # program test_config_msys.py
 
-"""Test config_msys.py for against a dummy directory structure.
+"""Test config_msys.py against a dummy directory structure.
 
 This test must be performed on an MSYS console.
 """
 import os.path
 import sys
 
-# Ensure the execution environment is correct
-if not ("MSYSTEM" in os.environ and os.environ["MSYSTEM"] == "MINGW32"):  # cond. and
-    print "This test must be run from an MSYS console."
-    sys.exit(1)
-
 test_dir = './testdir'
 if not os.path.isdir(test_dir):
     print "Test directory %s not found." % test_dir

configtest/test_config_msys_i.py

-# test_config_msys_i.py program
-
-"""Unit test of config_msys.py internals
-
-This test need not be run from an MSYS console.
-"""
-
-import os
-import sys
-
-msys_root_directory = 'C:/_msys_/1.0'
-mingw_root_directory = 'C:/_mingw_'
-os.environ['SHELL'] = os.path.join(msys_root_directory, 'bin', 'sh.exe')
-os.environ['MINGW_ROOT_DIRECTORY'] = mingw_root_directory
-
-sys.path.append('..')
-
-import config_msys
-
-import unittest
-
-def join_path(b, *p):
-    return os.path.join(b, *p).replace(os.sep, '/')
-
-class PathsTestCase(unittest.TestCase):
-    """Test congif_msys.msys_to_windows"""
-    some_file_name = 'foo.txt'
-
-    def test_path_usr(self):
-        """Ensure /usr translates"""
-        self.failUnlessEqual(config_msys.msys_to_windows('/usr'), msys_root_directory)
-
-    def test_path_usr_somefile(self):
-        """Ensure /usr/..... translates"""
-        msys_path = '/usr/%s' % self.some_file_name
-        win_path = join_path(msys_root_directory, self.some_file_name)
-        self.failUnlessEqual(config_msys.msys_to_windows(msys_path), win_path)
-
-    def test_path_mingw(self):
-        """Ensure /mingw translates"""
-        self.failUnlessEqual(config_msys.msys_to_windows('/mingw'), mingw_root_directory)
-
-    def test_path_mingw_something(self):
-        """Ensure /mingw/.... translates"""
-        msys_path = '/mingw/%s' % self.some_file_name
-        win_path = join_path(mingw_root_directory, self.some_file_name)
-        self.failUnlessEqual(config_msys.msys_to_windows(msys_path), win_path)
-
-    def test_path_root(self):
-        """Ensure / translates"""
-        self.failUnlessEqual(config_msys.msys_to_windows('/'), msys_root_directory)
-
-    def test_path_root_something(self):
-        """Ensure /.... translates"""
-        msys_path = '/%s' % self.some_file_name
-        win_path = join_path(msys_root_directory, self.some_file_name)
-        self.failUnlessEqual(config_msys.msys_to_windows(msys_path), win_path)
-
-    def test_drive_letter_absolute(self):
-        """Ensure x:/.... translates"""
-        for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
-            msys_path = '/%s/%s' % (d, self.some_file_name)
-            win_path = '%s:/%s' % (d, self.some_file_name)
-            self.failUnlessEqual(config_msys.msys_to_windows(msys_path), win_path)
-
-    def test_drive_letter_relative(self):
-        """Ensure x:.... translates"""
-        for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
-            msys_path = '%s:dir/%s' % (d, self.some_file_name)
-            win_path = join_path('%s:' % d, 'dir', self.some_file_name)
-            self.failUnlessEqual(config_msys.msys_to_windows(msys_path), win_path)
-
-    def test_path_relative(self):
-        """Ensure relative paths translate"""
-        msys_path = './dir/%s' % self.some_file_name
-        win_path = join_path('.', 'dir', self.some_file_name)
-        self.failUnlessEqual(config_msys.msys_to_windows(msys_path), win_path)
-
-if __name__ == '__main__':
-    unittest.main()

configtest/test_msys.py

+# program test_msys.py
+
+"""Test msys.py.
+
+This test requires that MSYS is installed and configured for MinGW."""
+
+import sys
+sys.path.append('..')
+
+import msys
+
+import unittest
+import re
+import os
+
+has_drive = msys.has_drive
+
+class FstabRegexTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.pattern = re.compile(msys.FSTAB_REGEX, re.MULTILINE)
+        
+    def test_firstline(self):
+        """Ensure first line is checked"""
+        fstab = ('c:/xxx /mingw\n'
+                 'c:/foobar /msys\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:/xxx')
+
+    def test_middleline(self):
+        """Ensure a middle line is checked"""
+        fstab = ('c:/xxx /msys\n'
+                 'c:/foobar /mingw\n'
+                 'c:/yyy /something\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:/foobar')
+
+    def test_lastline(self):
+        """Ensure the last line is checked"""
+        fstab = ('c:/xxx /msys\n'
+                 'c:/foobar /whatever\n'
+                 'c:/yyy /mingw\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:/yyy')
+
+    def test_notfound(self):
+        """Ensure no match when /mingw is missing"""
+        fstab = ('c:/xxx /msys\n'
+                 'c:/foobar /whatever\n'
+                 'c:/yyy /something\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is None)
+
+    def test_extra(self):
+        """Ensure no match for something like /mingwx"""
+        fstab = 'c:/xxx /mingwx\n'
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is None)
+
+    def test_extra_entries(self):
+        """Ensure extra entries are allowed on the line"""
+        fstab = 'c:/xxx /mingw x'
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:/xxx')
+
+    def test_crlf(self):
+        """Ensure \\r\\n line endings are handled"""
+        fstab = ('c:/xxx /msys\r\n'
+                 'c:/foobar /mingw\r\n'
+                 'c:/yyy /something\r\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:/foobar')
+
+    def test_leading_space(self):
+        """Ensure leading space is ignored"""
+        fstabs = [' c:/xxx /mingw\n', '  c:/xxx /mingw\n', '\tc:/xxx /mingw\n']
+        for fstab in fstabs:
+            ma = self.pattern.search(fstab)
+            self.failUnless(ma is not None)
+            self.failUnlessEqual(ma.groupdict()['path'], 'c:/xxx')
+
+    def test_multiple_spaces(self):
+        """Ensure multiple spaces are ingored"""
+        fstabs = ['c:/foobar  /mingw\n', 'c:/foobar   /mingw\n', 'c:/foobar\t /mingw\n']
+        for fstab in fstabs:
+            ma = self.pattern.search(fstab)
+            self.failUnless(ma is not None)
+            self.failUnlessEqual(ma.groupdict()['path'], 'c:/foobar')
+
+    def test_multi_element_path(self):
+        """Ensure a multi-element path is recognized"""
+        fstab = ('c:/xxx /msys\n'
+                 'c:/foo/bar /mingw\n'
+                 'c:/yyy /something\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:/foo/bar')
+
+    def test_backslash(self):
+        """Ensure the backslashes is recognized as a path separator"""
+        fstab = ('c:\\xxx /msys\n'
+                 'c:\\foobar /mingw\n'
+                 'c:\\yyy /something\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:\\foobar')
+
+    def test_upper_case(self):
+        """Ensure upper case letters are accepted"""
+        fstab = ('c:/xxx /msys\n'
+                 'C:/FOOBAR /mingw\n'
+                 'c:/yyy /something\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'C:/FOOBAR')
+
+    def test_non_letters(self):
+        """Ensure non-letter characters are accepted"""
+        fstab = ('c:/xxx /msys\n'
+                 'c:/-57.2(_)/s /mingw\n'
+                 'c:/yyy /something')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], 'c:/-57.2(_)/s')
+
+    def test_no_drive_letter(self):
+        """Ensure a missing drive letter is accepted"""
+        fstab = ('c:/xxx /msys\n'
+                 '/foobar /mingw\n'
+                 'c:/yyy /something\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is not None)
+        self.failUnlessEqual(ma.groupdict()['path'], '/foobar')
+
+    def test_relative_path(self):
+        """Ensure a relative path is rejected"""
+        fstab = ('c:/xxx /msys\n'
+                 'c/foobar /mingw\n'
+                 'c:/yyy /something\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is None)
+
+    def test_invalid_characters(self):
+        """Ensure invalid characters are rejected"""
+        fstab = ('c:/*xxx /mingw\n'
+                 'c:/?foobar /mingw\n'
+                 'c:/%yyy /mingw\n'
+                 '_:/%zzz /mingw\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is None)
+
+    def test_drive_letters(self):
+        """Ensure drive letters a to z are accepted"""
+        for d in 'abcdefghijklmnopqrstuvwxyz':
+            path = '%s:/xxx' % d
+            fstab = '%s /mingw' % path
+            ma = self.pattern.search(fstab)
+            self.failUnless(ma is not None)
+            self.failUnlessEqual(ma.groupdict()['path'], path)
+            
+    def test_upper_case_drive_letters(self):
+        """Ensure drive letters A to Z are accepted"""
+        for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
+            path = '%s:/xxx' % d
+            fstab = '%s /mingw' % path
+            ma = self.pattern.search(fstab)
+            self.failUnless(ma is not None)
+            self.failUnlessEqual(ma.groupdict()['path'], path)
+            
+    def test_doubled_separators(self):
+        """Does the regular expression reject doubled path separators?"""
+        fstab = ('c:/\\xxx /mingw\n'
+                 'c://foobar /mingw\n'
+                 'c:\\\\yyy /mingw\n'
+                 'c:\\/zzz /mingw\n')
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is None)
+
+    def test_root_directory(self):
+        """Does the regular expression reject the root directory?"""
+        # This just documents a quirk of the regular expression
+        fstab = 'c:/ /mingw\n'
+        ma = self.pattern.search(fstab)
+        self.failUnless(ma is None)
+
+
+class MsysToWindowsTestCase(unittest.TestCase):
+    """Test Msys.msys_to_windows"""
+    some_file_name = 'foo.txt'
+
+    def setUp(self):
+        self.msys = msys.Msys()
+
+    def test_path_usr(self):
+        """Ensure /usr translates"""
+        self.failUnlessEqual(self.msys.msys_to_windows('/usr'),
+                             self.msys.msys_root.replace(os.sep, '/'))
+
+    def test_path_usr_somefile(self):
+        """Ensure /usr/..... translates"""
+        msys_path = '/usr/%s' % self.some_file_name
+        win_path = os.path.join(self.msys.msys_root, self.some_file_name).replace(os.sep, '/')
+        self.failUnlessEqual(self.msys.msys_to_windows(msys_path), win_path)
+
+    def test_path_mingw(self):
+        """Ensure /mingw translates"""
+        self.failUnlessEqual(self.msys.msys_to_windows('/mingw'),
+                             self.msys.mingw_root.replace(os.sep, '/'))
+
+    def test_path_mingw_something(self):
+        """Ensure /mingw/.... translates"""
+        msys_path = '/mingw/%s' % self.some_file_name
+        win_path = os.path.join(self.msys.mingw_root, self.some_file_name).replace(os.sep, '/')
+        self.failUnlessEqual(self.msys.msys_to_windows(msys_path), win_path)
+
+    def test_path_root(self):
+        """Ensure / translates"""
+        self.failUnlessEqual(self.msys.msys_to_windows('/'),
+                             self.msys.msys_root.replace(os.sep, '/'))
+
+    def test_path_root_something(self):
+        """Ensure /.... translates"""
+        msys_path = '/%s' % self.some_file_name
+        win_path = os.path.join(self.msys.msys_root, self.some_file_name).replace(os.sep, '/')
+        self.failUnlessEqual(self.msys.msys_to_windows(msys_path), win_path)
+
+    def test_drive_letter_absolute(self):
+        """Ensure x:/.... translates"""
+        for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
+            msys_path = '/%s/%s' % (d, self.some_file_name)
+            win_path = '%s:/%s' % (d, self.some_file_name)
+            self.failUnlessEqual(self.msys.msys_to_windows(msys_path), win_path)
+
+    def test_drive_letter_relative(self):
+        """Ensure x:.... translates"""
+        for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
+            msys_path = '%s:dir/%s' % (d, self.some_file_name)
+            win_path = os.path.join('%s:' % d, 'dir', self.some_file_name).replace(os.sep, '/')
+            self.failUnlessEqual(self.msys.msys_to_windows(msys_path), win_path)
+
+    def test_path_relative(self):
+        """Ensure relative paths translate"""
+        msys_path = './dir/%s' % self.some_file_name
+        win_path = os.path.join('.', 'dir', self.some_file_name).replace(os.sep, '/')
+        self.failUnlessEqual(self.msys.msys_to_windows(msys_path), win_path)
+
+
+class WindowsToMsysTestCase(unittest.TestCase):
+    """Test Msys.windows_to_msys"""
+
+    some_file_name = 'foo.txt'
+
+    def setUp(self):
+        self.msys = msys.Msys()
+
+    def test_path_root(self):
+        """Ensure MSYS directory maps to /usr"""
+        win_path = os.path.join(self.msys.msys_root, self.some_file_name)
+        msys_path = '/usr/%s' % self.some_file_name
+        self.failUnlessEqual(self.msys.windows_to_msys(win_path), msys_path)
+
+    def test_path_mingw(self):
+        """Ensure MinGW directory maps to /mingw"""
+        win_path = os.path.join(self.msys.mingw_root, self.some_file_name)
+        msys_path = '/mingw/%s' % self.some_file_name
+        self.failUnlessEqual(self.msys.windows_to_msys(win_path), msys_path)
+
+    def test_drive_letter(self):
+        """Ensure x:/.... translates"""
+        for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
+            win_path = '%s:\%s' % (d, self.some_file_name)
+            msys_path = '/%s/%s' % (d, self.some_file_name)
+            self.failUnlessEqual(self.msys.windows_to_msys(win_path), msys_path)
+
+    def test_foward_slashes(self):
+        """Ensure forward slashes in a Windows path are recognized"""
+        self.failUnlessEqual(self.msys.windows_to_msys('C:/one/two'), '/C/one/two')
+
+
+class ShellTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.msys = msys.Msys()
+        self.testscript_path = os.path.abspath('.\\testscript')
+        open(self.testscript_path, 'wb').write('echo $XXXYYYZZZ\n')
+        self.msys.environ['XXXYYYZZZ'] = 'passed'
+
+    def tearDown(self):
+        try:
+            os.remove(self.testscript_path)
+        except StandardError:
+            pass
+ 
+    def test_environment(self):
+        """Ensure MINGW_ROOT_DIRECTORY is set"""
+        self.failUnlessEqual(self.msys.environ['MINGW_ROOT_DIRECTORY'],
+                             self.msys.mingw_root)
+
+    def test_shell_script_return_value(self):
+        """Ensure run_shell_script returns the return value of the shell"""
+        self.failUnlessEqual(self.msys.run_shell_script('exit 0'), 0)
+        self.failUnlessEqual(self.msys.run_shell_script('exit 42'), 42)
+
+    def test_shell_script_environment(self):
+        """Ensure environment variables are passed to the shell"""
+        script = 'test x"$SOMETHING" == xsomevalue'
+        self.msys.environ['SOMETHING'] = 'somevalue'
+        working_dir = os.getcwd()
+        self.failUnlessEqual(self.msys.run_shell_script(script), 0)
+        self.failUnlessEqual(os.getcwd(), working_dir)
+
+    def test_shell_command(self):
+        """Ensure msys_shell_command works"""
+        cmd = self.msys.windows_to_msys(self.testscript_path)
+        working_dir = os.getcwd()
+        self.failUnlessEqual(self.msys.run_shell_command([cmd]).strip(), 'passed')
+        self.failUnlessEqual(os.getcwd(), working_dir)
+
+if __name__ == '__main__':
+    unittest.main()
 
 """DLL specifics"""
 
+# Some definitions:
+#   Library name (name): An internal identifier, a string, for a library.
+#       e.g. FONT
+#   Library file root (root): The library root used in linker -l options.
+#       e.g. SDL_mixer
+   
 import re
 
 # Table of dependencies.
-# Name, File root name, File regex, Dependency list of file root names
-dependencies = [
-    ('MIXER', 'SDL_mixer',  r'(lib){0,1}SDL_mixer\.dll$', ['SDL', 'vorbisfile', 'smpeg']),
-    ('VORBISFILE', 'vorbisfile',  r'(lib){0,1}vorbisfile(-3){0,1}\.dll$',  ['vorbis']),
+# name, root, File regex, Dependency list of file root names
+libraries = [
+    ('MIXER', 'SDL_mixer', r'(lib){0,1}SDL_mixer\.dll$',
+     ['SDL', 'vorbisfile', 'smpeg']),
+    ('VORBISFILE', 'vorbisfile',  r'(lib){0,1}vorbisfile(-3){0,1}\.dll$',
+     ['vorbis']),
     ('VORBIS', 'vorbis', r'(lib){0,1}vorbis(-0){0,1}\.dll$', ['ogg']),
     ('OGG', 'ogg', r'(lib){0,1}ogg(-0){0,1}\.dll$', []),
     ('SMPEG', 'smpeg', r'(lib){0,1}smpeg\.dll$', ['SDL']),
-    ('IMAGE', 'SDL_image', r'(lib){0,1}SDL_image\.dll$', ['SDL', 'jpeg', 'png', 'tiff']),
+    ('IMAGE', 'SDL_image', r'(lib){0,1}SDL_image\.dll$',
+     ['SDL', 'jpeg', 'png', 'tiff']),
     ('TIFF', 'tiff', r'(lib){0,1}tiff\.dll$',  ['jpeg', 'z']),
     ('JPEG', 'jpeg', r'(lib){0,1}jpeg\.dll$', []),
     ('PNG', 'png', r'(lib){0,1}png(1[23])\.dll$', ['z']),
     ('SDL', 'SDL', r'(lib){0,1}SDL\.dll$', [])
 ]
 
-# regexs: Maps name to regex.
-# lib_dependencies: Maps file root name to list of dependencies.
-# file_root_name: Maps name to root name.
+# regexs: Maps name to DLL file name regex.
+# lib_dependencies: Maps name to list of dependencies.
+# file_root_names: Maps name to root.
 
 regexs = {}
 lib_dependencies = {}
 file_root_names = {}
-for name, root, regex, deps in dependencies:
+for name, root, regex, deps in libraries:
     regexs[name] = regex
     lib_dependencies[root] = deps
     file_root_names[name] = root
+del name, root, regex, deps
 
 def tester(name):
-    def test(f):
-        return match(f) is not None
+    """For a library name return a function which tests dll file names"""
+    
+    def test(file_name):
+        """Return true if file name f is a valid DLL name"""
+        
+        return match(file_name) is not None
+
     match =  re.compile(regexs[name], re.I).match
+    test.library_name = name  # Available for debugging.
     return test
 
-def dependencies(libs):
-    r = {}
-    for lib in libs:
+def dependencies(roots):
+    """Return a set of dependencies for the list of library file roots
+
+    The return set is a dictionary keyed on library root name with values of 1.
+    """
+
+    root_set = {}
+    for root in roots:
         try:
-            deps = lib_dependencies[lib]
+            deps = lib_dependencies[root]
         except KeyError:
             pass
         else:
-            r[lib] = 1
-            r.update(dependencies(deps))
-    return r
+            root_set[root] = 1
+            root_set.update(dependencies(deps))
+    return root_set
 
 def name_to_root(name):
+    """Return the library file root for the library name"""
+    
     return file_root_names[name]

mingw32ccompiler.py

+# module mingw32ccompiler.py
+# Requires Python 2.1 or better.
+
+"""Win32 GUI/console versions of the distutils mingw32 compiler classes."""
+
+from distutils.cygwinccompiler import Mingw32CCompiler
+
+def intersect (sequence_a, sequence_b):
+    """Return true if the two sequences contain items in common
+
+    If sequence_a is a non-sequence then return false.
+    """
+    try:
+        for item in sequence_a:
+            if item in sequence_b:
+                return 1
+    except TypeError:
+        return 0
+    return 0
+
+def difference (sequence_a, sequence_b):
+    """Return a list of items in sequence_a but not in sequence_b
+
+    Will raise a ValueError if either argument is not a sequence.
+    """
+    new_sequence = []
+    for item in sequence_a:
+        if item not in sequence_b:
+            new_sequence.append(item)
+    return new_sequence
+
+subsystem_options = ['-mwindows', '-mconsole']  # Item position is critical.
+
+class Mingw32DefaultCCompiler (Mingw32CCompiler):
+    """This mingw32 compiler class builds a Win32 GUI DLL by default.
+
+    It is overridden by subsystem options in the linker extras.
+    """
+
+    def set_executables (self, **args):
+        """Has no linker subsystem option for shared libraries"""
+        Mingw32CCompiler.set_executables(self, **args)
+        try:
+            self.linker_so = difference (self.linker_so, subsystem_options)
+        except TypeError:
+            pass
+                          
+    def link (self,
+              target_desc,
+              objects,
+              output_filename,
+              output_dir=None,
+              libraries=None,
+              library_dirs=None,
+              runtime_library_dirs=None,
+              export_symbols=None,
+              debug=0,
+              extra_preargs=None,
+              extra_postargs=None,
+              build_temp=None,
+              target_lang=None):
+        """Do a Win32 GUI link if no subsystem option given."""
+
+        if (target_desc != self.EXECUTABLE and
+            not intersect(subsystem_options, extra_preargs) and
+            not intersect(subsystem_options, extra_postargs)):
+            try:
+                extra_preargs = extra_preargs + subsystem_options[0]
+            except TypeError:
+                extra_preargs = subsystem_options[0:1]
+
+        Mingw32CCompiler.link (self,
+                               target_desc,
+                               objects,
+                               output_filename,
+                               output_dir,
+                               libraries,
+                               library_dirs,
+                               runtime_library_dirs,
+                               export_symbols,
+                               debug,
+                               extra_preargs,
+                               extra_postargs,
+                               build_temp,
+                               target_lang)
+
+class Mingw32ConsoleCCompiler (Mingw32CCompiler):
+    """This mingw32 compiler class builds a console DLL.
+
+    It is not overridden by subsystem options in the linker extras.
+    """
+
+    def set_executables (self, **args):
+        """Has console subsystem linker option for shared libraries."""
+        Mingw32CCompiler.set_executables(self, **args)
+        try:
+            linker_so = difference(self.linker_so, subsystem_options)
+        except TypeError:
+            linker_so = subsystem_options[1:2]
+        else:
+            linker_so.append(subsystem_options[1])
+        self.linker_so = linker_so
+                          
+    def link (self,
+              target_desc,
+              objects,
+              output_filename,
+              output_dir=None,
+              libraries=None,
+              library_dirs=None,
+              runtime_library_dirs=None,
+              export_symbols=None,
+              debug=0,
+              extra_preargs=None,
+              extra_postargs=None,
+              build_temp=None,
+              target_lang=None):
+        """Do a console link."""
+
+        if target_desc != self.EXECUTABLE:
+            try:
+                extra_preargs = difference(extra_preargs, subsystem_options)
+            except TypeError:
+                pass
+            try:
+                extra_postargs = difference(extra_postargs, subsystem_options)
+            except TypeError:
+                pass
+        Mingw32CCompiler.link (self,
+                               target_desc,
+                               objects,
+                               output_filename,
+                               output_dir,
+                               libraries,
+                               library_dirs,
+                               runtime_library_dirs,
+                               export_symbols,
+                               debug,
+                               extra_preargs,
+                               extra_postargs,
+                               build_temp,
+                               target_lang)

mingw32distutils.py

+# module mingw32distutils.py
+# Requires Python 2.1 or better.
+
+"""Enhance distutils mingw32 compilation by adding Win32 GUI/console support."""
+
+from distutils import ccompiler
+from distutils.errors import DistutilsModuleError
+
+compilers = ['mingw32', 'mingw32-console']
+
+# Add the compiler classes to the ccompiler table. Unfortunate hacks follow.
+
+compiler_class = ccompiler.compiler_class
+value = compiler_class['mingw32']
+assert len(value) == 3, "distutils.ccompiler.compiler_class has changed"
+compiler_class['mingw32'] = ('', '',
+                             value[2] + ", Win32 GUI shared libraries defaullt")
+compiler_class['mingw32-console'] = ('', '',
+                                     value[2] + ", console shared libraries")
+
+original_new_compiler = ccompiler.new_compiler
+def new_compiler (plat=None,
+                  compiler=None,
+                  verbose=0,
+                  dry_run=0,
+                  force=0):
+    """Recognizes replacement mingw32 compiler classes"""
+
+    if compiler == 'mingw32':
+        from mingw32ccompiler import Mingw32DefaultCCompiler
+        return Mingw32DefaultCCompiler (None, dry_run, force)
+    if compiler == 'mingw32-console':
+        from mingw32CCompiler import Mingw32ConsoleCCompiler
+        return Mingw32ConsoleCCompiler (None, dry_run, force)
+    return original_new_compiler (plat, compiler, verbose, dry_run, force)
+
+ccompiler.new_compiler = new_compiler
+# module mingwcfg.py
+
+"""Manage the MinGW configuration file for setup.py"""
+
+import os
+
+directory = os.path.abspath(os.path.split(__file__)[0])
+path = os.path.join(directory, 'mingw.cfg')
+
+def write(mingw_root):
+    cnf = open(path, 'w')
+    try:
+        cnf.write(os.path.abspath(mingw_root))
+        cnf.write('\n')
+    finally:
+        cnf.close
+
+def read():
+    cnf = open(path, 'r')
+    try:
+        for ln in cnf:
+            return ln.strip()
+    finally:
+        cnf.close()
+# module msys.py
+# Requires Python 2.4 or better and win32api.
+
+"""MSYS specifics for Msys terminal IO and for running shell scripts
+
+exports msys_raw_input, MsysException, Msys
+"""
+
+from msysio import raw_input_ as msys_raw_input, print_ as msys_print
+from msysio import is_msys
+import os
+import time
+import subprocess
+import re
+import glob
+import _winreg
+
+FSTAB_REGEX = (r'^[ \t]*(?P<path>'
+               r'([a-zA-Z]:){0,1}([\\/][^\s*^?:%\\/]+)+)'
+               r'[ \t]+/mingw(\s|$)'
+               )
+
+def has_drive(path):
+    """Return true if the MSYS path strats with a drive letter"""
+    
+    return re.match('/[A-Z]/', path, re.I) is not None
+
+class MsysException(Exception):
+    """Path retrieval problem"""
+    pass
+
+def find_msys_version_subdir(msys_dir):
+    """Return the full MSYS root directory path
+
+    If msys_dir path lacks the version subdirectory, e.g. 1.0, then the
+    path is searched for one. The user will be prompted to choose if more
+    than one version is found.
+    """
+
+    regex = r'[\\/][1-9][.][0-9]$'
+    if re.search(regex, msys_dir) is not None:
+        return msys_dir
+    
+    roots = glob.glob(os.path.join(msys_dir, '[1-9].[0-9]'))
+    roots.sort()
+    roots.reverse()
+    if not roots:
+        raise MsysException("No msys versions found.\n")
+    else:
+        if len(roots) == 1:
+            root = roots[0]
+        else:
+            msys_print("Select an Msys version:")
+            for i, path in enumerate(roots):
+                msys_print("  %d = %s" % (i+1, os.path.split(path)[1]))
+            choice = msys_raw_input("Select 1-%d (1 = default):")
+            if not choice:
+                root = roots[0]
+            else:
+                root = roots[int(choice)-1]
+        return root
+        
+def input_msys_dir():
+    """Return user entered MSYS directory path
+
+    May raise MsysException."""
+
+    while 1:
+        dir_path = msys_raw_input("Enter the MSYS directory path,\n"
+                              "(or press [Enter] to quit):")
+        dir_path = dir_path.strip()
+        if not dir_path:
+            raise MsysException("Input aborted by user")
+        dir_path = os.path.abspath(dir_path)
+        try:
+            return find_msys_version_subdir(dir_path)
+        except MsysException, e:
+            msys_print(e)
+            
+def find_msys_registry():
+    """Return the MSYS 1.0 directory path stored in the Windows registry
+
+    The return value is an encoded ascii str. The registry entry for the
+    uninstaller is used. Raise a LookupError if not found.
+    """
+    
+    subkey = (
+        'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MSYS-1.0_is1')
+    key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey)
+    try:
+        try:
+            return _winreg.QueryValueEx(key, 'Inno Setup: App Path')[0].encode()
+        except WindowsError:
+            raise LookupError("MSYS not found in the registry")
+    finally:
+        key.Close()
+
+def as_shell(msys_root):
+    """Append MSYS shell program to MSYS root directory path"""
+
+    return os.path.join(msys_root, 'bin', 'sh.exe')
+
+def check_for_shell(msys_directory=None):
+    """Check various locations for MSYS shell or root directory.
+
+    May raise MsysException.
+    """
+
+    if msys_directory is not None:
+        try:
+            dir_path = find_msys_version_subdir(msys_directory)
+        except MsysException:
+            pass
+        else:
+            return as_shell(dir_path)
+
+    try:
+        shell = os.environ['SHELL']
+    except KeyError:
+        pass
+    else:
+        if is_msys():
+            return shell + '.exe'
+        return shell
+
+    try:
+        dir_path = find_msys_registry()
+    except LookupError:
+        pass
+    else:
+        return as_shell(dir_path)
+
+    return as_shell(input_msys_dir())
+
+def find_msys_shell(msys_directory=None):
+    """Retrun the MSYS shell program path
+
+    MsysException is raised if the shell program is not found. The user
+    is prompt is prompted as a last resort if no directory is found or
+    there are multiple choices.
+    """
+
+    shell = check_for_shell(msys_directory)
+
+    while 1:
+        shell = os.path.abspath(shell.replace('/', os.sep))
+        if os.path.isfile(shell):
+            break
+        msys_print("Directory %s has no MSYS shell." % shell)
+        shell = as_shell(input_msys_dir())
+    return shell
+
+def find_mingw_root(msys_directory):
+    """Return the Windows equivalent of /mingw"""
+
+    # Look it up in the fstabs file.
+    fstab_path = os.path.join(msys_directory, 'etc', 'fstab')
+    try:
+        fstab = open(fstab_path, 'r')
+    except IOError:
+        raise MsysException("Unable to open MSYS fstab file %s" % fstab_path)
+    else:
+        match = re.search(FSTAB_REGEX, fstab.read(), re.MULTILINE)
+        if match is None:
+            raise MsysException(
+                "The required MinGW path is not in the MSYS fstab file")
+
+        dir_path = os.path.abspath(match.groupdict()['path'])
+        if not os.path.isdir(dir_path):
+            raise MsysException("%s is not a directory" % dir_path)
+    return dir_path
+
+
+class Msys(object):
+    """Return a new Msys environment;  May raise MsysException
+
+    Msys([msys_directory, [require_mingw]])
+
+    msys_directory: A string giving the path of the MSYS directory.
+
+    Either or both keyword arguments can be omitted. If msys_directory
+    is not provided then the environment variable SHELL and the Windows
+    registry are checked. Finally the user is prompted for the directory
+    path. If require_mingw is True, the default, the mingw directory path
+    is retrieved from the MSYS fstab file. An MsysException is raised if
+    the required paths are not found.
+    """
+
+    _is_msys = is_msys()
+
+    def __init__(self, msys_directory=None, require_mingw=None):
+        """New environment
+
+        May raise MsysException"""
+
+        if require_mingw is None:
+            require_mingw = True
+        self._environ = os.environ.copy()
+        self._shell = find_msys_shell(msys_directory)
+        self._msys_root = os.path.split(os.path.split(self.shell)[0])[0].lower()
+        try:
+            self._mingw_root = find_mingw_root(self.msys_root)
+        except MsysException:
+            if require_mingw:
+                raise
+            self._mingw_root = None
+        else:
+            self.environ['MINGW_ROOT_DIRECTORY'] = self._mingw_root
+
+    environ = property(lambda self: self._environ,
+                       doc="Environment variables")
+    shell = property(lambda self: self._shell,