Skip to content

Commit

Permalink
Fix for Python 2 framework support on macOS 12.3
Browse files Browse the repository at this point in the history
virtualenv's trick of adding a symlink to the Python dylib --
`/path/to/Python.framework/Versions/2.7/Python` -- in the virtualenv
root and rewriting the dependent dylib list of `<virtualenv>/bin/python`
with the path of this symlink no longer works in 12.3.

As of macOS 12.3, when <https://github.com/python/cpython/blob/v2.7.18/Modules/getpath.c>
computes `sys.prefix`, it ends up resolving the symlink. The result is
that `sys.prefix` points to the Python framework, e.g.
`/path/to/Python.framework/Versions/2.7` instead of at the virtualenv
root.

To mitigate this, add a _copy_ of the Python dylib into the virtualenv
instead of a symlink. To placate code signing, add a symlink to the
`Resources` dir so the `Info.plist` file is accessible.
  • Loading branch information
nickhutchinson committed Mar 29, 2022
1 parent 1aa302f commit 408c3bb
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 20 deletions.
1 change: 1 addition & 0 deletions docs/changelog/2284.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix for creating virtualenvs from a Python 2.7 framework on macOS 12.3 - by :user:`nickhutchinson`.
46 changes: 26 additions & 20 deletions src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,11 @@ class CPythonmacOsFramework(CPython):
def can_describe(cls, interpreter):
return is_mac_os_framework(interpreter) and super(CPythonmacOsFramework, cls).can_describe(interpreter)

@classmethod
def sources(cls, interpreter):
for src in super(CPythonmacOsFramework, cls).sources(interpreter):
yield src
# add a symlink to the host python image
exe = cls.image_ref(interpreter)
ref = PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)
yield ref

def create(self):
super(CPythonmacOsFramework, self).create()

# change the install_name of the copied python executables
target = "@executable_path/../.Python"
target = self.desired_mach_o_image_path()
current = self.current_mach_o_image_path()
for src in self._sources:
if isinstance(src, ExePathRefToDest):
Expand All @@ -62,8 +53,8 @@ def _executables(cls, interpreter):
def current_mach_o_image_path(self):
raise NotImplementedError

@classmethod
def image_ref(cls, interpreter):
@abstractmethod
def desired_mach_o_image_path(self):
raise NotImplementedError


Expand All @@ -74,13 +65,12 @@ def can_create(cls, interpreter):
return super(CPython2macOsFramework, cls).can_create(interpreter)
return False

@classmethod
def image_ref(cls, interpreter):
return Path(interpreter.prefix) / "Python"

def current_mach_o_image_path(self):
return os.path.join(self.interpreter.prefix, "Python")

def desired_mach_o_image_path(self):
return "@executable_path/../Python"

@classmethod
def sources(cls, interpreter):
for src in super(CPython2macOsFramework, cls).sources(interpreter):
Expand All @@ -89,6 +79,14 @@ def sources(cls, interpreter):
exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload")
yield PathRefToDest(exec_marker_file, dest=to_path)

# add a copy of the host python image
exe = Path(interpreter.prefix) / "Python"
yield PathRefToDest(exe, dest=lambda self, _: self.dest / "Python", must=RefMust.COPY)

# add a symlink to the Resources dir
resources = Path(interpreter.prefix) / "Resources"
yield PathRefToDest(resources, dest=lambda self, _: self.dest / "Resources")

@property
def reload_code(self):
result = super(CPython2macOsFramework, self).reload_code
Expand Down Expand Up @@ -147,13 +145,21 @@ def fix_signature(self):


class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix):
@classmethod
def image_ref(cls, interpreter):
return Path(interpreter.prefix) / "Python3"

def current_mach_o_image_path(self):
return "@executable_path/../../../../Python3"

def desired_mach_o_image_path(self):
return "@executable_path/../.Python"

@classmethod
def sources(cls, interpreter):
for src in super(CPython3macOsFramework, cls).sources(interpreter):
yield src

# add a symlink to the host python image
exe = Path(interpreter.prefix) / "Python3"
yield PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)

@property
def reload_code(self):
result = super(CPython3macOsFramework, self).reload_code
Expand Down

0 comments on commit 408c3bb

Please sign in to comment.