update the rule engine #27
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: Build SmartEye | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| push: | |
| branches: | |
| - main | |
| - dev | |
| jobs: | |
| validate: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python 3.12 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| architecture: "x64" | |
| cache: "pip" | |
| cache-dependency-path: | | |
| requirements.txt | |
| requirements-dev.txt | |
| - name: Install validation dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements-dev.txt | |
| - name: Ruff checks | |
| run: | | |
| python -m ruff check backend frontend utils scripts main.py tests --select F401,F821,F823,F811,F841,E722,B006,B007,B008,B023,B904,RUF012,RUF034 | |
| - name: Pytest | |
| run: | | |
| python -m pytest -q | |
| build: | |
| if: github.event_name == 'workflow_dispatch' | |
| needs: validate | |
| runs-on: windows-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python 3.12 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| architecture: "x64" | |
| cache: "pip" | |
| cache-dependency-path: requirements.txt | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Remove duplicate top-level insightface path | |
| shell: pwsh | |
| run: | | |
| $pythonRoot = (python -c "import sys; print(sys.prefix)").Trim() | |
| $topLevelInsightface = Join-Path $pythonRoot "insightface" | |
| if (Test-Path $topLevelInsightface) { | |
| Write-Host "Removing duplicate package path: $topLevelInsightface" | |
| Remove-Item -Path $topLevelInsightface -Recurse -Force | |
| Write-Host "Done." | |
| } else { | |
| Write-Host "No duplicate top-level insightface path detected" | |
| } | |
| python -c "import insightface, sys; print('Python prefix:', sys.prefix); print('insightface module:', insightface.__file__)" | |
| - name: Ensure setuptools is installed for Nuitka | |
| run: | | |
| pip install --upgrade setuptools | |
| pip show setuptools | |
| - name: Collect PySide6 plugins | |
| shell: pwsh | |
| run: | | |
| $p = (python -c "import PySide6, os; print(os.path.join(os.path.dirname(PySide6.__file__), 'plugins'))").Trim() | |
| if (Test-Path $p) { | |
| Write-Host "Copying PySide6 plugins from $p" | |
| Remove-Item -Path "$PWD\frontend\pyside6_plugins" -Recurse -Force -ErrorAction SilentlyContinue | |
| Copy-Item -Path $p -Destination "$PWD\frontend\pyside6_plugins" -Recurse -Force | |
| Write-Host "PySide6 plugins copied to frontend/pyside6_plugins" | |
| } else { | |
| Write-Host "PySide6 plugins path not found: $p" | |
| } | |
| - name: Download model bundle (optional) | |
| shell: pwsh | |
| run: | | |
| if (-not (Test-Path "data/models")) { | |
| if ($env:MODEL_BUNDLE_URL) { | |
| Write-Host "MODEL_BUNDLE_URL set - downloading model bundle..." | |
| Invoke-WebRequest -Uri $env:MODEL_BUNDLE_URL -OutFile models.zip -UseBasicParsing | |
| Expand-Archive models.zip -DestinationPath data/models | |
| Remove-Item models.zip -Force | |
| Write-Host "Model bundle downloaded and extracted to data/models" | |
| } else { | |
| Write-Host "MODEL_BUNDLE_URL not set; skipping model download" | |
| } | |
| } else { | |
| Write-Host "data/models already exists - skipping download" | |
| } | |
| - name: Cache Nuitka build cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: C:/Users/runneradmin/AppData/Local/Nuitka/Nuitka | |
| key: nuitka-${{ runner.os }}-${{ hashFiles('requirements.txt', 'main.py') }} | |
| restore-keys: | | |
| nuitka-${{ runner.os }}- | |
| - name: Stage scientific runtime libs for packaging | |
| shell: pwsh | |
| run: | | |
| @' | |
| import importlib.util | |
| import pathlib | |
| import shutil | |
| root = pathlib.Path(".").resolve() | |
| out_root = root / "build_assets" | |
| out_root.mkdir(exist_ok=True) | |
| def stage_lib_dir(pkg_name: str): | |
| spec = importlib.util.find_spec(pkg_name) | |
| if not spec or not spec.origin: | |
| print(f"[warn] could not resolve {pkg_name}") | |
| return | |
| pkg_dir = pathlib.Path(spec.origin).resolve().parent | |
| libs_dir = pkg_dir.parent / f"{pkg_name}.libs" | |
| if not libs_dir.exists(): | |
| print(f"[info] no {pkg_name}.libs at {libs_dir}") | |
| return | |
| dst = out_root / f"{pkg_name}.libs" | |
| if dst.exists(): | |
| shutil.rmtree(dst) | |
| shutil.copytree(libs_dir, dst) | |
| print(f"[ok] staged {libs_dir} -> {dst}") | |
| stage_lib_dir("scipy") | |
| stage_lib_dir("numpy") | |
| '@ | python - | |
| Get-ChildItem -Path "$PWD\build_assets" -Directory -ErrorAction SilentlyContinue | ForEach-Object { | |
| Write-Host "Staged runtime dir: $($_.FullName)" | |
| } | |
| - name: Check for model bundle | |
| id: check_models | |
| shell: pwsh | |
| run: | | |
| $hasModels = (Get-ChildItem -Path "data/models" -ErrorAction SilentlyContinue | Measure-Object).Count -gt 0 | |
| "has_models=$($hasModels.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Check for object model file | |
| id: check_obj | |
| shell: pwsh | |
| run: | | |
| $hasObj = Test-Path "data/models/Obj-Detection.onnx" | |
| "has_obj=$($hasObj.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Build with Nuitka | |
| uses: Nuitka/Nuitka-Action@main | |
| with: | |
| nuitka-version: 2.7.11 | |
| script-name: main.py | |
| mode: standalone | |
| output-dir: build | |
| output-file: SmartEye | |
| jobs: "2" | |
| lto: "yes" | |
| windows-console-mode: disable | |
| windows-icon-from-ico: frontend/assets/icons/icon.ico | |
| enable-plugins: pyside6 | |
| show-scons: true | |
| include-package: | | |
| psutil | |
| PySide6 | |
| shiboken6 | |
| reportlab | |
| requests | |
| pyqtgraph | |
| onnx | |
| onnxruntime | |
| insightface | |
| GPUtil | |
| wmi | |
| backend | |
| frontend | |
| utils | |
| streamlink | |
| numpy | |
| scipy | |
| skimage | |
| albumentations | |
| include-module: | | |
| scipy._cyutility | |
| scipy._lib._ccallback_c | |
| include-package-data: | | |
| reportlab | |
| pyqtgraph | |
| insightface | |
| onnxruntime | |
| streamlink | |
| numpy | |
| scipy | |
| skimage | |
| albumentations | |
| include-data-dir: | | |
| frontend/assets=frontend/assets | |
| frontend/pyside6_plugins=PySide6/plugins | |
| build_assets/scipy.libs=scipy.libs | |
| build_assets/numpy.libs=numpy.libs | |
| include-data-files: | | |
| app_info.json=app_info.json | |
| backend/database/schema.sql=backend/database/schema.sql | |
| ${{ steps.check_obj.outputs.has_obj == 'true' && 'data/models/Obj-Detection.onnx=data/models/Obj-Detection.onnx' || '' }} | |
| nofollow-import-to: | | |
| numpy.distutils | |
| numpy.random.tests | |
| numpy.random.tests.test_extending | |
| scipy.tests | |
| scipy.stats.tests | |
| scipy.stats.tests.test_censored_data | |
| scipy.stats.tests.test_continuous | |
| sympy.polys.polyquinticconst | |
| trio.testing | |
| onnxruntime.training | |
| onnxruntime.tools | |
| onnx.backend.test | |
| cv2.samples | |
| pyqtgraph.tests | |
| pyqtgraph.opengl | |
| pyqtgraph.exporters | |
| PySide6.QtWebEngine | |
| PySide6.QtWebEngineWidgets | |
| PySide6.QtWebEngineCore | |
| PySide6.Qt3DCore | |
| PySide6.Qt3DRender | |
| PySide6.Qt3DInput | |
| PySide6.Qt3DLogic | |
| PySide6.Qt3DAnimation | |
| PySide6.Qt3DExtras | |
| PySide6.QtCharts | |
| PySide6.QtDataVisualization | |
| PySide6.QtLocation | |
| PySide6.QtPositioning | |
| PySide6.QtRemoteObjects | |
| PySide6.QtSensors | |
| PySide6.QtSerialPort | |
| PySide6.QtTextToSpeech | |
| PySide6.QtBluetooth | |
| PySide6.QtNfc | |
| unittest | |
| test | |
| distutils | |
| setuptools | |
| pkg_resources | |
| pip | |
| doctest | |
| noinclude-data-files: | | |
| qt6webenginecore.dll | |
| qt6webenginequick.dll | |
| qt6quick.dll | |
| qt6quick3d.dll | |
| qt6quick3druntimerender.dll | |
| qt6quick3dutils.dll | |
| qt6quickcontrols2.dll | |
| qt6quickshapes.dll | |
| qt6quicktemplates2.dll | |
| qt6quicktest.dll | |
| qt6quickwidgets.dll | |
| qt6qml.dll | |
| qt6qmlmeta.dll | |
| qt6qmlmodels.dll | |
| qt6qmlworkerscript.dll | |
| qt6scxml.dll | |
| qt6spatialaudio.dll | |
| skimage/data/* | |
| pyqtgraph/examples/* | |
| assume-yes-for-downloads: true | |
| - name: Compress executable with UPX | |
| shell: pwsh | |
| run: | | |
| choco install upx -y | |
| $exe = Get-ChildItem -Path "$PWD\build" -Filter *.exe -File -Recurse | Select-Object -First 1 | |
| if ($exe) { | |
| Write-Host "Compressing $($exe.FullName) with UPX..." | |
| upx -9 $exe.FullName | |
| Write-Host "Compression complete!" | |
| } else { | |
| Write-Host "Warning: No .exe file found to compress" | |
| } | |
| - name: Create NSIS Installer | |
| shell: pwsh | |
| run: | | |
| choco install nsis -y | |
| $appInfo = Get-Content app_info.json | ConvertFrom-Json | |
| $appVersion = $appInfo.version | |
| $distDir = Get-ChildItem -Path (Join-Path $PWD 'build') -Directory -Filter *.dist | Select-Object -First 1 | |
| if (-not $distDir) { | |
| throw "No .dist folder found" | |
| } | |
| $nsiContent = @" | |
| !define APPNAME "SmartEye" | |
| !define VERSION "$appVersion" | |
| !define EXEFILE "SmartEye.exe" | |
| SetCompressor /SOLID lzma | |
| SetCompressorDictSize 64 | |
| Name "`${APPNAME} Setup" | |
| !define MUI_ICON "frontend\assets\icons\icon.ico" | |
| !define MUI_UNICON "frontend\assets\icons\icon.ico" | |
| !include "MUI2.nsh" | |
| OutFile "build\SmartEyeInstaller.exe" | |
| InstallDir "`$PROGRAMFILES\`${APPNAME}" | |
| Caption "`${APPNAME} Installer" | |
| BrandingText "`${APPNAME}" | |
| !insertmacro MUI_PAGE_WELCOME | |
| !insertmacro MUI_PAGE_DIRECTORY | |
| !insertmacro MUI_PAGE_INSTFILES | |
| !insertmacro MUI_PAGE_FINISH | |
| !insertmacro MUI_UNPAGE_CONFIRM | |
| !insertmacro MUI_UNPAGE_INSTFILES | |
| !insertmacro MUI_LANGUAGE "English" | |
| Section "Install" | |
| SetOutPath "`$INSTDIR" | |
| File /r "$($distDir.FullName)\*" | |
| CreateDirectory "`$SMPROGRAMS\`${APPNAME}" | |
| CreateShortcut "`$SMPROGRAMS\`${APPNAME}\`${APPNAME}.lnk" "`$INSTDIR\`${EXEFILE}" | |
| CreateShortcut "`$DESKTOP\`${APPNAME}.lnk" "`$INSTDIR\`${EXEFILE}" | |
| WriteUninstaller "`$INSTDIR\Uninstall.exe" | |
| CreateShortcut "`$SMPROGRAMS\`${APPNAME}\Uninstall.lnk" "`$INSTDIR\Uninstall.exe" | |
| WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "DisplayName" "`${APPNAME}" | |
| WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "UninstallString" "`$INSTDIR\Uninstall.exe" | |
| WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "DisplayIcon" "`$INSTDIR\`${EXEFILE}" | |
| WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "DisplayVersion" "`${VERSION}" | |
| SectionEnd | |
| Section "Uninstall" | |
| RMDir /r "`$INSTDIR" | |
| Delete "`$DESKTOP\`${APPNAME}.lnk" | |
| Delete "`$SMPROGRAMS\`${APPNAME}\`${APPNAME}.lnk" | |
| Delete "`$SMPROGRAMS\`${APPNAME}\Uninstall.lnk" | |
| RMDir "`$SMPROGRAMS\`${APPNAME}" | |
| DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" | |
| SectionEnd | |
| "@ | |
| $nsiContent | Out-File -FilePath "SmartEye.nsi" -Encoding UTF8 | |
| Write-Host "Compiling installer with LZMA compression (this may take a few minutes)..." | |
| & "C:\Program Files (x86)\NSIS\makensis.exe" SmartEye.nsi | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "NSIS compilation failed with exit code $LASTEXITCODE" | |
| } | |
| Write-Host "Installer created successfully!" | |
| - name: Collect release files | |
| id: release_files | |
| shell: pwsh | |
| run: | | |
| $appInfo = Get-Content app_info.json | ConvertFrom-Json | |
| $appName = $appInfo.name | |
| $appVersion = $appInfo.version | |
| $appBuild = Get-Date -Format "yyyyMMdd" | |
| $installerPath = Join-Path $PWD "build\SmartEyeInstaller.exe" | |
| if (-not (Test-Path $installerPath)) { | |
| throw "Installer not found at $installerPath" | |
| } | |
| "app_name=$appName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "app_version=$appVersion" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "app_build=$appBuild" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "installer_path=$installerPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| - name: Create or update release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: pwsh | |
| run: | | |
| $appName = "${{ steps.release_files.outputs.app_name }}" | |
| $appVersion = "${{ steps.release_files.outputs.app_version }}" | |
| $appBuild = "${{ steps.release_files.outputs.app_build }}" | |
| $tag = "v$appVersion" | |
| $name = "$appName v$appVersion (build $appBuild)" | |
| $repo = "${{ github.repository }}" | |
| $installer = "${{ steps.release_files.outputs.installer_path }}" | |
| gh release view $tag --repo $repo *> $null | |
| if ($LASTEXITCODE -ne 0) { | |
| gh release create $tag $installer --repo $repo --title $name --generate-notes | |
| } else { | |
| gh release upload $tag $installer --repo $repo --clobber | |
| } |