Commit 59953087 authored by Brett Cannon's avatar Brett Cannon

merge

parents dcf22693 25fefc47
...@@ -41,6 +41,13 @@ byte-code cache files in the directory containing the source code. ...@@ -41,6 +41,13 @@ byte-code cache files in the directory containing the source code.
is raised. This function returns the path to byte-compiled file, i.e. is raised. This function returns the path to byte-compiled file, i.e.
whatever *cfile* value was used. whatever *cfile* value was used.
If the path that *cfile* becomes (either explicitly specified or computed)
is a symlink or non-regular file, :exc:`FileExistsError` will be raised.
This is to act as a warning that import will turn those paths into regular
files if it is allowed to write byte-compiled files to those paths. This is
a side-effect of import using file renaming to place the final byte-compiled
file into place to prevent concurrent file writing issues.
*optimize* controls the optimization level and is passed to the built-in *optimize* controls the optimization level and is passed to the built-in
:func:`compile` function. The default of ``-1`` selects the optimization :func:`compile` function. The default of ``-1`` selects the optimization
level of the current interpreter. level of the current interpreter.
...@@ -53,7 +60,9 @@ byte-code cache files in the directory containing the source code. ...@@ -53,7 +60,9 @@ byte-code cache files in the directory containing the source code.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Changed code to use :mod:`importlib` for the byte-code cache file writing. Changed code to use :mod:`importlib` for the byte-code cache file writing.
This means file creation/writing semantics now match what :mod:`importlib` This means file creation/writing semantics now match what :mod:`importlib`
does, e.g. permissions, write-and-move semantics, etc. does, e.g. permissions, write-and-move semantics, etc. Also added the
caveat that :exc:`FileExistsError` is raised if *cfile* is a symlink or
non-regular file.
.. function:: main(args=None) .. function:: main(args=None)
......
...@@ -272,3 +272,8 @@ that may require changes to your code. ...@@ -272,3 +272,8 @@ that may require changes to your code.
* :c:func:`PyErr_SetImportError` now sets :exc:`TypeError` when its **msg** * :c:func:`PyErr_SetImportError` now sets :exc:`TypeError` when its **msg**
argument is not set. Previously only ``NULL`` was returned. argument is not set. Previously only ``NULL`` was returned.
* :func:`py_compile.compile` now raises :exc:`FileExistsError` if the file path
it would write to is a symlink or a non-regular file. This is to act as a
warning that import will overwrite those files with a regular file regardless
of what type of file path they were originally.
...@@ -7,6 +7,7 @@ import imp ...@@ -7,6 +7,7 @@ import imp
import importlib._bootstrap import importlib._bootstrap
import importlib.machinery import importlib.machinery
import os import os
import os.path
import sys import sys
import traceback import traceback
...@@ -96,12 +97,25 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1): ...@@ -96,12 +97,25 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1):
See compileall.py for a script/module that uses this module to See compileall.py for a script/module that uses this module to
byte-compile all installed files (or all files in selected byte-compile all installed files (or all files in selected
directories). directories).
Do note that FileExistsError is raised if cfile ends up pointing at a
non-regular file or symlink. Because the compilation uses a file renaming,
the resulting file would be regular and thus not the same type of file as
it was previously.
""" """
if cfile is None: if cfile is None:
if optimize >= 0: if optimize >= 0:
cfile = imp.cache_from_source(file, debug_override=not optimize) cfile = imp.cache_from_source(file, debug_override=not optimize)
else: else:
cfile = imp.cache_from_source(file) cfile = imp.cache_from_source(file)
if os.path.islink(cfile):
msg = ('{} is a symlink and will be changed into a regular file if '
'import writes a byte-compiled file to it')
raise FileExistsError(msg.format(file, cfile))
elif os.path.exists(cfile) and not os.path.isfile(cfile):
msg = ('{} is a non-regular file and will be changed into a regular '
'one if import writes a byte-compiled file to it')
raise FileExistsError(msg.format(file, cfile))
loader = importlib.machinery.SourceFileLoader('<py_compile>', file) loader = importlib.machinery.SourceFileLoader('<py_compile>', file)
source_bytes = loader.get_data(file) source_bytes = loader.get_data(file)
try: try:
......
...@@ -36,6 +36,26 @@ class PyCompileTests(unittest.TestCase): ...@@ -36,6 +36,26 @@ class PyCompileTests(unittest.TestCase):
self.assertTrue(os.path.exists(self.pyc_path)) self.assertTrue(os.path.exists(self.pyc_path))
self.assertFalse(os.path.exists(self.cache_path)) self.assertFalse(os.path.exists(self.cache_path))
def test_do_not_overwrite_symlinks(self):
# In the face of a cfile argument being a symlink, bail out.
# Issue #17222
try:
os.symlink(self.pyc_path + '.actual', self.pyc_path)
except OSError:
self.skipTest('need to be able to create a symlink for a file')
else:
assert os.path.islink(self.pyc_path)
with self.assertRaises(FileExistsError):
py_compile.compile(self.source_path, self.pyc_path)
@unittest.skipIf(not os.path.exists(os.devnull) or os.path.isfile(os.devnull),
'requires os.devnull and for it to be a non-regular file')
def test_do_not_overwrite_nonregular_files(self):
# In the face of a cfile argument being a non-regular file, bail out.
# Issue #17222
with self.assertRaises(FileExistsError):
py_compile.compile(self.source_path, os.devnull)
def test_cache_path(self): def test_cache_path(self):
py_compile.compile(self.source_path) py_compile.compile(self.source_path)
self.assertTrue(os.path.exists(self.cache_path)) self.assertTrue(os.path.exists(self.cache_path))
......
...@@ -1008,6 +1008,9 @@ Library ...@@ -1008,6 +1008,9 @@ Library
Python file. Patch by Ben Morgan. Python file. Patch by Ben Morgan.
- Have py_compile use importlib as much as possible to avoid code duplication. - Have py_compile use importlib as much as possible to avoid code duplication.
Code now raises FileExistsError if the file path to be used for the
byte-compiled file is a symlink or non-regular file as a warning that import
will not keep the file path type if it writes to that path.
- Issue #180022: Have site.addpackage() consider already known paths even when - Issue #180022: Have site.addpackage() consider already known paths even when
none are explicitly passed in. Bug report and fix by Kirill. none are explicitly passed in. Bug report and fix by Kirill.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment