Commits

Steven Knight  committed 9f4dd4f

Fix case-sensitive packaging problem on Win32.

  • Participants
  • Parent commits 5cafeb0

Comments (0)

Files changed (8)

         # cd build/scons
         # python setup.py install
 
-This will install the scons script in the default system script
-directory (/usr/bin or C:\Python*\Scripts, for example) and the build
-engine in an appropriate SCons library directory (/usr/lib/scons or
-C:\Python*\SCons, for example).
+If this is the first time you are installing SCons on your system,
+the above command will install the scons script in the default system
+script directory (/usr/bin or C:\Python*\Scripts, for example) and the
+build engine in an appropriate stand-alone SCons library directory
+(/usr/lib/scons or C:\Python*\scons, for example).
 
-You should have system installation privileges (that is, "root" on POSIX
-or "Administrator" on Windows) when running the setup.py script to
-install SCons in the default system directories.
+Note that, by default, SCons does not install its library in the
+standard Python library directories.  If you want to be able to use the
+SCons library modules (the build engine) in other Python scripts, you
+can run the setup script as follows:
 
-If you don't have system installation privileges, you can use the
---prefix option to specify an alternate installation location, such as
-your home directory:
+        # cd build/scons
+        # python setup.py install --standard-lib
+
+This will install the build engine in the standard Python
+library directory (/usr/lib/python*/site-packages or
+C:\Python*\Lib\site-packages).
+
+Alternatively, you may want to install multiple versions of SCons
+side-by-side, which you can do as follows:
+
+        # cd build/scons
+        # python setup.py install --version-lib
+
+This will install the build engine in a version-specific library
+directory (/usr/lib/scons-__VERSION__ or C:\Python*\scons-__VERSION__).
+
+If this is not the first time you are installing SCons on your system,
+the setup script will, by default, search for where you have previously
+installed the SCons library, and install this version's library the
+same way--that is, if you previously installed the SCons library in
+the standard Python library, the setup script will install this one
+in the same location.  You may, of course, specify one of the --*-lib
+options described above to select a specific library location, or use
+the following option to explicitly select installation into the default
+standalone library directory (/usr/lib/scons or C:\Python*\scons):
+
+        # cd build/scons
+        # python setup.py install --standalone-lib
+
+Note that, to install SCons in any of the above system directories,
+you should have system installation privileges (that is, "root" or
+"Administrator") when running the setup.py script.  If you don't have
+system installation privileges, you can use the --prefix option to
+specify an alternate installation location, such as your home directory:
 
         $ cd build/scons
         $ python setup.py install --prefix=$HOME
 
-This will install the scons script itself in $HOME/bin and the
-associated library in $HOME/lib/scons
+This will install SCons in the appropriate locations relative to
+$HOME--that is, the scons script itself $HOME/bin and the associated
+library in $HOME/lib/scons, for example.
 
 
 TESTING
  * is set appropriately during a baseline test.  So we just use the
  * proper aesub variable to comment out the expanded $spe.
  */
-test_command = "python ${Source runtest.py Absolute} -p tar-gz -q ${File_Name}";
+test_command = "python ${Source runtest.py Absolute} -p tar-gz -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -q ${File_Name}";
 
-batch_test_command = "python ${Source runtest.py Absolute} -p tar-gz -o ${Output} ${File_Names} ${COMment $spe}";
+batch_test_command = "python ${Source runtest.py Absolute} -p tar-gz -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -o ${Output} ${File_Names} ${COMment $spe}";
 
 new_test_filename = "test/CHANGETHIS.py";
 
 scons = None
 scons_exec = None
 output = None
+version = ''
 
 if os.name == 'java':
     python = os.path.join(sys.prefix, 'jython')
                                 tar-gz        .tar.gz distribution
                                 zip           .zip distribution
   -q, --quiet                 Don't print the test being executed.
+  -v version                  Specify the SCons version.
   -X                          Test script is executable, don't feed to Python.
   -x SCRIPT, --exec SCRIPT    Test SCRIPT.
 """
 
-opts, args = getopt.getopt(sys.argv[1:], "adho:P:p:qXx:",
+opts, args = getopt.getopt(sys.argv[1:], "adho:P:p:qv:Xx:",
                             ['all', 'debug', 'help', 'output=',
-                             'package=', 'python=', 'quiet', 'exec='])
+                             'package=', 'python=', 'quiet',
+                             'version=', 'exec='])
 
 for o, a in opts:
     if o == '-a' or o == '--all':
         package = a
     elif o == '-q' or o == '--quiet':
         printcmd = 0
+    elif o == '-v' or o == '--version':
+        version = a
     elif o == '-X':
         scons_exec = 1
     elif o == '-x' or o == '--exec':
         if os.path.isfile(f):
             try:
                 st = os.stat(f)
-            except:
+            except OSError:
                 continue
             if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
                 return f
 if scons_exec:
     os.environ['SCONS_EXEC'] = '1'
 
+os.environ['SCONS_CWD'] = cwd
+
+os.environ['SCONS_VERSION'] = version
+
 os.environ['PYTHONPATH'] = pythonpath_dir + \
                            os.pathsep + \
                            os.path.join(cwd, 'build', 'etc') + \

File src/CHANGES.txt

     everything in the current directory and below (like Make).  This
     can be disabled by specifying Default(None) in an SConscript.
 
+  - Revamp SCons installation to fix a case-sensitive installation
+    on Win32 systems, and to add SCons-specific --standard-lib,
+    --standalone-lib, and --version-lib options for easier user
+    control of where the libraries get installed.
+
   From Steve Leblanc:
 
   - Fix the output of -c -n when directories are involved, so it

File src/README.txt

 
         # python setup.py install
 
-This will install the scons script in the default system script
-directory (/usr/bin or C:\Python*\Scripts, for example) and the build
-engine in an appropriate SCons library directory (/usr/lib/scons or
-C:\Python*\SCons, for example).
+If this is the first time you are installing SCons on your system,
+the above command will install the scons script in the default system
+script directory (/usr/bin or C:\Python*\Scripts, for example) and the
+build engine in an appropriate stand-alone SCons library directory
+(/usr/lib/scons or C:\Python*\scons, for example).
 
-You should have system installation privileges (that is, "root" or
-"Administrator") when running the setup.py script to install SCons in
-the default system directories.
+Note that, by default, SCons does not install its library in the
+standard Python library directories.  If you want to be able to use the
+SCons library modules (the build engine) in other Python scripts, you
+can run the setup script as follows:
 
-If you don't have system installation privileges, you can use the
---prefix option to specify an alternate installation location, such as
-your home directory:
+        # python setup.py install --standard-lib
+
+This will install the build engine in the standard Python
+library directory (/usr/lib/python*/site-packages or
+C:\Python*\Lib\site-packages).
+
+Alternatively, you may want to install multiple versions of SCons
+side-by-side, which you can do as follows:
+
+        # python setup.py install --version-lib
+
+This will install the build engine in a version-specific library
+directory (/usr/lib/scons-__VERSION__ or C:\Python*\scons-__VERSION__).
+
+If this is not the first time you are installing SCons on your system,
+the setup script will, by default, search for where you have previously
+installed the SCons library, and install this version's library the
+same way--that is, if you previously installed the SCons library in
+the standard Python library, the setup script will install this one
+in the same location.  You may, of course, specify one of the --*-lib
+options described above to select a specific library location, or use
+the following option to explicitly select installation into the default
+standalone library directory (/usr/lib/scons or C:\Python*\scons):
+
+        # python setup.py install --standalone-lib
+
+Note that, to install SCons in any of the above system directories,
+you should have system installation privileges (that is, "root" or
+"Administrator") when running the setup.py script.  If you don't have
+system installation privileges, you can use the --prefix option to
+specify an alternate installation location, such as your home directory:
 
         $ python setup.py install --prefix=$HOME
 
-This will install the scons script itself in $HOME/bin and the
-associated library in $HOME/lib/scons
+This will install SCons in the appropriate locations relative to
+$HOME--that is, the scons script itself $HOME/bin and the associated
+library in $HOME/lib/scons, for example.
 
 
 DOCUMENTATION

File src/script/scons.py

     local = os.path.join(script_dir, local)
 libs.append(local)
 
+scons_version = 'scons-%s' % __version__
+
+prefs = []
+
 if sys.platform == 'win32':
-    libs.extend([ os.path.join(sys.prefix, 'SCons-%s' % __version__),
-                  os.path.join(sys.prefix, 'SCons') ])
+    # sys.prefix is (likely) C:\Python*;
+    # check only C:\Python*.
+    prefs.append(sys.prefix)
 else:
-    prefs = []
-
-    _bin = os.path.join('', 'bin')
-    _usr = os.path.join('', 'usr')
-    _usr_local = os.path.join('', 'usr', 'local')
-
+    # On other (POSIX) platforms, things are more complicated due to
+    # the variety of path names and library locations.  Try to be smart
+    # about it.
     if script_dir == 'bin':
+        # script_dir is `pwd`/bin;
+        # check `pwd`/lib/scons*.
         prefs.append(os.getcwd())
     else:
         if script_dir == '.' or script_dir == '':
             script_dir = os.getcwd()
-        if script_dir[-len(_bin):] == _bin:
-            prefs.append(script_dir[:-len(_bin)])
+        head, tail = os.path.split(script_dir)
+        if tail == "bin":
+            # script_dir is /foo/bin;
+            # check /foo/lib/scons*.
+            prefs.append(head)
 
-    if sys.prefix[-len(_usr):] == _usr:
+    head, tail = os.path.split(sys.prefix)
+    if tail == "usr":
+        # sys.prefix is /foo/usr;
+        # check /foo/usr/lib/scons* first,
+        # then /foo/usr/local/lib/scons*.
         prefs.append(sys.prefix)
-	prefs.append(os.path.join(sys.prefix, "local"))
-    elif sys.prefix[-len(_usr_local):] == _usr_local:
-        _local = os.path.join('', 'local')
-        prefs.append(sys.prefix[:-len(_local)])
-        prefs.append(sys.prefix)
+        prefs.append(os.path.join(sys.prefix, "local"))
+    elif tail == "local":
+        h, t = os.path.split(head)
+        if t == "usr":
+            # sys.prefix is /foo/usr/local;
+            # check /foo/usr/local/lib/scons* first,
+            # then /foo/usr/lib/scons*.
+            prefs.append(sys.prefix)
+            prefs.append(head)
+        else:
+            # sys.prefix is /foo/local;
+            # check only /foo/local/lib/scons*.
+            prefs.append(sys.prefix)
     else:
+        # sys.prefix is /foo (ends in neither /usr or /local);
+        # check only /foo/lib/scons*.
         prefs.append(sys.prefix)
 
-    libs.extend(map(lambda x: os.path.join(x, 'lib', 'scons-%s' % __version__), prefs))
-    libs.extend(map(lambda x: os.path.join(x, 'lib', 'scons'), prefs))
+    prefs = map(lambda x: os.path.join(x, 'lib'), prefs)
+
+# Look first for 'scons-__version__' in all of our preference libs,
+# then for 'scons'.
+libs.extend(map(lambda x: os.path.join(x, scons_version), prefs))
+libs.extend(map(lambda x: os.path.join(x, 'scons'), prefs))
 
 sys.path = libs + sys.path
 

File src/setup.py

 
 import os
 import os.path
+import string
 import sys
 
 (head, tail) = os.path.split(sys.argv[0])
     sys.argv[0] = tail
 
 try:
-    from distutils.core import setup
-    from distutils.command.install_lib import install_lib
+    import distutils.core
+    import distutils.command.install
+    import distutils.command.install_lib
+    import distutils.command.install_scripts
 except ImportError:
     sys.stderr.write("""Could not import distutils.
 
 """)
     sys.exit(1)
 
-class my_install_lib(install_lib):
+_install = distutils.command.install.install
+_install_lib = distutils.command.install_lib.install_lib
+_install_scripts = distutils.command.install_scripts.install_scripts
+
+standard_lib = 0
+standalone_lib = 0
+version_lib = 0
+
+installed_lib_dir = None
+installed_scripts_dir = None
+
+def set_explicitly(name, args):
+    """
+    Return if the installation directory was set explicitly by the
+    user on the command line.  This is complicated by the fact that
+    "install --install-lib=/foo" gets turned into "install_lib
+    --install-dir=/foo" internally.
+    """
+    if args[0] == "install_" + name:
+        s = "--install-dir="
+    else:
+        # The command is something else (usually "install")
+        s = "--install-%s=" % name
+    set = 0
+    length = len(s)
+    for a in args[1:]:
+        if a[:length] == s:
+            set = 1
+            break
+    return set
+
+class install(_install):
+    user_options = _install.user_options + [
+                    ('standard-lib', None,
+                     "install SCons library in standard Python location"),
+                    ('standalone-lib', None,
+                     "install SCons library in separate standalone directory"),
+                    ('version-lib', None,
+                     "install SCons library in version-specific directory")
+                   ]
+    boolean_options = _install.boolean_options + [
+                       'standard-lib',
+                       'standalone-lib',
+                       'version-lib'
+                      ]
+
+    def initialize_options(self):
+        _install.initialize_options(self)
+        self.standard_lib = 0
+        self.standalone_lib = 0
+        self.version_lib = 0
+
     def finalize_options(self):
-        install_lib.finalize_options(self)
-	head = self.install_dir
-        drive, head = os.path.splitdrive(self.install_dir)
-        while head:
-	    if head == os.sep:
-		head = None
-		break
-	    else:
-	        head, tail = os.path.split(head)
-            if tail[:6] == "python":
-                self.install_dir = os.path.join(drive + head, "scons")
-                # Our original packaging scheme placed the build engine
-                # in a private library directory that contained the SCons
-                # version number in the directory name.  Here's how this
-                # was supported here.  See the Construct file for details
-                # on other files that would need to be changed to support
-                # this as well.
-                #self.install_dir = os.path.join(drive + head, "scons-__VERSION__")
-                return
-            elif tail[:6] == "Python":
-                self.install_dir = os.path.join(drive + head, tail)
-                return
+        _install.finalize_options(self)
+        global standard_lib, standalone_lib, version_lib
+        standard_lib = self.standard_lib
+        standalone_lib = self.standalone_lib
+        version_lib = self.version_lib
+
+def get_scons_prefix(libdir):
+    """
+    Return the right prefix for SCons library installation.  Find
+    this by starting with the library installation directory
+    (.../site-packages, most likely) and crawling back up until we reach
+    a directory name beginning with "python" (or "Python").
+    """
+    drive, head = os.path.splitdrive(libdir)
+    while head:
+        if head == os.sep:
+            break
+        head, tail = os.path.split(head)
+        if string.lower(tail)[:6] == "python":
+            # Found the Python library directory...
+            if sys.platform == "win32":
+                # ...on Win32 systems, "scons" goes in the directory:
+                #    C:\PythonXX => C:\PythonXX\scons
+                return os.path.join(drive + head, tail)
+            else:
+                # ...on other systems, "scons" goes above the directory:
+                #    /usr/lib/pythonX.X => /usr/lib/scons
+                return os.path.join(drive + head)
+    return libdir
+
+class install_lib(_install_lib):
+    def initialize_options(self):
+        _install_lib.initialize_options(self)
+        global standard_lib, standalone_lib, version_lib
+        self.standard_lib = standard_lib
+        self.standalone_lib = standalone_lib
+        self.version_lib = version_lib
+
+    def finalize_options(self):
+        _install_lib.finalize_options(self)
+        if not set_explicitly("lib", self.distribution.script_args):
+            # They didn't explicitly specify the installation
+            # directory for libraries...
+            prefix = get_scons_prefix(self.install_dir)
+            standard_dir = os.path.join(self.install_dir, "SCons")
+            version_dir = os.path.join(prefix, "scons-0.11")
+            standalone_dir = os.path.join(prefix, "scons")
+            if self.version_lib:
+                # ...but they asked for a version-specific directory.
+                self.install_dir = version_dir
+            elif self.standalone_lib:
+                # ...but they asked for a standalone directory.
+                self.install_dir = standalone_dir
+            elif not self.standard_lib:
+                # ...and they didn't explicitly ask for the standard
+                # directory, so guess based on what's out there.
+                try:
+                    e = filter(lambda x: x[:6] == "scons-", os.listdir(prefix))
+                except:
+                    e = None
+                if e:
+                    # We found a path name (e.g.) /usr/lib/scons-XXX,
+                    # so pick the version-specific directory.
+                    self.install_dir = version_dir
+                elif os.path.exists(standalone_dir) or \
+                     not os.path.exists(standard_dir):
+                    # There's already a standalone directory, or
+                    # there's no SCons library in the standard
+                    # directory, so go with the standalone.
+                    self.install_dir = standalone_dir
+        global installed_lib_dir
+        installed_lib_dir = self.install_dir
+
+class install_scripts(_install_scripts):
+    def finalize_options(self):
+        _install_scripts.finalize_options(self)
+        global installed_scripts_dir
+        installed_scripts_dir = self.install_dir
 
 arguments = {
     'name'             : "scons",
-    'version'          : "__VERSION__",
+    'version'          : "0.11",
     'packages'         : ["SCons",
                           "SCons.Node",
                           "SCons.Optik",
                           "SCons.Tool"],
     'package_dir'      : {'' : 'engine'},
     'scripts'          : ["script/scons"],
-    'cmdclass'         : {'install_lib' : my_install_lib}
+    'cmdclass'         : {'install'         : install,
+                          'install_lib'     : install_lib,
+                          'install_scripts' : install_scripts}
 }
 
 try:
 except IndexError:
     pass
 
-apply(setup, (), arguments)
+apply(distutils.core.setup, (), arguments)
+
+if installed_lib_dir:
+    print "Installed SCons library modules into %s" % installed_lib_dir
+if installed_scripts_dir:
+    print "Installed SCons script into %s" % installed_scripts_dir

File src/setupTests.py

+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test how the setup.py script installs SCons (specifically, its libraries).
+"""
+
+import os
+import os.path
+import shutil
+import string
+
+import TestSCons
+
+python = TestSCons.python
+
+class MyTestSCons(TestSCons.TestSCons):
+    def installed(self, lib):
+        lines = string.split(self.stdout(), '\n')
+        return lines[-3] == 'Installed SCons library modules into %s' % lib
+
+try:
+    cwd = os.environ['SCONS_CWD']
+except KeyError:
+    cwd = os.getcwd()
+
+try:
+    version = os.environ['SCONS_VERSION']
+except KeyError:
+    version = '__VERSION__'
+
+scons_version = 'scons-%s' % version
+
+tar_gz = os.path.join(cwd, 'build', 'dist', '%s.tar.gz' % scons_version)
+
+test = MyTestSCons()
+
+if not os.path.isfile(tar_gz):
+    print "Did not find an SCons package `%s'." % tar_gz
+    print "Cannot test package installation."
+    test.no_result(1)
+
+test.subdir('root')
+
+root = test.workpath('root')
+
+standard_lib = '%s/usr/lib/python1.5/site-packages/' % root
+standalone_lib = '%s/usr/lib/scons' % root
+version_lib = '%s/usr/lib/%s' % (root, scons_version)
+
+def installed(lib):
+    return 'Installed SCons library modules into %s' % lib
+
+os.system("tar zxf %s" % tar_gz)
+
+# Verify that a virgin installation installs the standalone library.
+test.run(chdir = scons_version,
+         program = python,
+         arguments = 'setup.py install --root=%s' % root,
+         stderr = None)
+test.fail_test(not test.installed(standalone_lib))
+
+# Verify that --standard-lib installs into the Python standard library.
+test.run(chdir = scons_version,
+         program = python,
+         arguments = 'setup.py install --root=%s --standard-lib' % root,
+         stderr = None)
+lines = string.split(test.stdout(), '\n')
+test.fail_test(not test.installed(standard_lib))
+
+# Verify that --standalone-lib installs the standalone library.
+test.run(chdir = scons_version,
+         program = python,
+         arguments = 'setup.py install --root=%s --standalone-lib' % root,
+         stderr = None)
+test.fail_test(not test.installed(standalone_lib))
+
+# Verify that --version-lib installs into a version-specific library directory.
+test.run(chdir = scons_version,
+         program = python,
+         arguments = 'setup.py install --root=%s --version-lib' % root,
+         stderr = None)
+test.fail_test(not test.installed(version_lib))
+
+# Now that all of the libraries are in place,
+# verify that a default installation finds the version-specific library first.
+test.run(chdir = scons_version,
+         program = python,
+         arguments = 'setup.py install --root=%s' % root,
+         stderr = None)
+test.fail_test(not test.installed(version_lib))
+
+shutil.rmtree(version_lib)
+
+# Now with only the standard and standalone libraries in place,
+# verify that a default installation finds the standalone library first.
+test.run(chdir = scons_version,
+         program = python,
+         arguments = 'setup.py install --root=%s' % root,
+         stderr = None)
+test.fail_test(not test.installed(standalone_lib))
+
+shutil.rmtree(standalone_lib)
+
+# Now with only the standard libraries in place,
+# verify that a default installation installs the standard library.
+test.run(chdir = scons_version,
+         program = python,
+         arguments = 'setup.py install --root=%s' % root,
+         stderr = None)
+test.fail_test(not test.installed(standard_lib))
+
+# All done.
+test.pass_test()