jpayne@68: import itertools jpayne@68: import os jpayne@68: jpayne@68: from .compat import py312 jpayne@68: jpayne@68: from distutils import log jpayne@68: jpayne@68: flatten = itertools.chain.from_iterable jpayne@68: jpayne@68: jpayne@68: class Installer: jpayne@68: nspkg_ext = '-nspkg.pth' jpayne@68: jpayne@68: def install_namespaces(self): jpayne@68: nsp = self._get_all_ns_packages() jpayne@68: if not nsp: jpayne@68: return jpayne@68: filename = self._get_nspkg_file() jpayne@68: self.outputs.append(filename) jpayne@68: log.info("Installing %s", filename) jpayne@68: lines = map(self._gen_nspkg_line, nsp) jpayne@68: jpayne@68: if self.dry_run: jpayne@68: # always generate the lines, even in dry run jpayne@68: list(lines) jpayne@68: return jpayne@68: jpayne@68: with open(filename, 'wt', encoding=py312.PTH_ENCODING) as f: jpayne@68: # Python<3.13 requires encoding="locale" instead of "utf-8" jpayne@68: # See: python/cpython#77102 jpayne@68: f.writelines(lines) jpayne@68: jpayne@68: def uninstall_namespaces(self): jpayne@68: filename = self._get_nspkg_file() jpayne@68: if not os.path.exists(filename): jpayne@68: return jpayne@68: log.info("Removing %s", filename) jpayne@68: os.remove(filename) jpayne@68: jpayne@68: def _get_nspkg_file(self): jpayne@68: filename, _ = os.path.splitext(self._get_target()) jpayne@68: return filename + self.nspkg_ext jpayne@68: jpayne@68: def _get_target(self): jpayne@68: return self.target jpayne@68: jpayne@68: _nspkg_tmpl = ( jpayne@68: "import sys, types, os", jpayne@68: "p = os.path.join(%(root)s, *%(pth)r)", jpayne@68: "importlib = __import__('importlib.util')", jpayne@68: "__import__('importlib.machinery')", jpayne@68: ( jpayne@68: "m = " jpayne@68: "sys.modules.setdefault(%(pkg)r, " jpayne@68: "importlib.util.module_from_spec(" jpayne@68: "importlib.machinery.PathFinder.find_spec(%(pkg)r, " jpayne@68: "[os.path.dirname(p)])))" jpayne@68: ), jpayne@68: ("m = m or sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))"), jpayne@68: "mp = (m or []) and m.__dict__.setdefault('__path__',[])", jpayne@68: "(p not in mp) and mp.append(p)", jpayne@68: ) jpayne@68: "lines for the namespace installer" jpayne@68: jpayne@68: _nspkg_tmpl_multi = ('m and setattr(sys.modules[%(parent)r], %(child)r, m)',) jpayne@68: "additional line(s) when a parent package is indicated" jpayne@68: jpayne@68: def _get_root(self): jpayne@68: return "sys._getframe(1).f_locals['sitedir']" jpayne@68: jpayne@68: def _gen_nspkg_line(self, pkg): jpayne@68: pth = tuple(pkg.split('.')) jpayne@68: root = self._get_root() jpayne@68: tmpl_lines = self._nspkg_tmpl jpayne@68: parent, sep, child = pkg.rpartition('.') jpayne@68: if parent: jpayne@68: tmpl_lines += self._nspkg_tmpl_multi jpayne@68: return ';'.join(tmpl_lines) % locals() + '\n' jpayne@68: jpayne@68: def _get_all_ns_packages(self): jpayne@68: """Return sorted list of all package namespaces""" jpayne@68: pkgs = self.distribution.namespace_packages or [] jpayne@68: return sorted(set(flatten(map(self._pkg_names, pkgs)))) jpayne@68: jpayne@68: @staticmethod jpayne@68: def _pkg_names(pkg): jpayne@68: """ jpayne@68: Given a namespace package, yield the components of that jpayne@68: package. jpayne@68: jpayne@68: >>> names = Installer._pkg_names('a.b.c') jpayne@68: >>> set(names) == set(['a', 'a.b', 'a.b.c']) jpayne@68: True jpayne@68: """ jpayne@68: parts = pkg.split('.') jpayne@68: while parts: jpayne@68: yield '.'.join(parts) jpayne@68: parts.pop() jpayne@68: jpayne@68: jpayne@68: class DevelopInstaller(Installer): jpayne@68: def _get_root(self): jpayne@68: return repr(str(self.egg_path)) jpayne@68: jpayne@68: def _get_target(self): jpayne@68: return self.egg_link