Skip to content
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

WIP direct module load shims #2964

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion _distutils_hack/override.py

This file was deleted.

6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ class install_with_pth(install):
"""

_pth_name = 'distutils-precedence'
# NB: this abuses the fact that site.py directly execs this with a `sitedir` local that contains the current sitedir it's working on; __file__ is no good here
_pth_contents = textwrap.dedent("""
import os
from importlib.util import spec_from_file_location, module_from_spec
var = 'SETUPTOOLS_USE_DISTUTILS'
enabled = os.environ.get(var, 'local') == 'local'
enabled and __import__('_distutils_hack').ensure_shim()
mod = module_from_spec(spec_from_file_location('_shimmod', os.path.join(sitedir, 'setuptools', '_distutils_shim.py'))) if enabled else None
mod and mod.__spec__.loader.exec_module(mod)
mod and mod.ensure_shim()
""").lstrip().replace('\n', '; ')

def initialize_options(self):
Expand Down
3 changes: 2 additions & 1 deletion setuptools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import os
import re

import _distutils_hack.override # noqa: F401
from ._distutils_shim import do_override # noqa: F401
do_override()
Comment on lines +8 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introducing imperative code ahead of imports is going to cause flake8 errors on all the subsequent imports. I'd suggest to just keep the previous technique where the shim is a package and a module of the package can be imported to invoke the behavior. i.e. mv _distutils_hack setuptools/_distutils_shim

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one's easy to solve any number of ways since it's a real import (I was just lazy with the PoC), but I think you're already starting to see the problems that the extra top-level package can cause (eg, d0da014)


import distutils.core
from distutils.errors import DistutilsOptionError
Expand Down
28 changes: 26 additions & 2 deletions _distutils_hack/__init__.py → setuptools/_distutils_shim.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def do_override():


class DistutilsMetaFinder:
# for forensic purposes, since we may not be able to get at the module object easily
_fromfile = __file__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe accessing __file__ may cause problems under a zipimport scenario. That's something we'd want to verify before accepting.


def find_spec(self, fullname, path, target=None):
if path is not None:
return
Expand All @@ -86,8 +89,16 @@ def spec_for_distutils(self):
import importlib.abc
import importlib.util

class DistutilsLoader(importlib.abc.Loader):
st = importlib.import_module('setuptools')
if os.path.dirname(st.__file__) != os.path.dirname(__file__):
# no-op; the setuptools we imported was from a foreign site-packages dir, remove self from meta_path
remove_shim()
return None
else:
# we're a sibling of the setuptools that actually got imported; kill any other shims
remove_foreign_shims()

class DistutilsLoader(importlib.abc.Loader):
def create_module(self, spec):
return importlib.import_module('setuptools._distutils')

Expand Down Expand Up @@ -130,7 +141,8 @@ def frame_file_is_setup(frame):


def ensure_shim():
DISTUTILS_FINDER in sys.meta_path or add_shim()
if not (any(s for s in sys.meta_path if type(s).__name__ == 'DistutilsMetaFinder' and getattr(s, '_fromfile', None) == DistutilsMetaFinder._fromfile)):
add_shim()
Comment on lines +144 to +145
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is unfortunate. Because the shim might be invoked independently several times, it has to detect different instances of the same name and origin. This complexity is one argument for keeping _distutils_hack as a top-level module in order to rely on import semantics to resolve exactly one instance of the module/finder per origin.

I'd like to explore an approach that doesn't require touching some many different aspects of the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the point though- since the requirements for the shim are certainly a moving target, the only sure way to get the right shim behavior for any given version of setuptools is to let each setuptools load its own locally, then clean up the others when one actually "fires". It's definitely "icky", but given the restrictions of what you can actually do in a .pth file, I can't think of another way that will also accommodate evolving shims.



@contextlib.contextmanager
Expand All @@ -151,3 +163,15 @@ def remove_shim():
sys.meta_path.remove(DISTUTILS_FINDER)
except ValueError:
pass


def remove_foreign_shims():
"""
Remove setuptools metapath shims that might've been installed by other installations on sys.path
"""
try:
foreign_shims = [l for l in sys.meta_path if type(l).__name__ == 'DistutilsMetaFinder' and l is not DISTUTILS_FINDER]
for shim in foreign_shims:
sys.meta_path.remove(shim)
except Exception:
pass