[NEW] Py Pkg Gen v2.3.0 Release #1942
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: CI/CD Pipeline | |
# Continuous Integration / Continuous Delivery | |
# Triggers on all Branches and v* Tags | |
### Stress-Testing, with Multi-Factor Job Matrix, on: ### | |
# - tags v* | |
# - the 'stress-test' branch (GITHUB_REF_NAME == 'stress-test') | |
### Production PyPI Publish, pypi.org, on: ### | |
# - v* tags on 'master' branch only | |
### Staging/Test PyPI Publish, test.pypi.org, on: ### | |
## Test PyPI publish on: ## | |
# - v*-rc 'pre-release' tags on 'release' branch | |
### Dockerhub publish on ### | |
# - all branches and tags | |
on: | |
push: | |
branches: | |
- "*" | |
tags: | |
- v* | |
env: | |
### STRESS TEST Job MATRIX ### | |
FULL_MATRIX_STRATEGY: "{\"platform\": [\"ubuntu-latest\", \"macos-latest\", \"windows-latest\"], \"python-version\": [\"3.7\", \"3.8\", \"3.9\", \"3.10\", \"3.12\"]}" | |
# Python 3.7 has reached End of Life (EOL) on June 27th, 2023 | |
# Python 3.12 is in bugfix mode, same as 3.11 -> can start supporting 3.12 it | |
UBUNTU_PY310_STRATEGY: "{\"platform\": [\"ubuntu-latest\"], \"python-version\": [\"3.10\"]}" | |
TEST_STRATEGY: "{\"platform\": [\"ubuntu-latest\", \"macos-latest\", \"windows-latest\"], \"python-version\":[\"3.10\"]}" | |
##### JOB ON/OFF SWITCHES - Top/1st level overrides ##### | |
RUN_UNIT_TESTS: "true" | |
RUN_LINT_CHECKS: "true" | |
DOCKER_JOB_ON: "true" | |
PUBLISH_ON_PYPI: "true" | |
DOCS_JOB_ON: "true" | |
DRAW_DEPENDENCIES: "true" | |
########################## | |
### DOCKER Job Policy #### | |
# Override Docker Policy-dependent decision-making and | |
# Accept any ALL (branch/build) to Publish to Dockerhub | |
# if true, it will push image and ignore DOCKER_JOB_POLICY | |
ALWAYS_BUILD_N_PUBLISH_DOCKER: "false" | |
DOCKER_JOB_POLICY: "CDeployment" | |
# - CDeployment : '2' Builds and Publishes only if Tests ran and passed | |
# - CDelivery : '3' Builds and Publishes if Tests Passed or if Tests were Skipped | |
############################ | |
#### DOCS Job #### | |
# 2nd level override | |
ALWAYS_DOCS: "false" | |
DOCS_JOB_POLICY: '2' # {2, 3} | |
PY_VERSION: "3.11" | |
#### STATIC CODE ANALYSIS Job #### | |
ALWAYS_LINT: "false" | |
LINT_JOB_POLICY: '2' # {2, 3} | |
## Python Runtime version to set the Job runner with ## | |
STATIC_ANALYSIS_PY: "3.10" | |
## Pylint Minimum Acceptance Rating/Score ## | |
PYLINT_SCORE_THRESHOLD: "8.2" | |
#### CODE VISUALIZATION Job #### | |
ALWAYS_CODE_VIZ: "false" | |
CODE_VIZ_POLICY: '2' # {2, 3} | |
########################## | |
jobs: | |
# we use the below to read the workflow env vars and be able to use in "- if:" Job conditionals | |
# now we can do -> if: ${{ needs.set_github_outputs.outputs.TESTS_ENABLED == 'true' }} | |
# github does not have a way to simply do "- if: ${{ env.RUN_UNIT_TESTS == 'true' }} " !! | |
set_github_outputs: | |
name: Read Workflow Env Section Vars and set Github Outputs | |
runs-on: ubuntu-latest | |
steps: | |
- name: Pass 'env' section variables to GITHUB_OUTPUT | |
id: pass-env-to-output | |
env: | |
BOARDING_EVENT: 'do Boarding CI Tests' | |
BOARDING_MSG: "Auto Merging '[^']+' carrying '([^']+)' Changes" | |
run: | | |
BRANCH_NAME=${GITHUB_REF_NAME} | |
PIPE_DOCS_POLICY="${{ (env.DOCS_JOB_ON != 'true' && '0') || (env.ALWAYS_DOCS == 'true' && '1') || env.DOCS_JOB_POLICY }}" | |
# set the matrix strategy to Full Matrix Stress Test if on master/main or stress-test branch or any tag | |
if [[ $BRANCH_NAME == "stress-test" || $GITHUB_REF == refs/tags/* ]]; then | |
echo "matrix=$FULL_MATRIX_STRATEGY" >> $GITHUB_OUTPUT | |
# github.event.head_commit.message has 'do Boarding CI Tests' string | |
elif [[ "${{ contains(github.event.head_commit.message, env.BOARDING_EVENT) }}" == 'true' ]]; then | |
echo "matrix=$TEST_STRATEGY" >> $GITHUB_OUTPUT | |
# interpret Boarding Message: if head_commit.message matches BOARDING_MSG regex | |
elif [[ "${{ github.event.head_commit.message }}" =~ ${BOARDING_MSG} ]]; then | |
affected_components="${BASH_REMATCH[1]}" | |
echo "--> Detected Affected Components: $affected_components <--" | |
if [[ "$affected_components" =~ "Distro" ]]; then | |
echo "matrix=$TEST_STRATEGY" >> $GITHUB_OUTPUT | |
else | |
echo "matrix=$UBUNTU_PY310_STRATEGY" >> $GITHUB_OUTPUT | |
fi | |
if [[ "$affected_components" =~ "Docs" ]]; then | |
# set policy to 1 to trigger Docs Build (higher level override might be in place) | |
PIPE_DOCS_POLICY="${{ (env.DOCS_JOB_ON != 'true' && '0') || (env.ALWAYS_DOCS == 'true' && '1') || '1' }}" | |
fi | |
else | |
# Fall back to the default strategy | |
echo "matrix=$UBUNTU_PY310_STRATEGY" >> $GITHUB_OUTPUT | |
fi | |
## we skip ci, on pushes to 'release' branch ## | |
echo "TESTS_ENABLED=${{ env.RUN_UNIT_TESTS == 'true' && github.ref_name != 'release' }}" >> $GITHUB_OUTPUT | |
echo "PUBLISH_ON_PYPI=$PUBLISH_ON_PYPI" >> $GITHUB_OUTPUT | |
echo "PIPE_DOCS_POLICY=$PIPE_DOCS_POLICY" >> $GITHUB_OUTPUT | |
echo "PIPE_DOCS_PY=$PY_VERSION" >> $GITHUB_OUTPUT | |
## Docker - Pipeline Settings ## | |
- id: derive_docker_policy | |
run: echo "POL=${{ (env.DOCKER_JOB_ON != 'true' && '0') || (env.ALWAYS_BUILD_N_PUBLISH_DOCKER == 'true' && '1') || (env.DOCKER_JOB_POLICY == 'CDeployment' && '2') || (env.DOCKER_JOB_POLICY == 'CDelivery' && '3') }}" >> $GITHUB_OUTPUT | |
## Static Code Analysis - Pipeline Settings ## | |
- id: derive_sqa_policy | |
run: echo "POL=${{ (env.RUN_LINT_CHECKS != 'true' && '0') || (env.ALWAYS_LINT == 'true' && '1') || env.LINT_JOB_POLICY }}" >> $GITHUB_OUTPUT | |
- id: read_sqa_py | |
run: echo SQA_PY=${{ env.STATIC_ANALYSIS_PY }} >> $GITHUB_OUTPUT | |
- id: read_pylint_score_threshold | |
run: echo PYLINT_SCORE_THRESHOLD=${{ env.PYLINT_SCORE_THRESHOLD }} >> $GITHUB_OUTPUT | |
## Code Visualization - Pipeline Settings ## | |
- id: derive_code_viz_policy | |
run: echo "POL=${{ (env.DRAW_DEPENDENCIES != 'true' && '0') || (env.ALWAYS_CODE_VIZ == 'true' && '1') || env.CODE_VIZ_POLICY }}" >> $GITHUB_OUTPUT | |
outputs: | |
matrix: ${{ steps.pass-env-to-output.outputs.matrix }} | |
TESTS_ENABLED: ${{ steps.pass-env-to-output.outputs.TESTS_ENABLED }} | |
PUBLISH_ON_PYPI: ${{ steps.pass-env-to-output.outputs.PUBLISH_ON_PYPI }} | |
## DOCS - Pipeline Settings ## | |
PIPE_DOCS_POLICY: ${{ steps.pass-env-to-output.outputs.PIPE_DOCS_POLICY }} | |
PIPE_DOCS_PY: ${{ steps.pass-env-to-output.outputs.PIPE_DOCS_PY }} | |
## Docker - Pipeline Settings ## | |
PIPE_DOCKER_POLICY: ${{ steps.derive_docker_policy.outputs.POL }} | |
## Static Code Analysis - Pipeline Settings ## | |
PIPE_SQA_POLICY: ${{ steps.derive_sqa_policy.outputs.POL }} | |
PIPE_SQA_PY: ${{ steps.read_sqa_py.outputs.SQA_PY }} | |
PIPE_SQA_PYLINT_PASS_SCORE: ${{ steps.read_pylint_score_threshold.outputs.PYLINT_SCORE_THRESHOLD }} | |
## Code Visualization - Pipeline Settings ## | |
PIPE_CODE_VIZ_POLICY: ${{ steps.derive_code_viz_policy.outputs.POL }} | |
# RUN TEST SUITE ON ALL PLATFORMS | |
test_suite: | |
runs-on: ${{ matrix.platform }} | |
needs: set_github_outputs | |
if: ${{ needs.set_github_outputs.outputs.TESTS_ENABLED == 'true' }} | |
strategy: | |
matrix: ${{fromJSON(needs.set_github_outputs.outputs.matrix)}} | |
env: | |
WHEELS_PIP_DIR: "wheels-pip" | |
outputs: | |
PEP_VERSION: ${{ steps.extract_wheel_info.outputs.PEP_VERSION }} | |
steps: | |
- run: echo "Platform -> ${{ matrix.platform }} , Python -> ${{ matrix.python-version }}" | |
- uses: actions/checkout@v4 | |
- name: Set up Python ${{ matrix.python-version }} | |
uses: actions/setup-python@v5 | |
with: | |
python-version: ${{ matrix.python-version }} | |
- run: python -m pip install --upgrade pip && python -m pip install tox==3.28 tox-gh-actions | |
- name: Pin 'Static Type Checking' Dependencies | |
run: tox -vv -s false -e pin-deps -- -E typing | |
- name: Do Type Checking | |
run: tox -e type -vv -s false | |
### Check if Requested RC Pipeline ### | |
- name: "Update Source Sem Ver with Release Candidate *-rc' suffix" | |
if: ${{ startsWith(github.event.ref, 'refs/tags/v') && contains(github.event.ref, '-rc') }} | |
shell: bash | |
run: | | |
# Extract PROD Sem Ver | |
SEMVER="$(grep -E -o '^version\s*=\s*\".*\"' pyproject.toml | cut -d'"' -f2)" | |
# Derive RC Sem Ver | |
RC_SEMVER="${SEMVER}-rc" | |
## Set Source Sem Ver to Release Candidate Sem Ver ## | |
# if needed: chmod +x ./scripts/distro-sem-ver-bump.sh | |
sh ./scripts/distro-sem-ver-bump.sh "${RC_SEMVER}" | |
###### TEST SUITE RUN ###### | |
- name: Run Unit Tests on Edit, and Sdist; and build Wheels | |
shell: bash | |
run: | | |
set -o pipefail | |
tox -vv -s false | tee test_output.log | |
env: | |
PLATFORM: ${{ matrix.platform }} | |
BUILD_DEST: ${{ env.WHEELS_PIP_DIR }} | |
- name: Show produced Wheel(s) for Distro and its Requirements | |
run: ls -l ${{ env.WHEELS_PIP_DIR }} | |
# parse test_output.log and match string, like: | |
# Created wheel for cookiecutter_python: filename=cookiecutter_python-1.12.5.dev0-py3-none-any.whl size=199750 sha256=446c75803a6eea1d7ac6af60e0fbb0000483f97ef44eb39469ab3bd997b2a7d8 | |
# starting line with 'Created wheel for' and extracting filename value and size value | |
- name: Extract Wheel Name and Size | |
id: extract_wheel_info | |
shell: bash | |
run: | | |
WHEEL_INFO=$(grep -E "Created wheel for" test_output.log | sed -E "s/.*filename=([^ ]+) size=([^ ]+) .*/\1 \2/") | |
# extract file name | |
WHEEL_NAME=$(echo $WHEEL_INFO | cut -d ' ' -f 1) | |
echo "WHEEL_NAME=$WHEEL_NAME" >> $GITHUB_ENV | |
# extract file size | |
WHEEL_SIZE=$(echo $WHEEL_INFO | cut -d ' ' -f 2) | |
echo "WHEEL_SIZE=$WHEEL_SIZE" >> $GITHUB_ENV | |
# extract, ie '1.12.5dev0' from 'cookiecutter_python-1.15.0rc0-py3-none-any.whl' | |
PEP_VERSION=$(echo $WHEEL_NAME | sed -E "s/cookiecutter_python-([^ ]+)-py3-none-any.whl/\1/") | |
echo "PEP_VERSION=${PEP_VERSION}" >> $GITHUB_OUTPUT | |
- run: 'echo "WHEEL_NAME: $WHEEL_NAME SIZE: $WHEEL_SIZE"' | |
- run: 'echo "PEP_VERSION: $PEP_VERSION"' | |
- name: Run Test Suite Against Wheel | |
run: tox -e ${{ env.TOX_ENV_WHEEL_TEST }} -s false | |
env: | |
PLATFORM: ${{ matrix.platform }} | |
TOX_ENV_WHEEL_TEST: ${{ (matrix.platform == 'windows-latest' && matrix.python-version != '3.7') && 'wheel-test-windows' || 'wheel-test'}} | |
BUILD_DEST: ${{ env.WHEELS_PIP_DIR }} | |
WHEEL: ${{ env.WHEEL_NAME }} | |
## Code Coverage ## | |
- name: "Combine Coverage (dev, sdist, wheel) & make Reports" | |
run: tox -e coverage --sitepackages -vv -s false | |
- name: Rename Coverage Files | |
shell: bash | |
run: | | |
mv ./.tox/coverage.xml ./coverage-${{ matrix.platform }}-${{ matrix.python-version }}.xml | |
- name: "Upload Test Coverage as Artifacts" | |
uses: actions/upload-artifact@v3 | |
with: | |
name: all_coverage_raw | |
path: coverage-${{ matrix.platform }}-${{ matrix.python-version }}.xml | |
if-no-files-found: error | |
- name: Check for compliance with Python Best Practices | |
shell: bash | |
env: | |
PKG_VERSION: ${{ steps.extract_wheel_info.outputs.PEP_VERSION }} | |
WHEEL_PATH: ${{ env.WHEELS_PIP_DIR }}/${{ env.WHEEL_NAME }} | |
run: | | |
DIST_DIR=dist | |
echo "DIST_DIR=${DIST_DIR}" >> $GITHUB_ENV | |
mkdir ${DIST_DIR} | |
mv ".tox/${DIST_DIR}/cookiecutter_python-${PKG_VERSION}.tar.gz" "${DIST_DIR}" | |
mv "${{ env.WHEEL_PATH }}" "${DIST_DIR}" | |
tox -e check -vv -s false | |
- name: Upload Source & Wheel distributions as Artefacts | |
uses: actions/upload-artifact@v3 | |
with: | |
name: dist-${{ matrix.platform }}-${{ matrix.python-version }} | |
path: ${{ env.DIST_DIR }} | |
if-no-files-found: error | |
codecov_coverage_host: | |
runs-on: ubuntu-latest | |
needs: test_suite | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Get Codecov binary | |
run: | | |
curl -Os https://uploader.codecov.io/latest/linux/codecov | |
chmod +x codecov | |
- name: Download Raw Coverage Data Artefacts | |
uses: actions/download-artifact@v3 | |
with: | |
name: all_coverage_raw | |
- name: Upload Coverage Reports to Codecov | |
run: | | |
for file in coverage*.xml; do | |
OS_NAME=$(echo $file | sed -E "s/coverage-(\w\+)-/\1/") | |
PY_VERSION=$(echo $file | sed -E "s/coverage-\w\+-(\d\.)\+/\1/") | |
./codecov -f $file -e "OS=$OS_NAME,PYTHON=$PY_VERSION" --flags unittests --verbose | |
echo "Sent to Codecov: $file !" | |
done | |
## DOCKER BUILD and PUBLISH ON DOCKERHUB ## | |
# Ref Page: https://automated-workflows.readthedocs.io/en/main/ref_docker/ | |
docker_build: | |
needs: [set_github_outputs, test_suite] | |
uses: boromir674/automated-workflows/.github/workflows/[email protected] | |
if: always() | |
with: | |
acceptance_policy: ${{ needs.set_github_outputs.outputs.PIPE_DOCKER_POLICY }} | |
image_slug: "generate-python" | |
# target_stage: "some_stage_alias" # no stage, means no `--target` flag, on build | |
tests_pass: ${{ needs.test_suite.result == 'success' }} | |
tests_run: ${{ !contains(fromJSON('["skipped", "cancelled"]'), needs.test_suite.result) }} | |
DOCKER_USER: ${{ vars.DOCKER_USER }} | |
secrets: | |
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | |
## JOB: Signal for Automated PyPI Upload ## | |
check_which_git_branch_we_are_on: | |
runs-on: ubuntu-latest | |
if: ${{ startsWith(github.event.ref, 'refs/tags/v') }} | |
env: | |
RELEASE_BR: 'release' | |
MAIN_BR: 'master' | |
steps: | |
# Fetch 'master' and 'release' branches | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- run: git branch --track "${{ env.RELEASE_BR }}" "origin/${{ env.RELEASE_BR }}" | |
- name: "Check if '${{ github.ref }}' tag is on '${{ env.MAIN_BR }}' branch" | |
uses: rickstaa/action-contains-tag@v1 | |
id: main_contains_tag | |
with: | |
reference: ${{ env.MAIN_BR }} | |
tag: "${{ github.ref }}" | |
- name: "Check if '${{ github.ref }}' tag is on '${{ env.RELEASE_BR }}' branch" | |
uses: rickstaa/action-contains-tag@v1 | |
id: release_contains_tag | |
with: | |
reference: ${{ env.RELEASE_BR }} | |
tag: "${{ github.ref }}" | |
- name: Pick Production or Test Environment, if tag on master or release branch respectively | |
id: set_environment_name | |
run: | | |
DEPLOY=true | |
if [[ "${{ steps.main_contains_tag.outputs.retval }}" == "true" ]]; then | |
echo "ENVIRONMENT_NAME=PROD_DEPLOYMENT" >> $GITHUB_OUTPUT | |
elif [[ "${{ steps.release_contains_tag.outputs.retval }}" == "true" ]]; then | |
echo "ENVIRONMENT_NAME=TEST_DEPLOYMENT" >> $GITHUB_OUTPUT | |
else | |
echo "A tag was pushed but not on master or release branch. No deployment will be done." | |
DEPLOY=false | |
fi | |
echo "AUTOMATED_DEPLOY=$DEPLOY" >> $GITHUB_OUTPUT | |
outputs: | |
ENVIRONMENT_NAME: ${{ steps.set_environment_name.outputs.ENVIRONMENT_NAME }} | |
AUTOMATED_DEPLOY: ${{ steps.set_environment_name.outputs.AUTOMATED_DEPLOY }} | |
## JOB: PYPI UPLOAD ## | |
pypi_publish: | |
needs: [set_github_outputs, test_suite, check_which_git_branch_we_are_on] | |
uses: boromir674/automated-workflows/.github/workflows/pypi_env.yml@8e97f596067fcbbaa0a6927ec1ee47dce4ab5f1a | |
with: | |
should_trigger: ${{ needs.set_github_outputs.outputs.PUBLISH_ON_PYPI == 'true' && needs.check_which_git_branch_we_are_on.outputs.AUTOMATED_DEPLOY == 'true' }} | |
distro_name: cookiecutter_python | |
distro_version: ${{ needs.test_suite.outputs.PEP_VERSION }} | |
pypi_env: '${{ needs.check_which_git_branch_we_are_on.outputs.ENVIRONMENT_NAME }}' | |
artifacts_path: downloaded-artifacts | |
require_wheel: true | |
allow_existing: true | |
secrets: | |
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} | |
### STATIC CODE ANALYSIS & LINTING ### | |
lint: | |
name: Static Code Analysis | |
needs: set_github_outputs | |
uses: ./.github/workflows/policy_lint.yml | |
with: | |
run_policy: ${{ needs.set_github_outputs.outputs.PIPE_SQA_POLICY }} | |
dedicated_branches: 'master, main, dev' | |
source_code_targets: 'src,tests,scripts' | |
python_version: ${{ needs.set_github_outputs.outputs.PIPE_SQA_PY }} | |
pylint_threshold: ${{ needs.set_github_outputs.outputs.PIPE_SQA_PYLINT_PASS_SCORE }} | |
### DOCS BUILD/TEST - DOCUMENTATION SITE ### | |
docs: | |
name: Build Documentation | |
needs: set_github_outputs | |
# bafaa2c2a014758a4421fe9b5c02ba66dbfdbef6 | |
uses: boromir674/automated-workflows/.github/workflows/policy_docs.yml@test | |
with: | |
run_policy: '${{ needs.set_github_outputs.outputs.PIPE_DOCS_POLICY }}' | |
python_version: ${{ needs.set_github_outputs.outputs.PIPE_DOCS_PY }} | |
command: "tox -e docs --sitepackages -vv -s false" | |
# command: "tox -e pin-deps -- -E docs && tox -e docs --sitepackages -vv -s false" | |
### DRAW PYTHON DEPENDENCY GRAPHS ### | |
code_visualization: | |
needs: set_github_outputs | |
name: Code Visualization of Python Imports as Graphs, in .svg | |
uses: boromir674/automated-workflows/.github/workflows/[email protected] | |
with: | |
run_policy: '${{ needs.set_github_outputs.outputs.PIPE_CODE_VIZ_POLICY }}' | |
branches: 'main, master, dev' | |
source_code_targets: 'src' | |
python_version: '3.10' | |
artifacts_dir: 'dependency-graphs' | |
### Make a Github Release ### | |
gh_release: | |
needs: [test_suite, check_which_git_branch_we_are_on] | |
if: ${{ needs.check_which_git_branch_we_are_on.outputs.AUTOMATED_DEPLOY == 'true' }} | |
uses: boromir674/automated-workflows/.github/workflows/gh-release.yml@test | |
name: 'GH Release' | |
with: | |
tag: ${{ github.ref_name }} | |
draft: ${{ needs.check_which_git_branch_we_are_on.outputs.ENVIRONMENT_NAME == 'TEST_DEPLOYMENT' }} | |
secrets: | |
# passing the GH_TOKEN PAT, to render in GH as ie: 'boromir674 released this yesterday', instead of 'github-actions released this yesterday' | |
gh_token: ${{ secrets.GH_TOKEN }} |