Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci-main.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Thin trigger wrapper, job logic lives in reusable-*.yml so ci-main and
# ci-release share it.
name: CI

on:
Expand All @@ -10,9 +12,11 @@ on:
jobs:

static-analysis:
if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
uses: ./.github/workflows/reusable-static-analysis.yml

# Template-only tests live in template-tests.yml: their reusable workflow is
# deleted by setup, and a dangling `uses:` breaks the caller even behind `if:`.

tests:
if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
uses: ./.github/workflows/reusable-maya-tests.yml
35 changes: 32 additions & 3 deletions .github/workflows/ci-release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
# Thin trigger wrapper, job logic lives in reusable-*.yml so ci-main and
# ci-release share it.
name: Release

on:
workflow_dispatch:
inputs:
version:
description: "Version to release (e.g. 1.2.3)"
required: true
type: string
platforms:
description: "Platforms to vendor runtime deps for (e.g: win64, linux, mac)"
required: false
type: string
# release.py never passes these, so the defaults are what ships. Keep
# in step with reusable-maya-tests.yml: a Maya version with no .mod row
# won't load the module, even though CI went green on it.
default: "win64,linux,mac"
maya_versions:
description: "Maya versions to vendor runtime deps for (e.g: 2024,2025,2026)"
required: false
type: string
default: "2024,2025,2026,2027"
vendor_deps:
description: "Vendor runtime dependencies into the module (uncheck to ship without them)"
required: false
type: boolean
default: true

jobs:

Expand All @@ -15,11 +40,15 @@ jobs:

do_release:
if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
needs: [tests]
uses: ./.github/workflows/reusable-semantic-release.yml
needs: [tests, static-analysis]
uses: ./.github/workflows/reusable-release.yml
with:
version: ${{ inputs.version }}
platforms: ${{ inputs.platforms }}
maya_versions: ${{ inputs.maya_versions }}
vendor_deps: ${{ inputs.vendor_deps }}
permissions:
contents: write
id-token: write

docs:
if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
Expand Down
18 changes: 16 additions & 2 deletions .github/workflows/initial-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Rename README.md and delete setup workflow
run: |
Expand All @@ -32,7 +32,11 @@ jobs:

# Rename _README.md to README.md
mv _README.md README.md


# Replace template AGENTS.md with the generated-project version
rm -f AGENTS.md
mv _AGENTS.md AGENTS.md

# Delete the template logo placeholder
rm -f docs/resources/images/maya_python_logo.png

Expand All @@ -44,14 +48,24 @@ jobs:
with:
python-version: '3.x'

- name: Install uv
uses: astral-sh/setup-uv@v7

- name: Run initial setup script
run: |
python initial_setup.py

- name: Regenerate uv.lock for the renamed project
run: uv lock

- name: Remove setup files
run: |
rm -f initial_setup.py
rm -f .github/workflows/initial-setup.yml
rm -f .github/workflows/template-tests.yml
rm -f .github/workflows/reusable-template-tests.yml
rm -rf tests/template
rm -f PLAN.md

- name: Commit and push changes
run: |
Expand Down
16 changes: 9 additions & 7 deletions .github/workflows/reusable-build-and-deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- uses: actions/setup-python@v5
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
python-version: 3.x
python-version: '3.13'

- uses: actions/cache@v4
- uses: actions/cache@v6
with:
key: ${{ github.ref }}
path: .cache

- run: |
pip install mkdocs-material mkdocstrings[python]
mkdocs gh-deploy --force
- name: Install dependencies
run: uv sync --only-group docs

- run: uv run --no-sync mkdocs gh-deploy --force
89 changes: 58 additions & 31 deletions .github/workflows/reusable-maya-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,57 @@ on:
workflow_dispatch: # Manual trigger

jobs:
# Cheap gate: skips the disk purge + multi-GB Maya image pull when there are
# no tests, the common case for freshly created template projects.
collect_tests:
runs-on: ubuntu-latest
outputs:
tests_collected: ${{ steps.check_tests.outputs.tests_collected }}
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- uses: actions/checkout@v5

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
python-version: '3.x'
python-version: '3.13'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
run: uv sync --only-group test

- name: Check for tests
id: check_tests
run: |
#!/bin/bash
set +e # Disable exit on error
pytest --collect-only
output=$(uv run --no-sync pytest --collect-only)
exit_code=$?
set -e # Re-enable exit on error

echo "$output"

# Exit 5 is the only non-zero status meaning "nothing to run". A
# collection error (exit 2) also prints "collected 0 items", so
# treating it as an empty suite would skip every test and stay green.
if [ $exit_code -eq 5 ]; then
echo "tests_collected=0" >> $GITHUB_OUTPUT
echo "No tests were collected."
else
collected=$(pytest --collect-only | grep -oP 'collected \K\d+')
echo "tests_collected=$collected" >> $GITHUB_OUTPUT
echo "Collected $collected tests."
exit 0
fi

if [ $exit_code -ne 0 ]; then
echo "::error::pytest --collect-only failed (exit $exit_code)." \
"Tests must import outside Maya: move 'from maya import ...'" \
"into a test or fixture, as tests/maya/conftest.py does."
exit 1
fi

collected=$(echo "$output" | grep -oP 'collected \K\d+')
if [ -z "$collected" ]; then
echo "::error::Could not parse a test count from pytest output."
exit 1
fi
echo "tests_collected=$collected" >> $GITHUB_OUTPUT
echo "Collected $collected tests."

no_tests_found:
needs: collect_tests
if: needs.collect_tests.outputs.tests_collected == '0'
Expand All @@ -58,28 +75,38 @@ jobs:
fail-fast: false

matrix:
# add or remove versions as needed, a job per version will be created
# supported tags here: https://github.com/mottosso/docker-maya/
maya_version: ["2024",]

container: mottosso/maya:${{ matrix.maya_version }} # ty mottosso!
# add/remove versions to match what you target; tags: https://github.com/mottosso/docker-maya/
maya_version: ["2024"]

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Install dependencies
# No job-level `container:`: that pulls before any step can free disk space first,
# and large Maya images can exceed the runner's free disk mid-pull.
- name: Free disk space
run: |
mayapy -m pip install --upgrade pip
mayapy -m pip install pip-tools
pip-compile requirements.in
pip-compile requirements-test.in
mayapy -m pip install --user -r requirements-test.txt
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc \
/usr/local/share/powershell /usr/share/swift /opt/hostedtoolcache/CodeQL
df -h /

- name: Set Maya environment variables
run: |
echo "XDG_RUNTIME_DIR=/var/tmp/runtime-root" >> $GITHUB_ENV
echo "MAYA_DISABLE_ADP=1" >> $GITHUB_ENV
- name: Pull Maya image
run: docker pull mottosso/maya:${{ matrix.maya_version }} # ty mottosso!

- name: Run tests
run: mayapy -m pytest tests
- name: Install test dependencies and run tests in Maya container
run: |
docker run --rm \
-e XDG_RUNTIME_DIR=/var/tmp/runtime-root \
-e MAYA_DISABLE_ADP=1 \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
mottosso/maya:${{ matrix.maya_version }} \
bash -c '
set -e
if command -v curl >/dev/null; then curl -LsSf https://astral.sh/uv/install.sh | sh
else wget -qO- https://astral.sh/uv/install.sh | sh
fi
export PATH="$HOME/.local/bin:$PATH"
uv pip install --python "$(command -v mayapy)" --group test .
mayapy -m pytest tests
'
119 changes: 119 additions & 0 deletions .github/workflows/reusable-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: release

on:
workflow_call:
inputs:
version:
description: "Version to release (e.g. 1.2.3, no leading v)"
required: true
type: string
platforms:
description: "Comma-separated platforms to vendor runtime deps for (win64, linux, mac). Ignored if the project has no runtime deps."
required: false
type: string
default: "win64,linux,mac"
maya_versions:
description: "Comma-separated Maya versions to vendor runtime deps for. Ignored if the project has no runtime deps."
required: false
type: string
default: "2024,2025,2026,2027"
vendor_deps:
description: "Vendor runtime dependencies into the module. Uncheck to ship without them even if the project has dependencies."
required: false
type: boolean
default: true

jobs:
release:
runs-on: ubuntu-latest
concurrency: release
permissions:
contents: write

steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

# Workflow inputs reach the shell through `env:`, never by `${{ }}`
# expansion inside `run:`. Direct expansion pastes the raw string into the
# script before bash parses it, so an input can close the quote and run
# arbitrary commands with this job's `contents: write` token, including in
# the guard step below, which would otherwise validate its input too late
# to matter.
- name: Guard version + tag
env:
VERSION: ${{ inputs.version }}
run: |
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::version '$VERSION' is not x.y.z"
exit 1
fi
if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then
echo "::error::tag v$VERSION already exists"
exit 1
fi

- name: Install uv
uses: astral-sh/setup-uv@v7

- name: Set version in pyproject.toml
env:
VERSION: ${{ inputs.version }}
run: |
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
uv lock # keep uv.lock's root-package version in sync with the bump

# Build and zip BEFORE any push: everything likely to fail (missing
# wheels, bad src/ layout) fails while the remote is untouched, so a
# failed release is a no-op that can simply be re-dispatched.
- name: Build Maya module
env:
VERSION: ${{ inputs.version }}
PLATFORMS: ${{ inputs.platforms }}
MAYA_VERSIONS: ${{ inputs.maya_versions }}
VENDOR_DEPS: ${{ inputs.vendor_deps }}
run: |
args=(--clean --version "$VERSION" --platforms "$PLATFORMS" --maya-versions "$MAYA_VERSIONS")
if [[ "$VENDOR_DEPS" != "true" ]]; then
args+=(--skip-vendor)
fi
python3 scripts/build_module.py "${args[@]}"

- name: Zip module (dot-free name)
# Hyphenate dots: macOS Archive Utility truncates the extracted folder
# name at the first '.'.
env:
VERSION: ${{ inputs.version }}
run: |
NAME=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])")
VERSION_SAFE="${VERSION//./-}"
echo "ZIP_PATH=/tmp/${NAME}-${VERSION_SAFE}.zip" >> "$GITHUB_ENV"
echo "NAME=${NAME}" >> "$GITHUB_ENV"
(cd dist && zip -rq "/tmp/${NAME}-${VERSION_SAFE}.zip" .)

- name: Commit version bump
env:
VERSION: ${{ inputs.version }}
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git add pyproject.toml uv.lock
# Skip the commit on a re-run where the bump already landed, so a
# release that failed after pushing can be re-dispatched.
git diff --cached --quiet || git commit -m "chore: release v$VERSION"
git push

# gh creates the tag along with the release, pointing at the bump
# commit; tag + release are one final step, so the guard's "tag
# exists" check really means "this release already happened".
- name: Create GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.version }}
run: |
gh release create "v$VERSION" "$ZIP_PATH" \
--target "$(git rev-parse HEAD)" \
--title "v$VERSION" \
--generate-notes \
--notes "Download the zip, extract it, and drag \`${NAME}_drag_and_drop_installer.py\` into a Maya viewport (or extract into your Maya modules dir manually)."
Loading