From 3726cb0f146cb229a5e9db8d41c713b023dcd474 Mon Sep 17 00:00:00 2001 From: hetmankp <728670+hetmankp@users.noreply.github.com> Date: Tue, 24 Oct 2023 00:55:39 +1100 Subject: [PATCH] gh-105931: Fix surprising compileall stripdir behaviour (GH-108671) Before, the '-s STRIPDIR' option on compileall lead to some surprising results as it only strips away path components that match, but leaves alone the non-matching ones interspersed in between. For example, with: python -m compileall -s/path/to/another/src /path/to/build/src/file.py The resulting written path will be: build/file.py This fix only strips directories that are a fully matching prefix of the source path. If a stripdir is provided that is not a valid prefix, a warning will be displayed (which can be silenced with '-qq'). --- Lib/compileall.py | 12 +++++----- Lib/test/test_compileall.py | 23 +++++++++++++++++++ ...-08-30-19-10-35.gh-issue-105931.Lpwve8.rst | 8 +++++++ 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-08-30-19-10-35.gh-issue-105931.Lpwve8.rst diff --git a/Lib/compileall.py b/Lib/compileall.py index d394156cedc..9b53086bf41 100644 --- a/Lib/compileall.py +++ b/Lib/compileall.py @@ -172,13 +172,13 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, if stripdir is not None: fullname_parts = fullname.split(os.path.sep) stripdir_parts = stripdir.split(os.path.sep) - ddir_parts = list(fullname_parts) - for spart, opart in zip(stripdir_parts, fullname_parts): - if spart == opart: - ddir_parts.remove(spart) - - dfile = os.path.join(*ddir_parts) + if stripdir_parts != fullname_parts[:len(stripdir_parts)]: + if quiet < 2: + print("The stripdir path {!r} is not a valid prefix for " + "source path {!r}; ignoring".format(stripdir, fullname)) + else: + dfile = os.path.join(*fullname_parts[len(stripdir_parts):]) if prependdir is not None: if dfile is None: diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index 9cd92ad365c..83a9532aecf 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -362,6 +362,29 @@ class CompileallTestsBase: str(err, encoding=sys.getdefaultencoding()) ) + def test_strip_only_invalid(self): + fullpath = ["test", "build", "real", "path"] + path = os.path.join(self.directory, *fullpath) + os.makedirs(path) + script = script_helper.make_script(path, "test", "1 / 0") + bc = importlib.util.cache_from_source(script) + stripdir = os.path.join(self.directory, *(fullpath[:2] + ['fake'])) + compileall.compile_dir(path, quiet=True, stripdir=stripdir) + rc, out, err = script_helper.assert_python_failure(bc) + expected_not_in = os.path.join(self.directory, *fullpath[2:]) + self.assertIn( + path, + str(err, encoding=sys.getdefaultencoding()) + ) + self.assertNotIn( + expected_not_in, + str(err, encoding=sys.getdefaultencoding()) + ) + self.assertNotIn( + stripdir, + str(err, encoding=sys.getdefaultencoding()) + ) + def test_prepend_only(self): fullpath = ["test", "build", "real", "path"] path = os.path.join(self.directory, *fullpath) diff --git a/Misc/NEWS.d/next/Library/2023-08-30-19-10-35.gh-issue-105931.Lpwve8.rst b/Misc/NEWS.d/next/Library/2023-08-30-19-10-35.gh-issue-105931.Lpwve8.rst new file mode 100644 index 00000000000..4e769ec4c47 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-08-30-19-10-35.gh-issue-105931.Lpwve8.rst @@ -0,0 +1,8 @@ +Change :mod:`compileall` to only strip the stripdir prefix from the full path +recorded in the compiled ``.pyc`` file, when the prefix matches the start of +the full path in its entirety. When the prefix does not match, no stripping is +performed and a warning to this effect is displayed. + +Previously all path components of the stripdir prefix that matched the full +path were removed, while those that did not match were left alone (including +ones interspersed between matching components).