diff --git a/.github/workflows/disperse.yml b/.github/workflows/disperse.yml deleted file mode 100644 index b65bb01..0000000 --- a/.github/workflows/disperse.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Disperse configuration - -"on": - - push - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - - name: Install dependencies - run: | - sudo apt install protobuf-compiler - - name: Install disperse - run: | - pip install git+https://github.com/jelmer/disperse - - name: Validate disperse.conf - run: | - PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python disperse validate . diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 1d6b7d4..0725c4c 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -1,11 +1,6 @@ ---- name: Python package -"on": - push: - pull_request: - schedule: - - cron: '0 6 * * *' # Daily 6AM UTC build +on: [push, pull_request] jobs: build: @@ -14,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: [3.7, 3.8, 3.9, '3.10'] + python-version: [3.7, 3.8] fail-fast: false steps: @@ -25,28 +20,28 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install -e ".[remote,dep_server,dev]" + python -m pip install --upgrade pip flake8 cython python setup.py develop - name: Install Debian-specific dependencies run: | - sudo apt update - sudo apt install python3-wheel libapt-pkg-dev - python -m pip install \ - python_apt@git+https://salsa.debian.org/apt-team/python-apt.git + sudo apt install libapt-pkg-dev + python -m pip install wheel + python -m pip install git+https://salsa.debian.org/apt-team/python-apt python -m pip install -e ".[debian]" + python -m pip install testtools + mkdir -p ~/.config/breezy/plugins + brz branch lp:brz-debian ~/.config/breezy/plugins/debian if: "matrix.python-version != 'pypy3' && matrix.os == 'ubuntu-latest'" - name: Style checks run: | - pip install flake8 python -m flake8 - name: Typing checks run: | - pip install -U mypy types-toml + pip install -U mypy python -m mypy ognibuild if: "matrix.python-version != 'pypy3'" - name: Test suite run run: | - python -m unittest tests.test_suite + python -m unittest ognibuild.tests.test_suite env: PYTHONHASHSEED: random diff --git a/.gitignore b/.gitignore index 9bbe794..13818ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.coverage build *~ ognibuild.egg-info diff --git a/Makefile b/Makefile deleted file mode 100644 index 0297c36..0000000 --- a/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -check:: style - -style: - flake8 - -check:: testsuite - -testsuite: - python3 -m unittest tests.test_suite - -check:: typing - -typing: - mypy ognibuild tests - -coverage: - python3 -m coverage run -m unittest tests.test_suite - -coverage-html: - python3 -m coverage html diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..8ab0d39 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,17 @@ +Metadata-Version: 2.1 +Name: ognibuild +Version: 0.0.7 +Summary: Detect and run any build system +Home-page: https://jelmer.uk/code/ognibuild +Maintainer: Jelmer Vernooij +Maintainer-email: jelmer@jelmer.uk +License: GNU GPLv2 or later +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Operating System :: POSIX +Provides-Extra: debian diff --git a/README.md b/README.md index b9e493f..5374ac2 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,6 @@ Ognibuild has a number of subcommands: It also includes a subcommand that can fix up the build dependencies for Debian packages, called deb-fix-build. -### Examples - -``` -ogni -d https://gitlab.gnome.org/GNOME/fractal install -``` - ## Status Ognibuild is functional, but sometimes rough around the edges. If you run into diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index 45290ce..0000000 --- a/debian/changelog +++ /dev/null @@ -1,62 +0,0 @@ -ognibuild (0.0.15-0.1) UNRELEASED; urgency=low - - * Non-maintainer upload. - * New upstream release. - * Bump debhelper from old 12 to 13. - * Update standards version to 4.6.1, no changes needed. - - -- Tianyu Chen Tue, 22 Nov 2022 11:12:48 +0800 - -ognibuild (0.0.7-1) UNRELEASED; urgency=low - - * New upstream release. - - -- Debian Janitor Wed, 02 Jun 2021 15:12:48 -0000 - -ognibuild (0.0.6+git20210517.1.8189e91-1) unstable; urgency=low - - * New upstream snapshot. - - -- Jelmer Vernooij Tue, 18 May 2021 20:53:15 +0100 - -ognibuild (0.0.5-1) unstable; urgency=low - - * New upstream release. - + Fixes cmake support. Closes: #988572 - + Preserve environment when building Python packages. Closes: #988571 - - -- Jelmer Vernooij Tue, 18 May 2021 01:34:11 +0100 - -ognibuild (0.0.4-1) unstable; urgency=low - - * New upstream release. - - -- Jelmer Vernooij Wed, 07 Apr 2021 00:11:09 +0100 - -ognibuild (0.0.3-1) unstable; urgency=medium - - * Add missing dependency on python3-lz4. - * Set upstream metadata fields: Security-Contact. - * Add upstream signing keys. - * New upstream release. - - -- Jelmer Vernooij Sat, 27 Mar 2021 17:49:29 +0000 - -ognibuild (0.0.2-1) unstable; urgency=medium - - * New upstream release. - - -- Jelmer Vernooij Tue, 02 Mar 2021 18:25:45 +0000 - -ognibuild (0.0.1~git20210228.bc79314-1) unstable; urgency=medium - - * New upstream snapshot. - * Add dependency on buildlog-consultant. - - -- Jelmer Vernooij Sun, 28 Feb 2021 14:55:03 +0000 - -ognibuild (0.0.1~git20201031.4cbc8df-1) unstable; urgency=low - - * Initial release. Closes: #981913 - - -- Jelmer Vernooij Fri, 05 Feb 2021 03:00:40 +0000 diff --git a/debian/control b/debian/control deleted file mode 100644 index 22fa4ba..0000000 --- a/debian/control +++ /dev/null @@ -1,43 +0,0 @@ -Rules-Requires-Root: no -Standards-Version: 4.6.1 -Build-Depends: debhelper-compat (= 13), - dh-sequence-python3, - python3-all, - python3-apt, - python3-breezy, - python3-breezy.tests, - python3-buildlog-consultant (>= 0.0.4), - python3-requirement-parser, - python3-debmutate, - python3-lz4, - python3-setuptools, - brz-debian, - python3-dulwich (>= 0.19.12) -Source: ognibuild -Priority: optional -Section: devel -Maintainer: Jelmer Vernooij -Vcs-Git: https://salsa.debian.org/jelmer/ognibuild.git -Vcs-Browser: https://salsa.debian.org/jelmer/ognibuild -Homepage: https://github.com/jelmer/ognibuild - -Package: ognibuild -Architecture: all -Depends: python3-apt, - python3-breezy, - python3-buildlog-consultant (>= 0.0.4), - python3-lz4, - python3-requirement-parser, - python3-toml, - ${misc:Depends}, - ${python3:Depends}, -Recommends: brz-debian, python3-debmutate -Description: Detect and run any build system - Ognibuild is a simple wrapper with a common interface for invoking any kind of - build tool. - . - The ideas is that it can be run to build and install any source code directory - by detecting the build system that is in use and invoking that with the correct - parameters. - . - It can also detect and install missing dependencies. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index fbf06a4..0000000 --- a/debian/copyright +++ /dev/null @@ -1,28 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: ognibuild - -Files: * -Copyright: Jelmer Vernooij -License: GPL-2+ - -Files: debian/* -Copyright: Jelmer Vernooij -License: GPL-2+ - -License: GPL-2+ - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - . - On Debian systems, the full text of the GNU General Public License is available - in /usr/share/common-licenses/GPL-2. diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 11cd9df..0000000 --- a/debian/rules +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/make -f - -%: - dh $@ --buildsystem=pybuild diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 163aaf8..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/debian/tests/control b/debian/tests/control deleted file mode 100644 index dcef085..0000000 --- a/debian/tests/control +++ /dev/null @@ -1,5 +0,0 @@ -Test-Command: python3 -m unittest ognibuild.tests.test_suite -Depends: python3, - @, - lintian-brush, brz-debian, python3-breezy.tests -Restrictions: allow-stderr diff --git a/debian/upstream/metadata b/debian/upstream/metadata deleted file mode 100644 index 6a60ea0..0000000 --- a/debian/upstream/metadata +++ /dev/null @@ -1,6 +0,0 @@ ---- -Bug-Database: https://github.com/jelmer/ognibuild/issues -Bug-Submit: https://github.com/jelmer/ognibuild/issues/new -Repository: https://github.com/jelmer/ognibuild.git -Repository-Browse: https://github.com/jelmer/ognibuild -Security-Contact: https://github.com/jelmer/ognibuild/tree/HEAD/SECURITY.md diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc deleted file mode 100644 index 32b347e..0000000 --- a/debian/upstream/signing-key.asc +++ /dev/null @@ -1,858 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -opbe/Bbo8mHXG9cKATCsqOGI0/BY9W036WEwadcy24RoZ/DhiJh/+c8Py9u3ZYdf -g56BO3Wy4MdtvSkDuZx0lSRql+X72DD5SveRwQed68CIOkHFWIa5iQezk+XdOqjb -i56T14jUEVWWq6p0d2Jw3obBy/uaEA53oxwAHckOURXMTzXKTqrhqRrQbcOQJTed -RW/8SdL41FNd3NvswjkP9fGnzsPJbOmHHyaZxEgfbZI1NiuQ9hvNwlPHQS3Qzvmu -t58eLwU9ofTDDmG/X8NSmSSKV7nRbENyFQHCKTjmJoqxpj55MKg9JCqlCJfaC1vt -7TkRVVytJ0HahFXOyAiTTLQjSmVsbWVyIFZlcm5vb2lqIDxqZWxtZXJAYXBhY2hl -Lm9yZz6JAlQEEwEKAD4CGwMCHgECF4AFCwkIBwMFFQoJCAsFFgIDAQAWIQTcg37h -Sn43NH6HBhcAgG8r1ymkVwUCYOuSgQUJGWCzAAAKCRAAgG8r1ymkV/nLD/9QbEYT -f0rlHJjgVa1u5FLjyBk9n19jleS11GsnKxmSmdvL3H28mE/MmSPPCHXatRL+ufjk -r7CPD2NJIvClehrr1aS1ZscWsx+DYWRC2JoAPVIgNywn2QdrWh3S3UYsY+9yFkS1 -plSskWnaHWz+jJmnO0oI2EeLyTByUL8uY6GYyR6VlfMUi1yj1LH1CGKzr8g8gNNn -tEU4yJxK2svRuL27xNUxGyro8VKzXLnnwEYZQ7lDVd+DxXDPyUwsuAEfOZvQ9OcL -jKGT19sXeGYzCmhM+Rts+rcZaotzKkPxXjq+GWCf2i0DYCUXlpwCx9F8T86GfDlZ -rNJrvWd2XfEITuVt4sxV2zFoQ9gu+akoCY2h6S7CcqJFJ4bvmPJQDjvDazS3Ow+R -wQpk5iK04Asq9qkB+rzq8oeEx0Jra70QKaEqvgcgtJJQHfGcATJJXXC1SvUQYibG -hM3ZxMkL3vhrlRixdsp0oDdVWJMXAKKBskfxL+zerT9Ween0VDguQyS9uKoZ+ba7 -hbjUeGFxB+qsuFRZj7U49sjMVRKQnGZW9G2yjqsWdtGLkWC+bVoQGrsZcUG1THin -4xswNL7wtSq2cuLKskPYsTsXGOXODkv1UvXS9ia6K0Im4x/2AQWr3fSxRj93N9y7 -k/wbU/MRMFpo5O1AZi0vXOi5TtL4CgJLP3uUMrQjSmVsbWVyIFZlcm5vb2lqIDxq -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 -uy6YbxCXn9fsxQQsf9aJ6PB7SKfT1gRbqLzqipfnmQOBQEHUcGC3YugjsWmRm21j -r6ez4l3YPTLVoYL9R8A0Pa9UgIbOcIUDVPacj7I/hj5nDah8ACAl+/baMo3HxfOP -yTV3tYCzfTl+rQKvwlQ0yiFjPSmC5OtyBhW98h0GYxq62niFM050w3MaTmdfxrou -D690eKq8O+naRCH+JTewquYr4dc3N1zGxgagJ57C1sX5ShkP90D3jNFK+U7crJkI -FPfg5K4KbYryAWl/JbXyxoiuZMczV59bUlUugBJ+AhSPp0KAPcKI8BYoHb0Na3Db -Maj41CyEVYCMUl7XWig+DUNXZ1eeA/9WbEXXAoMFeY05xnCV6WVZudcLa1T4qEWU -XSZ2mWdTKXH5POPPV0h3I57oANDCF+E3jq3++RcvSCTTq6SkrAWp+UaL910pySnt -GSlkkN95DN6PhdVgnFArpi5csFrjNJ0cBGdJEKBGJ9/oO9C4x++0JUplbG1lciBW -ZXJub29paiA8amVsbWVyQG5sLmxpbnV4Lm9yZz6JAjwEMAECACYFAk1vtFIfHSBu -bC5saW51eC5vcmcgbm8gbG9uZ2VyIGV4aXN0cwAKCRAAgG8r1ymkVw47D/9VGUUe -AZ59IiTGDfzXCjy3fyDs1dsvAa2PG8mOevCfFBJWd6wbGlkeDKj9OrQvL489i0/M -9RfattXD3QnHqAPZiXsjqudig26s2EmoawZEjIJDXVR0qYO7mOA8S6sSPJzFkrVQ -6Sqd+tV4K5sOKUU2BO685BoLWvDnUzTSyq//wq+SFYThkaS+A54Yot7k1vE24FOI -87JEhVYkParjYsJprJitwz/VwhmTnl0x4+JIIeQMuDQZgx2RuGyiETxgGvc9abmv -osYjXmKGbjdhrAziRV2BJu8MHmSfXWM2454wupVPxUJ+E3xX0n+g993yfgRlyG/C -K6kYKLyJHm7BYiDhY5Avx1P+64I8zR9l5lhTmjFy+OZPjwVJPNkc59eDweHOGU1q -ni1BL5y8eV5SYO8iZej8Of0vbS/k6DAG79aryVEQFVmO4RTGnhr4b2/pIyG1iQV6 -1acC8K6LcM5AqWaIvoYqlQ7HdltcF725KyMyxqGGEePJzgxGwWPgFzEJzcgwr+nx -Fa+uuXeX1smVCtGzFZtP3l9U+dQiobs77xO3GX6gi65ucoGfednyVYFuBZszs95U -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 -vA== -=e6vU ------END PGP PUBLIC KEY BLOCK----- diff --git a/debian/watch b/debian/watch deleted file mode 100644 index 6082aa8..0000000 --- a/debian/watch +++ /dev/null @@ -1,2 +0,0 @@ -version=4 -opts=pgpsigurlmangle=s/$/.asc/ https://pypi.debian.net/ognibuild/ognibuild-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) diff --git a/disperse.conf b/disperse.conf deleted file mode 100644 index 4e8de5b..0000000 --- a/disperse.conf +++ /dev/null @@ -1,8 +0,0 @@ -# See https://github.com/jelmer/disperse -timeout_days: 5 -tag_name: "v$VERSION" -verify_command: "python3 -m unittest tests.test_suite" -update_version { - path: "ognibuild/__init__.py" - new_line: "__version__ = $TUPLED_VERSION" -} diff --git a/ognibuild.egg-info/PKG-INFO b/ognibuild.egg-info/PKG-INFO new file mode 100644 index 0000000..8ab0d39 --- /dev/null +++ b/ognibuild.egg-info/PKG-INFO @@ -0,0 +1,17 @@ +Metadata-Version: 2.1 +Name: ognibuild +Version: 0.0.7 +Summary: Detect and run any build system +Home-page: https://jelmer.uk/code/ognibuild +Maintainer: Jelmer Vernooij +Maintainer-email: jelmer@jelmer.uk +License: GNU GPLv2 or later +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Operating System :: POSIX +Provides-Extra: debian diff --git a/ognibuild.egg-info/SOURCES.txt b/ognibuild.egg-info/SOURCES.txt new file mode 100644 index 0000000..2067e75 --- /dev/null +++ b/ognibuild.egg-info/SOURCES.txt @@ -0,0 +1,52 @@ +.flake8 +.gitignore +AUTHORS +CODE_OF_CONDUCT.md +LICENSE +README.md +SECURITY.md +TODO +releaser.conf +setup.cfg +setup.py +.github/workflows/pythonpackage.yml +notes/architecture.md +notes/concepts.md +notes/roadmap.md +ognibuild/__init__.py +ognibuild/__main__.py +ognibuild/build.py +ognibuild/buildlog.py +ognibuild/buildsystem.py +ognibuild/clean.py +ognibuild/dist.py +ognibuild/dist_catcher.py +ognibuild/fix_build.py +ognibuild/fixers.py +ognibuild/info.py +ognibuild/install.py +ognibuild/outputs.py +ognibuild/requirements.py +ognibuild/test.py +ognibuild/vcs.py +ognibuild.egg-info/PKG-INFO +ognibuild.egg-info/SOURCES.txt +ognibuild.egg-info/dependency_links.txt +ognibuild.egg-info/entry_points.txt +ognibuild.egg-info/requires.txt +ognibuild.egg-info/top_level.txt +ognibuild/debian/__init__.py +ognibuild/debian/apt.py +ognibuild/debian/build.py +ognibuild/debian/build_deps.py +ognibuild/debian/file_search.py +ognibuild/debian/fix_build.py +ognibuild/debian/udd.py +ognibuild/resolver/__init__.py +ognibuild/resolver/apt.py +ognibuild/session/__init__.py +ognibuild/session/plain.py +ognibuild/session/schroot.py +ognibuild/tests/__init__.py +ognibuild/tests/test_debian_build.py +ognibuild/tests/test_debian_fix_build.py \ No newline at end of file diff --git a/ognibuild.egg-info/dependency_links.txt b/ognibuild.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ognibuild.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/ognibuild.egg-info/entry_points.txt b/ognibuild.egg-info/entry_points.txt new file mode 100644 index 0000000..dc61857 --- /dev/null +++ b/ognibuild.egg-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +deb-fix-build = ognibuild.debian.fix_build:main +ogni = ognibuild.__main__:main + diff --git a/ognibuild.egg-info/requires.txt b/ognibuild.egg-info/requires.txt new file mode 100644 index 0000000..9c36b12 --- /dev/null +++ b/ognibuild.egg-info/requires.txt @@ -0,0 +1,8 @@ +breezy +buildlog-consultant>=0.0.10 +requirements-parser + +[debian] +debmutate +python_apt +python_debian diff --git a/ognibuild.egg-info/top_level.txt b/ognibuild.egg-info/top_level.txt new file mode 100644 index 0000000..7e9084f --- /dev/null +++ b/ognibuild.egg-info/top_level.txt @@ -0,0 +1 @@ +ognibuild diff --git a/ognibuild/__init__.py b/ognibuild/__init__.py index bc255e1..22dfaec 100644 --- a/ognibuild/__init__.py +++ b/ognibuild/__init__.py @@ -18,14 +18,12 @@ import os import stat -from typing import List, Dict, Type -__version__ = (0, 0, 15) -version_string = '.'.join(map(str, __version__)) +__version__ = (0, 0, 7) -USER_AGENT = f"Ognibuild/{version_string}" +USER_AGENT = "Ognibuild" class DetailedFailure(Exception): @@ -34,12 +32,6 @@ class DetailedFailure(Exception): self.argv = argv self.error = error - def __eq__(self, other): - return (isinstance(other, type(self)) and - self.retcode == other.retcode and - self.argv == other.argv and - self.error == other.error) - class UnidentifiedError(Exception): """An unidentified error.""" @@ -50,13 +42,6 @@ class UnidentifiedError(Exception): self.lines = lines self.secondary = secondary - def __eq__(self, other): - return (isinstance(other, type(self)) and - self.retcode == other.retcode and - self.argv == other.argv and - self.lines == other.lines and - self.secondary == other.secondary) - def __repr__(self): return "<%s(%r, %r, ..., secondary=%r)>" % ( type(self).__name__, @@ -79,64 +64,17 @@ def shebang_binary(p): return os.path.basename(args[0].decode()).strip() -class UnknownRequirementFamily(Exception): - """Requirement family is unknown""" - - def __init__(self, family): - self.family = family - - class Requirement(object): # Name of the family of requirements - e.g. "python-package" family: str - _JSON_DESERIALIZERS: Dict[str, Type["Requirement"]] = {} - - @classmethod - def _from_json(self, js): - raise NotImplementedError(self._from_json) - - @classmethod - def from_json(self, js): - try: - family = Requirement._JSON_DESERIALIZERS[js[0]] - except KeyError: - raise UnknownRequirementFamily(js[0]) - return family._from_json(js[1]) + def __init__(self, family): + self.family = family def met(self, session): raise NotImplementedError(self) - def _json(self): - raise NotImplementedError(self._json) - - def json(self): - return (type(self).family, self._json()) - - @classmethod - def register_json(cls, subcls): - Requirement._JSON_DESERIALIZERS[subcls.family] = subcls - - -class OneOfRequirement(Requirement): - - elements: List[Requirement] - - family = 'or' - - def __init__(self, elements): - self.elements = elements - - def met(self, session): - for req in self.elements: - if req.met(session): - return True - return False - - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.elements) - class UpstreamOutput(object): def __init__(self, family): diff --git a/ognibuild/__main__.py b/ognibuild/__main__.py index 0826eb4..27ddccd 100644 --- a/ognibuild/__main__.py +++ b/ognibuild/__main__.py @@ -15,13 +15,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from contextlib import ExitStack import logging import os import shlex import sys -from urllib.parse import urlparse -from . import UnidentifiedError, DetailedFailure, version_string +from . import UnidentifiedError, DetailedFailure from .buildlog import ( InstallFixer, ExplainInstallFixer, @@ -31,10 +29,9 @@ from .buildlog import ( from .buildsystem import NoBuildToolsFound, detect_buildsystems from .resolver import ( auto_resolver, - select_resolvers, - UnsatisfiedRequirements, + native_resolvers, ) -from .session import SessionSetupFailure +from .resolver.apt import AptResolver def display_explain_commands(commands): @@ -42,33 +39,34 @@ def display_explain_commands(commands): for command, reqs in commands: if isinstance(command, list): command = shlex.join(command) - logging.info( - " %s (to install %s)", command, ", ".join(map(str, reqs))) + logging.info(" %s (to install %s)", command, ", ".join(map(str, reqs))) + + +def get_necessary_declared_requirements(resolver, requirements, stages): + missing = [] + for stage, req in requirements: + if stage in stages: + missing.append(req) + return missing def install_necessary_declared_requirements( session, resolver, fixers, buildsystems, stages, explain=False ): + relevant = [] + declared_reqs = [] + for buildsystem in buildsystems: + try: + declared_reqs.extend(buildsystem.get_declared_dependencies(session, fixers)) + except NotImplementedError: + logging.warning( + "Unable to determine declared dependencies from %r", buildsystem + ) + relevant.extend( + get_necessary_declared_requirements(resolver, declared_reqs, stages) + ) - if explain: - relevant = [] - for buildsystem in buildsystems: - declared_reqs = buildsystem.get_declared_dependencies( - session, fixers) - for stage, req in declared_reqs: - if stage in stages: - relevant.append(req) - install_missing_reqs(session, resolver, relevant, explain=True) - else: - for buildsystem in buildsystems: - try: - buildsystem.install_declared_requirements( - stages, session, resolver, fixers) - except NotImplementedError: - logging.warning( - "Unable to determine declared dependencies from %r", - buildsystem - ) + install_missing_reqs(session, resolver, relevant, explain=explain) # Types of dependencies: @@ -84,7 +82,6 @@ STAGE_MAP = { "test": ["test", "build", "core"], "build": ["build", "core"], "clean": [], - "verify": ["build", "core", "test"], } @@ -98,13 +95,9 @@ def determine_fixers(session, resolver, explain=False): def main(): # noqa: C901 import argparse - parser = argparse.ArgumentParser(prog='ogni') + parser = argparse.ArgumentParser() parser.add_argument( - "--version", action="version", version="%(prog)s " + version_string - ) - parser.add_argument( - "--directory", "-d", type=str, help="Directory for project.", - default="." + "--directory", "-d", type=str, help="Directory for project.", default="." ) parser.add_argument("--schroot", type=str, help="schroot to run in.") parser.add_argument( @@ -130,15 +123,6 @@ def main(): # noqa: C901 action="store_true", help="Ignore declared dependencies, follow build errors only", ) - parser.add_argument( - "--user", action="store_true", - help="Install in local-user directories." - ) - parser.add_argument( - "--dep-server-url", type=str, - help="ognibuild dep server to use", - default=os.environ.get('OGNIBUILD_DEPS')) - parser.add_argument("--verbose", action="store_true", help="Be verbose") subparsers = parser.add_subparsers(dest="subcommand") subparsers.add_parser("dist") @@ -146,11 +130,12 @@ def main(): # noqa: C901 subparsers.add_parser("clean") subparsers.add_parser("test") subparsers.add_parser("info") - subparsers.add_parser("verify") exec_parser = subparsers.add_parser("exec") - exec_parser.add_argument( - 'subargv', nargs=argparse.REMAINDER, help='Command to run.') + exec_parser.add_argument('subargv', nargs=argparse.REMAINDER, help='Command to run.') install_parser = subparsers.add_parser("install") + install_parser.add_argument( + "--user", action="store_true", help="Install in local-user directories." + ) install_parser.add_argument( "--prefix", type=str, help='Prefix to install in') @@ -170,72 +155,38 @@ def main(): # noqa: C901 from .session.plain import PlainSession session = PlainSession() - - with ExitStack() as es: - try: - es.enter_context(session) - except SessionSetupFailure as e: - logging.debug('Error lines: %r', e.errlines) - logging.fatal('Failed to set up session: %s', e.reason) - return 1 - - parsed_url = urlparse(args.directory) - # TODO(jelmer): Get a list of supported schemes from breezy? - if parsed_url.scheme in ('git', 'http', 'https', 'ssh'): - import breezy.git # noqa: F401 - import breezy.bzr # noqa: F401 - from breezy.branch import Branch - from silver_platter.utils import TemporarySprout - b = Branch.open(args.directory) - logging.info("Cloning %s", args.directory) - wt = es.enter_context(TemporarySprout(b)) - external_dir, internal_dir = session.setup_from_vcs(wt) - else: - if parsed_url.scheme == 'file': - directory = parsed_url.path - else: - directory = args.directory - logging.info("Preparing directory %s", directory) - external_dir, internal_dir = session.setup_from_directory( - directory) + with session: + logging.info("Preparing directory %s", args.directory) + external_dir, internal_dir = session.setup_from_directory(args.directory) session.chdir(internal_dir) os.chdir(external_dir) if not session.is_temporary and args.subcommand == 'info': args.explain = True - if args.resolve == "auto": + if args.resolve == "apt": + resolver = AptResolver.from_session(session) + elif args.resolve == "native": + resolver = native_resolvers(session, user_local=args.user) + elif args.resolve == "auto": resolver = auto_resolver(session, explain=args.explain) - else: - resolver = select_resolvers( - session, user_local=args.user, - resolvers=args.resolve.split(','), - dep_server_url=args.dep_server_url) logging.info("Using requirement resolver: %s", resolver) fixers = determine_fixers(session, resolver, explain=args.explain) try: if args.subcommand == "exec": from .fix_build import run_with_build_fixers - run_with_build_fixers(fixers, session, args.subargv) + run_with_build_fixers(session, args.subargv, fixers) return 0 - bss = list(detect_buildsystems(external_dir)) + bss = list(detect_buildsystems(args.directory)) logging.info("Detected buildsystems: %s", ", ".join(map(str, bss))) if not args.ignore_declared_dependencies: stages = STAGE_MAP[args.subcommand] if stages: - logging.info( - "Checking that declared requirements are present") + logging.info("Checking that declared requirements are present") try: install_necessary_declared_requirements( - session, resolver, fixers, bss, stages, - explain=args.explain + session, resolver, fixers, bss, stages, explain=args.explain ) - except UnsatisfiedRequirements as e: - logging.info( - 'Unable to install declared dependencies:') - for req in e.requirements: - logging.info(' * %s', req) - return 1 except ExplainInstall as e: display_explain_commands(e.commands) return 1 @@ -256,15 +207,11 @@ def main(): # noqa: C901 if args.subcommand == "build": from .build import run_build - run_build( - session, buildsystems=bss, resolver=resolver, - fixers=fixers) + run_build(session, buildsystems=bss, resolver=resolver, fixers=fixers) if args.subcommand == "clean": from .clean import run_clean - run_clean( - session, buildsystems=bss, resolver=resolver, - fixers=fixers) + run_clean(session, buildsystems=bss, resolver=resolver, fixers=fixers) if args.subcommand == "install": from .install import run_install @@ -279,42 +226,14 @@ def main(): # noqa: C901 if args.subcommand == "test": from .test import run_test - run_test( - session, buildsystems=bss, resolver=resolver, - fixers=fixers) + run_test(session, buildsystems=bss, resolver=resolver, fixers=fixers) if args.subcommand == "info": from .info import run_info run_info(session, buildsystems=bss, fixers=fixers) - - if args.subcommand == "verify": - from .build import run_build - from .test import run_test - - run_build( - session, buildsystems=bss, resolver=resolver, - fixers=fixers) - run_test( - session, buildsystems=bss, resolver=resolver, - fixers=fixers) - except ExplainInstall as e: display_explain_commands(e.commands) - except UnidentifiedError: - logging.info( - 'If there is a clear indication of a problem in the build ' - 'log, please consider filing a request to update the patterns ' - 'in buildlog-consultant at ' - 'https://github.com/jelmer/buildlog-consultant/issues/new') - return 1 - except DetailedFailure: - if not args.verbose: - logging.info( - 'Run with --verbose to get more information ' - 'about steps taken to try to resolve error') - logging.info( - 'Please consider filing a bug report at ' - 'https://github.com/jelmer/ognibuild/issues/new') + except (UnidentifiedError, DetailedFailure): return 1 except NoBuildToolsFound: logging.info("No build tools found.") diff --git a/ognibuild/build.py b/ognibuild/build.py index 17df903..1b03bf5 100644 --- a/ognibuild/build.py +++ b/ognibuild/build.py @@ -15,28 +15,16 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from functools import partial - from .buildsystem import NoBuildToolsFound -from .fix_build import iterate_with_build_fixers -from .logs import NoLogManager -BUILD_LOG_FILENAME = 'build.log' - - -def run_build(session, buildsystems, resolver, fixers, log_manager=None): +def run_build(session, buildsystems, resolver, fixers): # Some things want to write to the user's home directory, # e.g. pip caches in ~/.cache session.create_home() - if log_manager is None: - log_manager = NoLogManager() - for buildsystem in buildsystems: - iterate_with_build_fixers( - fixers, log_manager.wrap( - partial(buildsystem.build, session, resolver))) + buildsystem.build(session, resolver, fixers) return raise NoBuildToolsFound() diff --git a/ognibuild/buildlog.py b/ognibuild/buildlog.py index 29bcddd..037f2c9 100644 --- a/ognibuild/buildlog.py +++ b/ognibuild/buildlog.py @@ -19,32 +19,65 @@ """ import logging -from typing import Optional, List, Callable, Union, Tuple from buildlog_consultant.common import ( - Problem, + MissingPythonModule, + MissingPythonDistribution, + MissingCHeader, + MissingPkgConfig, + MissingCommand, + MissingFile, + MissingJavaScriptRuntime, + MissingSprocketsFile, + MissingGoPackage, MissingPerlFile, + MissingPerlModule, + MissingXmlEntity, + MissingJDKFile, + MissingJDK, + MissingJRE, + MissingNodeModule, + MissingNodePackage, + MissingPhpClass, + MissingRubyGem, + MissingLibrary, MissingSetupPyCommand, - MissingCMakeComponents, + MissingJavaClass, + MissingCSharpCompiler, + MissingRPackage, + MissingRubyFile, + MissingAutoconfMacro, + MissingValaPackage, + MissingBoostComponents, MissingXfceDependency, MissingHaskellDependencies, + MissingVagueDependency, + DhAddonLoadFailure, MissingMavenArtifacts, + MissingIntrospectionTypelib, + GnomeCommonMissing, MissingGnomeCommonDependency, + UnknownCertificateAuthority, + CMakeFilesMissing, + MissingLibtool, + MissingQt, + MissingX11, MissingPerlPredeclared, MissingLatexFile, MissingCargoCrate, + MissingStaticLibrary, ) +from buildlog_consultant.apt import UnsatisfiedAptDependencies -from . import OneOfRequirement from .fix_build import BuildFixer from .requirements import ( - Requirement, BinaryRequirement, PathRequirement, PkgConfigRequirement, CHeaderRequirement, JavaScriptRuntimeRequirement, ValaPackageRequirement, + RubyGemRequirement, GoPackageRequirement, DhAddonRequirement, PhpClassRequirement, @@ -59,7 +92,6 @@ from .requirements import ( HaskellPackageRequirement, MavenArtifactRequirement, BoostComponentRequirement, - KF5ComponentRequirement, GnomeCommonRequirement, JDKFileRequirement, JDKRequirement, @@ -80,124 +112,86 @@ from .requirements import ( LatexPackageRequirement, CargoCrateRequirement, StaticLibraryRequirement, - GnulibDirectoryRequirement, - LuaModuleRequirement, - PHPExtensionRequirement, - VcsControlDirectoryAccessRequirement, - RubyGemRequirement, - QtModuleRequirement, ) from .resolver import UnsatisfiedRequirements -def map_pytest_arguments_to_plugin(args): - # TODO(jelmer): Map argument to PytestPluginRequirement - return None - - -ProblemToRequirementConverter = Callable[[Problem], Optional[Requirement]] - - -PROBLEM_CONVERTERS: List[Union[ - Tuple[str, ProblemToRequirementConverter], - Tuple[str, ProblemToRequirementConverter, str]]] = [ - ('missing-file', lambda p: PathRequirement(p.path)), - ('command-missing', lambda p: BinaryRequirement(p.command)), - ('valac-cannot-compile', lambda p: VagueDependencyRequirement('valac'), - '0.0.27'), - ('missing-cmake-files', lambda p: OneOfRequirement( - [CMakefileRequirement(filename, p.version) - for filename in p.filenames])), - ('missing-command-or-build-file', lambda p: BinaryRequirement(p.command)), - ('missing-pkg-config-package', - lambda p: PkgConfigRequirement(p.module, p.minimum_version)), - ('missing-c-header', lambda p: CHeaderRequirement(p.header)), - ('missing-introspection-typelib', - lambda p: IntrospectionTypelibRequirement(p.library)), - ('missing-python-module', lambda p: PythonModuleRequirement( - p.module, python_version=p.python_version, - minimum_version=p.minimum_version)), - ('missing-python-distribution', lambda p: PythonPackageRequirement( - p.distribution, python_version=p.python_version, - minimum_version=p.minimum_version)), - ('javascript-runtime-missing', lambda p: JavaScriptRuntimeRequirement()), - ('missing-node-module', lambda p: NodeModuleRequirement(p.module)), - ('missing-node-package', lambda p: NodePackageRequirement(p.package)), - ('missing-ruby-gem', lambda p: RubyGemRequirement(p.gem, p.version)), - ('missing-qt-modules', lambda p: QtModuleRequirement(p.modules[0]), - '0.0.27'), - ('missing-php-class', lambda p: PhpClassRequirement(p.php_class)), - ('missing-r-package', lambda p: RPackageRequirement( - p.package, p.minimum_version)), - ('missing-vague-dependency', - lambda p: VagueDependencyRequirement( - p.name, minimum_version=p.minimum_version)), - ('missing-c#-compiler', lambda p: BinaryRequirement("msc")), - ('missing-gnome-common', lambda p: GnomeCommonRequirement()), - ('missing-jdk', lambda p: JDKRequirement()), - ('missing-jre', lambda p: JRERequirement()), - ('missing-qt', lambda p: QTRequirement()), - ('missing-x11', lambda p: X11Requirement()), - ('missing-libtool', lambda p: LibtoolRequirement()), - ('missing-php-extension', - lambda p: PHPExtensionRequirement(p.extension)), - ('missing-rust-compiler', lambda p: BinaryRequirement("rustc")), - ('missing-java-class', lambda p: JavaClassRequirement(p.classname)), - ('missing-go-package', lambda p: GoPackageRequirement(p.package)), - ('missing-autoconf-macro', lambda p: AutoconfMacroRequirement(p.macro)), - ('missing-vala-package', lambda p: ValaPackageRequirement(p.package)), - ('missing-lua-module', lambda p: LuaModuleRequirement(p.module)), - ('missing-jdk-file', lambda p: JDKFileRequirement(p.jdk_path, p.filename)), - ('missing-ruby-file', lambda p: RubyFileRequirement(p.filename)), - ('missing-library', lambda p: LibraryRequirement(p.library)), - ('missing-sprockets-file', - lambda p: SprocketsFileRequirement(p.content_type, p.name)), - ('dh-addon-load-failure', lambda p: DhAddonRequirement(p.path)), - ('missing-xml-entity', lambda p: XmlEntityRequirement(p.url)), - ('missing-gnulib-directory', - lambda p: GnulibDirectoryRequirement(p.directory)), - ('vcs-control-directory-needed', - lambda p: VcsControlDirectoryAccessRequirement(p.vcs)), - ('missing-static-library', - lambda p: StaticLibraryRequirement(p.library, p.filename)), - ('missing-perl-module', - lambda p: PerlModuleRequirement( - module=p.module, filename=p.filename, inc=p.inc)), - ('unknown-certificate-authority', - lambda p: CertificateAuthorityRequirement(p.url)), - ('unsupported-pytest-arguments', - lambda p: map_pytest_arguments_to_plugin(p.args), '0.0.27'), -] - - -def problem_to_upstream_requirement( - problem: Problem) -> Optional[Requirement]: # noqa: C901 - for entry in PROBLEM_CONVERTERS: - kind, fn = entry[:2] - if kind == problem.kind: - return fn(problem) - if isinstance(problem, MissingCMakeComponents): - if problem.name.lower() == 'boost': - return OneOfRequirement( - [BoostComponentRequirement(name) - for name in problem.components]) - elif problem.name.lower() == 'kf5': - return OneOfRequirement( - [KF5ComponentRequirement(name) for name in problem.components]) - return None +def problem_to_upstream_requirement(problem): # noqa: C901 + if isinstance(problem, MissingFile): + return PathRequirement(problem.path) + elif isinstance(problem, MissingCommand): + return BinaryRequirement(problem.command) + elif isinstance(problem, MissingPkgConfig): + return PkgConfigRequirement(problem.module, problem.minimum_version) + elif isinstance(problem, MissingCHeader): + return CHeaderRequirement(problem.header) + elif isinstance(problem, MissingIntrospectionTypelib): + return IntrospectionTypelibRequirement(problem.library) + elif isinstance(problem, MissingJavaScriptRuntime): + return JavaScriptRuntimeRequirement() + elif isinstance(problem, MissingRubyGem): + return RubyGemRequirement(problem.gem, problem.version) + elif isinstance(problem, MissingValaPackage): + return ValaPackageRequirement(problem.package) + elif isinstance(problem, MissingGoPackage): + return GoPackageRequirement(problem.package) + elif isinstance(problem, MissingBoostComponents): + return [BoostComponentRequirement(name) for name in problem.components] + elif isinstance(problem, DhAddonLoadFailure): + return DhAddonRequirement(problem.path) + elif isinstance(problem, MissingPhpClass): + return PhpClassRequirement(problem.php_class) + elif isinstance(problem, MissingRPackage): + return RPackageRequirement(problem.package, problem.minimum_version) + elif isinstance(problem, MissingNodeModule): + return NodeModuleRequirement(problem.module) + elif isinstance(problem, MissingStaticLibrary): + return StaticLibraryRequirement(problem.library, problem.filename) + elif isinstance(problem, MissingNodePackage): + return NodePackageRequirement(problem.package) elif isinstance(problem, MissingLatexFile): if problem.filename.endswith('.sty'): return LatexPackageRequirement(problem.filename[:-4]) return None + elif isinstance(problem, MissingVagueDependency): + return VagueDependencyRequirement(problem.name, minimum_version=problem.minimum_version) + elif isinstance(problem, MissingLibrary): + return LibraryRequirement(problem.library) + elif isinstance(problem, MissingRubyFile): + return RubyFileRequirement(problem.filename) + elif isinstance(problem, MissingXmlEntity): + return XmlEntityRequirement(problem.url) + elif isinstance(problem, MissingSprocketsFile): + return SprocketsFileRequirement(problem.content_type, problem.name) + elif isinstance(problem, MissingJavaClass): + return JavaClassRequirement(problem.classname) + elif isinstance(problem, CMakeFilesMissing): + return [CMakefileRequirement(filename) for filename in problem.filenames] elif isinstance(problem, MissingHaskellDependencies): - return OneOfRequirement( - [HaskellPackageRequirement.from_string(dep) - for dep in problem.deps]) + return [HaskellPackageRequirement.from_string(dep) for dep in problem.deps] elif isinstance(problem, MissingMavenArtifacts): - return OneOfRequirement([ + return [ MavenArtifactRequirement.from_str(artifact) for artifact in problem.artifacts - ]) + ] + elif isinstance(problem, MissingCSharpCompiler): + return BinaryRequirement("msc") + elif isinstance(problem, GnomeCommonMissing): + return GnomeCommonRequirement() + elif isinstance(problem, MissingJDKFile): + return JDKFileRequirement(problem.jdk_path, problem.filename) + elif isinstance(problem, MissingJDK): + return JDKRequirement() + elif isinstance(problem, MissingJRE): + return JRERequirement() + elif isinstance(problem, MissingQt): + return QTRequirement() + elif isinstance(problem, MissingX11): + return X11Requirement() + elif isinstance(problem, MissingLibtool): + return LibtoolRequirement() + elif isinstance(problem, UnknownCertificateAuthority): + return CertificateAuthorityRequirement(problem.url) elif isinstance(problem, MissingPerlPredeclared): ret = PerlPreDeclaredRequirement(problem.name) try: @@ -216,20 +210,36 @@ def problem_to_upstream_requirement( return BinaryRequirement("glib-gettextize") else: logging.warning( - "No known command for gnome-common dependency %s", - problem.package + "No known command for gnome-common dependency %s", problem.package ) return None elif isinstance(problem, MissingXfceDependency): if problem.package == "gtk-doc": return BinaryRequirement("gtkdocize") else: - logging.warning( - "No known command for xfce dependency %s", problem.package) + logging.warning("No known command for xfce dependency %s", problem.package) return None + elif isinstance(problem, MissingPerlModule): + return PerlModuleRequirement( + module=problem.module, filename=problem.filename, inc=problem.inc + ) elif isinstance(problem, MissingPerlFile): return PerlFileRequirement(filename=problem.filename) - elif problem.kind == 'unsatisfied-apt-dependencies': + elif isinstance(problem, MissingAutoconfMacro): + return AutoconfMacroRequirement(problem.macro) + elif isinstance(problem, MissingPythonModule): + return PythonModuleRequirement( + problem.module, + python_version=problem.python_version, + minimum_version=problem.minimum_version, + ) + elif isinstance(problem, MissingPythonDistribution): + return PythonPackageRequirement( + problem.distribution, + python_version=problem.python_version, + minimum_version=problem.minimum_version, + ) + elif isinstance(problem, UnsatisfiedAptDependencies): from .resolver.apt import AptRequirement return AptRequirement(problem.relations) else: diff --git a/ognibuild/buildsystem.py b/ognibuild/buildsystem.py index f508219..f3cd7a0 100644 --- a/ognibuild/buildsystem.py +++ b/ognibuild/buildsystem.py @@ -20,7 +20,8 @@ import logging import os import re -from typing import Optional, Tuple, Type, List, Iterable +import shlex +from typing import Optional, Tuple import warnings from . import shebang_binary, UnidentifiedError @@ -42,9 +43,8 @@ from .requirements import ( MavenArtifactRequirement, GoRequirement, GoPackageRequirement, - VagueDependencyRequirement, ) -from .fix_build import run_with_build_fixers, run_detecting_problems +from .fix_build import run_with_build_fixers from .session import which @@ -69,14 +69,6 @@ class InstallTarget(object): # TODO(jelmer): Add information about target directory, layout, etc. -def get_necessary_declared_requirements(resolver, requirements, stages): - missing = [] - for stage, req in requirements: - if stage in stages: - missing.append(req) - return missing - - class BuildSystem(object): """A particular buildsystem.""" @@ -86,27 +78,20 @@ class BuildSystem(object): return self.name def dist( - self, session, resolver, target_directory: str, quiet=False + self, session, resolver, fixers, target_directory: str, quiet=False ) -> str: raise NotImplementedError(self.dist) - def install_declared_requirements(self, stages, session, resolver, fixers): - from .buildlog import install_missing_reqs - declared_reqs = self.get_declared_dependencies(session, fixers) - relevant = get_necessary_declared_requirements( - resolver, declared_reqs, stages) - install_missing_reqs(session, resolver, relevant, explain=False) - - def test(self, session, resolver): + def test(self, session, resolver, fixers): raise NotImplementedError(self.test) - def build(self, session, resolver): + def build(self, session, resolver, fixers): raise NotImplementedError(self.build) - def clean(self, session, resolver): + def clean(self, session, resolver, fixers): raise NotImplementedError(self.clean) - def install(self, session, resolver, install_target): + def install(self, session, resolver, fixers, install_target): raise NotImplementedError(self.install) def get_declared_dependencies(self, session, fixers=None): @@ -116,7 +101,7 @@ class BuildSystem(object): raise NotImplementedError(self.get_declared_outputs) @classmethod - def probe(cls, path: str) -> Optional["BuildSystem"]: + def probe(cls, path): return None @@ -143,33 +128,23 @@ class Pear(BuildSystem): def __init__(self, path): self.path = path - def dist(self, session, resolver, target_directory: str, - quiet: bool = False): + def dist(self, session, resolver, fixers, target_directory: str, quiet=False): with DistCatcher([session.external_path(".")]) as dc: - run_detecting_problems( - session, - [guaranteed_which(session, resolver, "pear"), "package"]) + run_with_build_fixers(session, [guaranteed_which(session, resolver, "pear"), "package"], fixers) return dc.copy_single(target_directory) - def test(self, session, resolver): - run_detecting_problems( - session, - [guaranteed_which(session, resolver, "pear"), "run-tests"]) + def test(self, session, resolver, fixers): + run_with_build_fixers(session, [guaranteed_which(session, resolver, "pear"), "run-tests"], fixers) - def build(self, session, resolver): - run_detecting_problems( - session, - [guaranteed_which(session, resolver, "pear"), "build", self.path]) + def build(self, session, resolver, fixers): + run_with_build_fixers(session, [guaranteed_which(session, resolver, "pear"), "build", self.path], fixers) - def clean(self, session, resolver): + def clean(self, session, resolver, fixers): self.setup(resolver) # TODO - def install(self, session, resolver, install_target): - run_detecting_problems( - session, - [guaranteed_which( - session, resolver, "pear"), "install", self.path]) + def install(self, session, resolver, fixers, install_target): + run_with_build_fixers(session, [guaranteed_which(session, resolver, "pear"), "install", self.path], fixers) def get_declared_dependencies(self, session, fixers=None): path = os.path.join(self.path, "package.xml") @@ -219,9 +194,7 @@ class Pear(BuildSystem): for ns in cls.PEAR_NAMESPACES: if tree.root.tag == '{%s}package' % ns: - logging.debug( - "Found package.xml with namespace %s, " - "assuming pear package.") + logging.debug("Found package.xml with namespace %s, assuming pear package.") return cls(path) @@ -230,11 +203,6 @@ class Pear(BuildSystem): def run_setup(script_name, script_args=None, stop_after="run"): - # Import setuptools, just in case it decides to replace distutils - try: - import setuptools # noqa: F401 - except ImportError: - pass from distutils import core import sys @@ -267,10 +235,6 @@ def run_setup(script_name, script_args=None, stop_after="run"): _setup_wrapper = """\ -try: - import setuptools -except ImportError: - pass import distutils from distutils import core import sys @@ -331,9 +295,6 @@ class SetupPy(BuildSystem): self.config = self.load_setup_cfg() except FileNotFoundError: self.config = None - except ModuleNotFoundError as e: - logging.warning('Error parsing setup.cfg: %s', e) - self.config = None try: self.pyproject = self.load_toml() @@ -352,7 +313,7 @@ class SetupPy(BuildSystem): return toml.load(pf) def load_setup_cfg(self): - from setuptools.config.setupcfg import read_configuration + from setuptools.config import read_configuration p = os.path.join(self.path, "setup.cfg") if os.path.exists(p): @@ -377,8 +338,7 @@ class SetupPy(BuildSystem): if d is None: logging.warning( "'distutils.core.setup()' was never called -- " - "perhaps '%s' is not a Distutils setup script?", - os.path.basename(p) + "perhaps '%s' is not a Distutils setup script?" % os.path.basename(p) ) return None @@ -397,7 +357,9 @@ class SetupPy(BuildSystem): import tempfile import json - interpreter = self._determine_interpreter() + interpreter = shebang_binary(os.path.join(self.path, "setup.py")) + if interpreter is None: + interpreter = self.DEFAULT_PYTHON output_f = tempfile.NamedTemporaryFile( dir=os.path.join(session.location, "tmp"), mode="w+t" ) @@ -407,16 +369,14 @@ class SetupPy(BuildSystem): argv = [ interpreter, "-c", - _setup_wrapper.replace( - "%(script_name)s", '"setup.py"').replace( - "%(output_path)s", - '"/' + os.path.relpath(output_f.name, session.location) - + '"', + _setup_wrapper.replace("%(script_name)s", '"setup.py"').replace( + "%(output_path)s", + '"/' + os.path.relpath(output_f.name, session.location) + '"', ), ] try: if fixers is not None: - run_with_build_fixers(fixers, session, argv, quiet=True) + run_with_build_fixers(session, argv, fixers) else: session.check_call(argv, close_fds=False) except RuntimeError as e: @@ -428,43 +388,31 @@ class SetupPy(BuildSystem): def __repr__(self): return "%s(%r)" % (type(self).__name__, self.path) - def test(self, session, resolver): + def test(self, session, resolver, fixers): if os.path.exists(os.path.join(self.path, "tox.ini")): - run_detecting_problems( - session, ["tox", "--skip-missing-interpreters"]) - return - if self.pyproject: - run_detecting_problems( - session, ["python3", "-m", "pep517.check", "."]) - return - if 'tool:pytest' in self.config or 'pytest' in self.config: - run_detecting_problems(session, ['pytest']) - return - if self.has_setup_py: + run_with_build_fixers(session, ["tox"], fixers) + elif self.pyproject: + run_with_build_fixers( + session, [self.DEFAULT_PYTHON, "-m", "pep517.check", "."], fixers + ) + elif self.has_setup_py: # Pre-emptively insall setuptools, since distutils doesn't provide # a 'test' subcommand and some packages fall back to distutils # if setuptools is not available. setuptools_req = PythonPackageRequirement("setuptools") if not setuptools_req.met(session): resolver.install([setuptools_req]) - try: - self._run_setup(session, resolver, ["test"]) - except UnidentifiedError as e: - if "error: invalid command 'test'" in e.lines: - pass - else: - raise - else: - return - raise NotImplementedError - - def build(self, session, resolver): - if self.has_setup_py: - self._run_setup(session, resolver, ["build"]) + self._run_setup(session, resolver, ["test"], fixers) else: raise NotImplementedError - def dist(self, session, resolver, target_directory, quiet=False): + def build(self, session, resolver, fixers): + if self.has_setup_py: + self._run_setup(session, resolver, ["build"], fixers) + else: + raise NotImplementedError + + def dist(self, session, resolver, fixers, target_directory, quiet=False): # TODO(jelmer): Look at self.build_backend if self.has_setup_py: preargs = [] @@ -476,68 +424,59 @@ class SetupPy(BuildSystem): if not setuptools_req.met(session): resolver.install([setuptools_req]) with DistCatcher([session.external_path("dist")]) as dc: - self._run_setup(session, resolver, preargs + ["sdist"]) + self._run_setup(session, resolver, preargs + ["sdist"], fixers) return dc.copy_single(target_directory) elif self.pyproject: with DistCatcher([session.external_path("dist")]) as dc: - run_detecting_problems( + run_with_build_fixers( session, - ["python3", "-m", "pep517.build", "--source", "."], + [self.DEFAULT_PYTHON, "-m", "pep517.build", "--source", "."], + fixers, ) return dc.copy_single(target_directory) raise AssertionError("no setup.py or pyproject.toml") - def clean(self, session, resolver): + def clean(self, session, resolver, fixers): if self.has_setup_py: - self._run_setup(session, resolver, ["clean"]) + self._run_setup(session, resolver, ["clean"], fixers) else: raise NotImplementedError - def install(self, session, resolver, install_target): + def install(self, session, resolver, fixers, install_target): if self.has_setup_py: extra_args = [] if install_target.user: extra_args.append("--user") if install_target.prefix: extra_args.append("--prefix=%s" % install_target.prefix) - self._run_setup( - session, resolver, ["install"] + extra_args) + self._run_setup(session, resolver, ["install"] + extra_args, fixers) else: raise NotImplementedError - def _determine_interpreter(self): - interpreter = None - if self.config: - python_requires = self.config.get( - 'options', {}).get('python_requires') - if python_requires: - if not python_requires.contains('2.7'): - interpreter = 'python3' - if interpreter is None: - interpreter = shebang_binary(os.path.join(self.path, "setup.py")) - if interpreter is None: - interpreter = self.DEFAULT_PYTHON - return interpreter - - def _run_setup(self, session, resolver, args): + def _run_setup(self, session, resolver, args, fixers): from .buildlog import install_missing_reqs # Install the setup_requires beforehand, since otherwise # setuptools might fetch eggs instead of our preferred resolver. install_missing_reqs(session, resolver, list(self._setup_requires())) - interpreter = self._determine_interpreter() + interpreter = shebang_binary(os.path.join(self.path, "setup.py")) + if interpreter is None: + interpreter = self.DEFAULT_PYTHON argv = [interpreter, "./setup.py"] + args # TODO(jelmer): Perhaps this should be additive? env = dict(os.environ) - run_detecting_problems(session, argv, env=env) + # Inherit SETUPTOOLS_SCM_PRETEND_VERSION from the current environment + if "SETUPTOOLS_SCM_PRETEND_VERSION" in os.environ: + env["SETUPTOOLS_SCM_PRETEND_VERSION"] = os.environ[ + "SETUPTOOLS_SCM_PRETEND_VERSION" + ] + run_with_build_fixers(session, argv, fixers, env=env) def _setup_requires(self): if self.pyproject: if "build-system" in self.pyproject: - requires = self.pyproject["build-system"].get("requires", []) - for require in requires: - yield PythonPackageRequirement.from_requirement_str( - require) + for require in self.pyproject["build-system"].get("requires", []): + yield PythonPackageRequirement.from_requirement_str(require) if self.config: options = self.config.get("options", {}) for require in options.get("setup_requires", []): @@ -547,35 +486,28 @@ class SetupPy(BuildSystem): distribution = self._extract_setup(session, fixers) if distribution is not None: for require in distribution["requires"]: - yield "core", PythonPackageRequirement.from_requirement_str( - require) + yield "core", PythonPackageRequirement.from_requirement_str(require) # Not present for distutils-only packages for require in distribution["setup_requires"]: - yield "build", PythonPackageRequirement.from_requirement_str( - require) + yield "build", PythonPackageRequirement.from_requirement_str(require) # Not present for distutils-only packages for require in distribution["install_requires"]: - yield "core", PythonPackageRequirement.from_requirement_str( - require) + yield "core", PythonPackageRequirement.from_requirement_str(require) # Not present for distutils-only packages for require in distribution["tests_require"]: - yield "test", PythonPackageRequirement.from_requirement_str( - require) + yield "test", PythonPackageRequirement.from_requirement_str(require) if self.pyproject: if "build-system" in self.pyproject: - requires = self.pyproject["build-system"].get("requires", []) - for require in requires: - yield ( - "build", - PythonPackageRequirement.from_requirement_str(require)) + for require in self.pyproject["build-system"].get("requires", []): + yield "build", PythonPackageRequirement.from_requirement_str( + require + ) if self.config: options = self.config.get("options", {}) for require in options.get("setup_requires", []): - yield "build", PythonPackageRequirement.from_requirement_str( - require) + yield "build", PythonPackageRequirement.from_requirement_str(require) for require in options.get("install_requires", []): - yield "core", PythonPackageRequirement.from_requirement_str( - require) + yield "core", PythonPackageRequirement.from_requirement_str(require) def get_declared_outputs(self, session, fixers=None): distribution = self._extract_setup(session, fixers) @@ -583,8 +515,7 @@ class SetupPy(BuildSystem): if distribution is not None: for script in distribution["scripts"]: yield BinaryOutput(os.path.basename(script)) - for script in distribution["entry_points"].get( - "console_scripts", []): + for script in distribution["entry_points"].get("console_scripts", []): yield BinaryOutput(script.split("=")[0]) all_packages.update(distribution["packages"]) if self.config: @@ -592,8 +523,7 @@ class SetupPy(BuildSystem): all_packages.update(options.get("packages", [])) for script in options.get("scripts", []): yield BinaryOutput(os.path.basename(script)) - for script in options.get("entry_points", {}).get( - "console_scripts", []): + for script in options.get("entry_points", {}).get("console_scripts", []): yield BinaryOutput(script.split("=")[0]) packages = set() @@ -641,49 +571,11 @@ class Bazel(BuildSystem): logging.debug("Found BUILD, assuming bazel package.") return cls(path) - def build(self, session, resolver): - run_detecting_problems(session, ["bazel", "build", "//..."]) + def build(self, session, resolver, fixers): + run_with_build_fixers(session, ["bazel", "build", "//..."], fixers) - def test(self, session, resolver): - run_detecting_problems(session, ["bazel", "test", "//..."]) - - -class GnomeShellExtension(BuildSystem): - - name = "gnome-shell-extension" - - def __init__(self, path): - self.path = path - - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.path) - - @classmethod - def exists(cls, path): - if not os.path.exists(os.path.join(path, "metadata.json")): - return False - return True - - @classmethod - def probe(cls, path): - if cls.exists(path): - logging.debug( - "Found metadata.json , assuming gnome-shell extension.") - return cls(path) - - def build(self, session, resolver): - pass - - def test(self, session, resolver): - pass - - def get_declared_dependencies(self, session, fixers=None): - import json - with open(os.path.join(self.path, 'metadata.json'), 'r') as f: - metadata = json.load(f) - if 'shell-version' in metadata: - # TODO(jelmer): Somehow represent supported versions - yield "core", VagueDependencyRequirement("gnome-shell") + def test(self, session, resolver, fixers): + run_with_build_fixers(session, ["bazel", "test", "//..."], fixers) class Octave(BuildSystem): @@ -747,8 +639,9 @@ class Gradle(BuildSystem): @classmethod def exists(cls, path): - return (os.path.exists(os.path.join(path, "build.gradle")) - or os.path.exists(os.path.join(path, "build.gradle.kts"))) + return os.path.exists(os.path.join(path, "build.gradle")) or os.path.exists( + os.path.join(path, "build.gradle.kts") + ) @classmethod def from_path(cls, path): @@ -768,7 +661,7 @@ class Gradle(BuildSystem): if not binary_req.met(session): resolver.install([binary_req]) - def _run(self, session, resolver, task, args): + def _run(self, session, resolver, task, args, fixers): self.setup(session, resolver) argv = [] if self.executable.startswith("./") and ( @@ -778,13 +671,12 @@ class Gradle(BuildSystem): argv.extend([self.executable, task]) argv.extend(args) try: - run_detecting_problems(session, argv) + run_with_build_fixers(session, argv, fixers) except UnidentifiedError as e: if any( [ re.match( - r"Task '" + task + - r"' not found in root project '.*'\.", line + r"Task '" + task + r"' not found in root project '.*'\.", line ) for line in e.lines ] @@ -792,24 +684,24 @@ class Gradle(BuildSystem): raise NotImplementedError raise - def clean(self, session, resolver): - self._run(session, resolver, "clean", []) + def clean(self, session, resolver, fixers): + self._run(session, resolver, "clean", [], fixers) - def build(self, session, resolver): - self._run(session, resolver, "build", []) + def build(self, session, resolver, fixers): + self._run(session, resolver, "build", [], fixers) - def test(self, session, resolver): - self._run(session, resolver, "test", []) + def test(self, session, resolver, fixers): + self._run(session, resolver, "test", [], fixers) - def dist(self, session, resolver, target_directory, quiet=False): + def dist(self, session, resolver, fixers, target_directory, quiet=False): with DistCatcher([session.external_path(".")]) as dc: - self._run(session, resolver, "distTar", []) + self._run(session, resolver, "distTar", [], fixers) return dc.copy_single(target_directory) - def install(self, session, resolver, install_target): + def install(self, session, resolver, fixers, install_target): raise NotImplementedError # TODO(jelmer): installDist just creates files under build/install/... - self._run(session, resolver, "installDist", []) + self._run(session, resolver, "installDist", [], fixers) class R(BuildSystem): @@ -824,39 +716,31 @@ class R(BuildSystem): def __repr__(self): return "%s(%r)" % (type(self).__name__, self.path) - def build(self, session, resolver): + def build(self, session, resolver, fixers): pass - def dist(self, session, resolver, target_directory, quiet=False): + def dist(self, session, resolver, fixers, target_directory, quiet=False): r_path = guaranteed_which(session, resolver, "R") with DistCatcher([session.external_path(".")]) as dc: - run_detecting_problems(session, [r_path, "CMD", "build", "."]) + run_with_build_fixers(session, [r_path, "CMD", "build", "."], fixers) return dc.copy_single(target_directory) - def install(self, session, resolver, install_target): + def install(self, session, resolver, fixers, install_target): extra_args = [] if install_target.prefix: extra_args.append("--prefix=%s" % install_target.prefix) r_path = guaranteed_which(session, resolver, "R") - run_detecting_problems( - session, [r_path, "CMD", "INSTALL", "."] + extra_args) + run_with_build_fixers(session, [r_path, "CMD", "INSTALL", "."] + extra_args, fixers) - def test(self, session, resolver): + def test(self, session, resolver, fixers): r_path = guaranteed_which(session, resolver, "R") - if session.exists("run_tests.sh"): - run_detecting_problems(session, ["./run_tests.sh"]) - elif session.exists("tests/testthat"): - run_detecting_problems( - session, [r_path, "-e", "testthat::test_dir('tests')"]) - - def lint(self, session, resolver): - r_path = guaranteed_which(session, resolver, "R") - run_detecting_problems(session, [r_path, "CMD", "check"]) + run_with_build_fixers(session, [r_path, "CMD", "check", "."], fixers) @classmethod def probe(cls, path): - if (os.path.exists(os.path.join(path, "DESCRIPTION")) - and os.path.exists(os.path.join(path, "NAMESPACE"))): + if os.path.exists(os.path.join(path, "DESCRIPTION")) and os.path.exists( + os.path.join(path, "NAMESPACE") + ): return cls(path) def _read_description(self): @@ -901,39 +785,37 @@ class Meson(BuildSystem): def __repr__(self): return "%s(%r)" % (type(self).__name__, self.path) - def _setup(self, session): + def _setup(self, session, fixers): if not session.exists("build"): session.mkdir("build") - run_detecting_problems(session, ["meson", "setup", "build"]) + run_with_build_fixers(session, ["meson", "setup", "build"], fixers) - def clean(self, session, resolver): - self._setup(session) - run_detecting_problems(session, ["ninja", "-C", "build", "clean"]) + def clean(self, session, resolver, fixers): + self._setup(session, fixers) + run_with_build_fixers(session, ["ninja", "-C", "build", "clean"], fixers) - def build(self, session, resolver): - self._setup(session) - run_detecting_problems(session, ["ninja", "-C", "build"]) + def build(self, session, resolver, fixers): + self._setup(session, fixers) + run_with_build_fixers(session, ["ninja", "-C", "build"], fixers) - def dist(self, session, resolver, target_directory, quiet=False): - self._setup(session) + def dist(self, session, resolver, fixers, target_directory, quiet=False): + self._setup(session, fixers) with DistCatcher([session.external_path("build/meson-dist")]) as dc: try: - run_detecting_problems( - session, ["ninja", "-C", "build", "dist"]) + run_with_build_fixers(session, ["ninja", "-C", "build", "dist"], fixers) except UnidentifiedError as e: - if ("ninja: error: unknown target 'dist', did you mean 'dino'?" - in e.lines): + if "ninja: error: unknown target 'dist', did you mean 'dino'?" in e.lines: raise NotImplementedError raise return dc.copy_single(target_directory) - def test(self, session, resolver): - self._setup(session) - run_detecting_problems(session, ["ninja", "-C", "build", "test"]) + def test(self, session, resolver, fixers): + self._setup(session, fixers) + run_with_build_fixers(session, ["ninja", "-C", "build", "test"], fixers) - def install(self, session, resolver, install_target): - self._setup(session) - run_detecting_problems(session, ["ninja", "-C", "build", "install"]) + def install(self, session, resolver, fixers, install_target): + self._setup(session, fixers) + run_with_build_fixers(session, ["ninja", "-C", "build", "install"], fixers) @classmethod def probe(cls, path): @@ -941,40 +823,6 @@ class Meson(BuildSystem): logging.debug("Found meson.build, assuming meson package.") return Meson(os.path.join(path, "meson.build")) - def _introspect(self, session, fixers, args): - ret = run_with_build_fixers( - fixers, - session, - ["meson", "introspect"] + args + ['./meson.build']) - import json - return json.loads(''.join(ret)) - - def get_declared_dependencies(self, session, fixers=None): - resp = self._introspect(session, fixers, ["--scan-dependencies"]) - for entry in resp: - version = entry.get('version', []) - minimum_version = None - if len(version) == 1 and version[0].startswith('>='): - minimum_version = version[0][2:] - elif len(version) > 1: - logging.warning( - 'Unable to parse version constraints: %r', version) - # TODO(jelmer): Include entry['required'] - yield ( - "core", - VagueDependencyRequirement( - entry['name'], minimum_version=minimum_version)) - - def get_declared_outputs(self, session, fixers=None): - resp = self._introspect(session, fixers, ["--targets"]) - for entry in resp: - if not entry['installed']: - continue - if entry['type'] == 'executable': - for name in entry['filename']: - yield BinaryOutput(os.path.basename(name)) - # TODO(jelmer): Handle other types - class Npm(BuildSystem): @@ -992,49 +840,49 @@ class Npm(BuildSystem): return "%s(%r)" % (type(self).__name__, self.path) def get_declared_dependencies(self, session, fixers=None): - for name, unused_version in self.package.get( - "dependencies", {}).items(): - # TODO(jelmer): Look at version - yield "core", NodePackageRequirement(name) - for name, unused_version in self.package.get( - "devDependencies", {}).items(): - # TODO(jelmer): Look at version - yield "build", NodePackageRequirement(name) + if "dependencies" in self.package: + for name, unused_version in self.package["dependencies"].items(): + # TODO(jelmer): Look at version + yield "core", NodePackageRequirement(name) + if "devDependencies" in self.package: + for name, unused_version in self.package["devDependencies"].items(): + # TODO(jelmer): Look at version + yield "build", NodePackageRequirement(name) def setup(self, session, resolver): binary_req = BinaryRequirement("npm") if not binary_req.met(session): resolver.install([binary_req]) - def dist(self, session, resolver, target_directory, quiet=False): + def dist(self, session, resolver, fixers, target_directory, quiet=False): self.setup(session, resolver) with DistCatcher([session.external_path(".")]) as dc: - run_detecting_problems(session, ["npm", "pack"]) + run_with_build_fixers(session, ["npm", "pack"], fixers) return dc.copy_single(target_directory) - def test(self, session, resolver): + def test(self, session, resolver, fixers): self.setup(session, resolver) - test_script = self.package.get("scripts", {}).get("test") + test_script = self.package["scripts"].get("test") if test_script: - run_detecting_problems(session, ['bash', '-c', test_script]) + run_with_build_fixers(session, shlex.split(test_script), fixers) else: - logging.info('No test command defined in package.json') + raise NotImplementedError - def build(self, session, resolver): + def build(self, session, resolver, fixers): self.setup(session, resolver) - build_script = self.package.get("scripts", {}).get("build") + build_script = self.package["scripts"].get("build") if build_script: - run_detecting_problems(session, ['bash', '-c', build_script]) + run_with_build_fixers(session, shlex.split(build_script), fixers) else: - logging.info('No build command defined in package.json') + raise NotImplementedError - def clean(self, session, resolver): + def clean(self, session, resolver, fixers): self.setup(session, resolver) - clean_script = self.package.get("scripts", {}).get("clean") + clean_script = self.package["scripts"].get("clean") if clean_script: - run_detecting_problems(session, ['bash', '-c', clean_script]) + run_with_build_fixers(session, shlex.split(clean_script), fixers) else: - logging.info('No clean command defined in package.json') + raise NotImplementedError @classmethod def probe(cls, path): @@ -1050,31 +898,20 @@ class Waf(BuildSystem): def __init__(self, path): self.path = path - def setup(self, session, resolver): + def setup(self, session, resolver, fixers): binary_req = BinaryRequirement("python3") if not binary_req.met(session): resolver.install([binary_req]) - def build(self, session, resolver): - try: - run_detecting_problems(session, [self.path, 'build']) - except UnidentifiedError as e: - if ("The project was not configured: run \"waf configure\" first!" - in e.lines): - run_detecting_problems(session, [self.path, 'configure']) - run_detecting_problems(session, [self.path, 'build']) - else: - raise - - def dist(self, session, resolver, target_directory, quiet=False): - self.setup(session, resolver) + def dist(self, session, resolver, fixers, target_directory, quiet=False): + self.setup(session, resolver, fixers) with DistCatcher.default(session.external_path(".")) as dc: - run_detecting_problems(session, ["./waf", "dist"]) + run_with_build_fixers(session, ["./waf", "dist"], fixers) return dc.copy_single(target_directory) - def test(self, session, resolver): - self.setup(session, resolver) - run_detecting_problems(session, ["./waf", "test"]) + def test(self, session, resolver, fixers): + self.setup(session, resolver, fixers) + run_with_build_fixers(session, ["./waf", "test"], fixers) @classmethod def probe(cls, path): @@ -1090,25 +927,22 @@ class Gem(BuildSystem): def __init__(self, path): self.path = path - def dist(self, session, resolver, target_directory, quiet=False): + def dist(self, session, resolver, fixers, target_directory, quiet=False): gemfiles = [ - entry.name for entry in session.scandir(".") - if entry.name.endswith(".gem") + entry.name for entry in session.scandir(".") if entry.name.endswith(".gem") ] if len(gemfiles) > 1: logging.warning("More than one gemfile. Trying the first?") with DistCatcher.default(session.external_path(".")) as dc: - run_detecting_problems( + run_with_build_fixers( session, - [guaranteed_which(session, resolver, "gem2tgz"), - gemfiles[0]]) + [guaranteed_which(session, resolver, "gem2tgz"), gemfiles[0]], fixers) return dc.copy_single(target_directory) @classmethod def probe(cls, path): gemfiles = [ - entry.path for entry in os.scandir(path) - if entry.name.endswith(".gem") + entry.path for entry in os.scandir(path) if entry.name.endswith(".gem") ] if gemfiles: return cls(gemfiles[0]) @@ -1129,11 +963,9 @@ class DistZilla(BuildSystem): (key, value) = line[2:].strip().split(b"=", 1) except ValueError: continue - if (key.strip() == b"class" - and value.strip().startswith(b"'Dist::Inkt")): + if key.strip() == b"class" and value.strip().startswith(b"'Dist::Inkt"): logging.debug( - "Found Dist::Inkt section in dist.ini, " - "assuming distinkt." + "Found Dist::Inkt section in dist.ini, " "assuming distinkt." ) self.name = "dist-inkt" self.dist_inkt_class = value.decode().strip("'") @@ -1147,51 +979,39 @@ class DistZilla(BuildSystem): ] ) - def dist(self, session, resolver, target_directory, quiet=False): + def dist(self, session, resolver, fixers, target_directory, quiet=False): self.setup(resolver) if self.name == "dist-inkt": with DistCatcher.default(session.external_path(".")) as dc: - run_detecting_problems( - session, - [guaranteed_which(session, resolver, "distinkt-dist")]) + run_with_build_fixers(session, [guaranteed_which(session, resolver, "distinkt-dist")], fixers) return dc.copy_single(target_directory) else: # Default to invoking Dist::Zilla with DistCatcher.default(session.external_path(".")) as dc: - run_detecting_problems( - session, - [guaranteed_which(session, resolver, "dzil"), - "build", "--tgz"]) + run_with_build_fixers(session, [guaranteed_which(session, resolver, "dzil"), "build", "--tgz"], fixers) return dc.copy_single(target_directory) - def test(self, session, resolver): - # see also - # https://perlmaven.com/how-to-run-the-tests-of-a-typical-perl-module + def test(self, session, resolver, fixers): self.setup(resolver) - run_detecting_problems( - session, - [guaranteed_which(session, resolver, "dzil"), "test"]) + run_with_build_fixers(session, [guaranteed_which(session, resolver, "dzil"), "test"], fixers) - def build(self, session, resolver): + def build(self, session, resolver, fixers): self.setup(resolver) - run_detecting_problems( - session, - [guaranteed_which(session, resolver, "dzil"), "build"]) + run_with_build_fixers(session, [guaranteed_which(session, resolver, "dzil"), "build"], fixers) @classmethod def probe(cls, path): - if (os.path.exists(os.path.join(path, "dist.ini")) - and not os.path.exists(os.path.join(path, "Makefile.PL"))): + if os.path.exists(os.path.join(path, "dist.ini")) and not os.path.exists( + os.path.join(path, "Makefile.PL") + ): return cls(os.path.join(path, "dist.ini")) def get_declared_dependencies(self, session, fixers=None): if os.path.exists(os.path.join(self.path, "dist.ini")): - lines = run_with_build_fixers( - fixers, session, ["dzil", "authordeps"]) + lines = run_with_build_fixers(session, ["dzil", "authordeps"], fixers) for entry in lines: yield "build", PerlModuleRequirement(entry.strip()) - if os.path.exists( - os.path.join(os.path.dirname(self.path), "cpanfile")): + if os.path.exists(os.path.join(os.path.dirname(self.path), "cpanfile")): yield from _declared_deps_from_cpanfile(session, fixers) @@ -1210,24 +1030,22 @@ class RunTests(BuildSystem): if os.path.exists(os.path.join(path, "runtests.sh")): return cls(path) - def test(self, session, resolver): + def test(self, session, resolver, fixers): if shebang_binary(os.path.join(self.path, "runtests.sh")) is not None: - run_detecting_problems(session, ["./runtests.sh"]) + run_with_build_fixers(session, ["./runtests.sh"], fixers) else: - run_detecting_problems(session, ["/bin/bash", "./runtests.sh"]) + run_with_build_fixers(session, ["/bin/bash", "./runtests.sh"], fixers) def _read_cpanfile(session, args, kind, fixers): - for line in run_with_build_fixers( - fixers, session, ["cpanfile-dump"] + args): + for line in run_with_build_fixers(session, ["cpanfile-dump"] + args, fixers): line = line.strip() if line: yield kind, PerlModuleRequirement(line) def _declared_deps_from_cpanfile(session, fixers): - yield from _read_cpanfile( - session, ["--configure", "--build"], "build", fixers) + yield from _read_cpanfile(session, ["--configure", "--build"], "build", fixers) yield from _read_cpanfile(session, ["--test"], "test", fixers) @@ -1251,53 +1069,6 @@ def _declared_deps_from_meta_yml(f): # TODO(jelmer): recommends -class CMake(BuildSystem): - - name = "cmake" - - def __init__(self, path): - self.path = path - self.builddir = 'build' - - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.path) - - def setup(self, session, resolver): - if not session.exists(self.builddir): - session.mkdir(self.builddir) - try: - run_detecting_problems( - session, ["cmake", '.', '-B%s' % self.builddir]) - except Exception: - session.rmtree(self.builddir) - raise - - @classmethod - def probe(cls, path): - if os.path.exists(os.path.join(path, 'CMakeLists.txt')): - return cls(path) - return None - - def build(self, session, resolver): - self.setup(session, resolver) - run_detecting_problems( - session, ["cmake", "--build", self.builddir]) - - def install(self, session, resolver, install_target): - self.setup(session, resolver) - run_detecting_problems( - session, ["cmake", "--install", self.builddir]) - - def clean(self, session, resolver): - self.setup(session, resolver) - run_detecting_problems( - session, - ["cmake", "--build %s" % self.builddir, ".", "--target", "clean"]) - - def test(self, session, resolver): - raise NotImplementedError(self.test) - - class Make(BuildSystem): def __init__(self, path): @@ -1309,72 +1080,67 @@ class Make(BuildSystem): elif any([os.path.exists(os.path.join(path, n)) for n in ['configure.ac', 'configure.in', 'autogen.sh']]): self.name = 'autoconf' - elif any([n.name.endswith(".pro") for n in os.scandir(path)]): - self.name = 'qmake' + elif os.path.exists(os.path.join(path, "CMakeLists.txt")): + self.name = 'cmake' else: self.name = "make" def __repr__(self): return "%s(%r)" % (type(self).__name__, self.path) - def setup(self, session, resolver, prefix=None): + def setup(self, session, resolver, fixers, prefix=None): def makefile_exists(): return any( - [session.exists(p) - for p in ["Makefile", "GNUmakefile", "makefile"]] + [session.exists(p) for p in ["Makefile", "GNUmakefile", "makefile"]] ) if session.exists("Makefile.PL") and not makefile_exists(): - run_detecting_problems(session, ["perl", "Makefile.PL"]) + run_with_build_fixers(session, ["perl", "Makefile.PL"], fixers) if not makefile_exists() and not session.exists("configure"): if session.exists("autogen.sh"): - if shebang_binary( - os.path.join(self.path, "autogen.sh")) is None: - run_detecting_problems( - session, ["/bin/sh", "./autogen.sh"]) + if shebang_binary(os.path.join(self.path, "autogen.sh")) is None: + run_with_build_fixers(session, ["/bin/sh", "./autogen.sh"], fixers) try: - run_detecting_problems(session, ["./autogen.sh"]) + run_with_build_fixers(session, ["./autogen.sh"], fixers) except UnidentifiedError as e: if ( "Gnulib not yet bootstrapped; " "run ./bootstrap instead." in e.lines ): - run_detecting_problems(session, ["./bootstrap"]) - run_detecting_problems(session, ["./autogen.sh"]) + run_with_build_fixers(session, ["./bootstrap"], fixers) + run_with_build_fixers(session, ["./autogen.sh"], fixers) else: raise - elif (session.exists("configure.ac") - or session.exists("configure.in")): - run_detecting_problems(session, ["autoreconf", "-i"]) + elif session.exists("configure.ac") or session.exists("configure.in"): + run_with_build_fixers(session, ["autoreconf", "-i"], fixers) if not makefile_exists() and session.exists("configure"): extra_args = [] if prefix is not None: extra_args.append('--prefix=%s' % prefix) - run_detecting_problems(session, ["./configure"] + extra_args) + run_with_build_fixers(session, ["./configure"] + extra_args, fixers) if not makefile_exists() and any( [n.name.endswith(".pro") for n in session.scandir(".")] ): - run_detecting_problems(session, ["qmake"]) + run_with_build_fixers(session, ["qmake"], fixers) - def build(self, session, resolver): - self.setup(session, resolver) - if self.name == 'qmake': - default_target = None - else: - default_target = 'all' - self._run_make( - session, - [default_target] if default_target else []) + if not makefile_exists() and session.exists('CMakeLists.txt'): + if not session.exists("build"): + session.mkdir('build') + run_with_build_fixers(session, ["cmake", '..'], fixers, cwd='build') - def clean(self, session, resolver): - self.setup(session, resolver) - self._run_make(session, ["clean"]) + def build(self, session, resolver, fixers): + self.setup(session, resolver, fixers) + self._run_make(session, ["all"], fixers) - def _run_make(self, session, args, prefix=None): + def clean(self, session, resolver, fixers): + self.setup(session, resolver, fixers) + self._run_make(session, ["clean"], fixers) + + def _run_make(self, session, args, fixers, prefix=None): def _wants_configure(line): if line.startswith("Run ./configure"): return True @@ -1384,116 +1150,96 @@ class Make(BuildSystem): return True if line.startswith("The project was not configured"): return True - if re.match( - r'Makefile:[0-9]+: \*\*\* ' - r'You need to run \.\/configure .*', line): - return True return False - if session.exists('build/Makefile'): + if session.exists('build'): cwd = 'build' else: cwd = None try: - run_detecting_problems(session, ["make"] + args, cwd=cwd) + run_with_build_fixers(session, ["make"] + args, fixers, cwd=cwd) except UnidentifiedError as e: - if len(e.lines) < 5 and any( - [_wants_configure(line) for line in e.lines]): + if len(e.lines) < 5 and any([_wants_configure(line) for line in e.lines]): extra_args = [] if prefix is not None: extra_args.append("--prefix=%s" % prefix) - run_detecting_problems(session, ["./configure"] + extra_args) - run_detecting_problems(session, ["make"] + args) + run_with_build_fixers(session, ["./configure"] + extra_args, fixers) + run_with_build_fixers(session, ["make"] + args, fixers) elif ( "Reconfigure the source tree " "(via './config' or 'perl Configure'), please." ) in e.lines: - run_detecting_problems(session, ["./config"]) - run_detecting_problems(session, ["make"] + args) + run_with_build_fixers(session, ["./config"], fixers) + run_with_build_fixers(session, ["make"] + args, fixers) else: raise - def test(self, session, resolver): - self.setup(session, resolver) - for target in ["check", "test"]: - try: - self._run_make(session, [target]) - except UnidentifiedError as e: - if (("make: *** No rule to make target '%s'. Stop." % target) - in e.lines): - pass - else: - raise - else: - break - else: - if os.path.isdir('t'): - # See - # https://perlmaven.com/how-to-run-the-tests-of-a-typical-perl-module - run_detecting_problems(session, ["prove", "-b", "t/"]) - else: - logging.warning('No test target found') + def test(self, session, resolver, fixers): + self.setup(session, resolver, fixers) + self._run_make(session, ["check"], fixers) - def install(self, session, resolver, install_target): - self.setup(session, resolver, prefix=install_target.prefix) - self._run_make(session, ["install"], prefix=install_target.prefix) + def install(self, session, resolver, fixers, install_target): + self.setup(session, resolver, fixers, prefix=install_target.prefix) + self._run_make(session, ["install"], fixers, prefix=install_target.prefix) - def dist(self, session, resolver, target_directory, quiet=False): - self.setup(session, resolver) + def dist(self, session, resolver, fixers, target_directory, quiet=False): + self.setup(session, resolver, fixers) with DistCatcher.default(session.external_path(".")) as dc: try: - self._run_make(session, ["dist"]) + self._run_make(session, ["dist"], fixers) except UnidentifiedError as e: - if ("make: *** No rule to make target 'dist'. Stop." - in e.lines): + if "make: *** No rule to make target 'dist'. Stop." in e.lines: raise NotImplementedError - elif ("make[1]: *** No rule to make target 'dist'. Stop." - in e.lines): + elif "make[1]: *** No rule to make target 'dist'. Stop." in e.lines: raise NotImplementedError - elif ("ninja: error: unknown target 'dist', " - "did you mean 'dino'?" in e.lines): + elif "ninja: error: unknown target 'dist', did you mean 'dino'?" in e.lines: raise NotImplementedError elif ( "Please try running 'make manifest' and then run " "'make dist' again." in e.lines ): - run_detecting_problems(session, ["make", "manifest"]) - run_detecting_problems(session, ["make", "dist"]) + run_with_build_fixers(session, ["make", "manifest"], fixers) + run_with_build_fixers(session, ["make", "dist"], fixers) elif any( [ re.match( r"(Makefile|GNUmakefile|makefile):[0-9]+: " r"\*\*\* Missing \'Make.inc\' " - r"Run \'./configure \[options\]\' and retry. " - r"Stop.", line, + r"Run \'./configure \[options\]\' and retry. Stop.", + line, ) for line in e.lines ] ): - run_detecting_problems(session, ["./configure"]) - run_detecting_problems(session, ["make", "dist"]) + run_with_build_fixers(session, ["./configure"], fixers) + run_with_build_fixers(session, ["make", "dist"], fixers) elif any( [ re.match( - r"Problem opening MANIFEST: " - r"No such file or directory " - r"at .* line [0-9]+\.", line, + r"Problem opening MANIFEST: No such file or directory " + r"at .* line [0-9]+\.", + line, ) for line in e.lines ] ): - run_detecting_problems(session, ["make", "manifest"]) - run_detecting_problems(session, ["make", "dist"]) + run_with_build_fixers(session, ["make", "manifest"], fixers) + run_with_build_fixers(session, ["make", "dist"], fixers) else: raise return dc.copy_single(target_directory) def get_declared_dependencies(self, session, fixers=None): + something = False # TODO(jelmer): Split out the perl-specific stuff? if os.path.exists(os.path.join(self.path, "META.yml")): with open(os.path.join(self.path, "META.yml"), "rb") as f: yield from _declared_deps_from_meta_yml(f) + something = True if os.path.exists(os.path.join(self.path, "cpanfile")): yield from _declared_deps_from_cpanfile(session, fixers) + something = True + if not something: + raise NotImplementedError @classmethod def probe(cls, path): @@ -1505,6 +1251,7 @@ class Make(BuildSystem): "GNUmakefile", "makefile", "Makefile.PL", + "CMakeLists.txt", "autogen.sh", "configure.ac", "configure.in", @@ -1533,9 +1280,6 @@ class Cargo(BuildSystem): with open(path, "r") as f: self.cargo = load(f) - def install_declared_requirements(self, stages, session, resolver, fixers): - run_with_build_fixers(fixers, session, ["cargo", "fetch"]) - def get_declared_dependencies(self, session, fixers=None): if "dependencies" in self.cargo: for name, details in self.cargo["dependencies"].items(): @@ -1548,26 +1292,14 @@ class Cargo(BuildSystem): version=details.get("version"), ) - def test(self, session, resolver): - run_detecting_problems(session, ["cargo", "test"]) + def test(self, session, resolver, fixers): + run_with_build_fixers(session, ["cargo", "test"], fixers) - def clean(self, session, resolver): - run_detecting_problems(session, ["cargo", "clean"]) + def clean(self, session, resolver, fixers): + run_with_build_fixers(session, ["cargo", "clean"], fixers) - def build(self, session, resolver): - try: - run_detecting_problems(session, ["cargo", "generate"]) - except UnidentifiedError as e: - if e.lines != ['error: no such subcommand: `generate`']: - raise - run_detecting_problems(session, ["cargo", "build"]) - - def install(self, session, resolver, install_target): - args = [] - if install_target.prefix: - args.append('-root=%s' % install_target.prefix) - run_detecting_problems( - session, ["cargo", "install", "--path=."] + args) + def build(self, session, resolver, fixers): + run_with_build_fixers(session, ["cargo", "build"], fixers) @classmethod def probe(cls, path): @@ -1612,16 +1344,16 @@ class Golang(BuildSystem): def __repr__(self): return "%s()" % (type(self).__name__) - def test(self, session, resolver): - run_detecting_problems(session, ["go", "test", "./..."]) + def test(self, session, resolver, fixers): + run_with_build_fixers(session, ["go", "test", "./..."], fixers) - def build(self, session, resolver): - run_detecting_problems(session, ["go", "build"]) + def build(self, session, resolver, fixers): + run_with_build_fixers(session, ["go", "build"], fixers) - def install(self, session, resolver, install_target): - run_detecting_problems(session, ["go", "install"]) + def install(self, session, resolver, fixers): + run_with_build_fixers(session, ["go", "install"], fixers) - def clean(self, session, resolver): + def clean(self, session, resolver, fixers): session.check_call(["go", "clean"]) def get_declared_dependencies(self, session, fixers=None): @@ -1633,8 +1365,7 @@ class Golang(BuildSystem): yield "build", GoRequirement(parts[1]) elif parts[0] == "require": yield "build", GoPackageRequirement( - parts[1], - parts[2].lstrip("v") if len(parts) > 2 else None + parts[1], parts[2].lstrip("v") if len(parts) > 2 else None ) elif parts[0] == "exclude": pass # TODO(jelmer): Create conflicts? @@ -1643,8 +1374,7 @@ class Golang(BuildSystem): elif parts[0] == "module": pass else: - logging.warning( - "Unknown directive %s in go.mod", parts[0]) + logging.warning("Unknown directive %s in go.mod", parts[0]) @classmethod def probe(cls, path): @@ -1668,28 +1398,25 @@ class Maven(BuildSystem): def __init__(self, path): self.path = path - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.path) - @classmethod def probe(cls, path): if os.path.exists(os.path.join(path, "pom.xml")): logging.debug("Found pom.xml, assuming maven package.") return cls(os.path.join(path, "pom.xml")) - def test(self, session, resolver): - run_detecting_problems(session, ["mvn", "test"]) + def test(self, session, resolver, fixers): + run_with_build_fixers(session, ["mvn", "test"], fixers) - def clean(self, session, resolver): - run_detecting_problems(session, ["mvn", "clean"]) + def clean(self, session, resolver, fixers): + run_with_build_fixers(session, ["mvn", "clean"], fixers) - def install(self, session, resolver, install_target): - run_detecting_problems(session, ["mvn", "install"]) + def install(self, session, resolver, fixers, install_target): + run_with_build_fixers(session, ["mvn", "install"], fixers) - def build(self, session, resolver): - run_detecting_problems(session, ["mvn", "compile"]) + def build(self, session, resolver, fixers): + run_with_build_fixers(session, ["mvn", "compile"], fixers) - def dist(self, session, resolver, target_directory, quiet=False): + def dist(self, session, resolver, fixers, target_directory, quiet=False): # TODO(jelmer): 'mvn generate-sources' creates a jar in target/. # is that what we need? raise NotImplementedError @@ -1726,30 +1453,31 @@ class Cabal(BuildSystem): def __repr__(self): return "%s(%r)" % (type(self).__name__, self.path) - def _run(self, session, args): + def _run(self, session, args, fixers): try: - run_detecting_problems( - session, ["runhaskell", "Setup.hs"] + args) + run_with_build_fixers(session, ["runhaskell", "Setup.hs"] + args, fixers) except UnidentifiedError as e: if "Run the 'configure' command first." in e.lines: - run_detecting_problems( - session, ["runhaskell", "Setup.hs", "configure"]) - run_detecting_problems( - session, ["runhaskell", "Setup.hs"] + args) + run_with_build_fixers( + session, ["runhaskell", "Setup.hs", "configure"], fixers + ) + run_with_build_fixers( + session, ["runhaskell", "Setup.hs"] + args, fixers + ) else: raise - def test(self, session, resolver): - self._run(session, ["test"]) + def test(self, session, resolver, fixers): + self._run(session, ["test"], fixers) - def dist(self, session, resolver, target_directory, quiet=False): + def dist(self, session, resolver, fixers, target_directory, quiet=False): with DistCatcher( [ session.external_path("dist-newstyle/sdist"), session.external_path("dist"), ] ) as dc: - self._run(session, ["sdist"]) + self._run(session, ["sdist"], fixers) return dc.copy_single(target_directory) @classmethod @@ -1787,70 +1515,63 @@ class PerlBuildTiny(BuildSystem): def __repr__(self): return "%s(%r)" % (type(self).__name__, self.path) - def setup(self, session, fixers=None): - run_with_build_fixers(fixers, session, ["perl", "Build.PL"]) + def setup(self, session, fixers): + run_with_build_fixers(session, ["perl", "Build.PL"], fixers) - def test(self, session, resolver): - self.setup(session) + def test(self, session, resolver, fixers): + self.setup(session, fixers) if self.minilla: - run_detecting_problems(session, ["minil", "test"]) + run_with_build_fixers(session, ["minil", "test"], fixers) else: - run_detecting_problems(session, ["./Build", "test"]) + run_with_build_fixers(session, ["./Build", "test"], fixers) - def build(self, session, resolver): - self.setup(session) - run_detecting_problems(session, ["./Build", "build"]) + def build(self, session, resolver, fixers): + self.setup(session, fixers) + run_with_build_fixers(session, ["./Build", "build"], fixers) - def clean(self, session, resolver): - self.setup(session) - run_detecting_problems(session, ["./Build", "clean"]) + def clean(self, session, resolver, fixers): + self.setup(session, fixers) + run_with_build_fixers(session, ["./Build", "clean"], fixers) - def dist(self, session, resolver, target_directory, quiet=False): - self.setup(session) + def dist(self, session, resolver, fixers, target_directory, quiet=False): + self.setup(session, fixers) with DistCatcher([session.external_path('.')]) as dc: if self.minilla: - # minil seems to return 0 even if it didn't produce a tarball - # :( - run_detecting_problems( - session, ["minil", "dist"], + # minil seems to return 0 even if it didn't produce a tarball :( + run_with_build_fixers( + session, ["minil", "dist"], fixers, check_success=lambda retcode, lines: bool(dc.find_files())) else: try: - run_detecting_problems(session, ["./Build", "dist"]) + run_with_build_fixers(session, ["./Build", "dist"], fixers) except UnidentifiedError as e: - if ("Can't find dist packages without a MANIFEST file" - in e.lines): - run_detecting_problems( - session, ["./Build", "manifest"]) - run_detecting_problems(session, ["./Build", "dist"]) + if "Can't find dist packages without a MANIFEST file" in e.lines: + run_with_build_fixers(session, ["./Build", "manifest"], fixers) + run_with_build_fixers(session, ["./Build", "dist"], fixers) elif "No such action 'dist'" in e.lines: raise NotImplementedError else: raise return dc.copy_single(target_directory) - def install(self, session, resolver, install_target): - self.setup(session) + def install(self, session, resolver, fixers, install_target): + self.setup(session, fixers) if self.minilla: - run_detecting_problems(session, ["minil", "install"]) + run_with_build_fixers(session, ["minil", "install"], fixers) else: - run_detecting_problems(session, ["./Build", "install"]) + run_with_build_fixers(session, ["./Build", "install"], fixers) def get_declared_dependencies(self, session, fixers=None): self.setup(session, fixers) if self.minilla: - # Minilla doesn't seem to have a way to just regenerate the - # metadata :( - pass + pass # Minilla doesn't seem to have a way to just regenerate the metadata :( else: try: - run_with_build_fixers(fixers, session, ["./Build", "distmeta"]) + run_with_build_fixers(session, ["./Build", "distmeta"], fixers) except UnidentifiedError as e: if "No such action 'distmeta'" in e.lines: pass - if ("Do not run distmeta. " - "Install Minilla and `minil install` instead." - in e.lines): + if "Do not run distmeta. Install Minilla and `minil install` instead." in e.lines: self.minilla = True else: raise @@ -1863,12 +1584,11 @@ class PerlBuildTiny(BuildSystem): @classmethod def probe(cls, path): if os.path.exists(os.path.join(path, "Build.PL")): - logging.debug( - "Found Build.PL, assuming Module::Build::Tiny package.") + logging.debug("Found Build.PL, assuming Module::Build::Tiny package.") return cls(path) -BUILDSYSTEM_CLSES: List[Type[BuildSystem]] = [ +BUILDSYSTEM_CLSES = [ Pear, SetupPy, Npm, @@ -1885,8 +1605,6 @@ BUILDSYSTEM_CLSES: List[Type[BuildSystem]] = [ R, Octave, Bazel, - CMake, - GnomeShellExtension, # Make is intentionally at the end of the list. Make, Composer, @@ -1894,30 +1612,21 @@ BUILDSYSTEM_CLSES: List[Type[BuildSystem]] = [ ] -def lookup_buildsystem_cls(name: str) -> Type[BuildSystem]: - for bs in BUILDSYSTEM_CLSES: - if bs.name == name: - return bs - raise KeyError(name) - - -def scan_buildsystems(path: str) -> List[Tuple[str, BuildSystem]]: +def scan_buildsystems(path): """Detect build systems.""" ret = [] - ret.extend([(".", bs) for bs in detect_buildsystems(path) if bs]) + ret.extend([(".", bs) for bs in detect_buildsystems(path)]) if not ret: # Nothing found. Try the next level? for entry in os.scandir(path): if entry.is_dir(): - ret.extend([ - (entry.name, bs) - for bs in detect_buildsystems(entry.path)]) + ret.extend([(entry.name, bs) for bs in detect_buildsystems(entry.path)]) return ret -def detect_buildsystems(path: str) -> Iterable[BuildSystem]: +def detect_buildsystems(path): for bs_cls in BUILDSYSTEM_CLSES: bs = bs_cls.probe(path) if bs is not None: diff --git a/ognibuild/clean.py b/ognibuild/clean.py index 2f76390..6bbb3ee 100644 --- a/ognibuild/clean.py +++ b/ognibuild/clean.py @@ -15,25 +15,16 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from functools import partial - -from .fix_build import iterate_with_build_fixers from .buildsystem import NoBuildToolsFound -from .logs import NoLogManager -def run_clean(session, buildsystems, resolver, fixers, log_manager=None): +def run_clean(session, buildsystems, resolver, fixers): # Some things want to write to the user's home directory, # e.g. pip caches in ~/.cache session.create_home() - if log_manager is None: - log_manager = NoLogManager() - for buildsystem in buildsystems: - iterate_with_build_fixers( - fixers, log_manager.wrap( - partial(buildsystem.clean, session, resolver))) + buildsystem.clean(session, resolver, fixers) return raise NoBuildToolsFound() diff --git a/ognibuild/debian/__init__.py b/ognibuild/debian/__init__.py index cf18419..23a56a1 100644 --- a/ognibuild/debian/__init__.py +++ b/ognibuild/debian/__init__.py @@ -29,8 +29,7 @@ def satisfy_build_deps(session: Session, tree, debian_path): deps.append(source[name].strip().strip(",")) except KeyError: pass - for name in ["Build-Conflicts", "Build-Conflicts-Indep", - "Build-Conflicts-Arch"]: + for name in ["Build-Conflicts", "Build-Conflicts-Indep", "Build-Conflicts-Arch"]: try: deps.append("Conflicts: " + source[name]) except KeyError: diff --git a/ognibuild/debian/apt.py b/ognibuild/debian/apt.py index 52789e7..1e704b0 100644 --- a/ognibuild/debian/apt.py +++ b/ognibuild/debian/apt.py @@ -16,9 +16,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from debian.changelog import Version import logging -from typing import List, Optional, Iterable +from typing import List, Optional import os from buildlog_consultant.apt import ( @@ -38,12 +37,7 @@ from .file_search import ( def run_apt( session: Session, args: List[str], prefix: Optional[List[str]] = None ) -> None: - """Run apt. - - Raises: - DetailedFailure: When a known error occurs - UnidentifiedError: If an unknown error occurs - """ + """Run apt.""" if prefix is None: prefix = [] args = prefix = ["apt", "-y"] + args @@ -54,7 +48,7 @@ def run_apt( match, error = find_apt_get_failure(lines) if error is not None: raise DetailedFailure(retcode, args, error) - while lines and lines[-1].rstrip('\n') == "": + while lines and lines[-1] == "": lines.pop(-1) raise UnidentifiedError(retcode, args, lines, secondary=match) @@ -99,18 +93,13 @@ class AptManager(object): def package_exists(self, package): return package in self.apt_cache - def package_versions(self, package: str) -> Optional[Iterable[Version]]: - try: - return list(self.apt_cache[package].versions) - except KeyError: - return None + def package_versions(self, package): + return list(self.apt_cache[package].versions) - async def get_packages_for_paths( - self, paths, regex: bool = False, case_insensitive: bool = False): + def get_packages_for_paths(self, paths, regex=False, case_insensitive=False): logging.debug("Searching for packages containing %r", paths) - return await get_packages_for_paths( - paths, self.searchers(), regex=regex, - case_insensitive=case_insensitive + return get_packages_for_paths( + paths, self.searchers(), regex=regex, case_insensitive=case_insensitive ) def missing(self, packages): diff --git a/ognibuild/debian/build.py b/ognibuild/debian/build.py index eb655a5..29d689e 100644 --- a/ognibuild/debian/build.py +++ b/ognibuild/debian/build.py @@ -17,7 +17,6 @@ __all__ = [ "get_build_architecture", - "version_add_suffix", "add_dummy_changelog_entry", "build", "DetailedDebianBuildFailure", @@ -25,22 +24,20 @@ __all__ = [ ] from datetime import datetime +from debmutate.changelog import ChangelogEditor import logging import os import re import shlex import subprocess import sys -from typing import Optional, List, Tuple -from debian.changelog import Changelog, Version, ChangeBlock -from debmutate.changelog import get_maintainer, ChangelogEditor -from debmutate.reformatting import GeneratedFile +from debian.changelog import Changelog +from debmutate.changelog import get_maintainer from breezy.mutabletree import MutableTree from breezy.plugins.debian.builder import BuildFailedError from breezy.tree import Tree -from breezy.workingtree import WorkingTree from buildlog_consultant.sbuild import ( worker_failure_from_sbuild_log, @@ -48,18 +45,10 @@ from buildlog_consultant.sbuild import ( from .. import DetailedFailure as DetailedFailure, UnidentifiedError -BUILD_LOG_FILENAME = 'build.log' DEFAULT_BUILDER = "sbuild --no-clean-source" -class ChangelogNotEditable(Exception): - """Changelog can not be edited.""" - - def __init__(self, path): - self.path = path - - class DetailedDebianBuildFailure(DetailedFailure): def __init__(self, stage, phase, retcode, argv, error, description): @@ -71,8 +60,7 @@ class DetailedDebianBuildFailure(DetailedFailure): class UnidentifiedDebianBuildError(UnidentifiedError): - def __init__(self, stage, phase, retcode, argv, lines, description, - secondary=None): + def __init__(self, stage, phase, retcode, argv, lines, description, secondary=None): super(UnidentifiedDebianBuildError, self).__init__( retcode, argv, lines, secondary) self.stage = stage @@ -87,12 +75,11 @@ class MissingChangesFile(Exception): self.filename = filename -def find_changes_files(path: str, package: str, version: Version): - non_epoch_version = version.upstream_version or '' +def find_changes_files(path, package, version): + non_epoch_version = version.upstream_version if version.debian_version is not None: non_epoch_version += "-%s" % version.debian_version - c = re.compile('%s_%s_(.*).changes' % ( - re.escape(package), re.escape(non_epoch_version))) + c = re.compile('%s_%s_(.*).changes' % (re.escape(package), re.escape(non_epoch_version))) for entry in os.scandir(path): m = c.match(entry.name) if m: @@ -122,32 +109,15 @@ def control_files_in_root(tree: Tree, subpath: str) -> bool: return False -def version_add_suffix(version: Version, suffix: str) -> Version: - version = Version(str(version)) - - def add_suffix(v): - m = re.fullmatch("(.*)(" + re.escape(suffix) + ")([0-9]+)", v) - if m: - return m.group(1) + m.group(2) + "%d" % (int(m.group(3)) + 1) - else: - return v + suffix + "1" - if version.debian_revision: - version.debian_revision = add_suffix(version.debian_revision) - else: - version.upstream_version = add_suffix(version.upstream_version) - return version - - def add_dummy_changelog_entry( tree: MutableTree, subpath: str, suffix: str, suite: str, message: str, - timestamp: Optional[datetime] = None, - maintainer: Tuple[Optional[str], Optional[str]] = None, - allow_reformatting: bool = True, -) -> Version: + timestamp=None, + maintainer=None, +): """Add a dummy changelog entry to a package. Args: @@ -155,10 +125,18 @@ def add_dummy_changelog_entry( suffix: Suffix for the version suite: Debian suite message: Changelog message - Returns: - version of the newly added entry """ + def add_suffix(v, suffix): + m = re.fullmatch( + "(.*)(" + re.escape(suffix) + ")([0-9]+)", + v, + ) + if m: + return m.group(1) + m.group(2) + "%d" % (int(m.group(3)) + 1) + else: + return v + suffix + "1" + if control_files_in_root(tree, subpath): path = os.path.join(subpath, "changelog") else: @@ -167,38 +145,38 @@ def add_dummy_changelog_entry( maintainer = get_maintainer() if timestamp is None: timestamp = datetime.now() - try: - with ChangelogEditor( - tree.abspath(path), # type: ignore - allow_reformatting=allow_reformatting) as editor: - version = version_add_suffix(editor[0].version, suffix) - editor.auto_version(version, timestamp=timestamp) - editor.add_entry( - summary=[message], maintainer=maintainer, timestamp=timestamp, - urgency='low') - editor[0].distributions = suite - return version - except GeneratedFile as e: - raise ChangelogNotEditable(path) from e + with ChangelogEditor(tree.abspath(os.path.join(path))) as editor: + version = editor[0].version + if version.debian_revision: + version.debian_revision = add_suffix(version.debian_revision, suffix) + else: + version.upstream_version = add_suffix(version.upstream_version, suffix) + editor.auto_version(version, timestamp=timestamp) + editor.add_entry( + summary=[message], maintainer=maintainer, timestamp=timestamp, urgency='low') + editor[0].distributions = suite -def get_latest_changelog_entry( - local_tree: WorkingTree, subpath: str = "") -> ChangeBlock: +def get_latest_changelog_entry(local_tree, subpath=""): if control_files_in_root(local_tree, subpath): path = os.path.join(subpath, "changelog") else: path = os.path.join(subpath, "debian", "changelog") with local_tree.get_file(path) as f: cl = Changelog(f, max_blocks=1) - return cl[0] + return cl.package, cl.version -def _builddeb_command( - build_command: str = DEFAULT_BUILDER, - result_dir: Optional[str] = None, - apt_repository: Optional[str] = None, - apt_repository_key: Optional[str] = None, - extra_repositories: Optional[List[str]] = None): +def build( + local_tree, + outf, + build_command=DEFAULT_BUILDER, + result_dir=None, + distribution=None, + subpath="", + source_date_epoch=None, + extra_repositories=None, +): for repo in extra_repositories or []: build_command += " --extra-repository=" + shlex.quote(repo) args = [ @@ -209,34 +187,8 @@ def _builddeb_command( "--guess-upstream-branch-url", "--builder=%s" % build_command, ] - if apt_repository: - args.append("--apt-repository=%s" % apt_repository) - if apt_repository_key: - args.append("--apt-repository-key=%s" % apt_repository_key) if result_dir: args.append("--result-dir=%s" % result_dir) - return args - - -def build( - local_tree: WorkingTree, - outf, - build_command: str = DEFAULT_BUILDER, - result_dir: Optional[str] = None, - distribution: Optional[str] = None, - subpath: str = "", - source_date_epoch: Optional[int] = None, - apt_repository: Optional[str] = None, - apt_repository_key: Optional[str] = None, - extra_repositories: Optional[List[str]] = None, -): - args = _builddeb_command( - build_command=build_command, - result_dir=result_dir, - apt_repository=apt_repository, - apt_repository_key=apt_repository_key, - extra_repositories=extra_repositories) - outf.write("Running %r\n" % (build_command,)) outf.flush() env = dict(os.environ.items()) @@ -247,25 +199,22 @@ def build( logging.info("Building debian packages, running %r.", build_command) try: subprocess.check_call( - args, cwd=local_tree.abspath(subpath), stdout=outf, stderr=outf, - env=env + args, cwd=local_tree.abspath(subpath), stdout=outf, stderr=outf, env=env ) except subprocess.CalledProcessError: raise BuildFailedError() def build_once( - local_tree: WorkingTree, - build_suite: str, - output_directory: str, - build_command: str, - subpath: str = "", - source_date_epoch: Optional[int] = None, - apt_repository: Optional[str] = None, - apt_repository_key: Optional[str] = None, - extra_repositories: Optional[List[str]] = None + local_tree, + build_suite, + output_directory, + build_command, + subpath="", + source_date_epoch=None, + extra_repositories=None ): - build_log_path = os.path.join(output_directory, BUILD_LOG_FILENAME) + build_log_path = os.path.join(output_directory, "build.log") logging.debug("Writing build log to %s", build_log_path) try: with open(build_log_path, "w") as f: @@ -277,8 +226,6 @@ def build_once( distribution=build_suite, subpath=subpath, source_date_epoch=source_date_epoch, - apt_repository=apt_repository, - apt_repository_key=apt_repository_key, extra_repositories=extra_repositories, ) except BuildFailedError as e: @@ -300,39 +247,27 @@ def build_once( [], sbuild_failure.description) cl_entry = get_latest_changelog_entry(local_tree, subpath) - if cl_entry.package is None: - raise Exception('missing package in changelog entry') changes_names = [] - for kind, entry in find_changes_files( - output_directory, cl_entry.package, cl_entry.version): + for kind, entry in find_changes_files(output_directory, cl_entry.package, cl_entry.version): changes_names.append((entry.name)) return (changes_names, cl_entry) -class GitBuildpackageMissing(Exception): - """git-buildpackage is not installed""" - - def gbp_dch(path): - try: - subprocess.check_call(["gbp", "dch", "--ignore-branch"], cwd=path) - except FileNotFoundError: - raise GitBuildpackageMissing() + subprocess.check_call(["gbp", "dch", "--ignore-branch"], cwd=path) def attempt_build( - local_tree: WorkingTree, - suffix: str, - build_suite: str, - output_directory: str, - build_command: str, - build_changelog_entry: Optional[str] = None, - subpath: str = "", - source_date_epoch: Optional[int] = None, - run_gbp_dch: bool = False, - apt_repository: Optional[str] = None, - apt_repository_key: Optional[str] = None, - extra_repositories: Optional[List[str]] = None + local_tree, + suffix, + build_suite, + output_directory, + build_command, + build_changelog_entry=None, + subpath="", + source_date_epoch=None, + run_gbp_dch=False, + extra_repositories=None ): """Attempt a build, with a custom distribution set. @@ -347,7 +282,7 @@ def attempt_build( source_date_epoch: Source date epoch to set Returns: Tuple with (changes_name, cl_version) """ - if run_gbp_dch and not subpath and hasattr(local_tree.controldir, '_git'): + if run_gbp_dch and not subpath: gbp_dch(local_tree.abspath(subpath)) if build_changelog_entry is not None: add_dummy_changelog_entry( @@ -360,7 +295,5 @@ def attempt_build( build_command, subpath, source_date_epoch=source_date_epoch, - apt_repository=apt_repository, - apt_repository_key=apt_repository_key, extra_repositories=extra_repositories, ) diff --git a/ognibuild/debian/build_deps.py b/ognibuild/debian/build_deps.py index 0241f9f..246a058 100644 --- a/ognibuild/debian/build_deps.py +++ b/ognibuild/debian/build_deps.py @@ -18,46 +18,43 @@ """Tie breaking by build deps.""" -from debian.deb822 import PkgRelation import logging -from breezy.plugins.debian.apt_repo import LocalApt, NoAptSources - class BuildDependencyTieBreaker(object): - def __init__(self, apt): - self.apt = apt + def __init__(self, rootdir): + self.rootdir = rootdir self._counts = None def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.apt) + return "%s(%r)" % (type(self).__name__, self.rootdir) @classmethod def from_session(cls, session): - return cls(LocalApt(session.location)) + return cls(session.location) def _count(self): counts = {} - with self.apt: - for source in self.apt.iter_sources(): - for field in ['Build-Depends', 'Build-Depends-Indep', - 'Build-Depends-Arch']: - for r in PkgRelation.parse_relations( - source.get(field, '')): - for p in r: - counts.setdefault(p['name'], 0) - counts[p['name']] += 1 + import apt_pkg + + apt_pkg.init() + apt_pkg.config.set("Dir", self.rootdir) + apt_cache = apt_pkg.SourceRecords() + apt_cache.restart() + while apt_cache.step(): + try: + for d in apt_cache.build_depends.values(): + for o in d: + for p in o: + counts.setdefault(p[0], 0) + counts[p[0]] += 1 + except AttributeError: + pass return counts def __call__(self, reqs): if self._counts is None: - try: - self._counts = self._count() - except NoAptSources: - logging.warning( - "No 'deb-src' in sources.list, " - "unable to break build-depends") - return None + self._counts = self._count() by_count = {} for req in reqs: try: @@ -83,5 +80,5 @@ if __name__ == "__main__": parser.add_argument("req", nargs="+") args = parser.parse_args() reqs = [AptRequirement.from_str(req) for req in args.req] - tie_breaker = BuildDependencyTieBreaker(LocalApt()) + tie_breaker = BuildDependencyTieBreaker("/") print(tie_breaker(reqs)) diff --git a/ognibuild/debian/file_search.py b/ognibuild/debian/file_search.py index f9422c7..5276835 100644 --- a/ognibuild/debian/file_search.py +++ b/ognibuild/debian/file_search.py @@ -17,13 +17,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import apt_pkg -import asyncio from datetime import datetime from debian.deb822 import Release import os import re import subprocess -from typing import List, AsyncIterator +from typing import Iterator, List import logging @@ -33,15 +32,11 @@ from ..session import Session class FileSearcher(object): def search_files( - self, path: str, regex: bool = False, - case_insensitive: bool = False) -> AsyncIterator[str]: + self, path: str, regex: bool = False, case_insensitive: bool = False + ) -> Iterator[str]: raise NotImplementedError(self.search_files) -class AptFileAccessError(Exception): - """Apt file access error.""" - - class ContentsFileNotFound(Exception): """The contents file was not found.""" @@ -76,8 +71,7 @@ def contents_urls_from_sources_entry(source, arches, load_url): response = load_url(release_url) except FileNotFoundError as e: logging.warning( - "Unable to download %s or %s: %s", inrelease_url, - release_url, e + "Unable to download %s or %s: %s", inrelease_url, release_url, e ) return @@ -124,7 +118,7 @@ def _unwrap(f, ext): def load_direct_url(url): - from urllib.error import HTTPError, URLError + from urllib.error import HTTPError from urllib.request import urlopen, Request for ext in [".xz", ".gz", ""]: @@ -134,11 +128,7 @@ def load_direct_url(url): except HTTPError as e: if e.status == 404: continue - raise AptFileAccessError( - 'Unable to access apt URL %s: %s' % (url + ext, e)) - except URLError as e: - raise AptFileAccessError( - 'Unable to access apt URL %s: %s' % (url + ext, e)) + raise break else: raise FileNotFoundError(url) @@ -197,7 +187,7 @@ class AptFileFileSearcher(FileSearcher): @classmethod def from_session(cls, session): - logging.debug('Using apt-file to search apt contents') + logging.info('Using apt-file to search apt contents') if not os.path.exists(session.external_path(cls.CACHE_IS_EMPTY_PATH)): from .apt import AptManager AptManager.from_session(session).install(['apt-file']) @@ -205,7 +195,7 @@ class AptFileFileSearcher(FileSearcher): session.check_call(['apt-file', 'update'], user='root') return cls(session) - async def search_files(self, path, regex=False, case_insensitive=False): + def search_files(self, path, regex=False, case_insensitive=False): args = [] if regex: args.append('-x') @@ -214,17 +204,15 @@ class AptFileFileSearcher(FileSearcher): if case_insensitive: args.append('-i') args.append(path) - process = await asyncio.create_subprocess_exec( - '/usr/bin/apt-file', 'search', *args, - stdout=asyncio.subprocess.PIPE) - (output, error) = await process.communicate(input=None) - if process.returncode == 1: - # No results - return - elif process.returncode == 3: - raise Exception('apt-file cache is empty') - elif process.returncode != 0: - raise Exception("unexpected return code %d" % process.returncode) + try: + output = self.session.check_output(['/usr/bin/apt-file', 'search'] + args) + except subprocess.CalledProcessError as e: + if e.returncode == 1: + # No results + return + if e.returncode == 3: + raise Exception('apt-file cache is empty') + raise for line in output.splitlines(False): pkg, path = line.split(b': ') @@ -265,8 +253,7 @@ class RemoteContentsFileSearcher(FileSearcher): return load_url_with_cache(url, cache_dirs) urls = list( - contents_urls_from_sourceslist( - sl, get_build_architecture(), load_url) + contents_urls_from_sourceslist(sl, get_build_architecture(), load_url) ) self._load_urls(urls, cache_dirs, load_url) @@ -290,8 +277,8 @@ class RemoteContentsFileSearcher(FileSearcher): return load_url_with_cache(url, cache_dirs) urls = list( - contents_urls_from_sourceslist( - sl, get_build_architecture(), load_url)) + contents_urls_from_sourceslist(sl, get_build_architecture(), load_url) + ) self._load_urls(urls, cache_dirs, load_url) def _load_urls(self, urls, cache_dirs, load_url): @@ -299,16 +286,13 @@ class RemoteContentsFileSearcher(FileSearcher): try: f = load_url(url) self.load_file(f, url) - except ConnectionResetError: - logging.warning("Connection reset error retrieving %s", url) - # TODO(jelmer): Retry? except ContentsFileNotFound: logging.warning("Unable to fetch contents file %s", url) def __setitem__(self, path, package): self._db[path] = package - async def search_files(self, path, regex=False, case_insensitive=False): + def search_files(self, path, regex=False, case_insensitive=False): path = path.lstrip("/").encode("utf-8", "surrogateescape") if case_insensitive and not regex: regex = True @@ -354,9 +338,9 @@ class GeneratedFileSearcher(FileSearcher): (path, pkg) = line.strip().split(None, 1) self._db.append(path, pkg) - async def search_files( - self, path: str, regex: bool = False, - case_insensitive: bool = False): + def search_files( + self, path: str, regex: bool = False, case_insensitive: bool = False + ) -> Iterator[str]: for p, pkg in self._db: if regex: flags = 0 @@ -387,17 +371,16 @@ GENERATED_FILE_SEARCHER = GeneratedFileSearcher( ) -async def get_packages_for_paths( +def get_packages_for_paths( paths: List[str], searchers: List[FileSearcher], regex: bool = False, case_insensitive: bool = False, ) -> List[str]: candidates: List[str] = list() - # TODO(jelmer): Combine these, perhaps by creating one gigantic regex? for path in paths: for searcher in searchers: - async for pkg in searcher.search_files( + for pkg in searcher.search_files( path, regex=regex, case_insensitive=case_insensitive ): if pkg not in candidates: @@ -410,10 +393,8 @@ def main(argv): from ..session.plain import PlainSession parser = argparse.ArgumentParser() - parser.add_argument( - "path", help="Path to search for.", type=str, nargs="*") - parser.add_argument( - "--regex", "-x", help="Search for regex.", action="store_true") + parser.add_argument("path", help="Path to search for.", type=str, nargs="*") + parser.add_argument("--regex", "-x", help="Search for regex.", action="store_true") parser.add_argument("--debug", action="store_true") args = parser.parse_args() @@ -422,14 +403,13 @@ def main(argv): else: logging.basicConfig(level=logging.INFO) - with PlainSession() as session: - main_searcher = get_apt_contents_file_searcher(session) - searchers = [main_searcher, GENERATED_FILE_SEARCHER] + main_searcher = get_apt_contents_file_searcher(PlainSession()) + main_searcher.load_local() + searchers = [main_searcher, GENERATED_FILE_SEARCHER] - packages = asyncio.run(get_packages_for_paths( - args.path, searchers=searchers, regex=args.regex)) - for package in packages: - print(package) + packages = get_packages_for_paths(args.path, searchers=searchers, regex=args.regex) + for package in packages: + print(package) if __name__ == "__main__": diff --git a/ognibuild/debian/fix_build.py b/ognibuild/debian/fix_build.py index 8e8a76b..ffb064d 100644 --- a/ognibuild/debian/fix_build.py +++ b/ognibuild/debian/fix_build.py @@ -22,10 +22,10 @@ __all__ = [ from functools import partial import logging import os +import re import shutil import sys -import time -from typing import List, Set, Optional, Type, Tuple +from typing import List, Set, Optional, Type from debian.deb822 import ( Deb822, @@ -34,8 +34,6 @@ from debian.deb822 import ( from breezy.commit import PointlessCommit, NullCommitReporter from breezy.tree import Tree -from breezy.workingtree import WorkingTree - from debmutate.changelog import ChangelogEditor from debmutate.control import ( ensure_relation, @@ -52,7 +50,49 @@ from debmutate.reformatting import ( GeneratedFile, ) -from breezy.workspace import reset_tree +try: + from breezy.workspace import reset_tree +except ImportError: # breezy < 3.2 + + def delete_items(deletables, dry_run=False): + """Delete files in the deletables iterable""" + import errno + import shutil + + def onerror(function, path, excinfo): + """Show warning for errors seen by rmtree.""" + # Handle only permission error while removing files. + # Other errors are re-raised. + if function is not os.remove or excinfo[1].errno != errno.EACCES: + raise + logging.warning("unable to remove %s" % path) + + for path, subp in deletables: + if os.path.isdir(path): + shutil.rmtree(path, onerror=onerror) + else: + try: + os.unlink(path) + except OSError as e: + # We handle only permission error here + if e.errno != errno.EACCES: + raise e + logging.warning('unable to remove "%s": %s.', path, e.strerror) + + def reset_tree(local_tree, subpath=""): + from breezy.transform import revert + from breezy.clean_tree import iter_deletables + + revert( + local_tree, + local_tree.branch.basis_tree(), + [subpath] if subpath not in (".", "") else None, + ) + deletables = list( + iter_deletables(local_tree, unknown=True, ignored=False, detritus=False) + ) + delete_items(deletables) + from debmutate._rules import ( dh_invoke_add_with, @@ -73,21 +113,18 @@ from buildlog_consultant.common import ( ) from buildlog_consultant.sbuild import ( DebcargoUnacceptablePredicate, - DebcargoUnacceptableComparator, ) from .build import ( DetailedDebianBuildFailure, UnidentifiedDebianBuildError, ) -from ..logs import rotate_logfile from ..buildlog import problem_to_upstream_requirement from ..fix_build import BuildFixer, resolve_error from ..resolver.apt import ( AptRequirement, ) -from .apt import AptManager -from .build import attempt_build, DEFAULT_BUILDER, BUILD_LOG_FILENAME +from .build import attempt_build, DEFAULT_BUILDER DEFAULT_MAX_ITERATIONS = 10 @@ -113,9 +150,7 @@ class DebianPackagingContext(object): def abspath(self, *parts): return self.tree.abspath(os.path.join(self.subpath, *parts)) - def commit( - self, summary: str, - update_changelog: Optional[bool] = None) -> bool: + def commit(self, summary: str, update_changelog: Optional[bool] = None) -> bool: if update_changelog is None: update_changelog = self.update_changelog with self.tree.lock_write(): @@ -179,11 +214,6 @@ def add_dependency(context, phase, requirement: AptRequirement): return add_test_dependency(context, phase[1], requirement) elif phase[0] == "build": return add_build_dependency(context, requirement) - elif phase[0] == "buildenv": - # TODO(jelmer): Actually, we probably just want to install it on the - # host system? - logging.warning("Unknown phase %r", phase) - return False else: logging.warning("Unknown phase %r", phase) return False @@ -201,19 +231,16 @@ def add_build_dependency(context, requirement: AptRequirement): raise CircularDependency(binary["Package"]) for rel in requirement.relations: updater.source["Build-Depends"] = ensure_relation( - updater.source.get("Build-Depends", ""), - PkgRelation.str([rel]) + updater.source.get("Build-Depends", ""), PkgRelation.str([rel]) ) except FormattingUnpreservable as e: - logging.info( - "Unable to edit %s in a way that preserves formatting.", e.path) + logging.info("Unable to edit %s in a way that preserves formatting.", e.path) return False desc = requirement.pkg_relation_str() if not updater.changed: - logging.info( - "Giving up; build dependency %s was already present.", desc) + logging.info("Giving up; dependency %s was already present.", desc) return False logging.info("Adding build dependency: %s", desc) @@ -245,18 +272,13 @@ def add_test_dependency(context, testname, requirement): control.get("Depends", ""), PkgRelation.str([rel]) ) except FormattingUnpreservable as e: - logging.info( - "Unable to edit %s in a way that preserves formatting.", e.path) + logging.info("Unable to edit %s in a way that preserves formatting.", e.path) + return False + if not updater.changed: return False desc = requirement.pkg_relation_str() - if not updater.changed: - logging.info( - "Giving up; dependency %s for test %s was already present.", - desc, testname) - return False - logging.info("Adding dependency to test %s: %s", testname, desc) return context.commit( "Add missing dependency for test %s on %s." % (testname, desc), @@ -266,8 +288,7 @@ def add_test_dependency(context, testname, requirement): def targeted_python_versions(tree: Tree, subpath: str) -> List[str]: with tree.get_file(os.path.join(subpath, "debian/control")) as f: control = Deb822(f) - build_depends = PkgRelation.parse_relations( - control.get("Build-Depends", "")) + build_depends = PkgRelation.parse_relations(control.get("Build-Depends", "")) all_build_deps: Set[str] = set() for or_deps in build_depends: all_build_deps.update(or_dep["name"] for or_dep in or_deps) @@ -291,7 +312,7 @@ def python_tie_breaker(tree, subpath, reqs): return True if pkg.startswith("lib%s-" % python_version): return True - if pkg == r'lib%s-dev' % python_version: + if re.match(r'lib%s\.[0-9]-dev' % python_version, pkg): return True return False @@ -316,8 +337,7 @@ def retry_apt_failure(error, phase, apt, context): def enable_dh_autoreconf(context, phase): # Debhelper >= 10 depends on dh-autoreconf and enables autoreconf by # default. - debhelper_compat_version = get_debhelper_compat_level( - context.tree.abspath(".")) + debhelper_compat_version = get_debhelper_compat_level(context.tree.abspath(".")) if debhelper_compat_version is not None and debhelper_compat_version < 10: def add_with_autoreconf(line, target): @@ -336,8 +356,9 @@ def enable_dh_autoreconf(context, phase): def fix_missing_configure(error, phase, context): - if (not context.tree.has_filename("configure.ac") - and not context.tree.has_filename("configure.in")): + if not context.tree.has_filename("configure.ac") and not context.tree.has_filename( + "configure.in" + ): return False return enable_dh_autoreconf(context, phase) @@ -412,7 +433,7 @@ def fix_missing_makefile_pl(error, phase, context): return False -def debcargo_coerce_unacceptable_prerelease(error, phase, context): +def coerce_unacceptable_predicate(error, phase, context): from debmutate.debcargo import DebcargoEditor with DebcargoEditor(context.abspath('debian/debcargo.toml')) as editor: editor['allow_prerelease_deps'] = True @@ -440,8 +461,7 @@ class SimpleBuildFixer(BuildFixer): class DependencyBuildFixer(BuildFixer): - def __init__(self, packaging_context, apt_resolver, - problem_cls: Type[Problem], fn): + def __init__(self, packaging_context, apt_resolver, problem_cls: Type[Problem], fn): self.context = packaging_context self.apt_resolver = apt_resolver self._problem_cls = problem_cls @@ -461,47 +481,32 @@ class DependencyBuildFixer(BuildFixer): return self._fn(problem, phase, self.apt_resolver, self.context) -def versioned_package_fixers(session, packaging_context, apt: AptManager): +def versioned_package_fixers(session, packaging_context, apt): return [ PgBuildExtOutOfDateControlFixer(packaging_context, session, apt), - SimpleBuildFixer( - packaging_context, MissingConfigure, fix_missing_configure), + SimpleBuildFixer(packaging_context, MissingConfigure, fix_missing_configure), SimpleBuildFixer( packaging_context, MissingAutomakeInput, fix_missing_automake_input ), SimpleBuildFixer( - packaging_context, MissingConfigStatusInput, - fix_missing_config_status_input + packaging_context, MissingConfigStatusInput, fix_missing_config_status_input ), - SimpleBuildFixer( - packaging_context, MissingPerlFile, fix_missing_makefile_pl), - SimpleBuildFixer( - packaging_context, DebcargoUnacceptablePredicate, - debcargo_coerce_unacceptable_prerelease), - SimpleBuildFixer( - packaging_context, DebcargoUnacceptableComparator, - debcargo_coerce_unacceptable_prerelease), + SimpleBuildFixer(packaging_context, MissingPerlFile, fix_missing_makefile_pl), + SimpleBuildFixer(packaging_context, DebcargoUnacceptablePredicate, coerce_unacceptable_predicate), ] -def apt_fixers(apt: AptManager, packaging_context, - dep_server_url: Optional[str] = None) -> List[BuildFixer]: +def apt_fixers(apt, packaging_context) -> List[BuildFixer]: from ..resolver.apt import AptResolver from .udd import popcon_tie_breaker from .build_deps import BuildDependencyTieBreaker apt_tie_breakers = [ - partial(python_tie_breaker, packaging_context.tree, - packaging_context.subpath), + partial(python_tie_breaker, packaging_context.tree, packaging_context.subpath), BuildDependencyTieBreaker.from_session(apt.session), popcon_tie_breaker, ] - resolver: AptResolver - if dep_server_url: - from ..resolver.dep_server import DepServerAptResolver - resolver = DepServerAptResolver(apt, dep_server_url, apt_tie_breakers) - else: - resolver = AptResolver(apt, apt_tie_breakers) + resolver = AptResolver(apt, apt_tie_breakers) return [ DependencyBuildFixer( packaging_context, apt, AptFetchFailure, retry_apt_failure @@ -510,49 +515,38 @@ def apt_fixers(apt: AptManager, packaging_context, ] -def default_fixers( - local_tree: WorkingTree, - subpath: str, apt: AptManager, - committer: Optional[str] = None, - update_changelog: Optional[bool] = None, - dep_server_url: Optional[str] = None): +def default_fixers(local_tree, subpath, apt, committer=None, update_changelog=None): packaging_context = DebianPackagingContext( local_tree, subpath, committer, update_changelog, commit_reporter=NullCommitReporter() ) - return (versioned_package_fixers(apt.session, packaging_context, apt) - + apt_fixers(apt, packaging_context, dep_server_url)) + return versioned_package_fixers(apt.session, packaging_context, apt) + apt_fixers( + apt, packaging_context + ) def build_incrementally( - local_tree: WorkingTree, - apt: AptManager, - suffix: str, - build_suite: str, - output_directory: str, - build_command: str, + local_tree, + apt, + suffix, + build_suite, + output_directory, + build_command, build_changelog_entry, - committer: Optional[str] = None, - max_iterations: int = DEFAULT_MAX_ITERATIONS, - subpath: str = "", + committer=None, + max_iterations=DEFAULT_MAX_ITERATIONS, + subpath="", source_date_epoch=None, - update_changelog: bool = True, - apt_repository: Optional[str] = None, - apt_repository_key: Optional[str] = None, - extra_repositories: Optional[List[str]] = None, - fixers: Optional[List[BuildFixer]] = None, - run_gbp_dch: Optional[bool] = None, - dep_server_url: Optional[str] = None, + update_changelog=True, + extra_repositories=None, + fixers=None ): - fixed_errors: List[Tuple[Problem, str]] = [] + fixed_errors = [] if fixers is None: fixers = default_fixers( local_tree, subpath, apt, committer=committer, - update_changelog=update_changelog, - dep_server_url=dep_server_url) + update_changelog=update_changelog) logging.info("Using fixers: %r", fixers) - if run_gbp_dch is None: - run_gbp_dch = (update_changelog is False) while True: try: return attempt_build( @@ -564,9 +558,7 @@ def build_incrementally( build_changelog_entry, subpath=subpath, source_date_epoch=source_date_epoch, - run_gbp_dch=run_gbp_dch, - apt_repository=apt_repository, - apt_repository_key=apt_repository_key, + run_gbp_dch=(update_changelog is False), extra_repositories=extra_repositories, ) except UnidentifiedDebianBuildError: @@ -577,19 +569,15 @@ def build_incrementally( logging.info("No relevant context, not making any changes.") raise if (e.error, e.phase) in fixed_errors: - logging.warning( - "Error was still not fixed on second try. Giving up.") + logging.warning("Error was still not fixed on second try. Giving up.") raise - if (max_iterations is not None - and len(fixed_errors) > max_iterations): - logging.warning( - "Last fix did not address the issue. Giving up.") + if max_iterations is not None and len(fixed_errors) > max_iterations: + logging.warning("Last fix did not address the issue. Giving up.") raise reset_tree(local_tree, subpath=subpath) try: if not resolve_error(e.error, e.phase, fixers): - logging.warning( - "Failed to resolve error %r. Giving up.", e.error) + logging.warning("Failed to resolve error %r. Giving up.", e.error) raise except GeneratedFile: logging.warning( @@ -600,71 +588,71 @@ def build_incrementally( raise e except CircularDependency: logging.warning( - "Unable to fix %r; it would introduce a circular " - "dependency.", + "Unable to fix %r; it would introduce a circular " "dependency.", e.error, ) raise e fixed_errors.append((e.error, e.phase)) - rotate_logfile(os.path.join(output_directory, BUILD_LOG_FILENAME)) + if os.path.exists(os.path.join(output_directory, "build.log")): + i = 1 + while os.path.exists( + os.path.join(output_directory, "build.log.%d" % i) + ): + i += 1 + target_path = os.path.join(output_directory, "build.log.%d" % i) + os.rename(os.path.join(output_directory, "build.log"), target_path) + logging.debug("Storing build log at %s", target_path) def main(argv=None): import argparse parser = argparse.ArgumentParser("ognibuild.debian.fix_build") - modifications = parser.add_argument_group('Modifications') - modifications.add_argument( - "--suffix", type=str, help="Suffix to use for test builds.", - default="fixbuild1" + parser.add_argument( + "--suffix", type=str, help="Suffix to use for test builds.", default="fixbuild1" ) - modifications.add_argument( + parser.add_argument( "--suite", type=str, help="Suite to target.", default="unstable" ) - modifications.add_argument( - "--committer", type=str, help="Committer string (name and email)", - default=None + parser.add_argument( + "--output-directory", type=str, help="Output directory.", default=None ) - modifications.add_argument( + parser.add_argument( + "--committer", type=str, help="Committer string (name and email)", default=None + ) + parser.add_argument( + "--build-command", + type=str, + help="Build command", + default=(DEFAULT_BUILDER + " -A -s -v"), + ) + parser.add_argument( "--no-update-changelog", action="store_false", default=None, dest="update_changelog", help="do not update the changelog", ) - modifications.add_argument( + parser.add_argument( + '--max-iterations', + type=int, + default=DEFAULT_MAX_ITERATIONS, + help='Maximum number of issues to attempt to fix before giving up.') + parser.add_argument( "--update-changelog", action="store_true", dest="update_changelog", help="force updating of the changelog", default=None, ) - build_behaviour = parser.add_argument_group('Build Behaviour') - build_behaviour.add_argument( - "--output-directory", type=str, help="Output directory.", default=None - ) - build_behaviour.add_argument( - "--build-command", - type=str, - help="Build command", - default=(DEFAULT_BUILDER + " -A -s -v"), - ) - - build_behaviour.add_argument( - '--max-iterations', - type=int, - default=DEFAULT_MAX_ITERATIONS, - help='Maximum number of issues to attempt to fix before giving up.') - build_behaviour.add_argument("--schroot", type=str, help="chroot to use.") - parser.add_argument( - "--dep-server-url", type=str, - help="ognibuild dep server to use", - default=os.environ.get('OGNIBUILD_DEPS')) + parser.add_argument("--schroot", type=str, help="chroot to use.") parser.add_argument("--verbose", action="store_true", help="Be verbose") args = parser.parse_args() + from breezy.workingtree import WorkingTree import breezy.git # noqa: F401 import breezy.bzr # noqa: F401 + from .apt import AptManager from ..session.plain import PlainSession from ..session.schroot import SchrootSession import tempfile @@ -681,10 +669,6 @@ def main(argv=None): logging.info("Using output directory %s", output_directory) else: output_directory = args.output_directory - if not os.path.isdir(output_directory): - parser.error( - 'output directory %s is not a directory' - % output_directory) tree = WorkingTree.open(".") if args.schroot: @@ -708,7 +692,6 @@ def main(argv=None): committer=args.committer, update_changelog=args.update_changelog, max_iterations=args.max_iterations, - dep_server_url=args.dep_server_url, ) except DetailedDebianBuildFailure as e: if e.phase is None: @@ -718,21 +701,6 @@ def main(argv=None): else: phase = "%s (%s)" % (e.phase[0], e.phase[1]) logging.fatal("Error during %s: %s", phase, e.error) - if not args.output_directory: - xdg_cache_dir = os.environ.get( - 'XDG_CACHE_HOME', os.path.expanduser('~/.cache')) - buildlogs_dir = os.path.join( - xdg_cache_dir, 'ognibuild', 'buildlogs') - os.makedirs(buildlogs_dir, exist_ok=True) - target_log_file = os.path.join( - buildlogs_dir, - '%s-%s.log' % ( - os.path.basename(getattr(tree, 'basedir', 'build')), - time.strftime('%Y-%m-%d_%H%M%s'))) - shutil.copy( - os.path.join(output_directory, 'build.log'), - target_log_file) - logging.info('Build log available in %s', target_log_file) return 1 except UnidentifiedDebianBuildError as e: if e.phase is None: diff --git a/ognibuild/debian/udd.py b/ognibuild/debian/udd.py index 3789585..0c73818 100644 --- a/ognibuild/debian/udd.py +++ b/ognibuild/debian/udd.py @@ -35,8 +35,7 @@ class UDD(object): def get_most_popular(self, packages): cursor = self._conn.cursor() cursor.execute( - "SELECT package FROM popcon " - "WHERE package IN %s ORDER BY insts DESC LIMIT 1", + "SELECT package FROM popcon WHERE package IN %s ORDER BY insts DESC LIMIT 1", (tuple(packages),), ) return cursor.fetchone()[0] @@ -55,8 +54,7 @@ def popcon_tie_breaker(candidates): names = {list(c.package_names())[0]: c for c in candidates} winner = udd.get_most_popular(list(names.keys())) if winner is None: - logging.warning( - "No relevant popcon information found, not ranking by popcon") + logging.warning("No relevant popcon information found, not ranking by popcon") return None logging.info("Picked winner using popcon") return names[winner] diff --git a/ognibuild/dep_server.py b/ognibuild/dep_server.py deleted file mode 100644 index 86ece6f..0000000 --- a/ognibuild/dep_server.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2022 Jelmer Vernooij -# encoding: utf-8 -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import logging -import sys - -from aiohttp import web -from aiohttp_openmetrics import setup_metrics - -from . import Requirement, UnknownRequirementFamily -from .debian.apt import AptManager -from .resolver.apt import resolve_requirement_apt - -SUPPORTED_RELEASES = ['unstable', 'sid'] - - -routes = web.RouteTableDef() - - -@routes.get('/health', name='health') -async def handle_health(request): - return web.Response(text='ok') - - -@routes.get('/families', name='families') -async def handle_families(request): - return web.json_response(list(Requirement._JSON_DESERIALIZERS.keys())) - - -@routes.post('/resolve-apt', name='resolve-apt') -async def handle_apt(request): - js = await request.json() - try: - req_js = js['requirement'] - except KeyError: - raise web.HTTPBadRequest(text="json missing 'requirement' key") - release = js.get('release') - if release and release not in SUPPORTED_RELEASES: - return web.json_response( - {"reason": "unsupported-release", "release": release}, - status=404) - try: - req = Requirement.from_json(req_js) - except UnknownRequirementFamily as e: - return web.json_response( - {"reason": "family-unknown", "family": e.family}, status=404) - apt_reqs = await resolve_requirement_apt(request.app['apt_mgr'], req) - return web.json_response([r.pkg_relation_str() for r in apt_reqs]) - - -@routes.get('/resolve-apt/{release}/{family}:{arg}', name='resolve-apt-simple') -async def handle_apt_simple(request): - if request.match_info['release'] not in SUPPORTED_RELEASES: - return web.json_response( - {"reason": "unsupported-release", - "release": request.match_info['release']}, - status=404) - try: - req = Requirement.from_json( - (request.match_info['family'], request.match_info['arg'])) - except UnknownRequirementFamily as e: - return web.json_response( - {"reason": "family-unknown", "family": e.family}, status=404) - apt_reqs = await resolve_requirement_apt(request.app['apt_mgr'], req) - return web.json_response([r.pkg_relation_str() for r in apt_reqs]) - - -def main(): - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('--listen-address', type=str, help='Listen address') - parser.add_argument('--schroot', type=str, help='Schroot session to use') - parser.add_argument('--port', type=str, help='Listen port', default=9934) - parser.add_argument('--debug', action='store_true') - parser.add_argument( - "--gcp-logging", action='store_true', help='Use Google cloud logging.') - args = parser.parse_args() - - if args.gcp_logging: - import google.cloud.logging - client = google.cloud.logging.Client() - client.get_default_handler() - client.setup_logging() - else: - if args.debug: - log_level = logging.DEBUG - else: - log_level = logging.INFO - - logging.basicConfig( - level=log_level, - format="[%(asctime)s] %(message)s", - datefmt="%Y-%m-%d %H:%M:%S") - - if args.schroot: - from .session.schroot import SchrootSession - session = SchrootSession(args.schroot) - else: - from .session.plain import PlainSession - session = PlainSession() - with session: - app = web.Application() - app.router.add_routes(routes) - app['apt_mgr'] = AptManager.from_session(session) - setup_metrics(app) - - web.run_app(app, host=args.listen_address, port=args.port) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/ognibuild/dist.py b/ognibuild/dist.py index 0eeb8f9..8206920 100644 --- a/ognibuild/dist.py +++ b/ognibuild/dist.py @@ -18,19 +18,18 @@ __all__ = [ "UnidentifiedError", "DetailedFailure", - "run_dist", - "create_dist_schroot", "create_dist", - "dist", + "create_dist_schroot", ] import errno -from functools import partial import logging import os import sys from typing import Optional, List +from debian.deb822 import Deb822 + from breezy.tree import Tree from breezy.workingtree import WorkingTree @@ -38,78 +37,16 @@ from buildlog_consultant.common import ( NoSpaceOnDevice, ) -from debian.deb822 import Deb822 - from . import DetailedFailure, UnidentifiedError from .dist_catcher import DistNoTarball -from .fix_build import iterate_with_build_fixers -from .logs import LogManager, NoLogManager from .buildsystem import NoBuildToolsFound from .resolver import auto_resolver from .session import Session from .session.schroot import SchrootSession -DIST_LOG_FILENAME = 'dist.log' - - -def run_dist(session, buildsystems, resolver, fixers, target_directory, - quiet=False, log_manager=None): - # Some things want to write to the user's home directory, - # e.g. pip caches in ~/.cache - session.create_home() - - logging.info('Using dependency resolver: %s', resolver) - - if log_manager is None: - log_manager = NoLogManager() - - for buildsystem in buildsystems: - filename = iterate_with_build_fixers(fixers, log_manager.wrap( - partial(buildsystem.dist, session, resolver, target_directory, - quiet=quiet))) - return filename - - raise NoBuildToolsFound() - - -def dist(session, export_directory, reldir, target_dir, log_manager, *, - version: Optional[str] = None, quiet=False): - from .fix_build import BuildFixer - from .buildsystem import detect_buildsystems - from .buildlog import InstallFixer - from .fixers import ( - GitIdentityFixer, - MissingGoSumEntryFixer, - SecretGpgKeyFixer, - UnexpandedAutoconfMacroFixer, - GnulibDirectoryFixer, - ) - - if version: - # TODO(jelmer): Shouldn't include backend-specific code here - os.environ['SETUPTOOLS_SCM_PRETEND_VERSION'] = version - - # TODO(jelmer): use scan_buildsystems to also look in subdirectories - buildsystems = list(detect_buildsystems(export_directory)) - resolver = auto_resolver(session) - fixers: List[BuildFixer] = [ - UnexpandedAutoconfMacroFixer(session, resolver), - GnulibDirectoryFixer(session), - MissingGoSumEntryFixer(session)] - - fixers.append(InstallFixer(resolver)) - - if session.is_temporary: - # Only muck about with temporary sessions - fixers.extend([ - GitIdentityFixer(session), - SecretGpgKeyFixer(session), - ]) - - session.chdir(reldir) - +def run_dist(session, buildsystems, resolver, fixers, target_directory, quiet=False): # Some things want to write to the user's home directory, # e.g. pip caches in ~/.cache session.create_home() @@ -117,34 +54,31 @@ def dist(session, export_directory, reldir, target_dir, log_manager, *, logging.info('Using dependency resolver: %s', resolver) for buildsystem in buildsystems: - filename = iterate_with_build_fixers(fixers, log_manager.wrap( - partial( - buildsystem.dist, session, resolver, target_dir, - quiet=quiet))) + filename = buildsystem.dist( + session, resolver, fixers, target_directory, quiet=quiet + ) return filename raise NoBuildToolsFound() -# This is the function used by debianize() def create_dist( session: Session, tree: Tree, target_dir: str, include_controldir: bool = True, subdir: Optional[str] = None, - log_manager: Optional[LogManager] = None, - version: Optional[str] = None, + cleanup: bool = False, ) -> Optional[str]: - """Create a dist tarball for a tree. + from .buildsystem import detect_buildsystems + from .buildlog import InstallFixer + from .fix_build import BuildFixer + from .fixers import ( + GitIdentityFixer, + SecretGpgKeyFixer, + UnexpandedAutoconfMacroFixer, + ) - Args: - session: session to run it - tree: Tree object to work in - target_dir: Directory to write tarball into - include_controldir: Whether to include the version control directory - subdir: subdirectory in the tree to operate in - """ if subdir is None: subdir = "package" try: @@ -156,11 +90,19 @@ def create_dist( raise DetailedFailure(1, ["mkdtemp"], NoSpaceOnDevice()) raise - if log_manager is None: - log_manager = NoLogManager() + # TODO(jelmer): use scan_buildsystems to also look in subdirectories + buildsystems = list(detect_buildsystems(export_directory)) + resolver = auto_resolver(session) + fixers: List[BuildFixer] = [UnexpandedAutoconfMacroFixer(session, resolver)] - return dist(session, export_directory, reldir, target_dir, - log_manager=log_manager, version=version) + fixers.append(InstallFixer(resolver)) + + if session.is_temporary: + # Only muck about with temporary sessions + fixers.extend([GitIdentityFixer(session), SecretGpgKeyFixer(session)]) + + session.chdir(reldir) + return run_dist(session, buildsystems, resolver, fixers, target_dir) def create_dist_schroot( @@ -171,35 +113,30 @@ def create_dist_schroot( packaging_subpath: Optional[str] = None, include_controldir: bool = True, subdir: Optional[str] = None, - log_manager: Optional[LogManager] = None, + cleanup: bool = False, ) -> Optional[str]: - """Create a dist tarball for a tree. - - Args: - session: session to run it - tree: Tree object to work in - target_dir: Directory to write tarball into - include_controldir: Whether to include the version control directory - subdir: subdirectory in the tree to operate in - """ with SchrootSession(chroot) as session: if packaging_tree is not None: from .debian import satisfy_build_deps satisfy_build_deps(session, packaging_tree, packaging_subpath) return create_dist( - session, tree, target_dir, - include_controldir=include_controldir, subdir=subdir, - log_manager=log_manager) + session, + tree, + target_dir, + include_controldir=include_controldir, + subdir=subdir, + cleanup=cleanup, + ) -def main(argv=None): +if __name__ == "__main__": import argparse import breezy.bzr # noqa: F401 import breezy.git # noqa: F401 from breezy.export import export - parser = argparse.ArgumentParser(argv) + parser = argparse.ArgumentParser() parser.add_argument( "--chroot", default="unstable-amd64-sbuild", @@ -220,12 +157,8 @@ def main(argv=None): "--target-directory", type=str, default="..", help="Target directory" ) parser.add_argument("--verbose", action="store_true", help="Be verbose") - parser.add_argument("--mode", choices=["auto", "vcs", "buildsystem"], - type=str, - help="Mechanism to use to create buildsystem") parser.add_argument( - "--include-controldir", action="store_true", - help="Clone rather than export." + "--include-controldir", action="store_true", help="Clone rather than export." ) args = parser.parse_args() @@ -236,10 +169,6 @@ def main(argv=None): logging.basicConfig(level=logging.INFO, format="%(message)s") tree = WorkingTree.open(args.directory) - - packaging_tree: Optional[WorkingTree] - subdir: Optional[str] - if args.packaging_directory: packaging_tree = WorkingTree.open(args.packaging_directory) with packaging_tree.lock_read(): @@ -250,47 +179,30 @@ def main(argv=None): packaging_tree = None subdir = None - if args.mode == 'vcs': + try: + ret = create_dist_schroot( + tree, + subdir=subdir, + target_dir=os.path.abspath(args.target_directory), + packaging_tree=packaging_tree, + chroot=args.chroot, + include_controldir=args.include_controldir, + ) + except (NoBuildToolsFound, NotImplementedError): + logging.info("No build tools found, falling back to simple export.") export(tree, "dist.tar.gz", "tgz", None) - elif args.mode in ('auto', 'buildsystem'): - try: - ret = create_dist_schroot( - tree, - subdir=subdir, - target_dir=os.path.abspath(args.target_directory), - packaging_tree=packaging_tree, - chroot=args.chroot, - include_controldir=args.include_controldir, - ) - except NoBuildToolsFound: - if args.mode == 'buildsystem': - logging.fatal('No build tools found, unable to create tarball') - return 1 - logging.info( - "No build tools found, falling back to simple export.") - export(tree, "dist.tar.gz", "tgz", None) - except NotImplementedError: - if args.mode == 'buildsystem': - logging.fatal('Unable to ask buildsystem for tarball') - return 1 - logging.info( - "Build system does not support dist tarball creation, " - "falling back to simple export." - ) - export(tree, "dist.tar.gz", "tgz", None) - except UnidentifiedError as e: - logging.fatal("Unidentified error: %r", e.lines) - return 1 - except DetailedFailure as e: - logging.fatal("Identified error during dist creation: %s", e.error) - return 1 - except DistNoTarball: - logging.fatal("dist operation did not create a tarball") - return 1 - else: - logging.info("Created %s", ret) - return 0 - - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) + except NotImplementedError: + logging.info( + "Build system does not support dist tarball creation, " + "falling back to simple export." + ) + export(tree, "dist.tar.gz", "tgz", None) + except UnidentifiedError as e: + logging.fatal("Unidentified error: %r", e.lines) + except DetailedFailure as e: + logging.fatal("Identified error during dist creation: %s", e.error) + except DistNoTarball: + logging.fatal("dist operation did not create a tarball") + else: + logging.info("Created %s", ret) + sys.exit(0) diff --git a/ognibuild/dist_catcher.py b/ognibuild/dist_catcher.py index 646d819..b2546a1 100644 --- a/ognibuild/dist_catcher.py +++ b/ognibuild/dist_catcher.py @@ -54,8 +54,7 @@ class DistCatcher(object): @classmethod def default(cls, directory): return cls( - [os.path.join(directory, "dist"), directory, - os.path.join(directory, "..")] + [os.path.join(directory, "dist"), directory, os.path.join(directory, "..")] ) def __enter__(self): @@ -88,23 +87,19 @@ class DistCatcher(object): continue if len(possible_new) == 1: entry = possible_new[0] - logging.info( - "Found new tarball %s in %s.", entry.name, directory) + logging.info("Found new tarball %s in %s.", entry.name, directory) self.files.append(entry.path) return entry.name elif len(possible_new) > 1: logging.warning( - "Found multiple tarballs %r in %s.", possible_new, - directory + "Found multiple tarballs %r in %s.", possible_new, directory ) self.files.extend([entry.path for entry in possible_new]) return possible_new[0].name if len(possible_updated) == 1: entry = possible_updated[0] - logging.info( - "Found updated tarball %s in %s.", entry.name, - directory) + logging.info("Found updated tarball %s in %s.", entry.name, directory) self.files.append(entry.path) return entry.name diff --git a/ognibuild/fix_build.py b/ognibuild/fix_build.py index 527273c..4863375 100644 --- a/ognibuild/fix_build.py +++ b/ognibuild/fix_build.py @@ -17,7 +17,7 @@ from functools import partial import logging -from typing import List, Tuple, Callable, Optional, TypeVar +from typing import List, Tuple, Callable, Any, Optional from buildlog_consultant import Problem from buildlog_consultant.common import ( @@ -29,14 +29,6 @@ from . import DetailedFailure, UnidentifiedError from .session import Session, run_with_tee -# Number of attempts to fix a build before giving up. -DEFAULT_LIMIT = 200 - - -class FixerLimitReached(Exception): - """The maximum number of fixes has been reached.""" - - class BuildFixer(object): """Build fixer.""" @@ -52,11 +44,7 @@ class BuildFixer(object): return self._fix(problem, phase) -def run_detecting_problems( - session: Session, args: List[str], check_success=None, - quiet=False, **kwargs) -> List[str]: - if not quiet: - logging.info('Running %r', args) +def run_detecting_problems(session: Session, args: List[str], check_success=None, **kwargs): if check_success is None: def check_success(retcode, contents): return (retcode == 0) @@ -75,26 +63,17 @@ def run_detecting_problems( logging.warning("Build failed with unidentified error:") logging.warning("%s", match.line.rstrip("\n")) else: - logging.warning( - "Build failed and unable to find cause. Giving up.") + logging.warning("Build failed and unable to find cause. Giving up.") raise UnidentifiedError(retcode, args, lines, secondary=match) raise DetailedFailure(retcode, args, error) -T = TypeVar('T') - - -def iterate_with_build_fixers( - fixers: List[BuildFixer], - cb: Callable[[], T], limit=DEFAULT_LIMIT) -> T: +def iterate_with_build_fixers(fixers: List[BuildFixer], cb: Callable[[], Any]): """Call cb() until there are no more DetailedFailures we can fix. Args: fixers: List of fixers to use to resolve issues - cb: Callable to run the build - limit: Maximum number of fixing attempts before giving up """ - attempts = 0 fixed_errors = [] while True: to_resolve = [] @@ -107,13 +86,9 @@ def iterate_with_build_fixers( logging.info("Identified error: %r", f.error) if f.error in fixed_errors: logging.warning( - "Failed to resolve error %r, it persisted. Giving up.", - f.error + "Failed to resolve error %r, it persisted. Giving up.", f.error ) raise f - attempts += 1 - if limit is not None and limit <= attempts: - raise FixerLimitReached(limit) try: resolved = resolve_error(f.error, None, fixers=fixers) except DetailedFailure as n: @@ -125,25 +100,23 @@ def iterate_with_build_fixers( else: if not resolved: logging.warning( - "Failed to find resolution for error %r. Giving up.", - f.error + "Failed to find resolution for error %r. Giving up.", f.error ) raise f fixed_errors.append(f.error) def run_with_build_fixers( - fixers: Optional[List[BuildFixer]], session: Session, args: List[str], - quiet: bool = False, **kwargs -) -> List[str]: + session: Session, args: List[str], fixers: Optional[List[BuildFixer]], **kwargs +): if fixers is None: fixers = [] return iterate_with_build_fixers( - fixers, - partial(run_detecting_problems, session, args, quiet=quiet, **kwargs)) + fixers, partial(run_detecting_problems, session, args, **kwargs) + ) -def resolve_error(error, phase, fixers) -> bool: +def resolve_error(error, phase, fixers): relevant_fixers = [] for fixer in fixers: if fixer.can_fix(error): diff --git a/ognibuild/fixers.py b/ognibuild/fixers.py index b3337db..c413f20 100644 --- a/ognibuild/fixers.py +++ b/ognibuild/fixers.py @@ -21,10 +21,8 @@ from typing import Tuple from buildlog_consultant import Problem from buildlog_consultant.common import ( MissingGitIdentity, - MissingGoSumEntry, MissingSecretGpgKey, MissingAutoconfMacro, - MissingGnulibDirectory, ) from ognibuild.requirements import AutoconfMacroRequirement from ognibuild.resolver import UnsatisfiedRequirements @@ -32,18 +30,6 @@ from ognibuild.resolver import UnsatisfiedRequirements from .fix_build import BuildFixer -class GnulibDirectoryFixer(BuildFixer): - def __init__(self, session): - self.session = session - - def can_fix(self, problem: Problem): - return isinstance(problem, MissingGnulibDirectory) - - def _fix(self, problem: Problem, phase: Tuple[str, ...]): - self.session.check_call(["./gnulib.sh"]) - return True - - class GitIdentityFixer(BuildFixer): def __init__(self, session): self.session = session @@ -91,26 +77,6 @@ Passphrase: "" return False -class MissingGoSumEntryFixer(BuildFixer): - def __init__(self, session): - self.session = session - - def __repr__(self): - return "%s()" % (type(self).__name__) - - def __str__(self): - return "missing go.sum entry fixer" - - def can_fix(self, error): - return isinstance(error, MissingGoSumEntry) - - def _fix(self, error, phase): - from .fix_build import run_detecting_problems - run_detecting_problems( - self.session, ["go", "mod", "download", error.package]) - return True - - class UnexpandedAutoconfMacroFixer(BuildFixer): def __init__(self, session, resolver): self.session = session diff --git a/ognibuild/info.py b/ognibuild/info.py index ae349bc..c0bc425 100644 --- a/ognibuild/info.py +++ b/ognibuild/info.py @@ -21,13 +21,11 @@ def run_info(session, buildsystems, fixers=None): print("%r:" % buildsystem) deps = {} try: - for kind, dep in buildsystem.get_declared_dependencies( - session, fixers=fixers): + for kind, dep in buildsystem.get_declared_dependencies(session, fixers=fixers): deps.setdefault(kind, []).append(dep) except NotImplementedError: print( - "\tUnable to detect declared dependencies for this type of " - "build system" + "\tUnable to detect declared dependencies for this type of build system" ) if deps: print("\tDeclared dependencies:") @@ -37,11 +35,9 @@ def run_info(session, buildsystems, fixers=None): print("\t\t\t%s" % dep) print("") try: - outputs = list(buildsystem.get_declared_outputs( - session, fixers=fixers)) + outputs = list(buildsystem.get_declared_outputs(session, fixers=fixers)) except NotImplementedError: - print("\tUnable to detect declared outputs for this type of " - "build system") + print("\tUnable to detect declared outputs for this type of build system") outputs = [] if outputs: print("\tDeclared outputs:") diff --git a/ognibuild/install.py b/ognibuild/install.py index be71846..d242ad7 100644 --- a/ognibuild/install.py +++ b/ognibuild/install.py @@ -15,34 +15,21 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from functools import partial +from .buildsystem import NoBuildToolsFound, InstallTarget from typing import Optional -from .buildsystem import NoBuildToolsFound, InstallTarget -from .fix_build import iterate_with_build_fixers -from .logs import NoLogManager - -def run_install( - session, buildsystems, resolver, fixers, *, user: bool = False, - prefix: Optional[str] = None, log_manager=None): +def run_install(session, buildsystems, resolver, fixers, user: bool = False, prefix: Optional[str] = None): # Some things want to write to the user's home directory, # e.g. pip caches in ~/.cache session.create_home() - if log_manager is None: - log_manager = NoLogManager() - install_target = InstallTarget() install_target.user = user install_target.prefix = prefix for buildsystem in buildsystems: - iterate_with_build_fixers( - fixers, - log_manager.wrap( - partial(buildsystem.install, session, resolver, - install_target))) + buildsystem.install(session, resolver, fixers, install_target) return raise NoBuildToolsFound() diff --git a/ognibuild/logs.py b/ognibuild/logs.py deleted file mode 100644 index dd0d412..0000000 --- a/ognibuild/logs.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2018 Jelmer Vernooij -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -from contextlib import contextmanager -import subprocess -import logging -import os -import sys - - -@contextmanager -def copy_output(output_log: str, tee: bool = False): - old_stdout = os.dup(sys.stdout.fileno()) - old_stderr = os.dup(sys.stderr.fileno()) - if tee: - p = subprocess.Popen(["tee", output_log], stdin=subprocess.PIPE) - newfd = p.stdin - else: - newfd = open(output_log, 'wb') - os.dup2(newfd.fileno(), sys.stdout.fileno()) # type: ignore - os.dup2(newfd.fileno(), sys.stderr.fileno()) # type: ignore - try: - yield - finally: - sys.stdout.flush() - sys.stderr.flush() - os.dup2(old_stdout, sys.stdout.fileno()) - os.dup2(old_stderr, sys.stderr.fileno()) - if newfd is not None: - newfd.close() - - -@contextmanager -def redirect_output(to_file): - sys.stdout.flush() - sys.stderr.flush() - old_stdout = os.dup(sys.stdout.fileno()) - old_stderr = os.dup(sys.stderr.fileno()) - os.dup2(to_file.fileno(), sys.stdout.fileno()) # type: ignore - os.dup2(to_file.fileno(), sys.stderr.fileno()) # type: ignore - try: - yield - finally: - sys.stdout.flush() - sys.stderr.flush() - os.dup2(old_stdout, sys.stdout.fileno()) - os.dup2(old_stderr, sys.stderr.fileno()) - - -def rotate_logfile(source_path: str) -> None: - if os.path.exists(source_path): - (directory_path, name) = os.path.split(source_path) - i = 1 - while os.path.exists( - os.path.join(directory_path, "%s.%d" % (name, i))): - i += 1 - target_path = os.path.join(directory_path, "%s.%d" % (name, i)) - os.rename(source_path, target_path) - logging.debug("Storing previous build log at %s", target_path) - - -class LogManager(object): - - def wrap(self, fn): - raise NotImplementedError(self.wrap) - - -class DirectoryLogManager(LogManager): - - def __init__(self, path, mode): - self.path = path - self.mode = mode - - def wrap(self, fn): - def _run(*args, **kwargs): - rotate_logfile(self.path) - if self.mode == 'copy': - with copy_output(self.path, tee=True): - return fn(*args, **kwargs) - elif self.mode == 'redirect': - with copy_output(self.path, tee=False): - return fn(*args, **kwargs) - else: - raise NotImplementedError(self.mode) - return _run - - -class NoLogManager(LogManager): - - def wrap(self, fn): - return fn diff --git a/ognibuild/requirements.py b/ognibuild/requirements.py index 31e81ec..29475b9 100644 --- a/ognibuild/requirements.py +++ b/ognibuild/requirements.py @@ -26,19 +26,16 @@ from . import Requirement class PythonPackageRequirement(Requirement): - family = "python-package" - package: str - def __init__( - self, package, python_version=None, specs=None, - minimum_version=None): + def __init__(self, package, python_version=None, specs=None, minimum_version=None): + super(PythonPackageRequirement, self).__init__("python-package") self.package = package self.python_version = python_version + if minimum_version is not None: + specs = [(">=", minimum_version)] if specs is None: specs = [] - if minimum_version is not None: - specs.append((">=", minimum_version)) self.specs = specs def __repr__(self): @@ -56,29 +53,11 @@ class PythonPackageRequirement(Requirement): return "python package: %s" % (self.package,) @classmethod - def from_requirement_str(cls, text, python_version=None): + def from_requirement_str(cls, text): from requirements.requirement import Requirement req = Requirement.parse(text) - return cls( - package=req.name, specs=req.specs, python_version=python_version) - - def requirement_str(self): - if self.specs: - return '%s;%s' % ( - self.package, ','.join([''.join(s) for s in self.specs])) - return self.package - - @classmethod - def _from_json(cls, js): - if isinstance(js, str): - return cls.from_requirement_str(js) - return cls.from_requirement_str(js[0], python_version=js[1]) - - def _json(self): - if self.python_version: - return [self.requirement_str(), self.python_version] - return self.requirement_str() + return cls(package=req.name, specs=req.specs) def met(self, session): if self.python_version == "cpython3": @@ -95,8 +74,7 @@ class PythonPackageRequirement(Requirement): raise NotImplementedError text = self.package + ",".join(["".join(spec) for spec in self.specs]) p = session.Popen( - [cmd, "-c", - "import pkg_resources; pkg_resources.require(%r)" % text], + [cmd, "-c", "import pkg_resources; pkg_resources.require(%r)" % text], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) @@ -104,33 +82,16 @@ class PythonPackageRequirement(Requirement): return p.returncode == 0 -Requirement.register_json(PythonPackageRequirement) - - class LatexPackageRequirement(Requirement): - family = "latex-package" - def __init__(self, package: str): self.package = package def __repr__(self): return "%s(%r)" % (type(self).__name__, self.package) - def _json(self): - return self.package - - def _from_json(cls, package): - return cls(package) - - -Requirement.register_json(LatexPackageRequirement) - class PhpPackageRequirement(Requirement): - - family = "php-package" - def __init__( self, package: str, @@ -143,13 +104,6 @@ class PhpPackageRequirement(Requirement): self.min_version = min_version self.max_version = max_version - def _json(self): - return (self.package, self.channel, self.min_version, self.max_version) - - @classmethod - def _from_json(cls, js): - return cls(*js) - def __repr__(self): return "%s(%r, %r, %r, %r)" % ( type(self).__name__, @@ -160,24 +114,14 @@ class PhpPackageRequirement(Requirement): ) -Requirement.register_json(PhpPackageRequirement) - - class BinaryRequirement(Requirement): - family = "binary" binary_name: str def __init__(self, binary_name): + super(BinaryRequirement, self).__init__("binary") self.binary_name = binary_name - def _json(self): - return self.binary_name - - @classmethod - def _from_json(cls, js): - return cls(js) - def __repr__(self): return "%s(%r)" % (type(self).__name__, self.binary_name) @@ -191,54 +135,14 @@ class BinaryRequirement(Requirement): return p.returncode == 0 -Requirement.register_json(BinaryRequirement) - - -class PHPExtensionRequirement(Requirement): - - family = "php-extension" - extension: str - - def __init__(self, extension: str): - self.extension = extension - - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.extension) - - -class PytestPluginRequirement(Requirement): - - family = "pytest-plugin" - - plugin: str - - def __init__(self, plugin: str): - self.plugin = plugin - - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.plugin) - - -class VcsControlDirectoryAccessRequirement(Requirement): - - vcs: List[str] - family = "vcs-access" - - def __init__(self, vcs): - self.vcs = vcs - - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.vcs) - - class PerlModuleRequirement(Requirement): module: str filename: Optional[str] inc: Optional[List[str]] - family = "perl-module" def __init__(self, module, filename=None, inc=None): + super(PerlModuleRequirement, self).__init__("perl-module") self.module = module self.filename = filename self.inc = inc @@ -254,10 +158,10 @@ class PerlModuleRequirement(Requirement): class VagueDependencyRequirement(Requirement): name: str - family = "vague" minimum_version: Optional[str] = None def __init__(self, name, minimum_version=None): + super(VagueDependencyRequirement, self).__init__("vague") self.name = name self.minimum_version = minimum_version @@ -265,26 +169,19 @@ class VagueDependencyRequirement(Requirement): if " " not in self.name: yield BinaryRequirement(self.name) yield LibraryRequirement(self.name) - yield PkgConfigRequirement( - self.name, minimum_version=self.minimum_version) + yield PkgConfigRequirement(self.name, minimum_version=self.minimum_version) if self.name.lower() != self.name: yield BinaryRequirement(self.name.lower()) yield LibraryRequirement(self.name.lower()) - yield PkgConfigRequirement( - self.name.lower(), minimum_version=self.minimum_version) - try: - from .resolver.apt import AptRequirement - except ModuleNotFoundError: - pass + yield PkgConfigRequirement(self.name.lower(), minimum_version=self.minimum_version) + from .resolver.apt import AptRequirement + + yield AptRequirement.simple(self.name.lower(), minimum_version=self.minimum_version) + if self.name.lower().startswith('lib'): + devname = '%s-dev' % self.name.lower() else: - yield AptRequirement.simple( - self.name.lower(), minimum_version=self.minimum_version) - if self.name.lower().startswith('lib'): - devname = '%s-dev' % self.name.lower() - else: - devname = 'lib%s-dev' % self.name.lower() - yield AptRequirement.simple( - devname, minimum_version=self.minimum_version) + devname = 'lib%s-dev' % self.name.lower() + yield AptRequirement.simple(devname, minimum_version=self.minimum_version) def met(self, session): for x in self.expand(): @@ -295,36 +192,19 @@ class VagueDependencyRequirement(Requirement): def __repr__(self): return "%s(%r)" % (type(self).__name__, self.name) - def __str__(self): - if self.minimum_version: - return "%s >= %s" % (self.name, self.minimum_version) - return self.name - class NodePackageRequirement(Requirement): package: str - family = "npm-package" def __init__(self, package): + super(NodePackageRequirement, self).__init__("npm-package") self.package = package def __repr__(self): return "%s(%r)" % (type(self).__name__, self.package) -class LuaModuleRequirement(Requirement): - - module: str - family = "lua-module" - - def __init__(self, module): - self.module = module - - def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.module) - - class PerlPreDeclaredRequirement(Requirement): name: str @@ -347,9 +227,8 @@ class PerlPreDeclaredRequirement(Requirement): 'auto_set_bugtracker': 'Module::Install::Bugtracker', } - family = "perl-predeclared" - def __init__(self, name): + super(PerlPreDeclaredRequirement, self).__init__("perl-predeclared") self.name = name def lookup_module(self): @@ -363,9 +242,9 @@ class PerlPreDeclaredRequirement(Requirement): class NodeModuleRequirement(Requirement): module: str - family = "npm-module" def __init__(self, module): + super(NodeModuleRequirement, self).__init__("npm-module") self.module = module def __repr__(self): @@ -376,45 +255,41 @@ class CargoCrateRequirement(Requirement): crate: str features: Set[str] - api_version: Optional[str] - minimum_version: Optional[str] - family = "cargo-crate" + version: Optional[str] - def __init__(self, crate, features=None, api_version=None, - minimum_version=None): + def __init__(self, crate, features=None, version=None): + super(CargoCrateRequirement, self).__init__("cargo-crate") self.crate = crate if features is None: features = set() self.features = features - self.api_version = api_version - self.minimum_version = minimum_version + self.version = version def __repr__(self): - return "%s(%r, features=%r, api_version=%r, minimum_version=%r)" % ( + return "%s(%r, features=%r, version=%r)" % ( type(self).__name__, self.crate, self.features, - self.api_version, - self.minimum_version, + self.version, ) def __str__(self): - ret = "cargo crate: %s %s" % ( - self.crate, - self.api_version or "") if self.features: - ret += " (%s)" % (", ".join(sorted(self.features))) - if self.minimum_version: - ret += " (>= %s)" % self.minimum_version - return ret + return "cargo crate: %s %s (%s)" % ( + self.crate, + self.version or "", + ", ".join(sorted(self.features)), + ) + else: + return "cargo crate: %s %s" % (self.crate, self.version or "") class PkgConfigRequirement(Requirement): module: str - family = "pkg-config" def __init__(self, module, minimum_version=None): + super(PkgConfigRequirement, self).__init__("pkg-config") self.module = module self.minimum_version = minimum_version @@ -426,9 +301,9 @@ class PkgConfigRequirement(Requirement): class PathRequirement(Requirement): path: str - family = "path" def __init__(self, path): + super(PathRequirement, self).__init__("path") self.path = path def __repr__(self): @@ -438,9 +313,9 @@ class PathRequirement(Requirement): class CHeaderRequirement(Requirement): header: str - family = "c-header" def __init__(self, header): + super(CHeaderRequirement, self).__init__("c-header") self.header = header def __repr__(self): @@ -448,15 +323,16 @@ class CHeaderRequirement(Requirement): class JavaScriptRuntimeRequirement(Requirement): - family = "javascript-runtime" + def __init__(self): + super(JavaScriptRuntimeRequirement, self).__init__("javascript-runtime") class ValaPackageRequirement(Requirement): package: str - family = "vala" def __init__(self, package: str): + super(ValaPackageRequirement, self).__init__("vala") self.package = package @@ -464,9 +340,9 @@ class RubyGemRequirement(Requirement): gem: str minimum_version: Optional[str] - family = "gem" def __init__(self, gem: str, minimum_version: Optional[str]): + super(RubyGemRequirement, self).__init__("gem") self.gem = gem self.minimum_version = minimum_version @@ -475,16 +351,12 @@ class GoPackageRequirement(Requirement): package: str version: Optional[str] - family = "go-package" def __init__(self, package: str, version: Optional[str] = None): + super(GoPackageRequirement, self).__init__("go-package") self.package = package self.version = version - def __repr__(self): - return "%s(%r, version=%r)" % ( - type(self).__name__, self.package, self.version) - def __str__(self): if self.version: return "go package: %s (= %s)" % (self.package, self.version) @@ -494,9 +366,9 @@ class GoPackageRequirement(Requirement): class GoRequirement(Requirement): version: Optional[str] - family = "go" def __init__(self, version: Optional[str] = None): + super(GoRequirement, self).__init__("go") self.version = version def __str__(self): @@ -506,18 +378,18 @@ class GoRequirement(Requirement): class DhAddonRequirement(Requirement): path: str - family = "dh-addon" def __init__(self, path: str): + super(DhAddonRequirement, self).__init__("dh-addon") self.path = path class PhpClassRequirement(Requirement): php_class: str - family = "php-class" def __init__(self, php_class: str): + super(PhpClassRequirement, self).__init__("php-class") self.php_class = php_class @@ -525,9 +397,9 @@ class RPackageRequirement(Requirement): package: str minimum_version: Optional[str] - family = "r-package" def __init__(self, package: str, minimum_version: Optional[str] = None): + super(RPackageRequirement, self).__init__("r-package") self.package = package self.minimum_version = minimum_version @@ -540,8 +412,7 @@ class RPackageRequirement(Requirement): def __str__(self): if self.minimum_version: - return "R package: %s (>= %s)" % ( - self.package, self.minimum_version) + return "R package: %s (>= %s)" % (self.package, self.minimum_version) else: return "R package: %s" % (self.package,) @@ -561,9 +432,9 @@ class OctavePackageRequirement(Requirement): package: str minimum_version: Optional[str] - family = "octave-package" def __init__(self, package: str, minimum_version: Optional[str] = None): + super(OctavePackageRequirement, self).__init__("octave-package") self.package = package self.minimum_version = minimum_version @@ -576,8 +447,7 @@ class OctavePackageRequirement(Requirement): def __str__(self): if self.minimum_version: - return "Octave package: %s (>= %s)" % ( - self.package, self.minimum_version) + return "Octave package: %s (>= %s)" % (self.package, self.minimum_version) else: return "Octave package: %s" % (self.package,) @@ -596,9 +466,9 @@ class OctavePackageRequirement(Requirement): class LibraryRequirement(Requirement): library: str - family = "lib" def __init__(self, library: str): + super(LibraryRequirement, self).__init__("lib") self.library = library @@ -606,9 +476,9 @@ class StaticLibraryRequirement(Requirement): library: str filename: str - family = "static-lib" def __init__(self, library: str, filename: str): + super(StaticLibraryRequirement, self).__init__("static-lib") self.library = library self.filename = filename @@ -616,18 +486,18 @@ class StaticLibraryRequirement(Requirement): class RubyFileRequirement(Requirement): filename: str - family = "ruby-file" def __init__(self, filename: str): + super(RubyFileRequirement, self).__init__("ruby-file") self.filename = filename class XmlEntityRequirement(Requirement): url: str - family = "xml-entity" def __init__(self, url: str): + super(XmlEntityRequirement, self).__init__("xml-entity") self.url = url @@ -635,9 +505,9 @@ class SprocketsFileRequirement(Requirement): content_type: str name: str - family = "sprockets-file" def __init__(self, content_type: str, name: str): + super(SprocketsFileRequirement, self).__init__("sprockets-file") self.content_type = content_type self.name = name @@ -645,29 +515,27 @@ class SprocketsFileRequirement(Requirement): class JavaClassRequirement(Requirement): classname: str - family = "java-class" def __init__(self, classname: str): + super(JavaClassRequirement, self).__init__("java-class") self.classname = classname class CMakefileRequirement(Requirement): filename: str - version: Optional[str] - family = "cmake-file" - def __init__(self, filename: str, version=None): + def __init__(self, filename: str): + super(CMakefileRequirement, self).__init__("cmake-file") self.filename = filename - self.version = version class HaskellPackageRequirement(Requirement): package: str - family = "haskell-package" def __init__(self, package: str, specs=None): + super(HaskellPackageRequirement, self).__init__("haskell-package") self.package = package self.specs = specs @@ -683,9 +551,9 @@ class MavenArtifactRequirement(Requirement): artifact_id: str version: Optional[str] kind: Optional[str] - family = "maven-artifact" def __init__(self, group_id, artifact_id, version=None, kind=None): + super(MavenArtifactRequirement, self).__init__("maven-artifact") self.group_id = group_id self.artifact_id = artifact_id self.version = version @@ -698,11 +566,6 @@ class MavenArtifactRequirement(Requirement): self.version, ) - def __repr__(self): - return "%s(group_id=%r, artifact_id=%r, version=%r, kind=%r)" % ( - type(self).__name__, self.group_id, self.artifact_id, - self.version, self.kind) - @classmethod def from_str(cls, text): return cls.from_tuple(text.split(":")) @@ -724,16 +587,17 @@ class MavenArtifactRequirement(Requirement): class GnomeCommonRequirement(Requirement): - family = "gnome-common" + def __init__(self): + super(GnomeCommonRequirement, self).__init__("gnome-common") class JDKFileRequirement(Requirement): jdk_path: str filename: str - family = "jdk-file" def __init__(self, jdk_path: str, filename: str): + super(JDKFileRequirement, self).__init__("jdk-file") self.jdk_path = jdk_path self.filename = filename @@ -743,70 +607,55 @@ class JDKFileRequirement(Requirement): class JDKRequirement(Requirement): - family = "jdk" + def __init__(self): + super(JDKRequirement, self).__init__("jdk") class JRERequirement(Requirement): - family = "jre" - - -class QtModuleRequirement(Requirement): - family = "qt-module" - - def __init__(self, module): - self.module = module + def __init__(self): + super(JRERequirement, self).__init__("jre") class QTRequirement(Requirement): - family = "qt" + def __init__(self): + super(QTRequirement, self).__init__("qt") class X11Requirement(Requirement): - family = "x11" + def __init__(self): + super(X11Requirement, self).__init__("x11") class CertificateAuthorityRequirement(Requirement): - family = "ca-cert" - def __init__(self, url): + super(CertificateAuthorityRequirement, self).__init__("ca-cert") self.url = url class PerlFileRequirement(Requirement): filename: str - family = "perl-file" def __init__(self, filename: str): + super(PerlFileRequirement, self).__init__("perl-file") self.filename = filename class AutoconfMacroRequirement(Requirement): - family = "autoconf-macro" macro: str def __init__(self, macro: str): + super(AutoconfMacroRequirement, self).__init__("autoconf-macro") self.macro = macro - def _json(self): - return self.macro - - @classmethod - def _from_json(cls, macro): - return cls(macro) - - -Requirement.register_json(AutoconfMacroRequirement) - class LibtoolRequirement(Requirement): - family = "libtool" + def __init__(self): + super(LibtoolRequirement, self).__init__("libtool") class IntrospectionTypelibRequirement(Requirement): - family = "introspection-type-lib" - def __init__(self, library): self.library = library @@ -816,9 +665,9 @@ class PythonModuleRequirement(Requirement): module: str python_version: Optional[str] minimum_version: Optional[str] - family = "python-module" def __init__(self, module, python_version=None, minimum_version=None): + super(PythonModuleRequirement, self).__init__("python-module") self.module = module self.python_version = python_version self.minimum_version = minimum_version @@ -853,25 +702,7 @@ class PythonModuleRequirement(Requirement): class BoostComponentRequirement(Requirement): name: str - family = "boost-component" def __init__(self, name): + super(BoostComponentRequirement, self).__init__("boost-component") self.name = name - - -class KF5ComponentRequirement(Requirement): - - name: str - family = "kf5-component" - - def __init__(self, name): - self.name = name - - -class GnulibDirectoryRequirement(Requirement): - - directory: str - family = "gnulib" - - def __init__(self, directory): - self.directory = directory diff --git a/ognibuild/resolver/__init__.py b/ognibuild/resolver/__init__.py index 958ff2b..7af26d5 100644 --- a/ognibuild/resolver/__init__.py +++ b/ognibuild/resolver/__init__.py @@ -18,11 +18,8 @@ import logging import subprocess -from typing import Optional, List, Type - -from .. import UnidentifiedError, Requirement +from .. import UnidentifiedError from ..fix_build import run_detecting_problems -from ..session import Session class UnsatisfiedRequirements(Exception): @@ -31,22 +28,13 @@ class UnsatisfiedRequirements(Exception): class Resolver(object): - - name: str - - def __init__(self, session, user_local): - raise NotImplementedError(self.__init__) - - def install(self, requirements: List[Requirement]): + def install(self, requirements): raise NotImplementedError(self.install) - def resolve(self, requirement: Requirement) -> Optional[Requirement]: + def resolve(self, requirement): raise NotImplementedError(self.resolve) - def resolve_all(self, requirement: Requirement) -> List[Requirement]: - raise NotImplementedError(self.resolve_all) - - def explain(self, requirements: List[Requirement]): + def explain(self, requirements): raise NotImplementedError(self.explain) def env(self): @@ -54,15 +42,13 @@ class Resolver(object): class CPANResolver(Resolver): - name = "cpan" - def __init__(self, session, user_local=False, skip_tests=True): self.session = session self.user_local = user_local self.skip_tests = skip_tests def __str__(self): - return self.name + return "cpan" def __repr__(self): return "%s(%r)" % (type(self).__name__, self.session) @@ -123,8 +109,7 @@ class TlmgrResolver(Resolver): self.repository = repository def __str__(self): - if (self.repository.startswith('http://') - or self.repository.startswith('https://')): + if self.repository.startswith('http://') or self.repository.startswith('https://'): return 'tlmgr(%r)' % self.repository else: return self.repository @@ -169,8 +154,7 @@ class TlmgrResolver(Resolver): try: run_detecting_problems(self.session, cmd, user=user) except UnidentifiedError as e: - if ("tlmgr: user mode not initialized, " - "please read the documentation!") in e.lines: + if "tlmgr: user mode not initialized, please read the documentation!" in e.lines: self.session.check_call(['tlmgr', 'init-usertree']) else: raise @@ -179,7 +163,6 @@ class TlmgrResolver(Resolver): class CTANResolver(TlmgrResolver): - name = "ctan" def __init__(self, session, user_local=False): super(CTANResolver, self).__init__( @@ -187,16 +170,13 @@ class CTANResolver(TlmgrResolver): class RResolver(Resolver): - - name: str - def __init__(self, session, repos, user_local=False): self.session = session self.repos = repos self.user_local = user_local def __str__(self): - return self.name + return "cran" def __repr__(self): return "%s(%r, %r)" % (type(self).__name__, self.session, self.repos) @@ -241,14 +221,12 @@ class RResolver(Resolver): class OctaveForgeResolver(Resolver): - name = "octave-forge" - def __init__(self, session, user_local=False): self.session = session self.user_local = user_local def __str__(self): - return self.name + return "octave-forge" def __repr__(self): return "%s(%r)" % (type(self).__name__, self.session) @@ -289,8 +267,6 @@ class OctaveForgeResolver(Resolver): class CRANResolver(RResolver): - name = "cran" - def __init__(self, session, user_local=False): super(CRANResolver, self).__init__( session, "http://cran.r-project.org", user_local=user_local @@ -298,25 +274,19 @@ class CRANResolver(RResolver): class BioconductorResolver(RResolver): - name = "bioconductor" - def __init__(self, session, user_local=False): super(BioconductorResolver, self).__init__( - session, "https://hedgehog.fhcrc.org/bioconductor", - user_local=user_local + session, "https://hedgehog.fhcrc.org/bioconductor", user_local=user_local ) class HackageResolver(Resolver): - - name = "hackage" - def __init__(self, session, user_local=False): self.session = session self.user_local = user_local def __str__(self): - return self.name + return "hackage" def __repr__(self): return "%s(%r)" % (type(self).__name__, self.session) @@ -325,8 +295,7 @@ class HackageResolver(Resolver): extra_args = [] if self.user_local: extra_args.append("--user") - return (["cabal", "install"] + extra_args - + [req.package for req in reqs]) + return ["cabal", "install"] + extra_args + [req.package for req in reqs] def install(self, requirements): from ..requirements import HaskellPackageRequirement @@ -360,15 +329,12 @@ class HackageResolver(Resolver): class PypiResolver(Resolver): - - name = "pypi" - def __init__(self, session, user_local=False): self.session = session self.user_local = user_local def __str__(self): - return self.name + return "pypi" def __repr__(self): return "%s(%r)" % (type(self).__name__, self.session) @@ -414,15 +380,12 @@ class PypiResolver(Resolver): class GoResolver(Resolver): - - name = "go" - def __init__(self, session, user_local): self.session = session self.user_local = user_local def __str__(self): - return self.name + return "go" def __repr__(self): return "%s(%r)" % (type(self).__name__, self.session) @@ -463,26 +426,17 @@ NPM_COMMAND_PACKAGES = { "del-cli": "del-cli", "husky": "husky", "cross-env": "cross-env", - "xo": "xo", - "standard": "standard", - "jshint": "jshint", - "if-node-version": "if-node-version", - "babel-cli": "babel", - "c8": "c8", - "prettier-standard": "prettier-standard", } class NpmResolver(Resolver): - name = "npm" - def __init__(self, session, user_local=False): self.session = session self.user_local = user_local # TODO(jelmer): Handle user_local def __str__(self): - return self.name + return "npm" def __repr__(self): return "%s(%r)" % (type(self).__name__, self.session) @@ -518,10 +472,7 @@ class NpmResolver(Resolver): if not isinstance(requirement, NodePackageRequirement): missing.append(requirement) continue - cmd = ["npm", "install"] - if not self.user_local: - cmd.append('-g') - cmd.append(requirement.package) + cmd = ["npm", "-g", "install", requirement.package] logging.info("npm: running %r", cmd) run_detecting_problems(self.session, cmd, user=user) if missing: @@ -578,7 +529,7 @@ class StackedResolver(Resolver): raise UnsatisfiedRequirements(requirements) -NATIVE_RESOLVER_CLS: List[Type[Resolver]] = [ +NATIVE_RESOLVER_CLS = [ CPANResolver, CTANResolver, PypiResolver, @@ -592,70 +543,24 @@ NATIVE_RESOLVER_CLS: List[Type[Resolver]] = [ def native_resolvers(session, user_local): - return StackedResolver( - [kls(session, user_local) for kls in NATIVE_RESOLVER_CLS]) + return StackedResolver([kls(session, user_local) for kls in NATIVE_RESOLVER_CLS]) -def select_resolvers(session, user_local, resolvers, - dep_server_url=None) -> Optional[Resolver]: - selected = [] - for resolver in resolvers: - for kls in NATIVE_RESOLVER_CLS: - if kls.name == resolver: - selected.append(kls(session, user_local)) - break - else: - if resolver == 'native': - selected.extend([ - kls(session, user_local) for kls in NATIVE_RESOLVER_CLS]) - elif resolver == 'apt': - if user_local: - raise NotImplementedError( - 'user local not supported for apt') - if dep_server_url: - from .dep_server import DepServerAptResolver - selected.append(DepServerAptResolver.from_session( - session, dep_server_url)) - else: - from .apt import AptResolver - selected.append(AptResolver.from_session(session)) - else: - raise KeyError(resolver) - if len(selected) == 0: - return None - if len(selected) == 1: - return selected[0] - return StackedResolver(selected) - - -def auto_resolver(session: Session, explain: bool = False, - system_wide: Optional[bool] = None, - dep_server_url: Optional[str] = None): +def auto_resolver(session, explain=False): # if session is SchrootSession or if we're root, use apt + from .apt import AptResolver from ..session.schroot import SchrootSession from ..session import get_user user = get_user(session) resolvers = [] - if system_wide is None: - # TODO(jelmer): Check VIRTUAL_ENV, and prioritize PypiResolver if - # present? - if isinstance(session, SchrootSession) or user == "root" or explain: - system_wide = True - else: - system_wide = False - if system_wide: - try: - from .apt import AptResolver - except ModuleNotFoundError: - pass - else: - if dep_server_url: - from .dep_server import DepServerAptResolver - resolvers.append( - DepServerAptResolver.from_session(session, dep_server_url)) - else: - resolvers.append(AptResolver.from_session(session)) - resolvers.extend([kls(session, not system_wide) - for kls in NATIVE_RESOLVER_CLS]) + # TODO(jelmer): Check VIRTUAL_ENV, and prioritize PypiResolver if + # present? + if isinstance(session, SchrootSession) or user == "root" or explain: + user_local = False + else: + user_local = True + if not user_local: + resolvers.append(AptResolver.from_session(session)) + resolvers.extend([kls(session, user_local) for kls in NATIVE_RESOLVER_CLS]) return StackedResolver(resolvers) diff --git a/ognibuild/resolver/apt.py b/ognibuild/resolver/apt.py index ae88a49..82ef627 100644 --- a/ognibuild/resolver/apt.py +++ b/ognibuild/resolver/apt.py @@ -15,13 +15,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -import asyncio from itertools import chain import logging import os import posixpath import re -from typing import Optional, List, Tuple, Callable, Type, Awaitable +from typing import Optional, List from debian.changelog import Version from debian.deb822 import PkgRelation @@ -29,7 +28,6 @@ from debian.deb822 import PkgRelation from ..debian.apt import AptManager from . import Resolver, UnsatisfiedRequirements -from .. import OneOfRequirement from ..requirements import ( Requirement, CargoCrateRequirement, @@ -50,11 +48,9 @@ from ..requirements import ( NodePackageRequirement, LibraryRequirement, BoostComponentRequirement, - KF5ComponentRequirement, StaticLibraryRequirement, RubyFileRequirement, XmlEntityRequirement, - OctavePackageRequirement, SprocketsFileRequirement, JavaClassRequirement, CMakefileRequirement, @@ -65,7 +61,6 @@ from ..requirements import ( JDKRequirement, JRERequirement, QTRequirement, - QtModuleRequirement, X11Requirement, PerlModuleRequirement, PerlFileRequirement, @@ -77,17 +72,12 @@ from ..requirements import ( VagueDependencyRequirement, PerlPreDeclaredRequirement, IntrospectionTypelibRequirement, - PHPExtensionRequirement, - VcsControlDirectoryAccessRequirement, ) class AptRequirement(Requirement): - - family = "apt" - def __init__(self, relations): - super(AptRequirement, self).__init__() + super(AptRequirement, self).__init__("apt") if not isinstance(relations, list): raise TypeError(relations) self.relations = relations @@ -106,26 +96,17 @@ class AptRequirement(Requirement): def pkg_relation_str(self): return PkgRelation.str(self.relations) - def _json(self): - return self.pkg_relation_str() - - @classmethod - def _from_json(cls, text): - return cls.from_str(text) - def __hash__(self): return hash((type(self), self.pkg_relation_str())) def __eq__(self, other): - return (isinstance(self, type(other)) - and self.relations == other.relations) + return isinstance(self, type(other)) and self.relations == other.relations def __str__(self): return "apt requirement: %s" % self.pkg_relation_str() def __repr__(self): - return "%s.from_str(%r)" % ( - type(self).__name__, self.pkg_relation_str()) + return "%s.from_str(%r)" % (type(self).__name__, self.pkg_relation_str()) def package_names(self): for rel in self.relations: @@ -152,34 +133,32 @@ class AptRequirement(Requirement): for rel in self.relations: for entry in rel: - if any(binary_pkg_matches(entry, binary) - for binary in binaries): + if any(binary_pkg_matches(entry, binary) for binary in binaries): break else: return False return True -async def resolve_perl_predeclared_req(apt_mgr, req): +def resolve_perl_predeclared_req(apt_mgr, req): try: req = req.lookup_module() except KeyError: logging.warning( 'Unable to map predeclared function %s to a perl module', req.name) return None - return await resolve_perl_module_req(apt_mgr, req) + return resolve_perl_module_req(apt_mgr, req) -async def find_package_names( - apt_mgr: AptManager, paths: List[str], regex: bool = False, - case_insensitive: bool = False +def find_package_names( + apt_mgr: AptManager, paths: List[str], regex: bool = False, case_insensitive=False ) -> List[str]: if not isinstance(paths, list): raise TypeError(paths) - return await apt_mgr.get_packages_for_paths(paths, regex, case_insensitive) + return apt_mgr.get_packages_for_paths(paths, regex, case_insensitive) -async def find_reqs_simple( +def find_reqs_simple( apt_mgr: AptManager, paths: List[str], regex: bool = False, @@ -190,8 +169,7 @@ async def find_reqs_simple( raise TypeError(paths) return [ AptRequirement.simple(package, minimum_version=minimum_version) - for package in await find_package_names( - apt_mgr, paths, regex, case_insensitive) + for package in find_package_names(apt_mgr, paths, regex, case_insensitive) ] @@ -212,10 +190,8 @@ def python_spec_to_apt_rels(pkg_name, specs): next_maj_deb_version = Version(".".join(parts)) deb_version = Version(spec[1]) rels.extend( - [[{"name": pkg_name, - "version": (">=", deb_version)}], - [{"name": pkg_name, - "version": ("<<", next_maj_deb_version)}]]) + [[{"name": pkg_name, "version": (">=", deb_version)}], + [{"name": pkg_name, "version": ("<<", next_maj_deb_version)}]]) elif spec[0] == "!=": deb_version = Version(spec[1]) rels.extend([ @@ -227,36 +203,28 @@ def python_spec_to_apt_rels(pkg_name, specs): n = list(s) n[-1] = str(int(n[-1]) + 1) rels.extend( - [[{"name": pkg_name, - "version": (">=", Version(".".join(s)))}], - [{"name": pkg_name, - "version": ("<<", Version(".".join(n)))}]]) + [[{"name": pkg_name, "version": (">=", Version(".".join(s)))}], + [{"name": pkg_name, "version": ("<<", Version(".".join(n)))}]]) else: - c = { - ">=": ">=", - "<=": "<=", - "<": "<<", - ">": ">>", - "==": "=", - }[spec[0]] + c = {">=": ">=", "<=": "<=", "<": "<<", ">": ">>", "==": "="}[spec[0]] deb_version = Version(spec[1]) rels.append([{"name": pkg_name, "version": (c, deb_version)}]) return rels -async def get_package_for_python_package( +def get_package_for_python_package( apt_mgr, package, python_version: Optional[str], specs=None ): - pypy_regex = ( - "/usr/lib/pypy/dist\\-packages/%s-.*\\.(dist|egg)\\-info" - % re.escape(package.replace("-", "_"))) + pypy_regex = "/usr/lib/pypy/dist-packages/%s-.*.egg-info" % re.escape( + package.replace("-", "_") + ) cpython2_regex = ( - "/usr/lib/python2\\.[0-9]/dist\\-packages/%s-.*\\.(dist|egg)\\-info" + "/usr/lib/python2\\.[0-9]/dist-packages/%s-.*.egg-info" % re.escape(package.replace("-", "_")) ) - cpython3_regex = ( - "/usr/lib/python3/dist\\-packages/%s-.*\\.(dist|egg)\\-info" - % re.escape(package.replace("-", "_"))) + cpython3_regex = "/usr/lib/python3/dist-packages/%s-.*.egg-info" % re.escape( + package.replace("-", "_") + ) if python_version == "pypy": paths = [pypy_regex] elif python_version == "cpython2": @@ -266,174 +234,109 @@ async def get_package_for_python_package( elif python_version is None: paths = [cpython3_regex, cpython2_regex, pypy_regex] else: - raise NotImplementedError( - "unsupported python version %s" % python_version) - names = await find_package_names( - apt_mgr, paths, regex=True, case_insensitive=True) - return [AptRequirement( - python_spec_to_apt_rels(name, specs)) for name in names] + raise NotImplementedError("unsupported python version %s" % python_version) + names = find_package_names(apt_mgr, paths, regex=True, case_insensitive=True) + return [AptRequirement(python_spec_to_apt_rels(name, specs)) for name in names] -def get_possible_python3_paths_for_python_object(object_path): - cpython3_regexes = [] - while True: - cpython3_regexes.extend([ - posixpath.join( - re.escape("/usr/lib/python3/dist-packages"), - re.escape(object_path.replace(".", "/")), - re.escape("__init__.py"), - ), - posixpath.join( - re.escape("/usr/lib/python3/dist-packages"), - re.escape(object_path.replace(".", "/")) + re.escape(".py"), - ), - posixpath.join( - "/usr/lib/python3\\.[0-9]+/lib\\-dynload", - re.escape(object_path.replace(".", "/")) - + ".cpython\\-.*\\.so", - ), - posixpath.join( - "/usr/lib/python3\\.[0-9]+/", - re.escape(object_path.replace(".", "/")) + "\\.py" - ), - posixpath.join( - "/usr/lib/python3\\.[0-9]+/", - re.escape(object_path.replace(".", "/")), - "__init__\\.py", - ), - ]) - try: - object_path, discarded = object_path.rsplit('.', 1) - except ValueError: - break - return cpython3_regexes - - -def get_possible_pypy_paths_for_python_object(object_path): - pypy_regexes = [] - while True: - pypy_regexes.extend([ - posixpath.join( - "/usr/lib/pypy/dist\\-packages", - re.escape(object_path.replace(".", "/")), - "__init__\\.py", - ), - posixpath.join( - "/usr/lib/pypy/dist\\-packages", - re.escape(object_path.replace(".", "/")) + "\\.py" - ), - posixpath.join( - "/usr/lib/pypy/dist\\-packages", - re.escape(object_path.replace(".", "/")) + "\\.pypy-.*\\.so", - ), - ]) - try: - object_path, discarded = object_path.rsplit('.', 1) - except ValueError: - break - return pypy_regexes - - -def get_possible_python2_paths_for_python_object(object_path): - cpython2_regexes = [] - while True: - cpython2_regexes.extend([ - posixpath.join( - "/usr/lib/python2\\.[0-9]/dist\\-packages", - re.escape(object_path.replace(".", "/")), - "__init__\\.py", - ), - posixpath.join( - "/usr/lib/python2\\.[0-9]/dist\\-packages", - re.escape(object_path.replace(".", "/")) + "\\.py", - ), - posixpath.join( - "/usr/lib/python2.\\.[0-9]/lib\\-dynload", - re.escape(object_path.replace(".", "/")) + "\\.so", - ), - ]) - try: - object_path, discarded = object_path.rsplit('.', 1) - except ValueError: - break - return cpython2_regexes - - -async def get_package_for_python_object_path( - apt_mgr, object_path, python_version, specs): - # Try to find the most specific file +def get_package_for_python_module(apt_mgr, module, python_version, specs): + cpython3_regexes = [ + posixpath.join( + "/usr/lib/python3/dist-packages", + re.escape(module.replace(".", "/")), + "__init__.py", + ), + posixpath.join( + "/usr/lib/python3/dist-packages", + re.escape(module.replace(".", "/")) + ".py", + ), + posixpath.join( + "/usr/lib/python3\\.[0-9]+/lib-dynload", + re.escape(module.replace(".", "/")) + "\\.cpython-.*\\.so", + ), + posixpath.join( + "/usr/lib/python3\\.[0-9]+/", re.escape(module.replace(".", "/")) + ".py" + ), + posixpath.join( + "/usr/lib/python3\\.[0-9]+/", + re.escape(module.replace(".", "/")), + "__init__.py", + ), + ] + cpython2_regexes = [ + posixpath.join( + "/usr/lib/python2\\.[0-9]/dist-packages", + re.escape(module.replace(".", "/")), + "__init__.py", + ), + posixpath.join( + "/usr/lib/python2\\.[0-9]/dist-packages", + re.escape(module.replace(".", "/")) + ".py", + ), + posixpath.join( + "/usr/lib/python2.\\.[0-9]/lib-dynload", + re.escape(module.replace(".", "/")) + ".so", + ), + ] + pypy_regexes = [ + posixpath.join( + "/usr/lib/pypy/dist-packages", + re.escape(module.replace(".", "/")), + "__init__.py", + ), + posixpath.join( + "/usr/lib/pypy/dist-packages", re.escape(module.replace(".", "/")) + ".py" + ), + posixpath.join( + "/usr/lib/pypy/dist-packages", + re.escape(module.replace(".", "/")) + "\\.pypy-.*\\.so", + ), + ] if python_version == "cpython3": - paths = get_possible_python3_paths_for_python_object(object_path) + paths = cpython3_regexes elif python_version == "cpython2": - paths = get_possible_python2_paths_for_python_object(object_path) + paths = cpython2_regexes elif python_version == "pypy": - paths = get_possible_pypy_paths_for_python_object(object_path) + paths = pypy_regexes elif python_version is None: - paths = (get_possible_python3_paths_for_python_object(object_path) - + get_possible_python2_paths_for_python_object(object_path) - + get_possible_pypy_paths_for_python_object(object_path)) + paths = cpython3_regexes + cpython2_regexes + pypy_regexes else: raise AssertionError("unknown python version %r" % python_version) - names = await find_package_names(apt_mgr, paths, regex=True) - return [AptRequirement(python_spec_to_apt_rels(name, specs)) - for name in names] + names = find_package_names(apt_mgr, paths, regex=True) + return [AptRequirement(python_spec_to_apt_rels(name, specs)) for name in names] vague_map = { "the Gnu Scientific Library": "libgsl-dev", "the required FreeType library": "libfreetype-dev", "the Boost C++ libraries": "libboost-dev", - "the sndfile library": "libsndfile-dev", # TODO(jelmer): Support resolving virtual packages "PythonLibs": "libpython3-dev", - "PythonInterp": "python3", "ZLIB": "libz3-dev", "Osmium": "libosmium2-dev", "glib": "libglib2.0-dev", - "OpenGL": "libgl-dev", - # TODO(jelmer): For Python, check minimum_version and map to python 2 or - # python 3 + # TODO(jelmer): For Python, check minimum_version and map to python 2 or python 3 "Python": "libpython3-dev", "Lua": "liblua5.4-dev", } -async def resolve_vague_dep_req(apt_mgr, req): +def resolve_vague_dep_req(apt_mgr, req): name = req.name options = [] - if ' or ' in name: - for entry in name.split(' or '): - options.extend(await resolve_vague_dep_req( - apt_mgr, VagueDependencyRequirement(entry))) - if name in vague_map: - options.append(AptRequirement.simple( - vague_map[name], minimum_version=req.minimum_version)) + options.append(AptRequirement.simple(vague_map[name], minimum_version=req.minimum_version)) for x in req.expand(): - options.extend(await resolve_requirement_apt(apt_mgr, x)) - - if name.startswith('GNU '): - options.extend(await resolve_vague_dep_req( - apt_mgr, VagueDependencyRequirement(name[4:]))) - - if name.startswith('py') or name.endswith('py'): - # TODO(jelmer): Try harder to determine whether this is a python - # package - options.append(await resolve_requirement_apt( - apt_mgr, PythonPackageRequirement(name))) - + options.extend(resolve_requirement_apt(apt_mgr, x)) # Try even harder if not options: - options.extend(await find_reqs_simple( + options.extend(find_reqs_simple( apt_mgr, [ - posixpath.join( - "/usr/lib", ".*", "pkgconfig", - re.escape(req.name) + "-.*\\.pc"), - posixpath.join( - "/usr/lib/pkgconfig", re.escape(req.name) + "\\-.*\\.pc") + posixpath.join("/usr/lib", ".*", "pkgconfig", re.escape(req.name) + "-.*\\.pc"), + posixpath.join("/usr/lib/pkgconfig", re.escape(req.name) + "-.*\\.pc") ], regex=True, case_insensitive=True, @@ -443,29 +346,18 @@ async def resolve_vague_dep_req(apt_mgr, req): return options -async def resolve_php_extension_req(apt_mgr, req): - return [AptRequirement.simple("php-%s" % req.extension)] - - -async def resolve_octave_pkg_req(apt_mgr, req): - return [AptRequirement.simple( - "octave-%s" % req.package, minimum_version=req.minimum_version)] - - -async def resolve_binary_req(apt_mgr, req): +def resolve_binary_req(apt_mgr, req): if posixpath.isabs(req.binary_name): paths = [req.binary_name] else: paths = [ - posixpath.join(dirname, req.binary_name) - for dirname in ["/usr/bin", "/bin"] + posixpath.join(dirname, req.binary_name) for dirname in ["/usr/bin", "/bin"] ] - # TODO(jelmer): Check for binaries which use alternatives - return await find_reqs_simple(apt_mgr, paths) + return find_reqs_simple(apt_mgr, paths) -async def resolve_pkg_config_req(apt_mgr, req): - names = await find_package_names( +def resolve_pkg_config_req(apt_mgr, req): + names = find_package_names( apt_mgr, [ posixpath.join( @@ -475,7 +367,7 @@ async def resolve_pkg_config_req(apt_mgr, req): regex=True, ) if not names: - names = await find_package_names( + names = find_package_names( apt_mgr, [posixpath.join("/usr/lib/pkgconfig", req.module + ".pc")] ) return [ @@ -484,16 +376,16 @@ async def resolve_pkg_config_req(apt_mgr, req): ] -async def resolve_path_req(apt_mgr, req): - return await find_reqs_simple(apt_mgr, [req.path]) +def resolve_path_req(apt_mgr, req): + return find_reqs_simple(apt_mgr, [req.path]) -async def resolve_c_header_req(apt_mgr, req): - reqs = await find_reqs_simple( +def resolve_c_header_req(apt_mgr, req): + reqs = find_reqs_simple( apt_mgr, [posixpath.join("/usr/include", req.header)], regex=False ) if not reqs: - reqs = await find_reqs_simple( + reqs = find_reqs_simple( apt_mgr, [posixpath.join("/usr/include", ".*", re.escape(req.header))], regex=True, @@ -501,149 +393,140 @@ async def resolve_c_header_req(apt_mgr, req): return reqs -async def resolve_js_runtime_req(apt_mgr, req): - return await find_reqs_simple(apt_mgr, ["/usr/bin/node", "/usr/bin/duk"]) +def resolve_js_runtime_req(apt_mgr, req): + return find_reqs_simple(apt_mgr, ["/usr/bin/node", "/usr/bin/duk"]) -async def resolve_vala_package_req(apt_mgr, req): +def resolve_vala_package_req(apt_mgr, req): path = "/usr/share/vala-[0-9.]+/vapi/%s\\.vapi" % re.escape(req.package) - return await find_reqs_simple(apt_mgr, [path], regex=True) + return find_reqs_simple(apt_mgr, [path], regex=True) -async def resolve_ruby_gem_req(apt_mgr, req): +def resolve_ruby_gem_req(apt_mgr, req): paths = [ posixpath.join( "/usr/share/rubygems-integration/all/" "specifications/%s-.*\\.gemspec" % re.escape(req.gem) ) ] - return await find_reqs_simple( + return find_reqs_simple( apt_mgr, paths, regex=True, minimum_version=req.minimum_version ) -async def resolve_go_package_req(apt_mgr, req): - return await find_reqs_simple( +def resolve_go_package_req(apt_mgr, req): + return find_reqs_simple( apt_mgr, - [posixpath.join( - "/usr/share/gocode/src", re.escape(req.package), ".*")], + [posixpath.join("/usr/share/gocode/src", re.escape(req.package), ".*")], regex=True, ) -async def resolve_go_req(apt_mgr, req): - return [ - AptRequirement.simple( - "golang-go", minimum_version="2:%s~" % req.version)] +def resolve_go_req(apt_mgr, req): + return [AptRequirement.simple("golang-go", minimum_version="2:%s" % req.version)] -async def resolve_dh_addon_req(apt_mgr, req): +def resolve_dh_addon_req(apt_mgr, req): paths = [posixpath.join("/usr/share/perl5", req.path)] - return await find_reqs_simple(apt_mgr, paths) + return find_reqs_simple(apt_mgr, paths) -async def resolve_php_class_req(apt_mgr, req): +def resolve_php_class_req(apt_mgr, req): path = "/usr/share/php/%s.php" % req.php_class.replace("\\", "/") - return await find_reqs_simple(apt_mgr, [path]) + return find_reqs_simple(apt_mgr, [path]) -async def resolve_php_package_req(apt_mgr, req): +def resolve_php_package_req(apt_mgr, req): return [ - AptRequirement.simple( - "php-%s" % req.package, minimum_version=req.min_version) + AptRequirement.simple("php-%s" % req.package, minimum_version=req.min_version) ] -async def resolve_r_package_req(apt_mgr, req): +def resolve_r_package_req(apt_mgr, req): paths = [ posixpath.join("/usr/lib/R/site-library", req.package, "DESCRIPTION") ] - return await find_reqs_simple( - apt_mgr, paths, minimum_version=req.minimum_version) + return find_reqs_simple(apt_mgr, paths, minimum_version=req.minimum_version) -async def resolve_node_module_req(apt_mgr, req): +def resolve_node_module_req(apt_mgr, req): paths = [ - "/usr/share/nodejs/.*/node_modules/%s/index\\.js" - % re.escape(req.module), - "/usr/lib/nodejs/%s/index\\.js" % re.escape(req.module), - "/usr/share/nodejs/%s/index\\.js" % re.escape(req.module), + "/usr/share/nodejs/.*/node_modules/%s/index.js" % re.escape(req.module), + "/usr/lib/nodejs/%s/index.js" % re.escape(req.module), + "/usr/share/nodejs/%s/index.js" % re.escape(req.module), ] - return await find_reqs_simple(apt_mgr, paths, regex=True) + return find_reqs_simple(apt_mgr, paths, regex=True) -async def resolve_node_package_req(apt_mgr, req): +def resolve_node_package_req(apt_mgr, req): paths = [ - "/usr/share/nodejs/.*/node_modules/%s/package\\.json" - % re.escape(req.package), + "/usr/share/nodejs/.*/node_modules/%s/package\\.json" % re.escape(req.package), "/usr/lib/nodejs/%s/package\\.json" % re.escape(req.package), "/usr/share/nodejs/%s/package\\.json" % re.escape(req.package), ] - return await find_reqs_simple(apt_mgr, paths, regex=True) + return find_reqs_simple(apt_mgr, paths, regex=True) -async def resolve_library_req(apt_mgr, req): +def resolve_library_req(apt_mgr, req): paths = [ - posixpath.join("/usr/lib/lib%s\\.so$" % re.escape(req.library)), - posixpath.join("/usr/lib/.*/lib%s\\.so$" % re.escape(req.library)), - posixpath.join("/usr/lib/lib%s\\.a$" % re.escape(req.library)), - posixpath.join("/usr/lib/.*/lib%s\\.a$" % re.escape(req.library)), + posixpath.join("/usr/lib/lib%s.so$" % re.escape(req.library)), + posixpath.join("/usr/lib/.*/lib%s.so$" % re.escape(req.library)), + posixpath.join("/usr/lib/lib%s.a$" % re.escape(req.library)), + posixpath.join("/usr/lib/.*/lib%s.a$" % re.escape(req.library)), ] - return await find_reqs_simple(apt_mgr, paths, regex=True) + return find_reqs_simple(apt_mgr, paths, regex=True) -async def resolve_static_library_req(apt_mgr, req): +def resolve_static_library_req(apt_mgr, req): paths = [ posixpath.join("/usr/lib/%s$" % re.escape(req.filename)), posixpath.join("/usr/lib/.*/%s$" % re.escape(req.filename)), ] - return await find_reqs_simple(apt_mgr, paths, regex=True) + return find_reqs_simple(apt_mgr, paths, regex=True) -async def resolve_ruby_file_req(apt_mgr, req): +def resolve_ruby_file_req(apt_mgr, req): paths = [posixpath.join("/usr/lib/ruby/vendor_ruby/%s.rb" % req.filename)] - reqs = await find_reqs_simple(apt_mgr, paths, regex=False) + reqs = find_reqs_simple(apt_mgr, paths, regex=False) if reqs: return reqs paths = [ posixpath.join( - r"/usr/share/rubygems\-integration/all/gems/([^/]+)/" + r"/usr/share/rubygems-integration/all/gems/([^/]+)/" "lib/%s\\.rb" % re.escape(req.filename) ) ] - return await find_reqs_simple(apt_mgr, paths, regex=True) + return find_reqs_simple(apt_mgr, paths, regex=True) -async def resolve_xml_entity_req(apt_mgr, req): +def resolve_xml_entity_req(apt_mgr, req): # Ideally we should be using the XML catalog for this, but hardcoding # a few URLs will do for now.. URL_MAP = { - "http://www.oasis-open.org/docbook/xml/": - "/usr/share/xml/docbook/schema/dtd/" + "http://www.oasis-open.org/docbook/xml/": "/usr/share/xml/docbook/schema/dtd/" } for url, path in URL_MAP.items(): if req.url.startswith(url): - search_path = posixpath.join(path, req.url[len(url):]) + search_path = posixpath.join(path, req.url[len(url) :]) break else: return None - return await find_reqs_simple(apt_mgr, [search_path], regex=False) + return find_reqs_simple(apt_mgr, [search_path], regex=False) -async def resolve_sprockets_file_req(apt_mgr, req): +def resolve_sprockets_file_req(apt_mgr, req): if req.content_type == "application/javascript": - path = ("/usr/share/.*/app/assets/javascripts/%s\\.js$" - % re.escape(req.name)) + path = "/usr/share/.*/app/assets/javascripts/%s\\.js$" % re.escape(req.name) else: - logging.warning( - "unable to handle content type %s", req.content_type) + logging.warning("unable to handle content type %s", req.content_type) return None - return await find_reqs_simple(apt_mgr, [path], regex=True) + return find_reqs_simple(apt_mgr, [path], regex=True) -async def resolve_java_class_req(apt_mgr, req): - apt_mgr.satisfy(["java-propose-classpath"]) +def resolve_java_class_req(apt_mgr, req): + # Unfortunately this only finds classes in jars installed on the host + # system :( output = apt_mgr.session.check_output( ["java-propose-classpath", "-c" + req.classname] ) @@ -652,22 +535,20 @@ async def resolve_java_class_req(apt_mgr, req): logging.warning("unable to find classpath for %s", req.classname) return False logging.info("Classpath for %s: %r", req.classname, classpath) - return await find_reqs_simple(apt_mgr, [classpath]) + return find_reqs_simple(apt_mgr, [classpath]) -async def resolve_cmake_file_req(apt_mgr, req): - paths = ['/usr/lib/.*/cmake/.*/%s' % re.escape(req.filename), - '/usr/share/.*/cmake/%s' % re.escape(req.filename)] - return await find_reqs_simple(apt_mgr, paths, regex=True) +def resolve_cmake_file_req(apt_mgr, req): + paths = ['/usr/lib/.*/cmake/.*/%s' % re.escape(req.filename)] + return find_reqs_simple(apt_mgr, paths, regex=True) -async def resolve_haskell_package_req(apt_mgr, req): - path = ("/var/lib/ghc/package\\.conf\\.d/%s\\-.*\\.conf" - % re.escape(req.package)) - return await find_reqs_simple(apt_mgr, [path], regex=True) +def resolve_haskell_package_req(apt_mgr, req): + path = "/var/lib/ghc/package\\.conf\\.d/%s-.*\\.conf" % re.escape(req.package) + return find_reqs_simple(apt_mgr, [path], regex=True) -async def resolve_maven_artifact_req(apt_mgr, req): +def resolve_maven_artifact_req(apt_mgr, req): if req.version is None: version = ".*" regex = True @@ -685,66 +566,50 @@ async def resolve_maven_artifact_req(apt_mgr, req): escape(req.group_id.replace(".", "/")), escape(req.artifact_id), version, - escape("%s-" % req.artifact_id) + version + escape("." + kind), + escape("%s-") + version + escape("." + kind), ) - return await find_reqs_simple(apt_mgr, [path], regex=regex) + return find_reqs_simple(apt_mgr, [path], regex=regex) -async def resolve_gnome_common_req(apt_mgr, req): +def resolve_gnome_common_req(apt_mgr, req): return [AptRequirement.simple("gnome-common")] -async def resolve_jdk_file_req(apt_mgr, req): +def resolve_jdk_file_req(apt_mgr, req): path = re.escape(req.jdk_path) + ".*/" + re.escape(req.filename) - return await find_reqs_simple(apt_mgr, [path], regex=True) + return find_reqs_simple(apt_mgr, [path], regex=True) -async def resolve_jdk_req(apt_mgr, req): +def resolve_jdk_req(apt_mgr, req): return [AptRequirement.simple("default-jdk")] -async def resolve_jre_req(apt_mgr, req): +def resolve_jre_req(apt_mgr, req): return [AptRequirement.simple("default-jre")] -async def resolve_x11_req(apt_mgr, req): +def resolve_x11_req(apt_mgr, req): return [AptRequirement.simple("libx11-dev")] -async def resolve_qt_req(apt_mgr, req): - return await find_reqs_simple( - apt_mgr, ["/usr/lib/.*/qt[0-9]+/bin/qmake"], regex=True) +def resolve_qt_req(apt_mgr, req): + return find_reqs_simple(apt_mgr, ["/usr/lib/.*/qt[0-9]+/bin/qmake"], regex=True) -async def resolve_qt_module_req(apt_mgr, req): - return await find_reqs_simple( - apt_mgr, - ["/usr/lib/.*/qt5/mkspecs/modules/qt_lib_%s\\.pri" - % re.escape(req.module)], - regex=True) - - -async def resolve_libtool_req(apt_mgr, req): +def resolve_libtool_req(apt_mgr, req): return [AptRequirement.simple("libtool")] -async def resolve_perl_module_req(apt_mgr, req): - DEFAULT_PERL_PATHS = [ - "/usr/share/perl5", "/usr/lib/.*/perl5/.*", "/usr/lib/.*/perl-base", - "/usr/lib/.*/perl/[^/]+", "/usr/share/perl/[^/]+"] +def resolve_perl_module_req(apt_mgr, req): + DEFAULT_PERL_PATHS = ["/usr/share/perl5", "/usr/lib/.*/perl5/.*", "/usr/lib/.*/perl-base"] if req.inc is None: if req.filename is None: - paths = [ - posixpath.join( - inc, re.escape(req.module.replace('::', '/') + '.pm')) - for inc in DEFAULT_PERL_PATHS] + paths = [posixpath.join(inc, re.escape(req.module.replace('::', '/') + '.pm')) for inc in DEFAULT_PERL_PATHS] regex = True elif not posixpath.isabs(req.filename): - paths = [ - posixpath.join(inc, re.escape(req.filename)) - for inc in DEFAULT_PERL_PATHS] + paths = [posixpath.join(inc, re.escape(req.filename)) for inc in DEFAULT_PERL_PATHS] regex = True else: paths = [req.filename] @@ -752,92 +617,79 @@ async def resolve_perl_module_req(apt_mgr, req): else: regex = False paths = [posixpath.join(inc, req.filename) for inc in req.inc] - return await find_reqs_simple(apt_mgr, paths, regex=regex) + return find_reqs_simple(apt_mgr, paths, regex=regex) -async def resolve_perl_file_req(apt_mgr, req): - return await find_reqs_simple(apt_mgr, [req.filename], regex=False) +def resolve_perl_file_req(apt_mgr, req): + return find_reqs_simple(apt_mgr, [req.filename], regex=False) -def _m4_macro_regex(macro): - defun_prefix = re.escape("AC_DEFUN([%s]," % macro) - au_alias_prefix = re.escape("AU_ALIAS([%s]," % macro) - m4_copy = r"m4_copy\(.*,\s*\[%s\]\)" % re.escape(macro) - return "(" + "|".join([defun_prefix, au_alias_prefix, m4_copy]) + ")" - - -def _find_local_m4_macro(macro): - # TODO(jelmer): Query some external service that can search all binary - # packages? - p = re.compile(_m4_macro_regex(macro).encode('ascii')) +def _find_aclocal_fun(macro): + # TODO(jelmer): Use the API for codesearch.debian.net instead? + defun_prefix = b"AC_DEFUN([%s]," % macro.encode("ascii") + au_alias_prefix = b"AU_ALIAS([%s]," % macro.encode("ascii") + prefixes = [defun_prefix, au_alias_prefix] for entry in os.scandir("/usr/share/aclocal"): if not entry.is_file(): continue with open(entry.path, "rb") as f: for line in f: - if any(p.finditer(line)): + if any([line.startswith(prefix) for prefix in prefixes]): return entry.path raise KeyError -async def resolve_autoconf_macro_req(apt_mgr, req): +def resolve_autoconf_macro_req(apt_mgr, req): try: - path = _find_local_m4_macro(req.macro) + path = _find_aclocal_fun(req.macro) except KeyError: logging.info("No local m4 file found defining %s", req.macro) return None - return await find_reqs_simple(apt_mgr, [path]) + return find_reqs_simple(apt_mgr, [path]) -async def resolve_python_module_req(apt_mgr, req): +def resolve_python_module_req(apt_mgr, req): if req.minimum_version: specs = [(">=", req.minimum_version)] else: specs = [] if req.python_version == 2: - return await get_package_for_python_object_path( - apt_mgr, req.module, "cpython2", specs) + return get_package_for_python_module(apt_mgr, req.module, "cpython2", specs) elif req.python_version in (None, 3): - return await get_package_for_python_object_path( - apt_mgr, req.module, "cpython3", specs) + return get_package_for_python_module(apt_mgr, req.module, "cpython3", specs) else: return None -async def resolve_python_package_req(apt_mgr, req): +def resolve_python_package_req(apt_mgr, req): if req.python_version == 2: - return await get_package_for_python_package( + return get_package_for_python_package( apt_mgr, req.package, "cpython2", req.specs ) elif req.python_version in (None, 3): - return await get_package_for_python_package( + return get_package_for_python_package( apt_mgr, req.package, "cpython3", req.specs ) else: return None -async def resolve_cargo_crate_req(apt_mgr, req): - paths = [ - "/usr/share/cargo/registry/%s\\-[0-9]+.*/Cargo\\.toml" - % re.escape(req.crate)] - return await find_reqs_simple( - apt_mgr, paths, regex=True, minimum_version=req.minimum_version) +def resolve_cargo_crate_req(apt_mgr, req): + paths = ["/usr/share/cargo/registry/%s-[0-9]+.*/Cargo.toml" % re.escape(req.crate)] + return find_reqs_simple(apt_mgr, paths, regex=True) -async def resolve_ca_req(apt_mgr, req): +def resolve_ca_req(apt_mgr, req): return [AptRequirement.simple("ca-certificates")] -async def resolve_introspection_typelib_req(apt_mgr, req): - return await find_reqs_simple( - apt_mgr, - [r'/usr/lib/.*/girepository\\-.*/%s\\-.*\.typelib' - % re.escape(req.library)], +def resolve_introspection_typelib_req(apt_mgr, req): + return find_reqs_simple( + apt_mgr, [r'/usr/lib/.*/girepository-.*/%s-.*\.typelib' % re.escape(req.library)], regex=True) -async def resolve_apt_req(apt_mgr, req): +def resolve_apt_req(apt_mgr, req): # TODO(jelmer): This should be checking whether versions match as well. for package_name in req.package_names(): if not apt_mgr.package_exists(package_name): @@ -845,47 +697,13 @@ async def resolve_apt_req(apt_mgr, req): return [req] -async def resolve_boost_component_req(apt_mgr, req): - return await find_reqs_simple( +def resolve_boost_component_req(apt_mgr, req): + return find_reqs_simple( apt_mgr, ["/usr/lib/.*/libboost_%s" % re.escape(req.name)], regex=True) -async def resolve_kf5_component_req(apt_mgr, req): - return await find_reqs_simple( - apt_mgr, ["/usr/lib/.*/cmake/KF5%s/KF5%sConfig\\.cmake" % ( - re.escape(req.name), re.escape(req.name))], - regex=True) - - -async def resolve_vcs_access_req(apt_mgr, req): - PKG_MAP = { - 'hg': 'mercurial', - 'svn': 'subversion', - 'git': 'git', - 'bzr': 'bzr', - } - ret = [] - for vcs in req.vcs: - try: - ret.append(PKG_MAP[vcs]) - except KeyError: - logging.debug('Unable to map VCS %s to package', vcs) - return [AptRequirement.from_str(','.join(ret))] - - -async def resolve_oneof_req(apt_mgr, req): - options = await asyncio.gather( - *[resolve_requirement_apt(apt_mgr, req) for req in req.elements]) - for option in options: - if not option: - continue - return option - - -APT_REQUIREMENT_RESOLVERS: List[Tuple[ - Type[Requirement], Callable[ - [AptManager, Requirement], Awaitable[List[AptRequirement]]]]] = [ +APT_REQUIREMENT_RESOLVERS = [ (AptRequirement, resolve_apt_req), (BinaryRequirement, resolve_binary_req), (VagueDependencyRequirement, resolve_vague_dep_req), @@ -918,7 +736,6 @@ APT_REQUIREMENT_RESOLVERS: List[Tuple[ (JDKRequirement, resolve_jdk_req), (JRERequirement, resolve_jre_req), (QTRequirement, resolve_qt_req), - (QtModuleRequirement, resolve_qt_module_req), (X11Requirement, resolve_x11_req), (LibtoolRequirement, resolve_libtool_req), (PerlModuleRequirement, resolve_perl_module_req), @@ -930,19 +747,13 @@ APT_REQUIREMENT_RESOLVERS: List[Tuple[ (CargoCrateRequirement, resolve_cargo_crate_req), (IntrospectionTypelibRequirement, resolve_introspection_typelib_req), (BoostComponentRequirement, resolve_boost_component_req), - (KF5ComponentRequirement, resolve_kf5_component_req), - (PHPExtensionRequirement, resolve_php_extension_req), - (OctavePackageRequirement, resolve_octave_pkg_req), - (VcsControlDirectoryAccessRequirement, resolve_vcs_access_req), - (OneOfRequirement, resolve_oneof_req), ] -async def resolve_requirement_apt( - apt_mgr, req: Requirement) -> List[AptRequirement]: +def resolve_requirement_apt(apt_mgr, req: Requirement) -> List[AptRequirement]: for rr_class, rr_fn in APT_REQUIREMENT_RESOLVERS: if isinstance(req, rr_class): - ret = await rr_fn(apt_mgr, req) + ret = rr_fn(apt_mgr, req) if not ret: return [] if not isinstance(ret, list): @@ -971,8 +782,7 @@ class AptResolver(Resolver): return "apt" def __repr__(self): - return "%s(%r, %r)" % ( - type(self).__name__, self.apt, self.tie_breakers) + return "%s(%r, %r)" % (type(self).__name__, self.apt, self.tie_breakers) @classmethod def from_session(cls, session, tie_breakers=None): @@ -998,8 +808,8 @@ class AptResolver(Resolver): apt_requirements.append(apt_req) if apt_requirements: self.apt.satisfy( - [PkgRelation.str( - chain(*[r.relations for r in apt_requirements]))]) + [PkgRelation.str(chain(*[r.relations for r in apt_requirements]))] + ) if still_missing: raise UnsatisfiedRequirements(still_missing) @@ -1021,23 +831,18 @@ class AptResolver(Resolver): [o for o, r in apt_requirements], ) - def resolve_all(self, req: Requirement): - return asyncio.run(resolve_requirement_apt(self.apt, req)) - def resolve(self, req: Requirement): - ret = self.resolve_all(req) + ret = resolve_requirement_apt(self.apt, req) if not ret: return None if len(ret) == 1: return ret[0] - logging.info( - "Need to break tie between %r with %r", ret, self.tie_breakers) + logging.info("Need to break tie between %r with %r", ret, self.tie_breakers) for tie_breaker in self.tie_breakers: winner = tie_breaker(ret) if winner is not None: if not isinstance(winner, AptRequirement): raise TypeError(winner) return winner - logging.info( - "Unable to break tie over %r, picking first: %r", ret, ret[0]) + logging.info("Unable to break tie over %r, picking first: %r", ret, ret[0]) return ret[0] diff --git a/ognibuild/resolver/dep_server.py b/ognibuild/resolver/dep_server.py deleted file mode 100644 index cd80385..0000000 --- a/ognibuild/resolver/dep_server.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/python3 -# Copyright (C) 2022 Jelmer Vernooij -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import asyncio -import logging -from typing import List - -from aiohttp import ( - ClientSession, - ClientConnectorError, - ClientResponseError, - ServerDisconnectedError, -) -from yarl import URL - - -from .. import Requirement, USER_AGENT -from ..debian.apt import AptManager -from .apt import AptRequirement, AptResolver - - -class DepServerError(Exception): - - def __init__(self, inner): - self.inner = inner - - -async def resolve_apt_requirement_dep_server( - url: str, req: Requirement) -> List[AptRequirement]: - """Resolve a requirement to an APT requirement with a dep server. - - Args: - url: Dep server URL - req: Requirement to resolve - Returns: - List of Apt requirements. - """ - async with ClientSession() as session: - try: - async with session.post(URL(url) / "resolve-apt", headers={ - 'User-Agent': USER_AGENT}, - json={'requirement': req.json()}, - raise_for_status=True) as resp: - return [ - AptRequirement._from_json(e) for e in await resp.json()] - except (ClientConnectorError, ClientResponseError, - ServerDisconnectedError) as e: - logging.warning('Unable to contact dep server: %r', e) - raise DepServerError(e) - - -class DepServerAptResolver(AptResolver): - def __init__(self, apt, dep_server_url, tie_breakers=None): - super(DepServerAptResolver, self).__init__( - apt, tie_breakers=tie_breakers) - self.dep_server_url = dep_server_url - - @classmethod - def from_session(cls, session, dep_server_url, tie_breakers=None): - return cls( - AptManager.from_session(session), dep_server_url, - tie_breakers=tie_breakers) - - def resolve_all(self, req: Requirement): - try: - req.json() - except NotImplementedError: - return super(DepServerAptResolver, self).resolve_all(req) - try: - return asyncio.run( - resolve_apt_requirement_dep_server(self.dep_server_url, req)) - except DepServerError: - logging.warning('Falling back to resolving error locally') - return super(DepServerAptResolver, self).resolve_all(req) diff --git a/ognibuild/session/__init__.py b/ognibuild/session/__init__.py index 0ef6a1a..464381d 100644 --- a/ognibuild/session/__init__.py +++ b/ognibuild/session/__init__.py @@ -69,14 +69,12 @@ class Session(object): raise NotImplementedError(self.check_output) def Popen( - self, argv, cwd: Optional[str] = None, user: Optional[str] = None, - **kwargs + self, argv, cwd: Optional[str] = None, user: Optional[str] = None, **kwargs ): raise NotImplementedError(self.Popen) def call( - self, argv: List[str], cwd: Optional[str] = None, - user: Optional[str] = None + self, argv: List[str], cwd: Optional[str] = None, user: Optional[str] = None ): raise NotImplementedError(self.call) @@ -102,26 +100,17 @@ class Session(object): def external_path(self, path: str) -> str: raise NotImplementedError - def rmtree(self, path: str) -> str: - raise NotImplementedError - is_temporary: bool class SessionSetupFailure(Exception): """Session failed to be set up.""" - def __init__(self, reason, errlines=None): - self.reason = reason - self.errlines = errlines - -def run_with_tee(session: Session, - args: List[str], **kwargs) -> Tuple[int, List[str]]: +def run_with_tee(session: Session, args: List[str], **kwargs): if "stdin" not in kwargs: kwargs["stdin"] = subprocess.DEVNULL - p = session.Popen( - args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) + p = session.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) contents = [] while p.poll() is None: line = p.stdout.readline() @@ -132,8 +121,7 @@ def run_with_tee(session: Session, def get_user(session): - return session.check_output( - ["sh", "-c", "echo $USER"], cwd="/").decode().strip() + return session.check_output(["echo", "$USER"], cwd="/").decode().strip() def which(session, name): diff --git a/ognibuild/session/plain.py b/ognibuild/session/plain.py index ab08e6c..df4b1a2 100644 --- a/ognibuild/session/plain.py +++ b/ognibuild/session/plain.py @@ -20,7 +20,6 @@ from . import Session, NoSessionOpen, SessionAlreadyOpen import contextlib import os -import shutil import subprocess import tempfile from typing import Optional, Dict, List @@ -73,8 +72,7 @@ class PlainSession(Session): close_fds: bool = True, ): argv = self._prepend_user(user, argv) - return subprocess.check_call( - argv, cwd=cwd, env=env, close_fds=close_fds) + return subprocess.check_call(argv, cwd=cwd, env=env, close_fds=close_fds) def check_output( self, @@ -86,19 +84,13 @@ class PlainSession(Session): argv = self._prepend_user(user, argv) return subprocess.check_output(argv, cwd=cwd, env=env) - def Popen( - self, args, stdout=None, stderr=None, stdin=None, user=None, - cwd=None, env=None): + def Popen(self, args, stdout=None, stderr=None, stdin=None, user=None, cwd=None, env=None): args = self._prepend_user(user, args) - return subprocess.Popen( - args, stdout=stdout, stderr=stderr, stdin=stdin, cwd=cwd, env=env) + return subprocess.Popen(args, stdout=stdout, stderr=stderr, stdin=stdin, cwd=cwd, env=env) def exists(self, path): return os.path.exists(path) - def rmtree(self, path): - return shutil.rmtree(path) - def scandir(self, path): return os.scandir(path) diff --git a/ognibuild/session/schroot.py b/ognibuild/session/schroot.py index 7a485f2..bc9bcd1 100644 --- a/ognibuild/session/schroot.py +++ b/ognibuild/session/schroot.py @@ -66,38 +66,25 @@ class SchrootSession(Session): if line.startswith(b"E: "): logging.error("%s", line[3:].decode(errors="replace")) logging.warning( - "Failed to close schroot session %s, leaving stray.", - self.session_id + "Failed to close schroot session %s, leaving stray.", self.session_id ) self.session_id = None return False self.session_id = None - self._location = None return True def __enter__(self) -> "Session": if self.session_id is not None: raise SessionAlreadyOpen(self) - stderr = tempfile.TemporaryFile() try: self.session_id = ( - subprocess.check_output( - ["schroot", "-c", self.chroot, "-b"], stderr=stderr) + subprocess.check_output(["schroot", "-c", self.chroot, "-b"]) .strip() .decode() ) except subprocess.CalledProcessError: - stderr.seek(0) - errlines = stderr.readlines() - if len(errlines) == 1: - raise SessionSetupFailure( - errlines[0].rstrip().decode(), errlines=errlines) - elif len(errlines) == 0: - raise SessionSetupFailure( - "No output from schroot", errlines=errlines) - else: - raise SessionSetupFailure( - errlines[-1].decode(), errlines=errlines) + # TODO(jelmer): Capture stderr and forward in SessionSetupFailure + raise SessionSetupFailure() logging.info( "Opened schroot session %s (from %s)", self.session_id, self.chroot ) @@ -169,28 +156,24 @@ class SchrootSession(Session): env: Optional[Dict[str, str]] = None, ) -> bytes: try: - return subprocess.check_output( - self._run_argv(argv, cwd, user, env=env)) + return subprocess.check_output(self._run_argv(argv, cwd, user, env=env)) except subprocess.CalledProcessError as e: raise subprocess.CalledProcessError(e.returncode, argv) def Popen( - self, argv, cwd: Optional[str] = None, user: Optional[str] = None, - **kwargs + self, argv, cwd: Optional[str] = None, user: Optional[str] = None, **kwargs ): return subprocess.Popen(self._run_argv(argv, cwd, user), **kwargs) def call( - self, argv: List[str], cwd: Optional[str] = None, - user: Optional[str] = None + self, argv: List[str], cwd: Optional[str] = None, user: Optional[str] = None ): return subprocess.call(self._run_argv(argv, cwd, user)) def create_home(self) -> None: """Create the user's home directory.""" home = ( - self.check_output( - ["sh", "-c", "echo $HOME"], cwd="/").decode().rstrip("\n") + self.check_output(["sh", "-c", "echo $HOME"], cwd="/").decode().rstrip("\n") ) user = ( self.check_output(["sh", "-c", "echo $LOGNAME"], cwd="/") @@ -206,8 +189,7 @@ class SchrootSession(Session): return os.path.join(self.location, path.lstrip("/")) if self._cwd is None: raise ValueError("no cwd set") - return os.path.join( - self.location, os.path.join(self._cwd, path).lstrip("/")) + return os.path.join(self.location, os.path.join(self._cwd, path).lstrip("/")) def exists(self, path: str) -> bool: fullpath = self.external_path(path) @@ -221,17 +203,13 @@ class SchrootSession(Session): fullpath = self.external_path(path) return os.mkdir(fullpath) - def rmtree(self, path: str): - import shutil - fullpath = self.external_path(path) - return shutil.rmtree(fullpath) - def setup_from_vcs( self, tree, include_controldir: Optional[bool] = None, subdir="package" ): from ..vcs import dupe_vcs_tree, export_vcs_tree build_dir = os.path.join(self.location, "build") + directory = tempfile.mkdtemp(dir=build_dir) reldir = "/" + os.path.relpath(directory, self.location) @@ -250,7 +228,7 @@ class SchrootSession(Session): directory = tempfile.mkdtemp(dir=build_dir) reldir = "/" + os.path.relpath(directory, self.location) export_directory = os.path.join(directory, subdir) - shutil.copytree(path, export_directory, symlinks=True) + shutil.copytree(path, export_directory, dirs_exist_ok=True) return export_directory, os.path.join(reldir, subdir) is_temporary = True diff --git a/ognibuild/test.py b/ognibuild/test.py index 2ab8261..750143f 100644 --- a/ognibuild/test.py +++ b/ognibuild/test.py @@ -15,25 +15,16 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from functools import partial - from .buildsystem import NoBuildToolsFound -from .fix_build import iterate_with_build_fixers -from .logs import NoLogManager -def run_test(session, buildsystems, resolver, fixers, log_manager=None): +def run_test(session, buildsystems, resolver, fixers): # Some things want to write to the user's home directory, # e.g. pip caches in ~/.cache session.create_home() - if log_manager is None: - log_manager = NoLogManager() - for buildsystem in buildsystems: - iterate_with_build_fixers( - fixers, log_manager.wrap( - partial(buildsystem.test, session, resolver))) + buildsystem.test(session, resolver, fixers) return raise NoBuildToolsFound() diff --git a/tests/__init__.py b/ognibuild/tests/__init__.py similarity index 85% rename from tests/__init__.py rename to ognibuild/tests/__init__.py index 71727f9..075535d 100644 --- a/tests/__init__.py +++ b/ognibuild/tests/__init__.py @@ -23,13 +23,10 @@ import unittest def test_suite(): names = [ - 'buildlog', - 'logs', + "debian_build", ] if os.path.exists("/usr/bin/dpkg-architecture"): - names.append("debian_build") names.append("debian_fix_build") - names.append("resolver_apt") - module_names = ["tests.test_" + name for name in names] + module_names = ["ognibuild.tests.test_" + name for name in names] loader = unittest.TestLoader() return loader.loadTestsFromNames(module_names) diff --git a/tests/test_debian_build.py b/ognibuild/tests/test_debian_build.py similarity index 72% rename from tests/test_debian_build.py rename to ognibuild/tests/test_debian_build.py index fe656cd..0b06869 100644 --- a/tests/test_debian_build.py +++ b/ognibuild/tests/test_debian_build.py @@ -17,17 +17,8 @@ import datetime import os -import sys -from debian.changelog import Version - -from ognibuild.debian.build import ( - add_dummy_changelog_entry, - get_build_architecture, - version_add_suffix, - _builddeb_command, - DEFAULT_BUILDER, -) +from ..debian.build import add_dummy_changelog_entry, get_build_architecture from breezy.tests import TestCaseWithTransport, TestCase @@ -159,43 +150,3 @@ class BuildArchitectureTests(TestCase): def test_is_str(self): self.assertIsInstance(get_build_architecture(), str) - - -class VersionAddSuffixTests(TestCase): - - def test_native(self): - self.assertEqual( - Version('1.0~jan+lint4'), - version_add_suffix(Version('1.0~jan+lint3'), '~jan+lint')) - self.assertEqual( - Version('1.0~jan+lint1'), - version_add_suffix(Version('1.0'), '~jan+lint')) - - def test_normal(self): - self.assertEqual( - Version('1.0-1~jan+lint4'), - version_add_suffix(Version('1.0-1~jan+lint3'), '~jan+lint')) - self.assertEqual( - Version('1.0-1~jan+lint1'), - version_add_suffix(Version('1.0-1'), '~jan+lint')) - self.assertEqual( - Version('0.0.12-1~jan+lint1'), - version_add_suffix(Version('0.0.12-1'), '~jan+lint')) - self.assertEqual( - Version('0.0.12-1~jan+unchanged1~jan+lint1'), - version_add_suffix( - Version('0.0.12-1~jan+unchanged1'), '~jan+lint')) - - -class BuilddebCommandTests(TestCase): - - def test_simple(self): - self.assertEqual( - [sys.executable, "-m", "breezy", "builddeb", - "--guess-upstream-branch-url", "--builder=" + DEFAULT_BUILDER], - _builddeb_command()) - self.assertEqual( - [sys.executable, "-m", "breezy", "builddeb", - "--guess-upstream-branch-url", "--builder=" + DEFAULT_BUILDER, - "--result-dir=/tmp/blah"], - _builddeb_command(result_dir="/tmp/blah")) diff --git a/tests/test_debian_fix_build.py b/ognibuild/tests/test_debian_fix_build.py similarity index 72% rename from tests/test_debian_fix_build.py rename to ognibuild/tests/test_debian_fix_build.py index ba28ffc..c0a5456 100644 --- a/tests/test_debian_fix_build.py +++ b/ognibuild/tests/test_debian_fix_build.py @@ -29,15 +29,13 @@ from buildlog_consultant.common import ( MissingRubyGem, MissingValaPackage, ) -from ognibuild.debian.apt import AptManager, FileSearcher -from ognibuild.debian.fix_build import ( +from ..debian.apt import AptManager, FileSearcher +from ..debian.fix_build import ( resolve_error, versioned_package_fixers, apt_fixers, DebianPackagingContext, - add_build_dependency, ) -from ognibuild.resolver.apt import AptRequirement from breezy.commit import NullCommitReporter from breezy.tests import TestCaseWithTransport @@ -46,7 +44,7 @@ class DummyAptSearcher(FileSearcher): def __init__(self, files): self._apt_files = files - async def search_files(self, path, regex=False, case_insensitive=False): + def search_files(self, path, regex=False, case_insensitive=False): for p, pkg in sorted(self._apt_files.items()): if case_insensitive: flags = re.I @@ -99,7 +97,7 @@ blah (0.1) UNRELEASED; urgency=medium self._apt_files = {} def resolve(self, error, context=("build",)): - from ognibuild.session.plain import PlainSession + from ..session.plain import PlainSession session = PlainSession() apt = AptManager(session) @@ -111,8 +109,7 @@ blah (0.1) UNRELEASED; urgency=medium update_changelog=True, commit_reporter=NullCommitReporter(), ) - fixers = versioned_package_fixers( - session, context, apt) + apt_fixers(apt, context) + fixers = versioned_package_fixers(session, context, apt) + apt_fixers(apt, context) return resolve_error(error, ("build",), fixers) def get_build_deps(self): @@ -121,8 +118,7 @@ blah (0.1) UNRELEASED; urgency=medium def test_missing_command_unknown(self): self._apt_files = {} - self.assertFalse(self.resolve( - MissingCommand("acommandthatdoesnotexist"))) + self.assertFalse(self.resolve(MissingCommand("acommandthatdoesnotexist"))) def test_missing_command_brz(self): self._apt_files = { @@ -134,8 +130,7 @@ blah (0.1) UNRELEASED; urgency=medium self.overrideEnv("DEBFULLNAME", "Jelmer Vernooij") self.assertTrue(self.resolve(MissingCommand("brz"))) self.assertEqual("libc6, brz", self.get_build_deps()) - rev = self.tree.branch.repository.get_revision( - self.tree.branch.last_revision()) + rev = self.tree.branch.repository.get_revision(self.tree.branch.last_revision()) self.assertEqual("Add missing build dependency on brz.\n", rev.message) self.assertFalse(self.resolve(MissingCommand("brz"))) self.assertEqual("libc6, brz", self.get_build_deps()) @@ -158,12 +153,10 @@ blah (0.1) UNRELEASED; urgency=medium def test_missing_ruby_file_from_gem(self): self._apt_files = { "/usr/share/rubygems-integration/all/gems/activesupport-" - "5.2.3/lib/active_support/core_ext/string/strip.rb": - "ruby-activesupport" + "5.2.3/lib/active_support/core_ext/string/strip.rb": "ruby-activesupport" } self.assertTrue( - self.resolve(MissingRubyFile( - "active_support/core_ext/string/strip")) + self.resolve(MissingRubyFile("active_support/core_ext/string/strip")) ) self.assertEqual("libc6, ruby-activesupport", self.get_build_deps()) @@ -180,8 +173,7 @@ blah (0.1) UNRELEASED; urgency=medium self.assertEqual("libc6, ruby-bio (>= 2.0.3)", self.get_build_deps()) def test_missing_perl_module(self): - self._apt_files = { - "/usr/share/perl5/App/cpanminus/fatscript.pm": "cpanminus"} + self._apt_files = {"/usr/share/perl5/App/cpanminus/fatscript.pm": "cpanminus"} self.assertTrue( self.resolve( MissingPerlModule( @@ -208,34 +200,28 @@ blah (0.1) UNRELEASED; urgency=medium def test_missing_pkg_config(self): self._apt_files = { - "/usr/lib/x86_64-linux-gnu/pkgconfig/xcb-xfixes.pc": - "libxcb-xfixes0-dev" + "/usr/lib/x86_64-linux-gnu/pkgconfig/xcb-xfixes.pc": "libxcb-xfixes0-dev" } self.assertTrue(self.resolve(MissingPkgConfig("xcb-xfixes"))) self.assertEqual("libc6, libxcb-xfixes0-dev", self.get_build_deps()) def test_missing_pkg_config_versioned(self): self._apt_files = { - "/usr/lib/x86_64-linux-gnu/pkgconfig/xcb-xfixes.pc": - "libxcb-xfixes0-dev" + "/usr/lib/x86_64-linux-gnu/pkgconfig/xcb-xfixes.pc": "libxcb-xfixes0-dev" } self.assertTrue(self.resolve(MissingPkgConfig("xcb-xfixes", "1.0"))) - self.assertEqual( - "libc6, libxcb-xfixes0-dev (>= 1.0)", self.get_build_deps()) + self.assertEqual("libc6, libxcb-xfixes0-dev (>= 1.0)", self.get_build_deps()) def test_missing_python_module(self): - self._apt_files = { - "/usr/lib/python3/dist-packages/m2r.py": "python3-m2r"} + self._apt_files = {"/usr/lib/python3/dist-packages/m2r.py": "python3-m2r"} self.assertTrue(self.resolve(MissingPythonModule("m2r"))) self.assertEqual("libc6, python3-m2r", self.get_build_deps()) def test_missing_go_package(self): self._apt_files = { - "/usr/share/gocode/src/github.com/chzyer/readline/utils_test.go": - "golang-github-chzyer-readline-dev", + "/usr/share/gocode/src/github.com/chzyer/readline/utils_test.go": "golang-github-chzyer-readline-dev", } - self.assertTrue(self.resolve( - MissingGoPackage("github.com/chzyer/readline"))) + self.assertTrue(self.resolve(MissingGoPackage("github.com/chzyer/readline"))) self.assertEqual( "libc6, golang-github-chzyer-readline-dev", self.get_build_deps() ) @@ -246,63 +232,3 @@ blah (0.1) UNRELEASED; urgency=medium } self.assertTrue(self.resolve(MissingValaPackage("posix"))) self.assertEqual("libc6, valac-0.48-vapi", self.get_build_deps()) - - -class AddBuildDependencyTests(TestCaseWithTransport): - - def setUp(self): - super(AddBuildDependencyTests, self).setUp() - self.tree = self.make_branch_and_tree(".") - self.build_tree_contents( - [ - ("debian/",), - ( - "debian/control", - """\ -Source: blah -Build-Depends: libc6 - -Package: python-blah -Depends: ${python3:Depends} -Description: A python package - Foo -""", - ), - ( - "debian/changelog", - """\ -blah (0.1) UNRELEASED; urgency=medium - - * Initial release. (Closes: #XXXXXX) - - -- Jelmer Vernooij Sat, 04 Apr 2020 14:12:13 +0000 -""", - ), - ] - ) - self.tree.add(["debian", "debian/control", "debian/changelog"]) - self.tree.commit("Initial commit") - self.context = DebianPackagingContext( - self.tree, - subpath="", - committer="ognibuild ", - update_changelog=True, - commit_reporter=NullCommitReporter(), - ) - - def test_already_present(self): - requirement = AptRequirement.simple('libc6') - self.assertFalse(add_build_dependency(self.context, requirement)) - - def test_basic(self): - requirement = AptRequirement.simple('foo') - self.assertTrue(add_build_dependency(self.context, requirement)) - self.assertFileEqual("""\ -Source: blah -Build-Depends: libc6, foo - -Package: python-blah -Depends: ${python3:Depends} -Description: A python package - Foo -""", 'debian/control') diff --git a/ognibuild/upstream.py b/ognibuild/upstream.py deleted file mode 100644 index 8a08c90..0000000 --- a/ognibuild/upstream.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/python3 -# Copyright (C) 2020-2021 Jelmer Vernooij -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -from dataclasses import dataclass, field -from typing import Optional, Dict, Any -from debian.changelog import Version -import logging -import re - -from . import Requirement -from .requirements import ( - CargoCrateRequirement, - GoPackageRequirement, - PythonPackageRequirement, -) -from .resolver.apt import AptRequirement, OneOfRequirement - - -@dataclass -class UpstreamInfo: - name: Optional[str] - buildsystem: Optional[str] = None - branch_url: Optional[str] = None - branch_subpath: Optional[str] = None - tarball_url: Optional[str] = None - version: Optional[str] = None - metadata: Dict[str, Any] = field(default_factory=dict) - - def json(self): - return { - 'name': self.name, - 'buildsystem': self.buildsystem, - 'branch_url': self.branch_url, - 'branch_subpath': self.branch_subpath, - 'tarball_url': self.tarball_url, - 'version': self.version - } - - -def go_base_name(package): - (hostname, path) = package.split('/', 1) - if hostname == "github.com": - hostname = "github" - if hostname == "gopkg.in": - hostname = "gopkg" - path = path.rstrip('/').replace("/", "-") - if path.endswith('.git'): - path = path[:-4] - return (hostname + path).replace("_", "-").lower() - - -def load_crate_info(crate): - import urllib.error - from urllib.request import urlopen, Request - import json - http_url = 'https://crates.io/api/v1/crates/%s' % crate - headers = {'User-Agent': 'debianize', 'Accept': 'application/json'} - http_contents = urlopen(Request(http_url, headers=headers)).read() - try: - return json.loads(http_contents) - except urllib.error.HTTPError as e: - if e.code == 404: - logging.warning('No crate %r', crate) - return None - raise - - -def find_python_package_upstream(requirement): - import urllib.error - from urllib.request import urlopen, Request - import json - http_url = 'https://pypi.org/pypi/%s/json' % requirement.package - headers = {'User-Agent': 'ognibuild', 'Accept': 'application/json'} - try: - http_contents = urlopen( - Request(http_url, headers=headers)).read() - except urllib.error.HTTPError as e: - if e.code == 404: - logging.warning('No pypi project %r', requirement.package) - return None - raise - pypi_data = json.loads(http_contents) - upstream_branch = None - for name, url in pypi_data['info']['project_urls'].items(): - if name.lower() in ('github', 'repository'): - upstream_branch = url - tarball_url = None - for url_data in pypi_data['urls']: - if url_data.get('package_type') == 'sdist': - tarball_url = url_data['url'] - return UpstreamInfo( - branch_url=upstream_branch, branch_subpath='', - name='python-%s' % pypi_data['info']['name'], - tarball_url=tarball_url) - - -def find_go_package_upstream(requirement): - if requirement.package.startswith('github.com/'): - return UpstreamInfo( - name='golang-%s' % go_base_name(requirement.package), - branch_url='https://%s' % '/'.join( - requirement.package.split('/')[:3]), - branch_subpath='') - - -def find_cargo_crate_upstream(requirement): - import semver - from debmutate.debcargo import semver_pair - data = load_crate_info(requirement.crate) - if data is None: - return None - upstream_branch = data['crate']['repository'] - name = 'rust-' + data['crate']['name'].replace('_', '-') - version = None - if requirement.api_version is not None: - for version_info in data['versions']: - if (not version_info['num'].startswith( - requirement.api_version + '.') - and not version_info['num'] == requirement.api_version): - continue - if version is None: - version = semver.VersionInfo.parse(version_info['num']) - else: - version = semver.max_ver( - version, semver.VersionInfo.parse(version_info['num'])) - if version is None: - logging.warning( - 'Unable to find version of crate %s ' - 'that matches API version %s', - name, requirement.api_version) - else: - name += '-' + semver_pair(str(version)) - return UpstreamInfo( - branch_url=upstream_branch, branch_subpath=None, - name=name, version=str(version) if version else None, - metadata={'X-Cargo-Crate': data['crate']['name']}, - buildsystem='cargo') - - -def apt_to_cargo_requirement(m, rels): - name = m.group(1) - api_version = m.group(2) - if m.group(3): - features = set(m.group(3)[1:].split('-')) - else: - features = set() - if not rels: - minimum_version = None - elif len(rels) == 1 and rels[0][0] == '>=': - minimum_version = Version(rels[0][1]).upstream_version - else: - logging.warning('Unable to parse Debian version %r', rels) - minimum_version = None - - return CargoCrateRequirement( - name, api_version=api_version, - features=features, minimum_version=minimum_version) - - -def apt_to_python_requirement(m, rels): - name = m.group(2) - python_version = m.group(1) - if not rels: - minimum_version = None - elif len(rels) == 1 and rels[0][0] == '>=': - minimum_version = Version(rels[0][1]).upstream_version - else: - logging.warning('Unable to parse Debian version %r', rels) - minimum_version = None - return PythonPackageRequirement( - name, python_version=(python_version or None), - minimum_version=minimum_version) - - -def apt_to_go_requirement(m, rels): - parts = m.group(1).split('-') - if parts[0] == 'github': - parts[0] = 'github.com' - if parts[0] == 'gopkg': - parts[0] = 'gopkg.in' - if not rels: - version = None - elif len(rels) == 1 and rels[0][0] == '=': - version = Version(rels[0][1]).upstream_version - else: - logging.warning('Unable to parse Debian version %r', rels) - version = None - return GoPackageRequirement('/'.join(parts), version=version) - - -BINARY_PACKAGE_UPSTREAM_MATCHERS = [ - (r'librust-(.*)-([^-+]+)(\+.*?)-dev', apt_to_cargo_requirement), - (r'python([0-9.]*)-(.*)', apt_to_python_requirement), - (r'golang-(.*)-dev', apt_to_go_requirement), -] - - -_BINARY_PACKAGE_UPSTREAM_MATCHERS = [ - (re.compile(r), fn) for (r, fn) in BINARY_PACKAGE_UPSTREAM_MATCHERS] - - -def find_apt_upstream(requirement: AptRequirement) -> Optional[UpstreamInfo]: - for option in requirement.relations: - for rel in option: - for matcher, fn in _BINARY_PACKAGE_UPSTREAM_MATCHERS: - m = matcher.fullmatch(rel['name']) - if m: - upstream_requirement = fn( - m, [rel['version']] if rel['version'] else []) - return find_upstream(upstream_requirement) - - logging.warning( - 'Unable to map binary package name %s to upstream', - rel['name']) - return None - - -def find_or_upstream(requirement: OneOfRequirement) -> Optional[UpstreamInfo]: - for req in requirement.elements: - info = find_upstream(req) - if info is not None: - return info - return None - - -UPSTREAM_FINDER = { - 'python-package': find_python_package_upstream, - 'go-package': find_go_package_upstream, - 'cargo-crate': find_cargo_crate_upstream, - 'apt': find_apt_upstream, - 'or': find_or_upstream, - } - - -def find_upstream(requirement: Requirement) -> Optional[UpstreamInfo]: - try: - return UPSTREAM_FINDER[requirement.family](requirement) - except KeyError: - return None diff --git a/ognibuild/vcs.py b/ognibuild/vcs.py index 9115e28..45b32c6 100644 --- a/ognibuild/vcs.py +++ b/ognibuild/vcs.py @@ -43,8 +43,7 @@ def dupe_vcs_tree(tree, directory): tree = tree.basis_tree() try: result = tree._repository.controldir.sprout( - directory, create_tree_if_local=True, - revision_id=tree.get_revision_id() + directory, create_tree_if_local=True, revision_id=tree.get_revision_id() ) except OSError as e: if e.errno == errno.ENOSPC: diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index fed528d..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" diff --git a/releaser.conf b/releaser.conf new file mode 100644 index 0000000..c4c19a4 --- /dev/null +++ b/releaser.conf @@ -0,0 +1,14 @@ +name: "ognibuild" +timeout_days: 5 +tag_name: "v$VERSION" +verify_command: "python3 setup.py test" +update_version { + path: "setup.py" + match: "^ version=\"(.*)\",$" + new_line: " version=\"$VERSION\"," +} +update_version { + path: "ognibuild/__init__.py" + match: "^__version__ = \\((.*)\\)$" + new_line: "__version__ = $TUPLED_VERSION" +} diff --git a/scripts/report-apt-deps-status b/scripts/report-apt-deps-status deleted file mode 100755 index b0681d3..0000000 --- a/scripts/report-apt-deps-status +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/python3 - -import argparse -from contextlib import ExitStack -import logging -import sys -from typing import Dict, List - -from ognibuild.buildsystem import NoBuildToolsFound, detect_buildsystems -from ognibuild.requirements import Requirement -from ognibuild.resolver.apt import AptResolver -from ognibuild.session.plain import PlainSession - -parser = argparse.ArgumentParser('report-apt-deps-status') -parser.add_argument('directory', type=str, default='.', nargs='?') -parser.add_argument( - '--detailed', action='store_true', help='Show detailed analysis') -args = parser.parse_args() - -logging.basicConfig(format='%(message)s', level=logging.INFO) - -session = PlainSession() -with ExitStack() as es: - es.enter_context(session) - session.chdir(args.directory) - resolver = AptResolver.from_session(session) - - try: - bss = list(detect_buildsystems(args.directory)) - except NoBuildToolsFound: - logging.fatal('No build tools found') - sys.exit(1) - logging.debug("Detected buildsystems: %s", ", ".join(map(str, bss))) - deps: Dict[str, List[Requirement]] = {} - for buildsystem in bss: - try: - declared_reqs = buildsystem.get_declared_dependencies(session, []) - for stage, req in declared_reqs: - deps.setdefault(stage, []).append(req) - except NotImplementedError: - logging.warning( - 'Unable to get dependencies from buildsystem %r, skipping', - buildsystem) - continue - - if args.detailed: - for stage, reqs in deps.items(): - logging.info("Stage: %s", stage) - for req in reqs: - apt_req = resolver.resolve(req) - logging.info("%s: %s", req, apt_req.pkg_relation_str()) - logging.info('') - else: - build_depends = [] - test_depends = [] - run_depends = [] - unresolved = [] - for stage, reqs in deps.items(): - for req in reqs: - apt_req = resolver.resolve(req) - if apt_req is None: - unresolved.append(req) - elif stage == 'core': - build_depends.append(apt_req) - run_depends.append(apt_req) - elif stage == 'build': - build_depends.append(apt_req) - elif stage == 'test': - test_depends.append(apt_req) - else: - raise NotImplementedError('stage %s not supported' % stage) - if build_depends: - logging.info( - 'Build-Depends: %s', - ', '.join([d.pkg_relation_str() for d in build_depends])) - if test_depends: - logging.info( - 'Test-Depends: %s', - ', '.join([d.pkg_relation_str() for d in test_depends])) - if run_depends: - logging.info( - 'Depends: %s', - ', '.join([d.pkg_relation_str() for d in run_depends])) - if unresolved: - sys.stdout.write('\n') - logging.warning( - 'Unable to find apt packages for the following dependencies:') - for req in unresolved: - logging.warning('* %s', req) diff --git a/setup.cfg b/setup.cfg index e4e8584..07ea45f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,65 +1,13 @@ -[metadata] -name = ognibuild -description = Detect and run any build system -version = attr:ognibuild.__version__ -maintainer = Jelmer Vernooij -maintainer_email = jelmer@jelmer.uk -license = GNU GPLv2 or later -url = https://jelmer.uk/code/ognibuild -classifiers = - Development Status :: 4 - Beta - License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: Implementation :: CPython - Operating System :: POSIX - -[options] -packages = - ognibuild - ognibuild.debian - ognibuild.resolver - ognibuild.session -scripts = scripts/report-apt-deps-status -install_requires = - breezy>=3.2 - buildlog-consultant>=0.0.21 - requirements-parser - toml - setuptools - ruamel.yaml -tests_require = - testtools - types-toml - -[options.entry_points] -console_scripts = - ogni=ognibuild.__main__:main - deb-fix-build=ognibuild.debian.fix_build:main - -[options.extras_require] -dev = - testtools -debian = - debmutate - python_debian - python_apt - brz-debian - lz4 -remote = - breezy - dulwich -dep_server = - aiohttp - aiohttp-openmetrics -gcp = google-cloud-logging - [flake8] banned-modules = silver-platter = Should not use silver-platter -exclude = build,.eggs/ [mypy] ignore_missing_imports = True [bdist_wheel] universal = 1 + +[egg_info] +tag_build = +tag_date = 0 + diff --git a/setup.py b/setup.py index eb46b40..8167aae 100755 --- a/setup.py +++ b/setup.py @@ -1,3 +1,40 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 +# encoding: utf-8 + from setuptools import setup -setup() + + +setup(name="ognibuild", + description="Detect and run any build system", + version="0.0.7", + maintainer="Jelmer Vernooij", + maintainer_email="jelmer@jelmer.uk", + license="GNU GPLv2 or later", + url="https://jelmer.uk/code/ognibuild", + packages=['ognibuild', 'ognibuild.tests', 'ognibuild.debian', 'ognibuild.resolver', 'ognibuild.session'], + classifiers=[ + 'Development Status :: 4 - Beta', + 'License :: OSI Approved :: ' + 'GNU General Public License v2 or later (GPLv2+)', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Operating System :: POSIX', + ], + entry_points={ + "console_scripts": [ + "ogni=ognibuild.__main__:main", + "deb-fix-build=ognibuild.debian.fix_build:main", + ] + }, + install_requires=[ + 'breezy', + 'buildlog-consultant>=0.0.10', + 'requirements-parser', + ], + extras_require={ + 'debian': ['debmutate', 'python_debian', 'python_apt'], + }, + tests_require=['python_debian', 'buildlog-consultant', 'breezy', 'testtools'], + test_suite='ognibuild.tests.test_suite', + ) diff --git a/tests/test_buildlog.py b/tests/test_buildlog.py deleted file mode 100644 index e1d19d9..0000000 --- a/tests/test_buildlog.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2022 Jelmer Vernooij -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -from ognibuild.buildlog import PROBLEM_CONVERTERS - -from buildlog_consultant import ( - problem_clses, - __version__ as buildlog_consultant_version, -) - -from unittest import TestCase - - -class TestProblemsExists(TestCase): - - def test_exist(self): - for entry in PROBLEM_CONVERTERS: - if len(entry) == 2: - problem_kind, fn = entry - min_version = None - elif len(entry) == 3: - problem_kind, fn, min_version = entry - else: - raise TypeError(entry) - if min_version is not None: - min_version_tuple = tuple( - [int(x) for x in min_version.split('.')]) - if buildlog_consultant_version < min_version_tuple: - continue - self.assertTrue( - problem_kind in problem_clses, - f"{problem_kind} does not exist in known " - "buildlog-consultant problem kinds") diff --git a/tests/test_logs.py b/tests/test_logs.py deleted file mode 100644 index 6fc4e65..0000000 --- a/tests/test_logs.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2022 Jelmer Vernooij -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import os -import sys -import tempfile -from unittest import TestCase - -from ognibuild.logs import ( - copy_output, - redirect_output, - rotate_logfile, - DirectoryLogManager, -) - - -class TestCopyOutput(TestCase): - - def test_no_tee(self): - with tempfile.TemporaryDirectory() as td: - p = os.path.join(td, 'foo.log') - with copy_output(p, tee=False): - sys.stdout.write('lala\n') - sys.stdout.flush() - with open(p, 'r') as f: - self.assertEqual('lala\n', f.read()) - - def test_tee(self): - with tempfile.TemporaryDirectory() as td: - p = os.path.join(td, 'foo.log') - with copy_output(p, tee=True): - sys.stdout.write('lala\n') - sys.stdout.flush() - with open(p, 'r') as f: - self.assertEqual('lala\n', f.read()) - - -class TestRedirectOutput(TestCase): - - def test_simple(self): - with tempfile.TemporaryDirectory() as td: - p = os.path.join(td, 'foo.log') - with open(p, 'w') as f: - with redirect_output(f): - sys.stdout.write('lala\n') - sys.stdout.flush() - with open(p, 'r') as f: - self.assertEqual('lala\n', f.read()) - - -class TestRotateLogfile(TestCase): - - def test_does_not_exist(self): - with tempfile.TemporaryDirectory() as td: - p = os.path.join(td, 'foo.log') - rotate_logfile(p) - self.assertEqual([], os.listdir(td)) - - def test_simple(self): - with tempfile.TemporaryDirectory() as td: - p = os.path.join(td, 'foo.log') - with open(p, 'w') as f: - f.write('contents\n') - rotate_logfile(p) - self.assertEqual(['foo.log.1'], os.listdir(td)) - - -class TestLogManager(TestCase): - - def test_simple(self): - with tempfile.TemporaryDirectory() as td: - p = os.path.join(td, 'foo.log') - lm = DirectoryLogManager(p, mode='redirect') - - def writesomething(): - sys.stdout.write('foo\n') - sys.stdout.flush() - fn = lm.wrap(writesomething) - fn() - with open(p, 'r') as f: - self.assertEqual('foo\n', f.read()) diff --git a/tests/test_resolver_apt.py b/tests/test_resolver_apt.py deleted file mode 100644 index abcad3f..0000000 --- a/tests/test_resolver_apt.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2022 Jelmer Vernooij -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -from unittest import TestCase - - -from ognibuild.resolver.apt import get_possible_python3_paths_for_python_object - - -class TestPython3Paths(TestCase): - - def test_paths(self): - self.assertEqual([ - '/usr/lib/python3/dist\\-packages/dulwich/__init__\\.py', - '/usr/lib/python3/dist\\-packages/dulwich\\.py', - '/usr/lib/python3\\.[0-9]+/' - 'lib\\-dynload/dulwich.cpython\\-.*\\.so', - '/usr/lib/python3\\.[0-9]+/dulwich\\.py', - '/usr/lib/python3\\.[0-9]+/dulwich/__init__\\.py'], - get_possible_python3_paths_for_python_object('dulwich')) - self.assertEqual([ - '/usr/lib/python3/dist\\-packages/cleo/foo/__init__\\.py', - '/usr/lib/python3/dist\\-packages/cleo/foo\\.py', - '/usr/lib/python3\\.[0-9]+/' - 'lib\\-dynload/cleo/foo.cpython\\-.*\\.so', - '/usr/lib/python3\\.[0-9]+/cleo/foo\\.py', - '/usr/lib/python3\\.[0-9]+/cleo/foo/__init__\\.py', - '/usr/lib/python3/dist\\-packages/cleo/__init__\\.py', - '/usr/lib/python3/dist\\-packages/cleo\\.py', - '/usr/lib/python3\\.[0-9]+/lib\\-dynload/cleo.cpython\\-.*\\.so', - '/usr/lib/python3\\.[0-9]+/cleo\\.py', - '/usr/lib/python3\\.[0-9]+/cleo/__init__\\.py'], - get_possible_python3_paths_for_python_object('cleo.foo'))