# DP: distutils: Add an option --install-layout=deb, which # DP: - installs into $prefix/dist-packages instead of $prefix/site-packages. # DP: - doesn't encode the python version into the egg name. Index: b/Doc/install/index.rst =================================================================== --- a/Doc/install/index.rst +++ b/Doc/install/index.rst @@ -240,6 +240,8 @@ +-----------------+-----------------------------------------------------+--------------------------------------------------+-------+ | Platform | Standard installation location | Default value | Notes | +=================+=====================================================+==================================================+=======+ +| Debian/Ubuntu | :file:`{prefix}/lib/python{X.Y}/dist-packages` | :file:`/usr/local/lib/python{X.Y}/dist-packages` | \(0) | ++-----------------+-----------------------------------------------------+--------------------------------------------------+-------+ | Unix (pure) | :file:`{prefix}/lib/python{X.Y}/site-packages` | :file:`/usr/local/lib/python{X.Y}/site-packages` | \(1) | +-----------------+-----------------------------------------------------+--------------------------------------------------+-------+ | Unix (non-pure) | :file:`{exec-prefix}/lib/python{X.Y}/site-packages` | :file:`/usr/local/lib/python{X.Y}/site-packages` | \(1) | @@ -249,6 +251,14 @@ Notes: +(0) + Starting with Python-2.6 Debian/Ubuntu uses for the Python which comes within + the Linux distribution a non-default name for the installation directory. This + is to avoid overwriting of the python modules which come with the distribution, + which unfortunately is the upstream behaviour of the installation tools. The + non-default name in :file:`/usr/local` is used not to overwrite a local python + installation (defaulting to :file:`/usr/local`). + (1) Most Linux distributions include Python as a standard part of the system, so :file:`{prefix}` and :file:`{exec-prefix}` are usually both :file:`/usr` on @@ -433,6 +443,15 @@ /usr/bin/python setup.py install --prefix=/usr/local +Starting with Python-2.6 Debian/Ubuntu does use +:file:`/usr/lib/python{X.Y}/dist-packages` and +:file:`/usr/local/lib/python{X.Y}/dist-packages` for the installation +of python modules included in the Linux distribution. To overwrite +the name of the site directory, explicitely use the :option:`--prefix` +option, however make sure that the installation path is included in +``sys.path``. For packaging of python modules for Debian/Ubuntu, use +the new ``setup.py install`` option :option:`--install-layout=deb`. + Another possibility is a network filesystem where the name used to write to a remote directory is different from the name used to read it: for example, the Python interpreter accessed as :file:`/usr/local/bin/python` might search for @@ -684,6 +703,17 @@ import them, this directory must be added to ``sys.path``. There are several different ways to add the directory. +On Debian/Ubuntu, starting with Python-2.6 the convention for system +installed packages is to put then in the +:file:`/usr/lib/python{X.Y}/dist-packages/` directory, and for locally +installed packages is to put them in the +:file:`/usr/lib/python{X.Y}/dist-packages/` directory. To share the +locally installed packages for the system provided Python with the +locally installed packages of a local python installation, make +:file:`/usr/lib/python{X.Y}/dist-packages/` a symbolic link to the +:file:`{...}/site-packages/` directory of your local python +installation. + The most convenient way is to add a path configuration file to a directory that's already on Python's path, usually to the :file:`.../site-packages/` directory. Path configuration files have an extension of :file:`.pth`, and each Index: b/Lib/distutils/command/install.py =================================================================== --- a/Lib/distutils/command/install.py +++ b/Lib/distutils/command/install.py @@ -47,6 +47,20 @@ 'scripts': '$base/bin', 'data' : '$base', }, + 'unix_local': { + 'purelib': '$base/local/lib/python$py_version_short/dist-packages', + 'platlib': '$platbase/local/lib/python$py_version_short/dist-packages', + 'headers': '$base/local/include/python$py_version_short/$dist_name', + 'scripts': '$base/local/bin', + 'data' : '$base/local', + }, + 'deb_system': { + 'purelib': '$base/lib/python$py_version_short/dist-packages', + 'platlib': '$platbase/lib/python$py_version_short/dist-packages', + 'headers': '$base/include/python$py_version_short/$dist_name', + 'scripts': '$base/bin', + 'data' : '$base', + }, 'unix_home': { 'purelib': '$base/lib/python', 'platlib': '$base/lib/python', @@ -154,6 +168,9 @@ ('record=', None, "filename in which to record list of installed files"), + + ('install-layout=', None, + "installation layout to choose (known values: deb, unix)"), ] boolean_options = ['compile', 'force', 'skip-build', 'user'] @@ -168,6 +185,7 @@ self.exec_prefix = None self.home = None self.user = 0 + self.prefix_option = None # These select only the installation base; it's up to the user to # specify the installation scheme (currently, that means supplying @@ -189,6 +207,9 @@ self.install_userbase = USER_BASE self.install_usersite = USER_SITE + # enable custom installation, known values: deb + self.install_layout = None + self.compile = None self.optimize = None @@ -421,6 +442,7 @@ self.install_base = self.install_platbase = self.home self.select_scheme("unix_home") else: + self.prefix_option = self.prefix if self.prefix is None: if self.exec_prefix is not None: raise DistutilsOptionError, \ @@ -435,7 +457,23 @@ self.install_base = self.prefix self.install_platbase = self.exec_prefix - self.select_scheme("unix_prefix") + if self.install_layout: + if self.install_layout.lower() in ['deb']: + self.select_scheme("deb_system") + elif self.install_layout.lower() in ['posix', 'unix']: + self.select_scheme("unix_prefix") + else: + raise DistutilsOptionError( + "unknown value for --install-layout") + elif (self.prefix_option and os.path.normpath(self.prefix) != '/usr/local') \ + or 'PYTHONUSERBASE' in os.environ \ + or 'real_prefix' in sys.__dict__: + self.select_scheme("unix_prefix") + else: + if os.path.normpath(self.prefix) == '/usr/local': + self.select_scheme("deb_system") + else: + self.select_scheme("unix_local") # finalize_unix () Index: b/Lib/distutils/command/install_egg_info.py =================================================================== --- a/Lib/distutils/command/install_egg_info.py +++ b/Lib/distutils/command/install_egg_info.py @@ -14,18 +14,37 @@ description = "Install package's PKG-INFO metadata as an .egg-info file" user_options = [ ('install-dir=', 'd', "directory to install to"), + ('install-layout', None, "custom installation layout"), ] def initialize_options(self): self.install_dir = None + self.install_layout = None + self.prefix_option = None def finalize_options(self): self.set_undefined_options('install_lib',('install_dir','install_dir')) - basename = "%s-%s-py%s.egg-info" % ( - to_filename(safe_name(self.distribution.get_name())), - to_filename(safe_version(self.distribution.get_version())), - sys.version[:3] - ) + self.set_undefined_options('install',('install_layout','install_layout')) + self.set_undefined_options('install',('prefix_option','prefix_option')) + if self.install_layout: + basename = "%s-%s.egg-info" % ( + to_filename(safe_name(self.distribution.get_name())), + to_filename(safe_version(self.distribution.get_version())) + ) + if not self.install_layout.lower() in ['deb']: + raise DistutilsOptionError( + "unknown value for --install-layout") + elif self.prefix_option or 'real_prefix' in sys.__dict__: + basename = "%s-%s-py%s.egg-info" % ( + to_filename(safe_name(self.distribution.get_name())), + to_filename(safe_version(self.distribution.get_version())), + sys.version[:3] + ) + else: + basename = "%s-%s.egg-info" % ( + to_filename(safe_name(self.distribution.get_name())), + to_filename(safe_version(self.distribution.get_version())) + ) self.target = os.path.join(self.install_dir, basename) self.outputs = [self.target] Index: b/Lib/distutils/sysconfig.py =================================================================== --- a/Lib/distutils/sysconfig.py +++ b/Lib/distutils/sysconfig.py @@ -110,6 +110,7 @@ If 'prefix' is supplied, use it instead of sys.prefix or sys.exec_prefix -- i.e., ignore 'plat_specific'. """ + is_default_prefix = not prefix or os.path.normpath(prefix) in ('/usr', '/usr/local') if prefix is None: prefix = plat_specific and EXEC_PREFIX or PREFIX @@ -118,6 +119,8 @@ "lib", "python" + get_python_version()) if standard_lib: return libpython + elif is_default_prefix and 'PYTHONUSERBASE' not in os.environ and 'real_prefix' not in sys.__dict__: + return os.path.join(libpython, "dist-packages") else: return os.path.join(libpython, "site-packages") Index: b/Lib/site.py =================================================================== --- a/Lib/site.py +++ b/Lib/site.py @@ -285,6 +285,13 @@ if ENABLE_USER_SITE and os.path.isdir(user_site): addsitedir(user_site, known_paths) + if ENABLE_USER_SITE: + for dist_libdir in ("local/lib", "lib"): + user_site = os.path.join(USER_BASE, dist_libdir, + "python" + sys.version[:3], + "dist-packages") + if os.path.isdir(user_site): + addsitedir(user_site, known_paths) return known_paths def getsitepackages(): Index: b/Lib/sysconfig.py =================================================================== --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py @@ -16,6 +16,26 @@ 'scripts': '{base}/bin', 'data': '{base}', }, + 'posix_local': { + 'stdlib': '{base}/lib/python{py_version_short}', + 'platstdlib': '{platbase}/lib/python{py_version_short}', + 'purelib': '{base}/local/lib/python{py_version_short}/dist-packages', + 'platlib': '{platbase}/local/lib/python{py_version_short}/dist-packages', + 'include': '{base}/local/include/python{py_version_short}', + 'platinclude': '{platbase}/local/include/python{py_version_short}', + 'scripts': '{base}/local/bin', + 'data': '{base}/local', + }, + 'deb_system': { + 'stdlib': '{base}/lib/python{py_version_short}', + 'platstdlib': '{platbase}/lib/python{py_version_short}', + 'purelib': '{base}/lib/python{py_version_short}/dist-packages', + 'platlib': '{platbase}/lib/python{py_version_short}/dist-packages', + 'include': '{base}/include/python{py_version_short}', + 'platinclude': '{platbase}/include/python{py_version_short}', + 'scripts': '{base}/bin', + 'data': '{base}', + }, 'posix_home': { 'stdlib': '{base}/lib/python', 'platstdlib': '{base}/lib/python', @@ -125,7 +145,7 @@ _PYTHON_BUILD = is_python_build() if _PYTHON_BUILD: - for scheme in ('posix_prefix', 'posix_home'): + for scheme in ('posix_prefix', 'posix_local', 'deb_system', 'posix_home'): _INSTALL_SCHEMES[scheme]['include'] = '{projectbase}/Include' _INSTALL_SCHEMES[scheme]['platinclude'] = '{srcdir}' @@ -159,8 +179,11 @@ def _get_default_scheme(): if os.name == 'posix': - # the default scheme for posix is posix_prefix - return 'posix_prefix' + # the default scheme for posix on Debian/Ubuntu is posix_local + # FIXME: return dist-packages/posix_prefix only for + # is_default_prefix and 'PYTHONUSERBASE' not in os.environ and 'real_prefix' not in sys.__dict__ + # is_default_prefix = not prefix or os.path.normpath(prefix) in ('/usr', '/usr/local') + return 'posix_local' return os.name def _getuserbase(): @@ -305,7 +328,7 @@ def _get_makefile_filename(): if _PYTHON_BUILD: return os.path.join(_PROJECT_BASE, "Makefile") - return os.path.join(get_path('platstdlib'), "config", "Makefile") + return os.path.join(get_path('platstdlib').replace("/usr/local","/usr",1), "config", "Makefile") def _init_posix(vars): @@ -390,7 +413,7 @@ else: inc_dir = _PROJECT_BASE else: - inc_dir = get_path('platinclude') + inc_dir = get_path('platinclude').replace("/usr/local","/usr",1) return os.path.join(inc_dir, 'pyconfig.h') def get_scheme_names(): Index: b/Lib/test/test_site.py =================================================================== --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -242,10 +242,13 @@ elif os.sep == '/': # OS X non-framwework builds, Linux, FreeBSD, etc self.assertEqual(len(dirs), 2) - wanted = os.path.join('xoxo', 'lib', 'python' + sys.version[:3], - 'site-packages') + wanted = os.path.join('xoxo', 'local', 'lib', + 'python' + sys.version[:3], + 'dist-packages') self.assertEqual(dirs[0], wanted) - wanted = os.path.join('xoxo', 'lib', 'site-python') + wanted = os.path.join('xoxo', 'lib', + 'python' + sys.version[:3], + 'dist-packages') self.assertEqual(dirs[1], wanted) else: # other platforms Index: b/Lib/test/test_sysconfig.py =================================================================== --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -230,8 +230,8 @@ self.assertTrue(os.path.isfile(config_h), config_h) def test_get_scheme_names(self): - wanted = ('nt', 'nt_user', 'os2', 'os2_home', 'osx_framework_user', - 'posix_home', 'posix_prefix', 'posix_user') + wanted = ('deb_system', 'nt', 'nt_user', 'os2', 'os2_home', 'osx_framework_user', + 'posix_home', 'posix_local', 'posix_prefix', 'posix_user') self.assertEqual(get_scheme_names(), wanted) def test_symlink(self): Index: b/Lib/distutils/tests/test_install.py =================================================================== --- a/Lib/distutils/tests/test_install.py +++ b/Lib/distutils/tests/test_install.py @@ -195,7 +195,7 @@ found = [os.path.basename(line) for line in content.splitlines()] expected = ['hello.py', 'hello.pyc', 'sayhi', - 'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2]] + 'UNKNOWN-0.0.0.egg-info'] self.assertEqual(found, expected) def test_record_extensions(self): @@ -225,7 +225,7 @@ found = [os.path.basename(line) for line in content.splitlines()] expected = [_make_ext_name('xx'), - 'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2]] + 'UNKNOWN-0.0.0.egg-info'] self.assertEqual(found, expected) def test_debug_mode(self): Index: b/Lib/pydoc.py =================================================================== --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -359,6 +359,7 @@ 'marshal', 'posix', 'signal', 'sys', 'thread', 'zipimport') or (file.startswith(basedir) and + not file.startswith(os.path.join(basedir, 'dist-packages')) and not file.startswith(os.path.join(basedir, 'site-packages')))) and object.__name__ not in ('xml.etree', 'test.pydoc_mod')): if docloc.startswith("http://"):