summaryrefslogtreecommitdiff
path: root/docs/design/wiki.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design/wiki.rst')
-rw-r--r--docs/design/wiki.rst625
1 files changed, 625 insertions, 0 deletions
diff --git a/docs/design/wiki.rst b/docs/design/wiki.rst
new file mode 100644
index 0000000..4fb0726
--- /dev/null
+++ b/docs/design/wiki.rst
@@ -0,0 +1,625 @@
+======================
+PyCon packaging sprint
+======================
+
+.. contents::
+
+We sprinted on Distutils2 at the US Pycon 2010 sessions. This is one
+of the results of that work.
+
+Distutils is used by both developers and packagers.
+
+:developers: People who write code in python. The code may be
+ distributed as an sdist or a variety of binary versions. The
+ developer presently uses setup.py to tell distutils how to build
+ the sdist and binary packages.
+:packagers: People who package sdists into binary packages for a Linux
+ distribution. (Is there an equivalent role for people who make
+ Windows binary packages? Or do developers generally do that for
+ their own packages?)
+
+----------------------
+Problems for packagers
+----------------------
+
+We identified specific problems packagers face when creating deb/rpm:
+
+The problem this document is intended to solve: **want to lay out files across the filesystem in a FHS-compliant way**
+
+Problems that are fixed simply by not using setuptools:
+
+- get rid of ez_setup.py
+
+- breakages using ez_setup => Some setup.py scripts import ez_setup.
+ ez_setup requires a specific version of setuptools but doesn't
+ actually need it -- lesser versions work fine.
+
+Problems that are out of scope, at least for now:
+
+- Requires that are for a specific version (or range) because of bugs
+ (not API change/features) but the distro packages have backported
+ the fixes needed to an earlier version:
+
+- differences between distribution names of packages e.g. "what are
+ the packages of NumPy called?"
+
+- Egg version (specified in a Require section in setup.py) doesn't
+ match the distro package version
+ https://www.redhat.com/archives/fedora-python-devel-list/2008-June/msg00002.html
+
+We want to solve these issues for Linux distribution packages without
+negatively impacting Windows, OS X, or pure-python
+(i.e. easy_install/pip/virtualenv) installs.
+
+Example problem with current distutils
+======================================
+
+In
+http://docs.python.org/distutils/setupscript.html#installing-additional-files
+there is this example of supplying an initscript within a setup.py::
+
+ setup(...,
+ data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
+ ('config', ['cfg/data.cfg']),
+ ('/etc/init.d', ['init-script'])]
+ )
+
+This suffers from several problems:
+
+1. The file hardcodes "/etc/init.d" as the directory for installation
+ of the initscript. However, this varies between different Linux
+ distributions. The above example assumes SysV init, but isn't
+ correct for systems using Upstart (the initscripts are likely to
+ need to be different for this case). Even within systems using
+ SysV init, the content of the script can vary between different
+ distributions
+
+2. The FHS mandates that configuration files go below /etc, but on a
+ Windows box that's meaningless.
+
+3. The file is a python script: if we want to extract data from it, we
+ have to either run it, or somehow parse it; we would need to
+ sandbox. We would prefer a declarative minilanguage for specifying
+ this data.
+
+Similarly: documentation files, .h files (e.g. NumPy)
+
+We want a system that:
+
+1. is easy for developers, does not require an "install" or "build"
+ phase during the edit/test development loop in a working copy
+
+2. supports both FHS-compliant scattering across the filesystem
+ hierarchy, and Windows, and virtualenv-style trees.
+
+-----------------------
+Problems for Developers
+-----------------------
+
+Package/file lists in multiple places
+=====================================
+
+* MANIFEST
+* MANIFEST.in
+* setup.py::data_files
+* setup.py::package_data
+* setup.py::packages
+* setup.py::scripts
+
+Replace all of these with settings in ``setup.cfg`` to specify runtime
+files (python code, resource files) and files that belong in the sdist
+but are not wanted for runtime.
+
+No idea where Linux distros want files
+======================================
+
+Sometimes programmers want to do the right thing for people wanting to
+package their programs in Linux distributions, but they don't know
+where they belong. Making matters worse, the files can go in
+different places on different Linux distributions or on Windows and
+MacOS. Placing the files in the wrong place can lead to errors at
+runtime, for instance, if the file needs to be writable by the module
+but it's placed on a read-only filesystem.
+
+This PEP attempts to deal with this by categorizing files so
+developers can properly mark what properties their files need and
+using an API to access the files, abstracting the different file
+locations on different platforms.
+
+Hard to extend the build commands
+=================================
+
+* distutils documentation is very poor
+
+* distutils build commands are classes with special method names --
+ why not simple functions?
+
+* how do you extend the data allowed to be set in entries setup()?
+
+* build commands sometimes need to act on the same arguments. No way
+ to pass these between them right now.
+
+
+-----------------------------------
+Proposed solution for placing files
+-----------------------------------
+
+This solution attempts to make several pieces of building and
+installing better. It merges the many file lists into a single file,
+simplifies (or eliminates the need for) setup.py, and allows packagers
+to place resource files in locations appropriate to their
+distribution.
+
+This solution comes in three pieces:
+
+1. A ``resources`` section in ``setup.cfg`` that maps resource files
+ to their categories (and optionally subdirectory prefixes within
+ those categories)
+
+2. A ``sysconfig.cfg`` file at the system Python level that maps
+ categories to a position on the filesystem
+
+3. A simple ``pkgutil.open()`` API to access resources from code
+
+Rationale
+=========
+
+1. The evidence (from ``__file__`` usage) is strong that package devs
+ want to think in terms of paths within their local dev tree. They
+ don't want to worry about categorizing or finding their static
+ files elsewhere.
+
+2. Package devs are more likely to use an API that makes them think
+ less and type less.
+
+3. Package devs are more likely to accept patches from packagers if
+ that patch only touches a single .cfg file, rather than touching
+ every single ``pkgutil.open()`` call throughout their code.
+
+Therefore, the ``pkgutil.open()`` call should accept a simple path
+relative to the package/distribution root. The ``resources`` section
+in ``setup.cfg`` uses globs to categorize those files:
+forward-thinking package devs can write this section, or packagers can
+do it for them and submit patches.
+
+
+"resources" section in setup.cfg
+================================
+
+The setup.py file has many sections that need to list files. We plan
+to remove those lists to ``setup.cfg``. The ``resources`` section of
+``setup.cfg`` replaces the current ``package_data``, ``data_files``,
+and ``extra_files`` options in ``setup.py``.
+
+There are three pieces of information that are needed for resource
+files:
+
+1. Position in the source tree
+ (e.g. 'mailman/database/schemas/schema.cfg', 'mywidget/jquery.js')
+
+* Position when installed
+ (e.g. '/etc/mailman/database/schemas/schema.cfg',
+ '/usr/share/mywidget-1.1/javascript/jquery.js'). For simple
+ virtualenv-style installations, this may well be the same as (1).
+
+* Key used when referencing the resource from code. Ideally, this
+ could be the same as (1), but because of difficulties in finding
+ "distribution root" at runtime from a ``pkgutil.open()`` call, it
+ will instead have to be a combination of "module name" and "path
+ relative to module", similar to what ``pkg_resources`` does.
+
+The information that the developer is concerned with:
+* Position in the source tree
+* Key used in referencing it
+
+The information the downstream packager (RPM/deb/sysadmin) cares about are:
+* Position when installed
+* Key used in referencing it
+
+Example
+-------
+
+We have a source tree with the following files::
+
+ mailman-1.0/
+ README
+ some.tpl
+ some-new-semantic.sns
+ mailman/
+ database/
+ mailman.db
+ schemas/
+ blah.schema
+ etc/
+ my.cnf
+ foo/
+ some/
+ path/
+ bar/
+ my.cfg
+ other.cfg
+ developer-docs/
+ index.txt
+ api/
+ toc.txt
+
+Here's where we want the files to end up in a typical Linux distribution:
+
+== ==================================== ===================================================================================================
+## Relative path in source tree Final full installed path
+== ==================================== ===================================================================================================
+1 mailman/database/schemas/blah.schema /var/mailman/schemas/blah.schema
+2 some.tpl /var/mailman/templates/some.tpl
+3 path/to/some.tpl /var/mailman/templates/path/to/some.tpl
+4 mailman/database/mailman.db /var/mailman/database/mailman.db
+5 developer-docs/index.txt /usr/share/doc/mailman/developer-docs/index.txt
+6 developer-docs/api/toc.txt /usr/share/doc/mailman/developer-docs/api/toc.txt
+7 README /usr/share/doc/mailman/README
+8 mailman/etc/my.cnf /etc/mailman/my.cnf
+9 mailman/foo/some/path/bar/my.cfg /etc/mailman/baz/some/path/bar/my.cfg AND
+ /etc/mailman/hmm/some/path/bar/my.cfg +
+ emit a warning
+10 mailman/foo/some/path/other.cfg /etc/mailman/some/path/other.cfg
+11 some-new-semantic.sns /var/funky/mailman/some-new-semantic.sns
+== ==================================== ===================================================================================================
+
+The numbers in the above placements are referenced below.
+
+setup.cfg
+~~~~~~~~~
+
+The setup.cfg file allows the developer and/or packager to mark what
+categories the files belong to. These are drawn from the types of
+files that the FHS and GNU coding standards define::
+
+ [resources]
+ # path glob category placement from above table
+
+ mailman/database/schemas/* = {appdata}/schemas # 1
+ **/*.tpl = {appdata}/templates # 2, 3 # does NOT flatten folder structure in destination
+ developer-docs/**/*.txt = {doc} # 5, 6
+ README = {doc} # 7
+ mailman/etc/* = {config} # 8
+ mailman/foo/**/bar/*.cfg = {config}/baz # 9
+ mailman/foo/**/*.cfg = {config}/hmm # 9, 10
+ some-new-semantic.txt = {funky-crazy-category} # 11
+
+The glob definitions are relative paths that match files from the top
+of the source tree (the location of ``setup.cfg``). Forward slashes
+(only) are used as path separator.
+
+:"*": is a glob that matches any characters within a file or directory
+name
+:"**": is a recursive glob that matches any (or no) characters within a file
+or directory name as well as a forward slash (thus an arbitrarily deep
+number of directories)
+
+The "category" value both categorizes the files and allows for placing
+them in a more fine-grained subdirectory within a category. This value
+must begin with a {category}; raw absolute or relative paths are not
+allowed.
+
+The full Python 3 string interpolation language is not supported, only
+simple {category} substitutions. The {category} is looked up in a
+system-level Python ``sysconfig.cfg`` file, where operating system
+vendors and system administrators can define where in the filesystem
+various types of files are placed. The category paths will generally
+include a {distribution.name} variable, to isolate one package's files
+of a given type from other packages.
+
+As can be seen from the examples above, explicitly-matched directory
+prefixes are stripped from the relative path before it is appended to
+the category location. Glob matches are never stripped (to avoid
+flattening hierarchies and overwriting files). In the
+``mailman/foo/\*\*/\*.cfg`` example, ``mailman/foo`` is removed, but
+not any directories matched by the recursive glob: see entries 9 and
+10 in the example table.
+
+sysconfig.cfg
+~~~~~~~~~~~~~
+
+This is a system-wide Python configuration file (TODO: can be
+overridden by e.g. virtualenv) that defines where on the filesystem
+resources will actually be installed. A sample ``sysconfig.cfg`` can
+be found in the ``distutils2`` repository at
+``src/distutils2/_backport/sysconfig.cfg`` [3].
+
+Links
+
+.. [1] Filesystem Hierarchy Standard http://www.pathname.com/fhs/
+.. [2] Rationale from the FHS which makes the distinctions between parts of the filesystem: http://www.pathname.com/fhs/pub/fhs-2.3.html#THEFILESYSTEM
+.. [3] sample sysconfig.cfg: http://bitbucket.org/tarek/distutils2/src/tip/src/distutils2/_backport/sysconfig.cfg
+
+What happens?
+~~~~~~~~~~~~~
+As an example, ``mailman/database/schemas/blah.schema``:
+
+1. The file ``mailman/database/schemas/blah.schema`` in the source
+ tree matches ``mailman/database/schemas/*`` within the
+ ``resources`` stanza of the setup.cfg, which has right-hand side
+ ``{appdata}/schemas``
+
+2. The ``*`` in the left-hand-side matches ``blah.schema``, and the
+ initial ``mailman/database/schemas/`` is stripped, so the
+ installation path for the file is mapped to
+ ``{appdata}/schemas/blah.schema``
+
+3. The label ``appdata`` is listed in the ``sysconfig.cfg`` section
+ for the ``posix_prefix`` installation scheme as installed to
+ ``/usr/share/{distribution.name}``. This expands out to:
+ ``/usr/share/mailman``
+
+4. The result is that the source file
+ ``mailman/database/schemas/blah.schema`` is installed to
+ ``/var/mailman/schemas/blah.schema``, and this mapping is recorded
+ in a RESOURCES file in the installation metadata for the
+ distribution.
+
+5. The source code can open the file at runtime via the API call
+ ``pkgutil.open('mailman', 'database/schemas/blah.schema')`` (where
+ the first argument is an importable Python package name, and the
+ second is a path relative to the location of that package), and
+ pkgutil will (using the RESOURCES mapping) open it from
+ ``/var/mailman/schemas/blah.schema``.
+
+6. If the package is not installed, and thus has no RESOURCES mapping,
+ ``pkgutil.open('mailman',
+ 'database/schemas/blah.schema')``
+
+1. The file `mailman/database/schemas/blah.schema` in the source tree matches `mailman/database/schemas/*` within the data clause of the setup.cfg, so it is treated as having the label `{data}`.
+2. The clause specified a prefix path, so the installation path for the file is mapped to "schemas/blah.schema"
+3. The label "data" is listed in the [resource_variables] stanza as being installed to "/var/mailman"
+4. The result is that the source file "mailman/database/schemas/blah.schema" is installed within the rpm/deb to "/var/mailman/schemas/blah.schema"
+5. The source code can still open the file via an API using pkgutil.open('mailman', 'database/schemas/blah.schema') and have the underlying system open it from "/var/mailman/schemas/blah.schema".
+
+
+Advice to packagers for fixing file locations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are two places where you might need to change things in order to
+customize the locations that files are installed into. The setup.cfg file can
+be patched if the files are miscategorized. For instance someone marks a help
+file that is used by the app at runtime as {doc} when it should be marked as
+{help}. These types of patches should be submitted to the upstream project.
+The resource_variables file can be changed to define different locations for
+all apps on a system. This should usually be done once in a systemwide file
+for the distribution. Changing this again may require the system packager to
+rebuild all their Python modules to change the file location. There is API in
+pkgutil to allow adding categories to the resource_variables file from
+a script that should be used instead of trying to edit the file with raw text
+processing.
+
+Open issues
+===========
+
+* setup.cfg is in the distribution, not in the module. Thus, in the unbuilt-egg
+ case, how can we find the distro when all we have is a module name? It would
+ be nice to not need an equivalent of ``setup.py develop``. Can we just walk up
+ the folder hierarchy from the module until we find a setup.cfg? A setup.cfg is
+ necessary if you use distutils2, is it not?
+* If sysconfig.cfg lands in Python 2.7, what happens when we run distutils2 in
+ 2.4?
+* Our new glob-based [resources] section is much more compact (and consistent
+ with other systems, like bash) than the explicit MANIFEST.in directives, but
+ they don't offer some of the old features. Is it okay to lose exclude,
+ global-exclude, and recursive-exclude? What do graft and prune do, and do we
+ cover their behavior? I think we could probably use a [resource:exclude]
+ section with additional exclude globs in it.
+
+API
+===
+
+pkgutil.open
+-------------
+
+Returns a file object for the resource.
+
+::
+
+ pkgutil.open('STRING_NAME_FOR_PACKAGE', 'filename/with/path/relative/to/the/source/package/directory')
+ Example:
+ pkgutil.open('mailman', 'database/schemas/blah.schema')
+ <open file '/usr/share/mailman/schemas/blah.schema', mode 'r' at 0x7f938e325d78>
+
+* First argument is the string name for a python package.
+* Second argument is the directory path relative to the python package's directory.
+* At install (or build) time we create a metadata file that maps from the source tree files to the files in their installed locations on the filesystem.
+* pkgutil.open() consults the metadata file to decide where to find the resource. If the metadata file is not found (as in a package before the egg is built), open() falls back to traversing the given relative path starting from the root of the calling package (using __name__).
+* pkgutil.open() calls from nested packages aren't a problem because, after all, we pass the desired 'module_name' to start from as the first arg.
+
+* ? Do we still need this? Default behavior: alongside the package files (if the real-installed-locations metadata file does not exist). Or if the package is installed without any resource_variables specified. ?</>?
+
+pkgutil.filename
+-----------------
+
+Returns a resource's filename with the full path.
+
+::
+
+ pkgutil.filename('STRING_NAME_FOR_PACKAGE', 'filename/with/path/relative/to/the/source/package/directory')
+ Example:
+ pkgutil.filename('mailman', 'database/schemas/blah.schema')
+ '/usr/share/mailman/schemas/blah.schema'
+
+pkgutil.add_category
+---------------------
+
+Adds a new category to the resource variables filename.
+
+::
+
+ pkgutil.add_category('CATEGORY', 'LOCATION')
+ Example:
+ pkgutil.add_category('lockdir', '{statedir}/lock')
+
+Using the API allows the parser to protect from adding duplicate categories.
+
+----
+Todo
+----
+
+These need to be worked in or discarded somehow
+
+ * Differences between applications and packages
+ - Applications sometimes want a private library (for instance to do their commandline parsing)
+
+Ideally, for every resource file, the developer (or the defaults) have classified with a "label" *TODO*: we don't have a default classifier right now: for instance::
+
+ **/*.txt = doc
+ **/*.png = data
+ **/*.jpg = data
+ **/*.gif = data
+ **/*.cfg = config
+
+
+Similar to i18n: marking of strings for translatability: gives you an ID, and a default value
+Analagous to gettext: parse the source, figure out the resources
+
+[X] Per-distro (per site ?): label placement file, mapping labels to system locations *I think this is done*
+
+[X] What strings are valid as labels? only strings that are valid Python 2 identifiers: ([_A-Za-z][_A-Za-z0-9]*) TODO: doublecheck this! *Obsolete* We have gotten rid of the labels
+
+[X] So now, when it comes to building a deb/rpm, we have another file: "label placement" which maps from labels to rules about placement on the filesystem, written once by each linux distribution: *I think this is done*
+
+
+
+How Debian does a .install file
+===============================
+
+In `packagename.install`::
+
+ etc/* etc
+ usr/* usr
+ Products/statusmessages/* usr/share/zope/Products/statusmessages3
+
+Each line has 2 parts:
+* A glob of the source path within the deb
+* Where it should land within the fakeroot (which corresponds to the final installed path)
+
+This gives the packager the opportunity to both move and rename things, and it's fairly concise.
+
+
+Building different packages from one source
+===========================================
+
+?? Do we want to do this??
+
+Use case
+--------
+
+Split the docs into a separate sdist from the code so that people can download them separately.
+(Matthias)
+
+Another use case
+----------------
+
+Need to split submodule into its own binary package (essentially converting top-level to a namespace package).
+
+
+
+Alternate ways of specifying labels
+-----------------------------------
+Noufal's::
+
+ [mailman]
+ data = *.txt, README
+ data.persistent = sqlite.db
+
+----------------------
+
+Tarek's::
+
+ [files]
+
+ data =
+ mailman/database/schemas/*
+ *.txt
+ README
+
+ data.persistent = sqlite.db
+
+----------------------
+
+Toshio's::
+
+ [resources]
+ *.jpg = data
+
+Alternative Label Idea
+======================
+
+labels for different resource types: images, manpages, script, config files etc, javascript, schema, sql, data files
+
+(those labels impose some other issues - what would one do to differences in statically servable on a webserver versus gtkbuilder can find it)
+
+pkg_resources already provides software with an API::
+
+ pkg_resource.open(label='javascript', name='jquery.js')
+
+Then we have the ability for Linux distros to place the different labels in FHS-compliant (or whatever) locations on the filesystem, mapping each label to a fs path::
+
+ pkg_resource.open(label='config', name=')
+
+ pkg_resource.resource_stream(pkgname='mailman', label='config', victim='schema.cfg')
+ pkg_resource.resource_stream('mailman', 'mailman.config', 'schema.cfg')
+ pkg_resource.resource_stream('mailman', 'mailman.config', 'schema.cfg', label='config')
+
+analogy with logging:
+
+- with logging: developer sets up streams of data; sysadmin decides what to do with each log stream
+- with packaging: developer sets up streams of data; packaging system decides where to put each one
+
+developer:
+
+(1) everything's within my local working copy; look within it; want to be able to quickly hack on stuff without having to "install" somewhere, for fast edit/test loop
+(2) "setup.py sdist" has given us a zipfile, put it on pypi, someone uses buildout on it
+(3) as (2) but a distribution has moved things to FHS-compliant location
+
+
+ pkgutil.open(pkgname='mailman.database.schemas', filename='schema.cfg') # <-- Does this work with our examples below?
+
+ pkgutil.open(pkgname='mailman', label='data', filename='schemas/schema.cfg')
+
+(It won't be easy to get package devs to use this API; __file__ feels less magic than some strange call from pkgutil. The simpler the API call and the more "builtin" it looks, the better.)
+
+*TODO* Can we make sane defaults? For instance, can pkgname default to the pkgname or modulename that the call is being made from?
+
+*TODO* can we match things against a range of packages/paths
+
+(1) ./mailman/config/schema.cfg
+(2) .
+(3) /etc/mailman/database/schemas/schema.cfg
+
+mapping from labels to dirs::
+
+ distro_dict = {
+ 'config':'/etc',
+ 'mandir':'/usr/share/mandir',
+ }
+
+Another old syntax proposal
+===========================
+::
+
+ [resources]
+
+ # data are composed of two elements
+ # 1. the path relative to the package
+ # 2. an optional prefix path that will replace the explicit (non-glob) initial path from (1)
+
+ data =
+ mailman/database/schemas/* schemas/
+ **/*.tpl templates/
+
+ data.persistent =
+ mailman/database/mailman.db database/
+
+ doc =
+ developer-docs/**/*.txt
+ README
+
+ config =
+ mailman/etc/* . # all files in mailman/etc/* copied to
+ mailman/foo/**/*.cfg foo # all .cfg files below mailman/foo/SOME/PATH/TO/FILE/ will get copied to foo/SOME/PATH/TO/FILE/
+ mailman/foo/**/*.cfg
+ mailman/foo/**/bar/*.cfg baz