From bc531d392adc87d57d407db1c3b8e86040cd5a21 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sun, 1 Mar 2026 16:20:41 -0800 Subject: [PATCH] feat: add Python wheel release pipeline and TestPyPI publish fixes --- .github/workflows/Release.yml | 101 +++++++++++++++++++++++++++++++++- python/pyproject.toml | 5 +- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 45ca836a..d239a7d1 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -11,19 +11,97 @@ defaults: env: DOTNET_VERSION: "10.x" + PYTHON_VERSION: "3.12" DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_NOLOGO: true jobs: + # ============================================================================== + # Build Python wheel (win-amd64) + # ============================================================================== + Build_Python_Wheel: + runs-on: windows-latest + timeout-minutes: 20 + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Resolve version + id: version + shell: pwsh + run: | + $version = (Get-Content "lib/VERSION" -Raw).Trim() + if ([string]::IsNullOrWhiteSpace($version)) { + Write-Error "lib/VERSION is empty" + exit 1 + } + "value=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "Using version: $version" + + - name: Restore dependencies + run: dotnet restore + + - name: Publish python native wrapper (win-x64) + shell: pwsh + run: | + dotnet publish ./python/python.csproj ` + -c Release ` + -r win-x64 ` + --self-contained true ` + -o ./python/quantalib/native/win_amd64 ` + -p:ContinuousIntegrationBuild=true + if (-not (Test-Path "./python/quantalib/native/win_amd64/quantalib_native.dll")) { + Write-Error "Missing quantalib_native.dll" + exit 1 + } + + - name: Set wheel version for development + if: github.ref_name != 'main' + shell: pwsh + run: | + $baseVersion = (Get-Content "lib/VERSION" -Raw).Trim() + $devVersion = "$baseVersion.dev$env:GITHUB_RUN_NUMBER" + Set-Content -Path "lib/VERSION" -Value $devVersion -NoNewline + "Using dev wheel version: $devVersion" + + - name: Build wheel + shell: pwsh + run: | + python -m pip install --upgrade pip build hatchling + python -m build --wheel ./python + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: python-wheel-win-amd64 + path: python/dist/*.whl + retention-days: 7 + # ============================================================================== # Publish Package (manual trigger only) # ============================================================================== Publish_Package: + needs: [Build_Python_Wheel] runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: write + id-token: write steps: - name: Checkout code uses: actions/checkout@v4 @@ -55,10 +133,10 @@ jobs: run: dotnet restore - name: Build (warnings as errors) - run: dotnet build ./QuanTAlib.slnx --configuration Release --no-restore --nologo /warnaserror -v:diag + run: dotnet build ./QuanTAlib.slnx --configuration Release --no-restore --nologo /warnaserror - name: Test (core) - run: dotnet test ./lib/QuanTAlib.Tests.csproj --configuration Release --no-build --nologo -v:diag + run: dotnet test ./lib/QuanTAlib.Tests.csproj --configuration Release --no-build --nologo - name: Test (quantower) run: dotnet test ./quantower/Quantower.Tests.csproj --configuration Release --no-build --nologo @@ -73,11 +151,18 @@ jobs: ls -lh lib/bin/Release/*.nupkg || true + - name: Download python wheel artifact + uses: actions/download-artifact@v4 + with: + name: python-wheel-win-amd64 + path: python-wheel + - name: Collect release assets run: | mkdir -p release-assets cp -v lib/bin/Release/*.nupkg release-assets/ + cp -v python-wheel/*.whl release-assets/ if [ ! -f "quantower/bin/Release/quantower.dll" ]; then echo "quantower.dll not found at quantower/bin/Release/quantower.dll" @@ -120,8 +205,19 @@ jobs: --prerelease \ --target ${{ github.sha }} \ release-assets/*.nupkg \ + release-assets/*.whl \ release-assets/quantower_quantalib.dll + - name: Publish wheel to TestPyPI + if: steps.release_type.outputs.type == 'development' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: python-wheel + skip-existing: true + verbose: true + attestations: false + - name: Push to MyGet if: steps.release_type.outputs.type == 'development' continue-on-error: true @@ -147,6 +243,7 @@ jobs: --title "Release ${{ steps.version.outputs.value }}" \ --generate-notes \ release-assets/*.nupkg \ + release-assets/*.whl \ release-assets/quantower_quantalib.dll - name: Push to NuGet diff --git a/python/pyproject.toml b/python/pyproject.toml index db70a86c..3641137c 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -26,10 +26,7 @@ filterwarnings = [ [tool.hatch.version] source = "regex" path = "../lib/VERSION" -pattern = "(?P\\d+\\.\\d+\\.\\d+)" +pattern = "(?P\\d+\\.\\d+\\.\\d+(?:\\.dev\\d+)?)" [tool.hatch.build.targets.wheel] packages = ["quantalib"] - -[tool.hatch.build.targets.wheel.force-include] -"quantalib/native" = "quantalib/native" \ No newline at end of file