From 5e3450b0552434281ba82e68ddd150f5d86e85d5 Mon Sep 17 00:00:00 2001 From: William Blevins Date: Sun, 13 Jul 2014 21:45:07 -0400 Subject: Issue 2395: Copy Symlink soft-copy enhancement. --- src/engine/SCons/Defaults.py | 20 ++++-- test/Copy-Symlinks.py | 149 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 test/Copy-Symlinks.py diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 563e5a8..b91a685 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -181,20 +181,30 @@ def chmod_strfunc(dest, mode): Chmod = ActionFactory(chmod_func, chmod_strfunc) -def copy_func(dest, src): +def copy_func(dest, src, symlinks=True): + 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..b2455c4 --- /dev/null +++ b/test/Copy-Symlinks.py @@ -0,0 +1,149 @@ +#!/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' + +try: + test.symlink( fileToLink, filelinkToCopy ) + test.symlink( dirToLink, dirlinkToCopy ) + test.symlink( treeToLink, treelinkToCopy ) +except: + test.no_result() + +test.write( fileToLink, fileContents ) +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( Mkdir( '%(dirToLink)s' ) ) +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 ) ) +""" +% locals() +) + +test.must_exist( 'SConstruct' ) +test.must_exist( fileToLink ) +test.must_exist( filelinkToCopy ) +test.must_exist( dirlinkToCopy ) +test.must_exist( treelinkToCopy ) + +expect = test.wrap_stdout( +read_str = +'''\ +Copy("F1", "%(filelinkToCopy)s") +Copy("L1", "%(filelinkToCopy)s") +Copy("L2", "%(filelinkToCopy)s") +Mkdir("%(dirToLink)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") +''' % locals(), +build_str = +'''\ +scons: `.' is up to date. +''' +) + +test.run( stdout = expect ) + +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('T1') + +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=(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.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v0.12 From 5298a7b408e87b9fbfb0a4c5193b36698913a47c Mon Sep 17 00:00:00 2001 From: William Blevins Date: Tue, 15 Jul 2014 23:22:15 -0400 Subject: Added Issue 2395 information to CHANGES.txt --- src/CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index f20fd32..6145e63 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -6,6 +6,8 @@ RELEASE 2.3.2.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE + From William Blevins: + - Added Copy Action symlink soft-copy support (#2395). RELEASE 2.3.2 -- cgit v0.12 From 84a2d823537b9257b7e4b4a5daacb8fc0b962d16 Mon Sep 17 00:00:00 2001 From: William Blevins Date: Sat, 19 Jul 2014 21:19:44 -0400 Subject: Issue 2395: Added doc comment for symlink parameter usage. --- src/engine/SCons/Defaults.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index b91a685..3f60bc0 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -182,6 +182,13 @@ def chmod_strfunc(dest, mode): Chmod = ActionFactory(chmod_func, chmod_strfunc) 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) -- cgit v0.12 From 78dde7017bdd278f24ea885514fbbf0e6c600711 Mon Sep 17 00:00:00 2001 From: William Blevins Date: Sun, 20 Jul 2014 13:01:59 -0400 Subject: Issue 2395: Added tests from copying broken symlinks. --- test/Copy-Symlinks.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/test/Copy-Symlinks.py b/test/Copy-Symlinks.py index b2455c4..f8f92d7 100644 --- a/test/Copy-Symlinks.py +++ b/test/Copy-Symlinks.py @@ -45,15 +45,19 @@ 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 ) @@ -66,7 +70,6 @@ Execute( Copy( 'F1', '%(filelinkToCopy)s', False ) ) Execute( Copy( 'L1', '%(filelinkToCopy)s' ) ) Execute( Copy( 'L2', '%(filelinkToCopy)s', True ) ) -Execute( Mkdir( '%(dirToLink)s' ) ) Execute( Copy( 'D1', '%(dirlinkToCopy)s', False ) ) Execute( Copy( 'L3', '%(dirlinkToCopy)s' ) ) Execute( Copy( 'L4', '%(dirlinkToCopy)s', True ) ) @@ -74,6 +77,10 @@ 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() ) @@ -83,20 +90,24 @@ 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 ) -expect = test.wrap_stdout( +expectStdout = test.wrap_stdout( read_str = '''\ Copy("F1", "%(filelinkToCopy)s") Copy("L1", "%(filelinkToCopy)s") Copy("L2", "%(filelinkToCopy)s") -Mkdir("%(dirToLink)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 = '''\ @@ -104,7 +115,12 @@ scons: `.' is up to date. ''' ) -test.run( stdout = expect ) +expectStderr = \ +'''\ +scons: *** None: No such file or directory +''' + +test.run( stdout = expectStdout, stderr = expectStderr, status = None ) test.must_exist('D1') test.must_exist('F1') @@ -113,7 +129,10 @@ 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 ) @@ -133,12 +152,18 @@ 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() -- cgit v0.12 From f7384a54a0d60838d10130f70790b4a16584c746 Mon Sep 17 00:00:00 2001 From: William Blevins Date: Sun, 20 Jul 2014 15:16:12 -0400 Subject: Issue 2395: Added user guide details for Copy symbolic link support. --- doc/user/factories.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) 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 -Q + + The &Copy; factory supports symbolic link copying behavior + which is controlled by a third optional argument. + + + + Symbolic links shallow copied as new symbolic links: + + + + Command("LinkIn", "LinkOut1", Copy("$TARGET", "$SOURCE"[, True])) + + + + Symbolic link deep copied as file target file or directory: + + + + Command("LinkIn", "FileOut", Copy("$TARGET", "$SOURCE", False)) + +
-- cgit v0.12