502 lines
19 KiB
Python
502 lines
19 KiB
Python
|
"""SCons.Tool.install
|
||
|
|
||
|
Tool-specific initialization for the install tool.
|
||
|
|
||
|
There normally shouldn't be any need to import this module directly.
|
||
|
It will usually be imported through the generic SCons.Tool.Tool()
|
||
|
selection method.
|
||
|
"""
|
||
|
|
||
|
#
|
||
|
# Copyright (c) 2001 - 2015 The SCons Foundation
|
||
|
#
|
||
|
# 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__ = "src/engine/SCons/Tool/install.py rel_2.3.5:3347:d31d5a4e74b6 2015/07/31 14:36:10 bdbaddog"
|
||
|
|
||
|
import os
|
||
|
import re
|
||
|
import shutil
|
||
|
import stat
|
||
|
|
||
|
import SCons.Action
|
||
|
from SCons.Util import make_path_relative
|
||
|
|
||
|
#
|
||
|
# We keep track of *all* installed files.
|
||
|
_INSTALLED_FILES = []
|
||
|
_UNIQUE_INSTALLED_FILES = None
|
||
|
|
||
|
class CopytreeError(EnvironmentError):
|
||
|
pass
|
||
|
|
||
|
# This is a patched version of shutil.copytree from python 2.5. It
|
||
|
# doesn't fail if the dir exists, which regular copytree does
|
||
|
# (annoyingly). Note the XXX comment in the docstring.
|
||
|
def scons_copytree(src, dst, symlinks=False):
|
||
|
"""Recursively copy a directory tree using copy2().
|
||
|
|
||
|
The destination directory must not already exist.
|
||
|
If exception(s) occur, an CopytreeError is raised with a list of reasons.
|
||
|
|
||
|
If the optional symlinks flag is true, symbolic links in the
|
||
|
source tree result in symbolic links in the destination tree; if
|
||
|
it is false, the contents of the files pointed to by symbolic
|
||
|
links are copied.
|
||
|
|
||
|
XXX Consider this example code rather than the ultimate tool.
|
||
|
|
||
|
"""
|
||
|
names = os.listdir(src)
|
||
|
# garyo@genarts.com fix: check for dir before making dirs.
|
||
|
if not os.path.exists(dst):
|
||
|
os.makedirs(dst)
|
||
|
errors = []
|
||
|
for name in names:
|
||
|
srcname = os.path.join(src, name)
|
||
|
dstname = os.path.join(dst, name)
|
||
|
try:
|
||
|
if symlinks and os.path.islink(srcname):
|
||
|
linkto = os.readlink(srcname)
|
||
|
os.symlink(linkto, dstname)
|
||
|
elif os.path.isdir(srcname):
|
||
|
scons_copytree(srcname, dstname, symlinks)
|
||
|
else:
|
||
|
shutil.copy2(srcname, dstname)
|
||
|
# XXX What about devices, sockets etc.?
|
||
|
except (IOError, os.error), why:
|
||
|
errors.append((srcname, dstname, str(why)))
|
||
|
# catch the CopytreeError from the recursive copytree so that we can
|
||
|
# continue with other files
|
||
|
except CopytreeError, err:
|
||
|
errors.extend(err.args[0])
|
||
|
try:
|
||
|
shutil.copystat(src, dst)
|
||
|
except WindowsError:
|
||
|
# can't copy file access times on Windows
|
||
|
pass
|
||
|
except OSError, why:
|
||
|
errors.extend((src, dst, str(why)))
|
||
|
if errors:
|
||
|
raise CopytreeError, errors
|
||
|
|
||
|
|
||
|
#
|
||
|
# Functions doing the actual work of the Install Builder.
|
||
|
#
|
||
|
def copyFunc(dest, source, env):
|
||
|
"""Install a source file or directory into a destination by copying,
|
||
|
(including copying permission/mode bits)."""
|
||
|
|
||
|
if os.path.isdir(source):
|
||
|
if os.path.exists(dest):
|
||
|
if not os.path.isdir(dest):
|
||
|
raise SCons.Errors.UserError("cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source)))
|
||
|
else:
|
||
|
parent = os.path.split(dest)[0]
|
||
|
if not os.path.exists(parent):
|
||
|
os.makedirs(parent)
|
||
|
scons_copytree(source, dest)
|
||
|
else:
|
||
|
shutil.copy2(source, dest)
|
||
|
st = os.stat(source)
|
||
|
os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
|
||
|
|
||
|
return 0
|
||
|
|
||
|
#
|
||
|
# Functions doing the actual work of the InstallVersionedLib Builder.
|
||
|
#
|
||
|
def copyFuncVersionedLib(dest, source, env):
|
||
|
"""Install a versioned library into a destination by copying,
|
||
|
(including copying permission/mode bits) and then creating
|
||
|
required symlinks."""
|
||
|
|
||
|
if os.path.isdir(source):
|
||
|
raise SCons.Errors.UserError("cannot install directory `%s' as a version library" % str(source) )
|
||
|
else:
|
||
|
# remove the link if it is already there
|
||
|
try:
|
||
|
os.remove(dest)
|
||
|
except:
|
||
|
pass
|
||
|
shutil.copy2(source, dest)
|
||
|
st = os.stat(source)
|
||
|
os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
|
||
|
versionedLibLinks(dest, source, env)
|
||
|
|
||
|
return 0
|
||
|
|
||
|
def versionedLibVersion(dest, source, env):
|
||
|
"""Check if dest is a version shared library name. Return version, libname, & install_dir if it is."""
|
||
|
Verbose = False
|
||
|
platform = env.subst('$PLATFORM')
|
||
|
if not (platform == 'posix' or platform == 'darwin' or platform == 'sunos'):
|
||
|
return (None, None, None)
|
||
|
|
||
|
if (hasattr(source[0], 'attributes') and
|
||
|
hasattr(source[0].attributes, 'shlibname')):
|
||
|
libname = source[0].attributes.shlibname
|
||
|
else:
|
||
|
libname = os.path.basename(str(dest))
|
||
|
install_dir = os.path.dirname(str(dest))
|
||
|
shlib_suffix = env.subst('$SHLIBSUFFIX')
|
||
|
# See if the source name is a versioned shared library, get the version number
|
||
|
result = False
|
||
|
|
||
|
version_re = re.compile("[0-9]+\\.[0-9]+\\.[0-9a-zA-Z]+")
|
||
|
version_File = None
|
||
|
if platform == 'posix' or platform == 'sunos':
|
||
|
# handle unix names
|
||
|
versioned_re = re.compile(re.escape(shlib_suffix + '.') + "[0-9]+\\.[0-9]+\\.[0-9a-zA-Z]+")
|
||
|
result = versioned_re.findall(libname)
|
||
|
if result:
|
||
|
version_File = version_re.findall(versioned_re.findall(libname)[-1])[-1]
|
||
|
elif platform == 'darwin':
|
||
|
# handle OSX names
|
||
|
versioned_re = re.compile("\\.[0-9]+\\.[0-9]+\\.[0-9a-zA-Z]+" + re.escape(shlib_suffix) )
|
||
|
result = versioned_re.findall(libname)
|
||
|
if result:
|
||
|
version_File = version_re.findall(versioned_re.findall(libname)[-1])[-1]
|
||
|
|
||
|
if Verbose:
|
||
|
print "install: version_File ", version_File
|
||
|
# result is False if we did not find a versioned shared library name, so return and empty list
|
||
|
if not result:
|
||
|
return (None, libname, install_dir)
|
||
|
|
||
|
version = None
|
||
|
# get version number from the environment
|
||
|
try:
|
||
|
version = env.subst('$SHLIBVERSION')
|
||
|
except KeyError:
|
||
|
version = None
|
||
|
|
||
|
if version != version_File:
|
||
|
#raise SCons.Errors.UserError("SHLIBVERSION '%s' does not match the version # '%s' in the filename" % (version, version_File) )
|
||
|
print "SHLIBVERSION '%s' does not match the version # '%s' in the filename, proceeding based on file name" % (version, version_File)
|
||
|
version = version_File
|
||
|
return (version, libname, install_dir)
|
||
|
|
||
|
def versionedLibLinks(dest, source, env):
|
||
|
"""If we are installing a versioned shared library create the required links."""
|
||
|
Verbose = False
|
||
|
linknames = []
|
||
|
version, libname, install_dir = versionedLibVersion(dest, source, env)
|
||
|
|
||
|
if version != None:
|
||
|
# libname includes the version number if one was given
|
||
|
linknames = SCons.Tool.VersionShLibLinkNames(version,libname,env)
|
||
|
if Verbose:
|
||
|
print "versionedLibLinks: linknames ",linknames
|
||
|
# Here we just need the file name w/o path as the target of the link
|
||
|
lib_ver = libname
|
||
|
# make symlink of adjacent names in linknames
|
||
|
for count in range(len(linknames)):
|
||
|
linkname = linknames[count]
|
||
|
fulllinkname = os.path.join(install_dir, linkname)
|
||
|
if Verbose:
|
||
|
print "full link name ",fulllinkname
|
||
|
if count > 0:
|
||
|
try:
|
||
|
os.remove(lastlinkname)
|
||
|
except:
|
||
|
pass
|
||
|
os.symlink(os.path.basename(fulllinkname),lastlinkname)
|
||
|
if Verbose:
|
||
|
print "versionedLibLinks: made sym link of %s -> %s" % (lastlinkname,os.path.basename(fulllinkname))
|
||
|
lastlinkname = fulllinkname
|
||
|
# finish chain of sym links with link to the actual library
|
||
|
if len(linknames)>0:
|
||
|
try:
|
||
|
os.remove(lastlinkname)
|
||
|
except:
|
||
|
pass
|
||
|
os.symlink(lib_ver,lastlinkname)
|
||
|
if Verbose:
|
||
|
print "versionedLibLinks: made sym link of %s -> %s" % (lib_ver,lastlinkname)
|
||
|
return
|
||
|
|
||
|
def installFunc(target, source, env):
|
||
|
"""Install a source file into a target using the function specified
|
||
|
as the INSTALL construction variable."""
|
||
|
try:
|
||
|
install = env['INSTALL']
|
||
|
except KeyError:
|
||
|
raise SCons.Errors.UserError('Missing INSTALL construction variable.')
|
||
|
|
||
|
assert len(target)==len(source), \
|
||
|
"Installing source %s into target %s: target and source lists must have same length."%(list(map(str, source)), list(map(str, target)))
|
||
|
for t,s in zip(target,source):
|
||
|
if install(t.get_path(),s.get_path(),env):
|
||
|
return 1
|
||
|
|
||
|
return 0
|
||
|
|
||
|
def installFuncVersionedLib(target, source, env):
|
||
|
"""Install a versioned library into a target using the function specified
|
||
|
as the INSTALLVERSIONEDLIB construction variable."""
|
||
|
try:
|
||
|
install = env['INSTALLVERSIONEDLIB']
|
||
|
except KeyError:
|
||
|
raise SCons.Errors.UserError('Missing INSTALLVERSIONEDLIB construction variable.')
|
||
|
|
||
|
assert len(target)==len(source), \
|
||
|
"Installing source %s into target %s: target and source lists must have same length."%(list(map(str, source)), list(map(str, target)))
|
||
|
for t,s in zip(target,source):
|
||
|
if hasattr(t.attributes, 'shlibname'):
|
||
|
tpath = os.path.join(t.get_dir(), t.attributes.shlibname)
|
||
|
else:
|
||
|
tpath = t.get_path()
|
||
|
if install(tpath,s.get_path(),env):
|
||
|
return 1
|
||
|
|
||
|
return 0
|
||
|
|
||
|
def stringFunc(target, source, env):
|
||
|
installstr = env.get('INSTALLSTR')
|
||
|
if installstr:
|
||
|
return env.subst_target_source(installstr, 0, target, source)
|
||
|
target = str(target[0])
|
||
|
source = str(source[0])
|
||
|
if os.path.isdir(source):
|
||
|
type = 'directory'
|
||
|
else:
|
||
|
type = 'file'
|
||
|
return 'Install %s: "%s" as "%s"' % (type, source, target)
|
||
|
|
||
|
#
|
||
|
# Emitter functions
|
||
|
#
|
||
|
def add_targets_to_INSTALLED_FILES(target, source, env):
|
||
|
""" an emitter that adds all target files to the list stored in the
|
||
|
_INSTALLED_FILES global variable. This way all installed files of one
|
||
|
scons call will be collected.
|
||
|
"""
|
||
|
global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
|
||
|
_INSTALLED_FILES.extend(target)
|
||
|
|
||
|
_UNIQUE_INSTALLED_FILES = None
|
||
|
return (target, source)
|
||
|
|
||
|
def add_versioned_targets_to_INSTALLED_FILES(target, source, env):
|
||
|
""" an emitter that adds all target files to the list stored in the
|
||
|
_INSTALLED_FILES global variable. This way all installed files of one
|
||
|
scons call will be collected.
|
||
|
"""
|
||
|
global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
|
||
|
Verbose = False
|
||
|
_INSTALLED_FILES.extend(target)
|
||
|
if Verbose:
|
||
|
print "ver lib emitter ",repr(target)
|
||
|
|
||
|
# see if we have a versioned shared library, if so generate side effects
|
||
|
version, libname, install_dir = versionedLibVersion(target[0], source, env)
|
||
|
if version != None:
|
||
|
# generate list of link names
|
||
|
linknames = SCons.Tool.VersionShLibLinkNames(version,libname,env)
|
||
|
for linkname in linknames:
|
||
|
if Verbose:
|
||
|
print "make side effect of %s" % os.path.join(install_dir, linkname)
|
||
|
fulllinkname = os.path.join(install_dir, linkname)
|
||
|
env.SideEffect(fulllinkname,target[0])
|
||
|
env.Clean(target[0],fulllinkname)
|
||
|
_INSTALLED_FILES.append(fulllinkname)
|
||
|
if Verbose:
|
||
|
print "installed list ", _INSTALLED_FILES
|
||
|
|
||
|
_UNIQUE_INSTALLED_FILES = None
|
||
|
return (target, source)
|
||
|
|
||
|
class DESTDIR_factory(object):
|
||
|
""" a node factory, where all files will be relative to the dir supplied
|
||
|
in the constructor.
|
||
|
"""
|
||
|
def __init__(self, env, dir):
|
||
|
self.env = env
|
||
|
self.dir = env.arg2nodes( dir, env.fs.Dir )[0]
|
||
|
|
||
|
def Entry(self, name):
|
||
|
name = make_path_relative(name)
|
||
|
return self.dir.Entry(name)
|
||
|
|
||
|
def Dir(self, name):
|
||
|
name = make_path_relative(name)
|
||
|
return self.dir.Dir(name)
|
||
|
|
||
|
#
|
||
|
# The Builder Definition
|
||
|
#
|
||
|
install_action = SCons.Action.Action(installFunc, stringFunc)
|
||
|
installas_action = SCons.Action.Action(installFunc, stringFunc)
|
||
|
installVerLib_action = SCons.Action.Action(installFuncVersionedLib, stringFunc)
|
||
|
|
||
|
BaseInstallBuilder = None
|
||
|
|
||
|
def InstallBuilderWrapper(env, target=None, source=None, dir=None, **kw):
|
||
|
if target and dir:
|
||
|
import SCons.Errors
|
||
|
raise SCons.Errors.UserError("Both target and dir defined for Install(), only one may be defined.")
|
||
|
if not dir:
|
||
|
dir=target
|
||
|
|
||
|
import SCons.Script
|
||
|
install_sandbox = SCons.Script.GetOption('install_sandbox')
|
||
|
if install_sandbox:
|
||
|
target_factory = DESTDIR_factory(env, install_sandbox)
|
||
|
else:
|
||
|
target_factory = env.fs
|
||
|
|
||
|
try:
|
||
|
dnodes = env.arg2nodes(dir, target_factory.Dir)
|
||
|
except TypeError:
|
||
|
raise SCons.Errors.UserError("Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir))
|
||
|
sources = env.arg2nodes(source, env.fs.Entry)
|
||
|
tgt = []
|
||
|
for dnode in dnodes:
|
||
|
for src in sources:
|
||
|
# Prepend './' so the lookup doesn't interpret an initial
|
||
|
# '#' on the file name portion as meaning the Node should
|
||
|
# be relative to the top-level SConstruct directory.
|
||
|
target = env.fs.Entry('.'+os.sep+src.name, dnode)
|
||
|
#tgt.extend(BaseInstallBuilder(env, target, src, **kw))
|
||
|
tgt.extend(BaseInstallBuilder(env, target, src, **kw))
|
||
|
return tgt
|
||
|
|
||
|
def InstallAsBuilderWrapper(env, target=None, source=None, **kw):
|
||
|
result = []
|
||
|
for src, tgt in map(lambda x, y: (x, y), source, target):
|
||
|
#result.extend(BaseInstallBuilder(env, tgt, src, **kw))
|
||
|
result.extend(BaseInstallBuilder(env, tgt, src, **kw))
|
||
|
return result
|
||
|
|
||
|
BaseVersionedInstallBuilder = None
|
||
|
|
||
|
def InstallVersionedBuilderWrapper(env, target=None, source=None, dir=None, **kw):
|
||
|
if target and dir:
|
||
|
import SCons.Errors
|
||
|
raise SCons.Errors.UserError("Both target and dir defined for Install(), only one may be defined.")
|
||
|
if not dir:
|
||
|
dir=target
|
||
|
|
||
|
import SCons.Script
|
||
|
install_sandbox = SCons.Script.GetOption('install_sandbox')
|
||
|
if install_sandbox:
|
||
|
target_factory = DESTDIR_factory(env, install_sandbox)
|
||
|
else:
|
||
|
target_factory = env.fs
|
||
|
|
||
|
try:
|
||
|
dnodes = env.arg2nodes(dir, target_factory.Dir)
|
||
|
except TypeError:
|
||
|
raise SCons.Errors.UserError("Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir))
|
||
|
sources = env.arg2nodes(source, env.fs.Entry)
|
||
|
tgt = []
|
||
|
for dnode in dnodes:
|
||
|
for src in sources:
|
||
|
# Prepend './' so the lookup doesn't interpret an initial
|
||
|
# '#' on the file name portion as meaning the Node should
|
||
|
# be relative to the top-level SConstruct directory.
|
||
|
target = env.fs.Entry('.'+os.sep+src.name, dnode)
|
||
|
tgt.extend(BaseVersionedInstallBuilder(env, target, src, **kw))
|
||
|
return tgt
|
||
|
|
||
|
added = None
|
||
|
|
||
|
def generate(env):
|
||
|
|
||
|
from SCons.Script import AddOption, GetOption
|
||
|
global added
|
||
|
if not added:
|
||
|
added = 1
|
||
|
AddOption('--install-sandbox',
|
||
|
dest='install_sandbox',
|
||
|
type="string",
|
||
|
action="store",
|
||
|
help='A directory under which all installed files will be placed.')
|
||
|
|
||
|
global BaseInstallBuilder
|
||
|
if BaseInstallBuilder is None:
|
||
|
install_sandbox = GetOption('install_sandbox')
|
||
|
if install_sandbox:
|
||
|
target_factory = DESTDIR_factory(env, install_sandbox)
|
||
|
else:
|
||
|
target_factory = env.fs
|
||
|
|
||
|
BaseInstallBuilder = SCons.Builder.Builder(
|
||
|
action = install_action,
|
||
|
target_factory = target_factory.Entry,
|
||
|
source_factory = env.fs.Entry,
|
||
|
multi = 1,
|
||
|
emitter = [ add_targets_to_INSTALLED_FILES, ],
|
||
|
name = 'InstallBuilder')
|
||
|
|
||
|
global BaseVersionedInstallBuilder
|
||
|
if BaseVersionedInstallBuilder is None:
|
||
|
install_sandbox = GetOption('install_sandbox')
|
||
|
if install_sandbox:
|
||
|
target_factory = DESTDIR_factory(env, install_sandbox)
|
||
|
else:
|
||
|
target_factory = env.fs
|
||
|
|
||
|
BaseVersionedInstallBuilder = SCons.Builder.Builder(
|
||
|
action = installVerLib_action,
|
||
|
target_factory = target_factory.Entry,
|
||
|
source_factory = env.fs.Entry,
|
||
|
multi = 1,
|
||
|
emitter = [ add_versioned_targets_to_INSTALLED_FILES, ],
|
||
|
name = 'InstallVersionedBuilder')
|
||
|
|
||
|
env['BUILDERS']['_InternalInstall'] = InstallBuilderWrapper
|
||
|
env['BUILDERS']['_InternalInstallAs'] = InstallAsBuilderWrapper
|
||
|
env['BUILDERS']['_InternalInstallVersionedLib'] = InstallVersionedBuilderWrapper
|
||
|
|
||
|
# We'd like to initialize this doing something like the following,
|
||
|
# but there isn't yet support for a ${SOURCE.type} expansion that
|
||
|
# will print "file" or "directory" depending on what's being
|
||
|
# installed. For now we punt by not initializing it, and letting
|
||
|
# the stringFunc() that we put in the action fall back to the
|
||
|
# hand-crafted default string if it's not set.
|
||
|
#
|
||
|
#try:
|
||
|
# env['INSTALLSTR']
|
||
|
#except KeyError:
|
||
|
# env['INSTALLSTR'] = 'Install ${SOURCE.type}: "$SOURCES" as "$TARGETS"'
|
||
|
|
||
|
try:
|
||
|
env['INSTALL']
|
||
|
except KeyError:
|
||
|
env['INSTALL'] = copyFunc
|
||
|
|
||
|
try:
|
||
|
env['INSTALLVERSIONEDLIB']
|
||
|
except KeyError:
|
||
|
env['INSTALLVERSIONEDLIB'] = copyFuncVersionedLib
|
||
|
|
||
|
def exists(env):
|
||
|
return 1
|
||
|
|
||
|
# Local Variables:
|
||
|
# tab-width:4
|
||
|
# indent-tabs-mode:nil
|
||
|
# End:
|
||
|
# vim: set expandtab tabstop=4 shiftwidth=4:
|