diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 4922395d2..79510215f 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -107,194 +107,181 @@ jobs: target/release/PKGBUILD if-no-files-found: error -# # ------------------------------------------------------------------ -# macOS: Apple Silicon only. The app/CLI/worker no longer link -# liboakengine (M14 R4: they call the module rlibs directly), so the -# .app bundle carries no dylib to fold in; package it with -# cargo-packager and create the DMG with hdiutil. -# ------------------------------------------------------------------ -macos: - name: macOS DMG (Apple Silicon) - runs-on: warp-macos-15-arm64-6x + # ------------------------------------------------------------------ + # macOS: Apple Silicon only. The app/CLI/worker call the module rlibs + # directly (oakengine is retired), so the .app bundle carries no dylib + # to fold in; package it with cargo-packager and create the DMG with + # hdiutil. + # ------------------------------------------------------------------ + macos: + name: macOS DMG (Apple Silicon) + runs-on: warp-macos-15-arm64-6x - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable - - name: Install system dependencies - run: | - tooling/install-deps.sh - brew install opencolorio librsvg + - name: Install system dependencies + run: | + tooling/install-deps.sh + brew install opencolorio librsvg - - name: Configure build environment - run: | - { - echo "OCIO_RS_ENABLE_REAL=1" - echo "OCIO_INSTALL_DIR=/opt/homebrew/opt/opencolorio" - echo "OCIO_RS_LINK=dynamic" - echo "CFLAGS=-I/opt/homebrew/include" - echo "LDFLAGS=-L/opt/homebrew/lib" - echo "PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig/openjpeg" - } >> "$GITHUB_ENV" + - name: Configure build environment + run: | + { + echo "OCIO_RS_ENABLE_REAL=1" + echo "OCIO_INSTALL_DIR=/opt/homebrew/opt/opencolorio" + echo "OCIO_RS_LINK=dynamic" + echo "CFLAGS=-I/opt/homebrew/include" + echo "LDFLAGS=-L/opt/homebrew/lib" + echo "PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig/openjpeg" + } >> "$GITHUB_ENV" - - name: Cache cargo artifacts - uses: Swatinem/rust-cache@v2 - with: - shared-key: oak-workspace + - name: Cache cargo artifacts + uses: Swatinem/rust-cache@v2 + with: + shared-key: oak-workspace - - name: Cache project FFmpeg - uses: actions/cache@v4 - with: - path: .cache/ffmpeg - key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }} + - name: Cache project FFmpeg + uses: actions/cache@v4 + with: + path: .cache/ffmpeg + key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }} - - name: Build project FFmpeg (static, GPL + free codecs + hwaccel) - run: | - tooling/ffmpeg/build-ffmpeg.sh - echo "FFMPEG_DIR=$PWD/.cache/ffmpeg" >> "$GITHUB_ENV" + - name: Build project FFmpeg (static, GPL + free codecs + hwaccel) + run: | + tooling/ffmpeg/build-ffmpeg.sh + echo "FFMPEG_DIR=$PWD/.cache/ffmpeg" >> "$GITHUB_ENV" - - name: Install cargo-packager - run: cargo install cargo-packager --locked + - name: Install cargo-packager + run: cargo install cargo-packager --locked - - name: Generate app icon (PNG from Oak_Icon.svg) - run: | - mkdir -p icons - # cargo-packager's tauri-icns 0.1.0 maps only 512x512@1x (and - # 1024x1024@2x); a plain 1024x1024 PNG aborts with "No matching - # IconType", so render 512x512. - rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png - file icons/icon.png + - name: Generate app icon (PNG from Oak_Icon.svg) + run: | + mkdir -p icons + # cargo-packager's tauri-icns 0.1.0 maps only 512x512@1x (and + # 1024x1024@2x); a plain 1024x1024 PNG aborts with "No matching + # IconType", so render 512x512. + rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png + file icons/icon.png - # Build the packaged binaries (default members: the app, oak-cli, - # oak-worker). oakengine is a workspace member but NOT a default - # member (M14 R4: pure cdylib for plugins/external consumers — no - # packaged binary links it), so it is not built here. - - name: Build (release) - run: cargo build --release --locked + # Build the packaged binaries (default members: the app, oak-cli, + # oak-worker). + - name: Build (release) + run: cargo build --release --locked - - name: Package .app bundle - run: cargo packager --release --formats app + - name: Package .app bundle + run: cargo packager --release --formats app - - name: Create DMG - run: | - rm -rf dmg-staging - mkdir -p dmg-staging - cp -R target/release/Oak.app dmg-staging/ - ln -s /Applications dmg-staging/Applications - hdiutil create -volname "Oak Video Editor" \ - -srcfolder dmg-staging -ov -format UDZO Oak-macOS-arm64.dmg + - name: Create DMG + run: | + rm -rf dmg-staging + mkdir -p dmg-staging + cp -R target/release/Oak.app dmg-staging/ + ln -s /Applications dmg-staging/Applications + hdiutil create -volname "Oak Video Editor" \ + -srcfolder dmg-staging -ov -format UDZO Oak-macOS-arm64.dmg - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: oak-macos - path: Oak-macOS-arm64.dmg - if-no-files-found: error + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: oak-macos + path: Oak-macOS-arm64.dmg + if-no-files-found: error # ------------------------------------------------------------------ - # Windows: NSIS installer — DISABLED. The app does not link on Windows - # yet: build.rs explicitly says "Windows: NOT SUPPORTED YET" (a DLL cannot - # carry the undefined oakcore_* imports without stub import-library / - # delay-load plumbing), and oak-cli/oak-worker build.rs emit GNU-style - # `-Wl` flags that the MSVC linker rejects. The NSIS packaging itself is - # fine (cargo-packager downloads its own makensis, SHA-1 verified); the - # blocker is the binary link. Re-enable this job once crates link on - # Windows; the job below is kept verbatim as the reference (the - # `-p oakengine` prebuild is obsolete since M14 R4 — no packaged binary - # links the cdylib — and should be dropped when re-enabling). + # Windows: NSIS installer (restored; cargo-packager downloads its own + # makensis, SHA-1 verified). The obsolete `-p oakengine` cdylib prebuild + # from before M14 R4 is dropped — no packaged binary links the cdylib. # ------------------------------------------------------------------ - # windows: - # name: Windows installer (NSIS) - # runs-on: warp-windows-latest-x64-16x - # - # defaults: - # run: - # shell: msys2 {0} - # - # steps: - # - name: Checkout - # uses: actions/checkout@v4 - # with: - # submodules: true - # - # - name: Install Rust (stable) - # uses: dtolnay/rust-toolchain@stable - # - # - name: Setup MSYS2 - # uses: msys2/setup-msys2@v2 - # with: - # msystem: UCRT64 - # update: true - # install: >- - # git - # mingw-w64-ucrt-x86_64-gcc - # - # - name: Install system dependencies - # run: | - # bash tooling/install-deps.sh - # pacman -S --needed --noconfirm \ - # mingw-w64-ucrt-x86_64-opencolorio \ - # mingw-w64-ucrt-x86_64-librsvg - # - # - name: Configure build environment - # run: | - # echo "OCIO_RS_ENABLE_REAL=1" >> "$GITHUB_ENV" - # echo "OCIO_INSTALL_DIR=/ucrt64" >> "$GITHUB_ENV" - # echo "OCIO_RS_LINK=dynamic" >> "$GITHUB_ENV" - # - # - name: Cache cargo artifacts - # uses: Swatinem/rust-cache@v2 - # with: - # shared-key: oak-workspace - # - # - name: Cache project FFmpeg - # uses: actions/cache@v4 - # with: - # path: .cache/ffmpeg - # key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }} - # - # - name: Build project FFmpeg (static, GPL + free codecs + hwaccel) - # run: | - # bash tooling/ffmpeg/build-ffmpeg.sh - # echo "FFMPEG_DIR=$(cygpath -m "$PWD/.cache/ffmpeg")" >> "$GITHUB_ENV" - # - # - name: Install cargo-packager - # run: cargo install cargo-packager --locked - # - # - name: Generate app icon (PNG from Oak_Icon.svg) - # run: | - # mkdir -p icons - # rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png - # - # - name: Build (release) - # run: | - # cargo build --release --locked -p oakengine - # cargo build --release --locked - # - # - name: Package (NSIS) - # run: cargo packager --release --formats nsis - # - # - name: Upload artifact - # uses: actions/upload-artifact@v4 - # with: - # name: oak-windows - # path: target/release/*-setup.exe - # if-no-files-found: error + windows: + name: Windows installer (NSIS) + runs-on: warp-windows-latest-x64-16x + + defaults: + run: + shell: msys2 {0} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: >- + git + mingw-w64-ucrt-x86_64-gcc + + - name: Install system dependencies + run: | + bash tooling/install-deps.sh + pacman -S --needed --noconfirm \ + mingw-w64-ucrt-x86_64-opencolorio \ + mingw-w64-ucrt-x86_64-librsvg + + - name: Configure build environment + run: | + echo "OCIO_RS_ENABLE_REAL=1" >> "$GITHUB_ENV" + echo "OCIO_INSTALL_DIR=/ucrt64" >> "$GITHUB_ENV" + echo "OCIO_RS_LINK=dynamic" >> "$GITHUB_ENV" + + - name: Cache cargo artifacts + uses: Swatinem/rust-cache@v2 + with: + shared-key: oak-workspace + + - name: Cache project FFmpeg + uses: actions/cache@v4 + with: + path: .cache/ffmpeg + key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }} + + - name: Build project FFmpeg (static, GPL + free codecs + hwaccel) + run: | + bash tooling/ffmpeg/build-ffmpeg.sh + echo "FFMPEG_DIR=$(cygpath -m "$PWD/.cache/ffmpeg")" >> "$GITHUB_ENV" + + - name: Install cargo-packager + run: cargo install cargo-packager --locked + + - name: Generate app icon (PNG from Oak_Icon.svg) + run: | + mkdir -p icons + rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png + + - name: Build (release) + run: cargo build --release --locked + + - name: Package (NSIS) + run: cargo packager --release --formats nsis + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: oak-windows + path: target/release/*-setup.exe + if-no-files-found: error # ------------------------------------------------------------------ # Publish: attach every platform package to the v* tag's GitHub release - # (skipped on workflow_dispatch, which only uploads artifacts). The - # Windows stays disabled (the app does not link there yet); macOS is - # back on the warp Apple Silicon runner. + # (skipped on workflow_dispatch, which only uploads artifacts). # ------------------------------------------------------------------ release: name: Publish GitHub release - needs: [linux, macos] + needs: [linux, macos, windows] if: startsWith(github.ref, 'refs/tags/v') runs-on: warp-ubuntu-latest-x64-8x steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7071341c5..113c6484f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -176,3 +176,23 @@ jobs: if: runner.os == 'Windows' shell: msys2 {0} run: cargo test --workspace --locked + + # ------------------------------------------------------------------ + # OFX plugin discovery end-to-end + # ------------------------------------------------------------------ + # Build a minimal but real OFX plugin into a .ofx.bundle, point + # OFX_PLUGIN_PATH at it and let the scan_probe example run the full + # host path (directory scan -> dlopen -> setHost -> load -> describe + # -> register). The assertion is the plugin's registration line; CI + # machines have no system-wide OFX plugins, so the fixture is the + # only discovery. + - name: Build OFX fixture plugin (Linux/macOS) + if: runner.os != 'Windows' + run: crates/oakplugin/tests/fixtures/build_fixture.sh .cache/ofx-fixture + + - name: Probe OFX plugin discovery (Linux/macOS) + if: runner.os != 'Windows' + run: | + OFX_PLUGIN_PATH="$PWD/.cache/ofx-fixture" \ + cargo run --locked -p oakplugin --example scan_probe > probe.log 2>&1 + grep -q 'type_id=rs.oak.CiTestPlugin' probe.log diff --git a/crates/oakplugin/src/host.rs b/crates/oakplugin/src/host.rs index ecaf620a7..d1f9e90e8 100644 --- a/crates/oakplugin/src/host.rs +++ b/crates/oakplugin/src/host.rs @@ -640,6 +640,8 @@ fn find_binary_in_bundle(bundle: &Path) -> Option { contents.join("MacOS"), contents.join("Linux-x86-64"), contents.join("Linux-aarch64"), + // OFX 规范的 64 位 Windows 平台目录。 + contents.join("Win64"), ] { if let Ok(entries) = std::fs::read_dir(&platform_dir) { for e in entries.flatten() { diff --git a/crates/oakplugin/tests/fixtures/build_fixture.sh b/crates/oakplugin/tests/fixtures/build_fixture.sh new file mode 100755 index 000000000..5825966b8 --- /dev/null +++ b/crates/oakplugin/tests/fixtures/build_fixture.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Oak Video Editor - Non-Linear Video Editor +# Copyright (C) 2026 Oak Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Build the CI fixture OFX plugin into a real .ofx.bundle layout under +# "$1" (the directory the scan is pointed at via OFX_PLUGIN_PATH). +set -euo pipefail + +OUT_DIR="${1:?usage: build_fixture.sh }" +SRC="$(cd "$(dirname "$0")" && pwd)/ci_test_plugin.c" +BUNDLE="$OUT_DIR/OakCiTest.ofx.bundle" + +case "$(uname -s)" in + Darwin) + PLATFORM_DIR="Contents/MacOS" + # .ofx bundles are Mach-O bundles (loadable modules). + LINK_FLAGS=(-bundle) + CC_BIN="${CC:-clang}" + ;; + Linux) + ARCH="$(uname -m)" + case "$ARCH" in + x86_64) PLATFORM_DIR="Contents/Linux-x86-64" ;; + aarch64|arm64) PLATFORM_DIR="Contents/Linux-aarch64" ;; + *) echo "unsupported Linux arch: $ARCH" >&2; exit 1 ;; + esac + LINK_FLAGS=(-shared -fPIC) + CC_BIN="${CC:-cc}" + ;; + *) + echo "unsupported OS: $(uname -s)" >&2 + exit 1 + ;; +esac + +mkdir -p "$BUNDLE/$PLATFORM_DIR" +"$CC_BIN" -O2 "${LINK_FLAGS[@]}" "$SRC" -o "$BUNDLE/$PLATFORM_DIR/OakCiTest.ofx" +echo "fixture plugin built: $BUNDLE/$PLATFORM_DIR/OakCiTest.ofx" diff --git a/crates/oakplugin/tests/fixtures/ci_test_plugin.c b/crates/oakplugin/tests/fixtures/ci_test_plugin.c new file mode 100644 index 000000000..ecdc158e0 --- /dev/null +++ b/crates/oakplugin/tests/fixtures/ci_test_plugin.c @@ -0,0 +1,116 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Minimal but real OFX image-effect plugin used by CI to prove the host's +// plugin discovery/loading path end to end: the CI step compiles this file +// into a .ofx.bundle, points OFX_PLUGIN_PATH at it and runs the scan_probe +// example, asserting the plugin is discovered and registered. +// +// Only what the host actually exercises is implemented: setHost, the load / +// describe / describeInContext / createInstance / destroyInstance actions +// (registration instantiates every plugin once for its metadata). describe +// declares the filter context through the real property-suite round trip. + +#include + +typedef void* OfxPropertySetHandle; +typedef int OfxStatus; + +typedef struct OfxHost { + OfxPropertySetHandle host; + const void* (*fetchSuite)(OfxPropertySetHandle host, const char* name, int version); +} OfxHost; + +typedef struct OfxPlugin { + const char* pluginApi; + int apiVersion; + const char* pluginIdentifier; + unsigned int pluginVersionMajor; + unsigned int pluginVersionMinor; + void (*setHost)(OfxHost* host); + OfxStatus (*mainEntry)(const char* action, const void* handle, + OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs); +} OfxPlugin; + +typedef struct OfxPropertySuiteV1 { + OfxStatus (*propSetPointer)(OfxPropertySetHandle, const char*, int, void*); + OfxStatus (*propSetString)(OfxPropertySetHandle, const char*, int, const char*); + /* The fixture only ever sets strings; the remaining entries stay + untouched (the host's table is larger, we just never call them). */ +} OfxPropertySuiteV1; + +enum { + kOfxStatOK = 0, + kOfxStatReplyDefault = 16, +}; + +static OfxHost* g_host = 0; + +static void ci_set_host(OfxHost* host) { + g_host = host; +} + +static OfxStatus ci_main_entry(const char* action, const void* handle, + OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs) { + (void)inArgs; + (void)outArgs; + if (strcmp(action, "OfxActionLoad") == 0) { + return kOfxStatOK; + } + if (strcmp(action, "OfxActionDescribe") == 0) { + if (g_host == 0 || g_host->fetchSuite == 0) { + return 1; /* kOfxStatFailed: no host, nothing to describe on */ + } + OfxPropertySuiteV1* props = (OfxPropertySuiteV1*)g_host->fetchSuite( + g_host->host, "OfxPropertySuite", 1); + if (props == 0 || props->propSetString == 0) { + return 1; + } + OfxPropertySetHandle desc = (OfxPropertySetHandle)handle; + if (props->propSetString(desc, "OfxPropLabel", 0, "Oak CI Test Plugin") != kOfxStatOK) { + return 1; + } + if (props->propSetString(desc, "OfxImageEffectPropSupportedContexts", 0, + "OfxImageEffectContextFilter") != kOfxStatOK) { + return 1; + } + return kOfxStatOK; + } + if (strcmp(action, "OfxImageEffectActionDescribeInContext") == 0 || + strcmp(action, "OfxActionCreateInstance") == 0 || + strcmp(action, "OfxActionDestroyInstance") == 0) { + return kOfxStatOK; + } + return kOfxStatReplyDefault; +} + +static OfxPlugin g_plugin = { + "OfxImageEffectPluginAPI", + 1, + "rs.oak.CiTestPlugin", + 1, + 0, + ci_set_host, + ci_main_entry, +}; + +int OfxGetNumberOfPlugins(void) { + return 1; +} + +OfxPlugin* OfxGetPlugin(int nth) { + return nth == 0 ? &g_plugin : (OfxPlugin*)0; +}