Repository navigation
Background compiling after updates and a more capable Repair (0.3.24) #378
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: Installation and launchers | |
| # Every merge to main publishes the rolling nightly (Windows unsigned, Mac not notarized, Linux AppImage). | |
| # Users get an update only from a stable vX.Y.Z release, published by running this | |
| # workflow with "release" checked; Windows signing then waits for the release environment. | |
| on: | |
| push: | |
| branches: [main] | |
| # A change to the workflows alone does not publish a new Windows build. | |
| paths-ignore: | |
| - '**/*.md' | |
| - '.github/**' | |
| pull_request: | |
| paths-ignore: | |
| - '**/*.md' | |
| workflow_dispatch: | |
| inputs: | |
| release: | |
| description: 'Publish main as the stable release vX.Y.Z named in release_notes.json; users get an update notice' | |
| type: boolean | |
| default: false | |
| defender_baseline: | |
| description: 'Also diagnose the currently published package before rebuilding' | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| # Stable runs have their own group: a later merge must not replace a pending release. | |
| concurrency: | |
| group: freevideo-public-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'build' }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| installation: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.12' | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '22' | |
| - name: Check every ComfyUI browser module | |
| run: | | |
| for file in web/*.js; do node --input-type=module --check < "$file"; done | |
| node scripts/test_freevideo_entry.mjs | |
| node scripts/test_prompt_guide.mjs | |
| - name: Check CLI, ComfyUI entry and packaged resources | |
| run: | | |
| python scripts/check_installation.py | |
| bash -n setup.sh test.sh freevideo scripts/bootstrap_linux.sh | |
| ./setup.sh --help | |
| ./test.sh --help | |
| ./freevideo --help | |
| - name: Build installable Python packages | |
| run: | | |
| python -m pip install build | |
| python -m build | |
| # Fail a stable run before anyone is asked to approve signing. | |
| - name: Check that this version is not released yet | |
| if: github.event_name == 'workflow_dispatch' && inputs.release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| version=$(python -c 'import json; print(json.load(open("freevideo_engine/release_notes.json"))["product_version"])') | |
| if gh api "repos/${{ github.repository }}/git/matching-refs/tags/v$version" --jq '.[].ref' | grep -qx "refs/tags/v$version"; then | |
| echo "::error::v$version is already released. Bump product_version and the notes in freevideo_engine/release_notes.json first." | |
| exit 1 | |
| fi | |
| echo "Publishing v$version" | |
| windows: | |
| # Build once, after merging; pull requests run the installation checks only. | |
| if: github.event_name != 'pull_request' | |
| needs: [installation] | |
| runs-on: windows-2025 | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| # Only stable releases are signed. They use the release environment, whose reviewers decide who can mint the OIDC token Azure trusts for signing. | |
| environment: ${{ github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo' && 'release' || '' }} | |
| env: | |
| PYTHONUTF8: '1' | |
| PYTHONIOENCODING: utf-8 | |
| FREEVIDEO_RELEASE_TRACK: ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.12' | |
| cache: pip | |
| cache-dependency-path: constraints/windows-launcher.txt | |
| - run: python scripts/check_installation.py | |
| - run: python -m pip install -r constraints/windows-launcher.txt | |
| - name: Diagnose previous release with real-time Defender | |
| if: github.event_name == 'workflow_dispatch' && inputs.defender_baseline | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: pwsh | |
| run: | | |
| gh release download windows-preview --repo FlashML-org/FreeVideo --pattern FreeVideo.exe --dir build/previous-release | |
| if ($LASTEXITCODE -ne 0) { throw 'Could not retrieve the previous release for comparison' } | |
| python scripts/scan_windows_release.py --file build/previous-release/FreeVideo.exe --report antivirus/previous-release.json | |
| $baselineExit = $LASTEXITCODE | |
| if (Test-Path antivirus/previous-release.json) { Get-Content antivirus/previous-release.json -Raw } | |
| Write-Output "Previous-release diagnosis exit code: $baselineExit" | |
| # The old package is evidence, not the replacement candidate. The | |
| # new final bytes must pass the mandatory protection gate below. | |
| exit 0 | |
| - name: Azure login (OIDC) | |
| if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo' | |
| uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0 | |
| with: | |
| client-id: ${{ vars.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ vars.AZURE_TENANT_ID }} | |
| subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} | |
| # The GitHub OIDC assertion behind the login expires after 5 minutes (azure-cli#29063). A signing token fetched now is cached by az for the whole job. | |
| - name: Fetch the signing token | |
| if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo' | |
| run: | | |
| az account get-access-token --scope https://codesigning.azure.net/.default --output none | |
| if ($LASTEXITCODE -ne 0) { throw 'Could not get a codesigning.azure.net token' } | |
| # The launcher runs these scripts from its unpacked tree, where the executable's signature does not cover them. Sign the checkout's copies before the build bundles them. | |
| - name: Sign bundled PowerShell scripts | |
| if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo' | |
| uses: azure/trusted-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0 | |
| with: | |
| endpoint: https://eus.codesigning.azure.net/ | |
| signing-account-name: flashmlsigning | |
| certificate-profile-name: freetoken-public | |
| files: | | |
| ${{ github.workspace }}/freevideo.ps1 | |
| ${{ github.workspace }}/setup.ps1 | |
| ${{ github.workspace }}/test.ps1 | |
| ${{ github.workspace }}/freevideo_engine/windows_bootstrap.ps1 | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - name: Build Windows launcher | |
| run: | | |
| python scripts/build_windows.py --onefolder --out build/windows-folder | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| python scripts/build_windows.py --out build/windows-exe | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| - name: Sign Windows launchers | |
| if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo' | |
| uses: azure/trusted-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0 | |
| with: | |
| endpoint: https://eus.codesigning.azure.net/ | |
| signing-account-name: flashmlsigning | |
| certificate-profile-name: freetoken-public | |
| files: | | |
| ${{ github.workspace }}/build/windows-exe/dist/FreeVideo.exe | |
| ${{ github.workspace }}/build/windows-folder/dist/FreeVideo/FreeVideo.exe | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| # Check the copies that ship: the folder layout exposes every bundled script on disk, and the one-file launcher is built from the same inputs. | |
| - name: Verify signatures and rebuild the folder ZIP around the signed launcher | |
| if: github.event_name == 'workflow_dispatch' && inputs.release && github.repository == 'FlashML-org/FreeVideo' | |
| shell: pwsh | |
| run: | | |
| $scripts = @(Get-ChildItem build/windows-folder/dist -Recurse -Filter *.ps1 | ForEach-Object FullName) | |
| "bundled scripts: $($scripts.Count)" | |
| ./scripts/verify_windows_release.ps1 -FolderDist build/windows-folder/dist -Signed ($scripts + 'build/windows-exe/dist/FreeVideo.exe' + 'build/windows-folder/dist/FreeVideo/FreeVideo.exe') | |
| - name: Hash final Windows launchers | |
| shell: pwsh | |
| run: | | |
| python -c "from pathlib import Path; import hashlib; p=Path('build/windows-exe/dist'); (p/'SHA256SUMS.txt').write_text(''.join(hashlib.sha256(f.read_bytes()).hexdigest()+' '+f.name+'\n' for f in (p/'FreeVideo.exe',)))" | |
| # Keep the full report in the job log; a blocked or inconclusive scan | |
| # must stop publication instead of making a suspicious build public. | |
| # The folder layout is only checked here, not published. | |
| - name: Open both packaged launchers and scan with real-time Defender | |
| timeout-minutes: 20 | |
| shell: pwsh | |
| run: | | |
| $scripts = @(Get-ChildItem build/windows-folder/dist -Recurse -Filter *.ps1 | ForEach-Object { '--script', $_.FullName }) | |
| python scripts/scan_windows_release.py --file build/windows-exe/dist/FreeVideo.exe --file build/windows-folder/dist/FreeVideo/FreeVideo.exe --file build/windows-folder/dist/FreeVideo-Windows-folder.zip @scripts --verify-launches build/windows-exe/dist/FreeVideo.exe build/windows-folder/dist/FreeVideo-Windows-folder.zip --report antivirus/report.json | |
| $scanExit = $LASTEXITCODE | |
| if (Test-Path antivirus/report.json) { Get-Content antivirus/report.json -Raw } | |
| exit $scanExit | |
| - name: Print retained protection and startup checks on failure | |
| if: failure() | |
| shell: pwsh | |
| run: | | |
| python -c "import json; from scripts.scan_windows_release import threat_history; print(json.dumps(threat_history(), indent=2))" | |
| Get-ChildItem antivirus -File -ErrorAction SilentlyContinue | ForEach-Object { Write-Output $_.Name; Get-Content $_.FullName -Tail 100 } | |
| Get-ChildItem (Join-Path $env:USERPROFILE 'Downloads/FreeVideo-release-check-*') -Recurse -File -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Name -eq 'smoke.json' } | ForEach-Object { Get-Content $_.FullName -Raw } | |
| - name: Keep the Windows launcher for publishing | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: windows-launcher | |
| path: | | |
| build/windows-exe/dist/FreeVideo.exe | |
| build/windows-exe/dist/launcher-build.json | |
| if-no-files-found: error | |
| retention-days: 1 | |
| macos: | |
| if: github.event_name != 'pull_request' | |
| needs: [installation] | |
| runs-on: macos-15 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| env: | |
| FREEVIDEO_RELEASE_TRACK: ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.12' | |
| architecture: arm64 | |
| cache: pip | |
| cache-dependency-path: constraints/macos-launcher.txt | |
| - name: Check installation and build the Apple Silicon launcher | |
| run: | | |
| python -c "import platform; assert platform.machine() == 'arm64'" | |
| python scripts/check_installation.py | |
| python -m pip install -r constraints/macos-launcher.txt | |
| python scripts/build_macos.py --out build/macos | |
| - name: Verify the disk image and native startup | |
| env: | |
| FREEVIDEO_LAUNCHER_HOME: ${{ runner.temp }}/freevideo-launcher-state | |
| run: | | |
| python scripts/verify_macos_build.py --build build/macos --out build/macos-verification | |
| build/macos/applications.noindex/FreeVideo.app/Contents/MacOS/FreeVideo --smoke-test "$RUNNER_TEMP/freevideo-smoke" | |
| python -c "import json, os; from pathlib import Path; value=json.loads((Path(os.environ['RUNNER_TEMP'])/'freevideo-smoke/smoke.json').read_text()); assert value['success'] and not value['model_modules_imported']" | |
| # Artifacts keep paths below the files' common folder; stage both side by side. | |
| - name: Stage the Mac launcher for publishing | |
| run: | | |
| mkdir -p build/macos-publish | |
| cp build/macos/dist/FreeVideo-Mac-arm64.dmg build/macos/launcher-build.json build/macos-publish/ | |
| - name: Keep the Mac launcher for publishing | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: macos-launcher | |
| path: build/macos-publish/ | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Keep failure diagnostics | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: macos-build-failure | |
| path: | | |
| build/macos/build-result.json | |
| build/macos-verification/result.json | |
| ${{ runner.temp }}/freevideo-smoke/smoke.json | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| linux: | |
| if: github.event_name != 'pull_request' | |
| needs: [installation] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 40 | |
| permissions: | |
| contents: read | |
| # glibc 2.34, the oldest PySide6 6.11 supports: Ubuntu 22.04, Debian 12, Fedora 36, RHEL 9 and newer. | |
| container: quay.io/pypa/manylinux_2_34_x86_64:2026.10.03-1@sha256:e4ca528d41ab66b3654110295d7a2c7bfa599ddbb6efb269ec21f7f73b88f1b6 | |
| env: | |
| FREEVIDEO_RELEASE_TRACK: ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install the libraries Qt loads, and those bundled for desktops that lack them | |
| run: | | |
| dnf install -y epel-release | |
| dnf install -y xcb-util-cursor xcb-util-image xcb-util-keysyms xcb-util-renderutil xcb-util-wm xcb-util \ | |
| libxkbcommon-x11 libxkbcommon mesa-libGL mesa-libEGL fontconfig freetype dbus-libs xorg-x11-server-Xvfb | |
| - name: Prepare Python 3.12 with a shared libpython | |
| run: | | |
| /opt/python/cp312-cp312/bin/python -m pip install uv==0.9.0 | |
| /opt/python/cp312-cp312/bin/uv python install 3.12 | |
| /opt/python/cp312-cp312/bin/uv venv /opt/launcher --python 3.12 | |
| /opt/python/cp312-cp312/bin/uv pip install --python /opt/launcher/bin/python -r constraints/linux-launcher.txt | |
| - name: Check installation and build the Linux launcher | |
| run: | | |
| git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| /opt/launcher/bin/python scripts/check_installation.py | |
| /opt/launcher/bin/python scripts/build_linux.py --out build/linux | |
| - name: Start the AppImage on a virtual display | |
| env: | |
| FREEVIDEO_LAUNCHER_HOME: ${{ runner.temp }}/freevideo-launcher-state | |
| APPIMAGE_EXTRACT_AND_RUN: '1' | |
| run: | | |
| Xvfb :99 -screen 0 1280x800x24 & | |
| export DISPLAY=:99 | |
| sleep 2 | |
| build/linux/release/FreeVideo-Linux-x86_64.AppImage --smoke-test "$RUNNER_TEMP/freevideo-smoke" | |
| /opt/launcher/bin/python -c "import json, os; from pathlib import Path; value=json.loads((Path(os.environ['RUNNER_TEMP'])/'freevideo-smoke/smoke.json').read_text()); assert value['success'] and not value['model_modules_imported'], value" | |
| - name: Stage the Linux launcher for publishing | |
| run: | | |
| mkdir -p build/linux-publish | |
| cp build/linux/release/FreeVideo-Linux-x86_64.AppImage build/linux/release/launcher-build.json build/linux-publish/ | |
| - name: Keep the Linux launcher for publishing | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: linux-launcher | |
| path: build/linux-publish/ | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Keep failure diagnostics | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: linux-build-failure | |
| path: | | |
| ${{ runner.temp }}/freevideo-smoke/smoke.json | |
| ${{ runner.temp }}/freevideo-smoke/launcher.png | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| publish: | |
| # Release assets use Releases storage, not the Actions artifact quota. | |
| if: github.event_name != 'pull_request' && github.repository == 'FlashML-org/FreeVideo' && github.ref == 'refs/heads/main' | |
| needs: [windows, macos, linux] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: '3.12' | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: windows-launcher | |
| path: build/windows | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: macos-launcher | |
| path: build/macos | |
| - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: linux-launcher | |
| path: build/linux | |
| - name: Publish the release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: >- | |
| python scripts/publish_release.py --track ${{ github.event_name == 'workflow_dispatch' && inputs.release && 'stable' || 'nightly' }} | |
| --windows build/windows --macos build/macos --linux build/linux --sha ${{ github.sha }} |