diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 811d0a9b..5a595ecb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,7 @@ [bumpversion] current_version = 0.20.0 commit = True -tag = True +tag = False message = chore: Bump version from {current_version} to {new_version} [bumpversion:file:cl_sii/__init__.py] diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index afa332e3..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,163 +0,0 @@ -# CircleCI 2.0 configuration file for this project. -# -# Notes: -# - Do not use CircleCI's brand of Docker images unless it is for a secondary environment. -# - We chose not to use dependencies caching because it is complicated to do it right and it is -# not worth the effort for a project so small. -# -# For more information check out: -# - https://circleci.com/docs/2.0/language-python/ for more details -# - https://circleci.com/docs/2.0/configuration-reference/ -# -version: "2.1" - -# -----BEGIN Environment Variables----- - -# Environment variables required for deployment: -# -# - PYPI_PASSWORD := PyPI password or API token. -# - PYPI_USERNAME := PyPI username. For API tokens, use "__token__". -# - TWINE_NON_INTERACTIVE := Do not interactively prompt for credentials if they are missing. -# - TWINE_REPOSITORY_URL := The repository (package index) URL to register the package to. - -# x-deploy-environment := Deployment environment variables -x-deploy-environment: &x-deploy-environment - TWINE_NON_INTERACTIVE: "true" - TWINE_REPOSITORY_URL: https://upload.pypi.org/legacy/ - -# -----END Environment Variables----- - -jobs: - test: - parameters: - python_version: - type: string - - docker: - - image: docker.io/library/python:<< parameters.python_version >> - - working_directory: ~/repo - - steps: - - checkout - - - run: - name: install dependencies - command: | - python3 -m venv venv - . venv/bin/activate - pip install --upgrade pip - pip install --upgrade setuptools wheel - make install-dev - - - run: - name: run tests - command: | - . venv/bin/activate - make lint - - # Set Tox environment to the installed Python version. - TOXENV=$( - python -c 'import sys; v = sys.version_info; print("py{}{}".format(v.major, v.minor))' - ) - - tox -e "$TOXENV" - - codecov - make test-coverage-report-console - make test-coverage-report-html - - - when: - condition: - # FIXME: There are issues related to testing with multiple Python versions. - matches: { pattern: "^3\\.7\\.\\d+$", value: << parameters.python_version >> } - steps: - - run: - name: Check that compiled Python dependency manifests are up-to-date with their sources - command: | - . venv/bin/activate - make python-deps-sync-check - - - store_artifacts: - path: test-reports - destination: test-reports - - dist: - docker: - - image: docker.io/library/python:3.10.9 - - working_directory: ~/repo - - steps: - - checkout - - - run: - name: install dependencies - command: | - python3 -m venv venv - . venv/bin/activate - pip install --upgrade pip - pip install --upgrade setuptools wheel - make install-dev - - - run: - name: make dist - command: | - . venv/bin/activate - make dist - - - store_artifacts: - path: dist - destination: dist - - - persist_to_workspace: - root: ~/repo - paths: - - dist - - venv - - deploy: - docker: - - image: docker.io/library/python:3.10.9 - environment: - <<: *x-deploy-environment - - working_directory: ~/repo - - steps: - - checkout - - - attach_workspace: - at: ~/repo - - - deploy: - name: Upload Artifacts to Repository - command: | - . venv/bin/activate - - make upload-release \ - TWINE_USERNAME="${PYPI_USERNAME:?}" \ - TWINE_PASSWORD="${PYPI_PASSWORD:?}" - -workflows: - version: 2 - ci: - jobs: - - test: - matrix: - parameters: - python_version: - - "3.7.9" - - "3.8.13" - - "3.9.1" - - "3.10.9" - - dist: - requires: - - test - - deploy: - requires: - - dist - filters: - branches: - only: - - master diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9ed34c67..96fccf14 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,3 +16,13 @@ updates: open-pull-requests-limit: 10 labels: - dependencies + + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + commit-message: + prefix: "chore:" + labels: + - dependencies + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml new file mode 100644 index 00000000..a787ecf5 --- /dev/null +++ b/.github/workflows/ci-cd.yaml @@ -0,0 +1,76 @@ +# GitHub Actions Workflow for Continuous Integration and Continuous Delivery +# +# Documentation: +# - https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions +# - https://docs.github.com/en/actions/learn-github-actions/contexts +# - https://docs.github.com/en/actions/learn-github-actions/expressions +# - https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python +# - https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts +# - https://docs.github.com/en/actions/using-workflows/reusing-workflows + +name: CI/CD + +on: + push: + +permissions: + contents: write + +env: + PRODUCTION_VCS_REF: refs/heads/master + STAGING_VCS_REF: refs/heads/develop + +jobs: + # -----BEGIN Workflow Configuration Job----- + workflow_config: + name: Workflow Configuration + runs-on: ubuntu-22.04 + + outputs: + PRODUCTION_VCS_REF: ${{ env.PRODUCTION_VCS_REF }} + STAGING_VCS_REF: ${{ env.STAGING_VCS_REF }} + + steps: + - run: "true" + + # -----END Workflow Configuration Job----- + + # -----BEGIN CI Job----- + ci: + name: CI + needs: + - workflow_config + + uses: ./.github/workflows/ci.yaml + + # -----END CI Job----- + + # -----BEGIN Release Job----- + release: + name: Release + if: ${{ github.ref == needs.workflow_config.outputs.PRODUCTION_VCS_REF }} + needs: + - ci + - workflow_config + + uses: ./.github/workflows/release.yaml + with: + create_git_tag_and_github_release: ${{ github.ref == needs.workflow_config.outputs.PRODUCTION_VCS_REF }} + + # -----END Release Job----- + + # -----BEGIN Deploy Job----- + deploy: + name: Deploy + if: ${{ github.ref == needs.workflow_config.outputs.PRODUCTION_VCS_REF }} + needs: + - release + - workflow_config + + uses: ./.github/workflows/deploy.yaml + with: + deploy_env: prod + artifacts_path: ${{ needs.release.outputs.artifacts_path }} + secrets: inherit + + # -----END Deploy Job----- diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..cc1154d5 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,149 @@ +# GitHub Actions Workflow for Continuous Integration + +name: CI + +on: + workflow_call: + +permissions: + contents: read + +env: + PYTHON_VIRTUALENV_ACTIVATE: venv/bin/activate + +jobs: + pre-build: + name: Pre-Build + runs-on: ubuntu-22.04 + + steps: + - run: "true" + + build: + name: Build + needs: + - pre-build + runs-on: ubuntu-22.04 + + strategy: + matrix: + python_version: + - "3.7.15" + - "3.8.13" + - "3.9.16" + - "3.10.9" + + steps: + - name: Check Out VCS Repository + uses: actions/checkout@v3.3.0 + + - name: Set Up Python ${{ matrix.python_version }} + uses: actions/setup-python@v4.5.0 + with: + python-version: "${{ matrix.python_version }}" + + - name: Create Python Virtual Environment + run: make python-virtualenv PYTHON_VIRTUALENV_DIR="venv" + + - name: Restoring/Saving Cache + uses: actions/cache@v3.2.5 + with: + path: "venv" + key: py-v1-deps-${{ runner.os }}-${{ matrix.python_version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} + + - name: Install Dependencies + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make install-deps-dev + + - name: Install Library + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make install-dev + + test: + name: Test + needs: + - build + runs-on: ubuntu-22.04 + + strategy: + matrix: + python_version: + - "3.7.15" + - "3.8.13" + - "3.9.16" + - "3.10.9" + + steps: + - name: Check Out VCS Repository + uses: actions/checkout@v3.3.0 + + - name: Set Up Python ${{ matrix.python_version }} + uses: actions/setup-python@v4.5.0 + with: + python-version: "${{ matrix.python_version }}" + + - name: Restoring/Saving Cache + uses: actions/cache@v3.2.5 + with: + path: "venv" + key: py-v1-deps-${{ runner.os }}-${{ matrix.python_version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} + + - name: Set Tox Environment + id: set_tox_environment + run: | + # Set Tox environment to the installed Python version. + tox_env=$( + python -c 'import sys; v = sys.version_info; print("py{}{}".format(v.major, v.minor))' + ) + + echo "tox_env=${tox_env:?}" >> "$GITHUB_OUTPUT" + + - name: Test + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make test + env: + TOXENV: ${{ steps.set_tox_environment.outputs.tox_env }} + + - name: Lint + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make lint + + - name: Test Coverage + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make test-coverage + + - name: Test Coverage Report + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make test-coverage-report + + codecov + + - name: Check that compiled Python dependency manifests are up-to-date with their sources + # FIXME: There are issues related to testing with multiple Python versions. + if: ${{ startsWith(matrix.python_version, '3.7.') }} + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make python-deps-sync-check + + - name: Store Artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v3.1.2 + with: + name: test_reports_${{ matrix.python_version }} + path: test-reports/ + if-no-files-found: warn + + post-test: + name: Post-Test + needs: + - test + runs-on: ubuntu-22.04 + + steps: + - run: "true" diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 00000000..37187b30 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,71 @@ +# GitHub Actions Workflow for Deployment + +name: Deploy + +on: + workflow_call: + inputs: + deploy_env: + type: string + required: true + description: Deployment Environment + artifacts_path: + type: string + required: true + +permissions: + contents: read + +# -----BEGIN Environment Variables----- + +# Environment variables required for deployment: +# +# - PYPI_PASSWORD := PyPI password or API token. +# - PYPI_USERNAME := PyPI username. For API tokens, use "__token__". +# - TWINE_NON_INTERACTIVE := Do not interactively prompt for credentials if they are missing. +# - TWINE_REPOSITORY_URL := The repository (package index) URL to register the package to. + +env: + PYTHON_VIRTUALENV_ACTIVATE: venv/bin/activate + +# -----END Environment Variables----- + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-22.04 + environment: ${{ inputs.deploy_env }} + + steps: + - name: Check Out VCS Repository + uses: actions/checkout@v3.3.0 + + - name: Set Up Python + id: set_up_python + uses: actions/setup-python@v4.5.0 + with: + python-version: "3.10.9" + + - name: Restoring/Saving Cache + uses: actions/cache@v3.2.5 + with: + path: "venv" + key: py-v1-deps-${{ runner.os }}-${{ steps.set_up_python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} + + - name: Restore Artifacts (Release) + uses: actions/download-artifact@v3.0.2 + with: + name: release + path: ${{ inputs.artifacts_path }}/ + + - name: Deploy + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make deploy \ + TWINE_USERNAME="${PYPI_USERNAME:?}" \ + TWINE_PASSWORD="${PYPI_PASSWORD:?}" + env: + PYPI_USERNAME: ${{ vars.PYPI_USERNAME }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + TWINE_NON_INTERACTIVE: "true" + TWINE_REPOSITORY_URL: https://upload.pypi.org/legacy/ diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..c55e9366 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,101 @@ +# GitHub Actions Workflow for Release + +name: Release + +on: + workflow_call: + inputs: + create_git_tag_and_github_release: + type: boolean + required: true + description: Create Git tag and GitHub release. + outputs: + artifacts_path: + value: ${{ jobs.release.outputs.artifacts_path }} + +permissions: + contents: read + +env: + PYTHON_VIRTUALENV_ACTIVATE: venv/bin/activate + +jobs: + release: + name: Release + runs-on: ubuntu-22.04 + + permissions: + contents: write + + env: + ARTIFACTS_PATH: dist + + outputs: + artifacts_path: ${{ env.ARTIFACTS_PATH }} + + steps: + - name: Check Out VCS Repository + uses: actions/checkout@v3.3.0 + + - name: Set Up Python + id: set_up_python + uses: actions/setup-python@v4.5.0 + with: + python-version: "3.10.9" + + - name: Create Python Virtual Environment + run: make python-virtualenv PYTHON_VIRTUALENV_DIR="venv" + + - name: Restoring/Saving Cache + uses: actions/cache@v3.2.5 + with: + path: "venv" + key: py-v1-deps-${{ runner.os }}-${{ steps.set_up_python.outputs.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }}-${{ hashFiles('Makefile', 'make/**.mk') }} + + - name: Install Dependencies + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make install-deps-dev + + - name: Build + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make clean-build build + + - name: Build for Distribution + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + make dist + + - name: Store Artifacts + uses: actions/upload-artifact@v3.1.2 + with: + name: release + path: ${{ env.ARTIFACTS_PATH }}/ + if-no-files-found: error + retention-days: 1 + + - name: Set Release Configuration + id: set_release_config + run: | + source "$PYTHON_VIRTUALENV_ACTIVATE" + + library_version=$(python3 ./setup.py --version) + + echo "library_version=${library_version:?}" >> "$GITHUB_OUTPUT" + + - name: Create Git Tag and GitHub Release + if: ${{ inputs.create_git_tag_and_github_release }} + run: | + gh release create \ + "${VCS_TAG_NAME:?}" \ + --target "${TARGET_VCS_REF:?}" \ + --generate-notes \ + ${ASSET_FILES:?} + + echo "library_vcs_tag_name=${VCS_TAG_NAME:?}" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VCS_TAG_NAME: v${{ steps.set_release_config.outputs.library_version }} + TARGET_VCS_REF: ${{ github.sha }} + ASSET_FILES: ${{ env.ARTIFACTS_PATH }}/* diff --git a/Makefile b/Makefile index 3bdf1216..4680ef81 100644 --- a/Makefile +++ b/Makefile @@ -3,19 +3,29 @@ SHELL = /usr/bin/env bash # Python PYTHON = python3 PYTHON_PIP = $(PYTHON) -m pip +PYTHON_PIP_VERSION_SPECIFIER = ==22.3.1 +PYTHON_SETUPTOOLS_VERSION_SPECIFIER = ==58.1.0 +PYTHON_WHEEL_VERSION_SPECIFIER = ==0.38.4 +PYTHON_VIRTUALENV_DIR = venv PYTHON_PIP_TOOLS_VERSION_SPECIFIER = ~=6.8.0 PYTHON_PIP_TOOLS_SRC_FILES = requirements.in requirements-dev.in # Black BLACK = black --config .black.cfg.toml +# Tox +TOXENV ?= py310 + .DEFAULT_GOAL := help .PHONY: help .PHONY: clean clean-build clean-pyc clean-test .PHONY: install-dev install-deps-dev -.PHONY: lint lint-fix test test-all test-coverage test-coverage-report-console test-coverage-report-html -.PHONY: dist upload-release +.PHONY: lint lint-fix test test-all test-coverage +.PHONY: test-coverage-report test-coverage-report-console test-coverage-report-html +.PHONY: build dist deploy upload-release +.PHONE: python-virtualenv .PHONY: python-deps-compile python-deps-sync-check python-pip-tools-install +.PHONY: python-pip-install python-setuptools-install python-wheel-install help: @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' @@ -48,6 +58,7 @@ install-dev: ## Install for development python -m pip install --editable . python -m pip check +install-deps-dev: python-pip-install python-setuptools-install python-wheel-install install-deps-dev: python-pip-tools-install install-deps-dev: ## Install dependencies for development python -m pip install -r requirements.txt @@ -66,8 +77,8 @@ lint-fix: ## Fix lint errors $(BLACK) . isort . -test: ## run tests quickly with the default Python - python setup.py test +test: ## run tests quickly with the default Tox Python + tox -e "$(TOXENV)" test-all: ## run tests on every Python version with tox tox @@ -75,13 +86,20 @@ test-all: ## run tests on every Python version with tox test-coverage: ## run tests and record test coverage coverage run --rcfile=setup.cfg setup.py test +test-coverage-report: test-coverage-report-console +test-coverage-report: test-coverage-report-html +test-coverage-report: ## Run tests, measure code coverage, and generate reports + test-coverage-report-console: ## print test coverage summary coverage report --rcfile=setup.cfg -m test-coverage-report-html: ## generate test coverage HTML report coverage html --rcfile=setup.cfg -dist: clean ## builds source and wheel package +build: ## Build Python package + $(PYTHON) setup.py build + +dist: build ## builds source and wheel package python setup.py sdist python setup.py bdist_wheel twine check dist/* @@ -90,6 +108,21 @@ dist: clean ## builds source and wheel package upload-release: ## upload dist packages python -m twine upload 'dist/*' +deploy: upload-release +deploy: ## Deploy or publish + +python-virtualenv: ## Create virtual Python environment + $(PYTHON) -m venv "$(PYTHON_VIRTUALENV_DIR)" + +python-pip-install: ## Install Pip + $(PYTHON_PIP) install 'pip$(PYTHON_PIP_VERSION_SPECIFIER)' + +python-setuptools-install: ## Install Setuptools + $(PYTHON_PIP) install 'setuptools$(PYTHON_SETUPTOOLS_VERSION_SPECIFIER)' + +python-wheel-install: ## Install Wheel + $(PYTHON_PIP) install 'wheel$(PYTHON_WHEEL_VERSION_SPECIFIER)' + python-deps-compile: $(patsubst %,python-deps-compile-%,$(PYTHON_PIP_TOOLS_SRC_FILES)) python-deps-compile: ## Compile Python dependency manifests diff --git a/README.md b/README.md index 2cda901f..24d33d5b 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ The full documentation is at . ### Development -| VCS Branch | Deployment Environment | VCS Repository | CI Status | -| ---------- | ---------------------- | -------------- | --------- | -| `develop` | Staging | [GitHub](https://github.com/fyntex/lib-cl-sii-python/tree/develop) | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/fyntex/lib-cl-sii-python/tree/develop.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/fyntex/lib-cl-sii-python/tree/develop) | -| `master` | Production | [GitHub](https://github.com/fyntex/lib-cl-sii-python/tree/master) | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/fyntex/lib-cl-sii-python/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/fyntex/lib-cl-sii-python/tree/master) | +| VCS Branch | Deployment Environment | VCS Repository | CI/CD Status | +| ---------- | ---------------------- | -------------- | ------------ | +| `develop` | Staging | [GitHub](https://github.com/fyntex/lib-cl-sii-python/tree/develop) | [![GitHub Actions](https://github.com/fyntex/lib-cl-sii-python/actions/workflows/ci-cd.yaml/badge.svg?branch=develop)](https://github.com/fyntex/lib-cl-sii-python/actions/workflows/ci-cd.yaml?query=branch:develop) | +| `master` | Production | [GitHub](https://github.com/fyntex/lib-cl-sii-python/tree/master) | [![GitHub Actions](https://github.com/fyntex/lib-cl-sii-python/actions/workflows/ci-cd.yaml/badge.svg?branch=master)](https://github.com/fyntex/lib-cl-sii-python/actions/workflows/ci-cd.yaml?query=branch:master) | | Code Coverage | Code Climate | Documentation | Project Analysis | | ------------- | ------------ | ------------- | ---------------- | diff --git a/docs/project-maintenance.rst b/docs/project-maintenance.rst index c29c3a29..a49e1fbf 100644 --- a/docs/project-maintenance.rst +++ b/docs/project-maintenance.rst @@ -48,14 +48,13 @@ Either of the following alternatives:: bumpversion major|minor|patch bumpversion --new-version 'X.Y.Z' part # 'part' is a dummy argument. -Push commit ``abcd1234`` and tag ``vX.Y.Z`` automatically created by ``bumpversion``:: +Push commit ``abcd1234`` automatically created by ``bumpversion``:: git push - git push --tags -Create branch ``release/vX.Y.Z`` that points to tag ``vX.Y.Z`` and push it:: +Create branch ``release/vX.Y.Z`` that points to commit ``abcd1234`` and push it:: - git checkout vX.Y.Z + git checkout abcd1234 git checkout -b release/vX.Y.Z git push origin HEAD @@ -79,28 +78,16 @@ Create branch ``release/vX.Y.Z`` that points to tag ``vX.Y.Z`` and push it:: * Merge PR. -* Go to the CircleCI job named ``ci/circleci: dist`` corresponding to commit ``abcd1234`` - (tagged ``vX.Y.Z``), tab "Artifacts", and download the generated package files to local repo - directory ``dist/``: - - * ``cl-sii-X.Y.Z.tar.gz`` - - * ``cl_sii-X.Y.Z-py3-none-any.whl`` - -* Create new release: +* Modify release: * Go to the repo's `"Releases/tags" section `_. - * Create release for the new tag just pushed. - - * Title: ``vX.Y.Z``. + * Edit the release that was automatically created by the CI/CD workflow for the new version. * Description: same as the PR just created. - * For the new GitHub release, add the files downloaded to ``dist/``. - - * "Publish release". + * "Update release". 4) Publish to PyPI +++++++++++++++++++