├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── RELEASE_NOTES.txt ├── build.earth ├── requirements.txt ├── ruff.toml ├── scripts ├── py2dsc ├── py2dsc-deb ├── pypi-download └── pypi-install ├── setup.py ├── stdeb.cfg ├── stdeb ├── __init__.py ├── cli_runner.py ├── command │ ├── __init__.py │ ├── bdist_deb.py │ ├── common.py │ ├── debianize.py │ ├── install_deb.py │ └── sdist_dsc.py ├── downloader.py ├── pypi_simple.py ├── transport.py └── util.py ├── test-pypi-install.sh ├── test.sh ├── test2and3.sh └── test_data ├── py2_only_pkg ├── py2_only_pkg │ ├── __init__.py │ └── py2_module.py └── setup.py ├── py3_only_pkg ├── py3_only_pkg │ └── py3_module.py └── setup.py └── simple_pkg ├── setup.py └── simple_pkg └── __init__.py /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | lint: 11 | strategy: 12 | fail-fast: false 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Earthly 17 | uses: earthly/actions-setup@v1 18 | - name: Lint 19 | run: | 20 | earthly --ci +lint 21 | build: 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | os: 26 | - "ubuntu:focal" 27 | - "debian:bullseye" 28 | - "ubuntu:jammy" 29 | - "debian:bookworm" 30 | - "ubuntu:noble" 31 | - "debian:trixie" 32 | runs-on: ubuntu-22.04 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Set up Earthly 36 | uses: earthly/actions-setup@v1 37 | - name: Run tests 38 | run: | 39 | earthly --ci +test --OS=${{matrix.os}} 40 | - name: Run pypi-install tests -- 41 | run: | 42 | earthly --ci +test-pypi-install --OS=${{matrix.os}} 43 | - name: Run 2and3 tests 44 | # This test can only be run on platforms that have Python 2 and Python 3 packages. 45 | if: ${{contains(fromJSON('["ubuntu:focal", "debian:bullseye", "ubuntu:jammy"]'), matrix.os)}} 46 | run: | 47 | earthly --ci +test-2and3 --OS=${{matrix.os}} 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | deb_dist 3 | *.pyc 4 | stdeb.egg-info/PKG-INFO 5 | stdeb.egg-info/SOURCES.txt 6 | stdeb.egg-info/dependency_links.txt 7 | stdeb.egg-info/entry_points.txt 8 | stdeb.egg-info/top_level.txt 9 | dist 10 | *~ 11 | MANIFEST 12 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | Since (and including) release 0.8.0, updates to this file are no 2 | longer made. Please see README.rst. 3 | 4 | = Release 0.7.1 (2014-05-04) = 5 | 6 | 2014-05-05 Add a install_deb distutils command. This builds and 7 | installs a python package as a system package. 8 | 9 | 2014-05-05 bugfixes 10 | 11 | = Release 0.7.0 (2014-05-04) = 12 | 13 | 2014-05-04 Run test scripts on http://travis-ci.org/ 14 | 15 | 2014-05-04 Add py2dsc-deb command. This command builds a .deb file 16 | from source package. 17 | 18 | 2014-04-10 Update PyPI URL to current location. (Thanks to Asheesh 19 | Laroia.) 20 | 21 | 2013-11-17 Use HTTPS URLS instead of HTTP. (Note: urllib2 does not 22 | validate certificates.) 23 | 24 | 2013-11-17 Add pypi-download command. This command provides a quick 25 | way to download a source package from PyPI. 26 | 27 | 2010-11-27 Use dh_python2 instead of dh_pysupport. (Thanks to Piotr 28 | Ożarowski.) 29 | 30 | 2010-06-19 Do not workaround Debian Bug #548392 by default. 31 | 32 | 2010-06-19 By default, do not attempt to migrate old stdeb based 33 | packages that used pycentral. 34 | 35 | = Release 0.6.0 (2010-06-18) = 36 | 37 | 2010-06-18 Do not die if sources.list does not contain deb-src 38 | lines. (Debian Bug#580376) 39 | 40 | 2010-06-18 Document need to call "apt-file update". Emit nicer error 41 | when user needs to call "apt-file update". (Closes #568122) 42 | 43 | 2010-06-18 Allow stopping if a requirement can't be satisfied. (Closes 44 | GH-7) Patch originally from Zooko. 45 | 46 | 2010-06-18 Create debian/source/format. (Closes GH-27) 47 | 48 | 2010-06-16 Added {$misc:Depends} to depends. (Closes #568692) 49 | 50 | 2010-06-03 Update to Standards-Version 3.8.4 51 | 52 | 2010-06-02 Bugfix: separate binary-arch and binary-indep targets. 53 | 54 | 2010-05-02 Add 'debianize' distutils command to create debian 55 | directory. This was suggested by Barry Warsaw in Python 56 | issue 1054967. 57 | 58 | 2010-05-02 Don't change dots to dashes in debian package names. (This 59 | was suggested by Barry Warsaw in Python issue 1054967.) 60 | Allow underscores in python package names, replace dashes 61 | with underscores (Derek Smith). 62 | 63 | 2010-02-05 Bugfix: add python-pkg-resouces to Depends for stdeb 64 | itself. 65 | 66 | = Release 0.5.1 (2010-01-09) = 67 | 68 | 2010-01-08 Fix bug where default description was wrong. Patch by 69 | Roland Sommer. (Closes GH-19). 70 | 71 | 2009-12-30 The option 'Distribution' option is changed to 'Suite' 72 | 73 | 2009-12-30 Limit long description to 20 lines. 74 | 75 | = Release 0.5.0 (2009-12-30) = 76 | 77 | 2009-12-30 All stdeb.cfg options can now be set using distutils 78 | command option passing. This required a couple of renamings 79 | (--default-maintiner should now be --maintainer, 80 | --default-distribution should now be --suite), although the 81 | old way will still work, as it is only deprecated for now. 82 | 83 | 2009-12-30 Add pypi-install script to automatically download a package 84 | distribution, build a .deb file from it, and install 85 | it. (Closes GH-16) 86 | 87 | 2009-12-30 Don't allow specifying sdist_dsc options to bdist_deb 88 | 89 | 2009-12-29 The --process-dependencies option to py2dsc was dropped, 90 | because this functionality went beyond the scope of the 91 | py2dsc script. The functionality could be added to a new 92 | script if desired. 93 | 94 | 2009-12-29 Add Section option to stdeb.cfg. (Closes GH-13) 95 | 96 | 2009-12-29 Move stdeb.cfg file from egg-info directory to alongside setup.py 97 | 98 | 2009-12-28 --guess-conflicts-provides-replaces option. Provides 99 | automatic finding of original Debian source package's 100 | binaries, and create Conflicts/Provides/Replaces entries in 101 | debian/control for them. (Closes GH-17) 102 | 103 | 2009-12-28 Remove dependency on setuptools. (Closes GH-18) 104 | 105 | = Release 0.4.3 (2009-12-28) = 106 | 107 | 2009-12-01 add '--force-buildsystem' command option to allow forcing 108 | of python-distutils build system, even in presence of 109 | Makefile (defaults to True) 110 | 111 | 2009-11-30 Fixed a typo on the sdist_dsc.py which was throwing 112 | exception from paver. Fix #14 113 | 114 | = Release 0.4.2 (2009-11-02) = 115 | 116 | 2009-11-02 Bump debian/compat to '7'. 117 | 118 | 2009-11-02 Don't include python-central in build package's Depends list. 119 | 120 | 2009-11-02 bugfix: actually parse list of supported Python versions correctly 121 | 122 | = Release 0.4.1 (2009-10-04) = 123 | 124 | 2009-10-03 Emit warnings if debhelper and python-support too old 125 | 126 | 2009-10-03 Update and improve documentation 127 | 128 | 2009-10-03 Allow command-line specified breaking of backward 129 | compatibility. (Closes GH-12) 130 | 131 | 2009-10-01 Fix incorrect handling of symlinks. (Ximin Luo) 132 | 133 | 2009-10-01 relax dependency on python-support down to 0.8.4 for Debian 134 | Lenny 135 | 136 | 2009-10-01 only include python-all-dev in Build-Deps if extension 137 | modules present (Alexander D. Sedov) 138 | 139 | 2009-09-29 bdist_deb accepts all sdist_dsc arguments (Gerry Reno) 140 | 141 | = Release 0.4 (2009-09-27) = 142 | 143 | 2009-06-01 Use debhelper 7 by default 144 | 145 | 2009-06-01 Switch from python-central to python-support 146 | 147 | 2009-06-02 workaround Debian #479852: remove left over python-central symlinks 148 | 149 | 2009-03-23 Unset environment variables set by dpkg-buildpackage 150 | 151 | = Release 0.3.2 (2009-10-04) = 152 | 153 | 2009-09-29 bdist_deb accepts all sdist_dsc arguments (Gerry Reno) 154 | 155 | 2009-09-29 add --disable-single-version-externally-managed option 156 | (initial patch from Gerry Reno) 157 | 158 | 159 | = Release 0.3.1 (2009-09-27) = 160 | 161 | 2009-09-22 Implement bdist_deb command (initial patch from Gerry Reno) 162 | 163 | = Release 0.3 (2009-03-21) = 164 | 165 | 2009-03-14 On Python2.6, pass "--install-layout=deb" to the distutils install 166 | command. This is required for Ubuntu Jaunty, and the current 167 | implementation in stdeb is also compatible with the python2.6 168 | package in Debian experimental. 169 | 170 | 2009-02-24 bugfix to autofind-depends feature: include good package if found, 171 | even when version information not specified 172 | 173 | = Release 0.2.3 (2009-02-17) = 174 | 175 | 2009-02-16 new patch from zooko to improve regexp search for dependencies 176 | 177 | 2009-02-14 fix a crash with an undefined variable 'ver' (from Brett) 178 | 179 | 2009-02-14 Add --ignore-install-requires option. (From Brett) 180 | adds the ignore-install-requires command line option to 181 | keep stdeb from guessing python packages names from 182 | requires.txt and lets you specify your own Depends: without 183 | adding those from the egg requires.txt.... 184 | 185 | = Release 0.2.2 (2009-01-29) = 186 | 187 | 2009-01-29 Rename README.txt to README.rst so it renders on github 188 | better. 189 | 190 | 2009-06-29 Integrate zooko's autofind-depends patch. 191 | 192 | 2008-04-28 Allow comments after config entry 193 | 194 | = Release 0.2.1 (2008-04-26) = 195 | 196 | 2008-04-26 Find .egg-info directory in debian/rules using a glob 197 | rather than attempting to guess it, which was fragile. 198 | 199 | 2008-04-26 Use Python2.5's subprocess.check_call() instead of our 200 | home-brewed version. 201 | 202 | 2008-04-26 Use "date -R" instead of "822-date". 203 | 204 | 2008-04-26 Erase temporary directories created on prior run of stdeb. 205 | 206 | 2008-04-26 Don't break if no author is specified in setup.py. 207 | 208 | 2008-04-26 Add tests for more styles of packages. 209 | 210 | = Release 0.2 (2008-04-26) = 211 | 212 | 2008-03-27 Add ability to pass environment variables to setup.py 213 | script. 214 | 215 | 2008-03-18 Do not allow '.' in source package names. 216 | 217 | 2008-01-20 Allows a user to build every dependency a package has 218 | stated on it's setup.py, recursively. 219 | 220 | 2007-10-29 Allow upstream tarball to have different name from debian 221 | .orig.tar.gz but keep md5sum. 222 | 223 | 2007-05-28 Fix bug where python distribution name contained '-' but 224 | setuptools renamed this to '_'. 225 | 226 | 2007-05-11 Fix py2dsc script to properly set __file__ and __name__. 227 | 228 | 2007-04-18 Fix bug where .egg-info renaming failed when upstream 229 | version contained '-'. 230 | 231 | = Release 0.2.a1 (2007-04-02) = 232 | 233 | 2007-04-02 Make default distribution 'unstable' 234 | 235 | 2007-03-28 Removed versioned Python interpreter from packages in 236 | /usr/bin and /usr/lib. Bump version to 0.2.a1. Include 237 | version in autogenerated debian/rules and debian/changelog. 238 | 239 | 2007-03-15 pycentral support enabled by default 240 | 241 | 2007-02-04 fix for cases in which setup.py wants __file__ variable 242 | 243 | 2006-09-07 added --patch-posix to support posix-mode patches 244 | 245 | 2006-09-07 always extract source using "dpkg-source -x" 246 | 247 | 2006-09-07 moved working directory for patches one level shallower 248 | (may require patch level 1 instead of patch level 0) 249 | 250 | 2006-08-25 added Forced-Upstream-Version field in .cfg file 251 | 252 | 2006-08-22 patching source works when --premade-distfile option is 253 | given but distfile must be regenerated 254 | 255 | 2006-08-22 patch level supported via --patch-level option 256 | 257 | 2006-08-22 Add support for bzip2 tarballs 258 | 259 | 2006-08-13 Generate source by copying tree, not "python setup.py 260 | sdist" 261 | 262 | 2006-08-13 Patches can be applied prior to first call to setup.py 263 | 264 | 2006-07-21 Allow patches to upstream source via Stdeb-Patch-File in 265 | .cfg file. 266 | 267 | 2006-07-21 Don't bother trying to rename .egg-info directories. 268 | 269 | 2006-07-09 Ability to specify MIME-Desktop-Files, MIME-File, and 270 | Shared-MIME-File in configuration file. 271 | 272 | 2006-07-09 Use of dh_python for generation of postinst and prerm 273 | scripts. 274 | 275 | 2006-07-05 Auto-generation of debian/postinst and debian/prerm scripts 276 | that create and remove .pyc and .pyo files. 277 | 278 | 2006-06-21 Upstream .tar.gz files can be used exactly, allowing 279 | md5sums to match. 280 | 281 | 2006-06-21 Expanded source directory is only deleted when commandline 282 | option is set. 283 | 284 | =========================================================== 285 | 2006-06-14 Release 0.1, svn version 11 286 | 287 | 2006-06-18 Refactored to support calling from scripts. 288 | 289 | =========================================================== 290 | 2006-06-14 Release 0.0.1, svn version 3 291 | 292 | 2006-06-14 Initial import of repository and initial release. 293 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2014 stdeb authors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include *.rst 3 | include *.sh 4 | include stdeb.cfg 5 | include MANIFEST.in 6 | include .gitignore 7 | recursive-include test_data * 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://github.com/astraw/stdeb/actions/workflows/ci.yaml/badge.svg 2 | :target: https://github.com/astraw/stdeb/actions/workflows/ci.yaml 3 | 4 | stdeb - Python to Debian source package conversion utility 5 | ========================================================== 6 | 7 | `stdeb `_ produces Debian source 8 | packages from Python packages via a new distutils command, 9 | ``sdist_dsc``. Automatic defaults are provided for the Debian package, 10 | but many aspects of the resulting package can be customized (see the 11 | customizing section, below). An additional command, ``bdist_deb``, 12 | creates a Debian binary package, a .deb file. The ``install_deb`` 13 | command installs this .deb file. The ``debianize`` command builds a 14 | ``debian/`` directory directly alongside your setup.py. 15 | 16 | Several convenience utilities are also provided: 17 | 18 | * ``pypi-download`` will query the `Python Package Index (PyPI) 19 | `_ for a package and download it. 20 | * ``pypi-install`` will query the `Python Package Index (PyPI) 21 | `_ for a package, download it, create a 22 | .deb from it, and then install the .deb. 23 | * ``py2dsc`` will convert a distutils-built source tarball into a 24 | Debian source package. 25 | * ``py2dsc-deb`` will convert a distutils-built source tarball into a 26 | Debian source package and then use the Debian machinery to build a 27 | .deb file from this. 28 | 29 | .. contents:: 30 | 31 | 32 | Python 3 support 33 | ---------------- 34 | 35 | As explained in more detail below, the heart of stdeb is the sdist_dsc 36 | distutils command. This command runs once to generate a Debian source 37 | package. This Debian source package can specify building packages for 38 | Python 2, Python 3, or both. Furthermore, this generation can be done 39 | with the Python 2 or Python 3 interpreter. By default, only packages 40 | are built for the version of Python being used. To override this, use 41 | ``--with-python2=True`` or ``--with-python3=True`` as an argument to 42 | the sdist_dsc distutils command (or use both to be sure). For example, 43 | to build only a Python 3 package using the Python 3 interpreter:: 44 | 45 | python3 setup.py --command-packages=stdeb.command bdist_deb 46 | 47 | To build both Python 2 and Python 3 packages using the Python 3 48 | interpreter (and only the Python3 package installs scripts):: 49 | 50 | python3 setup.py --command-packages=stdeb.command sdist_dsc --with-python2=True --with-python3=True --no-python2-scripts=True bdist_deb 51 | 52 | News 53 | ---- 54 | 55 | * 2024-11-14: **Version 0.10.1**. See the `download page 56 | `__. 57 | This is the last planned release of stdeb which supports running stdeb 58 | scripts with Python 2.7. Generating Python 2 packages with future releases 59 | will be maintained on a best-effort basis. Users of stdeb's Python 2 support 60 | are encouraged to report issues and provide test cases which can be added to 61 | CI. 62 | 63 | * Bugfixes: 64 | 65 | * Fix udev rule filenames for automatic dh_installudev recognition. (#180) 66 | 67 | * Improvements: 68 | 69 | * The ``--sign-key`` argument can now be used to provide an alternative key 70 | rather than always signing with the default key. (#187) 71 | 72 | * Switch PyPI API usage to JSON and "Simple" APIs now that the XML-RPC API is deprecated. (#201, #202) 73 | 74 | * Detect and use the current binary name for Python 2. (#203) 75 | Ubuntu Focal and Ubuntu Jammy install a `python2` binary when the 76 | `python-all-dev` package is installed. Rather than assuming that the 77 | `python` binary is available and is Python 2, check for a `python` or 78 | `python2` binary and use what is found. 79 | 80 | * Development changes: 81 | 82 | * Continuous Integration is now run on GitHub Actions using Earthly. (#199) 83 | * Use ruff for style and lint checks (currently not enforced). (#199) 84 | 85 | * 2020-10-28: **Version 0.10.0**. See the `download page 86 | `__. 87 | 88 | * Bugfixes: 89 | 90 | * add sleep between PyPI API calls to avoid rate limit (#173) 91 | 92 | * Improvements: 93 | 94 | * use SOURCE_DATE_EPOCH if set for timestamp in generated changelog to 95 | generate reproducbile artifacts (#166) 96 | * update debhelper compat version from 7 to 9 (#158) 97 | * added flag --with-dh-systemd (#162) 98 | * add support for DEBEMAIL envvar (#168) 99 | * use setuptools "url" field for "Homepage" field in debian/control (#169) 100 | * dh_virtualenv: specify Python version (#165) 101 | * added compat flag to modify Debian compatibility level (#163) 102 | 103 | * Cosmetic: 104 | * remove excess newlines from debian/control and rules file (#167) 105 | * use flake8 to check style in Travis CI, update code to comply (#171) 106 | 107 | * 2020-06-11: **Version 0.9.1**. See the `download page 108 | `__. 109 | 110 | * Bugfixes: 111 | 112 | * handle path with spaces in zip tarball (#150) 113 | * fix map() iterator issue in Python 3 (#152) 114 | * fix checking for python3-all (instead of python-all) when using only 115 | Python 3 (#154) 116 | 117 | * Improvements: 118 | 119 | * get date in Python, avoiding requiring ``date`` on macOS (#115) 120 | * add configuration file option ``Python2-Depends-Name`` (#156) 121 | * add an option ``--with-dh-virtualenv`` (#155) 122 | * add an option named ``ignore-source-changes`` (#151) 123 | 124 | * 2019-12-09: **Version 0.9.0**. See the `download page 125 | `__. 126 | 127 | * Bugfixes: 128 | 129 | * fix applying patch files under Python 3 130 | 131 | * Improvements: 132 | 133 | * add ``--sign-results`` to sdist_dsc and bdist_deb command 134 | * add ``--debian-version`` to CLI program options 135 | * add support for ``Breaks`` / ``Breaks3`` in debian/control 136 | * add support for ``Suite3`` option 137 | * support zip files in pypi-install 138 | 139 | * Breaking changes: 140 | 141 | * remove deprecated ``dh_desktop`` call 142 | 143 | * 2015-02-18: **Version 0.8.5**. See the `download page 144 | `__. Bugfixes: reverted 145 | change that installed into virtualenv when built in 146 | virtualenv. Improvements: Added 147 | `--allow-virtualenv-install-location` to allow installing into 148 | virtualenv location. Supports Debian Squeeze (6), Debian Wheezy 149 | (7), Ubuntu Precise (12.04), Ubuntu Trusty (14.04) and later 150 | releases. 151 | 152 | * 2015-02-16: **Version 0.8.4**. See the `download page 153 | `__. Bugfixes: works on 154 | Python 3.4 (e.g. Ubuntu Trusty) again. Improvements: Improved 155 | customization for Python 3 (Dirk Thomas added 156 | `force-x-python3-version` and `X-Python3-Version` and Louis for 157 | `Recommends3`, `Suggests3`, `Provides3` and `Replaces3` 158 | support. Supports Debian Squeeze (6), Debian Wheezy (7), Ubuntu 159 | Precise (12.04), Ubuntu Trusty (14.04) and later releases. 160 | 161 | * 2015-02-14: **Version 0.8.3**. See the `download page 162 | `__. This is a bugfix 163 | release which fixes several aspects of Unicode support. Tests pass 164 | on Debian Squeeze (6), Debian Wheezy (7), and Ubuntu Precise 165 | (12.04). Support for Python 3.4 (e.g. Ubuntu Trusty 14.04) was 166 | mistakenly broken and was fixed in the 0.8.3 release. 167 | 168 | * 2014-8-14: **Version 0.8.2**. See the `download page 169 | `__. This is a bugfix 170 | release fixing a serious issue that would cause a Python 2 package 171 | to be built if only a Python 3 package was requested in some 172 | circumstances. 173 | 174 | * 2014-8-10: **Version 0.8.1**. See the `download page 175 | `__. Due 176 | to bugs in 0.8.0, this release is the first announced from the 0.8 177 | series. Highlights since 0.7.1: 178 | 179 | - Full support for Python 3. This includes being run from Python 3 180 | and generating packages for Python 3. The default is to build 181 | Python 3 packages when run with Python 3 and to build Python 2 182 | packages when run from Python 2. Command line options can be used 183 | to build packages for the other Python interpreter, too. 184 | 185 | - Build .changes file for source package. While this still must be 186 | signed for upload to a PPA, for example, it should still be 187 | useful in some cases. 188 | 189 | - Switch to Debian source format 3.0 (quilt). Practically speaking, 190 | the .diff.gz file that used to come with a source package is now 191 | replaced by a .debian.tar.gz file. 192 | 193 | - Verify SSL certificates when talking to PyPI using 194 | Requests. (Verification requires Requests >= 0.8.8.) 195 | 196 | - Many bugfixes. 197 | 198 | * 2014-05-05: **Version 0.7.1**. See the `download page 199 | `__. Highlights for this 200 | release (you may also wish to consult the full `changelog 201 | `__). Due 202 | to bugs in 0.7.0, this release is the first announced from the 0.7 203 | series. Highlights since 0.6.0: 204 | 205 | - New commands: pypi-download and pypi-install to directly download 206 | and install packages from PyPI, respectively. py2dsc-deb directly 207 | creates a .deb file from a source tarball. 208 | 209 | - New distutils command: install_deb lets you directly install a 210 | python package as a standard system package. 211 | 212 | - Many bugfixes, including the new URL for PyPI. 213 | 214 | - Automated runs of test suite, thanks to Travis CI 215 | 216 | - Thanks to many, especially Piotr Ożarowski for help with stdeb. 217 | 218 | * 2010-06-18: **Version 0.6.0**. See the `download page 219 | `__. Highlights for this 220 | release (you may also wish to consult the full `changelog 221 | `__): 222 | 223 | - A new ``debianize`` command to build a ``debian/`` directory 224 | alongside your setup.py file. 225 | 226 | - Bugfixes. 227 | 228 | * 2010-01-09: **Version 0.5.1**. Bugfix release. See the `download 229 | page `__, the `changelog 230 | `__ 231 | and `release notes 232 | `__. 233 | 234 | * 2009-12-30: **Version 0.5.0**. See the `download page 235 | `__. Highlights for this 236 | release (you may also wish to consult the full `changelog 237 | `__): 238 | 239 | - A new ``pypi-install`` script will automatically download, make a 240 | .deb, and install packages from the `Python Package Index (PyPI)`_. 241 | 242 | - Removal of the setuptools dependency. 243 | 244 | - New option (`--guess-conflicts-provides-replaces`) to query 245 | original Debian packages for Conflicts/Provides/Replaces 246 | information. 247 | 248 | - As a result of these changes and to fix a couple bugs/warts, some 249 | minor backwards incompatible changes and deprecations were 250 | made. Please check the `release notes 251 | `__. 252 | 253 | * 2009-12-28: Version 0.4.3 Released. See the `download page`__. See the 254 | `changelog`__ and `release notes`__. 255 | * 2009-11-02: Version 0.4.2 Released. See the `download page`__. See the 256 | `changelog`__ and `release notes`__. 257 | * 2009-10-04: Version 0.4.1 Released. See the `download page`__. See the 258 | `changelog`__ and `release notes`__. 259 | * 2009-09-27: Version 0.4 Released. See the `download page`__. This 260 | version switches to debhelper 7. See the `Changelog for 0.4`__. 261 | 262 | __ http://pypi.python.org/pypi/stdeb/0.4.3 263 | __ http://github.com/astraw/stdeb/blob/release-0.4.3/CHANGELOG.txt 264 | __ http://github.com/astraw/stdeb/blob/release-0.4.3/RELEASE_NOTES.txt 265 | __ http://pypi.python.org/pypi/stdeb/0.4.2 266 | __ http://github.com/astraw/stdeb/blob/release-0.4.2/CHANGELOG.txt 267 | __ http://github.com/astraw/stdeb/blob/release-0.4.2/RELEASE_NOTES.txt 268 | __ http://pypi.python.org/pypi/stdeb/0.4.1 269 | __ http://github.com/astraw/stdeb/blob/release-0.4.1/CHANGELOG.txt 270 | __ http://github.com/astraw/stdeb/blob/release-0.4.1/RELEASE_NOTES.txt 271 | __ http://pypi.python.org/pypi/stdeb/0.4 272 | __ http://github.com/astraw/stdeb/blob/release-0.4/CHANGELOG.txt 273 | 274 | Releases up to and including 0.3.2 are compatible with Ubuntu Hardy. 275 | 276 | * 2009-10-04: Version 0.3.2 Released. See the `download page`__. See the `Changelog for 0.3.2`__ 277 | * 2009-09-27: Version 0.3.1 Released. See the `download page`__. See the `Changelog for 0.3.1`__ 278 | * 2009-03-21: Version 0.3 Released. See the `download page`__. See the `Changelog for 0.3`__ 279 | * 2009-02-17: Version 0.2.3 Released. See the `download page`__. See the `Changelog for 0.2.3`__ 280 | * 2009-01-29: Version 0.2.2 Released. See the `download page`__. See the `Changelog for 0.2.2`__ 281 | * 2008-04-26: Version 0.2.1 Released. See the `download page`__. See the `Changelog for 0.2.1`__ 282 | * 2008-04-26: Version 0.2 Released. See the `download page`__. See the `Changelog for 0.2`__ 283 | * 2007-04-02: Version 0.2.a1 Released. See the `old download page`_. 284 | * 2006-06-19: Version 0.1 Released. See the `old download page`_. 285 | 286 | __ http://pypi.python.org/pypi/stdeb/0.3.2 287 | __ http://github.com/astraw/stdeb/blob/release-0.3.2/CHANGELOG.txt 288 | __ http://pypi.python.org/pypi/stdeb/0.3.1 289 | __ http://github.com/astraw/stdeb/blob/release-0.3.1/CHANGELOG.txt 290 | __ http://pypi.python.org/pypi/stdeb/0.3 291 | __ http://github.com/astraw/stdeb/blob/release-0.3/CHANGELOG.txt 292 | __ http://pypi.python.org/pypi/stdeb/0.2.3 293 | __ http://github.com/astraw/stdeb/blob/release-0.2.3/CHANGELOG.txt 294 | __ http://pypi.python.org/pypi/stdeb/0.2.2 295 | __ http://github.com/astraw/stdeb/blob/release-0.2.2/CHANGELOG.txt 296 | __ http://pypi.python.org/pypi/stdeb/0.2.1 297 | __ http://github.com/astraw/stdeb/blob/release-0.2.1/CHANGELOG.txt 298 | __ http://pypi.python.org/pypi/stdeb/0.2 299 | __ http://github.com/astraw/stdeb/blob/release-0.2/CHANGELOG.txt 300 | 301 | The commands 302 | ------------ 303 | 304 | pypi-download, command-line command 305 | ``````````````````````````````````` 306 | 307 | ``pypi-download`` takes a package name, queries PyPI for it and downloads 308 | it:: 309 | 310 | pypi-download [options] mypackage 311 | 312 | pypi-install, command-line command 313 | `````````````````````````````````` 314 | 315 | ``pypi-install`` takes a package name, queries PyPI for it, downloads 316 | it, builds a Debian source package and then .deb from it, and this 317 | installs it:: 318 | 319 | pypi-install [options] mypackage 320 | 321 | py2dsc, command-line command 322 | ```````````````````````````` 323 | 324 | ``py2dsc`` takes a .tar.gz source package and build a Debian source 325 | package from it:: 326 | 327 | py2dsc [options] mypackage-0.1.tar.gz # uses pre-built Python source package 328 | 329 | py2dsc-deb, command-line command 330 | ```````````````````````````````` 331 | 332 | ``py2dsc-deb`` takes a .tar.gz source package and build a Debian source 333 | package and then a .deb file from it:: 334 | 335 | py2dsc-deb [options] mypackage-0.1.tar.gz # uses pre-built Python source package 336 | 337 | sdist_dsc, distutils command 338 | ```````````````````````````` 339 | All methods eventually result in a call to the ``sdist_dsc`` distutils 340 | command. You may prefer to do so directly:: 341 | 342 | python setup.py --command-packages=stdeb.command sdist_dsc 343 | 344 | A Debian source package is produced from unmodified 345 | Python packages. The following files are produced in a newly created 346 | subdirectory ``deb_dist``: 347 | 348 | * ``packagename_versionname.orig.tar.gz`` 349 | * ``packagename_versionname-debianversion.dsc`` 350 | * ``packagename_versionname-debianversion.diff.gz`` 351 | 352 | These can then be compiled into binary packages using the standard 353 | Debian machinery (e.g. dpkg-buildpackage). 354 | 355 | bdist_deb, distutils command 356 | ```````````````````````````` 357 | A ``bdist_deb`` distutils command is installed. This calls the 358 | sdist_dsc command and then runs dpkg-buildpackage on the result:: 359 | 360 | python setup.py --command-packages=stdeb.command bdist_deb 361 | 362 | install_deb, distutils command 363 | `````````````````````````````` 364 | 365 | The ``install_deb`` distutils command calls the bdist_deb command and 366 | then installs the result. You need to run this with superuser privilege:: 367 | 368 | sudo python setup.py --command-packages=stdeb.command install_deb 369 | 370 | debianize, distutils command 371 | ```````````````````````````` 372 | The ``debianize`` distutils command builds the same ``debian/`` 373 | directory as used in the previous command, but the output is placed 374 | directly in the project's root folder (alongside setup.py). This is 375 | useful for customizing the Debian package directly (rather than using 376 | the various stdeb options to tune the generated package). 377 | 378 | :: 379 | 380 | python setup.py --command-packages=stdeb.command debianize 381 | 382 | A note about telling distutils to use the stdeb distutils commands 383 | `````````````````````````````````````````````````````````````````` 384 | 385 | Distutils command packages can also be specified in distutils 386 | configuration files (rather than using the ``--command-packages`` 387 | command line argument to ``setup.py``), as specified in the `distutils 388 | documentation 389 | `_. Specifically, 390 | you could include this in your ``~/.pydistutils.cfg`` file:: 391 | 392 | [global] 393 | command-packages: stdeb.command 394 | 395 | Examples 396 | -------- 397 | 398 | These all assume you have stdeb installed in your system Python 399 | path. stdeb also works from a non-system Python path (e.g. a 400 | `virtualenv `_). 401 | 402 | Quickstart 1: Install something from PyPI now, I don't care about anything else 403 | ``````````````````````````````````````````````````````````````````````````````` 404 | 405 | Do this from the command line:: 406 | 407 | pypi-install mypackage 408 | 409 | **Warning: Despite doing its best, there is absolutely no way stdeb 410 | can guarantee all the Debian package dependencies will be properly 411 | fulfilled without manual intervention. Using pypi-install bypasses 412 | your ability to customize stdeb's behavior. Read the rest of this 413 | document to understand how to make better packages.** 414 | 415 | Quickstart 2: Just tell me the fastest way to make a .deb 416 | ````````````````````````````````````````````````````````` 417 | 418 | (First, install stdeb as you normally install Python packages.) 419 | 420 | Do this from the directory with your `setup.py` file:: 421 | 422 | python setup.py --command-packages=stdeb.command bdist_deb 423 | 424 | This will make a Debian source package (.dsc, .orig.tar.gz and 425 | .diff.gz files) and then compile it to a Debian binary package (.deb) 426 | for your current system. The result will be in ``deb_dist``. 427 | 428 | **Warning: installing the .deb file on other versions of Ubuntu or 429 | Debian than the one on which it was compiled will result in undefined 430 | behavior. If you have extension modules, they will probably 431 | break. Even in the absence of extension modules, bad stuff will likely 432 | happen.** 433 | 434 | For this reason, it is much better to build the Debian source package 435 | and then compile that (e.g. using `Ubuntu's PPA`__) for each target 436 | version of Debian or Ubuntu. 437 | 438 | __ https://help.launchpad.net/Packaging/PPA 439 | 440 | Quickstart 3: I read the warning, so show me how to make a source package, then compile it 441 | `````````````````````````````````````````````````````````````````````````````````````````` 442 | 443 | This generates a source package:: 444 | 445 | pypi-download Reindent --release=0.1.0 446 | py2dsc Reindent-0.1.0.tar.gz 447 | 448 | This turns it into a .deb using the standard Debian tools. (Do *this* 449 | on the same source package for each target distribution):: 450 | 451 | cd deb_dist/reindent-0.1.0/ 452 | dpkg-buildpackage -rfakeroot -uc -us 453 | 454 | This installs it:: 455 | 456 | cd .. 457 | sudo dpkg -i python-reindent_0.1.0-1_all.deb 458 | 459 | Quickstart 4: Install from a Python package direct to a debian system package 460 | ````````````````````````````````````````````````````````````````````````````` 461 | 462 | (First, install stdeb as you normally install Python packages.) 463 | 464 | Do this from the directory with your `setup.py` file:: 465 | 466 | python setup.py --command-packages=stdeb.command install_deb 467 | 468 | This will make a Debian source package (.dsc, .orig.tar.gz and 469 | .diff.gz files), compile it to a Debian binary package (.deb) for your 470 | current system and then install it using ``dpkg``. 471 | 472 | 473 | Another example, with more explanation 474 | `````````````````````````````````````` 475 | 476 | This example is more useful if you don't have a Python source package 477 | (.tar.gz file generated by ``python setup.py sdist``). For the sake of 478 | illustration, we do download such a tarball, but immediately unpack it 479 | (alternatively, use a version control system to grab the unpacked 480 | source of a package):: 481 | 482 | pypi-download Reindent --release=0.1.0 483 | tar xzf Reindent-0.1.0.tar.gz 484 | cd Reindent-0.1.0 485 | 486 | The following will generate a directory ``deb_dist`` containing the 487 | files ``reindent_0.1.0-1.dsc``, ``reindent_0.1.0.orig.tar.gz`` and 488 | ``reindent_0.1.0-1.diff.gz``, which, together, are a debian source 489 | package:: 490 | 491 | python setup.py --command-packages=stdeb.command sdist_dsc 492 | 493 | The source generated in the above way is also extracted (using 494 | ``dpkg-source -x``) and placed in the ``deb_dist`` subdirectory. To 495 | continue the example above:: 496 | 497 | cd deb_dist/reindent-0.1.0 498 | dpkg-buildpackage -rfakeroot -uc -us 499 | 500 | Finally, the generated package can be installed:: 501 | 502 | cd .. 503 | sudo dpkg -i python-reindent_0.1.0-1_all.deb 504 | 505 | For yet another example of use, with still more explanation, see 506 | `allmydata-tahoe ticket 251`_. 507 | 508 | .. _allmydata-tahoe ticket 251: http://allmydata.org/trac/tahoe/ticket/251 509 | 510 | Download 511 | -------- 512 | 513 | Files are available at the `download page`_ (for ancient releases, see 514 | the `old download page`_). 515 | 516 | .. _download page: https://pypi.python.org/pypi/stdeb 517 | .. _old download page: http://stdeb.python-hosting.com/wiki/Download 518 | 519 | The git repository is available at 520 | http://github.com/astraw/stdeb 521 | 522 | Install (or, using stdeb to create an stdeb installer) 523 | ------------------------------------------------------ 524 | 525 | For a bit of fun, here's how to install stdeb using stdeb. Note that 526 | stdeb is also in Debian and Ubuntu, so this recipe is only necessary 527 | to install a more recent stdeb. 528 | 529 | :: 530 | 531 | STDEB_VERSION="0.10.1" 532 | 533 | # Download stdeb 534 | pypi-download stdeb --release=$STDEB_VERSION 535 | 536 | # Extract it 537 | tar xzf stdeb-$STDEB_VERSION.tar.gz 538 | 539 | # Enter extracted source package 540 | cd stdeb-$STDEB_VERSION 541 | 542 | # Build .deb (making use of stdeb package directory in sys.path). 543 | python setup.py --command-packages=stdeb.command bdist_deb 544 | 545 | # Install it 546 | sudo dpkg -i deb_dist/python-stdeb_$STDEB_VERSION-1_all.deb 547 | 548 | Background 549 | ---------- 550 | 551 | For the average Python package, its source distribution 552 | (python_package.tar.gz created with ``python setup.py sdist``) 553 | contains nearly everything necessary to make a Debian source 554 | package. This near-equivalence encouraged me to write this distutils 555 | extension, which executes the setup.py file to extract relevant 556 | information. `setuptools 557 | `_ may optionally 558 | be used. 559 | 560 | I wrote this initially to Debianize several Python packages of my own, 561 | but I have the feeling it could be generally useful. It appears 562 | similar, at least in theory, to easydeb_, `Logilab's Devtools`_, 563 | bdist_dpkg_, bdist_deb_, pkgme_ and `dh-virtualenv 564 | `__. 565 | 566 | .. _easydeb: http://easy-deb.sourceforge.net/ 567 | .. _Logilab's DevTools: http://www.logilab.org/projects/devtools 568 | .. _bdist_dpkg: http://svn.python.org/view/sandbox/trunk/Lib/bdist_dpkg.py 569 | .. _bdist_deb: http://bugs.python.org/issue1054967 570 | .. _pkgme: https://launchpad.net/pkgme 571 | 572 | Features 573 | -------- 574 | 575 | * Create a package for all Python versions supported by 576 | python-support. (Limiting this range is possible with the 577 | ``XS-Python-Version:`` config option.) 578 | 579 | * Automatic conversion of Python package names into valid Debian 580 | package names. 581 | 582 | * Attempt to automatically convert version numbers such that ordering 583 | is maintained. See also the config option 584 | ``Forced-Upstream-Version``. 585 | 586 | * Fine grained control of version numbers. (``Debian-Version``, 587 | ``Forced-Upstream-Version``, ``Upstream-Version-Prefix``, 588 | ``Upstream-Version-Suffix`` config options.) 589 | 590 | * Install .desktop files. (``MIME-Desktop-Files`` config option.) 591 | 592 | * Install .mime and .sharedmimeinfo files. (``MIME-File`` and 593 | ``Shared-MIME-File`` config options.) 594 | 595 | * Install copyright files. (``Copyright-File`` config option.) 596 | 597 | * Apply patches to upstream sources. (``Stdeb-Patch-File`` config 598 | option.) 599 | 600 | * Pass environment variables to setup.py script. (``Setup-Env-Vars`` 601 | config option.) 602 | 603 | Customizing the produced Debian source package (config options) 604 | --------------------------------------------------------------- 605 | 606 | stdeb will attempt to provide reasonable defaults, but these are only 607 | guesses. 608 | 609 | There are two ways to customize the Debian source package produced by 610 | stdeb. First, you may provide options to the distutils 611 | commands. Second, you may provide an ``stdeb.cfg`` file. 612 | 613 | stdeb distutils command options 614 | ``````````````````````````````` 615 | 616 | The sdist_dsc command takes command-line options to the distutils 617 | command. For example:: 618 | 619 | python setup.py --command-packages=stdeb.command sdist_dsc --debian-version 0MyName1 620 | 621 | This creates a Debian package with the Debian version set to 622 | "0MyName1". 623 | 624 | These options can also be set via distutils configuration 625 | files. (These are the ``setup.cfg`` file alongside ``setup.py`` and 626 | the ~/.pydistutils.cfg file.) In that case, put the arguments in the 627 | ``[sdist_dsc]`` section. For example, a project's ``~/.setup.cfg`` 628 | file might have this:: 629 | 630 | [sdist_dsc] 631 | debian-version: 0MyName1 632 | 633 | To pass these commands to sdist_dsc when calling bdist_deb, do this:: 634 | 635 | python setup.py sdist_dsc --debian-version 0MyName1 bdist_deb 636 | 637 | ====================================== ========================================= 638 | Command line option Effect 639 | ====================================== ========================================= 640 | --with-python2 build Python 2 package (default=True) 641 | --with-python3 build Python 3 package (default=False) 642 | --no-python2-scripts disable installation of Python 2 scripts (default=False) 643 | --no-python3-scripts disable installation of Python 3 scripts (default=False) 644 | --force-x-python3-version Override default minimum python3:any 645 | dependency with value from x-python3- 646 | version 647 | --allow-virtualenv-install-location Allow installing into 648 | /some/random/virtualenv-path 649 | --with-dh-virtualenv Build the package using dh_virtualenv, so all dependencies 650 | are embedded into the packages. 651 | --with-dh-systemd Add the systemd addon that will add dh_systemd_enable and 652 | dh_systemd_start helpers at the correct time during build. 653 | --sign-results Use gpg to sign the resulting .dsc and 654 | .changes file 655 | --sign-key Specify signing key 656 | --dist-dir (-d) directory to put final built 657 | distributions in (default='deb_dist') 658 | --patch-already-applied (-a) patch was already applied (used when 659 | py2dsc calls sdist_dsc) 660 | --default-distribution deprecated (see --suite) 661 | --compat debian compatibility level (default=9) 662 | --suite (-z) distribution name to use if not 663 | specified in .cfg (default='unstable') 664 | --default-maintainer deprecated (see --maintainer) 665 | --maintainer (-m) maintainer name and email to use if not 666 | specified in .cfg (default from 667 | setup.py) 668 | --extra-cfg-file (-x) additional .cfg file (in addition to 669 | stdeb.cfg if present) 670 | --patch-file (-p) patch file applied before setup.py 671 | called (incompatible with file 672 | specified in .cfg) 673 | --patch-level (-l) patch file applied before setup.py 674 | called (incompatible with file 675 | specified in .cfg) 676 | --patch-posix (-q) apply the patch with --posix mode 677 | --remove-expanded-source-dir (-r) remove the expanded source directory 678 | --ignore-install-requires (-i) ignore the requirements from 679 | requires.txt in the egg-info directory 680 | --ignore-source-changes ignore all changes on source when 681 | building source package (add -i.* 682 | option to dpkg-source) 683 | --no-backwards-compatibility This option has no effect, is here for 684 | backwards compatibility, and may be 685 | removed someday. 686 | --guess-conflicts-provides-replaces If True, attempt to guess 687 | Conflicts/Provides/Replaces in 688 | debian/control based on apt-cache 689 | output. (Default=False). 690 | --use-premade-distfile (-P) use .zip or .tar.gz file already made 691 | by sdist command 692 | --source debian/control Source: (Default: 693 | ) 694 | --package debian/control Package: (Default: 695 | python-) 696 | --suite suite (e.g. stable, lucid) in changelog 697 | (Default: unstable) 698 | --maintainer debian/control Maintainer: (Default: 699 | ) 700 | --debian-version debian version (Default: 1) 701 | --section debian/control Section: (Default: 702 | python) 703 | --epoch version epoch 704 | --forced-upstream-version forced upstream version 705 | --upstream-version-prefix upstream version prefix 706 | --upstream-version-suffix upstream version suffix 707 | --uploaders uploaders 708 | --copyright-file copyright file 709 | --build-depends debian/control Build-Depends: 710 | --build-conflicts debian/control Build-Conflicts: 711 | --stdeb-patch-file file containing patches for stdeb to 712 | apply 713 | --stdeb-patch-level patch level provided to patch command 714 | --depends debian/control Depends: 715 | --suggests debian/control Suggests: 716 | --recommends debian/control Recommends: 717 | --xs-python-version debian/control XS-Python-Version: 718 | --x-python3-version debian/control X-Python3-Version: 719 | --dpkg-shlibdeps-params parameters passed to dpkg-shlibdeps 720 | --conflicts debian/control Conflicts: 721 | --provides debian/control Provides: 722 | --replaces debian/control Replaces: 723 | --mime-desktop-files MIME desktop files 724 | --mime-file MIME file 725 | --shared-mime-file shared MIME file 726 | --setup-env-vars environment variables passed to 727 | setup.py 728 | --udev-rules file with rules to install to udev 729 | 730 | ====================================== ========================================= 731 | 732 | 733 | You may also pass any arguments described below for the stdeb.cfg file 734 | via distutils options. Passing the arguments this way (either on the 735 | command line, or in the ``[sdist_dsc]`` section of a distutils .cfg 736 | file) will take precedence. The option name should be given in lower 737 | case. 738 | 739 | stdeb.cfg configuration file 740 | ```````````````````````````` 741 | 742 | You may write config files of the format understood by `ConfigParser 743 | `_. When building 744 | each package, stdeb looks for the existence of a ``stdeb.cfg`` in the 745 | directory with ``setup.py``. You may specify an additional config file 746 | with the command-line option --extra-cfg-file. The section should 747 | should either be [DEFAULT] or [package_name], which package_name is 748 | specified as the name argument to the setup() command. An example 749 | stdeb.cfg file is:: 750 | 751 | [DEFAULT] 752 | Depends: python-numpy 753 | XS-Python-Version: >= 2.6 754 | 755 | All available options: 756 | 757 | ====================================== ========================================= 758 | Config file option Effect 759 | ====================================== ========================================= 760 | Source debian/control Source: (Default: 761 | ) 762 | Package debian/control Package: (Default: 763 | python-) 764 | Package3 debian/control Package: for python3 765 | (Default: 766 | python3-) 767 | Suite suite (e.g. stable, lucid) in changelog 768 | (Default: unstable) 769 | Suite3 suite (e.g. stable, lucid) for python3 770 | (Default: uses value of Suite option) 771 | Maintainer debian/control Maintainer: (Default: 772 | ) 773 | Debian-Version debian version (Default: 1) 774 | Section debian/control Section: (Default: 775 | python) 776 | Epoch version epoch 777 | Forced-Upstream-Version forced upstream version 778 | Upstream-Version-Prefix upstream version prefix 779 | Upstream-Version-Suffix upstream version suffix 780 | Uploaders uploaders 781 | Copyright-File copyright file 782 | Build-Depends debian/control Build-Depends: 783 | Build-Conflicts debian/control Build-Conflicts: 784 | Stdeb-Patch-File file containing patches for stdeb to 785 | apply 786 | Stdeb-Patch-Level patch level provided to patch command 787 | Depends debian/control Depends: 788 | Depends3 debian/control Depends: for python3 789 | Suggests debian/control Suggests: 790 | Suggests3 debian/control Suggests: for python3 791 | Recommends debian/control Recommends: 792 | Recommends3 debian/control Recommends: for python3 793 | XS-Python-Version debian/control XS-Python-Version: 794 | X-Python3-Version debian/control X-Python3-Version: 795 | Dpkg-Shlibdeps-Params parameters passed to dpkg-shlibdeps 796 | Conflicts debian/control Conflicts: 797 | Conflicts3 debian/control Conflicts: for python3 798 | Breaks debian/control Breaks: 799 | Breaks3 debian/control Breaks: for python3 800 | Provides debian/control Provides: 801 | Provides3 debian/control Provides: for python3 802 | Replaces debian/control Replaces: 803 | Replaces3 debian/control Replaces: for python3 804 | MIME-Desktop-Files MIME desktop files 805 | MIME-File MIME file 806 | Shared-MIME-File shared MIME file 807 | Setup-Env-Vars environment variables passed to 808 | setup.py 809 | Udev-Rules file with rules to install to udev 810 | Python2-Depends-Name override Python 2 Debian package name in 811 | ${python:Depends} 812 | ====================================== ========================================= 813 | 814 | The option names in stdeb.cfg files are not case sensitive. 815 | 816 | Reproducible builds 817 | ------------------- 818 | 819 | By default stdeb uses the current time for the the timestamp in the generated 820 | changelog file. This results in a non-reproducible build since every invocation 821 | generates a different changelog / ``.deb``. 822 | The environment variable ``SOURCE_DATE_EPOCH`` can be set to a fixed timestamp 823 | (e.g. when the version was tagged or of the last commit was made) which will be 824 | used in the changelog instead. This will ensure that the produced ``.deb`` is 825 | reproducible on repeated invocations. 826 | 827 | For more information about reproducible builds and this specific environment 828 | variable please see https://reproducible-builds.org/docs/source-date-epoch/ 829 | 830 | Prerequisites 831 | ------------- 832 | 833 | * Python 2.7 or Python 3.x 834 | * Standard Debian utilities such as ``date``, ``dpkg-source`` and 835 | Debhelper 7 (use stdeb 0.3.x if you need to support older 836 | distributions without dh7) 837 | * If your setup.py uses the setuptools features ``setup_requires`` or 838 | ``install_requires``, you must run ``apt-file update`` prior to 839 | running any stdeb command. 840 | 841 | TODO 842 | ---- 843 | 844 | * Make output meet `Debian Python Policy`_ specifications or the `new 845 | python policy`_. This will include several things, among which are: 846 | 847 | - the ability to make custom changelogs 848 | - the ability to include project-supplied documentation as a -doc package 849 | - include license information in debian/copyright 850 | - the ability to include project-supplied examples, tests, and data 851 | as a separate package 852 | - much more not listed 853 | 854 | * Create (better) documentation 855 | 856 | * Log output using standard distutils mechanisms 857 | 858 | * Refactor the source code to have a simpler, more sane design 859 | 860 | .. _debian python policy: http://www.debian.org/doc/packaging-manuals/python-policy/ 861 | .. _new python policy: http://wiki.debian.org/DebianPython/NewPolicy 862 | 863 | Call for volunteers 864 | ------------------- 865 | 866 | I don't have a lot of time for this. This project stands a very real 867 | chance of being only a shadow of its potential self unless people step 868 | up and contribute. There are numerous ways in which people could 869 | help. In particular, I'd be interested in finding a co-maintainer or 870 | maintainer if the project generates any interest. Secondarily, I would 871 | appreciate advice from Debian developers or Ubuntu MOTUs about the 872 | arcane details of Python packaging. 873 | 874 | Mailing list 875 | ------------ 876 | 877 | Please address all questions to the distutils-SIG_ 878 | 879 | .. _distutils-SIG: http://mail.python.org/mailman/listinfo/distutils-sig 880 | 881 | License 882 | ------- 883 | 884 | MIT-style license. Copyright (c) 2006-2015 stdeb authors. 885 | 886 | See the LICENSE.txt file provided with the source distribution for 887 | full details. 888 | 889 | Authors 890 | ------- 891 | 892 | * Andrew Straw 893 | * Pedro Algarvio, aka, s0undt3ch 894 | * Gerry Reno (initial bdist_deb implementation) 895 | 896 | Additional Credits 897 | ------------------ 898 | 899 | * Zooko O'Whielacronx for the autofind-depends patch. 900 | * Brett (last name unknown) for the --ignore-install-requires patch. 901 | * Ximin Luo for a bug fix. 902 | * Alexander D. Sedov for bug fixes and suggestions. 903 | * Michele Mattioni for bug fix. 904 | * Alexander V. Nikolaev for the debhelper buildsystem specification. 905 | * Roland Sommer for the description field bugfix. 906 | * Barry Warsaw for suggesting the debianize command. 907 | * Asheesh Laroia for updating the PyPI URL. 908 | * Piotr Ożarowski for implementing dh_python2 support. 909 | * Nikita Burtsev for unicode tests and fixes 910 | * Mikołaj Siedlarek for a bugfix 911 | * Dirk Thomas for --force-x-python3-version and X-Python3-Version 912 | * Louis for Recommends3, Suggests3, Provides3 and Replaces3 support 913 | * kzwin for interop with virtualenv 914 | * GitHub_ for hosting services. 915 | * WebFaction_ (aka `python-hosting`_) for previous hosting services. 916 | * TravisCI_ for previous continuous integration support 917 | 918 | .. _GitHub: http://github.com/ 919 | .. _WebFaction: http://webfaction.com/ 920 | .. _python-hosting: http://python-hosting.com/ 921 | .. _TravisCI: http://travis-ci.org/ 922 | 923 | 924 | .. image:: https://badges.gitter.im/Join%20Chat.svg 925 | :alt: Join the chat at https://gitter.im/astraw/stdeb 926 | :target: https://gitter.im/astraw/stdeb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 927 | -------------------------------------------------------------------------------- /RELEASE_NOTES.txt: -------------------------------------------------------------------------------- 1 | See the News section in the README for more details. 2 | 3 | Release 0.10.0 4 | ============== 5 | 6 | The default Debian compat version has been changed from 7 to 9 as well as new 7 | options and features. 8 | 9 | Release 0.9.1 10 | ============= 11 | 12 | A few improvements for Python 3 as well as new options. 13 | 14 | Release 0.9.0 15 | ============= 16 | 17 | The following backwards incompatible changes were made: 18 | 19 | * The deprecated call dh_desktop has been removed. 20 | 21 | Release 0.8.5 22 | ============= 23 | 24 | Building package in a virtual environment does not install in a 25 | virtual environment (restores 0.8.3 behavior). 26 | 27 | Release 0.8.4 28 | ============= 29 | 30 | Building package in a virtual environment installs in a virtual 31 | environment. 32 | 33 | Release 0.8.3 34 | ============= 35 | 36 | Fixes various Unicode handling bugs. 37 | 38 | Release 0.8.2 39 | ============= 40 | 41 | Fixes a bug. 42 | 43 | Release 0.8.1 44 | ============= 45 | 46 | Fixes documentation bugs. 47 | 48 | Release 0.8.0 49 | ============= 50 | 51 | Removed "--force-buildsystem" option. 52 | 53 | Release 0.7.1 54 | ============= 55 | 56 | Fixes several bugs. 57 | 58 | Release 0.7.0 59 | ============= 60 | 61 | stdeb uses dh_python2 instead of dh_pysupport (thanks to Piotr 62 | Ożarowski). 63 | 64 | A long time passed between releases, so there are probably backwards 65 | incompatible changes that have crept in. 66 | 67 | Release 0.6.0 68 | ============= 69 | 70 | A new ``debianize`` command was added. This command builds a 71 | ``debian/`` directory alongside your setup.py file. 72 | 73 | No backwards incompatible changes were made with this release. 74 | 75 | Release 0.5.1 76 | ============= 77 | 78 | This is a bugfix release. This fixes a bug that caused the 79 | debian/control Description: field to be wrong. 80 | 81 | No backwards incompatible changes were made with this release. 82 | 83 | Release 0.5.0 84 | ============= 85 | 86 | The following backwards incompatible changes were made: 87 | 88 | * The --process-dependencies option to py2dsc was dropped, because 89 | this functionality went beyond the scope of the py2dsc script. The 90 | functionality could be added to a new script if desired. 91 | 92 | * The ability to pass command options to sdist_dsc by specifying them 93 | to bdist_deb was removed. In other words, if you did this in the 94 | past:: 95 | 96 | python setup.py bdist_deb --no-backwards-compatibility 97 | 98 | You must now do this:: 99 | 100 | python setup.py sdist_dsc --no-backwards-compatibility bdist_deb 101 | 102 | The deprecations were made: 103 | 104 | * The stdeb.cfg file location has been moved from the .egg-info 105 | directory into the directory holding setup.py. The old location will 106 | work, but is deprecated and will be removed sometime in the future. 107 | 108 | * Some options to the sdist_dsc command have now changed. 109 | (--default-maintiner should now be --maintainer, 110 | --default-distribution should now be --suite), although the old way 111 | will still work, as it is only deprecated for now. 112 | 113 | Release 0.4.3 114 | ============= 115 | 116 | This is a bugfix release. The most significant change is that by 117 | default the "DH_OPTIONS=--buildsystem=python_distutils" environment 118 | variable is passed to debhelper. This causes debhelper to ignore 119 | Makefiles and forces use of "python setup.py install". 120 | 121 | No backwards incompatible changes were made with this release. 122 | 123 | Release 0.4.2 124 | ============= 125 | 126 | This is a bugfix release fixing a couple issues. The most significant 127 | fix is that if a package specified "XS-Python-Version" in stdeb.cfg, 128 | the binary package wouldn't install the Python packages. 129 | 130 | No backwards incompatible changes were made with this release. 131 | 132 | Release 0.4.1 133 | ============= 134 | 135 | This release maintains backward compatibility with old versions of 136 | stdeb and debhelper, but stdeb now allows packages that don't carry 137 | the cruft of backward compatibility. To disable all backwards 138 | compatibility, specify the --no-backwards-compatibility flag. This 139 | will set --pycentral-backwards-compatibility=False and 140 | --workaround-548392=False. You may set these flags individually if 141 | desired. 142 | 143 | By "maintains backward compatibility with stdeb", I mean that newer 144 | packages made with newer stdeb (0.4.1 and up) will properly upgrade 145 | from older package made with older versions of stdeb (less than 146 | 0.4.0). The problem, in particular, is the migration from 147 | python-central. See Debian bug #479852 for more information. 148 | -------------------------------------------------------------------------------- /build.earth: -------------------------------------------------------------------------------- 1 | VERSION 0.8 2 | 3 | 4 | SRC: 5 | FUNCTION 6 | COPY --dir scripts stdeb test_data \ 7 | test*.sh *.txt *.py *.cfg *.toml *.rst \ 8 | MANIFEST.in \ 9 | /src/stdeb 10 | WORKDIR /src/stdeb 11 | 12 | BUILD: 13 | FUNCTION 14 | ENV DEBIAN_FRONTEND=noninteractive 15 | RUN apt-get update; apt-get install -y \ 16 | # Build deps \ 17 | debhelper dh-python python3-all python3-pip \ 18 | # Install deps \ 19 | python3-requests apt-file \ 20 | # Test deps \ 21 | libpq-dev python3-all-dev 22 | 23 | DO +SRC 24 | RUN python3 setup.py --command-packages=stdeb.command bdist_deb 25 | RUN for f in deb_dist/*.deb; do echo; echo $f; dpkg --contents $f; done 26 | 27 | INSTALL: 28 | FUNCTION 29 | # Install stdeb 30 | RUN dpkg -i deb_dist/*.deb 31 | 32 | lint: 33 | FROM docker.io/library/python:3.10-alpine 34 | DO +SRC 35 | RUN python3 -m pip install -r requirements.txt 36 | RUN ruff format --check || true 37 | RUN ruff check || true 38 | 39 | build: 40 | ARG OS=debian:bookworm 41 | FROM $OS 42 | DO +BUILD 43 | 44 | test: 45 | FROM +build 46 | DO +INSTALL 47 | RUN env PYEXE=/usr/bin/python3 bash -x ./test.sh 48 | 49 | test-pypi-install: 50 | FROM +build 51 | DO +INSTALL 52 | RUN bash -x ./test-pypi-install.sh 53 | 54 | test-2and3: 55 | FROM +build 56 | DO +INSTALL 57 | # Not all platforms provide python2 58 | RUN apt-get update; apt-get install -y python-all-dev || true 59 | RUN bash -x ./test2and3.sh 60 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ruff 2 | 3 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 120 2 | -------------------------------------------------------------------------------- /scripts/py2dsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import stdeb.cli_runner 4 | import sys 5 | 6 | USAGE = """\ 7 | usage: py2dsc [options] distfile 8 | or: py2dsc --help 9 | 10 | where distfile is a .zip or .tar.gz file built with the sdist command 11 | of distutils. 12 | """ 13 | 14 | 15 | def main(): 16 | sys.exit(stdeb.cli_runner.runit(cmd='sdist_dsc', usage=USAGE)) 17 | 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /scripts/py2dsc-deb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import stdeb.cli_runner 4 | import sys 5 | 6 | USAGE = """\ 7 | usage: py2dsc-deb [options] distfile 8 | or: py2dsc-deb --help 9 | 10 | where distfile is a .zip or .tar.gz file built with the sdist command 11 | of distutils. 12 | """ 13 | 14 | 15 | def main(): 16 | sys.exit(stdeb.cli_runner.runit(cmd='bdist_deb', usage=USAGE)) 17 | 18 | 19 | if __name__ == '__main__': 20 | main() 21 | -------------------------------------------------------------------------------- /scripts/pypi-download: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from optparse import OptionParser 4 | from stdeb.downloader import myprint, get_source_tarball 5 | 6 | 7 | def main(): 8 | usage = '%prog PACKAGE_NAME [options]' 9 | parser = OptionParser(usage) 10 | parser.add_option('--verbose', type='int', 11 | help='verbosity level', 12 | default=0) 13 | parser.add_option('--release', type='str', 14 | help='specify a particular release', 15 | default=None) 16 | parser.add_option('--allow-unsafe-download', action='store_true', 17 | default=False, 18 | help='allow unsafe downloads') 19 | (options, args) = parser.parse_args() 20 | if len(args) != 1: 21 | myprint('need exactly one PACKAGE_NAME', file=sys.stderr) 22 | parser.print_help() 23 | sys.exit(1) 24 | 25 | package_name = args[0] 26 | 27 | tarball_fname = get_source_tarball( 28 | package_name, verbose=options.verbose, 29 | release=options.release, 30 | allow_unsafe_download=options.allow_unsafe_download) 31 | myprint('OK: %s' % tarball_fname) 32 | 33 | 34 | if __name__ == '__main__': 35 | main() 36 | -------------------------------------------------------------------------------- /scripts/pypi-install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import shutil 4 | import sys 5 | import subprocess 6 | import argparse 7 | from stdeb.downloader import myprint, get_source_tarball 8 | import tempfile 9 | 10 | 11 | def main(): 12 | parser = argparse.ArgumentParser( 13 | usage='%(prog)s PACKAGE_NAME [options] [py2dsc-deb options]') 14 | parser.add_argument( 15 | '--verbose', type=int, 16 | help='verbosity level', 17 | default=0) 18 | parser.add_argument( 19 | '--release', type=str, 20 | help='specify a particular release', 21 | default=None) 22 | parser.add_argument( 23 | '--keep', action='store_true', 24 | default=False, 25 | help='do not remove temporary files') 26 | parser.add_argument( 27 | '--allow-unsafe-download', action='store_true', 28 | default=False, 29 | help='allow unsafe downloads') 30 | (options, args) = parser.parse_known_args() 31 | 32 | if os.geteuid() != 0: 33 | myprint( 34 | '%s must be run as root' % os.path.basename(sys.argv[0]), 35 | file=sys.stderr) 36 | sys.exit(1) 37 | 38 | if len(args) < 1: 39 | myprint('need exactly one PACKAGE_NAME', file=sys.stderr) 40 | parser.print_help() 41 | sys.exit(1) 42 | 43 | package_name = args[0] 44 | py2dsc_args = args[1:] 45 | if package_name.startswith('-'): 46 | myprint('PACKAGE_NAME must be first argument', file=sys.stderr) 47 | parser.print_help() 48 | sys.exit(1) 49 | 50 | orig_dir = os.path.abspath(os.curdir) 51 | tmpdir = os.path.abspath(tempfile.mkdtemp()) 52 | try: 53 | if options.verbose >= 2: 54 | myprint('downloading to %s' % tmpdir) 55 | os.chdir(tmpdir) 56 | tarball_fname = get_source_tarball( 57 | package_name, verbose=options.verbose, 58 | release=options.release, 59 | allow_unsafe_download=options.allow_unsafe_download) 60 | 61 | cmd = ' '.join(['py2dsc-deb'] + py2dsc_args + [tarball_fname]) 62 | if options.verbose >= 2: 63 | myprint('executing: %s' % cmd) 64 | subprocess.check_call(cmd, shell=True) 65 | 66 | os.chdir('deb_dist') 67 | cmd = 'dpkg -i *.deb' 68 | if options.verbose >= 2: 69 | myprint('executing: %s' % cmd) 70 | subprocess.check_call(cmd, shell=True) 71 | 72 | finally: 73 | os.chdir(orig_dir) 74 | if not options.keep: 75 | shutil.rmtree(tmpdir) 76 | 77 | 78 | if __name__ == '__main__': 79 | main() 80 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | import codecs 3 | 4 | with codecs.open('README.rst', encoding='utf-8') as file: 5 | long_description = file.read() 6 | 7 | 8 | setup( 9 | name='stdeb', 10 | # Keep version in sync with stdeb/__init__.py and install section 11 | # of README.rst. 12 | version='0.10.1', 13 | author='Andrew Straw', 14 | author_email='strawman@astraw.com', 15 | description='Python to Debian source package conversion utility', 16 | long_description=long_description, 17 | license='MIT', 18 | url='http://github.com/astraw/stdeb', 19 | packages=['stdeb', 'stdeb.command'], 20 | scripts=[ 21 | 'scripts/py2dsc', 22 | 'scripts/py2dsc-deb', 23 | 'scripts/pypi-download', 24 | 'scripts/pypi-install', 25 | ], 26 | classifiers=[ 27 | 'Development Status :: 5 - Production/Stable', 28 | 'Intended Audience :: Developers', 29 | 'Intended Audience :: System Administrators', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Operating System :: POSIX :: Linux', 32 | 'Programming Language :: Python :: 2.7', 33 | 'Programming Language :: Python :: 3', 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /stdeb.cfg: -------------------------------------------------------------------------------- 1 | [stdeb] 2 | Depends: apt-file, dpkg-dev, python-pkg-resources, python-requests 3 | Depends3: apt-file, dpkg-dev, python3-pkg-resources, python3-requests 4 | -------------------------------------------------------------------------------- /stdeb/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | __version__ = '0.10.1' # keep in sync with ../setup.py 3 | 4 | log = logging.getLogger('stdeb') 5 | log.setLevel(logging.INFO) 6 | handler = logging.StreamHandler() 7 | handler.setLevel(logging.INFO) 8 | formatter = logging.Formatter('%(message)s') 9 | handler.setFormatter(formatter) 10 | log.addHandler(handler) 11 | -------------------------------------------------------------------------------- /stdeb/cli_runner.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | import shutil 4 | import subprocess 5 | import sys 6 | from configparser import ConfigParser # noqa: F401 7 | from distutils.util import strtobool 8 | from distutils.fancy_getopt import FancyGetopt, translate_longopt 9 | from stdeb.util import stdeb_cmdline_opts, stdeb_cmd_bool_opts 10 | from stdeb.util import expand_sdist_file, apply_patch 11 | from stdeb import log 12 | 13 | from pkg_resources import Requirement, Distribution # noqa: F401 14 | 15 | 16 | class OptObj: 17 | pass 18 | 19 | 20 | def runit(cmd, usage): 21 | if cmd not in ['sdist_dsc', 'bdist_deb']: 22 | raise ValueError('unknown command %r' % cmd) 23 | # process command-line options 24 | bool_opts = list(map(translate_longopt, stdeb_cmd_bool_opts)) 25 | parser = FancyGetopt(stdeb_cmdline_opts+[ 26 | ('help', 'h', "show detailed help message"), 27 | ]) 28 | optobj = OptObj() 29 | args = parser.getopt(object=optobj) 30 | for option in optobj.__dict__: 31 | value = getattr(optobj, option) 32 | is_string = type(value) == str 33 | if option in bool_opts and is_string: 34 | setattr(optobj, option, strtobool(value)) 35 | 36 | if hasattr(optobj, 'help'): 37 | print(usage) 38 | parser.set_option_table(stdeb_cmdline_opts) 39 | parser.print_help("Options:") 40 | return 0 41 | 42 | if len(args) != 1: 43 | log.error('not given single argument (distfile), args=%r', args) 44 | print(usage) 45 | return 1 46 | 47 | sdist_file = args[0] 48 | 49 | final_dist_dir = optobj.__dict__.get('dist_dir', 'deb_dist') 50 | tmp_dist_dir = os.path.join(final_dist_dir, 'tmp_py2dsc') 51 | if os.path.exists(tmp_dist_dir): 52 | shutil.rmtree(tmp_dist_dir) 53 | os.makedirs(tmp_dist_dir) 54 | 55 | if not os.path.isfile(sdist_file): 56 | log.error("Package %s not found." % sdist_file) 57 | sys.exit(1) 58 | 59 | patch_file = optobj.__dict__.get('patch_file', None) 60 | patch_level = int(optobj.__dict__.get('patch_level', 0)) 61 | patch_posix = int(optobj.__dict__.get('patch_posix', 0)) 62 | 63 | expand_dir = os.path.join(tmp_dist_dir, 'stdeb_tmp') 64 | if os.path.exists(expand_dir): 65 | shutil.rmtree(expand_dir) 66 | if not os.path.exists(tmp_dist_dir): 67 | os.mkdir(tmp_dist_dir) 68 | os.mkdir(expand_dir) 69 | 70 | expand_sdist_file(os.path.abspath(sdist_file), cwd=expand_dir) 71 | 72 | # now the sdist package is expanded in expand_dir 73 | expanded_root_files = os.listdir(expand_dir) 74 | assert len(expanded_root_files) == 1 75 | repackaged_dirname = expanded_root_files[0] 76 | fullpath_repackaged_dirname = os.path.join( 77 | tmp_dist_dir, repackaged_dirname) 78 | base_dir = os.path.join(expand_dir, expanded_root_files[0]) 79 | if os.path.exists(fullpath_repackaged_dirname): 80 | # prevent weird build errors if this dir exists 81 | shutil.rmtree(fullpath_repackaged_dirname) 82 | os.renames(base_dir, fullpath_repackaged_dirname) 83 | del base_dir # no longer useful 84 | 85 | ############################################## 86 | if patch_file is not None: 87 | log.info('py2dsc applying patch %s', patch_file) 88 | apply_patch(patch_file, 89 | posix=patch_posix, 90 | level=patch_level, 91 | cwd=fullpath_repackaged_dirname) 92 | patch_already_applied = 1 93 | else: 94 | patch_already_applied = 0 95 | ############################################## 96 | 97 | abs_dist_dir = os.path.abspath(final_dist_dir) 98 | 99 | extra_args = [] 100 | for long in parser.long_opts: 101 | if long in ['dist-dir=', 'patch-file=']: 102 | continue # dealt with by this invocation 103 | attr = parser.get_attr_name(long).rstrip('=') 104 | if hasattr(optobj, attr): 105 | val = getattr(optobj, attr) 106 | if attr == 'extra_cfg_file': 107 | val = os.path.abspath(val) 108 | if long in bool_opts or long.replace('-', '_') in bool_opts: 109 | extra_args.append('--%s' % long) 110 | else: 111 | extra_args.append('--'+long+str(val)) 112 | 113 | if patch_already_applied == 1: 114 | extra_args.append('--patch-already-applied') 115 | 116 | if cmd == 'bdist_deb': 117 | extra_args.append('bdist_deb') 118 | 119 | args = [sys.executable, 'setup.py', '--command-packages', 'stdeb.command', 120 | 'sdist_dsc', '--dist-dir=%s' % abs_dist_dir, 121 | '--use-premade-distfile=%s' % os.path.abspath(sdist_file) 122 | ] + extra_args 123 | 124 | log.info('-='*35 + '-') 125 | # print >> sys.stderr, '-='*20 126 | # print >> sys.stderr, "Note that the .cfg file(s), if present, have not "\ 127 | # "been read at this stage. If options are necessary, pass them "\ 128 | # "from the command line" 129 | log.info("running the following command in directory: %s\n%s", 130 | fullpath_repackaged_dirname, ' '.join(args)) 131 | log.info('-='*35 + '-') 132 | 133 | try: 134 | returncode = subprocess.call( 135 | args, cwd=fullpath_repackaged_dirname, 136 | ) 137 | except Exception: 138 | log.error('ERROR running: %s', ' '.join(args)) 139 | log.error('ERROR in %s', fullpath_repackaged_dirname) 140 | raise 141 | 142 | if returncode: 143 | log.error('ERROR running: %s', ' '.join(args)) 144 | log.error('ERROR in %s', fullpath_repackaged_dirname) 145 | # log.error(' stderr: %s'res.stderr.read()) 146 | # print >> sys.stderr, 'ERROR running: %s'%(' '.join(args),) 147 | # print >> sys.stderr, res.stderr.read() 148 | return returncode 149 | # raise RuntimeError('returncode %d'%returncode) 150 | # result = res.stdout.read().strip() 151 | 152 | shutil.rmtree(tmp_dist_dir) 153 | return returncode 154 | -------------------------------------------------------------------------------- /stdeb/command/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['sdist_dsc', 'bdist_deb', 'install_deb', 'debianize'] 2 | -------------------------------------------------------------------------------- /stdeb/command/bdist_deb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import stdeb.util as util 3 | 4 | from distutils.core import Command 5 | 6 | __all__ = ['bdist_deb'] 7 | 8 | 9 | class bdist_deb(Command): 10 | description = 'distutils command to create debian binary package' 11 | 12 | user_options = [ 13 | ('sign-results', None, 14 | 'Use gpg to sign the resulting .dsc and .changes file'), 15 | ('ignore-source-changes', None, 16 | 'Ignore all changes on source when building source package ' 17 | '(add -i.* option to dpkg-source'), 18 | ] 19 | boolean_options = [ 20 | 'sign-results', 21 | 'ignore-source-changes', 22 | ] 23 | 24 | def initialize_options(self): 25 | self.sign_results = False 26 | self.ignore_source_changes = False 27 | 28 | def finalize_options(self): 29 | self.sign_results = bool(self.sign_results) 30 | self.ignore_source_changes = bool(self.ignore_source_changes) 31 | 32 | def run(self): 33 | # generate .dsc source pkg 34 | self.run_command('sdist_dsc') 35 | 36 | # get relevant options passed to sdist_dsc 37 | sdist_dsc = self.get_finalized_command('sdist_dsc') 38 | dsc_tree = sdist_dsc.dist_dir 39 | 40 | # execute system command and read output 41 | # (execute and read output of find cmd) 42 | target_dirs = [] 43 | for entry in os.listdir(dsc_tree): 44 | fulldir = os.path.join(dsc_tree, entry) 45 | if os.path.isdir(fulldir): 46 | if entry == 'tmp_py2dsc': 47 | continue 48 | target_dirs.append(fulldir) 49 | 50 | if len(target_dirs) > 1: 51 | raise ValueError('More than one directory in deb_dist. ' 52 | 'Unsure which is source directory. All: %r' % ( 53 | target_dirs,)) 54 | 55 | if len(target_dirs) == 0: 56 | raise ValueError('could not find debian source directory') 57 | 58 | # define system command to execute (gen .deb binary pkg) 59 | syscmd = ['dpkg-buildpackage', '-rfakeroot', '-b'] 60 | 61 | if not self.sign_results: 62 | syscmd.append('-uc') 63 | 64 | if self.ignore_source_changes: 65 | syscmd.append('-i.*') 66 | 67 | util.process_command(syscmd, cwd=target_dirs[0]) 68 | -------------------------------------------------------------------------------- /stdeb/command/common.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from stdeb import log 5 | from distutils.core import Command 6 | from distutils.errors import DistutilsModuleError 7 | 8 | from stdeb.util import DebianInfo, DH_DEFAULT_VERS, stdeb_cfg_options 9 | 10 | 11 | class common_debian_package_command(Command): 12 | def initialize_options(self): 13 | self.patch_already_applied = 0 14 | self.remove_expanded_source_dir = 0 15 | self.patch_posix = 0 16 | self.dist_dir = None 17 | self.extra_cfg_file = None 18 | self.patch_file = None 19 | self.patch_level = None 20 | self.ignore_install_requires = None 21 | self.debian_version = None 22 | self.no_backwards_compatibility = None 23 | self.guess_conflicts_provides_replaces = None 24 | if sys.version_info[0] == 2: 25 | self.with_python2 = 'True' 26 | self.with_python3 = 'False' 27 | else: 28 | assert sys.version_info[0] == 3 29 | self.with_python2 = 'False' 30 | self.with_python3 = 'True' 31 | self.no_python2_scripts = 'False' 32 | self.no_python3_scripts = 'False' 33 | self.force_x_python3_version = False 34 | self.allow_virtualenv_install_location = False 35 | self.with_dh_virtualenv = False 36 | self.with_dh_systemd = False 37 | self.sign_results = False 38 | self.sign_key = None 39 | self.ignore_source_changes = False 40 | self.compat = DH_DEFAULT_VERS 41 | 42 | # deprecated options 43 | self.default_distribution = None 44 | self.default_maintainer = None 45 | 46 | # make distutils happy by filling in default values 47 | for longopt, shortopt, description in stdeb_cfg_options: 48 | assert longopt.endswith('=') 49 | name = longopt[:-1] 50 | name = name.replace('-', '_') 51 | setattr(self, name, None) 52 | 53 | def finalize_options(self): 54 | def str_to_bool(mystr): 55 | if mystr.lower() == 'false': 56 | return False 57 | elif mystr.lower() == 'true': 58 | return True 59 | else: 60 | raise ValueError( 61 | 'bool string "%s" is not "true" or "false"' % mystr) 62 | if self.dist_dir is None: 63 | self.dist_dir = 'deb_dist' 64 | if self.patch_level is not None: 65 | self.patch_level = int(self.patch_level) 66 | 67 | if self.guess_conflicts_provides_replaces is None: 68 | # the default 69 | self.guess_conflicts_provides_replaces = False 70 | else: 71 | self.guess_conflicts_provides_replaces = str_to_bool( 72 | self.guess_conflicts_provides_replaces) 73 | 74 | self.with_python2 = str_to_bool(self.with_python2) 75 | self.with_python3 = str_to_bool(self.with_python3) 76 | self.no_python2_scripts = str_to_bool(self.no_python2_scripts) 77 | self.no_python3_scripts = str_to_bool(self.no_python3_scripts) 78 | if self.maintainer is not None: 79 | # Get the locale specifying the encoding in sys.argv 80 | import codecs 81 | import locale 82 | fs_enc = codecs.lookup(locale.getpreferredencoding()).name 83 | if hasattr(os, 'fsencode'): # this exists only in Python 3 84 | m = os.fsencode(self.maintainer) # convert to orig raw bytes 85 | 86 | # Now, convert these raw bytes into unicode. 87 | m = m.decode(fs_enc) # Set your locale if you get errors here 88 | 89 | self.maintainer = m 90 | else: 91 | # Python 2 92 | if hasattr(self.maintainer, 'decode'): 93 | self.maintainer = self.maintainer.decode(fs_enc) 94 | 95 | def get_debinfo(self): 96 | ############################################### 97 | # 1. setup initial variables 98 | # A. create config defaults 99 | module_name = self.distribution.get_name() 100 | 101 | if 1: 102 | # set default maintainer 103 | if os.environ.get('DEBEMAIL'): 104 | guess_maintainer = "%s <%s>" % ( 105 | os.environ.get('DEBFULLNAME', os.environ['DEBEMAIL']), 106 | os.environ['DEBEMAIL']) 107 | elif ( 108 | self.distribution.get_maintainer() != 'UNKNOWN' and 109 | self.distribution.get_maintainer_email() != 'UNKNOWN' 110 | ): 111 | guess_maintainer = "%s <%s>" % ( 112 | self.distribution.get_maintainer(), 113 | self.distribution.get_maintainer_email()) 114 | elif (self.distribution.get_author() != 'UNKNOWN' and 115 | self.distribution.get_author_email() != 'UNKNOWN'): 116 | guess_maintainer = "%s <%s>" % ( 117 | self.distribution.get_author(), 118 | self.distribution.get_author_email()) 119 | else: 120 | guess_maintainer = "unknown " 121 | if self.default_maintainer is not None: 122 | log.warn('Deprecation warning: you are using the ' 123 | '--default-maintainer option. ' 124 | 'Switch to the --maintainer option.') 125 | guess_maintainer = self.default_maintainer 126 | if hasattr(guess_maintainer, 'decode'): 127 | # python 2 : convert (back to) unicode 128 | guess_maintainer = guess_maintainer.decode('utf-8') 129 | 130 | # B. find config files (if any) 131 | cfg_files = [] 132 | if self.extra_cfg_file is not None: 133 | cfg_files.append(self.extra_cfg_file) 134 | 135 | use_setuptools = True 136 | try: 137 | ei_cmd = self.distribution.get_command_obj('egg_info') 138 | except DistutilsModuleError: 139 | use_setuptools = False 140 | 141 | config_fname = 'stdeb.cfg' 142 | # Distutils fails if not run from setup.py dir, so this is OK. 143 | if os.path.exists(config_fname): 144 | cfg_files.append(config_fname) 145 | 146 | if use_setuptools: 147 | self.run_command('egg_info') 148 | egg_info_dirname = ei_cmd.egg_info 149 | 150 | # Pickup old location of stdeb.cfg 151 | config_fname = os.path.join(egg_info_dirname, 'stdeb.cfg') 152 | if os.path.exists(config_fname): 153 | log.warn('Deprecation warning: stdeb detected old location of ' 154 | 'stdeb.cfg in %s. This file will be used, but you ' 155 | 'should move it alongside setup.py.' % 156 | egg_info_dirname) 157 | cfg_files.append(config_fname) 158 | 159 | egg_module_name = egg_info_dirname[ 160 | :egg_info_dirname.index('.egg-info')] 161 | egg_module_name = egg_module_name.split(os.sep)[-1] 162 | else: 163 | # We don't have setuptools, so guess egg_info_dirname to 164 | # find old stdeb.cfg. 165 | 166 | entries = os.listdir(os.curdir) 167 | for entry in entries: 168 | if not (entry.endswith('.egg-info') and os.path.isdir(entry)): 169 | continue 170 | # Pickup old location of stdeb.cfg 171 | config_fname = os.path.join(entry, 'stdeb.cfg') 172 | if os.path.exists(config_fname): 173 | log.warn('Deprecation warning: stdeb detected ' 174 | 'stdeb.cfg in %s. This file will be used, but ' 175 | 'you should move it alongside setup.py.' % entry) 176 | cfg_files.append(config_fname) 177 | 178 | upstream_version = self.distribution.get_version() 179 | bad_chars = ':_' 180 | for bad_char in bad_chars: 181 | if bad_char in upstream_version: 182 | raise ValueError("Illegal character (%r) detected in version. " 183 | "This will break the debian tools." % 184 | bad_char) 185 | 186 | description = self.distribution.get_description() 187 | if hasattr(description, 'decode'): 188 | # python 2 : convert (back to) unicode 189 | description = description.decode('utf-8') 190 | description = description[:60] 191 | 192 | long_description = self.distribution.get_long_description() 193 | if hasattr(long_description, 'decode'): 194 | # python 2 : convert (back to) unicode 195 | long_description = long_description.decode('utf-8') 196 | long_description = long_description 197 | 198 | debinfo = DebianInfo( 199 | cfg_files=cfg_files, 200 | module_name=module_name, 201 | default_distribution=self.default_distribution, 202 | guess_maintainer=guess_maintainer, 203 | upstream_version=upstream_version, 204 | has_ext_modules=self.distribution.has_ext_modules(), 205 | description=description, 206 | long_description=long_description, 207 | homepage=self.distribution.get_url(), 208 | patch_file=self.patch_file, 209 | patch_level=self.patch_level, 210 | debian_version=self.debian_version, 211 | setup_requires=(), # XXX How do we get the setup_requires? 212 | use_setuptools=use_setuptools, 213 | guess_conflicts_provides_replaces=( 214 | self.guess_conflicts_provides_replaces), 215 | sdist_dsc_command=self, 216 | with_python2=self.with_python2, 217 | with_python3=self.with_python3, 218 | no_python2_scripts=self.no_python2_scripts, 219 | no_python3_scripts=self.no_python3_scripts, 220 | force_x_python3_version=self.force_x_python3_version, 221 | allow_virtualenv_install_location=( 222 | self.allow_virtualenv_install_location), 223 | compat=self.compat, 224 | with_dh_virtualenv=self.with_dh_virtualenv, 225 | with_dh_systemd=self.with_dh_systemd, 226 | ) 227 | return debinfo 228 | -------------------------------------------------------------------------------- /stdeb/command/debianize.py: -------------------------------------------------------------------------------- 1 | from distutils.core import Command # noqa: F401 2 | from stdeb.command.common import common_debian_package_command 3 | 4 | from stdeb.util import build_dsc, stdeb_cmdline_opts, \ 5 | stdeb_cmd_bool_opts, stdeb_cfg_options 6 | 7 | 8 | class debianize(common_debian_package_command): 9 | description = "distutils command to create a debian directory" 10 | 11 | user_options = stdeb_cmdline_opts + stdeb_cfg_options 12 | boolean_options = stdeb_cmd_bool_opts 13 | 14 | def run(self): 15 | debinfo = self.get_debinfo() 16 | if debinfo.patch_file != '': 17 | raise RuntimeError( 18 | 'Patches cannot be applied in debianize command') 19 | 20 | dist_dir = None 21 | repackaged_dirname = None 22 | 23 | build_dsc(debinfo, 24 | dist_dir, 25 | repackaged_dirname, 26 | debian_dir_only=True, 27 | ) 28 | -------------------------------------------------------------------------------- /stdeb/command/install_deb.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import stdeb.util as util 4 | 5 | from distutils.core import Command 6 | 7 | __all__ = ['install_deb'] 8 | 9 | 10 | class install_deb(Command): 11 | description = 'distutils command to install debian binary package' 12 | 13 | user_options = [] 14 | boolean_options = [] 15 | 16 | def initialize_options(self): 17 | pass 18 | 19 | def finalize_options(self): 20 | pass 21 | 22 | def run(self): 23 | # generate .deb file 24 | self.run_command('bdist_deb') 25 | 26 | # get relevant options passed to sdist_dsc 27 | sdist_dsc = self.get_finalized_command('sdist_dsc') 28 | 29 | # execute system command and read output 30 | # (execute and read output of find cmd) 31 | target_debs = glob.glob(os.path.join(sdist_dsc.dist_dir, '*.deb')) 32 | 33 | if len(target_debs) == 0: 34 | raise ValueError('could not find .deb file') 35 | 36 | for target_deb in target_debs: 37 | # define system command to execute (install .deb binary pkg) 38 | syscmd = ['dpkg', '--install', target_deb] 39 | util.process_command(syscmd) 40 | -------------------------------------------------------------------------------- /stdeb/command/sdist_dsc.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import tempfile 4 | 5 | from stdeb import log 6 | from stdeb.util import expand_sdist_file 7 | from stdeb.util import build_dsc, stdeb_cmdline_opts, \ 8 | stdeb_cmd_bool_opts, stdeb_cfg_options 9 | from stdeb.util import repack_tarball_with_debianized_dirname 10 | from stdeb.command.common import common_debian_package_command 11 | 12 | __all__ = ['sdist_dsc'] 13 | 14 | 15 | class sdist_dsc(common_debian_package_command): 16 | description = "distutils command to create a debian source distribution" 17 | 18 | user_options = stdeb_cmdline_opts + [ 19 | ('use-premade-distfile=', 'P', 20 | 'use .zip or .tar.gz file already made by sdist command'), 21 | ] + stdeb_cfg_options 22 | 23 | boolean_options = stdeb_cmd_bool_opts 24 | 25 | def initialize_options(self): 26 | self.use_premade_distfile = None 27 | common_debian_package_command.initialize_options(self) 28 | 29 | def run(self): 30 | debinfo = self.get_debinfo() 31 | if debinfo.patch_file != '' and self.patch_already_applied: 32 | raise RuntimeError('A patch was already applied, but another ' 33 | 'patch is requested.') 34 | 35 | repackaged_dirname = debinfo.source+'-'+debinfo.upstream_version 36 | fullpath_repackaged_dirname = os.path.join(self.dist_dir, 37 | repackaged_dirname) 38 | 39 | cleanup_dirs = [] 40 | if self.use_premade_distfile is None: 41 | # generate original tarball 42 | sdist_cmd = self.distribution.get_command_obj('sdist') 43 | self.run_command('sdist') 44 | 45 | source_tarball = None 46 | for archive_file in sdist_cmd.get_archive_files(): 47 | if archive_file.endswith('.tar.gz'): 48 | source_tarball = archive_file 49 | 50 | if source_tarball is None: 51 | raise RuntimeError('sdist did not produce .tar.gz file') 52 | 53 | # make copy of source tarball in deb_dist/ 54 | local_source_tarball = os.path.split(source_tarball)[-1] 55 | shutil.copy2(source_tarball, local_source_tarball) 56 | source_tarball = local_source_tarball 57 | self.use_premade_distfile = source_tarball 58 | else: 59 | source_tarball = self.use_premade_distfile 60 | 61 | # Copy source tree assuming that package-0.1.tar.gz contains 62 | # single top-level path 'package-0.1'. The contents of this 63 | # directory are then used. 64 | 65 | if os.path.exists(fullpath_repackaged_dirname): 66 | shutil.rmtree(fullpath_repackaged_dirname) 67 | 68 | tmpdir = tempfile.mkdtemp() 69 | expand_sdist_file(os.path.abspath(source_tarball), 70 | cwd=tmpdir) 71 | expanded_base_files = os.listdir(tmpdir) 72 | assert len(expanded_base_files) == 1 73 | actual_package_dirname = expanded_base_files[0] 74 | shutil.move(os.path.join(tmpdir, actual_package_dirname), 75 | fullpath_repackaged_dirname) 76 | 77 | # ensure premade sdist can actually be used 78 | self.use_premade_distfile = os.path.abspath(self.use_premade_distfile) 79 | expand_dir = os.path.join(self.dist_dir, 'tmp_sdist_dsc') 80 | cleanup_dirs.append(expand_dir) 81 | if os.path.exists(expand_dir): 82 | shutil.rmtree(expand_dir) 83 | if not os.path.exists(self.dist_dir): 84 | os.mkdir(self.dist_dir) 85 | os.mkdir(expand_dir) 86 | 87 | expand_sdist_file(self.use_premade_distfile, cwd=expand_dir) 88 | 89 | is_tgz = False 90 | if self.use_premade_distfile.lower().endswith('.tar.gz'): 91 | is_tgz = True 92 | 93 | # now the sdist package is expanded in expand_dir 94 | expanded_root_files = os.listdir(expand_dir) 95 | assert len(expanded_root_files) == 1 96 | distname_in_premade_distfile = expanded_root_files[0] 97 | debianized_dirname = repackaged_dirname 98 | original_dirname = os.path.split(distname_in_premade_distfile)[-1] 99 | do_repack = False 100 | if is_tgz: 101 | source_tarball = self.use_premade_distfile 102 | else: 103 | log.warn('WARNING: .orig.tar.gz will be generated from sdist ' 104 | 'archive ("%s") because it is not a .tar.gz file', 105 | self.use_premade_distfile) 106 | do_repack = True 107 | 108 | if do_repack: 109 | tmp_dir = os.path.join(self.dist_dir, 'tmp_repacking_dir') 110 | os.makedirs(tmp_dir) 111 | cleanup_dirs.append(tmp_dir) 112 | source_tarball = os.path.join(tmp_dir, 'repacked_sdist.tar.gz') 113 | repack_tarball_with_debianized_dirname(self.use_premade_distfile, 114 | source_tarball, 115 | debianized_dirname, 116 | original_dirname) 117 | if source_tarball is not None: 118 | # Because we deleted all .pyc files above, if the 119 | # original source dist has them, we will have 120 | # (wrongly) deleted them. So, quit loudly rather 121 | # than fail silently. 122 | for root, dirs, files in os.walk(fullpath_repackaged_dirname): 123 | for name in files: 124 | if name.endswith('.pyc'): 125 | raise RuntimeError('original source dist cannot ' 126 | 'contain .pyc files') 127 | 128 | ############################################### 129 | # 3. Find all directories 130 | 131 | for pkgdir in self.distribution.packages or []: 132 | debinfo.dirlist += ' ' + pkgdir.replace('.', '/') 133 | 134 | ############################################### 135 | # 4. Build source tree and rename it to be in self.dist_dir 136 | 137 | build_dsc(debinfo, 138 | self.dist_dir, 139 | repackaged_dirname, 140 | orig_sdist=source_tarball, 141 | patch_posix=self.patch_posix, 142 | remove_expanded_source_dir=self.remove_expanded_source_dir, 143 | sign_dsc=self.sign_results, 144 | sign_key=self.sign_key, 145 | ignore_source_changes=self.ignore_source_changes, 146 | ) 147 | 148 | for rmdir in cleanup_dirs: 149 | shutil.rmtree(rmdir) 150 | -------------------------------------------------------------------------------- /stdeb/downloader.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | from functools import partial 4 | import requests 5 | import hashlib 6 | import warnings 7 | from stdeb.pypi_simple import PyPIClient, USER_AGENT 8 | 9 | myprint = print 10 | 11 | 12 | def find_tar_gz(package_name, pypi_url='https://pypi.org', 13 | verbose=0, release=None): 14 | pypi_client = PyPIClient(pypi_url, USER_AGENT) 15 | 16 | download_url = None 17 | expected_md5_digest = None 18 | 19 | if verbose >= 2: 20 | myprint('querying PyPI (%s) for package name "%s"' % (pypi_url, 21 | package_name)) 22 | 23 | all_releases = pypi_client.release_versions(package_name) 24 | if release is not None: 25 | # A specific release is requested. 26 | if verbose >= 2: 27 | myprint( 28 | 'found all available releases: %s' % 29 | (', '.join(all_releases),)) 30 | 31 | if release not in all_releases: 32 | raise ValueError('your desired release %r is not among available ' 33 | 'releases %r' % (release, all_releases)) 34 | version = release 35 | else: 36 | default_release = pypi_client.release_version(package_name) 37 | if verbose >= 2: 38 | myprint( 39 | 'found default release: %s' % (', '.join(default_release),)) 40 | 41 | version = default_release 42 | 43 | download_url, expected_md5_digest = pypi_client.download_url(package_name, version) 44 | 45 | if download_url is None: 46 | raise ValueError('no package "%s" was found' % package_name) 47 | return download_url, expected_md5_digest 48 | 49 | 50 | def md5sum(filename): 51 | # from 52 | # http://stackoverflow.com/questions/7829499/using-hashlib-to-compute-md5-digest-of-a-file-in-python-3 53 | with open(filename, mode='rb') as f: 54 | d = hashlib.md5() 55 | for buf in iter(partial(f.read, 128), b''): 56 | d.update(buf) 57 | return d.hexdigest() 58 | 59 | 60 | def get_source_tarball(package_name, verbose=0, allow_unsafe_download=False, 61 | release=None): 62 | download_url, expected_md5_digest = find_tar_gz(package_name, 63 | verbose=verbose, 64 | release=release) 65 | if not download_url.startswith('https://'): 66 | if allow_unsafe_download: 67 | warnings.warn('downloading from unsafe url: %r' % download_url) 68 | else: 69 | raise ValueError('PYPI returned unsafe url: %r' % download_url) 70 | 71 | fname = download_url.split('/')[-1] 72 | if expected_md5_digest is not None: 73 | if os.path.exists(fname): 74 | actual_md5_digest = md5sum(fname) 75 | if actual_md5_digest == expected_md5_digest: 76 | if verbose >= 1: 77 | myprint('Download URL: %s' % download_url) 78 | myprint( 79 | 'File "%s" already exists with correct checksum.' % 80 | fname) 81 | return fname 82 | else: 83 | raise ValueError( 84 | 'File "%s" exists but has wrong checksum.' % fname) 85 | if verbose >= 1: 86 | myprint('downloading %s' % download_url) 87 | headers = {'User-Agent': USER_AGENT} 88 | r = requests.get(download_url, headers=headers) 89 | r.raise_for_status() 90 | package_tar_gz = r.content 91 | if verbose >= 1: 92 | myprint('done downloading %d bytes.' % (len(package_tar_gz), )) 93 | if expected_md5_digest is not None: 94 | m = hashlib.md5() 95 | m.update(package_tar_gz) 96 | actual_md5_digest = m.hexdigest() 97 | if verbose >= 2: 98 | myprint('md5: actual %s\n expected %s' % 99 | (actual_md5_digest, expected_md5_digest)) 100 | if actual_md5_digest != expected_md5_digest: 101 | raise ValueError('actual and expected md5 digests do not match') 102 | else: 103 | warnings.warn('no md5 digest found -- cannot verify source file') 104 | 105 | fd = open(fname, mode='wb') 106 | fd.write(package_tar_gz) 107 | fd.close() 108 | return fname 109 | -------------------------------------------------------------------------------- /stdeb/pypi_simple.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | import stdeb 4 | 5 | USER_AGENT = 'pypi-install/%s ( https://github.com/astraw/stdeb )' % \ 6 | stdeb.__version__ 7 | 8 | 9 | def normalize_package_name(package_name): 10 | return re.sub(r"[-_.]+", "-", package_name).lower() 11 | 12 | 13 | class PyPIClient(object): 14 | def __init__(self, pypi_url="https://pypi.org", user_agent=USER_AGENT): 15 | self.pypi_url = pypi_url 16 | self._http_session = requests.Session() 17 | self._http_session.headers["User-Agent"] = user_agent 18 | 19 | def release_version(self, package_name): 20 | package_name = normalize_package_name(package_name) 21 | response = self._http_session.get("%s/pypi/%s/json" % (self.pypi_url, package_name)) 22 | data = response.json() 23 | return data["info"]["version"] 24 | 25 | def release_versions(self, package_name): 26 | package_name = normalize_package_name(package_name) 27 | response = self._http_session.get( 28 | "%s/simple/%s" % (self.pypi_url, package_name), 29 | headers={"Accept": "application/vnd.pypi.simple.latest+json"}) 30 | data = response.json() 31 | return data["versions"] 32 | 33 | def download_url(self, package_name, version): 34 | download_url = None 35 | md5_digest = None 36 | package_name = normalize_package_name(package_name) 37 | response = self._http_session.get( 38 | "%s/pypi/%s/%s/json" % (self.pypi_url, package_name, version)) 39 | data = response.json() 40 | for url in data["urls"]: 41 | if url["packagetype"] == "sdist" and \ 42 | url["python_version"] == "source" and \ 43 | url["url"].endswith((".tar.gz", ".zip")): 44 | download_url = url["url"] 45 | md5_digest = url["digests"]["md5"] 46 | break 47 | 48 | return download_url, md5_digest 49 | -------------------------------------------------------------------------------- /stdeb/transport.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | A replacement transport for Python xmlrpc library. 4 | 5 | Usage: 6 | 7 | >>> import xmlrpclib 8 | >>> from transport import RequestsTransport 9 | >>> s = xmlrpclib.ServerProxy( 10 | >>> 'http://yoursite.com/xmlrpc', transport=RequestsTransport()) 11 | >>> s.demo.sayHello() 12 | Hello! 13 | """ 14 | try: 15 | import xmlrpc.client as xmlrpc 16 | except ImportError: 17 | import xmlrpclib as xmlrpc 18 | 19 | import requests 20 | import requests.utils 21 | 22 | import sys 23 | from distutils.version import StrictVersion 24 | import warnings 25 | 26 | 27 | class RequestsTransport(xmlrpc.Transport): 28 | """ 29 | Drop in Transport for xmlrpclib that uses Requests instead of httplib 30 | """ 31 | # change our user agent to reflect Requests 32 | user_agent = "Python XMLRPC with Requests (python-requests.org)" 33 | 34 | # override this if you'd like to https 35 | use_https = False 36 | 37 | def request(self, host, handler, request_body, verbose): 38 | """ 39 | Make an xmlrpc request. 40 | """ 41 | headers = {'User-Agent': self.user_agent, 42 | 'Content-Type': 'text/xml', 43 | } 44 | url = self._build_url(host, handler) 45 | kwargs = {} 46 | if StrictVersion(requests.__version__) >= StrictVersion('0.8.8'): 47 | kwargs['verify'] = True 48 | else: 49 | if self.use_https: 50 | warnings.warn( 51 | 'using https transport but no certificate ' 52 | 'verification. (Hint: upgrade requests package.)') 53 | try: 54 | resp = requests.post(url, data=request_body, headers=headers, 55 | **kwargs) 56 | except ValueError: 57 | raise 58 | except Exception: 59 | raise # something went wrong 60 | else: 61 | try: 62 | resp.raise_for_status() 63 | except requests.RequestException as e: 64 | raise xmlrpc.ProtocolError( 65 | url, resp.status_code, str(e), resp.headers) 66 | else: 67 | return self.parse_response(resp) 68 | 69 | def parse_response(self, resp): 70 | """ 71 | Parse the xmlrpc response. 72 | """ 73 | p, u = self.getparser() 74 | 75 | if hasattr(resp, 'text'): 76 | # modern requests will do this for us 77 | text = resp.text # this is unicode(py2)/str(py3) 78 | else: 79 | 80 | encoding = requests.utils.get_encoding_from_headers(resp.headers) 81 | if encoding is None: 82 | encoding = 'utf-8' # FIXME: what to do here? 83 | 84 | if sys.version_info[0] == 2: 85 | text = unicode( # noqa: F821 86 | resp.content, encoding, errors='replace') 87 | else: 88 | assert sys.version_info[0] == 3 89 | text = str(resp.content, encoding, errors='replace') 90 | p.feed(text) 91 | p.close() 92 | return u.close() 93 | 94 | def _build_url(self, host, handler): 95 | """ 96 | Build a url for our request based on the host, handler and use_http 97 | property 98 | """ 99 | scheme = 'https' if self.use_https else 'http' 100 | return '%s://%s/%s' % (scheme, host, handler) 101 | -------------------------------------------------------------------------------- /stdeb/util.py: -------------------------------------------------------------------------------- 1 | # 2 | # This module contains most of the code of stdeb. 3 | # 4 | import calendar 5 | import os 6 | import re 7 | import select 8 | import shutil 9 | import sys 10 | import time 11 | import codecs 12 | import configparser as ConfigParser 13 | import subprocess 14 | import tempfile 15 | import stdeb # noqa: F401 16 | from stdeb import log, __version__ as __stdeb_version__ 17 | 18 | if hasattr(os, 'link'): 19 | link_func = os.link 20 | else: 21 | # matplotlib deletes link from os namespace, expected distutils workaround 22 | link_func = shutil.copyfile 23 | 24 | __all__ = ['DebianInfo', 'build_dsc', 'expand_tarball', 'expand_zip', 25 | 'stdeb_cmdline_opts', 'stdeb_cmd_bool_opts', 'recursive_hardlink', 26 | 'apply_patch', 'repack_tarball_with_debianized_dirname', 27 | 'expand_sdist_file', 'stdeb_cfg_options'] 28 | 29 | DH_MIN_VERS = '9' # Fundamental to stdeb >= 0.10 30 | DH_DEFAULT_VERS = 9 31 | 32 | PYTHON_ALL_MIN_VERS = '2.6.6-3' 33 | 34 | try: 35 | # Python 2.x 36 | from exceptions import Exception 37 | except ImportError: 38 | # Python 3.x 39 | pass 40 | 41 | 42 | class CalledProcessError(Exception): 43 | pass 44 | 45 | 46 | class CantSatisfyRequirement(Exception): 47 | pass 48 | 49 | 50 | def check_call(*popenargs, **kwargs): 51 | retcode = subprocess.call(*popenargs, **kwargs) 52 | if retcode == 0: 53 | return 54 | raise CalledProcessError(retcode) 55 | 56 | 57 | if sys.version_info[0] == 2: 58 | help_str_py2 = 'If True, build package for python 2. (Default=True).' 59 | help_str_py3 = 'If True, build package for python 3. (Default=False).' 60 | else: 61 | assert sys.version_info[0] == 3 62 | help_str_py2 = 'If True, build package for python 2. (Default=False).' 63 | help_str_py3 = 'If True, build package for python 3. (Default=True).' 64 | 65 | stdeb_cmdline_opts = [ 66 | ('dist-dir=', 'd', 67 | "directory to put final built distributions in (default='deb_dist')"), 68 | ('patch-already-applied', 'a', 69 | 'patch was already applied (used when py2dsc calls sdist_dsc)'), 70 | ('default-distribution=', None, 71 | "deprecated (see --suite)"), 72 | ('suite=', 'z', 73 | "distribution name to use if not specified in .cfg (default='unstable')"), 74 | ('default-maintainer=', None, 75 | 'deprecated (see --maintainer)'), 76 | ('maintainer=', 'm', 77 | 'maintainer name and email to use if not specified in .cfg ' 78 | '(default from setup.py)'), 79 | ('debian-version=', None, 'debian version (Default: 1)'), 80 | ('extra-cfg-file=', 'x', 81 | 'additional .cfg file (in addition to stdeb.cfg if present)'), 82 | ('patch-file=', 'p', 83 | 'patch file applied before setup.py called ' 84 | '(incompatible with file specified in .cfg)'), 85 | ('patch-level=', 'l', 86 | 'patch file applied before setup.py called ' 87 | '(incompatible with file specified in .cfg)'), 88 | ('patch-posix', 'q', 89 | 'apply the patch with --posix mode'), 90 | ('remove-expanded-source-dir', 'r', 91 | 'remove the expanded source directory'), 92 | ('ignore-install-requires', 'i', 93 | 'ignore the requirements from requires.txt in the egg-info directory'), 94 | ('pycentral-backwards-compatibility=', None, 95 | 'This option has no effect, is here for backwards compatibility, and may ' 96 | 'be removed someday.'), 97 | ('workaround-548392=', None, 98 | 'This option has no effect, is here for backwards compatibility, and may ' 99 | 'be removed someday.'), 100 | ('no-backwards-compatibility', None, 101 | 'This option has no effect, is here for backwards compatibility, and may ' 102 | 'be removed someday.'), 103 | ('guess-conflicts-provides-replaces=', None, 104 | 'If True, attempt to guess Conflicts/Provides/Replaces in debian/control ' 105 | 'based on apt-cache output. (Default=False).'), 106 | ('with-python2=', None, 107 | help_str_py2), 108 | ('with-python3=', None, 109 | help_str_py3), 110 | ('no-python2-scripts=', None, 111 | 'If True, do not install scripts for python 2. (Default=False).'), 112 | ('no-python3-scripts=', None, 113 | 'If True, do not install scripts for python 3. (Default=False).'), 114 | ('force-x-python3-version', None, 115 | 'Override default minimum python3:any dependency with value from ' 116 | 'x-python3-version'), 117 | ('allow-virtualenv-install-location', None, 118 | 'Allow installing into /some/random/virtualenv-path'), 119 | ('compat=', 'c', 120 | 'Debian compatibility level (Default={})'.format(DH_DEFAULT_VERS)), 121 | ('with-dh-virtualenv', None, 122 | 'Use dh_virtualenv to build the package instead of python_distutils. ' 123 | 'This is useful to embed an application with all its dependencies ' 124 | 'and dont touch to the system libraries.'), 125 | ('with-dh-systemd', None, 126 | 'Adds the systemd addon that will dh_systemd_enable and' 127 | 'dh_systemd_start helpers at the correct time during build.'), 128 | ('sign-results', None, 129 | 'Use gpg to sign the resulting .dsc and .changes file'), 130 | ('sign-key=', None, 131 | 'Specify signing key'), 132 | ('ignore-source-changes', None, 133 | 'Ignore all changes on source when building source package (add -i.* ' 134 | 'option to dpkg-source)'), 135 | ] 136 | 137 | # old entries from stdeb.cfg: 138 | 139 | # These should be settable as distutils command options, but in case 140 | # we want to support other packaging methods, they should also be 141 | # settable outside distutils. Consequently, we keep the ability to 142 | # parse ConfigParser files (specified with --extra-cfg-file). TODO: 143 | # Also, some (most, in fact) of the above options should also be 144 | # settable in the ConfigParser file. 145 | 146 | stdeb_cfg_options = [ 147 | # With defaults 148 | ('source=', None, 149 | 'debian/control Source: (Default: )'), 150 | ('package=', None, 151 | 'debian/control Package: (Default: python-)'), 152 | ('package3=', None, 153 | 'debian/control Package: (Default: python3-)'), 154 | ('suite=', None, 155 | 'suite (e.g. stable, lucid) in changelog (Default: unstable)'), 156 | ('suite3=', None, 157 | 'suite3 Suites used when building with python3 only (Default: ' 158 | ')'), 159 | ('maintainer=', None, 160 | 'debian/control Maintainer: (Default: )'), 161 | ('debian-version=', None, 'debian version (Default: 1)'), 162 | ('section=', None, 'debian/control Section: (Default: python)'), 163 | 164 | # With no defaults 165 | ('epoch=', None, 'version epoch'), 166 | ('forced-upstream-version=', None, 'forced upstream version'), 167 | ('upstream-version-prefix=', None, 'upstream version prefix'), 168 | ('upstream-version-suffix=', None, 'upstream version suffix'), 169 | ('uploaders=', None, 'uploaders'), 170 | ('copyright-file=', None, 'copyright file'), 171 | ('build-depends=', None, 'debian/control Build-Depends:'), 172 | ('build-conflicts=', None, 'debian/control Build-Conflicts:'), 173 | ('stdeb-patch-file=', None, 'file containing patches for stdeb to apply'), 174 | ('stdeb-patch-level=', None, 'patch level provided to patch command'), 175 | ('depends=', None, 'debian/control Depends:'), 176 | ('depends3=', None, 'debian/control Depends:'), 177 | ('suggests=', None, 'debian/control Suggests:'), 178 | ('suggests3=', None, 'debian/control Suggests:'), 179 | ('recommends=', None, 'debian/control Recommends:'), 180 | ('recommends3=', None, 'debian/control Recommends:'), 181 | ('xs-python-version=', None, 'debian/control XS-Python-Version:'), 182 | ('x-python3-version=', None, 'debian/control X-Python3-Version:'), 183 | ('dpkg-shlibdeps-params=', None, 'parameters passed to dpkg-shlibdeps'), 184 | ('conflicts=', None, 'debian/control Conflicts:'), 185 | ('conflicts3=', None, 'debian/control Conflicts:'), 186 | ('breaks=', None, 'debian/control Breaks:'), 187 | ('breaks3=', None, 'debian/control Breaks:'), 188 | ('provides=', None, 'debian/control Provides:'), 189 | ('provides3=', None, 'debian/control Provides3:'), 190 | ('replaces=', None, 'debian/control Replaces:'), 191 | ('replaces3=', None, 'debian/control Replaces3:'), 192 | ('mime-desktop-files=', None, 'MIME desktop files'), 193 | ('mime-file=', None, 'MIME file'), 194 | ('shared-mime-file=', None, 'shared MIME file'), 195 | ('setup-env-vars=', None, 'environment variables passed to setup.py'), 196 | ('udev-rules=', None, 'file with rules to install to udev'), 197 | ('python2-depends-name=', None, 198 | 'Python 2 Debian package name used in ${python:Depends}'), 199 | ] 200 | 201 | stdeb_cmd_bool_opts = [ 202 | 'patch-already-applied', 203 | 'remove-expanded-source-dir', 204 | 'patch-posix', 205 | 'ignore-install-requires', 206 | 'no-backwards-compatibility', 207 | 'force-x-python3-version', 208 | 'allow-virtualenv-install-location', 209 | 'with-dh-virtualenv', 210 | 'with-dh-systemd', 211 | 'sign-results', 212 | 'ignore-source-changes', 213 | ] 214 | 215 | 216 | class NotGiven: 217 | pass 218 | 219 | 220 | def process_command(args, cwd=None): 221 | if not isinstance(args, (list, tuple)): 222 | raise RuntimeError("args passed must be in a list") 223 | check_call(args, cwd=cwd) 224 | 225 | 226 | def recursive_hardlink(src, dst): 227 | dst = os.path.abspath(dst) 228 | orig_dir = os.path.abspath(os.curdir) 229 | os.chdir(src) 230 | try: 231 | for root, dirs, files in os.walk(os.curdir): 232 | for file in files: 233 | fullpath = os.path.normpath(os.path.join(root, file)) 234 | dirname, fname = os.path.split(fullpath) 235 | dstdir = os.path.normpath(os.path.join(dst, dirname)) 236 | if not os.path.exists(dstdir): 237 | os.makedirs(dstdir) 238 | newpath = os.path.join(dstdir, fname) 239 | if os.path.exists(newpath): 240 | if os.path.samefile(fullpath, newpath): 241 | continue 242 | else: 243 | os.unlink(newpath) 244 | # print 'linking %s -> %s'%(fullpath,newpath) 245 | link_func(fullpath, newpath) 246 | finally: 247 | os.chdir(orig_dir) 248 | 249 | 250 | def debianize_name(name): 251 | "make name acceptable as a Debian (binary) package name" 252 | name = name.replace('_', '-') 253 | name = name.lower() 254 | return name 255 | 256 | 257 | def source_debianize_name(name): 258 | "make name acceptable as a Debian source package name" 259 | name = name.replace('_', '-') 260 | name = name.replace('.', '-') 261 | name = name.lower() 262 | return name 263 | 264 | 265 | def debianize_version(name): 266 | "make name acceptable as a Debian package name" 267 | # XXX should use setuptools' version sorting and do this properly: 268 | name = name.replace('.dev', '~dev') 269 | 270 | name = name.lower() 271 | return name 272 | 273 | 274 | def dpkg_compare_versions(v1, op, v2): 275 | args = ['/usr/bin/dpkg', '--compare-versions', v1, op, v2] 276 | cmd = subprocess.Popen(args) 277 | returncode = cmd.wait() 278 | if returncode: 279 | return False 280 | else: 281 | return True 282 | 283 | 284 | def get_cmd_stdout(args): 285 | cmd = subprocess.Popen(args, stdout=subprocess.PIPE) 286 | returncode = cmd.wait() 287 | if returncode: 288 | log.error('ERROR running: %s', ' '.join(args)) 289 | raise RuntimeError('returncode %d', returncode) 290 | return cmd.stdout.read() 291 | 292 | 293 | def get_date_822(): 294 | """return output of 822-date command""" 295 | source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH', None) 296 | if source_date_epoch: 297 | t = time.gmtime(int(source_date_epoch)) 298 | else: 299 | t = time.localtime() 300 | return time.strftime( 301 | calendar.day_abbr[t.tm_wday] + ", %d " + 302 | calendar.month_abbr[t.tm_mon] + " %Y %H:%M:%S %z", t) 303 | 304 | 305 | def get_version_str(pkg): 306 | args = ['/usr/bin/dpkg-query', '--show', '--showformat=${Version}', pkg] 307 | stdout = get_cmd_stdout(args) 308 | return stdout.strip().decode('ascii') 309 | 310 | 311 | def load_module(name, fname): 312 | import imp 313 | 314 | suffix = '.py' 315 | found = False 316 | for description in imp.get_suffixes(): 317 | if description[0] == suffix: 318 | found = True 319 | break 320 | assert found 321 | 322 | fd = open(fname, mode='r') 323 | try: 324 | module = imp.load_module(name, fd, fname, description) 325 | finally: 326 | fd.close() 327 | return module 328 | 329 | 330 | def get_deb_depends_from_setuptools_requires(requirements, on_failure="warn"): 331 | """ 332 | Suppose you can't confidently figure out a .deb which satisfies a given 333 | requirement. If on_failure == 'warn', then log a warning. If on_failure 334 | == 'raise' then raise CantSatisfyRequirement exception. If on_failure == 335 | 'guess' then guess that python-$FOO will satisfy the dependency and that 336 | the Python version numbers will apply to the Debian packages (in addition 337 | to logging a warning message). 338 | """ 339 | assert on_failure in ("raise", "warn", "guess"), on_failure 340 | 341 | import pkg_resources 342 | 343 | depends = [] # This will be the return value from this function. 344 | 345 | parsed_reqs = [] 346 | 347 | for extra, reqs in pkg_resources.split_sections(requirements): 348 | if extra: 349 | continue 350 | parsed_reqs.extend(pkg_resources.parse_requirements(reqs)) 351 | 352 | if not parsed_reqs: 353 | return depends 354 | 355 | if not os.path.exists('/usr/bin/apt-file'): 356 | raise ValueError('apt-file not in /usr/bin. Please install ' 357 | 'with: sudo apt-get install apt-file') 358 | 359 | # Ask apt-file for any packages which have a .egg-info file by 360 | # these names. 361 | 362 | # Note that apt-file appears to think that some packages 363 | # e.g. setuptools itself have "foo.egg-info/BLAH" files but not a 364 | # "foo.egg-info" directory. 365 | 366 | egginfore = (r"(/(%s)(?:-[^/]+)?(?:-py[0-9]\.[0-9.]+)?\.egg-info)" 367 | % '|'.join( 368 | req.project_name.replace('-', '_') 369 | for req in parsed_reqs)) 370 | 371 | args = ["apt-file", "search", "--ignore-case", "--regexp", egginfore] 372 | 373 | if 1: 374 | # do dry run on apt-file 375 | dry_run_args = args[:] + ['--dummy', '--non-interactive'] 376 | cmd = subprocess.Popen(dry_run_args, stderr=subprocess.PIPE) 377 | returncode = cmd.wait() 378 | if returncode: 379 | err_output = cmd.stderr.read() 380 | raise RuntimeError('Error running "apt-file search": ' + 381 | err_output.strip()) 382 | 383 | try: 384 | cmd = subprocess.Popen(args, stdin=subprocess.PIPE, 385 | stdout=subprocess.PIPE, 386 | universal_newlines=True) 387 | except Exception as le: 388 | # TODO: catch rc=1 and "E: The cache directory is empty. You need to 389 | # run 'apt-file update' first.", and tell the user to follow those 390 | # instructions. 391 | log.error('ERROR running: %s', ' '.join(args)) 392 | raise RuntimeError('exception %s from subprocess %s' % (le, args)) 393 | returncode = cmd.wait() 394 | if returncode: 395 | log.error('ERROR running: %s', ' '.join(args)) 396 | raise RuntimeError('returncode %d from subprocess %s' % (returncode, 397 | args)) 398 | 399 | inlines = cmd.stdout.readlines() 400 | 401 | dd = {} # {pydistname: {pydist: set(debpackagename)}} 402 | E = re.compile(egginfore, re.I) 403 | D = re.compile("^([^:]*):", re.I) 404 | eggsndebs = set() 405 | for line in inlines: 406 | if line: 407 | emo = E.search(line) 408 | assert emo, line 409 | dmo = D.search(line) 410 | assert dmo, line 411 | eggsndebs.add((emo.group(1), dmo.group(1))) 412 | 413 | for (egginfo, debname) in eggsndebs: 414 | pydist = pkg_resources.Distribution.from_filename(egginfo) 415 | try: 416 | dd.setdefault( 417 | pydist.project_name.lower(), {}).setdefault( 418 | pydist, set()).add(debname) 419 | except ValueError as le: 420 | log.warn("I got an error parsing a .egg-info file named \"%s\" " 421 | "from Debian package \"%s\" as a pkg_resources " 422 | "Distribution: %s" % (egginfo, debname, le,)) 423 | pass 424 | 425 | # Now for each requirement, see if a Debian package satisfies it. 426 | ops = {'<': '<<', '>': '>>', '==': '=', '<=': '<=', '>=': '>='} 427 | for req in parsed_reqs: 428 | reqname = req.project_name.lower() 429 | gooddebs = set() 430 | for pydist, debs in dd.get(reqname, {}).iteritems(): 431 | if pydist in req: 432 | # log.info("I found Debian packages \"%s\" which provides " 433 | # "Python package \"%s\", version \"%s\", which " 434 | # "satisfies our version requirements: \"%s\"" 435 | # % (', '.join(debs), req.project_name, ver, req) 436 | gooddebs |= (debs) 437 | else: 438 | log.info("I found Debian packages \"%s\" which provides " 439 | "Python package \"%s\" which " 440 | "does not satisfy our version requirements: " 441 | "\"%s\" -- ignoring." 442 | % (', '.join(debs), req.project_name, req)) 443 | if not gooddebs: 444 | if on_failure == 'warn': 445 | log.warn( 446 | "I found no Debian package which provides the required " 447 | "Python package \"%s\" with version requirements " 448 | "\"%s\"." % (req.project_name, req.specs)) 449 | elif on_failure == "raise": 450 | raise CantSatisfyRequirement( 451 | "I found no Debian package which provides the required " 452 | "Python package \"%s\" with version requirements " 453 | "\"%s\"." % (req.project_name, req.specs), req) 454 | elif on_failure == "guess": 455 | log.warn("I found no Debian package which provides the " 456 | "required Python package \"%s\" with version " 457 | "requirements \"%s\". Guessing blindly that the " 458 | "name \"python-%s\" will be it, and that the Python " 459 | "package version number requirements will apply to " 460 | "the Debian package." % (req.project_name, 461 | req.specs, reqname)) 462 | gooddebs.add("python-" + reqname) 463 | elif len(gooddebs) == 1: 464 | log.info("I found a Debian package which provides the require " 465 | "Python package. Python package: \"%s\", " 466 | "Debian package: \"%s\"; adding Depends specifications " 467 | "for the following version(s): \"%s\"" 468 | % (req.project_name, tuple(gooddebs)[0], req.specs)) 469 | else: 470 | log.warn("I found multiple Debian packages which provide the " 471 | "Python distribution required. I'm listing them all " 472 | "as alternates. Candidate debs which claim to provide " 473 | "the Python package \"%s\" are: \"%s\"" 474 | % (req.project_name, ', '.join(gooddebs),)) 475 | 476 | alts = [] 477 | for deb in gooddebs: 478 | added_any_alt = False 479 | for spec in req.specs: 480 | # Here we blithely assume that the Debian package 481 | # versions are enough like the Python package versions 482 | # that the requirement can be ported straight over... 483 | alts.append("%s (%s %s)" % (deb, ops[spec[0]], spec[1])) 484 | added_any_alt = True 485 | 486 | if not added_any_alt: 487 | # No alternates were added, but we have the name of a 488 | # good package. 489 | alts.append("%s" % deb) 490 | 491 | if len(alts): 492 | depends.append(' | '.join(alts)) 493 | 494 | return depends 495 | 496 | 497 | def make_tarball(tarball_fname, directory, cwd=None): 498 | "create a tarball from a directory" 499 | if tarball_fname.endswith('.gz'): 500 | opts = 'czf' 501 | else: 502 | opts = 'cf' 503 | args = ['/bin/tar', opts, tarball_fname, directory] 504 | process_command(args, cwd=cwd) 505 | 506 | 507 | def expand_tarball(tarball_fname, cwd=None): 508 | "expand a tarball" 509 | if tarball_fname.endswith('.gz'): 510 | opts = 'xzf' 511 | elif tarball_fname.endswith('.bz2'): 512 | opts = 'xjf' 513 | else: 514 | opts = 'xf' 515 | args = ['/bin/tar', opts, tarball_fname] 516 | process_command(args, cwd=cwd) 517 | 518 | 519 | def expand_zip(zip_fname, cwd=None): 520 | "expand a zip" 521 | unzip_path = '/usr/bin/unzip' 522 | if not os.path.exists(unzip_path): 523 | log.error('ERROR: {} does not exist'.format(unzip_path)) 524 | sys.exit(1) 525 | args = [unzip_path, zip_fname] 526 | # Does it have a top dir 527 | res = subprocess.Popen( 528 | [args[0], '-l', args[1]], cwd=cwd, 529 | stdout=subprocess.PIPE, 530 | stderr=subprocess.PIPE, 531 | ) 532 | contents = [] 533 | for line in res.stdout.readlines()[3:-2]: 534 | contents.append(line.split(None, 3)[-1]) 535 | commonprefix = os.path.commonprefix(contents) 536 | if not commonprefix: 537 | extdir = os.path.join(cwd, os.path.basename(zip_fname[:-4])) 538 | args.extend(['-d', os.path.abspath(extdir)]) 539 | 540 | process_command(args, cwd=cwd) 541 | 542 | 543 | def expand_sdist_file(sdist_file, cwd=None): 544 | lower_sdist_file = sdist_file.lower() 545 | if lower_sdist_file.endswith('.zip'): 546 | expand_zip(sdist_file, cwd=cwd) 547 | elif lower_sdist_file.endswith('.tar.bz2'): 548 | expand_tarball(sdist_file, cwd=cwd) 549 | elif lower_sdist_file.endswith('.tar.gz'): 550 | expand_tarball(sdist_file, cwd=cwd) 551 | else: 552 | raise RuntimeError('could not guess format of original sdist file') 553 | 554 | 555 | def repack_tarball_with_debianized_dirname(orig_sdist_file, 556 | repacked_sdist_file, 557 | debianized_dirname, 558 | original_dirname): 559 | working_dir = tempfile.mkdtemp() 560 | expand_sdist_file(orig_sdist_file, cwd=working_dir) 561 | fullpath_original_dirname = os.path.join(working_dir, original_dirname) 562 | fullpath_debianized_dirname = os.path.join(working_dir, debianized_dirname) 563 | 564 | # ensure sdist looks like sdist: 565 | assert os.path.exists(fullpath_original_dirname) 566 | assert len(os.listdir(working_dir)) == 1 567 | 568 | if fullpath_original_dirname != fullpath_debianized_dirname: 569 | # rename original dirname to debianized dirname 570 | os.rename(fullpath_original_dirname, 571 | fullpath_debianized_dirname) 572 | make_tarball(repacked_sdist_file, debianized_dirname, cwd=working_dir) 573 | shutil.rmtree(working_dir) 574 | 575 | 576 | def dpkg_buildpackage(*args, **kwargs): 577 | cwd = kwargs.pop('cwd', None) 578 | if len(kwargs) != 0: 579 | raise ValueError('only kwarg can be "cwd"') 580 | "call dpkg-buildpackage [arg1] [...] [argN]" 581 | args = ['/usr/bin/dpkg-buildpackage']+list(args) 582 | process_command(args, cwd=cwd) 583 | 584 | 585 | def dpkg_source(b_or_x, arg1, cwd=None): 586 | "call dpkg-source -b|x arg1 [arg2]" 587 | assert b_or_x in ['-b', '-x'] 588 | args = ['/usr/bin/dpkg-source', b_or_x, arg1] 589 | process_command(args, cwd=cwd) 590 | 591 | 592 | def apply_patch(patchfile, cwd=None, posix=False, level=0): 593 | """call 'patch -p[level] [--posix] < arg1' 594 | 595 | posix mode is sometimes necessary. It keeps empty files so that 596 | dpkg-source removes their contents. 597 | 598 | """ 599 | if not os.path.exists(patchfile): 600 | raise RuntimeError('patchfile "%s" does not exist' % patchfile) 601 | fd = open(patchfile, mode='r') 602 | 603 | level_str = '-p%d' % level 604 | args = ['/usr/bin/patch', level_str] 605 | if posix: 606 | args.append('--posix') 607 | 608 | log.info('PATCH COMMAND: %s < %s', ' '.join(args), patchfile) 609 | log.info(' PATCHING in dir: %s', cwd) 610 | # print >> sys.stderr, 'PATCH COMMAND:',' '.join(args),'<',patchfile 611 | # print >> sys.stderr, ' PATCHING in dir:',cwd 612 | res = subprocess.Popen( 613 | args, cwd=cwd, 614 | stdin=fd, 615 | stdout=subprocess.PIPE, 616 | stderr=subprocess.PIPE, 617 | universal_newlines=True 618 | ) 619 | returncode = None 620 | while returncode is None: 621 | returncode = res.poll() 622 | ready = select.select([res.stdout, res.stderr], [], [], 0.1) 623 | # XXX figure out how to do this without reading byte-by-byte 624 | if res.stdout in ready[0]: 625 | sys.stdout.write(res.stdout.read(1)) 626 | sys.stdout.flush() 627 | if res.stderr in ready[0]: 628 | sys.stderr.write(res.stderr.read(1)) 629 | sys.stderr.flush() 630 | # finish outputting file 631 | sys.stdout.write(res.stdout.read()) 632 | sys.stdout.flush() 633 | sys.stderr.write(res.stderr.read()) 634 | sys.stderr.flush() 635 | 636 | if returncode: 637 | log.error('ERROR running: %s', ' '.join(args)) 638 | log.error('ERROR in %s', cwd) 639 | # print >> sys.stderr, 'ERROR running: %s'%(' '.join(args),) 640 | # print >> sys.stderr, 'ERROR in',cwd 641 | raise RuntimeError('returncode %d' % returncode) 642 | 643 | 644 | def parse_vals(cfg, section, option): 645 | """parse comma separated values in debian control file style from .cfg""" 646 | try: 647 | vals = cfg.get(section, option) 648 | except ConfigParser.NoSectionError as err: 649 | if section != 'DEFAULT': 650 | vals = cfg.get('DEFAULT', option) 651 | else: 652 | raise err 653 | vals = vals.split('#')[0] 654 | vals = vals.strip() 655 | vals = vals.split(',') 656 | vals = [v.strip() for v in vals] 657 | vals = [v for v in vals if len(v)] 658 | return vals 659 | 660 | 661 | def parse_val(cfg, section, option): 662 | """extract a single value from .cfg""" 663 | vals = parse_vals(cfg, section, option) 664 | if len(vals) == 0: 665 | return '' 666 | else: 667 | assert len(vals) == 1, (section, option, vals, type(vals)) 668 | return vals[0] 669 | 670 | 671 | def apt_cache_info(apt_cache_cmd, package_name): 672 | if apt_cache_cmd not in ('showsrc', 'show'): 673 | raise NotImplementedError( 674 | "don't know how to run apt-cache command '%s'" % apt_cache_cmd) 675 | 676 | result_list = [] 677 | args = ["apt-cache", apt_cache_cmd, package_name] 678 | cmd = subprocess.Popen(args, 679 | stdin=subprocess.PIPE, 680 | stderr=subprocess.PIPE, 681 | stdout=subprocess.PIPE) 682 | returncode = cmd.wait() 683 | if returncode: 684 | errline = cmd.stderr.read() 685 | if not ( 686 | returncode == 100 and errline == 687 | "E: You must put some 'source' URIs in your sources.list\n" 688 | ): 689 | log.error('ERROR running: %s', ' '.join(args)) 690 | raise RuntimeError( 691 | 'returncode %d from subprocess %s' % (returncode, args)) 692 | inlines = cmd.stdout.read() 693 | version_blocks = inlines.split('\n\n') 694 | for version_block in version_blocks: 695 | block_dict = {} 696 | 697 | if len(version_block) == 0: 698 | continue 699 | version_lines = version_block.split('\n') 700 | assert version_lines[0].startswith('Package: ') 701 | block_dict['Package'] = version_lines[0][len('Package: '):] 702 | 703 | if apt_cache_cmd == 'showsrc': 704 | assert version_lines[1].startswith('Binary: ') 705 | block_dict['Binary'] = version_lines[1][len('Binary: '):] 706 | block_dict['Binary'] = block_dict['Binary'].split(', ') 707 | 708 | elif apt_cache_cmd == 'show': 709 | for start in ('Provides: ', 'Conflicts: ', 'Replaces: '): 710 | key = start[:-2] 711 | for line in version_lines[2:]: 712 | if line.startswith(start): 713 | unsplit_line_result = line[len(start):] 714 | split_result = unsplit_line_result.split(', ') 715 | block_dict[key] = split_result 716 | if key not in block_dict: 717 | block_dict[key] = [] 718 | result_list.append(block_dict) 719 | return result_list 720 | 721 | 722 | def check_cfg_files(cfg_files, module_name): 723 | """check if the configuration files actually specify something 724 | 725 | If config files are given, give warning if they don't contain 726 | information. This may indicate a wrong module name name, for 727 | example. 728 | """ 729 | 730 | cfg = ConfigParser.ConfigParser() 731 | cfg.read(cfg_files) 732 | if cfg.has_section(module_name): 733 | section_items = cfg.items(module_name) 734 | else: 735 | section_items = [] 736 | default_items = cfg.items('DEFAULT') 737 | 738 | n_items = len(section_items) + len(default_items) 739 | if n_items == 0: 740 | log.warn('configuration files were specified, but no options were ' 741 | 'found in "%s" or "DEFAULT" sections.' % (module_name,)) 742 | 743 | 744 | class DebianInfo: 745 | """encapsulate information for Debian distribution system""" 746 | def __init__(self, 747 | cfg_files=NotGiven, 748 | module_name=NotGiven, 749 | default_distribution=NotGiven, 750 | guess_maintainer=NotGiven, 751 | upstream_version=NotGiven, 752 | has_ext_modules=NotGiven, 753 | description=NotGiven, 754 | long_description=NotGiven, 755 | homepage=NotGiven, 756 | patch_file=None, 757 | patch_level=None, 758 | setup_requires=None, 759 | debian_version=None, 760 | use_setuptools=False, 761 | guess_conflicts_provides_replaces=False, 762 | sdist_dsc_command=None, 763 | with_python2=None, 764 | with_python3=None, 765 | no_python2_scripts=None, 766 | no_python3_scripts=None, 767 | force_x_python3_version=False, 768 | allow_virtualenv_install_location=False, 769 | compat=DH_DEFAULT_VERS, 770 | with_dh_virtualenv=False, 771 | with_dh_systemd=False, 772 | ): 773 | if cfg_files is NotGiven: 774 | raise ValueError("cfg_files must be supplied") 775 | if module_name is NotGiven: 776 | raise ValueError("module_name must be supplied") 777 | if default_distribution is NotGiven: 778 | raise ValueError("default_distribution must be supplied") 779 | if guess_maintainer is NotGiven: 780 | raise ValueError("guess_maintainer must be supplied") 781 | if upstream_version is NotGiven: 782 | raise ValueError("upstream_version must be supplied") 783 | if has_ext_modules is NotGiven: 784 | raise ValueError("has_ext_modules must be supplied") 785 | if description is NotGiven: 786 | raise ValueError("description must be supplied") 787 | if long_description is NotGiven: 788 | raise ValueError("long_description must be supplied") 789 | if homepage is NotGiven: 790 | raise ValueError("homepage must be supplied") 791 | 792 | cfg_defaults = self._make_cfg_defaults( 793 | module_name=module_name, 794 | default_distribution=default_distribution, 795 | guess_maintainer=guess_maintainer, 796 | ) 797 | 798 | if len(cfg_files): 799 | check_cfg_files(cfg_files, module_name) 800 | 801 | cfg = ConfigParser.ConfigParser(cfg_defaults) 802 | for cfg_file in cfg_files: 803 | with codecs.open(cfg_file, mode='r', encoding='utf-8') as fd: 804 | cfg.read_file(fd) 805 | 806 | if sdist_dsc_command is not None: 807 | # Allow distutils commands to override config files (this lets 808 | # command line options beat file options). 809 | for longopt, shortopt, desc in stdeb_cfg_options: 810 | opt_name = longopt[:-1] 811 | name = opt_name.replace('-', '_') 812 | value = getattr(sdist_dsc_command, name) 813 | if value is not None: 814 | if not cfg.has_section(module_name): 815 | cfg.add_section(module_name) 816 | cfg.set(module_name, opt_name, value) 817 | 818 | self.stdeb_version = __stdeb_version__ 819 | self.module_name = module_name 820 | self.compat = compat 821 | self.source = parse_val(cfg, module_name, 'Source') 822 | self.package = parse_val(cfg, module_name, 'Package') 823 | self.package3 = parse_val(cfg, module_name, 'Package3') 824 | forced_upstream_version = parse_val(cfg, module_name, 825 | 'Forced-Upstream-Version') 826 | if forced_upstream_version == '': 827 | upstream_version_prefix = parse_val(cfg, module_name, 828 | 'Upstream-Version-Prefix') 829 | upstream_version_suffix = parse_val(cfg, module_name, 830 | 'Upstream-Version-Suffix') 831 | self.upstream_version = (upstream_version_prefix + 832 | debianize_version(upstream_version) + 833 | upstream_version_suffix) 834 | else: 835 | if ( 836 | debianize_version(forced_upstream_version) != 837 | forced_upstream_version 838 | ): 839 | raise ValueError( 840 | 'forced upstream version ("%s") not a ' 841 | 'Debian-compatible version (e.g. "%s")' % ( 842 | forced_upstream_version, 843 | debianize_version(forced_upstream_version))) 844 | self.upstream_version = forced_upstream_version 845 | self.epoch = parse_val(cfg, module_name, 'Epoch') 846 | if self.epoch != '' and not self.epoch.endswith(':'): 847 | self.epoch = self.epoch + ':' 848 | self.packaging_version = parse_val(cfg, module_name, 'Debian-Version') 849 | if debian_version is not None: 850 | # command-line arg overrides file 851 | self.packaging_version = debian_version 852 | self.dsc_version = '%s-%s' % ( 853 | self.upstream_version, 854 | self.packaging_version) 855 | self.full_version = '%s%s-%s' % ( 856 | self.epoch, 857 | self.upstream_version, 858 | self.packaging_version) 859 | self.distname = parse_val(cfg, module_name, 'Suite') 860 | self.distname3 = parse_val(cfg, module_name, 'Suite3') 861 | self.maintainer = ', '.join(parse_vals(cfg, module_name, 'Maintainer')) 862 | self.uploaders = parse_vals(cfg, module_name, 'Uploaders') 863 | self.date822 = get_date_822() 864 | 865 | build_deps = [] 866 | if use_setuptools: 867 | if with_python2: 868 | build_deps.append('python-setuptools (>= 0.6b3)') 869 | if with_python3: 870 | build_deps.append('python3-setuptools') 871 | if setup_requires is not None and len(setup_requires): 872 | build_deps.extend( 873 | get_deb_depends_from_setuptools_requires(setup_requires)) 874 | 875 | depends = ['${misc:Depends}', '${python:Depends}'] 876 | depends3 = ['${misc:Depends}', '${python3:Depends}'] 877 | need_custom_binary_target = False 878 | 879 | if has_ext_modules: 880 | self.architecture = 'any' 881 | if with_python2: 882 | build_deps.append( 883 | 'python-all-dev (>= %s)' % PYTHON_ALL_MIN_VERS) 884 | depends.append('${shlibs:Depends}') 885 | 886 | self.architecture3 = 'any' 887 | if with_python3: 888 | build_deps.append('python3-all-dev') 889 | depends3.append('${shlibs:Depends}') 890 | 891 | else: 892 | self.architecture = 'all' 893 | if with_python2: 894 | build_deps.append('python-all (>= %s)' % PYTHON_ALL_MIN_VERS) 895 | 896 | self.architecture3 = 'all' 897 | if with_python3: 898 | build_deps.append('python3-all') 899 | 900 | self.copyright_file = parse_val(cfg, module_name, 'Copyright-File') 901 | self.mime_file = parse_val(cfg, module_name, 'MIME-File') 902 | 903 | self.shared_mime_file = parse_val(cfg, module_name, 'Shared-MIME-File') 904 | 905 | if self.mime_file == '' and self.shared_mime_file == '': 906 | self.dh_installmime_indep_line = '' 907 | else: 908 | need_custom_binary_target = True 909 | self.dh_installmime_indep_line = '\tdh_installmime' 910 | 911 | mime_desktop_files = parse_vals(cfg, module_name, 'MIME-Desktop-Files') 912 | if len(mime_desktop_files): 913 | need_custom_binary_target = True 914 | 915 | # E. any mime .desktop files 916 | self.install_file_lines = [] 917 | for mime_desktop_file in mime_desktop_files: 918 | self.install_file_lines.append( 919 | '%s usr/share/applications' % mime_desktop_file) 920 | 921 | depends.extend(parse_vals(cfg, module_name, 'Depends')) 922 | depends3.extend(parse_vals(cfg, module_name, 'Depends3')) 923 | self.depends = ', '.join(depends) 924 | self.depends3 = ', '.join(depends3) 925 | 926 | self.debian_section = parse_val(cfg, module_name, 'Section') 927 | 928 | self.description = re.sub(r'\s+', ' ', description).strip() 929 | if long_description != 'UNKNOWN': 930 | ld2 = [] 931 | for line in long_description.split('\n'): 932 | ls = line.strip() 933 | if len(ls): 934 | ld2.append(' '+line) 935 | else: 936 | ld2.append(' .') 937 | ld2 = ld2[:20] 938 | self.long_description = '\n'.join(ld2) 939 | else: 940 | self.long_description = '' 941 | 942 | build_deps.append('debhelper (>= %s)' % DH_MIN_VERS) 943 | 944 | build_deps.extend(parse_vals(cfg, module_name, 'Build-Depends')) 945 | self.build_depends = ', '.join(build_deps) 946 | 947 | suggests = ', '.join(parse_vals(cfg, module_name, 'Suggests')) 948 | suggests3 = ', '.join(parse_vals(cfg, module_name, 'Suggests3')) 949 | recommends = ', '.join(parse_vals(cfg, module_name, 'Recommends')) 950 | recommends3 = ', '.join(parse_vals(cfg, module_name, 'Recommends3')) 951 | 952 | self.source_stanza_extras = '' 953 | 954 | if homepage != 'UNKNOWN': 955 | self.source_stanza_extras += 'Homepage: %s\n' % homepage 956 | 957 | build_conflicts = parse_vals(cfg, module_name, 'Build-Conflicts') 958 | if len(build_conflicts): 959 | self.source_stanza_extras += ('Build-Conflicts: ' + 960 | ', '.join(build_conflicts)+'\n') 961 | 962 | self.patch_file = parse_val(cfg, module_name, 'Stdeb-Patch-File') 963 | 964 | if patch_file is not None: 965 | if self.patch_file != '': 966 | raise RuntimeError('A patch file was specified on the command ' 967 | 'line and in .cfg file.') 968 | else: 969 | self.patch_file = patch_file 970 | 971 | self.patch_level = parse_val(cfg, module_name, 'Stdeb-Patch-Level') 972 | if self.patch_level != '': 973 | if patch_level is not None: 974 | raise RuntimeError('A patch level was specified on the ' 975 | 'command line and in .cfg file.') 976 | else: 977 | self.patch_level = int(self.patch_level) 978 | else: 979 | if patch_level is not None: 980 | self.patch_level = patch_level 981 | else: 982 | self.patch_level = 0 983 | 984 | python2_depends_name = parse_val( 985 | cfg, module_name, 'Python2-Depends-Name') 986 | 987 | xs_python_version = parse_vals(cfg, module_name, 'XS-Python-Version') 988 | 989 | if len(xs_python_version) != 0: 990 | self.source_stanza_extras += ('X-Python-Version: ' + 991 | ', '.join(xs_python_version)+'\n') 992 | 993 | x_python3_version = parse_vals(cfg, module_name, 'X-Python3-Version') 994 | x_python3_version = [v.strip('"') for v in x_python3_version] 995 | 996 | if len(x_python3_version) != 0: 997 | self.source_stanza_extras += ('X-Python3-Version: ' + 998 | ', '.join(x_python3_version)+'\n') 999 | 1000 | dpkg_shlibdeps_params = parse_val( 1001 | cfg, module_name, 'dpkg-shlibdeps-params') 1002 | if dpkg_shlibdeps_params: 1003 | need_custom_binary_target = True 1004 | self.dh_binary_arch_lines = """\tdh binary-arch --before dh_shlibdeps 1005 | \tdh_shlibdeps -a --dpkg-shlibdeps-params=%s 1006 | \tdh binary --after dh_shlibdeps""" % dpkg_shlibdeps_params 1007 | else: 1008 | self.dh_binary_arch_lines = '\tdh binary-arch' 1009 | self.dh_binary_indep_lines = '\tdh binary-indep' 1010 | 1011 | conflicts = parse_vals(cfg, module_name, 'Conflicts') 1012 | conflicts3 = parse_vals(cfg, module_name, 'Conflicts3') 1013 | breaks = parse_vals(cfg, module_name, 'Breaks') 1014 | breaks3 = parse_vals(cfg, module_name, 'Breaks3') 1015 | provides = parse_vals(cfg, module_name, 'Provides') 1016 | provides3 = parse_vals(cfg, module_name, 'Provides3') 1017 | replaces = parse_vals(cfg, module_name, 'Replaces') 1018 | replaces3 = parse_vals(cfg, module_name, 'Replaces3') 1019 | 1020 | if guess_conflicts_provides_replaces: 1021 | # Find list of binaries which we will conflict/provide/replace. 1022 | 1023 | cpr_binaries = set() 1024 | 1025 | # Get original Debian information for the package named the same. 1026 | for version_info in apt_cache_info('showsrc', self.package): 1027 | 1028 | # Remember each of the binary packages produced by the Debian 1029 | # source 1030 | for binary in version_info['Binary']: 1031 | cpr_binaries.add(binary) 1032 | 1033 | # TODO: do this for every version available , just the 1034 | # first, or ??? 1035 | break 1036 | 1037 | # Descend each of the original binaries and see what 1038 | # packages they conflict/ provide/ replace: 1039 | for orig_binary in cpr_binaries: 1040 | for version_info in apt_cache_info('show', orig_binary): 1041 | provides.extend(version_info['Provides']) 1042 | provides3.extend(version_info['Provides']) 1043 | conflicts.extend(version_info['Conflicts']) 1044 | conflicts3.extend(version_info['Conflicts']) 1045 | replaces.extend(version_info['Replaces']) 1046 | replaces3.extend(version_info['Replaces']) 1047 | 1048 | if self.package in cpr_binaries: 1049 | cpr_binaries.remove(self.package) # don't include ourself 1050 | 1051 | cpr_binaries = list(cpr_binaries) # convert to list 1052 | 1053 | conflicts.extend(cpr_binaries) 1054 | conflicts3.extend(cpr_binaries) 1055 | provides.extend(cpr_binaries) 1056 | provides3.extend(cpr_binaries) 1057 | replaces.extend(cpr_binaries) 1058 | replaces3.extend(cpr_binaries) 1059 | 1060 | # round-trip through set to get unique entries 1061 | conflicts = list(set(conflicts)) 1062 | conflicts3 = list(set(conflicts3)) 1063 | provides = list(set(provides)) 1064 | provides3 = list(set(provides3)) 1065 | replaces = list(set(replaces)) 1066 | replaces3 = list(set(replaces3)) 1067 | 1068 | self.package_stanza_extras = '' 1069 | self.package_stanza_extras3 = '' 1070 | 1071 | if len(conflicts): 1072 | self.package_stanza_extras += ( 1073 | 'Conflicts: ' + ', '.join(conflicts)+'\n') 1074 | if len(conflicts3): 1075 | self.package_stanza_extras3 += ( 1076 | 'Conflicts: ' + ', '.join(conflicts3)+'\n') 1077 | 1078 | if len(breaks): 1079 | self.package_stanza_extras += ( 1080 | 'Breaks: ' + ', '.join(breaks)+'\n') 1081 | if len(breaks3): 1082 | self.package_stanza_extras3 += ( 1083 | 'Breaks: ' + ', '.join(breaks3)+'\n') 1084 | if len(provides): 1085 | self.package_stanza_extras += ( 1086 | 'Provides: ' + ', '.join(provides)+'\n') 1087 | 1088 | if len(provides3): 1089 | self.package_stanza_extras3 += ( 1090 | 'Provides: ' + ', '.join(provides3)+'\n') 1091 | 1092 | if len(replaces): 1093 | self.package_stanza_extras += ( 1094 | 'Replaces: ' + ', '.join(replaces)+'\n') 1095 | if len(replaces3): 1096 | self.package_stanza_extras3 += ( 1097 | 'Replaces: ' + ', '.join(replaces3)+'\n') 1098 | if len(recommends): 1099 | self.package_stanza_extras += ('Recommends: ' + recommends+'\n') 1100 | 1101 | if len(recommends3): 1102 | self.package_stanza_extras3 += ('Recommends: ' + recommends3+'\n') 1103 | 1104 | if len(suggests): 1105 | self.package_stanza_extras += ('Suggests: ' + suggests+'\n') 1106 | 1107 | if len(suggests3): 1108 | self.package_stanza_extras3 += ('Suggests: ' + suggests3+'\n') 1109 | 1110 | self.dirlist = "" 1111 | 1112 | if not (with_python2 or with_python3): 1113 | raise RuntimeError('nothing to do - neither Python 2 or 3.') 1114 | 1115 | if with_python2: 1116 | if shutil.which("python"): 1117 | self.python2_binname = "python" 1118 | elif shutil.which("python2"): 1119 | self.python2_binname = "python2" 1120 | else: 1121 | raise RuntimeError("Python 2 binary not found on path as either `python` or `python2`") 1122 | sequencer_with = [] 1123 | if with_python2: 1124 | sequencer_with.append('python2') 1125 | if with_python3: 1126 | sequencer_with.append('python3') 1127 | 1128 | no_script_lines = [] 1129 | 1130 | if no_python2_scripts: 1131 | # install to a location where debian tools do not find them 1132 | self.no_python2_scripts_cli_args = '--install-scripts=/trash' 1133 | no_script_lines.append( 1134 | 'rm -rf debian/%s/trash' % (self.package,)) 1135 | else: 1136 | self.no_python2_scripts_cli_args = '' 1137 | if no_python3_scripts: 1138 | # install to a location where debian tools do not find them 1139 | self.no_python3_scripts_cli_args = '--install-scripts=/trash' 1140 | no_script_lines.append( 1141 | 'rm -rf debian/%s/trash' % (self.package3,)) 1142 | else: 1143 | self.no_python3_scripts_cli_args = '' 1144 | 1145 | self.scripts_cleanup = '\n'.join( 1146 | [' ' + s for s in no_script_lines]) 1147 | 1148 | if sys.prefix != '/usr': 1149 | if not allow_virtualenv_install_location: 1150 | # virtualenv will set distutils 1151 | # --prefix=/path/to/virtualenv, but unless explicitly 1152 | # requested, we want to install into /usr. 1153 | self.install_prefix = '--prefix=/usr' 1154 | else: 1155 | self.install_prefix = '--prefix=%s' % sys.prefix 1156 | else: 1157 | self.install_prefix = '' 1158 | 1159 | rules_override_clean_target_pythons = [] 1160 | rules_override_build_target_pythons = [] 1161 | rules_override_install_target_pythons = [] 1162 | if with_python2: 1163 | rules_override_clean_target_pythons.append( 1164 | RULES_OVERRIDE_CLEAN_TARGET_PY2 % self.__dict__ 1165 | ) 1166 | rules_override_build_target_pythons.append( 1167 | RULES_OVERRIDE_BUILD_TARGET_PY2 % self.__dict__ 1168 | ) 1169 | rules_override_install_target_pythons.append( 1170 | RULES_OVERRIDE_INSTALL_TARGET_PY2 % self.__dict__ 1171 | ) 1172 | if with_python3: 1173 | rules_override_clean_target_pythons.append( 1174 | RULES_OVERRIDE_CLEAN_TARGET_PY3 % self.__dict__ 1175 | ) 1176 | rules_override_build_target_pythons.append( 1177 | RULES_OVERRIDE_BUILD_TARGET_PY3 % self.__dict__ 1178 | ) 1179 | rules_override_install_target_pythons.append( 1180 | RULES_OVERRIDE_INSTALL_TARGET_PY3 % self.__dict__ 1181 | ) 1182 | self.rules_override_clean_target_pythons = \ 1183 | '\n'.join(rules_override_clean_target_pythons) 1184 | self.rules_override_build_target_pythons = \ 1185 | '\n'.join(rules_override_build_target_pythons) 1186 | self.rules_override_install_target_pythons = \ 1187 | '\n'.join(rules_override_install_target_pythons) 1188 | 1189 | self.override_dh_auto_clean = \ 1190 | RULES_OVERRIDE_CLEAN_TARGET % self.__dict__ 1191 | self.override_dh_auto_build = \ 1192 | RULES_OVERRIDE_BUILD_TARGET % self.__dict__ 1193 | self.override_dh_auto_install = \ 1194 | RULES_OVERRIDE_INSTALL_TARGET % self.__dict__ 1195 | 1196 | scripts = '' 1197 | if with_python2 and python2_depends_name: 1198 | scripts = ( 1199 | ' sed -i ' + 1200 | r'"s/\([ =]\)python[0-9]\?\(\(:any\)\? (\)/\\1%s\\2/g" ' + 1201 | 'debian/%s.substvars') % (python2_depends_name, self.package) 1202 | self.override_dh_python2 = RULES_OVERRIDE_PYTHON2 % { 1203 | 'scripts': scripts 1204 | } 1205 | 1206 | if force_x_python3_version and with_python3 and x_python3_version and \ 1207 | x_python3_version[0]: 1208 | # override dh_python3 target to modify the dependencies 1209 | # to ensure that the passed minimum X-Python3-Version is usedby 1210 | version = x_python3_version[0] 1211 | if not version.endswith('~'): 1212 | version += '~' 1213 | self.override_dh_python3 = RULES_OVERRIDE_PYTHON3 % { 1214 | 'scripts': ( 1215 | ' sed -i ' + 1216 | r'"s/\([ =]python3:any (\)>= [^)]*\()\)/\\1%s\\2/g" ' + 1217 | 'debian/%s.substvars') % (version, self.package3) 1218 | } 1219 | else: 1220 | self.override_dh_python3 = '' 1221 | 1222 | sequencer_options = ['--with '+','.join(sequencer_with)] 1223 | 1224 | if with_dh_virtualenv: 1225 | if with_python2: 1226 | self.override_dh_virtualenv_py = \ 1227 | RULES_OVERRIDE_DH_VIRTUALENV_PY2 % self.__dict__ 1228 | if with_python3: 1229 | self.override_dh_virtualenv_py = \ 1230 | RULES_OVERRIDE_DH_VIRTUALENV_PY3 % self.__dict__ 1231 | 1232 | sequencer_options.append('--with python-virtualenv') 1233 | else: 1234 | sequencer_options.append('--buildsystem=python_distutils') 1235 | self.override_dh_virtualenv_py = '' 1236 | 1237 | if with_dh_systemd: 1238 | sequencer_options.append('--with systemd') 1239 | 1240 | self.sequencer_options = ' '.join(sequencer_options) 1241 | 1242 | setup_env_vars = parse_vals(cfg, module_name, 'Setup-Env-Vars') 1243 | self.exports = "" 1244 | if len(setup_env_vars): 1245 | self.exports += '\n' 1246 | self.exports += '#exports specified using stdeb Setup-Env-Vars:\n' 1247 | self.exports += '\n'.join( 1248 | ['export %s' % v for v in setup_env_vars]) 1249 | self.exports += '\n' 1250 | self.udev_rules = parse_val(cfg, module_name, 'Udev-Rules') 1251 | 1252 | if need_custom_binary_target: 1253 | if self.architecture == 'all': 1254 | self.binary_target_lines = ( 1255 | RULES_BINARY_ALL_TARGET % self.__dict__ + 1256 | RULES_BINARY_INDEP_TARGET % self.__dict__) 1257 | else: 1258 | self.binary_target_lines = ( 1259 | RULES_BINARY_TARGET % self.__dict__ + 1260 | RULES_BINARY_INDEP_TARGET % self.__dict__ + 1261 | RULES_BINARY_ARCH_TARGET % self.__dict__) 1262 | else: 1263 | self.binary_target_lines = '' 1264 | 1265 | self.changelog_distname = CHANGELOG_PY2_DISTNAME % self.__dict__ 1266 | if with_python2: 1267 | self.control_py2_stanza = CONTROL_PY2_STANZA % self.__dict__ 1268 | else: 1269 | self.control_py2_stanza = '' 1270 | 1271 | if with_python3: 1272 | self.control_py3_stanza = CONTROL_PY3_STANZA % self.__dict__ 1273 | if self.distname3 and self.distname3 != self.distname: 1274 | if with_python2: 1275 | raise ValueError( 1276 | "Suites are shared between versions. To use a " 1277 | "different value for Suite3 run --with-python3 true " 1278 | "--with-python2 false only.") 1279 | self.changelog_distname = \ 1280 | CHANGELOG_PY3_DISTNAME % self.__dict__ 1281 | else: 1282 | self.control_py3_stanza = '' 1283 | 1284 | self.with_python2 = with_python2 1285 | self.with_python3 = with_python3 1286 | self.no_python2_scripts = no_python2_scripts 1287 | self.no_python3_scripts = no_python3_scripts 1288 | 1289 | def _make_cfg_defaults(self, 1290 | module_name=NotGiven, 1291 | default_distribution=NotGiven, 1292 | guess_maintainer=NotGiven, 1293 | ): 1294 | defaults = {} 1295 | default_re = re.compile(r'^.* \(Default: (.*)\)$') 1296 | for longopt, shortopt, description in stdeb_cfg_options: 1297 | assert longopt.endswith('=') 1298 | assert longopt.lower() == longopt 1299 | key = longopt[:-1] 1300 | matchobj = default_re.search(description) 1301 | if matchobj is not None: 1302 | # has a default value 1303 | groups = matchobj.groups() 1304 | assert len(groups) == 1 1305 | value = groups[0] 1306 | # A few special cases 1307 | if value == '': 1308 | assert key == 'source' 1309 | value = source_debianize_name(module_name) 1310 | elif value == 'python-': 1311 | assert key == 'package' 1312 | value = 'python-' + debianize_name(module_name) 1313 | elif value == 'python3-': 1314 | assert key == 'package3' 1315 | value = 'python3-' + debianize_name(module_name) 1316 | elif value == '': 1317 | assert key == 'maintainer' 1318 | value = guess_maintainer 1319 | elif value == '': 1320 | assert key == 'suite3' 1321 | # Set to empty string so value of suite is used. 1322 | value = '' 1323 | if key == 'suite': 1324 | if default_distribution is not None: 1325 | value = default_distribution 1326 | log.warn('Deprecation warning: you are using the ' 1327 | '--default-distribution option. ' 1328 | 'Switch to the --suite option.') 1329 | else: 1330 | # no default value 1331 | value = '' 1332 | defaults[key] = value 1333 | return defaults 1334 | 1335 | 1336 | def build_dsc(debinfo, 1337 | dist_dir, 1338 | repackaged_dirname, 1339 | orig_sdist=None, 1340 | patch_posix=0, 1341 | remove_expanded_source_dir=0, 1342 | debian_dir_only=False, 1343 | sign_dsc=False, 1344 | sign_key=None, 1345 | ignore_source_changes=False, 1346 | ): 1347 | """make debian source package""" 1348 | # A. Find new dirname and delete any pre-existing contents 1349 | 1350 | # dist_dir is usually 'deb_dist' 1351 | 1352 | # the location of the copied original source package (it was 1353 | # re-recreated in dist_dir) 1354 | if debian_dir_only: 1355 | fullpath_repackaged_dirname = os.path.abspath(os.curdir) 1356 | else: 1357 | fullpath_repackaged_dirname = os.path.join( 1358 | dist_dir, repackaged_dirname) 1359 | 1360 | ############################################### 1361 | # 1. make temporary original source tarball 1362 | 1363 | # Note that, for the final tarball, best practices suggest 1364 | # using "dpkg-source -b". See 1365 | # http://www.debian.org/doc/developers-reference/ch-best-pkging-practices.en.html 1366 | 1367 | # Create the name of the tarball that qualifies as the upstream 1368 | # source. If the original was specified, we'll link to 1369 | # it. Otherwise, we generate our own .tar.gz file from the output 1370 | # of "python setup.py sdist" (done above) so that we avoid 1371 | # packaging .svn directories, for example. 1372 | 1373 | if not debian_dir_only: 1374 | repackaged_orig_tarball = \ 1375 | '%(source)s_%(upstream_version)s.orig.tar.gz' % debinfo.__dict__ 1376 | repackaged_orig_tarball_path = os.path.join(dist_dir, 1377 | repackaged_orig_tarball) 1378 | if orig_sdist is not None: 1379 | if os.path.exists(repackaged_orig_tarball_path): 1380 | os.unlink(repackaged_orig_tarball_path) 1381 | link_func(orig_sdist, repackaged_orig_tarball_path) 1382 | else: 1383 | make_tarball(repackaged_orig_tarball, 1384 | repackaged_dirname, 1385 | cwd=dist_dir) 1386 | 1387 | # apply patch 1388 | if debinfo.patch_file != '': 1389 | apply_patch(debinfo.patch_file, 1390 | posix=patch_posix, 1391 | level=debinfo.patch_level, 1392 | cwd=fullpath_repackaged_dirname) 1393 | 1394 | for fname in ['Makefile', 'makefile']: 1395 | if os.path.exists(os.path.join(fullpath_repackaged_dirname, fname)): 1396 | sys.stderr.write('*'*1000 + '\n') 1397 | sys.stderr.write('WARNING: a Makefile exists in this package. ' 1398 | 'stdeb will tell debhelper 7 to use setup.py ' 1399 | 'to build and install the package, and the ' 1400 | 'Makefile will be ignored.\n') 1401 | sys.stderr.write('*'*1000 + '\n') 1402 | 1403 | ############################################### 1404 | # 2. create debian/ directory and contents 1405 | debian_dir = os.path.join(fullpath_repackaged_dirname, 'debian') 1406 | if not os.path.exists(debian_dir): 1407 | os.mkdir(debian_dir) 1408 | 1409 | # A. debian/changelog 1410 | changelog = CHANGELOG_FILE % debinfo.__dict__ 1411 | with codecs.open(os.path.join(debian_dir, 'changelog'), 1412 | mode='w', encoding='utf-8') as fd: 1413 | fd.write(changelog) 1414 | 1415 | # B. debian/control 1416 | if debinfo.uploaders: 1417 | debinfo.uploaders = 'Uploaders: %s\n' % ', '.join(debinfo.uploaders) 1418 | else: 1419 | debinfo.uploaders = '' 1420 | control = CONTROL_FILE % debinfo.__dict__ 1421 | control = re.sub('\n{3,}', '\n\n', control) 1422 | with codecs.open(os.path.join(debian_dir, 'control'), 1423 | mode='w', encoding='utf-8') as fd: 1424 | fd.write(control) 1425 | 1426 | # C. debian/rules 1427 | debinfo.percent_symbol = '%' 1428 | rules = RULES_MAIN % debinfo.__dict__ 1429 | 1430 | rules = rules.replace(' ', '\t') 1431 | rules = re.sub('\n{3,}', '\n\n', rules) 1432 | rules_fname = os.path.join(debian_dir, 'rules') 1433 | with codecs.open(rules_fname, 1434 | mode='w', encoding='utf-8') as fd: 1435 | fd.write(rules) 1436 | os.chmod(rules_fname, 0o755) 1437 | 1438 | # D. debian/compat 1439 | fd = open(os.path.join(debian_dir, 'compat'), mode='w') 1440 | fd.write('{}\n'.format(str(debinfo.compat))) 1441 | fd.close() 1442 | 1443 | # E. debian/package.mime 1444 | if debinfo.mime_file != '': 1445 | if not os.path.exists(debinfo.mime_file): 1446 | raise ValueError( 1447 | 'a MIME file was specified, but does not exist: %s' % ( 1448 | debinfo.mime_file,)) 1449 | link_func(debinfo.mime_file, 1450 | os.path.join(debian_dir, debinfo.package + '.mime')) 1451 | if debinfo.shared_mime_file != '': 1452 | if not os.path.exists(debinfo.shared_mime_file): 1453 | raise ValueError( 1454 | 'a shared MIME file was specified, but does not exist: %s' % ( 1455 | debinfo.shared_mime_file,)) 1456 | link_func(debinfo.shared_mime_file, 1457 | os.path.join( 1458 | debian_dir, debinfo.package + '.sharedmimeinfo')) 1459 | 1460 | # F. debian/copyright 1461 | if debinfo.copyright_file != '': 1462 | link_func(debinfo.copyright_file, 1463 | os.path.join(debian_dir, 'copyright')) 1464 | 1465 | # H. debian/.install 1466 | if len(debinfo.install_file_lines): 1467 | fd = open( 1468 | os.path.join(debian_dir, '%s.install' % debinfo.package), mode='w') 1469 | fd.write('\n'.join(debinfo.install_file_lines)+'\n') 1470 | fd.close() 1471 | 1472 | # I. debian/.udev 1473 | if debinfo.udev_rules != '': 1474 | fname = debinfo.udev_rules 1475 | if not os.path.exists(fname): 1476 | raise ValueError('udev rules file specified, but does not exist') 1477 | if debinfo.with_python2: 1478 | link_func(fname, 1479 | os.path.join(debian_dir, '%s.udev' % debinfo.package)) 1480 | if debinfo.with_python3: 1481 | link_func(fname, 1482 | os.path.join(debian_dir, '%s.udev' % debinfo.package3)) 1483 | 1484 | # J. debian/source/format 1485 | os.mkdir(os.path.join(debian_dir, 'source')) 1486 | fd = open(os.path.join(debian_dir, 'source', 'format'), mode='w') 1487 | fd.write('3.0 (quilt)\n') 1488 | fd.close() 1489 | 1490 | fd = open(os.path.join(debian_dir, 'source', 'options'), mode='w') 1491 | fd.write(r'extend-diff-ignore="\.egg-info$"') 1492 | fd.close() 1493 | 1494 | if debian_dir_only: 1495 | return 1496 | 1497 | ############################################### 1498 | # 3. unpack original source tarball 1499 | 1500 | debianized_package_dirname = fullpath_repackaged_dirname+'.debianized' 1501 | if os.path.exists(debianized_package_dirname): 1502 | raise RuntimeError('debianized_package_dirname exists: %s' % 1503 | debianized_package_dirname) 1504 | # A. move debianized tree away 1505 | os.rename(fullpath_repackaged_dirname, debianized_package_dirname) 1506 | if orig_sdist is not None: 1507 | # B. expand repackaged original tarball 1508 | tmp_dir = os.path.join(dist_dir, 'tmp-expand') 1509 | os.mkdir(tmp_dir) 1510 | try: 1511 | expand_tarball(orig_sdist, cwd=tmp_dir) 1512 | orig_tarball_top_contents = os.listdir(tmp_dir) 1513 | 1514 | # make sure original tarball has exactly one directory 1515 | assert len(orig_tarball_top_contents) == 1 1516 | orig_dirname = orig_tarball_top_contents[0] 1517 | fullpath_orig_dirname = os.path.join(tmp_dir, orig_dirname) 1518 | 1519 | # C. remove original repackaged tree 1520 | shutil.rmtree(fullpath_orig_dirname) 1521 | 1522 | finally: 1523 | shutil.rmtree(tmp_dir) 1524 | 1525 | if 1: 1526 | # check versions of debhelper and python-all 1527 | debhelper_version_str = get_version_str('debhelper') 1528 | if len(debhelper_version_str) == 0: 1529 | log.warn('This version of stdeb requires debhelper >= %s, but you ' 1530 | 'do not have debhelper installed. ' 1531 | 'Could not check compatibility.' % DH_MIN_VERS) 1532 | else: 1533 | if not dpkg_compare_versions( 1534 | debhelper_version_str, 'ge', DH_MIN_VERS 1535 | ): 1536 | log.warn('This version of stdeb requires debhelper >= %s. ' 1537 | 'Use stdeb 0.9.x to generate source packages ' 1538 | 'compatible with older versions of debhelper.' % ( 1539 | DH_MIN_VERS,)) 1540 | 1541 | if debinfo.with_python2: 1542 | python_defaults_version_str = get_version_str('python-all') 1543 | 1544 | if len(python_defaults_version_str) == 0: 1545 | log.warn('This version of stdeb requires python-all >= %s, ' 1546 | 'but you do not have this package installed. Could ' 1547 | 'not check compatibility.' % PYTHON_ALL_MIN_VERS) 1548 | else: 1549 | if not dpkg_compare_versions( 1550 | python_defaults_version_str, 'ge', PYTHON_ALL_MIN_VERS 1551 | ): 1552 | log.warn('This version of stdeb requires python-all >= ' 1553 | '%s. Use stdeb 0.6.0 or older to generate source ' 1554 | 'packages that use python-support.' % ( 1555 | PYTHON_ALL_MIN_VERS,)) 1556 | 1557 | if debinfo.with_python3: 1558 | python3_defaults_version_str = get_version_str('python3-all') 1559 | 1560 | if len(python3_defaults_version_str) == 0: 1561 | log.warn('This version of stdeb requires python3-all, ' 1562 | 'but you do not have this package installed.') 1563 | 1564 | # D. restore debianized tree 1565 | os.rename(fullpath_repackaged_dirname+'.debianized', 1566 | fullpath_repackaged_dirname) 1567 | 1568 | # Re-generate tarball using best practices see 1569 | # http://www.debian.org/doc/developers-reference/ch-best-pkging-practices.en.html 1570 | 1571 | args = ['-S', '-sa'] 1572 | 1573 | if not sign_dsc: 1574 | args += ['-uc', '-us'] 1575 | elif sign_key is not None: 1576 | args += ['--sign-key={}'.format(sign_key)] 1577 | 1578 | if ignore_source_changes: 1579 | args.append('-i.*') 1580 | 1581 | dpkg_buildpackage(*args, cwd=fullpath_repackaged_dirname) 1582 | 1583 | if 1: 1584 | shutil.rmtree(fullpath_repackaged_dirname) 1585 | 1586 | if not remove_expanded_source_dir: 1587 | # expand the debian source package 1588 | dsc_name = debinfo.source + '_' + debinfo.dsc_version + '.dsc' 1589 | dpkg_source('-x', dsc_name, 1590 | cwd=dist_dir) 1591 | 1592 | 1593 | CHANGELOG_PY2_DISTNAME = '%(distname)s' 1594 | CHANGELOG_PY3_DISTNAME = '%(distname3)s' 1595 | CHANGELOG_FILE = """\ 1596 | %(source)s (%(full_version)s) %(changelog_distname)s; urgency=low 1597 | 1598 | * source package automatically created by stdeb %(stdeb_version)s 1599 | 1600 | -- %(maintainer)s %(date822)s\n""" 1601 | 1602 | CONTROL_FILE = """\ 1603 | Source: %(source)s 1604 | Maintainer: %(maintainer)s 1605 | %(uploaders)sSection: %(debian_section)s 1606 | Priority: optional 1607 | Build-Depends: %(build_depends)s 1608 | Standards-Version: 3.9.1 1609 | %(source_stanza_extras)s 1610 | 1611 | %(control_py2_stanza)s 1612 | 1613 | %(control_py3_stanza)s 1614 | """ 1615 | 1616 | CONTROL_PY2_STANZA = """ 1617 | Package: %(package)s 1618 | Architecture: %(architecture)s 1619 | Depends: %(depends)s 1620 | %(package_stanza_extras)sDescription: %(description)s 1621 | %(long_description)s 1622 | """ 1623 | 1624 | CONTROL_PY3_STANZA = """ 1625 | Package: %(package3)s 1626 | Architecture: %(architecture3)s 1627 | Depends: %(depends3)s 1628 | %(package_stanza_extras3)sDescription: %(description)s 1629 | %(long_description)s 1630 | """ 1631 | 1632 | RULES_MAIN = """\ 1633 | #!/usr/bin/make -f 1634 | 1635 | # This file was automatically generated by stdeb %(stdeb_version)s at 1636 | # %(date822)s 1637 | %(exports)s 1638 | %(percent_symbol)s: 1639 | dh $@ %(sequencer_options)s 1640 | 1641 | %(override_dh_auto_clean)s 1642 | 1643 | %(override_dh_auto_build)s 1644 | 1645 | %(override_dh_auto_install)s 1646 | 1647 | %(override_dh_python2)s 1648 | 1649 | %(override_dh_python3)s 1650 | 1651 | %(override_dh_virtualenv_py)s 1652 | 1653 | %(binary_target_lines)s 1654 | """ 1655 | 1656 | RULES_OVERRIDE_CLEAN_TARGET_PY2 = " %(python2_binname)s setup.py clean -a" 1657 | RULES_OVERRIDE_CLEAN_TARGET_PY3 = " python3 setup.py clean -a" 1658 | RULES_OVERRIDE_CLEAN_TARGET = r""" 1659 | override_dh_auto_clean: 1660 | %(rules_override_clean_target_pythons)s 1661 | find . -name \*.pyc -exec rm {} \; 1662 | """ 1663 | 1664 | RULES_OVERRIDE_BUILD_TARGET_PY2 = " %(python2_binname)s setup.py build --force" 1665 | RULES_OVERRIDE_BUILD_TARGET_PY3 = " python3 setup.py build --force" 1666 | RULES_OVERRIDE_BUILD_TARGET = """ 1667 | override_dh_auto_build: 1668 | %(rules_override_build_target_pythons)s 1669 | """ 1670 | 1671 | RULES_OVERRIDE_INSTALL_TARGET_PY2 = " %(python2_binname)s setup.py install --force --root=debian/%(package)s --no-compile -O0 --install-layout=deb %(install_prefix)s %(no_python2_scripts_cli_args)s" # noqa: E501 1672 | 1673 | RULES_OVERRIDE_INSTALL_TARGET_PY3 = " python3 setup.py install --force --root=debian/%(package3)s --no-compile -O0 --install-layout=deb %(install_prefix)s %(no_python3_scripts_cli_args)s" # noqa: E501 1674 | 1675 | RULES_OVERRIDE_INSTALL_TARGET = """ 1676 | override_dh_auto_install: 1677 | %(rules_override_install_target_pythons)s 1678 | %(scripts_cleanup)s 1679 | """ 1680 | 1681 | RULES_OVERRIDE_PYTHON2 = """ 1682 | override_dh_python2: 1683 | dh_python2 --no-guessing-versions 1684 | %(scripts)s 1685 | """ 1686 | RULES_OVERRIDE_PYTHON3 = """ 1687 | override_dh_python3: 1688 | dh_python3 1689 | %(scripts)s 1690 | """ 1691 | 1692 | RULES_OVERRIDE_DH_VIRTUALENV_PY2 = """ 1693 | override_dh_virtualenv: 1694 | dh_virtualenv --python python2 1695 | """ 1696 | 1697 | RULES_OVERRIDE_DH_VIRTUALENV_PY3 = """ 1698 | override_dh_virtualenv: 1699 | dh_virtualenv --python python3 1700 | """ 1701 | 1702 | RULES_BINARY_TARGET = """ 1703 | binary: binary-arch binary-indep 1704 | """ 1705 | 1706 | RULES_BINARY_ALL_TARGET = """ 1707 | binary: binary-indep 1708 | """ 1709 | 1710 | RULES_BINARY_ARCH_TARGET = """ 1711 | binary-arch: build 1712 | %(dh_binary_arch_lines)s 1713 | """ 1714 | 1715 | RULES_BINARY_INDEP_TARGET = """ 1716 | binary-indep: build 1717 | %(dh_binary_indep_lines)s 1718 | %(dh_installmime_indep_line)s 1719 | """ 1720 | -------------------------------------------------------------------------------- /test-pypi-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$UID" -ne "0" ]; then 5 | echo "$0 must be run as root" 6 | exit 1 7 | fi 8 | 9 | # Package with source tarball on PyPI: 10 | pypi-install pyflakes --verbose=2 11 | dpkg --purge python-pyflakes 12 | 13 | # This test fails on Ubuntu 12.04 due to what looks like a bug with 14 | # "dh_auto_clean -O--buildsystem=python_distutils" not changing into the 15 | # directory with setup.py and thus its "import prober" fails. That's not 16 | # an stdeb bug. We could run this test on later versions of Debian/Ubuntu. 17 | # 18 | # Package with no source tarball on PyPI: (v 0.6.2, 2009-12-30) 19 | #pypi-install posix_ipc --release=0.6.2 --verbose=2 --allow-unsafe-download 20 | #dpkg --purge python-posixipc 21 | 22 | echo "skipping known failure tests" 23 | exit 0 24 | 25 | # A pure python package with source tarball on PyPI. (This fails if 26 | # the Debian/Ubuntu original "pyro" package is already 27 | # installed. This should use apt-file to find that binary package is 28 | # "pyro".) 29 | apt-get install pyro # get upstream version 30 | pypi-install Pyro --verbose=2 31 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # setup tests 5 | 6 | ## remove old build results 7 | rm -rf deb_dist 8 | 9 | # setup paths 10 | 11 | if [ "${PYEXE}" == "" ]; then 12 | PYEXE=`which python`; 13 | fi 14 | 15 | echo "using Python at ${PYEXE}" 16 | 17 | PY2DSC_LOC=`which py2dsc` 18 | PY2DSC_DEB_LOC=`which py2dsc-deb` 19 | PYPI_DOWNLOAD_LOC=`which pypi-download` 20 | PYPI_INSTALL_LOC=`which pypi-install` 21 | 22 | PY2DSC="${PYEXE} ${PY2DSC_LOC}" 23 | PY2DSC_DEB="${PYEXE} ${PY2DSC_DEB_LOC}" 24 | PYPI_DOWNLOAD="${PYEXE} ${PYPI_DOWNLOAD_LOC}" 25 | PYPI_INSTALL="${PYEXE} ${PYPI_INSTALL_LOC}" 26 | 27 | # Run tests 28 | 29 | ## Test some basic tests. Just make sure these don't fail. 30 | 31 | ${PY2DSC} --help > /dev/null 32 | ${PY2DSC_DEB} --help > /dev/null 33 | ${PYPI_DOWNLOAD} --help > /dev/null 34 | ${PYPI_INSTALL} --help > /dev/null 35 | 36 | ## Run test cases on each of the following packages 37 | 38 | # Set an upper bound on the size of the compressed deb_specific. We are not 39 | # applying any patches here so this should be pretty small. 40 | MAX_DEB_SPECIFIC_SIZE=5000 41 | 42 | for i in `seq 1 3`; do 43 | if [ $i -eq "1" ]; then 44 | SOURCE_PACKAGE=requests 45 | SOURCE_RELEASE=2.6.0 46 | SOURCE_TARBALL_DIR=${SOURCE_PACKAGE}-${SOURCE_RELEASE} 47 | SOURCE_TARBALL=${SOURCE_TARBALL_DIR}.tar.gz 48 | DEBSOURCE=${SOURCE_TARBALL_DIR} 49 | elif [ $i -eq "2" ]; then 50 | SOURCE_PACKAGE=Reindent 51 | SOURCE_RELEASE=0.1.1 52 | SOURCE_TARBALL_DIR=${SOURCE_PACKAGE}-${SOURCE_RELEASE} 53 | SOURCE_TARBALL=${SOURCE_TARBALL_DIR}.tar.gz 54 | DEBSOURCE=reindent-${SOURCE_RELEASE} 55 | elif [ $i -eq "3" ]; then 56 | SOURCE_PACKAGE=psycopg2 57 | SOURCE_RELEASE=2.9.9 58 | SOURCE_TARBALL_DIR=${SOURCE_PACKAGE}-${SOURCE_RELEASE} 59 | SOURCE_TARBALL=${SOURCE_TARBALL_DIR}.tar.gz 60 | DEBSOURCE=${SOURCE_TARBALL_DIR} 61 | else 62 | echo "unknown case" 63 | exit 1 64 | fi 65 | 66 | export DEB_BUILD_OPTIONS=nocheck # psycopg2 tests fail 67 | 68 | # get a file to work with 69 | # ============================================================== 70 | ${PYPI_DOWNLOAD} ${SOURCE_PACKAGE} --release ${SOURCE_RELEASE} 71 | 72 | # case 1: build from pre-existing source tarball with py2dsc 73 | # ============================================================== 74 | ${PY2DSC} $SOURCE_TARBALL 75 | 76 | cd deb_dist/$DEBSOURCE 77 | dpkg-buildpackage -rfakeroot -uc -us 78 | cd ../.. 79 | for DEBFILE in deb_dist/*.deb; do 80 | echo "contents of $DEBFILE from $SOURCE_TARBALL in case 1:" 81 | dpkg --contents $DEBFILE 82 | done 83 | DEB_SPECIFIC_SIZE=$(stat -c '%s' deb_dist/*.debian.tar.?z) 84 | if ((${DEB_SPECIFIC_SIZE}>${MAX_DEB_SPECIFIC_SIZE})); then 85 | echo "ERROR: debian specific file larger than expected" 86 | exit 1 87 | else 88 | echo "${SOURCE_PACKAGE} case 1: deb_specific size ${DEB_SPECIFIC_SIZE}" 89 | fi 90 | 91 | #cleanup case 1 92 | rm -rf deb_dist 93 | 94 | # case 2: build from pre-existing source tarball using distutils 95 | # ============================================================== 96 | tar xzf $SOURCE_TARBALL 97 | cd $SOURCE_TARBALL_DIR 98 | which python3 99 | python3 -c "import sys; print('sys.version',sys.version)" 100 | python3 setup.py --command-packages=stdeb.command sdist_dsc 101 | cd deb_dist/$DEBSOURCE 102 | dpkg-buildpackage -rfakeroot -uc -us 103 | cd ../.. 104 | for DEBFILE in deb_dist/*.deb; do 105 | echo "contents of $DEBFILE from $SOURCE_TARBALL in case 2:" 106 | dpkg --contents $DEBFILE 107 | done 108 | DEB_SPECIFIC_SIZE=$(stat -c '%s' deb_dist/*.debian.tar.?z) 109 | if ((${DEB_SPECIFIC_SIZE}>${MAX_DEB_SPECIFIC_SIZE})); then 110 | echo "ERROR: debian specific file larger than expected" 111 | exit 1 112 | else 113 | echo "${SOURCE_PACKAGE} case 2: deb_specific size ${DEB_SPECIFIC_SIZE}" 114 | fi 115 | cd .. 116 | 117 | #cleanup case 2 118 | # ============================================================== 119 | rm -rf $SOURCE_TARBALL_DIR 120 | 121 | 122 | # case 3: build from pre-existing source tarball with py2dsc 123 | # ============================================================== 124 | ${PY2DSC_DEB} $SOURCE_TARBALL 125 | 126 | for DEBFILE in deb_dist/*.deb; do 127 | echo "contents of $DEBFILE from $SOURCE_TARBALL in case 3:" 128 | dpkg --contents $DEBFILE 129 | done 130 | DEB_SPECIFIC_SIZE=$(stat -c '%s' deb_dist/*.debian.tar.?z) 131 | if ((${DEB_SPECIFIC_SIZE}>${MAX_DEB_SPECIFIC_SIZE})); then 132 | echo "ERROR: debian specific file larger than expected" 133 | exit 1 134 | else 135 | echo "${SOURCE_PACKAGE} case 3: deb_specific size ${DEB_SPECIFIC_SIZE}" 136 | fi 137 | 138 | #cleanup case 3 139 | rm -rf deb_dist 140 | 141 | 142 | #cleanup original tarball 143 | rm -rf $SOURCE_TARBALL 144 | 145 | done 146 | 147 | echo "All tests passed." 148 | -------------------------------------------------------------------------------- /test2and3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export DO_PY2=true 5 | export DO_PY3=true 6 | 7 | # setup paths 8 | 9 | if [ "${PY2EXE}" == "" ]; then 10 | PY2EXE=`which python2` || echo 11 | if [ "${PY2EXE}" == "" ]; then 12 | PY2EXE=`which python` || export DO_PY2=false 13 | fi 14 | fi 15 | 16 | if [ "${PY3EXE}" == "" ]; then 17 | PY3EXE=`which python3` || export DO_PY3=false 18 | fi 19 | 20 | # check that stdeb is actually installed 21 | cd test_data # do test in path without stdeb source 22 | if [ "$DO_PY2" = true ]; then 23 | ${PY2EXE} -c "import stdeb; print stdeb.__version__,stdeb.__file__" || export DO_PY2=false 24 | fi 25 | 26 | if [ "$DO_PY3" = true ]; then 27 | ${PY3EXE} -c "import stdeb; print(stdeb.__version__,stdeb.__file__)" || export DO_PY3=false 28 | fi 29 | cd .. 30 | 31 | 32 | # -------------- 33 | 34 | PYTHONS="" 35 | if [ "$DO_PY2" = true ]; then 36 | PYTHONS="${PYTHONS} ${PY2EXE}" 37 | fi 38 | 39 | if [ "$DO_PY3" = true ]; then 40 | PYTHONS="${PYTHONS} ${PY3EXE}" 41 | fi 42 | 43 | ## ---------------------------- 44 | 45 | # Test unicode in CLI args and .cfg file 46 | 47 | for MAINTAINER_ARGS in cli none cfgfile; do 48 | for PYTHON in ${PYTHONS}; do 49 | 50 | echo ${PYTHON} MAINTAINER_ARGS ${MAINTAINER_ARGS} 51 | 52 | if [ "$MAINTAINER_ARGS" = cli ]; then 53 | M1="--maintainer" 54 | M2="Herr Unicöde " 55 | M3="${M1},${M2}" 56 | else 57 | M3="" 58 | fi 59 | 60 | rm -f test_data/simple_pkg/stdeb.cfg 61 | 62 | if [ "$MAINTAINER_ARGS" = cfgfile ]; then 63 | cat > test_data/simple_pkg/stdeb.cfg < 66 | EOF 67 | fi 68 | 69 | cd test_data/simple_pkg 70 | IFS=, 71 | ${PYTHON} setup.py --command-packages stdeb.command sdist_dsc ${M3} bdist_deb 72 | unset IFS 73 | cd ../.. 74 | done 75 | done 76 | 77 | ## ------------------------- 78 | 79 | ## Tell Python that we do not have e.g. UTF-8 file encodings and thus 80 | ## force everything to be very explicit. 81 | export LC_ALL="C" 82 | 83 | ## Test very basic py2 and py3 packages ------ 84 | 85 | if [ "$DO_PY2" = true ]; then 86 | echo "using Python 2 at ${PY2EXE}" 87 | cd test_data/py2_only_pkg 88 | 89 | # test the "debianize" command 90 | rm -rf debian 91 | ${PY2EXE} setup.py --command-packages stdeb.command debianize 92 | rm -rf debian 93 | 94 | # test the "sdist_dsc" and "bdist_deb" commands 95 | ${PY2EXE} setup.py --command-packages stdeb.command sdist_dsc --with-python2=true --with-python3=false bdist_deb 96 | cd ../.. 97 | else 98 | echo "skipping Python 2 test" 99 | fi 100 | 101 | 102 | if [ "$DO_PY3" = true ]; then 103 | # Due to http://bugs.python.org/issue9561 (fixed in Py 3.2) we skip this test in 3.0 and 3.1. 104 | ${PY3EXE} -c "import sys; ec=0 if sys.version_info[1]>=2 else 1; sys.exit(ec)" && rc=$? || rc=$? 105 | 106 | if [ "$rc" == 0 ]; then 107 | 108 | echo "using Python 3 at ${PY3EXE}" 109 | cd test_data/py3_only_pkg 110 | 111 | # test the "debianize" command 112 | rm -rf debian 113 | ${PY3EXE} setup.py --command-packages stdeb.command debianize 114 | rm -rf debian 115 | 116 | # test the "sdist_dsc" and "bdist_deb" commands 117 | ${PY3EXE} setup.py --command-packages stdeb.command sdist_dsc --with-python3=true --with-python2=false bdist_deb 118 | cd ../.. 119 | 120 | echo "using Python 3 to test 2 and 3 generation" 121 | cd test_data/simple_pkg 122 | ${PY3EXE} setup.py --command-packages stdeb.command sdist_dsc --with-python3=true --with-python2=true bdist_deb 123 | cd ../.. 124 | else 125 | echo "skipping Python >= 3.2 test" 126 | fi 127 | else 128 | echo "skipping Python 3 test" 129 | fi 130 | -------------------------------------------------------------------------------- /test_data/py2_only_pkg/py2_only_pkg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astraw/stdeb/8d018c9cd5703b35a7745cc693269c661071c346/test_data/py2_only_pkg/py2_only_pkg/__init__.py -------------------------------------------------------------------------------- /test_data/py2_only_pkg/py2_only_pkg/py2_module.py: -------------------------------------------------------------------------------- 1 | def my_py2_function(value): 2 | """this is a function that works only in python 2""" 3 | if False: 4 | raise Exception, 'this is a SyntaxError in py3 but not 2' # noqa 5 | return value+value 6 | -------------------------------------------------------------------------------- /test_data/py2_only_pkg/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from distutils.core import setup 4 | from distutils.command.build import build 5 | import sys 6 | 7 | 8 | class my_build(build): 9 | """ensure (at runtime) we are running python 2""" 10 | def __init__(self, *args, **kwargs): 11 | assert sys.version_info[0] == 2 12 | build.__init__(self, *args, **kwargs) 13 | 14 | 15 | setup( 16 | name='py2_only_pkg', 17 | packages=['py2_only_pkg'], 18 | version='0.1', 19 | cmdclass={'build': my_build}, 20 | author='Mister Unicodé', 21 | author_email='mister.unicode@example.tld', 22 | description='Python 2 package with Unicodé fields', 23 | long_description='This is a Python 2 package with Unicodé data.', 24 | ) 25 | -------------------------------------------------------------------------------- /test_data/py3_only_pkg/py3_only_pkg/py3_module.py: -------------------------------------------------------------------------------- 1 | def my_py3_function(value: int = 2) -> int: # SyntaxError in py2 (but not 3) 2 | """this is a function that works only in python 3""" 3 | return value+value 4 | -------------------------------------------------------------------------------- /test_data/py3_only_pkg/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from distutils.core import setup 4 | from distutils.command.build import build 5 | import sys 6 | 7 | 8 | class my_build(build): 9 | """ensure (at runtime) we are running python 3""" 10 | def __init__(self, *args, **kwargs): 11 | assert sys.version_info[0] == 3 12 | build.__init__(self, *args, **kwargs) 13 | 14 | 15 | setup( 16 | name='py3_only_pkg', 17 | packages=['py3_only_pkg'], 18 | version='0.1', 19 | cmdclass={'build': my_build}, 20 | author='Mister Unicodé', 21 | author_email='mister.unicode@example.tld', 22 | description='Python 3 package with Unicodé fields', 23 | long_description='This is a Python 3 package with Unicodé data.', 24 | ) 25 | -------------------------------------------------------------------------------- /test_data/simple_pkg/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from distutils.core import setup 4 | 5 | setup(name='simple_pkg', 6 | packages=['simple_pkg'], 7 | version='0.1', 8 | author='Mister Unicodé', 9 | author_email='mister.unicode@example.tld', 10 | description='Python package with Unicodé fields', 11 | long_description='This is a Python package with Unicodé data.', 12 | ) 13 | -------------------------------------------------------------------------------- /test_data/simple_pkg/simple_pkg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astraw/stdeb/8d018c9cd5703b35a7745cc693269c661071c346/test_data/simple_pkg/simple_pkg/__init__.py --------------------------------------------------------------------------------