summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGary Oberbrunner <garyo@oberbrunner.com>2014-07-20 19:49:33 (GMT)
committerGary Oberbrunner <garyo@oberbrunner.com>2014-07-20 19:49:33 (GMT)
commit53e30bebc2a633fc95589b9dfe663483775138a5 (patch)
tree3f718990ef6cce8fbe87d0c200e61d5818530f0c
parenta3b104e286e7d7bb3ab73e2943fe4faa5f0d43b7 (diff)
parentf7384a54a0d60838d10130f70790b4a16584c746 (diff)
downloadSCons-53e30bebc2a633fc95589b9dfe663483775138a5.zip
SCons-53e30bebc2a633fc95589b9dfe663483775138a5.tar.gz
SCons-53e30bebc2a633fc95589b9dfe663483775138a5.tar.bz2
Merged in williamblevins/scons (pull request #156, symlink copying)
-rw-r--r--doc/user/factories.xml21
-rw-r--r--src/CHANGES.txt1
-rw-r--r--src/engine/SCons/Defaults.py27
-rw-r--r--test/Copy-Symlinks.py174
4 files changed, 218 insertions, 5 deletions
diff --git a/doc/user/factories.xml b/doc/user/factories.xml
index 08f20e6..815efe3 100644
--- a/doc/user/factories.xml
+++ b/doc/user/factories.xml
@@ -176,6 +176,27 @@ touch $*
<scons_output_command>scons -Q</scons_output_command>
</scons_output>
+ <para>
+ The &Copy; factory supports symbolic link copying behavior
+ which is controlled by a third optional argument.
+ </para>
+
+ <para>
+ Symbolic links shallow copied as new symbolic links:
+ </para>
+
+ <para>
+ <literal>Command("LinkIn", "LinkOut1", Copy("$TARGET", "$SOURCE"[, True]))</literal>
+ </para>
+
+ <para>
+ Symbolic link deep copied as file target file or directory:
+ </para>
+
+ <para>
+ <literal>Command("LinkIn", "FileOut", Copy("$TARGET", "$SOURCE", False))</literal>
+ </para>
+
</section>
<section>
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 8ff593d..1081d6f 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -8,6 +8,7 @@ RELEASE 2.3.2.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE
From William Blevins:
- Added test for Java derived-source dependency tree generation.
+ - Added Copy Action symlink soft-copy support (#2395).
RELEASE 2.3.2
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 563e5a8..3f60bc0 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -181,20 +181,37 @@ def chmod_strfunc(dest, mode):
Chmod = ActionFactory(chmod_func, chmod_strfunc)
-def copy_func(dest, src):
+def copy_func(dest, src, symlinks=True):
+ """
+ If symlinks (is true), then a symbolic link will be
+ shallow copied and recreated as a symbolic link; otherwise, copying
+ a symbolic link will be equivalent to copying the symbolic link's
+ final target regardless of symbolic link depth.
+ """
+
+ dest = str(dest)
+ src = str(src)
+
SCons.Node.FS.invalidate_node_memos(dest)
if SCons.Util.is_List(src) and os.path.isdir(dest):
for file in src:
shutil.copy2(file, dest)
return 0
+ elif os.path.islink(src):
+ linkto = os.readlink(src)
+ if symlinks:
+ return os.symlink(linkto, dest)
+ else:
+ return copy_func(dest, linkto, symlinks)
elif os.path.isfile(src):
return shutil.copy2(src, dest)
else:
- return shutil.copytree(src, dest, 1)
+ return shutil.copytree(src, dest, symlinks)
-Copy = ActionFactory(copy_func,
- lambda dest, src: 'Copy("%s", "%s")' % (dest, src),
- convert=str)
+Copy = ActionFactory(
+ copy_func,
+ lambda dest, src, symlinks=True: 'Copy("%s", "%s")' % (dest, src)
+)
def delete_func(dest, must_exist=0):
SCons.Node.FS.invalidate_node_memos(dest)
diff --git a/test/Copy-Symlinks.py b/test/Copy-Symlinks.py
new file mode 100644
index 0000000..f8f92d7
--- /dev/null
+++ b/test/Copy-Symlinks.py
@@ -0,0 +1,174 @@
+#!/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__"
+
+"""
+Verify that the Copy() Action symlink soft-copy support works.
+"""
+
+import os
+import stat
+import sys
+import TestSCons
+
+import SCons.Defaults
+SCons.Defaults.DefaultEnvironment( tools = [] )
+
+test = TestSCons.TestSCons()
+
+filelinkToCopy = 'filelinkToCopy'
+fileToLink = 'file.in'
+fileContents = 'stuff n things\n'
+dirToLink = 'dir'
+dirlinkToCopy = 'dirlinkToCopy'
+treeToLink = 'tree'
+treelinkToCopy = 'treelinkToCopy'
+badToLink = 'None' # do not write this item
+badlinkToCopy = 'badlinkToCopy'
+
+try:
+ test.symlink( fileToLink, filelinkToCopy )
+ test.symlink( dirToLink, dirlinkToCopy )
+ test.symlink( treeToLink, treelinkToCopy )
+ test.symlink( badToLink, badlinkToCopy )
+except:
+ test.no_result()
+
+test.write( fileToLink, fileContents )
+test.subdir( dirToLink )
+test.subdir( treeToLink )
+test.write( os.path.join( treeToLink, fileToLink ), fileContents )
+
+test.write('SConstruct',
+"""\
+import SCons.Defaults
+SCons.Defaults.DefaultEnvironment( tools = [] )
+
+Execute( Copy( 'F1', '%(filelinkToCopy)s', False ) )
+Execute( Copy( 'L1', '%(filelinkToCopy)s' ) )
+Execute( Copy( 'L2', '%(filelinkToCopy)s', True ) )
+
+Execute( Copy( 'D1', '%(dirlinkToCopy)s', False ) )
+Execute( Copy( 'L3', '%(dirlinkToCopy)s' ) )
+Execute( Copy( 'L4', '%(dirlinkToCopy)s', True ) )
+
+Execute( Copy( 'T1', '%(treelinkToCopy)s', False ) )
+Execute( Copy( 'L5', '%(treelinkToCopy)s' ) )
+Execute( Copy( 'L6', '%(treelinkToCopy)s', True ) )
+
+Execute( Copy( 'Fails', '%(badlinkToCopy)s', False ) )
+Execute( Copy( 'L7', '%(badlinkToCopy)s' ) )
+Execute( Copy( 'L8', '%(badlinkToCopy)s', True ) )
+"""
+% locals()
+)
+
+test.must_exist( 'SConstruct' )
+test.must_exist( fileToLink )
+test.must_exist( filelinkToCopy )
+test.must_exist( dirlinkToCopy )
+test.must_exist( treelinkToCopy )
+test.must_not_exist( badToLink )
+test.must_exist( badlinkToCopy )
+
+expectStdout = test.wrap_stdout(
+read_str =
+'''\
+Copy("F1", "%(filelinkToCopy)s")
+Copy("L1", "%(filelinkToCopy)s")
+Copy("L2", "%(filelinkToCopy)s")
+Copy("D1", "%(dirlinkToCopy)s")
+Copy("L3", "%(dirlinkToCopy)s")
+Copy("L4", "%(dirlinkToCopy)s")
+Copy("T1", "%(treelinkToCopy)s")
+Copy("L5", "%(treelinkToCopy)s")
+Copy("L6", "%(treelinkToCopy)s")
+Copy("Fails", "badlinkToCopy")
+Copy("L7", "%(badlinkToCopy)s")
+Copy("L8", "%(badlinkToCopy)s")
+''' % locals(),
+build_str =
+'''\
+scons: `.' is up to date.
+'''
+)
+
+expectStderr = \
+'''\
+scons: *** None: No such file or directory
+'''
+
+test.run( stdout = expectStdout, stderr = expectStderr, status = None )
+
+test.must_exist('D1')
+test.must_exist('F1')
+test.must_exist('L2')
+test.must_exist('L3')
+test.must_exist('L4')
+test.must_exist('L5')
+test.must_exist('L6')
+test.must_exist('L7')
+test.must_exist('L8')
+test.must_exist('T1')
+test.must_not_exist( 'Fails' )
+
+test.must_match( fileToLink, fileContents )
+test.must_match( 'F1', fileContents )
+test.must_match( 'L1', fileContents )
+test.must_match( 'L2', fileContents )
+test.must_match( os.path.join( treeToLink, fileToLink ), fileContents )
+
+test.fail_test( condition=os.path.islink('D1') )
+test.fail_test( condition=os.path.islink('F1') )
+test.fail_test( condition=os.path.islink('T1') )
+test.fail_test( condition=(not os.path.isdir('D1')) )
+test.fail_test( condition=(not os.path.isfile('F1')) )
+test.fail_test( condition=(not os.path.isdir('T1')) )
+test.fail_test( condition=(not os.path.islink('L1')) )
+test.fail_test( condition=(not os.path.islink('L2')) )
+test.fail_test( condition=(not os.path.islink('L3')) )
+test.fail_test( condition=(not os.path.islink('L4')) )
+test.fail_test( condition=(not os.path.islink('L5')) )
+test.fail_test( condition=(not os.path.islink('L6')) )
+test.fail_test( condition=(not os.path.islink('L7')) )
+test.fail_test( condition=(not os.path.islink('L8')) )
+test.fail_test( condition=(os.path.exists('L7')) )
+test.fail_test( condition=(os.path.exists('L8')) )
+test.fail_test( condition=(os.readlink(filelinkToCopy) != os.readlink('L1')) )
+test.fail_test( condition=(os.readlink(filelinkToCopy) != os.readlink('L2')) )
+test.fail_test( condition=(os.readlink(dirlinkToCopy) != os.readlink('L3')) )
+test.fail_test( condition=(os.readlink(dirlinkToCopy) != os.readlink('L4')) )
+test.fail_test( condition=(os.readlink(treelinkToCopy) != os.readlink('L5')) )
+test.fail_test( condition=(os.readlink(treelinkToCopy) != os.readlink('L6')) )
+test.fail_test( condition=(os.readlink(badlinkToCopy) != os.readlink('L7')) )
+test.fail_test( condition=(os.readlink(badlinkToCopy) != os.readlink('L8')) )
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: