|
| 1 | +name: Build WinPython for 2026-01 Cycle |
| 2 | + |
| 3 | +on: |
| 4 | + workflow_dispatch: |
| 5 | + inputs: |
| 6 | + python_version: |
| 7 | + description: 'Python version to build (3.13, 3.14 or 3.15)' |
| 8 | + required: true |
| 9 | + default: '3.14' |
| 10 | + type: choice |
| 11 | + options: |
| 12 | + - '3.13' |
| 13 | + - '3.14' |
| 14 | + - '3.15' |
| 15 | + |
| 16 | +jobs: |
| 17 | + build-winpython: |
| 18 | + runs-on: windows-latest |
| 19 | + strategy: |
| 20 | + fail-fast: true |
| 21 | + matrix: |
| 22 | + flavor: |
| 23 | + - name: "dot" |
| 24 | + REQUIREMENTS_313: "winpython\\portable\\cycle_2025_05\\requir.64-3_13_11_0dot.txt" |
| 25 | + REQUIREMENTS_314: "winpython\\portable\\cycle_2025_05\\requir.64-3_14_2_0dot.txt" |
| 26 | + REQUIREMENTS_315: "winpython\\portable\\cycle_2025_05\\requir.64-3_15_0_0dot.txt" |
| 27 | + REQUIREMENTS_WHL_313: "" |
| 28 | + REQUIREMENTS_WHL_314: "" |
| 29 | + REQUIREMENTS_WHL_315: "" |
| 30 | + ZIP: "1" |
| 31 | + SEVEN_Z: "0" |
| 32 | + EXE: "1" |
| 33 | + PANDOC: "0" |
| 34 | + WINPYARCHDET: "64" |
| 35 | + - name: "slim" |
| 36 | + REQUIREMENTS_313: "winpython\\portable\\cycle_2025_05\\requir.64-3_13_11_0slim.txt" |
| 37 | + REQUIREMENTS_314: "winpython\\portable\\cycle_2025_05\\requir.64-3_14_2_0slim.txt" |
| 38 | + REQUIREMENTS_315: "" |
| 39 | + REQUIREMENTS_WHL_313: "" |
| 40 | + REQUIREMENTS_WHL_314: "" |
| 41 | + REQUIREMENTS_WHL_315: "" |
| 42 | + ZIP: "0" |
| 43 | + SEVEN_Z: "1" |
| 44 | + EXE: "1" |
| 45 | + PANDOC: "1" |
| 46 | + WINPYARCHDET: "64" |
| 47 | + - name: "whl" |
| 48 | + REQUIREMENTS_313: "winpython\\portable\\cycle_2025_05\\requir.64-3_13_11_0dot.txt" |
| 49 | + REQUIREMENTS_314: "winpython\\portable\\cycle_2025_05\\requir.64-3_14_2_0dot.txt" |
| 50 | + REQUIREMENTS_315: "" |
| 51 | + REQUIREMENTS_WHL_313: "winpython\\portable\\cycle_2025_05\\requir.64-3_13_11_0whl_wheels.txt" |
| 52 | + REQUIREMENTS_WHL_314: "winpython\\portable\\cycle_2025_05\\requir.64-3_14_2_0whl_wheels.txt" |
| 53 | + REQUIREMENTS_WHL_315: "" |
| 54 | + ZIP: "0" |
| 55 | + SEVEN_Z: "1" |
| 56 | + EXE: "0" |
| 57 | + PANDOC: "0" |
| 58 | + WINPYARCHDET: "64" |
| 59 | + - name: "free" |
| 60 | + REQUIREMENTS_313: "" |
| 61 | + REQUIREMENTS_314: "winpython\\portable\\cycle_2025_05\\requir.64-3_14_2_0free.txt" |
| 62 | + REQUIREMENTS_315: "" |
| 63 | + REQUIREMENTS_WHL_313: "" |
| 64 | + REQUIREMENTS_WHL_314: "" |
| 65 | + REQUIREMENTS_WHL_315: "" |
| 66 | + ZIP: "1" |
| 67 | + SEVEN_Z: "0" |
| 68 | + EXE: "1" |
| 69 | + PANDOC: "0" |
| 70 | + WINPYARCHDET: "64F" |
| 71 | + - name: "slimf" |
| 72 | + REQUIREMENTS_313: "" |
| 73 | + REQUIREMENTS_314: "winpython\\portable\\cycle_2025_05\\requir.64-3_14_2_0slimf.txt" |
| 74 | + REQUIREMENTS_315: "" |
| 75 | + REQUIREMENTS_WHL_313: "" |
| 76 | + REQUIREMENTS_WHL_314: "" |
| 77 | + REQUIREMENTS_WHL_315: "" |
| 78 | + ZIP: "1" |
| 79 | + SEVEN_Z: "0" |
| 80 | + EXE: "1" |
| 81 | + PANDOC: "1" |
| 82 | + WINPYARCHDET: "64F" |
| 83 | + |
| 84 | + env: |
| 85 | + PYTHON_VERSION: ${{ github.event.inputs.python_version }} |
| 86 | + WINPYFLAVOR: ${{ matrix.flavor.name }} |
| 87 | + PANDOC: ${{ matrix.flavor.PANDOC }} |
| 88 | + WINPYZIP: ${{ matrix.flavor.ZIP }} |
| 89 | + WINPY7Z: ${{ matrix.flavor.SEVEN_Z }} |
| 90 | + WINPYEXE: ${{ matrix.flavor.EXE }} |
| 91 | + WINPYARCHDET: ${{ matrix.flavor.WINPYARCHDET }} |
| 92 | + # constants |
| 93 | + WINPYARCH: "64" |
| 94 | + my_release_level: "b0" |
| 95 | + dotwheelhouse: dotpython\\wheelhouse\\included.wheels |
| 96 | + pandoc_source: "https://github.com/jgm/pandoc/releases/download/3.1.9/pandoc-3.1.9-windows-x86_64.zip" |
| 97 | + pandoc_sha256: "11eb6dbe5286c9e5edb0cca4412e7d99ec6578ec04158b0b7fe11f7fd96688e5" |
| 98 | + |
| 99 | + steps: |
| 100 | + - name: Checkout repository |
| 101 | + uses: actions/checkout@v4 |
| 102 | + |
| 103 | + - name: Set static and matrix variables based on selected Python version |
| 104 | + shell: bash |
| 105 | + run: | |
| 106 | + PYTHON_VERSION="${{ env.PYTHON_VERSION }}" |
| 107 | + WINPYARCHDET="${{ env.WINPYARCHDET }}" |
| 108 | + |
| 109 | + WINPYVERSION=${PYTHON_VERSION//./} |
| 110 | + echo "WINPYVERSION=$WINPYVERSION" >> $GITHUB_ENV |
| 111 | +
|
| 112 | + # Populate generic per-flavor / per-version selections |
| 113 | + if [ "$PYTHON_VERSION" = "3.13" ]; then |
| 114 | + echo "WINPYREQUIREMENTS=${{ matrix.flavor.REQUIREMENTS_313 }}" >> $GITHUB_ENV |
| 115 | + echo "WINPYREQUIREMENTSwhl=${{ matrix.flavor.REQUIREMENTS_WHL_313 }}" >> $GITHUB_ENV |
| 116 | + WINPYVER2="3.13.11.1" |
| 117 | + elif [ "$PYTHON_VERSION" = "3.14" ]; then |
| 118 | + echo "WINPYREQUIREMENTS=${{ matrix.flavor.REQUIREMENTS_314 }}" >> $GITHUB_ENV |
| 119 | + echo "WINPYREQUIREMENTSwhl=${{ matrix.flavor.REQUIREMENTS_WHL_314 }}" >> $GITHUB_ENV |
| 120 | + WINPYVER2="3.14.2.1" |
| 121 | + elif [ "$PYTHON_VERSION" = "3.15" ]; then |
| 122 | + echo "WINPYREQUIREMENTS=${{ matrix.flavor.REQUIREMENTS_315 }}" >> $GITHUB_ENV |
| 123 | + echo "WINPYREQUIREMENTSwhl=${{ matrix.flavor.REQUIREMENTS_WHL_315 }}" >> $GITHUB_ENV |
| 124 | + echo "WINPYVERSION=315" >> $GITHUB_ENV |
| 125 | + WINPYVER2="3.15.0.1" |
| 126 | + fi |
| 127 | +
|
| 128 | + #direct inference |
| 129 | + CLEAN_VER=${WINPYVER2//./} |
| 130 | + BUILD_LOCATION="WPy64-$CLEAN_VER" |
| 131 | +
|
| 132 | + # write common flavor env vars |
| 133 | + echo "WINPYVER2=$WINPYVER2" >> $GITHUB_ENV |
| 134 | + echo "ARTIFACT_NAME=publish_${PYTHON_VERSION}${{ matrix.flavor.name }}" >> $GITHUB_ENV |
| 135 | + echo "build_location=$BUILD_LOCATION" >> $GITHUB_ENV |
| 136 | + echo "destwheelhouse=${BUILD_LOCATION}\\wheelhouse\\included.wheels" >> $GITHUB_ENV |
| 137 | + echo "WINPYVER=${WINPYVER2}${{ matrix.flavor.name }}${{ env.my_release_level }}" >> $GITHUB_ENV |
| 138 | +
|
| 139 | + # Centralized mapping of python binaries and SHAs by version and arch. |
| 140 | + # 3.13 x64 (GIL) |
| 141 | + if [ "$PYTHON_VERSION" = "3.13" ] && [ "$WINPYARCHDET" = "64" ]; then |
| 142 | + echo "python_source=https://github.com/astral-sh/python-build-standalone/releases/download/20251205/cpython-3.13.11+20251205-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" >> $GITHUB_ENV |
| 143 | + echo "python_sha256=d8a2b5e05ef71fc71f048a6f409d69b940bc5d33da8b112611cfba68fc5b86c3" >> $GITHUB_ENV |
| 144 | + fi |
| 145 | +
|
| 146 | + # 3.14 x64 (GIL) |
| 147 | + if [ "$PYTHON_VERSION" = "3.14" ] && [ "$WINPYARCHDET" = "64" ]; then |
| 148 | + echo "python_source=https://github.com/astral-sh/python-build-standalone/releases/download/20251205/cpython-3.14.2+20251205-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" >> $GITHUB_ENV |
| 149 | + echo "python_sha256=512744d8a86dc6042a712035ada5d87c5e2ce4218f5dbdc74d039cee46e76fb4" >> $GITHUB_ENV |
| 150 | + fi |
| 151 | +
|
| 152 | + # 3.14 X64 (free-threading) |
| 153 | + if [ "$PYTHON_VERSION" = "3.14" ] && [ "$WINPYARCHDET" = "64F" ]; then |
| 154 | + echo "python_source=https://github.com/astral-sh/python-build-standalone/releases/download/20251205/cpython-3.14.2+20251205-x86_64-pc-windows-msvc-freethreaded+pgo-full.tar.zst" >> $GITHUB_ENV |
| 155 | + echo "python_sha256=536cf813857ea566fcfae18a1b7dbcd185385f1dc1f04d5a0951bad235c8fc61" >> $GITHUB_ENV |
| 156 | + fi |
| 157 | +
|
| 158 | + # 3.15 X64 (GIL) |
| 159 | + if [ "$PYTHON_VERSION" = "3.15" ] && [ "$WINPYARCHDET" = "64" ]; then |
| 160 | + echo "python_source=https://github.com/astral-sh/python-build-standalone/releases/download/20251217/cpython-3.15.0a3+20251217-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" >> $GITHUB_ENV |
| 161 | + echo "python_sha256=4cac37170050bb402eaabad84e7ceb4679ec8ffaf43fbea71d62651ca761dcd7" >> $GITHUB_ENV |
| 162 | + fi |
| 163 | +
|
| 164 | + - name: Download, verify and extract python standalone |
| 165 | + if: env.WINPYREQUIREMENTS != '' |
| 166 | + shell: pwsh |
| 167 | + run: | |
| 168 | + Write-Output "Downloading, hash-checking, and extracting $env:python_source" |
| 169 | + curl.exe -L -o "python-3-embed.tar.gz" $env:python_source |
| 170 | +
|
| 171 | + $expectedHash = $env:python_sha256 |
| 172 | + $actualHash = (Get-FileHash -Path "python-3-embed.tar.gz" -Algorithm SHA256).Hash.ToLower() |
| 173 | + if ($actualHash -ne $expectedHash.ToLower()) { |
| 174 | + Write-Error "SHA mismatch: Actual $actualHash doesn't match $expectedHash" |
| 175 | + exit 1 |
| 176 | + } else { Write-Output "Python SHA ok" } |
| 177 | +
|
| 178 | + mkdir dotpythonpre -Force |
| 179 | + mkdir dotpython -Force |
| 180 | + mkdir dotpython/python -Force |
| 181 | + tar -xf python-3-embed.tar.gz -C dotpythonpre |
| 182 | + if (Test-Path dotpythonpre/python/install) { |
| 183 | + Move-Item -Path dotpythonpre/python/install/* -Destination dotpython/python -Force |
| 184 | + } elseif (Test-Path dotpythonpre/python) { |
| 185 | + Move-Item -Path dotpythonpre/python/* -Destination dotpython/python -Force |
| 186 | + } |
| 187 | + |
| 188 | + - name: Copy launchers_final files to dotpython |
| 189 | + if: env.WINPYREQUIREMENTS != '' |
| 190 | + shell: bash |
| 191 | + run: | |
| 192 | + cp -r winpython/portable/launchers_final/* dotpython/ |
| 193 | + mkdir $env:dotwheelhouse |
| 194 | +
|
| 195 | + - name: List dotpython contents (for debugging) |
| 196 | + if: env.WINPYREQUIREMENTS != '' |
| 197 | + shell: pwsh |
| 198 | + run: | |
| 199 | + Get-ChildItem dotpython |
| 200 | + Get-ChildItem dotpython\python |
| 201 | +
|
| 202 | + - name: Prepare WinPython target structure |
| 203 | + if: env.WINPYREQUIREMENTS != '' |
| 204 | + shell: pwsh |
| 205 | + run: | |
| 206 | + New-Item -ItemType Directory -Path $env:build_location |
| 207 | + Get-ChildItem -Path dotpython -Force | Move-Item -Destination $env:build_location -Force |
| 208 | +
|
| 209 | + - name: Download, checking hash and integrating pandoc binary |
| 210 | + if: env.WINPYREQUIREMENTS != '' && env.PANDOC == '1' |
| 211 | + shell: pwsh |
| 212 | + run: | |
| 213 | + $pandocZipPath = "pandoc.zip" |
| 214 | + $tempDir = "pandoc_temp" |
| 215 | + $targetDir = Join-Path $env:build_location "t" |
| 216 | + Write-Host "Downloading Pandoc from $env:pandoc_source" |
| 217 | + curl.exe -L -o $pandocZipPath $env:pandoc_source |
| 218 | + |
| 219 | + $expectedHash = $env:pandoc_sha256.ToLower() |
| 220 | + $actualHash = (Get-FileHash -Path $pandocZipPath -Algorithm SHA256).Hash.ToLower() |
| 221 | +
|
| 222 | + if ($actualHash -ne $expectedHash) { |
| 223 | + Write-Error "Pandoc SHA mismatch: $actualHash vs expected $expectedHash" |
| 224 | + exit 1 |
| 225 | + } else { Write-Output "Pandoc SHA ok" } |
| 226 | +
|
| 227 | + Expand-Archive -Path $pandocZipPath -DestinationPath $tempDir -Force |
| 228 | + New-Item -ItemType Directory -Path $targetDir -Force | Out-Null |
| 229 | + |
| 230 | + Write-Output "Copying pandoc.exe to $targetDir" |
| 231 | + Copy-Item -Path (Join-Path $tempDir "pandoc-3.1.9\pandoc.exe") -Destination $targetDir -Force |
| 232 | +
|
| 233 | + Write-Output "Showing the content of $targetDir" |
| 234 | + Get-ChildItem -Path $targetDir |
| 235 | +
|
| 236 | + Write-Output "Cleaning up temporary files..." |
| 237 | + Remove-Item -Path $tempDir -Recurse -Force |
| 238 | + Remove-Item -Path $pandocZipPath -Force |
| 239 | +
|
| 240 | + - name: Upgrade pip and patch launchers |
| 241 | + if: env.WINPYREQUIREMENTS != '' |
| 242 | + shell: pwsh |
| 243 | + run: | |
| 244 | + & "$env:build_location\python\python.exe" -m pip install --upgrade pip |
| 245 | + & "$env:build_location\python\python.exe" -m pip install --upgrade packaging==25.0 |
| 246 | + & "$env:build_location\python\python.exe" -c "from wppm import wppm;dist=wppm.Distribution();dist.patch_standard_packages('pip', to_movable=True)" |
| 247 | +
|
| 248 | + - name: Write env.ini file |
| 249 | + if: env.WINPYREQUIREMENTS != '' |
| 250 | + shell: pwsh |
| 251 | + run: | |
| 252 | + $destDir = "$env:build_location\scripts" |
| 253 | + echo "WINPYthon_exe=$env:WINPYthon_exe" > env.ini |
| 254 | + echo "WINPYthon_subdirectory_name=$env:WINPYthon_subdirectory_name" >> env.ini |
| 255 | + echo "WINPYVER=$env:WINPYVER" >> env.ini |
| 256 | + echo "WINPYVER2=$env:WINPYVER2" >> env.ini |
| 257 | + echo "WINPYFLAVOR=$env:WINPYFLAVOR" >> env.ini |
| 258 | + echo "WINPYARCH=$env:WINPYARCH" >> env.ini |
| 259 | + Copy-Item -Path "env.ini" -Destination "$destDir\env.ini" |
| 260 | +
|
| 261 | + - name: Download requirements to $env:dotwheelhouse |
| 262 | + if: env.WINPYREQUIREMENTS != '' |
| 263 | + shell: pwsh |
| 264 | + run: | |
| 265 | + & "$env:build_location\python\python.exe" -m pip download --dest $env:dotwheelhouse --no-deps --require-hashes -r $env:WINPYREQUIREMENTS |
| 266 | + if (env.WINPYREQUIREMENTSwhl -ne '') { |
| 267 | + & "$env:build_location\python\python.exe" -m pip download --dest $env:destwheelhouse --no-deps --require-hashes -r $env:WINPYREQUIREMENTSwhl |
| 268 | + } |
| 269 | +
|
| 270 | + - name: Install requirements |
| 271 | + if: env.WINPYREQUIREMENTS != '' |
| 272 | + shell: pwsh |
| 273 | + run: | |
| 274 | + & "$env:build_location\python\python.exe" -m pip install --no-deps --no-index --trusted-host=None --find-links=$env:dotwheelhouse --require-hashes -r $env:WINPYREQUIREMENTS |
| 275 | +
|
| 276 | + - name: Generate Markdown content and pylock file |
| 277 | + if: env.WINPYREQUIREMENTS != '' |
| 278 | + shell: pwsh |
| 279 | + run: | |
| 280 | + mkdir publish_output |
| 281 | +
|
| 282 | + # Ensure unicode for wppm output |
| 283 | + $env:PYTHONIOENCODING="utf-8" |
| 284 | +
|
| 285 | + $destfile_md = "publish_output\WinPython$env:WINPYFLAVOR-$($env:WINPYARCH)bit-$env:WINPYVER2.md" |
| 286 | + & "$env:build_location\python\python.exe" -m wppm -md | Out-File -FilePath $destfile_md -Encoding utf8 |
| 287 | +
|
| 288 | + gc $destfile_md |
| 289 | +
|
| 290 | + & "$env:build_location\python\python.exe" -m pip freeze | Out-File -FilePath dotpython\freeze.txt |
| 291 | + $destfile_pylock = "publish_output\pylock.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_').toml" |
| 292 | + & "$env:build_location\python\python.exe" -m pip lock --no-deps --find-links=$env:dotwheelhouse -r dotpython\freeze.txt -o $destfile_pylock |
| 293 | +
|
| 294 | + $outreq = "publish_output\requir.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_').txt" |
| 295 | + & "$env:build_location\python\python.exe" -X utf8 -c "from wppm import wheelhouse as wh; wh.pylock_to_req(r'$destfile_pylock', r'$outreq')" |
| 296 | +
|
| 297 | + if ($env:WINPYREQUIREMENTSwhl -eq "") { |
| 298 | + Write-Output "No additional wheelhouse requirements." |
| 299 | + } else { |
| 300 | + $destfile_pylockwheel = "publish_output\pylock.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_')_wheels.toml" |
| 301 | + & "$env:build_location\python\python.exe" -m pip lock --no-deps --require-hashes -r $env:WINPYREQUIREMENTSwhl -o $destfile_pylockwheel |
| 302 | +
|
| 303 | + $outreqwheel = "publish_output\requir.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_')_wheels.txt" |
| 304 | + & "$env:build_location\python\python.exe" -X utf8 -c "from wppm import wheelhouse as wh; wh.pylock_to_req(r'$destfile_pylockwheel', r'$outreqwheel')" |
| 305 | + Copy-Item -Path $outreqwheel -Destination (Join-Path $env:build_location "wheelhouse") -Force |
| 306 | + Copy-Item -Path $destfile_pylockwheel -Destination (Join-Path $env:build_location "wheelhouse") -Force |
| 307 | + } |
| 308 | +
|
| 309 | + - name: compress the result (zip / 7z / self-extracting.exe) |
| 310 | + if: env.WINPYREQUIREMENTS != '' |
| 311 | + shell: pwsh |
| 312 | + run: | |
| 313 | + $destfile = "publish_output\WinPython$env:WINPYARCH-$env:WINPYVER.zip" |
| 314 | + if ($env.WINPYZIP -eq '1') { Compress-Archive -Path "$env:build_location" -DestinationPath $destfile } |
| 315 | +
|
| 316 | + $destfile7z = "publish_output\WinPython$env:WINPYARCH-$env:WINPYVER.7z" |
| 317 | + if ($env.WINPY7Z -eq '1') { 7z a $destfile7z $env:build_location } |
| 318 | +
|
| 319 | + $destfileexe = "publish_output\WinPython$env:WINPYARCH-$env:WINPYVER.exe" |
| 320 | + $SFXModulePath = "C:\Program Files\7-Zip\7z.sfx" |
| 321 | + if ($env.WINPYEXE -eq '1') { 7z a -t7z -sfx"$SFXModulePath" $destfileexe $env:build_location } |
| 322 | +
|
| 323 | + - name: generate hashes wppm style |
| 324 | + if: env.WINPYREQUIREMENTS != '' |
| 325 | + shell: pwsh |
| 326 | + run: | |
| 327 | + $DESTFILE="./publish_output/hashes.md" |
| 328 | + Get-ChildItem -Path ".\publish_output\*.*" |
| 329 | + # Get the list of files matching the pattern and pass them as arguments |
| 330 | + $filesToHash = Get-ChildItem -Path ".\publish_output\*64*.*" |
| 331 | + & "$env:build_location\python\python.exe" -c "import sys;from wppm import hash; hash.print_hashes(sys.argv[1:])" @($filesToHash.FullName) | Out-File -FilePath $DESTFILE |
| 332 | + gc $DESTFILE |
| 333 | +
|
| 334 | + - name: Upload artifacts |
| 335 | + if: env.WINPYREQUIREMENTS != '' |
| 336 | + uses: actions/upload-artifact@v4 |
| 337 | + with: |
| 338 | + name: ${{ env.ARTIFACT_NAME }} |
| 339 | + path: publish_output |
| 340 | + retention-days: 66 # keeps artifact for 66 days |
0 commit comments