Workspace Validation #3
This file contains hidden or 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: Workspace Validation | |
| # Pulse-owned workspace-integration validation (Stage 4 Phase B). | |
| # | |
| # This is the "heavy validation CI" that used to live in PyAutoBuild's | |
| # release.yml (find_scripts / generate_notebooks / run_scripts / run_notebooks / | |
| # analyze_results). Pulse owns it now: Build is a pure executor. It runs every | |
| # workspace's scripts AND generated notebooks against the CURRENT source `main` | |
| # of the 5 libraries (source-shadowed via PYTHONPATH), aggregates into the same | |
| # report.json contract, and uploads it as the `workspace-validation-report` | |
| # artifact. Pulse's test_run check reads that run's | |
| # conclusion + timestamp into the authoritative `readiness` verdict (with a | |
| # staleness window). | |
| # | |
| # Run mechanics (script_matrix.py / run_python.py / aggregate_results.py) are | |
| # Build's executor primitives — checked out from PyAutoBuild, not duplicated. | |
| # | |
| # Triggers: weekly schedule (the continuous readiness signal), manual dispatch, | |
| # and workflow_call (so a release path can invoke it on demand). Heavy, so never | |
| # in the <30s pulse tick. | |
| on: | |
| schedule: | |
| - cron: "0 3 * * 1" # Mondays 03:00 UTC | |
| workflow_dispatch: | |
| workflow_call: | |
| permissions: | |
| contents: read | |
| env: | |
| # Workspace pins lag the libraries' source __version__ between releases; | |
| # without this every script fails fast with WorkspaceVersionMismatchError. | |
| # Same bypass run_scripts uses in release.yml. | |
| PYAUTO_SKIP_WORKSPACE_VERSION_CHECK: "1" | |
| jobs: | |
| find_scripts: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.script_matrix.outputs.matrix }} | |
| steps: | |
| - name: Checkout PyAutoBuild (run primitives) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/PyAutoBuild | |
| path: PyAutoBuild | |
| - name: Clone workspaces (paths match script_matrix project names) | |
| run: | | |
| set -e | |
| declare -A WS=( | |
| [autofit]=autofit_workspace [autogalaxy]=autogalaxy_workspace [autolens]=autolens_workspace | |
| [autofit_test]=autofit_workspace_test [autogalaxy_test]=autogalaxy_workspace_test | |
| [autolens_test]=autolens_workspace_test | |
| [howtogalaxy]=HowToGalaxy [howtolens]=HowToLens [howtofit]=HowToFit | |
| ) | |
| for proj in "${!WS[@]}"; do | |
| git clone --depth 1 "https://github.com/PyAutoLabs/${WS[$proj]}" "$proj" | |
| done | |
| - name: Make script matrix | |
| id: script_matrix | |
| run: | | |
| matrix="$(python3 PyAutoBuild/autobuild/script_matrix.py \ | |
| autofit autogalaxy autolens autofit_test autogalaxy_test autolens_test \ | |
| howtogalaxy howtolens howtofit)" | |
| echo "matrix=$matrix" >> "$GITHUB_OUTPUT" | |
| generate_notebooks: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Notebooks exist only for the 6 doc workspaces (the *_test workspaces | |
| # publish none). generate.py just converts scripts → .ipynb (no lib run). | |
| project: | |
| - { name: autofit, workspace: autofit_workspace } | |
| - { name: autogalaxy, workspace: autogalaxy_workspace } | |
| - { name: autolens, workspace: autolens_workspace } | |
| - { name: howtogalaxy, workspace: HowToGalaxy } | |
| - { name: howtolens, workspace: HowToLens } | |
| - { name: howtofit, workspace: HowToFit } | |
| steps: | |
| - name: Checkout PyAutoBuild (generate.py) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/PyAutoBuild | |
| path: PyAutoBuild | |
| - name: Checkout workspace | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/${{ matrix.project.workspace }} | |
| ref: main | |
| path: workspace | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install notebook tooling | |
| run: pip install jupyter ipynb-py-convert PyYAML | |
| - name: Generate notebooks | |
| run: | | |
| export PYTHONPATH="$PYTHONPATH:$(pwd)/PyAutoBuild" | |
| pushd workspace | |
| python3 "$(pwd)/../PyAutoBuild/autobuild/generate.py" "${{ matrix.project.name }}" | |
| - name: Upload generated notebooks | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: notebooks-${{ matrix.project.name }} | |
| path: workspace/notebooks/ | |
| retention-days: 7 | |
| run_scripts: | |
| runs-on: ubuntu-latest | |
| needs: find_scripts | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12"] | |
| project: ${{ fromJSON(needs.find_scripts.outputs.matrix) }} | |
| steps: | |
| - name: Checkout PyAutoBuild (run primitives) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/PyAutoBuild | |
| path: PyAutoBuild | |
| - name: Resolve workspace repo for ${{ matrix.project.name }} | |
| id: ws | |
| run: | | |
| case "${{ matrix.project.name }}" in | |
| autofit) echo "repo=autofit_workspace" >> "$GITHUB_OUTPUT" ;; | |
| autogalaxy) echo "repo=autogalaxy_workspace" >> "$GITHUB_OUTPUT" ;; | |
| autolens) echo "repo=autolens_workspace" >> "$GITHUB_OUTPUT" ;; | |
| autofit_test) echo "repo=autofit_workspace_test" >> "$GITHUB_OUTPUT" ;; | |
| autogalaxy_test) echo "repo=autogalaxy_workspace_test" >> "$GITHUB_OUTPUT" ;; | |
| autolens_test) echo "repo=autolens_workspace_test" >> "$GITHUB_OUTPUT" ;; | |
| howtogalaxy) echo "repo=HowToGalaxy" >> "$GITHUB_OUTPUT" ;; | |
| howtolens) echo "repo=HowToLens" >> "$GITHUB_OUTPUT" ;; | |
| howtofit) echo "repo=HowToFit" >> "$GITHUB_OUTPUT" ;; | |
| *) echo "repo=autolens_workspace_test" >> "$GITHUB_OUTPUT" ;; | |
| esac | |
| - name: Checkout workspace | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/${{ steps.ws.outputs.repo }} | |
| ref: main | |
| path: workspace | |
| - name: Checkout libraries (current main; source-shadowed via PYTHONPATH) | |
| run: | | |
| set -e | |
| for lib in PyAutoConf PyAutoArray PyAutoFit PyAutoGalaxy PyAutoLens; do | |
| git clone --depth 1 "https://github.com/PyAutoLabs/$lib" "libs/$lib" | |
| done | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install third-party deps (libs run from source) | |
| run: | | |
| # Pull the full transitive dependency set (matplotlib/numba/jax/…) by | |
| # installing the published autolens[optional]; the source checkouts | |
| # below shadow the PyAuto packages via PYTHONPATH. | |
| pip install "autolens[optional]" | |
| pip install "jax>=0.7,<0.11" "jaxlib>=0.7,<0.11" | |
| # nufftax backs the interferometer TransformerNUFFT and is NOT pulled | |
| # transitively by autolens[optional]; without it every interferometer | |
| # simulator (and its downstream scripts) fails fast. Matches the pin | |
| # PyAutoArray declares (jax 0.10.x + nufftax 0.4.x is the tested combo). | |
| pip install "nufftax>=0.4.0,<0.5.0" | |
| - name: Run Python scripts | |
| run: | | |
| LIBS="$(pwd)/libs" | |
| export PYTHONPATH="$LIBS/PyAutoConf:$LIBS/PyAutoArray:$LIBS/PyAutoFit:$LIBS/PyAutoGalaxy:$LIBS/PyAutoLens:$(pwd)/PyAutoBuild:$PYTHONPATH" | |
| pushd workspace | |
| python3 "$(pwd)/../PyAutoBuild/autobuild/run_python.py" \ | |
| "${{ matrix.project.name }}" "scripts/${{ matrix.project.directory }}" \ | |
| --report-dir test-results | |
| - name: Upload script results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: results-scripts-${{ matrix.project.name }}-${{ matrix.project.directory }} | |
| path: workspace/test-results/ | |
| retention-days: 30 | |
| run_notebooks: | |
| runs-on: ubuntu-latest | |
| needs: [find_scripts, generate_notebooks] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12"] | |
| project: ${{ fromJSON(needs.find_scripts.outputs.matrix) }} | |
| steps: | |
| - name: Gate (*_test workspaces have no notebooks) | |
| id: gate | |
| run: | | |
| if [[ "${{ matrix.project.name }}" == *_test ]]; then | |
| echo "run=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Checkout PyAutoBuild (run primitives) | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/PyAutoBuild | |
| path: PyAutoBuild | |
| - name: Resolve workspace repo | |
| if: steps.gate.outputs.run == 'true' | |
| id: ws | |
| run: | | |
| case "${{ matrix.project.name }}" in | |
| autofit) echo "repo=autofit_workspace" >> "$GITHUB_OUTPUT" ;; | |
| autogalaxy) echo "repo=autogalaxy_workspace" >> "$GITHUB_OUTPUT" ;; | |
| autolens) echo "repo=autolens_workspace" >> "$GITHUB_OUTPUT" ;; | |
| howtogalaxy) echo "repo=HowToGalaxy" >> "$GITHUB_OUTPUT" ;; | |
| howtolens) echo "repo=HowToLens" >> "$GITHUB_OUTPUT" ;; | |
| howtofit) echo "repo=HowToFit" >> "$GITHUB_OUTPUT" ;; | |
| *) echo "repo=autolens_workspace" >> "$GITHUB_OUTPUT" ;; | |
| esac | |
| - name: Checkout workspace | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/${{ steps.ws.outputs.repo }} | |
| ref: main | |
| path: workspace | |
| - name: Checkout libraries (source-shadowed via PYTHONPATH) | |
| if: steps.gate.outputs.run == 'true' | |
| run: | | |
| set -e | |
| for lib in PyAutoConf PyAutoArray PyAutoFit PyAutoGalaxy PyAutoLens; do | |
| git clone --depth 1 "https://github.com/PyAutoLabs/$lib" "libs/$lib" | |
| done | |
| - name: Download generated notebooks | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: notebooks-${{ matrix.project.name }} | |
| path: workspace/notebooks/ | |
| - name: Set up Python ${{ matrix.python-version }} | |
| if: steps.gate.outputs.run == 'true' | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install deps (libs from source) + jupyter | |
| if: steps.gate.outputs.run == 'true' | |
| run: | | |
| pip install "autolens[optional]" | |
| pip install "jax>=0.7,<0.11" "jaxlib>=0.7,<0.11" | |
| pip install "nufftax>=0.4.0,<0.5.0" | |
| pip install jupyter ipynb-py-convert | |
| - name: Run notebooks | |
| if: steps.gate.outputs.run == 'true' | |
| run: | | |
| LIBS="$(pwd)/libs" | |
| export PYTHONPATH="$LIBS/PyAutoConf:$LIBS/PyAutoArray:$LIBS/PyAutoFit:$LIBS/PyAutoGalaxy:$LIBS/PyAutoLens:$(pwd)/PyAutoBuild:$PYTHONPATH" | |
| pushd workspace | |
| python3 "$(pwd)/../PyAutoBuild/autobuild/run.py" \ | |
| "${{ matrix.project.name }}" "notebooks/${{ matrix.project.directory }}" \ | |
| --report-dir test-results | |
| - name: Upload notebook results | |
| if: always() && steps.gate.outputs.run == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: results-notebooks-${{ matrix.project.name }}-${{ matrix.project.directory }} | |
| path: workspace/test-results/ | |
| retention-days: 30 | |
| analyze: | |
| runs-on: ubuntu-latest | |
| needs: [run_scripts, run_notebooks] | |
| if: always() | |
| steps: | |
| - name: Checkout PyAutoBuild (aggregate_results) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: PyAutoLabs/PyAutoBuild | |
| path: PyAutoBuild | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Download all result artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: all-results | |
| pattern: results-* | |
| merge-multiple: true | |
| - name: Aggregate into report.json | |
| run: | | |
| export PYTHONPATH="$PYTHONPATH:$(pwd)/PyAutoBuild" | |
| python3 PyAutoBuild/autobuild/aggregate_results.py all-results/ \ | |
| --output report.json --markdown report.md | |
| - name: Post summary | |
| if: always() | |
| run: cat report.md >> "$GITHUB_STEP_SUMMARY" || true | |
| - name: Upload report (consumed by pyauto-pulse readiness) | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: workspace-validation-report | |
| path: | | |
| report.json | |
| report.md | |
| retention-days: 90 | |
| - name: Fail the run if validation is not ready | |
| run: | | |
| python3 -c "import json,sys; sys.exit(0 if json.load(open('report.json')).get('ready') else 1)" |