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