From 1e93334baa5bafe099bba0bbb2878cfbc4349ac1 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Fri, 7 Feb 2003 07:05:09 +0000 Subject: Fix case-sensitive packaging problem on Win32. --- README | 58 ++++++++++++++---- config | 4 +- runtest.py | 15 ++++- src/CHANGES.txt | 5 ++ src/README.txt | 55 +++++++++++++---- src/script/scons.py | 63 +++++++++++++------ src/setup.py | 173 +++++++++++++++++++++++++++++++++++++++++++--------- src/setupTests.py | 137 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 434 insertions(+), 76 deletions(-) create mode 100644 src/setupTests.py diff --git a/README b/README index b1bdc04..a0661dc 100644 --- a/README +++ b/README @@ -104,24 +104,58 @@ script as follows: # 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 diff --git a/config b/config index f1b58ad..0769a2b 100644 --- a/config +++ b/config @@ -250,9 +250,9 @@ diff_command = * 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"; diff --git a/runtest.py b/runtest.py index 52e76d4..eff0a35 100644 --- a/runtest.py +++ b/runtest.py @@ -73,6 +73,7 @@ package = None scons = None scons_exec = None output = None +version = '' if os.name == 'java': python = os.path.join(sys.prefix, 'jython') @@ -107,13 +108,15 @@ Options: 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': @@ -133,6 +136,8 @@ for o, a in opts: 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': @@ -144,7 +149,7 @@ def whereis(file): 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 @@ -304,6 +309,10 @@ elif scons_lib_dir: 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') + \ diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 237aa60..9f45dbd 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -64,6 +64,11 @@ RELEASE 0.11 - XXX 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 diff --git a/src/README.txt b/src/README.txt index ca73a55..e9c6455 100644 --- a/src/README.txt +++ b/src/README.txt @@ -87,23 +87,54 @@ provided Python-standard setup script as follows: # 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 diff --git a/src/script/scons.py b/src/script/scons.py index f21a3eb..f128d1d 100644 --- a/src/script/scons.py +++ b/src/script/scons.py @@ -62,36 +62,61 @@ if script_dir: local = os.path.join(script_dir, local) libs.append(local) -if sys.platform == 'win32': - libs.extend([ os.path.join(sys.prefix, 'SCons-%s' % __version__), - os.path.join(sys.prefix, 'SCons') ]) -else: - prefs = [] +scons_version = 'scons-%s' % __version__ - _bin = os.path.join('', 'bin') - _usr = os.path.join('', 'usr') - _usr_local = os.path.join('', 'usr', 'local') +prefs = [] +if sys.platform == 'win32': + # sys.prefix is (likely) C:\Python*; + # check only C:\Python*. + prefs.append(sys.prefix) +else: + # 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)]) - - if sys.prefix[-len(_usr):] == _usr: - 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)]) + head, tail = os.path.split(script_dir) + if tail == "bin": + # script_dir is /foo/bin; + # check /foo/lib/scons*. + prefs.append(head) + + 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 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 diff --git a/src/setup.py b/src/setup.py index 3c240a9..6aa673a 100644 --- a/src/setup.py +++ b/src/setup.py @@ -25,6 +25,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import os.path +import string import sys (head, tail) = os.path.split(sys.argv[0]) @@ -34,8 +35,10 @@ if head: 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. @@ -46,34 +49,141 @@ your system, or on how to install SCons from a different package. """) 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", @@ -84,7 +194,9 @@ arguments = { "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: @@ -93,4 +205,9 @@ 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 diff --git a/src/setupTests.py b/src/setupTests.py new file mode 100644 index 0000000..6c151eb --- /dev/null +++ b/src/setupTests.py @@ -0,0 +1,137 @@ +#!/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() -- cgit v0.12