1"""SCons.Tool.install
2
3Tool-specific initialization for the install tool.
4
5There normally shouldn't be any need to import this module directly.
6It will usually be imported through the generic SCons.Tool.Tool()
7selection method.
8"""
9
10#
11# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation
12#
13# Permission is hereby granted, free of charge, to any person obtaining
14# a copy of this software and associated documentation files (the
15# "Software"), to deal in the Software without restriction, including
16# without limitation the rights to use, copy, modify, merge, publish,
17# distribute, sublicense, and/or sell copies of the Software, and to
18# permit persons to whom the Software is furnished to do so, subject to
19# the following conditions:
20#
21# The above copyright notice and this permission notice shall be included
22# in all copies or substantial portions of the Software.
23#
24# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
25# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
26# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31#
32
33__revision__ = "src/engine/SCons/Tool/install.py 4369 2009/09/19 15:58:29 scons"
34
35import os
36import shutil
37import stat
38
39import SCons.Action
40from SCons.Util import make_path_relative
41
42#
43# We keep track of *all* installed files.
44_INSTALLED_FILES = []
45_UNIQUE_INSTALLED_FILES = None
46
47#
48# Functions doing the actual work of the Install Builder.
49#
50def copyFunc(dest, source, env):
51    """Install a source file or directory into a destination by copying,
52    (including copying permission/mode bits)."""
53
54    if os.path.isdir(source):
55        if os.path.exists(dest):
56            if not os.path.isdir(dest):
57                raise SCons.Errors.UserError("cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source)))
58        else:
59            parent = os.path.split(dest)[0]
60            if not os.path.exists(parent):
61                os.makedirs(parent)
62        shutil.copytree(source, dest)
63    else:
64        shutil.copy2(source, dest)
65        st = os.stat(source)
66        os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
67
68    return 0
69
70def installFunc(target, source, env):
71    """Install a source file into a target using the function specified
72    as the INSTALL construction variable."""
73    try:
74        install = env['INSTALL']
75    except KeyError:
76        raise SCons.Errors.UserError('Missing INSTALL construction variable.')
77
78    assert len(target)==len(source), \
79           "Installing source %s into target %s: target and source lists must have same length."%(map(str, source), map(str, target))
80    for t,s in zip(target,source):
81        if install(t.get_path(),s.get_path(),env):
82            return 1
83
84    return 0
85
86def stringFunc(target, source, env):
87    installstr = env.get('INSTALLSTR')
88    if installstr:
89        return env.subst_target_source(installstr, 0, target, source)
90    target = str(target[0])
91    source = str(source[0])
92    if os.path.isdir(source):
93        type = 'directory'
94    else:
95        type = 'file'
96    return 'Install %s: "%s" as "%s"' % (type, source, target)
97
98#
99# Emitter functions
100#
101def add_targets_to_INSTALLED_FILES(target, source, env):
102    """ an emitter that adds all target files to the list stored in the
103    _INSTALLED_FILES global variable. This way all installed files of one
104    scons call will be collected.
105    """
106    global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
107    _INSTALLED_FILES.extend(target)
108    _UNIQUE_INSTALLED_FILES = None
109    return (target, source)
110
111class DESTDIR_factory:
112    """ a node factory, where all files will be relative to the dir supplied
113    in the constructor.
114    """
115    def __init__(self, env, dir):
116        self.env = env
117        self.dir = env.arg2nodes( dir, env.fs.Dir )[0]
118
119    def Entry(self, name):
120        name = make_path_relative(name)
121        return self.dir.Entry(name)
122
123    def Dir(self, name):
124        name = make_path_relative(name)
125        return self.dir.Dir(name)
126
127#
128# The Builder Definition
129#
130install_action   = SCons.Action.Action(installFunc, stringFunc)
131installas_action = SCons.Action.Action(installFunc, stringFunc)
132
133BaseInstallBuilder               = None
134
135def InstallBuilderWrapper(env, target=None, source=None, dir=None, **kw):
136    if target and dir:
137        import SCons.Errors
138        raise SCons.Errors.UserError("Both target and dir defined for Install(), only one may be defined.")
139    if not dir:
140        dir=target
141
142    import SCons.Script
143    install_sandbox = SCons.Script.GetOption('install_sandbox')
144    if install_sandbox:
145        target_factory = DESTDIR_factory(env, install_sandbox)
146    else:
147        target_factory = env.fs
148
149    try:
150        dnodes = env.arg2nodes(dir, target_factory.Dir)
151    except TypeError:
152        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))
153    sources = env.arg2nodes(source, env.fs.Entry)
154    tgt = []
155    for dnode in dnodes:
156        for src in sources:
157            # Prepend './' so the lookup doesn't interpret an initial
158            # '#' on the file name portion as meaning the Node should
159            # be relative to the top-level SConstruct directory.
160            target = env.fs.Entry('.'+os.sep+src.name, dnode)
161            #tgt.extend(BaseInstallBuilder(env, target, src, **kw))
162            tgt.extend(BaseInstallBuilder(*(env, target, src), **kw))
163    return tgt
164
165def InstallAsBuilderWrapper(env, target=None, source=None, **kw):
166    result = []
167    for src, tgt in map(lambda x, y: (x, y), source, target):
168        #result.extend(BaseInstallBuilder(env, tgt, src, **kw))
169        result.extend(BaseInstallBuilder(*(env, tgt, src), **kw))
170    return result
171
172added = None
173
174def generate(env):
175
176    from SCons.Script import AddOption, GetOption
177    global added
178    if not added:
179        added = 1
180        AddOption('--install-sandbox',
181                  dest='install_sandbox',
182                  type="string",
183                  action="store",
184                  help='A directory under which all installed files will be placed.')
185
186    global BaseInstallBuilder
187    if BaseInstallBuilder is None:
188        install_sandbox = GetOption('install_sandbox')
189        if install_sandbox:
190            target_factory = DESTDIR_factory(env, install_sandbox)
191        else:
192            target_factory = env.fs
193
194        BaseInstallBuilder = SCons.Builder.Builder(
195                              action         = install_action,
196                              target_factory = target_factory.Entry,
197                              source_factory = env.fs.Entry,
198                              multi          = 1,
199                              emitter        = [ add_targets_to_INSTALLED_FILES, ],
200                              name           = 'InstallBuilder')
201
202    env['BUILDERS']['_InternalInstall'] = InstallBuilderWrapper
203    env['BUILDERS']['_InternalInstallAs'] = InstallAsBuilderWrapper
204
205    # We'd like to initialize this doing something like the following,
206    # but there isn't yet support for a ${SOURCE.type} expansion that
207    # will print "file" or "directory" depending on what's being
208    # installed.  For now we punt by not initializing it, and letting
209    # the stringFunc() that we put in the action fall back to the
210    # hand-crafted default string if it's not set.
211    #
212    #try:
213    #    env['INSTALLSTR']
214    #except KeyError:
215    #    env['INSTALLSTR'] = 'Install ${SOURCE.type}: "$SOURCES" as "$TARGETS"'
216
217    try:
218        env['INSTALL']
219    except KeyError:
220        env['INSTALL']    = copyFunc
221
222def exists(env):
223    return 1
224
225# Local Variables:
226# tab-width:4
227# indent-tabs-mode:nil
228# End:
229# vim: set expandtab tabstop=4 shiftwidth=4:
230