From 38ecc2ba5f3653a87bf01d66e93762d9bc6158b5 Mon Sep 17 00:00:00 2001
From: Pierre Smeyers <pierre.smeyers@gmail.com>
Date: Sun, 7 May 2023 17:00:13 +0200
Subject: [PATCH] refactor: reuse $PYTHON_BUILD_SYSTEM for packaging and
 release

---
 templates/gitlab-ci-python.yml | 187 +++++++++------------------------
 1 file changed, 52 insertions(+), 135 deletions(-)

diff --git a/templates/gitlab-ci-python.yml b/templates/gitlab-ci-python.yml
index 25803e4..12acc84 100644
--- a/templates/gitlab-ci-python.yml
+++ b/templates/gitlab-ci-python.yml
@@ -251,15 +251,7 @@ variables:
     case "${PYTHON_BUILD_SYSTEM:-auto}" in
     auto)
       ;;
-    poetry*)
-      log_info "--- Build system explictly declared: ${PYTHON_BUILD_SYSTEM}"
-      return
-      ;;
-    setuptools*)
-      log_info "--- Build system explictly declared: ${PYTHON_BUILD_SYSTEM}"
-      return
-      ;;
-    pipenv*)
+    poetry*|setuptools*|pipenv*)
       log_info "--- Build system explictly declared: ${PYTHON_BUILD_SYSTEM}"
       return
       ;;
@@ -320,6 +312,14 @@ variables:
     fi
   }
 
+  function maybe_install_poetry() {
+    if [[ "$PYTHON_BUILD_SYSTEM" == poetry* ]] && ! command -v poetry > /dev/null
+    then
+      # shellcheck disable=SC2086
+      pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
+    fi
+  }
+
   # install requirements
   function install_requirements() {
     case "$PYTHON_BUILD_SYSTEM" in
@@ -327,8 +327,7 @@ variables:
       if  [[ ! -f "poetry.lock" ]]; then
         log_warn "Using Poetry but \\e[33;1mpoetry.lock\\e[0m file not found: you shall commit it with your project files"
       fi
-      # shellcheck disable=SC2086
-      pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
+      maybe_install_poetry
       poetry install ${PYTHON_EXTRA_DEPS:+--extras "$PYTHON_EXTRA_DEPS"}
       ;;
     setuptools*)
@@ -368,10 +367,9 @@ variables:
   }
 
   function _run() {
-    if [[ "${PYTHON_BUILD_SYSTEM}" =~ poetry.* ]]
+    if [[ "$PYTHON_BUILD_SYSTEM" == poetry* ]]
     then
-      # shellcheck disable=SC2086
-      if ! command -v poetry > /dev/null; then pip install ${PIP_OPTS} poetry; fi
+      maybe_install_poetry
       poetry run "$@"
     else
       "$@"
@@ -387,19 +385,16 @@ variables:
     _run pip ${PIP_OPTS} "$@"
   }
 
-  function _package() {
-    case "$PYTHON_BUILD_SYSTEM" in
-    poetry)
-      # shellcheck disable=SC2086
-      if ! command -v poetry > /dev/null; then pip install ${PIP_OPTS} poetry; fi
+  function py_package() {
+    if [[ "$PYTHON_BUILD_SYSTEM" == poetry* ]]
+    then
+      maybe_install_poetry
       poetry build
-      ;;
-    *)
+    else
       # shellcheck disable=SC2086
       pip install ${PIP_OPTS} build
       python -m build
-      ;;
-    esac
+    fi
   }
 
   function configure_scm_auth() {
@@ -425,44 +420,7 @@ variables:
     fi
   }
 
-  function _release() {
-    # 0: guess packaging system
-    if [[ -f "pyproject.toml" ]]
-    then
-      # that might be PEP 517 if a build-backend is specified
-      # otherwise it might be only used as configuration file for development tools...
-      build_backend=$(sed -rn 's/^build-backend *= *"([^"]*)".*/\1/p' pyproject.toml)
-      if [[ "$build_backend" ]]
-      then
-        case "$build_backend" in
-        poetry.core.masonry.api)
-          log_info "--- Packaging system auto-detected: Poetry"
-          pkg_system="poetry"
-          ;;
-        setuptools.build_meta)
-          log_info "--- Packaging system auto-detected: Setuptools (PEP 517)"
-          pkg_system="setuptools"
-          ;;
-        *)
-          log_error "--- Unsupported PEP 517 backend \\e[33;1m${build_backend}\\e[0m: abort"
-          exit 1
-          ;;
-        esac
-      fi
-    fi
-
-    if [[ -z "$pkg_system" ]]
-    then
-      if [[ -f "setup.py" ]]
-      then
-        log_info "--- Packaging system auto-detected: Setuptools (legacy)"
-        pkg_system="setuptools"
-      else
-        log_error "--- Couldn't find any supported packaging system: abort"
-        exit 1
-      fi
-    fi
-
+  function py_release() {
     # 1: retrieve next release info from semantic-release
     if [ "$SEMREL_INFO_ON" ] && [ "$PYTHON_SEMREL_RELEASE_DISABLED" != "true" ]
     then
@@ -479,19 +437,19 @@ variables:
     fi
 
     # 2: bumpversion (+ Git commit & tag)
-    if [[ "$pkg_system" == "poetry" ]]
+    if [[ "$PYTHON_BUILD_SYSTEM" == poetry* ]]
     then
-      # shellcheck disable=SC2086
-      if ! command -v poetry > /dev/null; then pip install ${PIP_OPTS} poetry; fi
+      maybe_install_poetry
       if [[ -z "$py_next_version" ]]
       then
         py_cur_version=$(poetry version --short)
         py_next_version="$PYTHON_RELEASE_NEXT"
       fi
-      log_info "[Poetry] change version \\e[1;94m${py_cur_version}\\e[0m → \\e[1;94m${py_next_version}\\e[0m"
+      log_info "[poetry] change version \\e[1;94m${py_cur_version}\\e[0m → \\e[1;94m${py_next_version}\\e[0m"
       poetry version ${TRACE+--verbose} "$py_next_version"
       # eval exact next version
       py_next_version=$(poetry version --short)
+      # Git commit and tag
       git add pyproject.toml
       git commit -m "chore(python-release): $py_cur_version → $py_next_version"
       git tag "$py_next_version"
@@ -503,7 +461,7 @@ variables:
       if [[ "$py_next_version" ]]
       then
         # explicit release version (semantic-release)
-        log_info "[Setuptools] bumpversion \\e[1;94m${py_cur_version}\\e[0m → \\e[1;94m${py_next_version}\\e[0m"
+        log_info "[bumpversion] change version \\e[1;94m${py_cur_version}\\e[0m → \\e[1;94m${py_next_version}\\e[0m"
         # create cfg in case it doesn't exist - will be updated by bumpversion
         touch .bumpversion.cfg
         bumpversion ${TRACE+--verbose} --current-version "$py_cur_version" --commit --message "$py_commit_message" --tag --tag-name "{new_version}" "$py_release_part"
@@ -518,7 +476,7 @@ variables:
         # retrieve current version from setup.py
         py_cur_version=$(python setup.py --version)
         py_release_part="$PYTHON_RELEASE_NEXT"
-        log_info "[Setuptools] bumpversion ($py_release_part) from \\e[1;94m${py_cur_version}\\e[0m"
+        log_info "[bumpversion] increase \\e[1;94m${py_release_part}\\e[0m (from current \\e[1;94m${py_cur_version}\\e[0m)"
         bumpversion ${TRACE+--verbose} --current-version "$py_cur_version" --commit --message "$py_commit_message" --tag --tag-name "{new_version}" "$py_release_part" setup.py
       else
         log_error "--- setup.py or .bumpversion.cfg file required to retrieve current version: cannot perform release"
@@ -532,67 +490,26 @@ variables:
     git push "$git_auth_url" --tags
   }
 
-  function _publish() {
-    # 1: guess packaging system
-    if [[ -f "pyproject.toml" ]]
-    then
-      # that might be PEP 517 if a build-backend is specified
-      # otherwise it might be only used as configuration file for development tools...
-      build_backend=$(sed -rn 's/^build-backend *= *"([^"]*)".*/\1/p' pyproject.toml)
-      if [[ "$build_backend" ]]
-      then
-        case "$build_backend" in
-        poetry.core.masonry.api)
-          log_info "--- Packaging system auto-detected: Poetry"
-          pkg_system="poetry"
-          ;;
-        setuptools.build_meta)
-          log_info "--- Packaging system auto-detected: Setuptools (PEP 517)"
-          pkg_system="setuptools"
-          ;;
-        *)
-          log_error "--- Unsupported PEP 517 backend \\e[33;1m${build_backend}\\e[0m: abort"
-          exit 1
-          ;;
-        esac
-      fi
-    fi
-
-    if [[ -z "$pkg_system" ]]
+  function py_publish() {
+    if [[ "$PYTHON_BUILD_SYSTEM" == poetry* ]]
     then
-      if [[ -f "setup.py" ]]
-      then
-        log_info "--- Packaging system auto-detected: Setuptools (legacy)"
-        pkg_system="setuptools"
-      else
-        log_error "--- Couldn't find any supported packaging system: abort"
-        exit 1
-      fi
-    fi
-
-    # 2: build (new version) distribution
-    log_info "--- build distribution packages..."
-    if [[ "$pkg_system" == "poetry" ]]
-    then
-      # shellcheck disable=SC2086
-      if ! command -v poetry > /dev/null; then pip install ${PIP_OPTS} poetry; fi
+      maybe_install_poetry
+ 
+      log_info "--- build packages (poetry)..."
       poetry build ${TRACE+--verbose}
+
+      log_info "--- publish packages (poetry)..."
+      poetry config repositories.user_defined "$PYTHON_REPOSITORY_URL"
+      poetry publish ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --repository user_defined
     else
       # shellcheck disable=SC2086
-      pip install ${PIP_OPTS} build
+      pip install ${PIP_OPTS} build twine
+ 
+      log_info "--- build packages (build)..."
       rm -rf dist
       python -m build
-    fi
 
-    # 3: publish built packages
-    log_info "--- publish distribution packages..."
-    if [[ "$pkg_system" == "poetry" ]]
-    then
-      poetry config repositories.user_defined  "$PYTHON_REPOSITORY_URL"
-      poetry publish ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --repository user_defined
-    else
-      # shellcheck disable=SC2086
-      pip install ${PIP_OPTS} twine
+      log_info "--- publish packages (twine)..."
       twine upload ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --repository-url "$PYTHON_REPOSITORY_URL" dist/*
     fi
   }
@@ -601,6 +518,14 @@ variables:
 
   # ENDSCRIPT
 
+###############################################################################################
+#                                      stages definition                                      #
+###############################################################################################
+stages:
+  - build
+  - test
+  - publish
+
 ###############################################################################################
 #                                      Generic python job                                     #
 ###############################################################################################
@@ -623,14 +548,6 @@ variables:
     - cd ${PYTHON_PROJECT_DIR}
     - guess_build_system
 
-###############################################################################################
-#                                      stages definition                                      #
-###############################################################################################
-stages:
-  - build
-  - test
-  - publish
-
 ###############################################################################################
 #                                      build stage                                             #
 ###############################################################################################
@@ -639,7 +556,7 @@ py-package:
   extends: .python-base
   stage: build
   script:
-    - _package
+    - py_package
   artifacts:
     paths:
       - $PYTHON_PROJECT_DIR/dist/*
@@ -840,7 +757,7 @@ py-trivy:
     - apt-get update
     - apt-get install trivy
     - |
-      if [[ "$PYTHON_BUILD_SYSTEM" =~ poetry.* ]]
+      if [[ "$PYTHON_BUILD_SYSTEM" == poetry* ]]
       then
         # When using Poetry, `pip freeze` outputs a requirements.txt with @file URLs for each wheel
         # These @file URLs in requirements.txt are not supported by Trivy
@@ -891,7 +808,7 @@ py-sbom:
     - install_requirements
     - |
       case "$PYTHON_BUILD_SYSTEM" in
-        setuptools* | reqfile)
+        setuptools*|reqfile)
           _pip freeze > "${PYTHON_REQS_FILE}"
           ;;
       esac
@@ -935,7 +852,7 @@ py-release:
     - git config --global user.name "$GITLAB_USER_LOGIN"
     - git checkout -B $CI_COMMIT_REF_NAME
     - configure_scm_auth
-    - _release
+    - py_release
   artifacts:
     paths:
       - $PYTHON_PROJECT_DIR/dist/*
@@ -948,12 +865,12 @@ py-release:
       when: manual
       allow_failure: true
 
-# (manual from master branch): triggers a release (tag creation)
+# (auto from release tag): publishes the Python package(s) to a PyPi registry
 py-publish:
   extends: .python-base
   stage: publish
   script:
-    - _publish $CI_COMMIT_TAG
+    - py_publish $CI_COMMIT_TAG
   artifacts:
     paths:
       - $PYTHON_PROJECT_DIR/dist/*
-- 
GitLab