Ben Finney <ben+deb...@benfinney.id.au> writes:

> How can I specify to Pybuild that an application should have its
> modules all in a private namespace, but have the Distutils metadata
> also available to `pkg_resources` queries?

I've made a real, minimal example of a Debian package of a Python
distribution, with the properties I've described.

* The ‘console_scripts’ entry point is used to specify command line
  programs, ‘lorem’ and ‘ipsum’, using that Distutils feature. Those are
  the correct names for the commands, so I don't want to mess with that.

* The ‘--install-lib’ option is used in ‘PYBUILD_INSTALL_ARGS’ to place
  the Python packages in an application-private directory,
  ‘/usr/share/foo-app/’. The point of that is so they won't be on the
  Python module search path.

* The ‘--install-scripts’ option is *not* used, because Distutils places
  the constructed scripts in the correct directory (‘/usr/bin/’) by
  default.

* The script names are correct, but are identical to names of
  directories in the ‘/usr/share/foo-app/’ top level. That's
  another good reason not to use the ‘--install-scripts’ option. It is
  also normal and expected, and shouldn't be a problem because the
  scripts will not exist there; they belong in ‘/usr/bin/’.

* The console scripts constructed by Distutils call the function
  ‘pkg_resources.load_entry_point’. That requires that the Distutils
  metadata (the ‘FooApp-1.2.3.egg-info/’ directory) be available to that
  function.

  According to Robert's earlier message, that means the Distutils
  metadata file needs to be not in the application's private directory,
  but in a directory on the Python module search path. That seems odd to
  me, since this is not amodule import being done.

What happens is that the Distutils metadata also gets hidden away in the
private directory. Then the ‘pkg_resources.load_entry_point’ function
tries to find the distribution, and can't because it's not in the Python
module search path.


So what i'm looking for is a way to tell Distutils at install time:

* Install the Python libraries to this specific private path that isn't
  part of the Python module search path” (‘--install-lib’).

* Ensure the Distutils metadata can be found by these specific console
  scripts (and, ideally, not by others) when they run. Either:

  * Construct the console scripts, at install time, to load the
    Distutils metadata at run time from that same private directory
    where Distutils installed it.

  Or:

  * Construct the Distutils metadata, at install time, such that it
    points to the private directory for these libraries. Put this
    metadata where it will be found at run time.

* Install the scripts to the default, public script install location.

That facility would ideally be an option I can just add to
‘PYBUILD_INSTALL_ARGS’.


Here is the output from the example. I can provide the files if anyone
wants to experiment.

=====
$ pwd
/home/bignose/Projects/debian/foo-app-1.2.3

$ find .
.
./setup.py
./lorem
./lorem/amet.py
./lorem/dolor.py
./lorem/sit.py
./lorem/__init__.py
./ipsum
./ipsum/elit.py
./ipsum/adipiscing.py
./ipsum/consecteur.py
./ipsum/__init__.py
./debian
./debian/compat
./debian/rules
./debian/changelog
./debian/control

$ cat ./setup.py
from setuptools import (setup, find_packages)

setup(
        name="FooApp",
        version="1.2.3",
        packages=find_packages(),

        entry_points={
            'console_scripts': [
                "lorem = FooApp.lorem.dolor:main",
                "ipsum = FooApp.ipsum.consecteur:main",
                ],
            },
        )

$ cat ./debian/rules 
#! /usr/bin/make -f

export PYBUILD_INSTALL_ARGS ?= --install-lib=/usr/share/foo-app/

%:
        dh $@ --with=python2 --buildsystem=pybuild

$ ./debian/rules clean
dh clean --with=python2 --buildsystem=pybuild
   dh_testdir -O--buildsystem=pybuild
   dh_auto_clean -O--buildsystem=pybuild
I: pybuild base:170: python2.7 setup.py clean 
running clean
removing 
'/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build' (and 
everything under it)
'build/bdist.linux-x86_64' does not exist -- can't clean it
'build/scripts-2.7' does not exist -- can't clean it
   dh_clean -O--buildsystem=pybuild

$ fakeroot ./debian/rules binary
dh binary --with=python2 --buildsystem=pybuild
   dh_testroot -O--buildsystem=pybuild
   dh_prep -O--buildsystem=pybuild
   dh_auto_install -O--buildsystem=pybuild
I: pybuild base:170: /usr/bin/python setup.py install --root 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app 
--install-lib=/usr/share/foo-app/
running install
running build
running build_py
running install_lib
creating /home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr
creating /home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share
creating 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app
creating 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/ipsum/elit.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/ipsum/adipiscing.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/ipsum/consecteur.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/ipsum/__init__.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum
creating 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/lorem/amet.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/lorem/dolor.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/lorem/sit.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem
copying 
/home/bignose/Projects/debian/foo-app-1.2.3/.pybuild/pythonX.Y_2.7/build/lorem/__init__.py
 -> 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum/elit.py
 to elit.pyc
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum/adipiscing.py
 to adipiscing.pyc
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum/consecteur.py
 to consecteur.pyc
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/ipsum/__init__.py
 to __init__.pyc
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem/amet.py
 to amet.pyc
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem/dolor.py
 to dolor.pyc
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem/sit.py
 to sit.pyc
byte-compiling 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/lorem/__init__.py
 to __init__.pyc
running install_egg_info
running egg_info
creating FooApp.egg-info
writing FooApp.egg-info/PKG-INFO
writing top-level names to FooApp.egg-info/top_level.txt
writing dependency_links to FooApp.egg-info/dependency_links.txt
writing entry points to FooApp.egg-info/entry_points.txt
writing manifest file 'FooApp.egg-info/SOURCES.txt'
reading manifest file 'FooApp.egg-info/SOURCES.txt'
writing manifest file 'FooApp.egg-info/SOURCES.txt'
Copying FooApp.egg-info to 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/share/foo-app/FooApp-1.2.3.egg-info
running install_scripts
Installing ipsum script to 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/bin
Installing lorem script to 
/home/bignose/Projects/debian/foo-app-1.2.3/debian/foo-app/usr/bin
   dh_installdocs -O--buildsystem=pybuild
   dh_installchangelogs -O--buildsystem=pybuild   
   dh_python2 -O--buildsystem=pybuild
   dh_perl -O--buildsystem=pybuild
   dh_link -O--buildsystem=pybuild
   dh_compress -O--buildsystem=pybuild
   dh_fixperms -O--buildsystem=pybuild
   dh_installdeb -O--buildsystem=pybuild
   dh_gencontrol -O--buildsystem=pybuild
   dh_md5sums -O--buildsystem=pybuild
   dh_builddeb -O--buildsystem=pybuild
dpkg-deb: building package 'foo-app' in '../foo-app_1.2.3-1_all.deb'.

$ sudo dpkg -i ../foo-app_1.2.3-1_all.deb
(Reading database ... 812074 files and directories currently installed.)
Preparing to unpack .../debian/foo-app_1.2.3-1_all.deb ...
Unpacking foo-app (1.2.3-1) over (1.2.3-1) ...
Setting up foo-app (1.2.3-1) ...

$ dpkg --listfiles foo-app
/.
/usr
/usr/bin
/usr/bin/ipsum
/usr/bin/lorem
/usr/share
/usr/share/doc
/usr/share/doc/foo-app
/usr/share/doc/foo-app/changelog.Debian.gz
/usr/share/python
/usr/share/python/runtime.d
/usr/share/python/runtime.d/foo-app.rtupdate
/usr/share/foo-app
/usr/share/foo-app/FooApp-1.2.3.egg-info
/usr/share/foo-app/FooApp-1.2.3.egg-info/top_level.txt
/usr/share/foo-app/FooApp-1.2.3.egg-info/dependency_links.txt
/usr/share/foo-app/FooApp-1.2.3.egg-info/entry_points.txt
/usr/share/foo-app/FooApp-1.2.3.egg-info/PKG-INFO
/usr/share/foo-app/ipsum
/usr/share/foo-app/ipsum/elit.py
/usr/share/foo-app/ipsum/adipiscing.py
/usr/share/foo-app/ipsum/consecteur.py
/usr/share/foo-app/ipsum/__init__.py
/usr/share/foo-app/lorem
/usr/share/foo-app/lorem/amet.py
/usr/share/foo-app/lorem/dolor.py
/usr/share/foo-app/lorem/sit.py
/usr/share/foo-app/lorem/__init__.py

$ cat /usr/bin/lorem
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'FooApp==1.2.3','console_scripts','lorem'
__requires__ = 'FooApp==1.2.3'
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.exit(
        load_entry_point('FooApp==1.2.3', 'console_scripts', 'lorem')()
    )

$ /usr/bin/lorem
Traceback (most recent call last):
  File "/usr/bin/lorem", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 3084, 
in <module>
    @_call_aside
  File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 3070, 
in _call_aside
    f(*args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 3097, 
in _initialize_master_working_set
    working_set = WorkingSet._build_master()
  File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 651, 
in _build_master
    ws.require(__requires__)
  File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 952, 
in require
    needed = self.resolve(parse_requirements(requirements))
  File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 839, 
in resolve
    raise DistributionNotFound(req, requirers)
pkg_resources.DistributionNotFound: The 'FooApp==1.2.3' distribution was not 
found and is required by the application

=====

-- 
 \        “Absurdity, n. A statement or belief manifestly inconsistent |
  `\            with one's own opinion.” —Ambrose Bierce, _The Devil's |
_o__)                                                Dictionary_, 1906 |
Ben Finney

Reply via email to