From a3620a45b3df9be32316bd8807ca5a83c1380c9a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 25 Mar 2020 13:29:50 -0400 Subject: [PATCH 1/4] Add test asserting that an executable script retains its executable bit. Ref #2041. --- setuptools/tests/test_build_py.py | 33 ++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index 92b455ddca..4cb11c1d33 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -26,9 +26,10 @@ def test_directories_in_package_data_glob(tmpdir_cwd): def test_read_only(tmpdir_cwd): """ - Ensure mode is not preserved in copy for package modules - and package data, as that causes problems - with deleting read-only files on Windows. + Ensure read-only flag is not preserved in copy + for package modules and package data, as that + causes problems with deleting read-only files on + Windows. #1451 """ @@ -47,3 +48,29 @@ def test_read_only(tmpdir_cwd): dist.parse_command_line() dist.run_commands() shutil.rmtree('build') + + +def test_executable_data(tmpdir_cwd): + """ + Ensure executable bit is preserved in copy for + package data, as users rely on it for scripts. + + #2041 + """ + dist = Distribution(dict( + script_name='setup.py', + script_args=['build_py'], + packages=['pkg'], + package_data={'pkg': ['run-me']}, + name='pkg', + )) + os.makedirs('pkg') + open('pkg/__init__.py', 'w').close() + open('pkg/run-me', 'w').close() + os.chmod('pkg/run-me', 0o700) + + dist.parse_command_line() + dist.run_commands() + + assert os.stat('build/lib/pkg/run-me').st_mode & stat.S_IEXEC, \ + "Script is not executable" From 70e95ee66a17e1655f70c9dbda107cea958f583f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 25 Mar 2020 13:44:58 -0400 Subject: [PATCH 2/4] When copying package data, make sure it's writable, but otherwise preserve the mode. Fixes #2041. --- setuptools/command/build_py.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index bac4fb1c93..9d0288a50d 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -7,6 +7,7 @@ import io import distutils.errors import itertools +import stat from setuptools.extern import six from setuptools.extern.six.moves import map, filter, filterfalse @@ -20,6 +21,10 @@ def run_2to3(self, files, doctests=True): "do nothing" +def make_writable(target): + os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE) + + class build_py(orig.build_py, Mixin2to3): """Enhanced 'build_py' command that includes data files with packages @@ -120,8 +125,8 @@ def build_package_data(self): target = os.path.join(build_dir, filename) self.mkpath(os.path.dirname(target)) srcfile = os.path.join(src_dir, filename) - outf, copied = self.copy_file( - srcfile, target, preserve_mode=False) + outf, copied = self.copy_file(srcfile, target) + make_writable(target) srcfile = os.path.abspath(srcfile) if (copied and srcfile in self.distribution.convert_2to3_doctests): From 8f8302da51016c6c98cf29417819fd0907e3993a Mon Sep 17 00:00:00 2001 From: jorikdima Date: Tue, 24 Mar 2020 20:21:35 -0700 Subject: [PATCH 3/4] Added description. --- changelog.d/2041.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/2041.bugfix.rst diff --git a/changelog.d/2041.bugfix.rst b/changelog.d/2041.bugfix.rst new file mode 100644 index 0000000000..8db757d89d --- /dev/null +++ b/changelog.d/2041.bugfix.rst @@ -0,0 +1 @@ +Preserve file modes during pkg files copying, but clear read only flag for target afterwards. From ef3a38644ccc9f65ea20493e25de0955695b9dff Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 25 Mar 2020 14:00:29 -0400 Subject: [PATCH 4/4] Mark test_executable_data as xfail on Windows. --- setuptools/tests/test_build_py.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index 4cb11c1d33..78a31ac49e 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -2,6 +2,8 @@ import stat import shutil +import pytest + from setuptools.dist import Distribution @@ -50,6 +52,12 @@ def test_read_only(tmpdir_cwd): shutil.rmtree('build') +@pytest.mark.xfail( + 'platform.system() == "Windows"', + reason="On Windows, files do not have executable bits", + raises=AssertionError, + strict=True, +) def test_executable_data(tmpdir_cwd): """ Ensure executable bit is preserved in copy for