Search code examples
pythondistutilspython-2.6

how to use distutils to create executable .zip file?


Python 2.6 and beyond has the ability to directly execute a .zip file if the zip file contains a __main__.py file at the top of the zip archive. I'm wanting to leverage this feature to provide preview releases of a tool I'm developing that won't require users to install anything beyond copying the .zip file to their disk. Is there a standard way to create such a zip file? I'm looking for a solution that works with python 2.6 and python 2.7.

Ideally I would like to use distutils, since I already have it working when I want to do a normal install. Is there a canonical way to use (or extend) distutils to create such a .zip file?

distutils provides an sdist command which creates a source distribution that is almost right, but creates a structure that is a little too deep.

For example, my source tree looks like this:

my_package/
  - setup.py
  - src/
      - __main__.py
      - module1/
      - module2/
      - module3/

When I do python setup.py sdist I end up with a .zip file with the following structure:

my_package-0.1.zip
  - my_package-0.1/
      - README.txt
      - PKG_INFO
      - src/
          - __main__.py
          - module1/
          - module2/
          - module3/

This isn't executable because __main__.py is not at the top of the distribution. Effectively what I want is a src distribution that doesn't include src, but only the files under src. That, or exactly what sdist gives me, but with an extra __main__.py at the top of the archive.


Solution

  • Updated: Since the setup.cfg is global, it affects the 'install-lib' setting for all commands, which is not what is desired. Unfortunately there is no way (to my knowledge) to pass a options to a subcommand via the command line, e.g. if you specify bdist --install-lib=/ it will raise an error instead of passing that down to the subcommands.

    To customize the install-lib for the install subcommand only when bdist is run, you can subclass the bdist_dumb command and set the path manually after the install subcommand is constructed / reinitialized:

    setup.py

    from distutils.core import setup
    from distutils.command.bdist_dumb import bdist_dumb
    
    class custom_bdist_dumb(bdist_dumb):
    
        def reinitialize_command(self, name, **kw):
            cmd = bdist_dumb.reinitialize_command(self, name, **kw)
            if name == 'install':
                cmd.install_lib = '/'
            return cmd
    
    if __name__ == '__main__':
        setup(
            # our custom class override
            cmdclass = {'bdist_dumb': custom_bdist_dumb},
            name='my_package',
            py_modules = ['__main__'],
            packages = ['module1', 'module2'],
            package_dir = {'': 'src'}
        )
    

    Running:

    % python setup.py bdist --format=zip
    % unzip -l dist/my_package-0.0.0.linux-x86_64.zip
    Archive:  dist/my_package-0.0.0.linux-x86_64.zip
      Length      Date    Time    Name
    ---------  ---------- -----   ----
          184  2011-05-31 20:34   my_package-0.0.0.egg-info
           30  2011-05-31 20:34   __main__.py
          128  2011-05-31 20:34   __main__.pyc
          107  2011-05-31 20:34   module1/__init__.pyc
            0  2011-05-31 20:27   module1/__init__.py
          107  2011-05-31 20:34   module2/__init__.pyc
            0  2011-05-31 20:27   module2/__init__.py
    ---------                     -------
          556                     7 files
    
    % python dist/my_package-0.0.0.linux-x86_64.zip
    my_package working.