Skip to content

Commit 39115f4

Browse files
committed
gh-160782: replace symlinked files in the venv directory
1 parent 4a9b981 commit 39115f4

3 files changed

Lines changed: 33 additions & 0 deletions

File tree

‎Lib/test/test_venv.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,29 @@ def test_unoverwritable_fails(self):
663663
self.assertRaises((ValueError, OSError), venv.create, self.env_dir)
664664
self.clear_directory(self.env_dir)
665665

666+
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
667+
def test_symlinked_file_replaced(self):
668+
"""
669+
Test that venv creation replaces a symlink where it writes a file.
670+
"""
671+
outside_dir = tempfile.mkdtemp()
672+
self.addCleanup(rmtree, outside_dir)
673+
outside = os.path.join(outside_dir, 'outside')
674+
with open(outside, 'w', encoding='utf-8') as f:
675+
f.write('untouched\n')
676+
links = ['pyvenv.cfg', '.gitignore',
677+
os.path.join(self.bindir, 'activate')]
678+
os.mkdir(os.path.join(self.env_dir, self.bindir))
679+
for link in links:
680+
os.symlink(outside, self.get_env_file(link))
681+
venv.create(self.env_dir, scm_ignore_files={'git'})
682+
for link in links:
683+
with self.subTest(link=link):
684+
self.assertFalse(os.path.islink(self.get_env_file(link)))
685+
self.assertTrue(os.path.isfile(self.get_env_file(link)))
686+
with open(outside, encoding='utf-8') as f:
687+
self.assertEqual(f.read(), 'untouched\n')
688+
666689
def test_upgrade(self):
667690
"""
668691
Test upgrading an existing environment directory.

‎Lib/venv/__init__.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
logger = logging.getLogger(__name__)
1919

2020

21+
def _unlink_symlink(path):
22+
if os.path.islink(path):
23+
os.unlink(path)
24+
25+
2126
class EnvBuilder:
2227
"""
2328
This class exists to allow virtual environment creation to be
@@ -223,6 +228,7 @@ def create_configuration(self, context):
223228
being processed.
224229
"""
225230
context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')
231+
_unlink_symlink(path)
226232
with open(path, 'w', encoding='utf-8') as f:
227233
f.write('home = %s\n' % context.python_dir)
228234
if self.system_site_packages:
@@ -291,6 +297,7 @@ def create_git_ignore_file(self, context):
291297
ignored by git.
292298
"""
293299
gitignore_path = os.path.join(context.env_dir, '.gitignore')
300+
_unlink_symlink(gitignore_path)
294301
with open(gitignore_path, 'w', encoding='utf-8') as file:
295302
file.write('# Created by venv; '
296303
'see https://docs.python.org/3/library/venv.html\n')
@@ -585,6 +592,7 @@ def skip_file(f):
585592
if not os.path.exists(dstdir):
586593
os.makedirs(dstdir)
587594
dstfile = os.path.join(dstdir, f)
595+
_unlink_symlink(dstfile)
588596
if os.name == 'nt' and srcfile.endswith(('.exe', '.pdb')):
589597
shutil.copy2(srcfile, dstfile)
590598
continue
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`venv`: a symbolic link in place of ``pyvenv.cfg``, ``.gitignore`` or
2+
an activation script is now replaced instead of written through.

0 commit comments

Comments
 (0)