-
-
Notifications
You must be signed in to change notification settings - Fork 35.1k
gh-96867: Set zipapp execute bits from readable bits #151970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8460f8d
a5e3cb7
3159382
127ca4b
2de44db
48c9437
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| """Test harness for the zipapp module.""" | ||
|
|
||
| import io | ||
| import os | ||
| import pathlib | ||
| import stat | ||
| import sys | ||
|
|
@@ -366,6 +367,38 @@ def test_shebang_is_executable(self): | |
| zipapp.create_archive(str(source), str(target), interpreter='python') | ||
| self.assertTrue(target.stat().st_mode & stat.S_IEXEC) | ||
|
|
||
| @unittest.skipIf(sys.platform == 'win32', | ||
| 'Windows does not support an executable bit') | ||
| @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') | ||
| @os_helper.skip_unless_working_chmod | ||
| def test_shebang_executable_bits_match_readable_bits(self): | ||
| source = self.tmpdir / 'source' | ||
| source.mkdir() | ||
| (source / '__main__.py').touch() | ||
| for umask, expected_mode in ((0o022, 0o755), (0o077, 0o700)): | ||
| with self.subTest(umask=umask): | ||
| target = self.tmpdir / f'source-{umask:o}.pyz' | ||
| with os_helper.temp_umask(umask): | ||
| zipapp.create_archive(source, target, interpreter='python') | ||
| self.assertEqual(stat.S_IMODE(target.stat().st_mode), expected_mode) | ||
|
|
||
| @unittest.skipIf(sys.platform == 'win32', | ||
| 'Windows does not support an executable bit') | ||
| @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') | ||
| @os_helper.skip_unless_working_chmod | ||
| def test_copied_shebang_executable_bits_match_readable_bits(self): | ||
| source = self.tmpdir / 'source' | ||
| source.mkdir() | ||
| (source / '__main__.py').touch() | ||
| archive = self.tmpdir / 'source.pyz' | ||
| zipapp.create_archive(source, archive) | ||
| for umask, expected_mode in ((0o022, 0o755), (0o077, 0o700)): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment about octal constants here. |
||
| with self.subTest(umask=umask): | ||
| target = self.tmpdir / f'target-{umask:o}.pyz' | ||
| with os_helper.temp_umask(umask): | ||
| zipapp.create_archive(archive, target, interpreter='python') | ||
| self.assertEqual(stat.S_IMODE(target.stat().st_mode), expected_mode) | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't the existing tests also need fixing? At the very least, |
||
| @unittest.skipIf(sys.platform == 'win32', | ||
| 'Windows does not support an executable bit') | ||
| def test_no_shebang_is_not_executable(self): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,16 @@ def _write_file_prefix(f, interpreter): | |
| f.write(shebang) | ||
|
|
||
|
|
||
| def _make_executable(path): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather this function required that That requires changing the call in |
||
| mode = os.stat(path).st_mode | ||
| executable = ( | ||
| (mode & stat.S_IRUSR) >> 2 | ||
| | (mode & stat.S_IRGRP) >> 2 | ||
| | (mode & stat.S_IROTH) >> 2 | ||
| ) | ||
| os.chmod(path, mode | executable) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not feel at all understandable. Why are we doing Also, while I know this also applies to the existing code, the concept of an "executable bit" doesn't exist on Windows, so this code should be Unix-only. In fact the documentation explicitly says that this only happens on POSIX. We should fix this at the same time. |
||
|
|
||
|
|
||
| def _copy_archive(archive, new_archive, interpreter=None): | ||
| """Copy an application archive, modifying the shebang line.""" | ||
| with _maybe_open(archive, 'rb') as src: | ||
|
|
@@ -69,8 +79,8 @@ def _copy_archive(archive, new_archive, interpreter=None): | |
| dst.write(first_2) | ||
| shutil.copyfileobj(src, dst) | ||
|
|
||
| if interpreter and isinstance(new_archive, str): | ||
| os.chmod(new_archive, os.stat(new_archive).st_mode | stat.S_IEXEC) | ||
| if interpreter and isinstance(new_archive, (str, os.PathLike)): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for spotting and fixing the discrepancy between this line and the test in |
||
| _make_executable(new_archive) | ||
|
|
||
|
|
||
| def create_archive(source, target=None, interpreter=None, main=None, | ||
|
|
@@ -169,7 +179,7 @@ def create_archive(source, target=None, interpreter=None, main=None, | |
| z.writestr('__main__.py', main_py.encode('utf-8')) | ||
|
|
||
| if interpreter and not hasattr(target, 'write'): | ||
| target.chmod(target.stat().st_mode | stat.S_IEXEC) | ||
| _make_executable(target) | ||
|
|
||
|
|
||
| def get_interpreter(archive): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Fix :mod:`zipapp` to set executable bits on archives with shebangs based on | ||
| their readable permission bits. Contributed by Xiao Yuan. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't use octal constants here. They aren't understandable to readers who aren't familiar with the octal representation of Unix permissions (e.g., me...)