diff options
Diffstat (limited to '')
-rwxr-xr-x | dh_python3 | 292 | ||||
-rw-r--r-- | dh_python3.rst | 238 |
2 files changed, 530 insertions, 0 deletions
diff --git a/dh_python3 b/dh_python3 new file mode 100755 index 0000000..561ade7 --- /dev/null +++ b/dh_python3 @@ -0,0 +1,292 @@ +#! /usr/bin/python3 +# vim: et ts=4 sw=4 + +# Copyright © 2010-2013 Piotr Ożarowski <piotr@debian.org> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import logging +import os +import sys +from argparse import ArgumentParser, SUPPRESS +from os.path import exists, join +from shutil import copy as fcopy +from dhpython.debhelper import DebHelper +from dhpython.depends import Dependencies +from dhpython.interpreter import Interpreter, EXTFILE_RE +from dhpython.version import supported, default, Version, VersionRange +from dhpython.pydist import validate as validate_pydist +from dhpython.fs import fix_locations, Scan +from dhpython.option import compiled_regex +from dhpython.tools import pyinstall, pyremove + +# initialize script +logging.basicConfig(format='%(levelname).1s: dh_python3 ' + '%(module)s:%(lineno)d: %(message)s') +log = logging.getLogger('dhpython') +os.umask(0o22) +DEFAULT = default('cpython3') +SUPPORTED = supported('cpython3') + + +class Scanner(Scan): + def handle_ext(self, fpath): + path, fname = fpath.rsplit('/', 1) + tagver = EXTFILE_RE.search(fname) + if tagver is None: + # yeah, python3.1 is not covered, but we don't want to + # mess with non-Python libraries, don't we? + return + tagver = tagver.groupdict()['ver'] + if tagver is None: + return + tagver = Version("%s.%s" % (tagver[0], tagver[1:])) + return tagver + + +def main(): + parser = ArgumentParser() + parser.add_argument( + '--version', action='version', version='%(prog)s DEVELV') + parser.add_argument( + '--no-guessing-deps', action='store_false', dest='guess_deps', + help='disable guessing dependencies') + parser.add_argument( + '--skip-private', action='store_true', + help="don't check private directories") + parser.add_argument( + '-v', '--verbose', action='store_true', + default=os.environ.get('DH_VERBOSE') == '1', + help='turn verbose mode on') + # arch=False->arch:all only, arch=True->arch:any only, None->all of them + parser.add_argument( + '-i', '--indep', action='store_false', dest='arch', default=None, + help='act on architecture independent packages') + parser.add_argument( + '-a', '-s', '--arch', action='store_true', dest='arch', + help='act on architecture dependent packages') + parser.add_argument( + '-q', '--quiet', action='store_false', dest='verbose', help='be quiet') + parser.add_argument( + '-p', '--package', action='append', metavar='PACKAGE', + help='act on the package named PACKAGE') + parser.add_argument( + '-N', '--no-package', action='append', metavar='PACKAGE', + help='do not act on the specified package') + parser.add_argument( + '--remaining-packages', action='store_true', + help='Do not act on the packages which have already been acted on by ' + 'this debhelper command earlier') + parser.add_argument( + '--compile-all', action='store_true', + help='compile all files from given private directory in postinst, not ' + 'just the ones provided by the package') + parser.add_argument( + '-V', type=VersionRange, dest='vrange', metavar='[X.Y][-][A.B]', + help='specify list of supported Python versions. See py3compile(1) for ' + 'examples') + parser.add_argument( + '-X', '--exclude', action='append', dest='regexpr', type=compiled_regex, + metavar='REGEXPR', + help='exclude items that match given REGEXPR. You may use this option ' + 'multiple times to build up a list of things to exclude.') + parser.add_argument( + '--accept-upstream-versions', action='store_true', + help='accept upstream versions while translating Python dependencies ' + 'into Debian ones') + parser.add_argument( + '--depends', action='append', metavar='REQ', + help='translate given requirements into Debian dependencies and add ' + 'them to ${python3:Depends}. Use it for missing items in ' + 'requires.txt.') + parser.add_argument( + '--depends-section', action='append', metavar='SECTION', + help='translate requirements from given section into Debian ' + 'dependencies and add them to ${python3:Depends}') + parser.add_argument( + '--recommends', action='append', metavar='REQ', + help='translate given requirements into Debian dependencies and add ' + 'them to ${python3:Recommends}') + parser.add_argument( + '--recommends-section', action='append', metavar='SECTION', + help='translate requirements from given section into Debian ' + 'dependencies and add them to ${python3:Recommends}') + parser.add_argument( + '--suggests', action='append', metavar='REQ', + help='translate given requirements into Debian dependencies and add ' + 'them to ${python3:Suggests}') + parser.add_argument( + '--suggests-section', action='append', metavar='SECTION', + help='translate requirements from given section into Debian ' + 'dependencies and add them to ${python3:Suggests}') + parser.add_argument( + '--requires', action='append', metavar='FILE', + help='translate requirements from given file into Debian dependencies ' + 'and add them to ${python3:Depends}') + parser.add_argument( + '--shebang', metavar='COMMAND', + help='use given command as shebang in scripts') + parser.add_argument( + '--ignore-shebangs', action='store_true', + help='do not translate shebangs into Debian dependencies') + parser.add_argument( + '--no-dbg-cleaning', action='store_false', dest='clean_dbg_pkg', + help='do not remove files from debug packages') + parser.add_argument( + '--no-ext-rename', action='store_true', + help='do not add magic tags nor multiarch tuples to extension file ' + 'names)') + parser.add_argument( + '--no-shebang-rewrite', action='store_true', + help='do not rewrite shebangs') + parser.add_argument('private_dir', nargs='?', + help='Private directory containing Python modules (optional)') + # debhelper options: + parser.add_argument('-O', action='append', help=SUPPRESS) + + options = parser.parse_args(os.environ.get('DH_OPTIONS', '').split() + + sys.argv[1:]) + if options.O: + parser.parse_known_args(options.O, options) + + private_dir = options.private_dir + if private_dir: + if not private_dir.startswith('/'): + # handle usr/share/foo dirs (without leading slash) + private_dir = '/' + private_dir + # TODO: support more than one private dir at the same time (see :meth:scan) + if options.skip_private: + private_dir = False + + if options.verbose: + log.setLevel(logging.DEBUG) + log.debug('version: DEVELV') + log.debug('argv: %s', sys.argv) + log.debug('options: %s', options) + log.debug('supported Python versions: %s (default=%s)', + ','.join(str(v) for v in SUPPORTED), DEFAULT) + else: + log.setLevel(logging.INFO) + + options.write_log = False + if os.environ.get('DH_INTERNAL_OVERRIDE', ''): + options.write_log = True + + try: + dh = DebHelper(options, impl='cpython3') + except Exception as e: + log.error('cannot initialize DebHelper: %s', e) + exit(2) + if not dh.packages: + log.error('no package to act on (python3-foo or one with ${python3:Depends} in Depends)') + # exit(7) + if not options.vrange and dh.python_version: + options.vrange = VersionRange(dh.python_version) + + interpreter = Interpreter('python3') + for package, pdetails in dh.packages.items(): + log.debug('processing package %s...', package) + interpreter.debug = package.endswith('-dbg') + + if not private_dir: + try: + pyinstall(interpreter, package, options.vrange) + except Exception as err: + log.error("%s.pyinstall: %s", package, err) + exit(4) + try: + pyremove(interpreter, package, options.vrange) + except Exception as err: + log.error("%s.pyremove: %s", package, err) + exit(5) + fix_locations(package, interpreter, SUPPORTED, options) + stats = Scanner(interpreter, package, private_dir, options).result + + dependencies = Dependencies(package, 'cpython3', dh.build_depends) + dependencies.parse(stats, options) + + pyclean_added = False # invoke pyclean only once in maintainer script + if stats['compile']: + args = '' + if options.vrange: + args += "-V %s" % options.vrange + dh.autoscript(package, 'postinst', 'postinst-py3compile', args) + dh.autoscript(package, 'prerm', 'prerm-py3clean', '') + pyclean_added = True + for pdir, details in sorted(stats['private_dirs'].items()): + if not details.get('compile'): + continue + if not pyclean_added: + dh.autoscript(package, 'prerm', 'prerm-py3clean', '') + pyclean_added = True + + args = pdir + + ext_for = details.get('ext_vers') + ext_no_version = details.get('ext_no_version') + if ext_for is None and not ext_no_version: # no extension + shebang_versions = list(i.version for i in details.get('shebangs', []) + if i.version and i.version.minor) + if not options.ignore_shebangs and len(shebang_versions) == 1: + # only one version from shebang + args += " -V %s" % shebang_versions[0] + elif options.vrange and options.vrange != (None, None): + args += " -V %s" % options.vrange + elif ext_no_version: + # at least one extension's version not detected + if options.vrange and '-' not in str(options.vrange): + ver = str(options.vrange) + else: # try shebang or default Python version + ver = (list(i.version for i in details.get('shebangs', []) + if i.version and i.version.minor) or [None])[0] or DEFAULT + dependencies.depend("python%s" % ver) + args += " -V %s" % ver + else: + extensions = sorted(ext_for) + vr = VersionRange(minver=extensions[0], maxver=extensions[-1]) + args += " -V %s" % vr + + for regex in options.regexpr or []: + args += " -X '%s'" % regex.pattern.replace("'", r"'\''") + + dh.autoscript(package, 'postinst', 'postinst-py3compile', args) + + dependencies.export_to(dh) + + pydist_file = join('debian', "%s.pydist" % package) + if exists(pydist_file): + if not validate_pydist(pydist_file): + log.warning("%s.pydist file is invalid", package) + else: + dstdir = join('debian', package, 'usr/share/python3/dist/') + if not exists(dstdir): + os.makedirs(dstdir) + fcopy(pydist_file, join(dstdir, package)) + bcep_file = join('debian', "%s.bcep" % package) + if exists(bcep_file): + dstdir = join('debian', package, 'usr/share/python3/bcep/') + if not exists(dstdir): + os.makedirs(dstdir) + fcopy(bcep_file, join(dstdir, package)) + + dh.save() + + +if __name__ == '__main__': + main() diff --git a/dh_python3.rst b/dh_python3.rst new file mode 100644 index 0000000..1c69634 --- /dev/null +++ b/dh_python3.rst @@ -0,0 +1,238 @@ +============ + dh_python3 +============ + +----------------------------------------------------------------------------------- +calculates Python dependencies, adds maintainer scripts to byte compile files, etc. +----------------------------------------------------------------------------------- + +:Manual section: 1 +:Author: Piotr Ożarowski, 2012-2013 + +SYNOPSIS +======== + dh_python3 -p PACKAGE [-V [X.Y][-][A.B]] DIR [-X REGEXPR] + +DESCRIPTION +=========== + +QUICK GUIDE FOR MAINTAINERS +--------------------------- + + * build depend on dh-python + * build-depend on python3 (Python application) or python3-all (Python module) + or python3-all-dev (Python extension), + * if necessary, describe supported Python 3 versions via X-Python3-Version field + in debian/control, + * build module/application using its standard build system (pybuild wrapper + recommended, see pybuild.1 for more details), remember to build extensions + for all supported Python 3 versions (loop over ``py3versions -vr``), + * install files to the *standard* locations, add `--install-layout=deb` to + setup.py's install command if your package is using distutils, + * add `python3` to dh's --with option, or: + * `include /usr/share/cdbs/1/class/python-distutils.mk` in debian/rules and + depend on `cdbs (>= 0.4.90)`, or: + * call ``dh_python3`` in the `binary-*` target, + * add `${python3:Depends}` to Depends + +NOTES +----- + +dependencies +~~~~~~~~~~~~ +dh_python3 tries to translate Python dependencies from `Requires-Dist` +entries in `dist-info` or `requires.txt` contents in `egg-info` to +Debian dependencies. +In many cases, this works without any additional configuration because +dh_python3 comes with a build-in mapping of Python module names to +Debian packages that is periodically regenerated from the Debian +archive. By default, the version information in the Python dependencies is +discarded. If you want dh_python3 to generate more strict dependencies (e.g. to +avoid ABI problems), or if the automatic mapping does not work correctly for +your package, you have to provide dh_python3 with additional rules for the +translation of Python module to Debian package dependencies. + +For a package *python3-foo* that depends on a package *python3-bar*, there are +two files that may provide such rules: + +#. If the *python3-foo* source package ships with a + `debian/py3dist-overrides` file, this file is used by dh_python3 + during the build of *python3-foo*. + +#. If the *python3-bar* source package ships with a + `debian/python3-bar.pydist` file (and uses dh_python3), this file + will be included in the binary package as + `/usr/share/dh-python/dist/cpython3/python3-bar`. During the build + of *python3-foo*, dh_python3 will then find and use the file. + +Both files have the same format described in +`/usr/share/doc/dh-python/README.PyDist`. If all you want is to generate +versioned dependencies (and assuming that the *python3-bar* package provides +the *pybar* Python module), in most cases it will be sufficient to put the line +``pybar python3-bar; PEP386`` into either of the above files. + +private dirs +~~~~~~~~~~~~ +`/usr/share/foo`, `/usr/share/games/foo`, `/usr/lib/foo` and +`/usr/lib/games/foo` private directories are scanned for Python files +by default (where `foo` is binary package name). If your package ships +Python files in some other directory, add another dh_python3 call in +debian/rules with directory name as an argument - you can use different set of +options in this call. If you need to change options (f.e. a list of supported +Python 3 versions) for a private directory that is checked by default, invoke +dh_python3 with --skip-private option and add another call with a path to this +directory and new options. + +debug packages +~~~~~~~~~~~~~~ +In binary packages which name ends with `-dbg`, all files in +`/usr/lib/python3/dist-packages/` directory +that have extensions different than `so` or `h` are removed by default. +Use --no-dbg-cleaning option to disable this feature. + +pyinstall files +~~~~~~~~~~~~~~~ +Files listed in debian/pkg.pyinstall file will be installed as public modules +(i.e. into .../dist-packages/ directory) for all requested Python versions. + +Syntax: ``path/to/file [NAMESPACE] [VERSION_RANGE]`` + +debian directory is automatically removed from the path, so you can place your +files in debian/ directory and install them from this location (if you want to +install them in "debian" namespace, set NAMESPACE to debian). If NAMESPACE is +set, all listed files will be installed in .../dist-packages/NAMESPACE/ +directory. + +Examples: + * ``foo.py`` installs .../dist-packages/foo.py for all supported Python versions + * ``foo/bar.py 3.3-`` installs .../dist-packages/foo/bar.py for versions >= 3.3 + * ``foo/bar.py spam`` installs .../dist-packages/spam/bar.py + * ``debian/*.py spam.egg 3.2`` installs .../python3.2/dist-packages/spam/egg/\*.py + files + +pyremove files +~~~~~~~~~~~~~~ +If you want to remove some public modules (i.e. files in .../dist-packages/ +directory) installed by build system (from all supported Python versions or +only from a subset of these versions), add them to debian/pkg.pyremove file. + +Examples: + * ``*.pth`` removes .pth files from .../dist-packages/ + * ``bar/baz.py 3.2`` removes .../python3.2/dist-packages/bar/baz.py + +bcep files +~~~~~~~~~~ +Byte-compilation exception patterns can be described in these files. Use it if +you want py3compile to skip specific files. This is the only way to skip .py +files in …/dist-packages/ directory (as `--exclude` passed to py3compile in +postinst is not used in rtupdate scripts and thus this option cannot be used +for non-private modules). + +``re|-3.6|/usr/lib/python3/dist-packages/jinja2|.*/async(foo|bar).py`` +will skip byte-compilation of `asyncfoo.py` and `asyncbar.py` in +`/usr/lib/python3/dist-packages/jinja2/` directory for each interpreter that +doesn't support `async` keyword (introduced in Python 3.6). + +If you want to skip byte-compilation in a subdirectory for all interpreters, use: +``dir|-4.0|/usr/lib/python3/dist-packages/foo/tests/``. +VERSION_RANGE (`-4.0` in the example) is described in `README.PyDist` file. + +`debian/python3-foo.bcep` file from source package will be included in the +binary package as `/usr/share/python3/bcep/python3-foo.bcep` + +overriding supported / default Python versions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you want to override system's list of supported Python versions or the +default one (f.e. to build a package that includes symlinks for older version +of Python or compile .py files only for given interpreter version), you can do +that via `DEBPYTHON3_SUPPORTED` and/or `DEBPYTHON3_DEFAULT` env. variables. + +Example: ``3.2,3.3`` limits the list of supported Python versions to Python 3.2 +and Python 3.3. + + +OPTIONS +======= +--version show program's version number and exit + +-h, --help show help message and exit + +--no-guessing-deps disable guessing dependencies + +--no-dbg-cleaning do not remove any files from debug packages + +--no-ext-rename do not add magic tags nor multiarch tuples to extension file names + +--no-shebang-rewrite do not rewrite shebangs + +--skip-private don't check private directories + +-v, --verbose turn verbose mode on + +-i, --indep act on architecture independent packages + +-a, --arch act on architecture dependent packages + +-q, --quiet be quiet + +-p PACKAGE, --package=PACKAGE act on the package named PACKAGE + +-N NO_PACKAGE, --no-package=NO_PACKAGE do not act on the specified package + +--remaining-packages Do not act on the packages which have already + been acted on by this command earlier in the same override. + +-V VERSION_RANGE specify list of supported Python 3 versions. See + py3compile(1) for examples + +-X REGEXPR, --exclude=REGEXPR exclude items that match given REGEXPR. You may + use this option multiple times to build up a list of things to exclude from + byte-compilation in private dirs. See also `bcep files`. + +--compile-all compile all files from given private directory in postinst/rtupdate + not just the ones provided by the package (i.e. do not pass the --package + parameter to py3compile/py3clean) + +--accept-upstream-versions accept upstream versions while translating + Python dependencies into Debian ones + +--depends=DEPENDS translate given requirements into Debian dependencies + and add them to ${python3:Depends}. Use it for missing items in + `requires.txt` / `Requires-Dist`. + +--depends-section=SECTION translate requirements from given extra + sections of `requres.txt` / `Requires-Dist` into Debian dependencies + and add them to ${python3:Depends}. May be repeated for multiple + sections. + +--recommends=RECOMMENDS translate given requirements into Debian dependencies + and add them to ${python3:Recommends} + +--recommends-section=SECTION translate requirements from given extra + sections of `requires.txt` / `Requires-Dist` into Debian dependencies + and add them to ${python3:Recommends}. May be repeated for multiple + sections. + +--suggests=SUGGESTS translate given requirements into Debian dependencies + and add them to ${python3:Suggests} + +--suggests-section=SECTION translate requirements from given extra + sections of `requires.txt` / `Requires-Dist` into Debian dependencies + and add them to ${python3:Suggests}. May be repeated for multiple + sections. + +--requires=FILENAME translate requirements from given file(s) into Debian + dependencies and add them to ${python3:Depends} + +--shebang=COMMAND use given command as shebang in scripts + +--ignore-shebangs do not translate shebangs into Debian dependencies + +SEE ALSO +======== +* /usr/share/doc/python3/python-policy.txt.gz +* /usr/share/doc/dh-python/README.PyDist +* pybuild(1) +* py3compile(1), py3clean(1) +* pycompile(1), pyclean(1) +* http://deb.li/dhp3 - most recent version of this document |