name: windows-release on: push: tags: - "v*" workflow_dispatch: inputs: version: description: "Override app version (e.g. 0.5.0). Defaults to tag name without leading v." required: false type: string jobs: build_windows: runs-on: windows-host defaults: run: shell: cmd env: QT_ROOT: C:\Qt\6.8.3\mingw_64 BUILD_DIR: build-ci DIST_DIR: dist BUILD_TYPE: Release APP_EXE_NAME: TactileIpc3D.exe steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive - name: Init submodules run: | git submodule sync --recursive git submodule update --init --recursive - name: Resolve version run: | set "APP_VERSION=${{ inputs.version }}" if "%APP_VERSION%"=="" ( if "%GITHUB_REF:~0,10%"=="refs/tags/" ( set "APP_VERSION=%GITHUB_REF_NAME%" ) else ( if exist "VERSION.txt" ( for /f "usebackq delims=" %%v in ("VERSION.txt") do set "APP_VERSION=%%v" ) else ( set "APP_VERSION=%GITHUB_REF_NAME%" ) ) ) if "%APP_VERSION:~0,1%"=="v" set "APP_VERSION=%APP_VERSION:~1%" echo APP_VERSION=%APP_VERSION%>> "%GITHUB_ENV%" echo Using APP_VERSION=%APP_VERSION% - name: Setup Qt env & sanity check run: | call "%QT_ROOT%\bin\qtenv2.bat" echo ==== qmake / windeployqt ==== where qmake qmake -v where windeployqt where g++ g++ --version # ========= 这里二选一:CMake 或 qmake ========= # A) CMake 框架(推荐) - name: Configure (CMake) run: | if defined GITHUB_WORKSPACE ( set "REPO_ROOT=%GITHUB_WORKSPACE%" ) else ( set "REPO_ROOT=%CD%" ) set "BUILD_DIR_ABS=%REPO_ROOT%\%BUILD_DIR%" call "%QT_ROOT%\bin\qtenv2.bat" if exist "%BUILD_DIR_ABS%" rmdir /s /q "%BUILD_DIR_ABS%" mkdir "%BUILD_DIR_ABS%" cmake -S "%REPO_ROOT%" -B "%BUILD_DIR_ABS%" -G "MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=%BUILD_TYPE% - name: Build (CMake) run: | if defined GITHUB_WORKSPACE ( set "REPO_ROOT=%GITHUB_WORKSPACE%" ) else ( set "REPO_ROOT=%CD%" ) set "BUILD_DIR_ABS=%REPO_ROOT%\%BUILD_DIR%" call "%QT_ROOT%\bin\qtenv2.bat" cmake --build "%BUILD_DIR_ABS%" --config %BUILD_TYPE% -j 8 # # B) qmake 框架(如果你用 qmake,把上面 CMake 三步注释掉,启用下面) # - name: Build (qmake) # run: | # call "%QT_ROOT%\bin\qtenv2.bat" # if exist "%BUILD_DIR%" rmdir /s /q "%BUILD_DIR%" # mkdir "%BUILD_DIR%" # cd "%BUILD_DIR%" # qmake .. # mingw32-make -j 8 # ========= 找到 exe & 产物目录 ========= - name: Collect exe run: | call "%QT_ROOT%\bin\qtenv2.bat" if defined GITHUB_WORKSPACE ( set "REPO_ROOT=%GITHUB_WORKSPACE%" ) else ( set "REPO_ROOT=%CD%" ) set "DIST_ABS=%REPO_ROOT%\%DIST_DIR%" if exist "%DIST_ABS%" rmdir /s /q "%DIST_ABS%" mkdir "%DIST_ABS%" set "BUILD_DIR_ABS=%REPO_ROOT%\%BUILD_DIR%" set "RUNTIME_OUT_DIR=%BUILD_DIR_ABS%\out" set "RUNTIME_OUT_DIR_CFG=%RUNTIME_OUT_DIR%\%BUILD_TYPE%" if exist "%RUNTIME_OUT_DIR_CFG%\%APP_EXE_NAME%" ( set "RUNTIME_BASE=%RUNTIME_OUT_DIR_CFG%" ) else ( set "RUNTIME_BASE=%RUNTIME_OUT_DIR%" ) if not exist "%RUNTIME_BASE%\%APP_EXE_NAME%" ( echo EXE not found: "%RUNTIME_BASE%\%APP_EXE_NAME%" exit /b 1 ) echo ==== copy app exe ==== copy /y "%RUNTIME_BASE%\%APP_EXE_NAME%" "%DIST_ABS%\%APP_EXE_NAME%" echo ==== copy plugin dlls ==== if not exist "%DIST_ABS%\plugins\decoders" mkdir "%DIST_ABS%\plugins\decoders" if not exist "%RUNTIME_BASE%\plugins\decoders\*.dll" ( echo Plugin DLLs not found: "%RUNTIME_BASE%\plugins\decoders\*.dll" exit /b 1 ) copy /y "%RUNTIME_BASE%\plugins\decoders\*.dll" "%DIST_ABS%\plugins\decoders\" echo ==== copy OpenCV runtime dlls ==== set "OPENCV_LIB_DIR=%REPO_ROOT%\3rdpart\OpenCV\lib" if not exist "%OPENCV_LIB_DIR%\libopencv_core4100.dll" ( echo OpenCV DLL not found: "%OPENCV_LIB_DIR%\libopencv_core4100.dll" exit /b 1 ) if not exist "%OPENCV_LIB_DIR%\libopencv_imgproc4100.dll" ( echo OpenCV DLL not found: "%OPENCV_LIB_DIR%\libopencv_imgproc4100.dll" exit /b 1 ) copy /y "%OPENCV_LIB_DIR%\libopencv_core4100.dll" "%DIST_ABS%\" copy /y "%OPENCV_LIB_DIR%\libopencv_imgproc4100.dll" "%DIST_ABS%\" echo Copied to %DIST_DIR% dir "%DIST_ABS%" - name: Deploy Qt runtime (windeployqt) run: | if defined GITHUB_WORKSPACE ( set "REPO_ROOT=%GITHUB_WORKSPACE%" ) else ( set "REPO_ROOT=%CD%" ) set "DIST_ABS=%REPO_ROOT%\%DIST_DIR%" call "%QT_ROOT%\bin\qtenv2.bat" "%QT_ROOT%\bin\windeployqt.exe" "%DIST_ABS%\%APP_EXE_NAME%" ^ --release --compiler-runtime --no-translations - name: Build installer (Inno Setup) run: | if defined GITHUB_WORKSPACE ( set "REPO_ROOT=%GITHUB_WORKSPACE%" ) else ( set "REPO_ROOT=%CD%" ) set "DIST_ABS=%REPO_ROOT%\%DIST_DIR%" set "ISS_FILE=%REPO_ROOT%\installer\TactileIpc3D.iss" if not exist "%ISS_FILE%" ( echo Inno Setup script not found: "%ISS_FILE%" exit /b 1 ) if not exist "%DIST_ABS%" ( echo Dist dir not found: "%DIST_ABS%" exit /b 1 ) where iscc || (echo ISCC not found & exit /b 1) iscc /DMyAppVersion=%APP_VERSION% /DSourceDir="%DIST_ABS%" /DOutputDir="%DIST_ABS%" "%ISS_FILE%" # (可选)打包 zip - name: Zip package run: | if defined GITHUB_WORKSPACE ( set "REPO_ROOT=%GITHUB_WORKSPACE%" ) else ( set "REPO_ROOT=%CD%" ) set "DIST_ABS=%REPO_ROOT%\%DIST_DIR%" powershell -NoProfile -Command ^ "if (Test-Path '%DIST_ABS%.zip') { Remove-Item -Force '%DIST_ABS%.zip' }; " ^ "Compress-Archive -Path '%DIST_ABS%\*' -DestinationPath '%DIST_ABS%.zip'" - name: Publish Gitea Release if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' shell: powershell env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | $ErrorActionPreference = "Stop" $token = $env:GITEA_TOKEN if (-not $token) { throw "GITEA_TOKEN is not set. Please add it in repo secrets." } $api = $env:GITEA_API_URL if (-not $api) { $api = $env:GITHUB_API_URL } if (-not $api) { if ($env:GITEA_SERVER_URL) { $api = "$($env:GITEA_SERVER_URL)/api/v1" } elseif ($env:GITHUB_SERVER_URL) { $api = "$($env:GITHUB_SERVER_URL)/api/v1" } } if (-not $api) { throw "API URL not found. Set GITEA_API_URL or GITEA_SERVER_URL." } $repo = $env:GITHUB_REPOSITORY if (-not $repo) { $repo = $env:GITEA_REPOSITORY } if (-not $repo) { throw "Repository not found in env." } $ref = $env:GITHUB_REF $isTag = $false if ($ref) { $isTag = $ref.StartsWith("refs/tags/") } if ($isTag) { $tag = $env:GITHUB_REF_NAME } else { $tag = "v$($env:APP_VERSION)" } if (-not $tag) { throw "Release tag not found." } $owner, $repoName = $repo.Split("/") if (-not $owner -or -not $repoName) { throw "Invalid repo format: $repo" } $headers = @{ Authorization = "token $token" } # Get or create release $release = $null try { $release = Invoke-RestMethod -Method Get -Headers $headers -Uri "$api/repos/$owner/$repoName/releases/tags/$tag" } catch { $release = $null } if (-not $release) { $body = @{ tag_name = $tag name = $tag draft = $false prerelease = $false } if (-not $isTag) { $body.target_commitish = $env:GITHUB_SHA } $body = $body | ConvertTo-Json $release = Invoke-RestMethod -Method Post -Headers $headers -ContentType "application/json" -Body $body -Uri "$api/repos/$owner/$repoName/releases" } $releaseId = $release.id if (-not $releaseId) { throw "Release ID not found." } # Resolve dist dir $repoRoot = $env:GITHUB_WORKSPACE if (-not $repoRoot) { $repoRoot = (Get-Location).Path } $distDir = Join-Path $repoRoot $env:DIST_DIR if (-not (Test-Path $distDir)) { throw "Dist dir not found: $distDir" } $installer = Get-ChildItem -Path $distDir -Filter "TactileIpc3D-Setup-*.exe" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if (-not $installer) { throw "Installer not found in $distDir" } $zipPath = Join-Path $repoRoot "$($env:DIST_DIR).zip" $assets = @($installer.FullName) if (Test-Path $zipPath) { $assets += $zipPath } # Delete existing assets with same name $existing = Invoke-RestMethod -Method Get -Headers $headers -Uri "$api/repos/$owner/$repoName/releases/$releaseId/assets" $existingMap = @{} foreach ($a in $existing) { $existingMap[$a.name] = $a.id } foreach ($asset in $assets) { $name = [System.IO.Path]::GetFileName($asset) if ($existingMap.ContainsKey($name)) { $assetId = $existingMap[$name] Invoke-RestMethod -Method Delete -Headers $headers -Uri "$api/repos/$owner/$repoName/releases/assets/$assetId" } $uploadUrl = "$api/repos/$owner/$repoName/releases/$releaseId/assets?name=$name" & curl.exe -sS -X POST -H "Authorization: token $token" -F "attachment=@$asset" $uploadUrl if ($LASTEXITCODE -ne 0) { throw "Upload failed for $name" } }