В прошлой статье я рассматривал один из способов публикации python пакета в registry. Сегодня попробуем добавить в этот способ автоматический инкремент версии пакета.
Проект будет все тот же.
Итак, что мы меняем? Во-первых нам нужно изменить файл cli.py, так как мы хотим уметь выводить версию нашего пакета используя ключ «-v» или «—version».
import argparse
from dataclasses import dataclass
from importlib.metadata import version
@dataclass
class CommandLineParser:
file: str
@classmethod
def create_parser(cls):
parser = argparse.ArgumentParser(description="Test CMD")
parser.add_argument(
"-f", "--file", dest="file",
required=True,
help="Path to yaml file"
)
parser.add_argument(
"-v", "--version",
action="version",
version=f"%(prog)s {version('test-package')}",
help="Show package version"
)
return parser
Версию берем из уже установленного пакета через либу importlib.metadata.
Но как быть с версией во время сборки, когда пакет еще не существует?
Для начала __init__.py в корне проекта (директория src) приведем к следующему виду:
import os
__version__ = os.environ.get("PACKAGE_VERSION", "0.1.0")
Здесь мы задаем переменную __version__ в которую будем помещать значение переменной окружения PACKAGE_VERSION (об этом будет чуть дальше) и устанавливать дефолт, если значение переменной не задано.
Редактируем setup.py:
from setuptools import setup, find_packages
from test_package import __version__
setup(
name='test_package',
author="Trusikhin Andrei",
version=__version__,
description="Test packages",
packages=find_packages(where='src/test_package'),
package_dir={'': 'src/test_package'},
install_requires=[
"PyYAML",
"requests",
"semver"
],
entry_points={
'console_scripts': ['cmd_test = test_package.main:main']
},
python_requires='>=3.8'
)
Здесь мы импортируем нашу переменную и присваиваем переменной version значение нашей переменной.
Для автоматического бампа версии пишем небольшой скрипт на bash:
#!/bin/bash
function git_fetch() {
git config --global --add safe.directory '*'
git remote set-url origin "ssh://git@$CI_SERVER_HOST:2222/$CI_PROJECT_PATH.git"
git config --global user.name "${GITLAB_USER_NAME}"
git config --global user.email "${GITLAB_USER_EMAIL}"
git checkout $CI_COMMIT_REF_NAME
git fetch
git reset --hard "origin/$CI_COMMIT_REF_NAME"
}
function git_commit() {
git commit -am "CI set new $1 -> $2"
git push origin $CI_COMMIT_REF_NAME -o ci.skip
}
function bump() {
git_fetch
export CURRENT_VERSION=$(cat version.yml|grep $1|awk '{print $2}'|tr -d '"')
export PACKAGE_VERSION=$(python3 -c 'import os;import semver;print(semver.bump_patch(os.environ["CURRENT_VERSION"]))')
echo -e "\033[0;32mCURRENT_VERSION: $CURRENT_VERSION\nNEW_VERSION: $PACKAGE_VERSION\e[0m"
echo "PACKAGE_VERSION=$PACKAGE_VERSION" > package_version.env
sed -i 's/'$1'.*/'$1': "'$PACKAGE_VERSION'"/' version.yml
git_commit "$(echo $1|tr '_' ' ')" "${PACKAGE_VERSION}"
}
if [[ $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH ]];then
bump "version_prod"
else
bump "version_stage"
fi;
Он будет парсить файл version.yml и автоматически обновлять патч версии для прода и дева. При пуше, используем ci.skip, чтобы избежать дублей. Код один и тот же, изменится только версия в version.yml, нет смысла запускать пайплайн ради этого, так как он уже был запущен при первом нашем коммите.
Так как в gitlab-ci это отдельный stage, то нам нужно передать артефакт между stage в котором мы будем хранить версию пакета. Для этого записываем файл package_version.env.
version_stage: "0.1.0"
version_prod: "1.0.0"
Создаем .gitlab-ci.yml
default:
image: "python3.10"
tags: [ test_tag ]
python:lint:
stage: quality
script:
- find src -name "*.py" |xargs pylint
bump:version:
stage: bump
script:
- pip3 install semver
- ./bump_version.sh
artifacts:
reports:
dotenv: package_version.env
release:
stage: release
needs:
- job: python:lint
- job: bump:version
artifacts: true
script:
- export PACKAGE_VERSION=$PACKAGE_VERSION
- pip3 install -r src/requirements.txt
- python3 src/setup.py sdist bdist_wheel
- TWINE_PASSWORD=${CI_DEPLOY_PASSWORD} TWINE_USERNAME=${CI_DEPLOY_USERNAME} python3 -m twine upload --repository test_package --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/* --verbose
rules:
- { when: manual }
Через artifacts мы передаем нашу переменную в другой stage и экспортируем ее в виде переменной окружения, чтобы python мог ее забрать.
Этот CI весьма условный и по-сути набросок. В конкретных компаниях, проектах и так далее обычно используется удаленный CI, построенный в конечном проекте на include.