.. _packaging: Packaging distributions ----------------------- .. index:: single: Distributions; packaging You can use ``distil`` to package distributions. When using ``distutils`` or ``setuptools`` / ``distribute``, you specify what will be packaged by passing arguments to the :func:`setup` function in ``setup.py``. However, we are moving away from executable code and towards declarative metadata. Accordingly, ``distil`` uses declarative metadata in a file, ``package.json``, that it uses instead of ``setup.py`` to describe how to package distributions. A description of this metadata is provided in :ref:`extended-metadata`, but you can see how the metadata looks for most distributions on PyPI by using ``distil`` to download them:: $ distil download -d /tmp config Downloading http://www.red-dove.com/config-0.3.7.tar.gz to /tmp/config-0.3.7 31KB @ 461 KB/s 100 % Done: 00:00:00 Unpacking ... done. $ cat /tmp/config-0.3.7/package.json { "source": { "modules": [ "config" ] }, "version": "1", "metadata": { "maintainer": "Vinay Sajip", "name": "config", "license": "Copyright (C) 2004-2007 by Vinay Sajip. All Rights Reserved. See LICENSE for license.", "author": "Vinay Sajip", "home-page": "http://www.red-dove.com/python_config.html", "summary": "A hierarchical, easy-to-use, powerful configuration module for Python", "version": "0.3.7", "maintainer-email": "vinay_sajip@red-dove.com", "author-email": "vinay_sajip@red-dove.com", "description": "This module allows a hierarchical configuration scheme with support for mappings\nand sequences, cross-references between one part of the configuration and\nanother, the ability to flexibly access real Python objects without full-blown\neval(), an include facility, simple expression evaluation and the ability to\nchange, save, cascade and merge configurations. Interfaces easily with\nenvironment variables and command-line options. It has been developed on python\n2.3 but should work on version 2.2 or greater." } } This metadata is automatically generated from distributions which are on PyPI. If a particular distribution you download using ``distil`` comes without a ``package.json`` file, there could be a number of reasons for this: * The distribution has recently been uploaded to PyPI, and the processing machinery hasn't got around to it yet. Try again in a few hours. * The processing machinery has failed to process the distribution on PyPI, which could be due to a bug in the automatic processing code or a bug in the distribution's ``setup.py``. Source distributions ~~~~~~~~~~~~~~~~~~~~ .. index:: single: Source distributions; packaging You can use ``distil`` to build source distributions in ``.tar.gz``, ``.tar.bz2`` or ``.zip`` formats. Let's consider a simple distribution called ``frobozz``. The source tree looks like this:: . ├── docs │  ├── _build │  ├── conf.py │  ├── index.rst │  ├── make.bat │  ├── Makefile │  ├── _static │  └── _templates ├── frobozz.py ├── MANIFEST ├── README └── setup.py The ``setup.py`` looks like this:: from distutils.core import setup setup( name='frobozz', version='0.1', py_modules=['frobozz'], author='Distlib User', author_email='distlib.user@dummy.org', ) Let's replace the ``setup.py`` with the equivalent ``package.json``:: { "source": { "include": [ "README" ], "modules": [ "frobozz" ] }, "version": 1, "metadata": { "version": "0.1", "name": "frobozz", "author-email": "distlib.user@dummy.org", "author": "Distlib User" } } If we're in the root directory of the ``frobozz`` project, we can package it by simply issuing the command:: $ distil package The following packages were built: frobozz-0.1.tar.gz By default, a ``.tar.gz`` source archive in ``dist`` is built. Let's look at its contents:: $ tar tzvf dist/frobozz-0.1.tar.gz drwxrwxr-x vinay/vinay 0 2013-03-21 12:46 frobozz-0.1/ -rw-rw-r-- vinay/vinay 0 2013-03-20 09:58 frobozz-0.1/README -rw-rw-r-- vinay/vinay 0 2013-03-20 09:57 frobozz-0.1/frobozz.py -rw-rw-r-- vinay/vinay 257 2013-03-21 12:40 frobozz-0.1/package.json To build other formats, you can specify them in a ``--formats`` parameter:: $ distil package --formats=gztar,bztar,zip The following packages were built: frobozz-0.1.tar.bz2 frobozz-0.1.tar.gz frobozz-0.1.zip Here is the complete help for ``distil``'s ``package`` command:: $ distil help package usage: distil package [-h] [--formats {gztar,bztar,zip,wheel}] [-d DESTDIR] [DIR] Create source distributions or wheels. positional arguments: DIR The directory containing the software to package. If not specified, the current directory is used. optional arguments: -h, --help show this help message and exit --formats {gztar,bztar,zip,wheel} The formats to produce packages in. -d DESTDIR, --destination DESTDIR Location to write the packaged distributions to. .. _binary-dist: Binary distributions ~~~~~~~~~~~~~~~~~~~~ .. index:: single: Binary distributions; packaging single: Wheel; building Currently, ``distil`` only supports building binary distributions using the Wheel format (see `PEP 427 <http://www.python.org/dev/peps/pep-0427/>`_). The method for creating wheels is just the same as for source distributions:: $ distil package --formats=wheel The following packages were built: /home/vinay/projects/frobozz/dist/frobozz-0.1-py27-none-any.whl $ unzip -l dist/frobozz-0.1-py27-none-any.whl Archive: dist/frobozz-0.1-py27-none-any.whl Length Date Time Name --------- ---------- ----- ---- 0 2013-03-21 17:50 frobozz.py 314 2013-03-21 17:50 frobozz-0.1.dist-info/METADATA 89 2013-03-21 17:50 frobozz-0.1.dist-info/WHEEL 263 2013-03-21 17:50 frobozz-0.1.dist-info/RECORD --------- ------- 666 4 files Building wheels for dependencies ................................ .. index:: single: Wheel; building dependencies When you build a wheel using the ``package`` command, only the package itself is built - not its dependencies. If you need to build dependencies of your package, use ``distil``'s ``pip`` command (see below). .. _pip-build: Using pip when building wheels .............................. .. index:: single: Wheel; building using pip Sometimes, the distribution you want to build a wheel for uses custom code in ``setup.py`` to set things up correctly for the build. In such cases, you may need to use ``pip`` to build your wheel. For this purpose, ``distil`` provides the ``pip`` command. This works from requirements rather than source directories, and is intended to be used for PyPI-hosted dependencies of your package rather than for your package itself (use ``distil package`` for that). When using ``distil pip``, note that ``pip`` is used to do a customised installation from which ``distil`` then builds the wheel using ``distlib``. This means that you should use a clean venv when running ``distil pip``, because ``pip`` won't install any already-installed distributions, and a clean venv won't have any of those, minimising problems in your workflow. Here's an example of running ``distil pip``:: $ distil -e d2 pip Flask Checking requirements for Flask (0.9) ... done. Pipping Jinja2==2.6 ... Pipping Werkzeug==0.8.3 ... Pipping Flask==0.9 ... The following wheels were built: Jinja2-2.6-py27-none-any.whl Werkzeug-0.8.3-py27-none-any.whl Flask-0.9-py27-none-any.whl The wheels are written in the current directory by default. You can also use requirements files, just as with ``distil install``. Here is the complete help for ``distil``'s ``pip`` command:: $ distil help pip usage: distil pip [-h] [-r REQTFILE [REQTFILE ...]] [-d DESTDIR] [--no-deps] [REQT [REQT ...]] Build wheels using pip. Use this when distil's build logic doesn't work because of code in setup.py which needs to be run. positional arguments: REQT A requirement using a distribution on PyPI. optional arguments: -h, --help show this help message and exit -r REQTFILE [REQTFILE ...] Get requirements from specified file(s) -d DESTDIR, --destination DESTDIR Location to write the wheels to. Defaults to the current directory. --no-deps Don't build wheels for dependencies when building wheels. The default behaviour is to build wheels for all dependencies. .. _extended-metadata: Packaging metadata ~~~~~~~~~~~~~~~~~~ The metadata discussed in :pep:`426`, and its precursor PEPs, is a (potentially) small subset of the total metadata relating to a distribution. If we call the PEP 426 metadata "index metadata", then the overall metadata for a distribution would comprise (the list may be incomplete): * The index metadata (PEP 426 et al) * Metadata about how to build a source distribution from a source tree * Metadata about how to build a binary distribution from a source tree/source distribution * Metadata about things a distribution exports for use by other distributions * Metadata used by installers to install the distribution We're attempting in the PEPs to standardise the next revision of the first of these, but the other categories haven't been considered at all (from a standardisation point of view). At present, in the ``distribute`` / ``setuptools`` / ``distutils`` world, they are provided by a mixture of ``MANIFEST.in`` files and a bunch of keyword arguments passed to :func:`setup`. This, coupled with the command-class design of ``distutils``, has led to a lot of *ad hoc* approaches to extending ``distutils`` where it fell short -- monkey-patching, custom command classes etc., which has led to the present less than ideal situation. A declarative approach is now generally considered better than ``setup.py``: it allows for multiple, competing implementations which should be interoperable. The ``distutils2`` approach was to focus on the declarative ``setup.cfg``, and the new wheel format is also essentially declarative in nature. A flat key-value structure for representing the other types of metadata doesn't seem ideal. It might seem heretical, but backward compatibility aside, it's not clear why we aren't thinking about JSON as a metadata format. It has mature support in the stdlib, handles Unicode, and allows more meaningful structuring of the metadata. Examples of JSON metadata can be seen in the following examples: * Assimulo (a Fortan-heavy numerical package): http://www.red-dove.com/pypi/projects/A/Assimulo/package-2.3.json * Twisted: http://www.red-dove.com/pypi/projects/T/Twisted/package-12.3.0.json * Pyramid: http://www.red-dove.com/pypi/projects/P/pyramid/package-1.4b3.json * Django: http://www.red-dove.com/pypi/projects/D/Django/package-1.4.3.json The index metadata is a small part of the overall metadata (it appears at key ``metadata`` in the top-level dict expressed in the JSON). You will most likely find metadata for distributions of interest to you by using the URI scheme indicated by the above examples. Using this type of metadata, ``distil`` can: * Build a source archive from the metadata and the source archive which is essentially the same as the source. * Install into a virtualenv such that the venv layout after installation is identical to that following an installation with ``pip``. So, while the metadata schema used is provisional and can be improved, it brings across what *can* be brought across from :func:`setup`, such that it can be used to install software identically to ``pip``, for a large number of distributions currently on PyPI. Such a declarative solution can work, provided there isn't custom code which runs at installation time. Where code *is* called at installation time from ``setup.py``, because the effects of that clearly can't be brought over into a declarative format, it may not be possible to install affected distributions correctly. (For such distributions, ``distil`` offers the ``pip`` command to create wheels which can then be installed using ``distil``'s ``install`` command. See :ref:`pip-build` for more information.) The other things that structured metadata makes possible is that it's relatively easy to transform into useful forms. For example, when ``distlib`` does dependency resolution, it can make effective use of selected metadata across all versions of a project. For example, you can see all the versions of a project, what the download URLs are and their digests and sizes, and the distribution dependencies -- just by rearranging and aggregating the data. Examples: - contextlib: http://www.red-dove.com/pypi/projects/C/contextlib2/project.json - Sphinx: http://www.red-dove.com/pypi/projects/S/Sphinx/project.json This allows ``distlib``-using code to resolve dependencies without ever downloading a distribution, as ``pip`` has to do. The overall effect is more like ``RPM`` and ``apt-get``: You get told before downloading any distribution what other dependencies will be installed, and whether any existing distributions will get upgraded. Schema for the extended JSON metadata ..................................... The schema for the extended metadata is given by the following sample JSON (not valid JSON, due to the comments). The example may not be exhaustive, but gives a flavour of what is covered by the metadata:: { "version": 1, # version of this schema "exports": { # equivalent to setuptools' entry points "frobozz.processors": [ "name1 = frobozz.sub.package:do_nothing", "name2 = frobozz.sub.package:do_something" ], "scripts": { "console": [ "frobozz = frobozz.cli:main" ], "gui": [ "frobozzw = frobozz.gui:main" ] } }, "requirements": { "install": [ # list of requirements needed post-install "foo (>= 1.0)" ] "setup": [ # list of requirements needed for setup "bar (>= 2.0)" ] "test": [ # list of requirements needed for testing "nose (>= 1.2)" ], "extras": { # dict of requirements needed for extras # list of requirements keyed by extra name "i18n": [ "Babel (>= 0.8)" ] } }, "source": { "include-package-data": true, "data-files": [ # a list of lists. Each entry in the outer list is # a directory followed by a list of data files in # that directory. [ "dir1", [ "file1_in_dir1.ext", "file2_in_dir1.ext" ] ], [ "dir2", [ "file1_in_dir2.ext", "file2_in_dir2.ext" ] ] # and so on ], "include": [ # e.g. scripts "bin/script1", "bin/script2" ], "packages": [ # Python packages "frobozz.foo", "frobozz.foo.bar", "frobozz.foo.bar.baz" ], "modules": [ "mod1", "mod2", "mod3" ], "manifest": [ "include data/global.dat", "include data/localedata/*.dat", "include doc/api/*.*", "include doc/*.html" ] }, "extensions": { # C extensions "frobozz.foo.ext_one": { "extra_link_args": [], "swig_opts": [], "language": null, "define_macros": [], "extra_objects": [], "runtime_library_dirs": [], "libraries": [], "sources": [ "foo/extension/module_one.c" ], "depends": [], "export_symbols": [], "extra_compile_args": [], "undef_macros": [], "include_dirs": [], "library_dirs": [], "name": "frobozz.foo.ext_one" }, "frobozz.foo.ext_two": { "extra_link_args": [], "swig_opts": [], "language": null, "define_macros": [], "extra_objects": [], "runtime_library_dirs": [], "libraries": [], "sources": [ "foo/extension/module_two.c" ], "depends": [], "export_symbols": [], "extra_compile_args": [], "undef_macros": [], "include_dirs": [], "library_dirs": [], "name": "frobozz.foo.ext_two" } }, "scripts": [ # list of scripts to install "bin/script1", "bin/script2" ], "metadata": { # index metadata (PKG-INFO / METADATA) "maintainer-email": "some.user@some.domain.com", "maintainer": "Some User", "name": "frobozz", "license": "MIT", "author": "Frobozz Developers", "home-page": "http://frobozz.com/", "summary": "An example project", "version": "1.3.0", "classifiers": [ "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7" ], "author-email": "some.user@some.domain.com", "description": "An example description.\n" }, "test": { # test specifications "test-suite": "frobozz.tests.suite", "test-runner": "frobozz.tests.runner" }, "build": { # build specifications "use-2to3": true, "use-2to3-fixers": [ "custom_fixers" ] } } You may find it useful to locate metadata for actual distributions on PyPI which you may be familiar with. You can start exploring `here <http://www.red-dove.com/pypi/projects/>`_. Creating an initial version of metadata for new projects ........................................................ The ``distil init`` command creates an initial ``package.json`` file in a specified directory. You can provide some command-line default values, as shown in the complete command-line help for ``distil init``:: $ distil help init usage: distil init [-h] [--name NAME] [--projver PROJVER] [--author AUTHOR] [--email EMAIL] [--home-page HOMEPAGE] PATH Create minimal metadata for a project that you can then add to during development. positional arguments: PATH A directory of a local project where the metadata file is to be created. optional arguments: -h, --help show this help message and exit --name NAME The name of the project. --projver PROJVER The version of the project. --author AUTHOR The author of the project. --email EMAIL The author's email address. --home-page HOMEPAGE The home page URL of the project.