diff --git a/.github/workflows/build-portable.yml b/.github/workflows/build-portable.yml new file mode 100644 index 0000000..f456a6b --- /dev/null +++ b/.github/workflows/build-portable.yml @@ -0,0 +1,132 @@ +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