Skip to content

public/tidy3d/python-client-release #31

public/tidy3d/python-client-release

public/tidy3d/python-client-release #31

name: "public/tidy3d/python-client-release"
on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release Tag (e.g., v2.10.0, v2.10.0rc1)'
required: true
type: string
release_type:
description: 'Release Type (determines deployment targets)'
type: choice
default: 'draft'
required: false
options:
- draft
- testpypi
- pypi
workflow_control:
description: 'Workflow Stage Control'
default: 'start-tag'
required: false
type: choice
options:
- start-tag
- start-tests
- start-deploy
- only-tag
- only-tests
- only-tag-tests
- only-tag-deploy
client_tests:
description: 'Run python-client-tests'
type: boolean
default: true
cli_tests:
description: 'Run develop-cli tests'
type: boolean
default: true
submodule_tests:
description: 'Run submodule tests'
type: boolean
default: true
workflow_call:
inputs:
release_tag:
description: 'Release Tag (e.g., v2.10.0, v2.10.0rc1)'
required: true
type: string
release_type:
description: 'Release Type (determines deployment targets)'
type: string
default: 'draft'
required: false
workflow_control:
description: 'Workflow Stage Control'
default: 'start-tag'
required: false
type: string
client_tests:
description: 'Run python-client-tests'
type: boolean
default: true
cli_tests:
description: 'Run develop-cli tests'
type: boolean
default: true
submodule_tests:
description: 'Run submodule tests'
type: boolean
default: true
deploy_testpypi:
description: 'Deploy to TestPyPI'
type: boolean
default: false
deploy_pypi:
description: 'Deploy to production PyPI'
type: boolean
default: false
outputs:
workflow_success:
description: 'Overall release workflow success status'
value: ${{ jobs.compile-tests-results.outputs.proceed_deploy == 'true' || jobs.compile-tests-results.result == 'skipped' }}
permissions:
contents: read
jobs:
determine-workflow-scope:
runs-on: ubuntu-latest
outputs:
release_tag: ${{ env.RELEASE_TAG }}
release_type: ${{ env.RELEASE_TYPE }}
is_rc_release: ${{ steps.determine-workflow-steps.outputs.is_rc_release }}
push_to_latest: ${{ steps.determine-workflow-steps.outputs.push_to_latest }}
run_tag: ${{ steps.determine-workflow-steps.outputs.run_tag }}
run_tests: ${{ steps.determine-workflow-steps.outputs.run_tests }}
run_deploy: ${{ steps.determine-workflow-steps.outputs.run_deploy }}
run_client_tests: ${{ steps.determine-workflow-steps.outputs.run_client_tests }}
run_cli_tests: ${{ steps.determine-workflow-steps.outputs.run_cli_tests }}
run_submodule_tests: ${{ steps.determine-workflow-steps.outputs.run_submodule_tests }}
deploy_github_release: ${{ steps.determine-workflow-steps.outputs.deploy_github_release }}
deploy_testpypi: ${{ steps.determine-workflow-steps.outputs.deploy_testpypi }}
deploy_pypi: ${{ steps.determine-workflow-steps.outputs.deploy_pypi }}
sync_readthedocs: ${{ steps.determine-workflow-steps.outputs.sync_readthedocs }}
sync_branches: ${{ steps.determine-workflow-steps.outputs.sync_branches }}
env:
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }}
RELEASE_TYPE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_type || inputs.release_type }}
WORKFLOW_CONTROL: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.workflow_control || inputs.workflow_control }}
CLIENT_TESTS: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.client_tests || inputs.client_tests }}
CLI_TESTS: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.cli_tests || inputs.cli_tests }}
SUBMODULE_TESTS: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.submodule_tests || inputs.submodule_tests }}
DEPLOY_TESTPYPI: ${{ inputs.deploy_testpypi || false }}
DEPLOY_PYPI: ${{ inputs.deploy_pypi || false }}
steps:
- name: validate-tag-format
run: |
set -e
echo "Validating release tag format..."
echo "Release tag: $RELEASE_TAG"
echo "Release type: $RELEASE_TYPE"
# Only enforce strict validation for PyPI releases
if [[ "$RELEASE_TYPE" == "pypi" ]]; then
echo "PyPI release detected - applying strict tag validation"
# Tag must match semantic versioning: v{major}.{minor}.{patch}[rc{num}]
TAG_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$'
if [[ ! "$RELEASE_TAG" =~ $TAG_REGEX ]]; then
echo "Invalid tag format: $RELEASE_TAG"
echo " Expected format: v{major}.{minor}.{patch}[rc{num}]"
echo " Examples: v2.10.0, v2.10.0rc1, v2.10.1rc2"
exit 1
fi
echo "Tag format is valid"
else
echo "Non-PyPI release - skipping strict tag validation"
echo "Tag accepted: $RELEASE_TAG"
fi
- name: determine-workflow-steps
id: determine-workflow-steps
run: |
set -v
echo "=== Input Parameters ==="
echo "RELEASE_TAG: $RELEASE_TAG"
echo "RELEASE_TYPE: $RELEASE_TYPE"
echo "WORKFLOW_CONTROL: $WORKFLOW_CONTROL"
echo "CLIENT_TESTS: $CLIENT_TESTS"
echo "CLI_TESTS: $CLI_TESTS"
echo "SUBMODULE_TESTS: $SUBMODULE_TESTS"
echo "DEPLOY_TESTPYPI: $DEPLOY_TESTPYPI"
echo "DEPLOY_PYPI: $DEPLOY_PYPI"
echo ""
# ============================================
# PART 1: WORKFLOW FLOW CONTROL
# ============================================
run_tag=false
run_tests=false
run_deploy=false
case "$WORKFLOW_CONTROL" in
start-tag)
run_tag=true
run_tests=true
run_deploy=true
;;
start-tests)
run_tests=true
run_deploy=true
;;
start-deploy)
run_deploy=true
;;
only-tag)
run_tag=true
;;
only-tests)
run_tests=true
;;
only-tag-tests)
run_tag=true
run_tests=true
;;
only-tag-deploy)
run_tag=true
run_deploy=true
;;
*)
echo "Invalid WORKFLOW_CONTROL: $WORKFLOW_CONTROL"
exit 1
;;
esac
echo "=== Workflow Stage Control ==="
echo "run_tag: $run_tag"
echo "run_tests: $run_tests"
echo "run_deploy: $run_deploy"
echo ""
# ============================================
# PART 2: DETERMINE RC STATUS
# ============================================
is_rc_release=false
if [[ "$RELEASE_TAG" == *"rc"* ]]; then
is_rc_release=true
fi
echo "=== Release Type ==="
echo "is_rc_release: $is_rc_release"
echo ""
# ============================================
# PART 2.5: DETERMINE PUSH TO LATEST
# ============================================
push_to_latest=false
SEMVER_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+$'
# Only push to latest if:
# 1. Release type is pypi
# 2. Not an RC release
# 3. Tag matches semantic versioning pattern v{major}.{minor}.{patch}
if [[ "$RELEASE_TYPE" == "pypi" && "$is_rc_release" == "false" && "$RELEASE_TAG" =~ $SEMVER_REGEX ]]; then
push_to_latest=true
echo "=== Push to Latest ==="
echo "Will push to 'latest' branch in readthedocs"
echo " Conditions met: pypi release + non-RC + semantic version tag"
else
echo "=== Push to Latest ==="
echo " Will NOT push to 'latest' branch"
if [[ "$RELEASE_TYPE" != "pypi" ]]; then
echo " Reason: Not a PyPI release (is $RELEASE_TYPE)"
elif [[ "$is_rc_release" == "true" ]]; then
echo " Reason: RC release"
elif [[ ! "$RELEASE_TAG" =~ $SEMVER_REGEX ]]; then
echo " Reason: Tag doesn't match semantic version pattern (v{major}.{minor}.{patch})"
fi
fi
echo ""
# ============================================
# PART 3: TEST CONTROL
# ============================================
run_client_tests=false
run_cli_tests=false
run_submodule_tests=false
if [[ "$run_tests" == "true" ]]; then
[[ "$CLIENT_TESTS" == "true" ]] && run_client_tests=true
[[ "$CLI_TESTS" == "true" ]] && run_cli_tests=true
# Submodule tests: user input OR auto-enable for PyPI non-RC releases
if [[ "$SUBMODULE_TESTS" == "true" ]]; then
run_submodule_tests=true
echo "?? Submodule tests enabled by user input"
elif [[ "$push_to_latest" == "true" ]]; then
run_submodule_tests=true
echo "?? Submodule tests auto-enabled for PyPI non-RC release (push_to_latest=true)"
fi
fi
echo "=== Test Control ==="
echo "run_client_tests: $run_client_tests"
echo "run_cli_tests: $run_cli_tests"
echo "run_submodule_tests: $run_submodule_tests"
echo ""
# ============================================
# PART 4: DEPLOYMENT CONTROL
# ============================================
deploy_github_release=false
deploy_testpypi=false
deploy_pypi=false
sync_readthedocs=false
sync_branches=false
if [[ "$run_deploy" == "true" ]]; then
# Always create GitHub release and sync docs
deploy_github_release=true
sync_readthedocs=true
# Deployment target logic:
# 1. If any deployment checkbox is explicitly set, use those
# 2. Otherwise, use automatic defaults based on release_type
if [[ "$DEPLOY_TESTPYPI" == "true" || "$DEPLOY_PYPI" == "true" ]]; then
# Manual override: use checkbox selections
echo "Using manual deployment target selections"
deploy_testpypi=$DEPLOY_TESTPYPI
deploy_pypi=$DEPLOY_PYPI
else
# Automatic defaults based on release_type
echo "Using automatic deployment defaults for release_type: $RELEASE_TYPE"
case "$RELEASE_TYPE" in
pypi)
# PyPI releases: deploy to PyPI and TestPyPI
deploy_testpypi=true
deploy_pypi=true
;;
testpypi)
# TestPyPI releases: deploy to TestPyPI
deploy_testpypi=true
;;
draft)
# Draft releases: no deployment
echo "Draft release - no automatic deployments"
;;
*)
echo "Unknown release_type: $RELEASE_TYPE - no automatic deployments"
;;
esac
fi
# Sync branches on PyPI releases if deploying to PyPI
if [[ "$RELEASE_TYPE" == "pypi" && "$deploy_pypi" == "true" ]]; then
sync_branches=true
fi
fi
echo "=== Deployment Control ==="
echo "deploy_github_release: $deploy_github_release"
echo "deploy_testpypi: $deploy_testpypi"
echo "deploy_pypi: $deploy_pypi"
echo "sync_readthedocs: $sync_readthedocs"
echo "sync_branches: $sync_branches"
echo ""
# ============================================
# PART 5: SAVE ALL OUTPUTS
# ============================================
echo "is_rc_release=$is_rc_release" >> $GITHUB_OUTPUT
echo "push_to_latest=$push_to_latest" >> $GITHUB_OUTPUT
echo "run_tag=$run_tag" >> $GITHUB_OUTPUT
echo "run_tests=$run_tests" >> $GITHUB_OUTPUT
echo "run_deploy=$run_deploy" >> $GITHUB_OUTPUT
echo "run_client_tests=$run_client_tests" >> $GITHUB_OUTPUT
echo "run_cli_tests=$run_cli_tests" >> $GITHUB_OUTPUT
echo "run_submodule_tests=$run_submodule_tests" >> $GITHUB_OUTPUT
echo "deploy_github_release=$deploy_github_release" >> $GITHUB_OUTPUT
echo "deploy_testpypi=$deploy_testpypi" >> $GITHUB_OUTPUT
echo "deploy_pypi=$deploy_pypi" >> $GITHUB_OUTPUT
echo "sync_readthedocs=$sync_readthedocs" >> $GITHUB_OUTPUT
echo "sync_branches=$sync_branches" >> $GITHUB_OUTPUT
echo "? Workflow scope determined"
create-tag:
name: create-and-push-tag
needs: determine-workflow-scope
if: needs.determine-workflow-scope.outputs.run_tag == 'true'
uses: ./.github/workflows/tidy3d-python-client-create-tag.yml
permissions:
contents: write
with:
release_tag: ${{ needs.determine-workflow-scope.outputs.release_tag }}
release_type: ${{ needs.determine-workflow-scope.outputs.release_type }}
secrets: inherit # zizmor: ignore[secrets-inherit]
run-client-tests:
name: run-python-client-tests
needs: [determine-workflow-scope, create-tag]
if: |
always() &&
needs.determine-workflow-scope.outputs.run_client_tests == 'true'
uses: ./.github/workflows/tidy3d-python-client-tests.yml
permissions:
contents: read
security-events: write
pull-requests: write
with:
release_tag: ${{ needs.determine-workflow-scope.outputs.release_tag }}
local_tests: true
remote_tests: true
cli_tests: ${{ needs.determine-workflow-scope.outputs.run_cli_tests == 'true' }}
submodule_tests: ${{ needs.determine-workflow-scope.outputs.run_submodule_tests == 'true' }}
version_match_tests: true
compile-tests-results:
name: compile-tests-results
if: |
always() &&
needs.determine-workflow-scope.outputs.run_tests == 'true' &&
needs.determine-workflow-scope.outputs.run_deploy == 'true'
needs:
- determine-workflow-scope
- run-client-tests
runs-on: ubuntu-latest
outputs:
proceed_deploy: ${{ steps.check-tests.outputs.proceed_deploy }}
steps:
- name: check-tests
id: check-tests
env:
RUN_CLIENT_TESTS: ${{ needs.determine-workflow-scope.outputs.run_client_tests }}
CLIENT_TESTS_RESULT: ${{ needs.run-client-tests.result }}
WORKFLOW_SUCCESS: ${{ needs.run-client-tests.outputs.workflow_success }}
run: |
echo "=== Checking Test Results ==="
echo ""
proceed_deploy=true
# Check client tests using the workflow_success output from tests workflow
# This output validates all tests: local, remote, CLI, submodule, lint, mypy, etc.
if [[ "$RUN_CLIENT_TESTS" == "true" ]]; then
echo "Client tests workflow result: $CLIENT_TESTS_RESULT"
echo "Client tests validation status: $WORKFLOW_SUCCESS"
if [[ "$CLIENT_TESTS_RESULT" != "success" ]] || [[ "$WORKFLOW_SUCCESS" != "true" ]]; then
echo "Client tests failed - see workflow-validation job in tests workflow for details"
proceed_deploy=false
else
echo "All client tests passed (local, remote, CLI, submodule, and quality checks)"
fi
else
echo "Client tests: not required (skipped)"
fi
echo ""
echo "=== Final Decision ==="
if [[ "$proceed_deploy" == "true" ]]; then
echo "All required tests passed - deployment can proceed"
else
echo "One or more required tests failed - deployment blocked"
exit 1
fi
echo "proceed_deploy=$proceed_deploy" >> $GITHUB_OUTPUT
sync-readthedocs:
name: sync-docs-to-readthedocs
needs: [determine-workflow-scope, compile-tests-results]
if: |
always() &&
(needs.compile-tests-results.outputs.proceed_deploy == 'true' || needs.compile-tests-results.result == 'skipped') &&
needs.determine-workflow-scope.outputs.sync_readthedocs == 'true'
uses: ./.github/workflows/tidy3d-docs-sync-readthedocs-repo.yml
permissions:
contents: write
with:
source_ref: ${{ needs.determine-workflow-scope.outputs.release_tag }}
target_ref: ${{ needs.determine-workflow-scope.outputs.push_to_latest == 'true' && 'latest' || '' }}
secrets: inherit # zizmor: ignore[secrets-inherit]
github-release:
name: create-github-release
needs: [determine-workflow-scope, compile-tests-results]
if: |
always() &&
(needs.compile-tests-results.outputs.proceed_deploy == 'true' || needs.compile-tests-results.result == 'skipped') &&
needs.determine-workflow-scope.outputs.deploy_github_release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
env:
RELEASE_TAG: ${{ needs.determine-workflow-scope.outputs.release_tag }}
IS_RC_RELEASE: ${{ needs.determine-workflow-scope.outputs.is_rc_release }}
steps:
- run: |
echo "steps"
# - name: checkout-tag
# uses: actions/checkout@v4
# with:
# ref: ${{ env.RELEASE_TAG }}
# persist-credentials: false
# - name: create-github-release
# uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
# with:
# tag_name: ${{ env.RELEASE_TAG }}
# generate_release_notes: true
# prerelease: ${{ env.IS_RC_RELEASE == 'true' }}
# env:
# GITHUB_TOKEN: ${{ secrets.GH_PAT }}
deploy-packages:
name: deploy-to-package-repositories
needs: [determine-workflow-scope, compile-tests-results]
if: |
always() &&
(needs.compile-tests-results.outputs.proceed_deploy == 'true' || needs.compile-tests-results.result == 'skipped') &&
needs.determine-workflow-scope.outputs.run_deploy == 'true' &&
(needs.determine-workflow-scope.outputs.deploy_testpypi == 'true' ||
needs.determine-workflow-scope.outputs.deploy_pypi == 'true')
uses: ./.github/workflows/tidy3d-python-client-deploy.yml
permissions:
contents: read
id-token: write
with:
release_tag: ${{ needs.determine-workflow-scope.outputs.release_tag }}
deploy_testpypi: ${{ needs.determine-workflow-scope.outputs.deploy_testpypi == 'true' }}
deploy_pypi: ${{ needs.determine-workflow-scope.outputs.deploy_pypi == 'true' }}
secrets: inherit