summaryrefslogtreecommitdiff
path: root/engine/SCons/Tool/install.py
diff options
context:
space:
mode:
Diffstat (limited to 'engine/SCons/Tool/install.py')
-rw-r--r--engine/SCons/Tool/install.py493
1 files changed, 493 insertions, 0 deletions
diff --git a/engine/SCons/Tool/install.py b/engine/SCons/Tool/install.py
new file mode 100644
index 0000000..5aff9a8
--- /dev/null
+++ b/engine/SCons/Tool/install.py
@@ -0,0 +1,493 @@
+"""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 - 2014 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 2014/09/27 12:51:43 garyo"
+
+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, 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'):
+ return (None, None, None)
+
+ libname = os.path.basename(dest)
+ install_dir = os.path.dirname(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':
+ # 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, 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 install(t.get_path(),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].path, 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: