3 Tool-specific initialization for the install tool.
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
11 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
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:
21 # The above copyright notice and this permission notice shall be included
22 # in all copies or substantial portions of the Software.
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.
33 __revision__ = "src/engine/SCons/Tool/install.py 3842 2008/12/20 22:59:52 scons"
40 from SCons.Util import make_path_relative
43 # We keep track of *all* installed files.
45 _UNIQUE_INSTALLED_FILES = None
48 # Functions doing the actual work of the Install Builder.
50 def copyFunc(dest, source, env):
51 """Install a source file or directory into a destination by copying,
52 (including copying permission/mode bits)."""
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))
59 parent = os.path.split(dest)[0]
60 if not os.path.exists(parent):
62 shutil.copytree(source, dest)
64 shutil.copy2(source, dest)
66 os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
70 def installFunc(target, source, env):
71 """Install a source file into a target using the function specified
72 as the INSTALL construction variable."""
74 install = env['INSTALL']
76 raise SCons.Errors.UserError('Missing INSTALL construction variable.')
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):
86 def stringFunc(target, source, env):
87 installstr = env.get('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):
96 return 'Install %s: "%s" as "%s"' % (type, source, target)
101 def 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.
106 global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
107 _INSTALLED_FILES.extend(target)
108 _UNIQUE_INSTALLED_FILES = None
109 return (target, source)
111 class DESTDIR_factory:
112 """ a node factory, where all files will be relative to the dir supplied
115 def __init__(self, env, dir):
117 self.dir = env.arg2nodes( dir, env.fs.Dir )[0]
119 def Entry(self, name):
120 name = make_path_relative(name)
121 return self.dir.Entry(name)
124 name = make_path_relative(name)
125 return self.dir.Dir(name)
128 # The Builder Definition
130 install_action = SCons.Action.Action(installFunc, stringFunc)
131 installas_action = SCons.Action.Action(installFunc, stringFunc)
133 BaseInstallBuilder = None
135 def InstallBuilderWrapper(env, target=None, source=None, dir=None, **kw):
138 raise SCons.Errors.UserError, "Both target and dir defined for Install(), only one may be defined."
143 install_sandbox = SCons.Script.GetOption('install_sandbox')
145 target_factory = DESTDIR_factory(env, install_sandbox)
147 target_factory = env.fs
150 dnodes = env.arg2nodes(dir, target_factory.Dir)
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)
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(apply(BaseInstallBuilder, (env, target, src), kw))
165 def InstallAsBuilderWrapper(env, target=None, source=None, **kw):
167 for src, tgt in map(lambda x, y: (x, y), source, target):
168 #result.extend(BaseInstallBuilder(env, tgt, src, **kw))
169 result.extend(apply(BaseInstallBuilder, (env, tgt, src), kw))
176 from SCons.Script import AddOption, GetOption
180 AddOption('--install-sandbox',
181 dest='install_sandbox',
184 help='A directory under which all installed files will be placed.')
186 global BaseInstallBuilder
187 if BaseInstallBuilder is None:
188 install_sandbox = GetOption('install_sandbox')
190 target_factory = DESTDIR_factory(env, install_sandbox)
192 target_factory = env.fs
194 BaseInstallBuilder = SCons.Builder.Builder(
195 action = install_action,
196 target_factory = target_factory.Entry,
197 source_factory = env.fs.Entry,
199 emitter = [ add_targets_to_INSTALLED_FILES, ],
200 name = 'InstallBuilder')
202 env['BUILDERS']['_InternalInstall'] = InstallBuilderWrapper
203 env['BUILDERS']['_InternalInstallAs'] = InstallAsBuilderWrapper
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.
215 # env['INSTALLSTR'] = 'Install ${SOURCE.type}: "$SOURCES" as "$TARGETS"'
220 env['INSTALL'] = copyFunc