Commit 92a61578 authored by PJ Eby's avatar PJ Eby

Support namespace packages in conjunction with system packagers, by omitting

the installation of any ``__init__.py`` files for namespace packages, and
adding a special ``.pth`` file to create a working package in ``sys.modules``.

--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4043119
parent 0b0d41cd
...@@ -1225,21 +1225,18 @@ packages), in a normal Python package layout. These ``__init__.py`` files ...@@ -1225,21 +1225,18 @@ packages), in a normal Python package layout. These ``__init__.py`` files
This code ensures that the namespace package machinery is operating and that This code ensures that the namespace package machinery is operating and that
the current package is registered as a namespace package. the current package is registered as a namespace package.
You can include other code and data in a namespace package's ``__init__.py``, You must NOT include any other code and data in a namespace package's
but it's generally not a great idea because the loading order of each ``__init__.py``. Even though it may appear to work during development, or when
project's namespace packages is not guaranteed, and thus the actual contents projects are installed as ``.egg`` files, it will not work when the projects
of the parent package at runtime may vary from one machine to another. While are installed using "system" packaging tools -- in such cases the
it's true that you won't have such conflicts if only one project defines the ``__init__.py`` files will not be installed, let alone executed.
contents of a particular namespace package's ``__init__.py``, it's less error-
prone to just leave ``__init__.py`` empty except for the declaration line. You must include the ``declare_namespace()`` line in the ``__init__.py`` of
*every* project that has contents for the namespace package in question, in
(Note that this non-deterministic ordering also means that you must include order to ensure that the namespace will be declared regardless of which
the declaration line in the ``__init__.py`` of *every* project that has project's copy of ``__init__.py`` is loaded first. If the first loaded
contents for the namespace package in question, in order to ensure that the ``__init__.py`` doesn't declare it, it will never *be* declared, because no
namespace will be declared regardless of which project's copy of other copies will ever be loaded!)
``__init__.py`` is loaded first. If the first loaded ``__init__.py`` doesn't
declare it, it will never *be* declared, because no other copies will ever be
loaded!)
TRANSITIONAL NOTE TRANSITIONAL NOTE
...@@ -2352,6 +2349,11 @@ Release Notes/Change History ...@@ -2352,6 +2349,11 @@ Release Notes/Change History
---------------------------- ----------------------------
0.6a11 0.6a11
* Support namespace packages in conjunction with system packagers, by omitting
the installation of any ``__init__.py`` files for namespace packages, and
adding a special ``.pth`` file to create a working package in
``sys.modules``.
* Made ``--single-version-externally-managed`` automatic when ``--root`` is * Made ``--single-version-externally-managed`` automatic when ``--root`` is
used, so that most system packagers won't require special support for used, so that most system packagers won't require special support for
setuptools. setuptools.
......
...@@ -1288,6 +1288,8 @@ def get_exe_prefixes(exe_filename): ...@@ -1288,6 +1288,8 @@ def get_exe_prefixes(exe_filename):
break break
if len(parts)<>2 or not name.endswith('.pth'): if len(parts)<>2 or not name.endswith('.pth'):
continue continue
if name.endswith('-nspkg.pth'):
continue
if parts[0] in ('PURELIB','PLATLIB'): if parts[0] in ('PURELIB','PLATLIB'):
pth = z.read(name).strip() pth = z.read(name).strip()
prefixes[0] = ('PURELIB/%s/' % pth), '' prefixes[0] = ('PURELIB/%s/' % pth), ''
...@@ -1308,8 +1310,6 @@ def parse_requirement_arg(spec): ...@@ -1308,8 +1310,6 @@ def parse_requirement_arg(spec):
) )
class PthDistributions(Environment): class PthDistributions(Environment):
"""A .pth file with Distribution paths in it""" """A .pth file with Distribution paths in it"""
......
...@@ -37,7 +37,7 @@ class install_egg_info(Command): ...@@ -37,7 +37,7 @@ class install_egg_info(Command):
self.execute(self.copytree, (), self.execute(self.copytree, (),
"Copying %s to %s" % (self.source, self.target) "Copying %s to %s" % (self.source, self.target)
) )
self.install_namespaces()
def get_outputs(self): def get_outputs(self):
return self.outputs return self.outputs
...@@ -58,25 +58,25 @@ class install_egg_info(Command): ...@@ -58,25 +58,25 @@ class install_egg_info(Command):
unpack_archive(self.source, self.target, skimmer) unpack_archive(self.source, self.target, skimmer)
def install_namespaces(self):
nsp = (self.distribution.namespace_packages or [])[:]
if not nsp: return
nsp.sort() # set up shorter names first
filename,ext = os.path.splitext(self.target)
filename += '-nspkg.pth'; self.outputs.append(filename)
log.info("Installing %s",filename)
if not self.dry_run:
f = open(filename,'wb')
for pkg in nsp:
pth = tuple(pkg.split('.'))
f.write(
"import sys,new; "
"m = sys.modules.setdefault(%(pkg)r,new.module(%(pkg)r)); "
"p = os.path.join(sys._getframe(1).f_locals['sitedir'], "
"*%(pth)r); "
"mp = m.__path__ = getattr(m,'__path__',[]); "
"(p not in mp) and mp.append(p)\n"
% locals()
)
f.close()
from distutils.command.install_lib import install_lib as _install_lib from distutils.command.install_lib import install_lib as _install_lib
import os
class install_lib(_install_lib): class install_lib(_install_lib):
"""Don't add compiled flags to filenames of non-Python files""" """Don't add compiled flags to filenames of non-Python files"""
...@@ -15,11 +16,67 @@ class install_lib(_install_lib): ...@@ -15,11 +16,67 @@ class install_lib(_install_lib):
return bytecode_files return bytecode_files
def run(self): def run(self):
self.build() self.build()
outfiles = self.install() outfiles = self.install()
if outfiles is not None: if outfiles is not None:
# always compile, in case we have any extension stubs to deal with # always compile, in case we have any extension stubs to deal with
self.byte_compile(outfiles) self.byte_compile(outfiles)
def get_exclusions(self):
exclude = {}
nsp = self.distribution.namespace_packages
if (nsp and self.get_finalized_command('install')
.single_version_externally_managed
):
for pkg in nsp:
parts = pkg.split('.')
while parts:
pkgdir = os.path.join(self.install_dir, *parts)
for f in '__init__.py', '__init__.pyc', '__init__.pyo':
exclude[os.path.join(pkgdir,f)] = 1
parts.pop()
return exclude
def copy_tree(
self, infile, outfile,
preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1
):
assert preserve_mode and preserve_times and not preserve_symlinks
exclude = self.get_exclusions()
if not exclude:
return _install_lib.copy_tree(self, infile, outfile)
# Exclude namespace package __init__.py* files from the output
from setuptools.archive_util import unpack_directory
from distutils import log
outfiles = []
def pf(src, dst):
if dst in exclude:
log.warn("Skipping installation of %s (namespace package)",dst)
return False
log.info("copying %s -> %s", src, os.path.dirname(dst))
outfiles.append(dst)
return dst
unpack_directory(infile, outfile, pf)
return outfiles
def get_outputs(self):
outputs = _install_lib.get_outputs(self)
exclude = self.get_exclusions()
if exclude:
return [f for f in outputs if f not in exclude]
return outputs
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment