From a82cfd286c4f820a3d8a4bf8e9f178de0f0a231c Mon Sep 17 00:00:00 2001 From: Julian Rother <julian@jrother.eu> Date: Wed, 17 Nov 2021 14:57:03 +0100 Subject: [PATCH] Improved debian packaging and CI testing Minimal supported python version is changed to 3.7 to support Debian Buster. --- .gitlab-ci.yml | 183 +++++++++++++++++++++++++++++-------- debian/changelog | 5 - debian/control | 7 +- debian/create_changelog.py | 106 +++++++++++++++++++++ setup.py | 2 +- 5 files changed, 254 insertions(+), 49 deletions(-) delete mode 100644 debian/changelog create mode 100755 debian/create_changelog.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b91ce53..68af49f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,75 @@ -linter: +image: registry.git.cccv.de/uffd/docker-images/buster + +variables: + DEBIAN_FRONTEND: noninteractive + GIT_SUBMODULE_STRATEGY: normal + PYTHONPATH: deps/ldapalchemy + APT_API_URL: https://packages.cccv.de + APT_REPO: uffd + PYLINT_PIN: pylint~=2.10.0 + +before_script: + - python3 -V + - lsb_release -a + - uname -a + - python3 -m pylint --version + - python3 -m coverage --version + - echo "${CI_COMMIT_TAG}" | grep -qE "v[0-9]+[.][0-9]+[.][0-9]+.*" && export PACKAGE_VERSION="${CI_COMMIT_TAG#v}" || export PACKAGE_VERSION="${CI_COMMIT_SHA}" + +.build: + stage: build + +build:pip: + extends: .build + script: + - PACKAGE_VERSION="${PACKAGE_VERSION}" python3 -m build + artifacts: + paths: + - dist/* + +build:apt: + extends: .build + script: + - ./debian/create_changelog.py python3-ldapserver > debian/changelog + - dpkg-buildpackage -us -uc + - mv ../*.deb ./ + - dpkg-deb -I *.deb + - dpkg-deb -c *.deb + artifacts: + paths: + - ./*.deb + +build:docs: + extends: .build + script: + - pip3 install Sphinx sphinx-rtd-theme + - cd docs && make html + artifacts: + paths: + - docs/_build/html/index.html + - docs/_build/html + expose_as: 'Documentation' + +linter:buster: + image: registry.git.cccv.de/uffd/docker-images/buster + stage: test + script: + - pip3 install $PYLINT_PIN pylint-gitlab + - python3 -m pylint --exit-zero --rcfile .pylintrc --output-format=pylint_gitlab.GitlabCodeClimateReporter ldapserver > codeclimate.json + - python3 -m pylint --exit-zero --rcfile .pylintrc --output-format=pylint_gitlab.GitlabPagesHtmlReporter ldapserver > pylint.html + - python3 -m pylint --rcfile .pylintrc --output-format=text ldapserver + artifacts: + when: always + paths: + - pylint.html + reports: + codequality: codeclimate.json + +linter:bullseye: + image: registry.git.cccv.de/uffd/docker-images/bullseye stage: test - image: python:3.7 script: - - pip install pylint-gitlab + - pip3 install $PYLINT_PIN pylint-gitlab - python3 -m pylint --exit-zero --rcfile .pylintrc --output-format=pylint_gitlab.GitlabCodeClimateReporter ldapserver > codeclimate.json - python3 -m pylint --exit-zero --rcfile .pylintrc --output-format=pylint_gitlab.GitlabPagesHtmlReporter ldapserver > pylint.html - python3 -m pylint --rcfile .pylintrc --output-format=text ldapserver @@ -10,19 +77,17 @@ linter: when: always paths: - pylint.html - expose_as: 'Linter Report' reports: codequality: codeclimate.json -test: +unittests:buster: + image: registry.git.cccv.de/uffd/docker-images/buster stage: test - image: python:3.7 script: - - pip install pytest coverage - - coverage run --include 'ldapserver/*.py' -m pytest --junitxml=report.xml - - coverage report -m - - coverage html - - coverage xml + - python3-coverage run --include 'ldapserver/*.py' -m pytest --junitxml=report.xml || true + - python3-coverage report -m + - python3-coverage html + - python3-coverage xml artifacts: when: always paths: @@ -34,40 +99,80 @@ test: junit: report.xml coverage: '/^TOTAL.*\s+(\d+\%)$/' -build-docs: - stage: deploy - image: python:3.7 +unittests:bullseye: + image: registry.git.cccv.de/uffd/docker-images/bullseye + stage: test script: - - pip install Sphinx sphinx-rtd-theme - - cd docs && make html + - python3-coverage run --include 'ldapserver/*.py' -m pytest --junitxml=report.xml || true + #- python3-coverage report -m + - python3-coverage html + #- python3-coverage xml artifacts: + when: always paths: - - docs/_build/html/index.html - - docs/_build/html - expose_as: 'Documentation' + - htmlcov/index.html + - htmlcov + expose_as: 'Coverage Report' + reports: + #cobertura: coverage.xml + junit: report.xml + #coverage: '/^TOTAL.*\s+(\d+\%)$/' -publish-pip: - stage: deploy - image: python:3.7 +test:package:pip:buster: + image: registry.git.cccv.de/uffd/docker-images/buster + stage: test script: - - pip install build twine - - PACKAGE_VERSION="${CI_COMMIT_TAG#v}" python -m build - - TWINE_USERNAME="${GITLABPKGS_USERNAME}" TWINE_PASSWORD="${GITLABPKGS_PASSWORD}" python -m twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/* - - TWINE_USERNAME="${PYPI_USERNAME}" TWINE_PASSWORD="${PYPI_PASSWORD}" python -m twine upload dist/* + - pip3 install dist/*.tar.gz + dependencies: + - build:pip + +test:package:pip:bullseye: + image: registry.git.cccv.de/uffd/docker-images/bullseye + stage: test + script: + - pip3 install dist/*.tar.gz + dependencies: + - build:pip + +test:package:apt:buster: + image: registry.git.cccv.de/uffd/docker-images/buster + stage: test + script: + - apt -y install ./*.deb + dependencies: + - build:apt + +test:package:apt:bullseye: + image: registry.git.cccv.de/uffd/docker-images/bullseye + stage: test + script: + - apt -y install ./*.deb + dependencies: + - build:apt + +.publish: + stage: deploy rules: - if: '$CI_COMMIT_TAG =~ /v[0-9]+[.][0-9]+[.][0-9]+.*/' -publish-apt: - image: debian:buster - stage: deploy +publish:pip: + extends: .publish script: - - apt update - - apt-get install -y python3-all debhelper python3-pip git-buildpackage - - gbp dch --ignore-branch --debian-tag=v%\(version\)s - - dpkg-buildpackage -us -uc - - mkdir build - - mv ../*.deb build/ - artifacts: - paths: - - build/* - - debian/changelog + - TWINE_USERNAME="${GITLABPKGS_USERNAME}" TWINE_PASSWORD="${GITLABPKGS_PASSWORD}" python3 -m twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/* + - TWINE_USERNAME="${PYPI_USERNAME}" TWINE_PASSWORD="${PYPI_PASSWORD}" python3 -m twine upload dist/* + dependencies: + - build:pip + +publish:apt: + extends: .publish + script: + - export DEBPATH="$(echo *.deb)" + - echo Upload deb file, add it to repo and clean up upload + - curl --user "${APTLY_API_USER}:${APTLY_API_PW}" -X POST -F file=@"$DEBPATH" "${APT_API_URL}/api/files/${APT_REPO}-ci-upload-${CI_JOB_ID}" + - curl --user "${APTLY_API_USER}:${APTLY_API_PW}" -X POST "${APT_API_URL}/api/repos/${APT_REPO}/file/${APT_REPO}-ci-upload-${CI_JOB_ID}" + - curl --user "${APTLY_API_USER}:${APTLY_API_PW}" -X DELETE "${APT_API_URL}/api/files/${APT_REPO}-ci-upload-${CI_JOB_ID}" + - echo Update published repo for all distros + - 'curl --user "${APTLY_API_USER}:${APTLY_API_PW}" -X PUT -H "Content-Type: application/json" --data "{ }" "${APT_API_URL}/api/publish/uffd/buster"' + - 'curl --user "${APTLY_API_USER}:${APTLY_API_PW}" -X PUT -H "Content-Type: application/json" --data "{ }" "${APT_API_URL}/api/publish/uffd/bullseye"' + dependencies: + - build:apt diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index f8ae9c6..0000000 --- a/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -python3-ldapserver (0.0.1.dev0) unstable; urgency=medium - - * Initial release. - - -- Andreas Valder <nd@cccv.de> Fri, 23 Jul 2021 18:02:31 +0200 diff --git a/debian/control b/debian/control index aaab10c..cd146b7 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: python3-ldapserver Section: python Priority: optional -Maintainer: Andreas Valder <nd@cccv.de> +Maintainer: CCCV <it@cccv.de> Build-Depends: debhelper-compat (= 12), dh-python, @@ -12,8 +12,7 @@ Homepage: https://git.cccv.de/uffd/python-ldapserver Vcs-Git: https://git.cccv.de/uffd/python-ldapserver.git Package: python3-ldapserver -Architecture: any +Architecture: all Depends: ${misc:Depends}, - ${python3:Depends}, -Description: LDAP server request handler +Description: Library to implement special-purpose LDAP servers diff --git a/debian/create_changelog.py b/debian/create_changelog.py new file mode 100755 index 0000000..b62f211 --- /dev/null +++ b/debian/create_changelog.py @@ -0,0 +1,106 @@ +#!/usr/bin/python3 +import sys +import re +import textwrap +import datetime +import email.utils + +import git + +package_name = 'UNKNOWN' + +alias_names = { + 'julian': 'Julian Rother', + 'Julian': 'Julian Rother', +} + +ignore_commit_regexes = [ + '^fixup!', +] + +def print_release(tag=None, commits=tuple(), last_tag=None): + release_version = '0.0.0' + release_author = git.objects.util.Actor('None', 'undefined@example.com') + release_date = 0 + release_status = 'UNRELEASED' + message = '' + + if tag: + release_status = 'unstable' + release_version = tag.name[1:] # strip leading "v" + if isinstance(tag.object, git.TagObject): + release_author = tag.object.tagger + release_date = tag.object.tagged_date + message = tag.object.message.split('-----BEGIN PGP SIGNATURE-----')[0].strip() + else: + release_author = tag.object.committer + release_date = tag.object.committed_date + elif commits: + release_author = commits[0].committer + release_date = commits[0].committed_date + date = datetime.datetime.fromtimestamp(release_date).strftime('%Y%m%dT%H%M%S') + last_version = '0.0.0' + if last_tag: + last_version = last_tag.name[1:] # strip leading "v" + release_version = f'{last_version}+git{date}-{commits[0].hexsha[:8]}' + + print(f'{package_name} ({release_version}) {release_status}; urgency=medium') + print() + if message: + print(textwrap.indent(message, ' ')) + print() + commit_authors = [] # list of (key, author), sorted by first commit date + commit_author_emails = {} # author email -> key + commit_author_names = {} # author name -> key + commit_author_commits = {} # key -> list of commits + for commit in commits: + if any(filter(lambda pattern: re.match(pattern, commit.summary), ignore_commit_regexes)): + continue + if len(commit.parents) > 1: + continue # Ignore merge commits + author_name = alias_names.get(commit.author.name, commit.author.name) + key = commit_author_emails.get(commit.author.email) + if key is None: + key = commit_author_names.get(author_name) + if key is None: + key = commit.author.email + commit_authors.append((key, author_name)) + commit_author_emails[commit.author.email] = key + commit_author_names[author_name] = key + commit_author_commits[key] = commit_author_commits.get(key, []) + [commit] + commit_authors.sort(key=lambda args: len(commit_author_commits[args[0]])) + for key, author_name in commit_authors: + print(f' [ {author_name} ]') + for commit in commit_author_commits[key]: + lines = '\n'.join(textwrap.wrap(commit.summary, 90)) + lines = ' * ' + textwrap.indent(lines, ' ').strip() + print(lines) + print() + print(f' -- {alias_names.get(release_author.name, release_author.name)} <{release_author.email}> {email.utils.formatdate(release_date)}') + +if __name__ == '__main__': + repo = git.Repo('.') + package_name = sys.argv[1] + + version_commits = {} + for tag in repo.tags: + if not re.fullmatch('v[0-9]+[.][0-9]+[.][0-9]+.*', tag.name): + continue + if isinstance(tag.object, git.TagObject): + commit_hexsha = tag.object.object.hexsha + else: + commit_hexsha = tag.object.hexsha + version_commits[commit_hexsha] = tag + + tag = None + commits = [] + for commit in repo.iter_commits('HEAD'): + if commit.hexsha in version_commits: + prev_tag = version_commits[commit.hexsha] + if commits: + print_release(tag, commits, last_tag=prev_tag) + print() + tag = prev_tag + commits = [] + commits.append(commit) + print_release(tag, commits) diff --git a/setup.py b/setup.py index 46ebb45..63910cf 100644 --- a/setup.py +++ b/setup.py @@ -22,5 +22,5 @@ setuptools.setup( ], package_dir={'': '.'}, packages=setuptools.find_packages(where='.'), - python_requires='>=3.9', + python_requires='>=3.7', ) -- GitLab