nns-r-package-updated #39
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: Regenerate R parity cache | |
| on: | |
| repository_dispatch: | |
| types: [nns-r-package-updated] | |
| workflow_dispatch: | |
| inputs: | |
| r_commit: | |
| description: Exact commit in OVVO-Financial/NNS containing the package archives | |
| required: true | |
| type: string | |
| r_version: | |
| description: NNS package version (13.0 or newer) | |
| required: true | |
| type: string | |
| source_tarball: | |
| description: Source package filename at the R repository root | |
| required: true | |
| type: string | |
| windows_binary: | |
| description: Windows binary filename at the R repository root | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: r-parity-cache-${{ github.event.client_payload.r_version || inputs.r_version }} | |
| cancel-in-progress: true | |
| jobs: | |
| regenerate-cache: | |
| runs-on: ubuntu-latest | |
| env: | |
| RGL_USE_NULL: 'true' | |
| R_KEEP_PKG_SOURCE: 'yes' | |
| steps: | |
| - name: Check out NNS-python | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve and validate package payload | |
| id: package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| r_repo="${{ github.event.client_payload.r_repo }}" | |
| r_commit="${{ github.event.client_payload.r_commit }}" | |
| r_version="${{ github.event.client_payload.r_version }}" | |
| source_tarball="${{ github.event.client_payload.source_tarball }}" | |
| windows_binary="${{ github.event.client_payload.windows_binary }}" | |
| else | |
| r_repo="OVVO-Financial/NNS" | |
| r_commit="${{ inputs.r_commit }}" | |
| r_version="${{ inputs.r_version }}" | |
| source_tarball="${{ inputs.source_tarball }}" | |
| windows_binary="${{ inputs.windows_binary }}" | |
| fi | |
| test "${r_repo}" = "OVVO-Financial/NNS" || { | |
| echo "Unsupported upstream repository: ${r_repo}" | |
| exit 1 | |
| } | |
| printf '%s' "${r_commit}" | grep -Eq '^[0-9a-fA-F]{40}$' || { | |
| echo "r_commit must be a full 40-character commit SHA." | |
| exit 1 | |
| } | |
| printf '%s' "${source_tarball}" | grep -Eq '^NNS_[0-9][0-9A-Za-z.-]*\.(tar\.gz|tgz)$' || { | |
| echo "Invalid source package filename: ${source_tarball}" | |
| exit 1 | |
| } | |
| printf '%s' "${windows_binary}" | grep -Eq '^NNS_[0-9][0-9A-Za-z.-]*\.zip$' || { | |
| echo "Invalid Windows package filename: ${windows_binary}" | |
| exit 1 | |
| } | |
| python - "${r_version}" "${source_tarball}" "${windows_binary}" <<'PY' | |
| import re | |
| import sys | |
| version, source, binary = sys.argv[1:] | |
| match = re.fullmatch(r"(\d+)\.(\d+)(?:\.\d+)?(?:[-+].*)?", version) | |
| if match is None: | |
| raise SystemExit(f"Unsupported NNS package version: {version!r}") | |
| if (int(match.group(1)), int(match.group(2))) < (13, 0): | |
| raise SystemExit(f"NNS {version} is below the supported minimum 13.0") | |
| expected = f"NNS_{version}" | |
| if not source.startswith(expected + ".") or binary != expected + ".zip": | |
| raise SystemExit("Package filenames do not match the dispatched NNS version") | |
| PY | |
| { | |
| echo "r_repo=${r_repo}" | |
| echo "r_commit=${r_commit}" | |
| echo "r_version=${r_version}" | |
| echo "source_tarball=${source_tarball}" | |
| echo "windows_binary=${windows_binary}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Set up R | |
| uses: r-lib/actions/setup-r@v2 | |
| with: | |
| r-version: release | |
| use-public-rspm: true | |
| - name: Install Linux system dependencies | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libgsl-dev libjpeg-dev libpng-dev libtiff5-dev libfreetype6-dev \ | |
| libharfbuzz-dev libfribidi-dev xorg-dev | |
| - name: Download exact R package archives | |
| shell: bash | |
| env: | |
| R_REPO: ${{ steps.package.outputs.r_repo }} | |
| R_COMMIT: ${{ steps.package.outputs.r_commit }} | |
| SOURCE_TARBALL: ${{ steps.package.outputs.source_tarball }} | |
| WINDOWS_BINARY: ${{ steps.package.outputs.windows_binary }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p upstream/package | |
| base_url="https://raw.githubusercontent.com/${R_REPO}/${R_COMMIT}" | |
| curl --fail --location --retry 3 \ | |
| "${base_url}/${SOURCE_TARBALL}" \ | |
| --output "upstream/${SOURCE_TARBALL}" | |
| curl --fail --location --retry 3 \ | |
| "${base_url}/${WINDOWS_BINARY}" \ | |
| --output "upstream/${WINDOWS_BINARY}" | |
| tar -xzf "upstream/${SOURCE_TARBALL}" -C upstream/package --strip-components=1 | |
| test -f upstream/package/DESCRIPTION | |
| - name: Install R package dependencies | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| Rscript -e "options(repos=c(CRAN='https://cloud.r-project.org')); install.packages(c('remotes','jsonlite')); remotes::install_deps('upstream/package', dependencies=NA, upgrade='never')" | |
| - name: Install exact NNS source package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python scripts/install_local_r_nns.py \ | |
| --source "upstream/${{ steps.package.outputs.source_tarball }}" \ | |
| --expected-version "${{ steps.package.outputs.r_version }}" | |
| - name: Install Python package and test tools | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python -m pip install -U pip | |
| python -m pip install build scikit-build-core nanobind pytest ruff mypy "numpy<2.5" scipy | |
| python -m pip install hypothesis pytest-benchmark pytest-xdist | |
| python -m pip install -e . --force-reinstall --no-deps | |
| - name: Regenerate complete live-R parity cache | |
| shell: bash | |
| run: python scripts/regenerate_r_cache.py --fresh --allow-ci | |
| - name: Verify committed-cache mode | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| rm -f tests/_r_cache.json.bak tests/_r_cache.lock | |
| python -m pytest -q tests/invariants | |
| NNS_R_CACHE_ONLY=1 python -m pytest -q tests/parity | |
| - name: Confirm cache version | |
| shell: bash | |
| env: | |
| EXPECTED_VERSION: ${{ steps.package.outputs.r_version }} | |
| run: | | |
| python - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| payload = json.loads(Path('tests/_r_cache.json').read_text(encoding='utf-8')) | |
| actual = payload.get('nns_version') | |
| expected = os.environ['EXPECTED_VERSION'] | |
| if actual != expected: | |
| raise SystemExit(f'cache version {actual!r} != dispatched version {expected!r}') | |
| entries = payload.get('entries') | |
| if not isinstance(entries, dict) or not entries: | |
| raise SystemExit('regenerated cache contains no entries') | |
| print(f'Validated {len(entries)} cache entries for NNS {actual}.') | |
| PY | |
| - name: Open or update R cache regeneration PR | |
| uses: peter-evans/create-pull-request@v6 | |
| with: | |
| token: ${{ secrets.OVVO_SYNC_TOKEN || github.token }} | |
| branch: automation/r-cache-nns-${{ steps.package.outputs.r_version }} | |
| delete-branch: true | |
| commit-message: Regenerate R parity cache for NNS ${{ steps.package.outputs.r_version }} | |
| title: Regenerate R parity cache for NNS ${{ steps.package.outputs.r_version }} | |
| body: | | |
| Automatic parity-cache refresh from the R source of truth. | |
| - R repository: `${{ steps.package.outputs.r_repo }}` | |
| - R commit: `${{ steps.package.outputs.r_commit }}` | |
| - NNS version: `${{ steps.package.outputs.r_version }}` | |
| - Source package: `${{ steps.package.outputs.source_tarball }}` | |
| - Windows binary verified: `${{ steps.package.outputs.windows_binary }}` | |
| The workflow installed the exact source tarball, regenerated all live-R parity fixtures, then reran invariants and the complete parity suite in committed-cache-only mode. | |
| add-paths: | | |
| tests/_r.py | |
| tests/_r_cache.json |