name: Build Windows Portable (Python Embarqué) on: workflow_dispatch: push: tags: - 'v*' jobs: build-portable: runs-on: windows-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Python 3.12 uses: actions/setup-python@v5 with: python-version: '3.12' cache: pip - name: Install deps (system Python) run: pip install -r requirements.txt - name: Download NER model run: | python -c "from ner_manager_onnx import NerModelManager; m=NerModelManager(cache_dir='models'); m.load('cmarkea/distilcamembert-base-ner'); print('Model OK:', m.is_loaded()); m.unload()" - name: Build portable distribution shell: pwsh run: | $ErrorActionPreference = "Stop" # --- Detect Python version --- $pyFull = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')" $pyShort = python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')" Write-Host "Python: $pyFull (short: $pyShort)" # --- Paths --- $dist = "dist\Pseudonymisation" $pyDir = "$dist\python" $appDir = "$dist\app" New-Item -ItemType Directory -Force $pyDir, $appDir | Out-Null # --- Download Python embeddable --- $url = "https://www.python.org/ftp/python/$pyFull/python-$pyFull-embed-amd64.zip" Write-Host "Downloading $url ..." Invoke-WebRequest -Uri $url -OutFile embed.zip -UseBasicParsing Expand-Archive embed.zip -DestinationPath $pyDir -Force # --- Configure _pth (enable site-packages + app path) --- $pthContent = @("python$pyShort.zip", ".", "..\app", "Lib\site-packages", "import site") $pthContent | Set-Content "$pyDir\python$pyShort._pth" -Encoding ASCII Write-Host "_pth configured" # --- Install pip in embeddable Python --- Invoke-WebRequest -Uri "https://bootstrap.pypa.io/get-pip.py" -OutFile get-pip.py -UseBasicParsing & "$pyDir\python.exe" get-pip.py --no-warn-script-location 2>&1 Write-Host "pip installed" # --- Install requirements in embeddable Python --- & "$pyDir\python.exe" -m pip install -r requirements.txt --no-warn-script-location 2>&1 Write-Host "Requirements installed" # --- Copy tkinter from full Python installation --- $fullPy = Split-Path (Get-Command python.exe).Source Write-Host "Full Python at: $fullPy" # DLLs Copy-Item "$fullPy\DLLs\_tkinter.pyd" $pyDir -Force Copy-Item "$fullPy\DLLs\tcl86t.dll" $pyDir -Force Copy-Item "$fullPy\DLLs\tk86t.dll" $pyDir -Force # tcl/tk library files if (Test-Path "$fullPy\tcl") { Copy-Item -Recurse "$fullPy\tcl" "$pyDir\tcl" -Force } # tkinter Python package New-Item -ItemType Directory -Force "$pyDir\Lib" | Out-Null Copy-Item -Recurse "$fullPy\Lib\tkinter" "$pyDir\Lib\tkinter" -Force Write-Host "tkinter copied" # --- Copy application files --- $appFiles = @( "Pseudonymisation_Gui_V5.py", "anonymizer_core_refactored_onnx.py", "ner_manager_onnx.py", "eds_pseudo_manager.py" ) foreach ($f in $appFiles) { Copy-Item $f $appDir -Force Write-Host " Copied $f" } Copy-Item -Recurse "config" "$appDir\config" -Force Copy-Item -Recurse "models" "$appDir\models" -Force Write-Host "App files copied" # --- Create launcher .bat --- $bat = @( '@echo off', 'cd /d "%~dp0"', 'start "" "%~dp0python\pythonw.exe" "%~dp0app\Pseudonymisation_Gui_V5.py"' ) $bat | Set-Content "$dist\Pseudonymisation.bat" -Encoding ASCII Write-Host "Launcher created" # --- Size report --- $size = (Get-ChildItem -Recurse $dist | Measure-Object -Property Length -Sum).Sum / 1MB Write-Host ("Distribution size: {0:N0} MB" -f $size) - name: Create zip shell: pwsh run: | Compress-Archive -Path "dist\Pseudonymisation" -DestinationPath "Pseudonymisation-Windows-Portable.zip" $zipSize = (Get-Item "Pseudonymisation-Windows-Portable.zip").Length / 1MB Write-Host ("ZIP size: {0:N0} MB" -f $zipSize) - name: Upload artifact uses: actions/upload-artifact@v4 with: name: Pseudonymisation-Windows-Portable path: Pseudonymisation-Windows-Portable.zip retention-days: 30 - name: Upload to release (on tag) if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: Pseudonymisation-Windows-Portable.zip