Commit d22ffbac authored by euri10's avatar euri10 💬 Committed by Gaëtan Montury
Browse files

feat(uv): add uv support as a new build system

parent fd87fcf1
Loading
Loading
Loading
Loading
+9 −7
Original line number Diff line number Diff line
@@ -65,10 +65,11 @@ and/or `setup.py` and/or `requirements.txt`), but the build system might also be
`$PYTHON_BUILD_SYSTEM` variable:

| Value            | Build System (scope)                                                                                   |
| ---------------- | ---------------------------------------------------------- |
| ---------------- |--------------------------------------------------------------------------------------------------------|
| _none_ (default) or `auto` | The template tries to **auto-detect** the actual build system                                          |
| `setuptools`     | [Setuptools](https://setuptools.pypa.io/) (dependencies, build & packaging)                            |
| `poetry`         | [Poetry](https://python-poetry.org/) (dependencies, build, test & packaging)                           |
| `uv`             | [uv](https://docs.astral.sh/uv/) (dependencies, build, test & packaging)|
| `pipenv`         | [Pipenv](https://pipenv.pypa.io/) (dependencies only)                                                  |
| `reqfile`        | [Requirements Files](https://pip.pypa.io/en/stable/user_guide/#requirements-files) (dependencies only) |

@@ -372,9 +373,10 @@ This job is **disabled by default** and allows to perform a complete release of
3. build the [Python packages](https://packaging.python.org/),
4. publish the built packages to a PyPI compatible repository ([GitLab packages](https://docs.gitlab.com/ee/user/packages/pypi_repository/) by default).

The Python template supports two packaging systems:
The Python template supports three packaging systems:

* [Poetry](https://python-poetry.org/): uses Poetry-specific [version](https://python-poetry.org/docs/cli/#version), [build](https://python-poetry.org/docs/cli/#build) and [publish](https://python-poetry.org/docs/cli/#publish) commands.
* [uv](https://docs.astral.sh/uv/): uses [uv](https://docs.astral.sh/uv/) as version management, [build](https://docs.astral.sh/uv/guides/publish/#building-your-package) as package builder and [publish](https://docs.astral.sh/uv/guides/publish/) to publish.
* [Setuptools](https://setuptools.pypa.io/): uses [bump-my-version](https://github.com/callowayproject/bump-my-version) as version management, [build](https://pypa-build.readthedocs.io/) as package builder and [Twine](https://twine.readthedocs.io/) to publish.

The release job is bound to the `publish` stage, appears only on production and integration branches and uses the following variables:
+1 −1
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@
      "name": "PYTHON_BUILD_SYSTEM",
      "description": "Python build-system to use to install dependencies, build and package the project",
      "type": "enum",
      "values": ["auto", "setuptools", "poetry", "pipenv", "reqfile"],
      "values": ["auto", "setuptools", "poetry", "pipenv", "reqfile", "uv"],
      "default": "auto",
      "advanced": true
    },
+100 −9
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ spec:
      - poetry
      - pipenv
      - reqfile
      - uv
      default: auto
    reqs-file:
      description: |-
@@ -569,7 +570,7 @@ variables:
    case "${PYTHON_BUILD_SYSTEM:-auto}" in
    auto)
      ;;
    poetry*|setuptools*|pipenv*)
    poetry*|setuptools*|pipenv*|uv*)
      log_info "--- Build system explicitly declared: ${PYTHON_BUILD_SYSTEM}"
      return
      ;;
@@ -589,6 +590,17 @@ variables:
      return
    fi
  
    if [[ -f "uv.lock" ]]
    then
      if [[ -f "pyproject.toml" ]]
      then
        log_info "--- Build system auto-detected: uv (uv.lock and pyproject.toml)"
        export PYTHON_BUILD_SYSTEM="uv"
        return
      fi
      log_error "--- Build system auto-detected: uv (uv.lock) but no pyproject.toml found: please read template doc"
    fi

    if [[ -f "pyproject.toml" ]]
    then
      # that might be PEP 517 if a build-backend is specified
@@ -636,6 +648,13 @@ variables:
      pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
    fi
  }
  function maybe_install_uv() {
    if [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]] && ! command -v uv > /dev/null
    then
      # shellcheck disable=SC2086
      pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
    fi
  }

  # install requirements
  function install_requirements() {
@@ -680,6 +699,13 @@ variables:
        log_warn "--- requirements build system defined, but no ${PYTHON_REQS_FILE} file found"
      fi
      ;;
    uv*)
      if  [[ ! -f "uv.lock" ]]; then
        log_warn "Using uv but \\e[33;1muv.lock\\e[0m file not found: you shall commit it with your project files"
      fi
      maybe_install_uv
      uv sync --frozen ${PYTHON_EXTRA_DEPS:+--extras "$PYTHON_EXTRA_DEPS"}
      ;;
    esac
  }

@@ -688,6 +714,10 @@ variables:
    then
      maybe_install_poetry
      poetry run "$@"
    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
    then
      maybe_install_uv
      uv run "$@"
    else
      "$@"
    fi
@@ -698,8 +728,15 @@ variables:
  }

  function _pip() {
    if [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
    then
      maybe_install_uv
      # shellcheck disable=SC2086
      uv pip ${PIP_OPTS} "$@"
    else
      # shellcheck disable=SC2086
      _run pip ${PIP_OPTS} "$@"
    fi
  }

  function py_package() {
@@ -707,6 +744,10 @@ variables:
    then
      maybe_install_poetry
      poetry build
    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
    then
      maybe_install_uv
      uv build
    else
      # shellcheck disable=SC2086
      pip install ${PIP_OPTS} build
@@ -772,6 +813,33 @@ variables:
      py_commit_message=$(python -c "print('$PYTHON_RELEASE_COMMIT_MESSAGE'.format(current_version='$py_cur_version', new_version='$py_next_version'))")
      git commit -m "$py_commit_message"
      git tag "$py_next_version"
    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
    then
      maybe_install_uv
      if [[ -z "$py_next_version" ]]
      then
        # quick version waiting for uv to manage bump
        # related uv MR https://github.com/astral-sh/uv/pull/7248#issuecomment-2395465334 
        mkdir -p -m 777 tbc_tmp
        uvx --from toml-cli toml get --toml-path pyproject.toml project.version > tbc_tmp/version.txt
        py_cur_version=$(cat tbc_tmp/version.txt)

        py_release_part="$PYTHON_RELEASE_NEXT"
        log_info "[bump-my-version] increase \\e[1;94m${py_release_part}\\e[0m (from current \\e[1;94m${py_cur_version}\\e[0m)"
        uvx bump-my-version bump ${TRACE+--verbose} --current-version "$py_cur_version" "$py_release_part" tbc_tmp/version.txt
        py_next_version=$(cat tbc_tmp/version.txt)
        rm -fr tbc_tmp/version.txt
      fi

      log_info "[uv] change version \\e[1;94m${py_cur_version}\\e[0m → \\e[1;94m${py_next_version}\\e[0m"
      uvx --from toml-cli toml set --toml-path pyproject.toml project.version "$py_next_version"

      # Git commit and tag
      git add pyproject.toml
      # emulate bump-my-version to generate commit message
      py_commit_message=$(python -c "print('$PYTHON_RELEASE_COMMIT_MESSAGE'.format(current_version='$py_cur_version', new_version='$py_next_version'))")
      git commit -m "$py_commit_message"
      git tag --force "$py_next_version"
    else
      # Setuptools / bump-my-version
      # shellcheck disable=SC2086
@@ -825,6 +893,18 @@ variables:
      log_info "--- publish packages (poetry) to $PYTHON_REPOSITORY_URL with user $PYTHON_REPOSITORY_USERNAME..."
      poetry config repositories.user_defined "$PYTHON_REPOSITORY_URL"
      poetry publish ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --repository user_defined
    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
    then
      maybe_install_uv
  
      if [[ "$PYTHON_PACKAGE_ENABLED" != "true" ]]
      then
        log_info "--- build packages (uv)..."
        uv build ${TRACE+--verbose}
      fi

      log_info "--- publish packages (uv) to $PYTHON_REPOSITORY_URL with user $PYTHON_REPOSITORY_USERNAME..."
      uv publish ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --publish-url "$PYTHON_REPOSITORY_URL"
    else
      # shellcheck disable=SC2086
      pip install ${PIP_OPTS} build twine
@@ -887,6 +967,7 @@ stages:
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
    POETRY_CACHE_DIR: "$CI_PROJECT_DIR/.cache/poetry"
    PIPENV_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pipenv"
    UV_CACHE_DIR: "$CI_PROJECT_DIR/.cache/uv"
    POETRY_VIRTUALENVS_IN_PROJECT: "false"
  cache:
    key: "$CI_COMMIT_REF_SLUG-python"
@@ -956,7 +1037,7 @@ py-black:
  script:
    - install_requirements
    - _pip install black
    - _run black . --check
    - _run black . --check --extend-exclude '(\/\.cache\/|\/\.venv\/)'
  rules:
    # exclude if $PYTHON_BLACK_ENABLED not set
    - if: '$PYTHON_BLACK_ENABLED != "true"'
@@ -969,7 +1050,7 @@ py-isort:
  script:
    - install_requirements
    - _pip install isort
    - _run isort . --check-only --extend-skip .cache
    - _run isort . --check-only --extend-skip .cache --extend-skip .venv
  rules:
    # exclude if $PYTHON_ISORT_ENABLED not set
    - if: '$PYTHON_ISORT_ENABLED != "true"'
@@ -1018,7 +1099,7 @@ py-mypy:
    - mkdir -p -m 777 reports
    - install_requirements
    - _pip install mypy mypy-to-codeclimate
    - _run mypy ${MYPY_ARGS} ${MYPY_FILES:-$(find -type f -name "*.py" -not -path "./.cache/*")} | tee reports/py-mypy.console.txt || true
    - _run mypy ${MYPY_ARGS} ${MYPY_FILES:-$(find -type f -name "*.py" -not -path "./.cache/*" -not -path "./.venv/*")} | tee reports/py-mypy.console.txt || true
    # mypy-to-codeclimate will fail if any error was found
    - _run mypy-to-codeclimate reports/py-mypy.console.txt reports/py-mypy.codeclimate.json
  artifacts:
@@ -1140,15 +1221,15 @@ py-bandit:
    - |
      if [[ "$SONAR_HOST_URL" ]]
      then
        _run bandit ${TRACE+--verbose} --exit-zero --exclude ./.cache --format csv --output reports/py-bandit.bandit.csv ${BANDIT_ARGS}
        _run bandit ${TRACE+--verbose} --exit-zero --exclude ./.cache --exclude ./.venv --format csv --output reports/py-bandit.bandit.csv ${BANDIT_ARGS}
      fi
    # JSON (for DefectDojo)
    - |
      if [[ "$DEFECTDOJO_BANDIT_REPORTS" ]]
      then
        _run bandit ${TRACE+--verbose} --exit-zero --exclude ./.cache --format json --output reports/py-bandit.bandit.json ${BANDIT_ARGS}
        _run bandit ${TRACE+--verbose} --exit-zero --exclude ./.cache --exclude ./.venv --format json --output reports/py-bandit.bandit.json ${BANDIT_ARGS}
      fi
    - _run bandit ${TRACE+--verbose} --exclude ./.cache ${BANDIT_ARGS}
    - _run bandit ${TRACE+--verbose} --exclude ./.cache --exclude ./.venv ${BANDIT_ARGS}
  artifacts:
    when: always
    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
@@ -1194,6 +1275,11 @@ py-trivy:
          log_info "$PYTHON_BUILD_SYSTEM build system (\\e[32muse lock file\\e[0m)"
          cp poetry.lock Pipfile.lock ./reports 2>/dev/null || true
          ;;
        uv*)
          log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt from uv.lock\\e[0m)"
          maybe_install_uv
          uv export > ./reports/requirements.txt
          ;;
        *)
          log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt\\e[0m)"
          install_requirements
@@ -1244,6 +1330,11 @@ py-sbom:
        poetry*|pipenv*)
          log_info "$PYTHON_BUILD_SYSTEM build system (\\e[32muse lock file\\e[0m)"
          ;;
        uv*)
          log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt from uv.lock\\e[0m)"
          maybe_install_uv
          uv export > ./reports/requirements.txt
          ;;
        *)
          log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt\\e[0m)"
          install_requirements