app-id: com.fluorine.manager runtime: org.kde.Platform runtime-version: '6.10' sdk: org.kde.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.rust-stable command: fluorine-manager finish-args: - --share=ipc - --share=network - --socket=x11 - --socket=wayland - --device=all - --filesystem=home - --filesystem=/run/media:ro - --filesystem=~/.steam:ro - --filesystem=~/.local/share/Steam - --filesystem=~/.var/app/com.valvesoftware.Steam:ro - --talk-name=org.freedesktop.Flatpak cleanup: - /include - /lib/cmake - /lib/pkgconfig - /share/doc - /share/man - '*.a' - '*.la' modules: # ── 1. Boost ── - name: boost buildsystem: simple build-commands: - ./bootstrap.sh --prefix=/app --with-libraries=program_options,thread,filesystem,system,locale - ./b2 install -j$FLATPAK_BUILDER_N_JOBS link=shared variant=release sources: - type: archive url: https://archives.boost.io/release/1.87.0/source/boost_1_87_0.tar.gz sha256: f55c340aa49763b1925ccf02b2e83f35fdcf634c9d5164a2acb87540173c741d # ── 2. spdlog ── - name: spdlog buildsystem: cmake-ninja config-opts: - -DCMAKE_BUILD_TYPE=Release - -DSPDLOG_BUILD_SHARED=ON sources: - type: git url: https://github.com/gabime/spdlog.git tag: v1.15.2 # ── 3. tinyxml2 ── - name: tinyxml2 buildsystem: cmake-ninja config-opts: - -DCMAKE_BUILD_TYPE=Release sources: - type: git url: https://github.com/leethomason/tinyxml2.git tag: 10.0.0 # ── 4. tomlplusplus ── - name: tomlplusplus buildsystem: cmake-ninja config-opts: - -DCMAKE_BUILD_TYPE=Release sources: - type: git url: https://github.com/marzer/tomlplusplus.git tag: v3.4.0 # ── 5. libfuse3 ── # Built manually to install fusermount3 without setuid (chown root fails in sandbox). # default_library=both produces libfuse3.a so the VFS helper can link statically. - name: libfuse3 buildsystem: simple build-commands: - meson setup _build --prefix=/app -Dexamples=false -Dtests=false -Dutils=true -Ddefault_library=both - ninja -C _build # Install library and headers (skip meson install which runs chown). - ninja -C _build install || true # Ensure fusermount3 is installed even if install_helper.sh failed. - install -Dm755 _build/util/fusermount3 /app/bin/fusermount3 2>/dev/null || true sources: - type: git url: https://github.com/libfuse/libfuse.git tag: fuse-3.16.2 # ── 6. pybind11 ── - name: pybind11 buildsystem: cmake-ninja config-opts: - -DCMAKE_BUILD_TYPE=Release - -DPYBIND11_TEST=OFF sources: - type: git url: https://github.com/pybind/pybind11.git tag: v2.13.6 # ── 7. libloot ── # libloot has a Rust crate at the root and a C++ cmake project in cpp/. # The Rust static library must be built first, then cmake links against it. - name: libloot buildsystem: simple build-options: append-path: /usr/lib/sdk/rust-stable/bin env: CARGO_HOME: /run/build/libloot/cargo # Network needed for cargo deps and cmake FetchContent (googletest). build-args: - --share=network build-commands: # Build Rust static library first. - cargo build --release # Build the C++ shared library (links against Rust static lib). - | cmake -S cpp -B cpp/build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=ON \ -DLIBLOOT_INSTALL_DOCS=OFF \ -DCMAKE_INSTALL_PREFIX=/app - cmake --build cpp/build --parallel - cmake --install cpp/build # Create pkg-config metadata. - mkdir -p /app/lib/pkgconfig - | printf '%s\n' \ 'prefix=/app' \ 'exec_prefix=${prefix}' \ 'libdir=${prefix}/lib' \ 'includedir=${prefix}/include' \ '' \ 'Name: libloot' \ 'Description: LOOT C++ API library' \ 'Version: 0.29.0' \ 'Libs: -L${libdir} -lloot' \ 'Cflags: -I${includedir}' \ > /app/lib/pkgconfig/libloot.pc sources: - type: git url: https://github.com/loot/libloot.git branch: master # ── 8. icoutils ── - name: icoutils buildsystem: autotools build-options: # icoutils 0.32.3 uses K&R-style () declarations that GCC 14+ rejects. cflags: -std=gnu17 sources: - type: archive url: https://download.savannah.gnu.org/releases/icoutils/icoutils-0.32.3.tar.bz2 sha256: 17abe02d043a253b68b47e3af69c9fc755b895db68fdc8811786125df564c6e0 # ── 9. cabextract ── # Required by winetricks for extracting Windows cab archives. - name: cabextract buildsystem: autotools sources: - type: archive url: https://www.cabextract.org.uk/cabextract-1.11.tar.gz sha256: b5546db1155e4c718ff3d4b278573604f30dd64c3c5bfd4657cd089b823a3ac6 # ── 10. Portable Python runtime ── # Pre-built Python used as the embedded interpreter for MO2's Python plugins. # pip packages are installed via network during build (--share=network). - name: portable-python buildsystem: simple build-options: build-args: - --share=network build-commands: # Extract, trim, symlink, and install pip packages in one step # (each build-command runs in its own shell). - | set -e mkdir -p /app/lib/fluorine/python unzip portable-python.zip -d /tmp/pp PP_DIR="$(find /tmp/pp -maxdepth 1 -mindepth 1 -type d | head -1)" cp -a "${PP_DIR}/." /app/lib/fluorine/python/ rm -rf /tmp/pp # Versioned soname symlink (pybind11 links against libpython3.13.so.1.0). if [ -f /app/lib/fluorine/python/lib/libpython3.13.so ] && \ [ ! -f /app/lib/fluorine/python/lib/libpython3.13.so.1.0 ]; then ln -sf libpython3.13.so /app/lib/fluorine/python/lib/libpython3.13.so.1.0 fi # Trim test suites and unnecessary files. rm -rf /app/lib/fluorine/python/lib/python3.13/test \ /app/lib/fluorine/python/lib/python3.13/unittest/test \ /app/lib/fluorine/python/lib/python3.13/idlelib \ /app/lib/fluorine/python/lib/python3.13/tkinter \ /app/lib/fluorine/python/lib/python3.13/turtledemo \ /app/lib/fluorine/python/include \ /app/lib/fluorine/python/share \ 2>/dev/null || true find /app/lib/fluorine/python -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true find /app/lib/fluorine/python -name "*.pyc" -delete 2>/dev/null || true # Install pip packages (PyQt6 for plugin UI, psutil, vdf). /app/lib/fluorine/python/bin/python3 -m ensurepip --default-pip 2>/dev/null || true /app/lib/fluorine/python/bin/python3 -m pip install --no-cache-dir PyQt6 psutil vdf sources: - type: file url: https://github.com/bjia56/portable-python/releases/download/cpython-v3.13.9-build.0/python-headless-3.13.9-linux-x86_64.zip sha256: d03507d107da86d74aa38bbc4957b8db5753de567024c5724a254909ae6b86d6 dest-filename: portable-python.zip # ── 10. Fluorine Manager (main project) ── - name: fluorine buildsystem: simple build-options: append-path: /usr/lib/sdk/rust-stable/bin env: CARGO_HOME: /run/build/fluorine/cargo PYTHONPATH: /app/lib/python3/dist-packages # Network needed for FetchContent (7zip, etc.) and cargo deps. build-args: - --share=network build-commands: # Install sip build tools into /app (SDK /usr is read-only). - /usr/bin/python3 -m pip install --target=/app/lib/python3/dist-packages sip # ── Configure and build ── - | cmake -S . -B _build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/app \ -DPython3_ROOT_DIR=/app/lib/fluorine/python \ -DPython3_EXECUTABLE=/app/lib/fluorine/python/bin/python3 \ -DBUILD_PLUGIN_PYTHON=ON - cmake --build _build --parallel # ── Create output layout ── - mkdir -p /app/lib/fluorine/plugins /app/lib/fluorine/dlls /app/lib/fluorine/lib # Main binary. - cp -f _build/src/src/ModOrganizer /app/lib/fluorine/ModOrganizer-core # umu-run (patch and repackage if present). - | if [ -f _build/src/src/umu-run ]; then UMU_PATCH_DIR="$(mktemp -d)" cd "${UMU_PATCH_DIR}" python3 << 'PATCHEOF' import zipfile, pathlib zf = zipfile.ZipFile('/run/build/fluorine/_build/src/src/umu-run') zf.extractall('src') run_py = pathlib.Path('src/umu/umu_run.py') src = run_py.read_text() old1 = ' env["STEAM_COMPAT_INSTALL_PATH"] = os.environ.get("STEAM_COMPAT_INSTALL_PATH", "")' new1 = (old1 + '\n env["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = os.environ.get(' + '\n "STEAM_COMPAT_CLIENT_INSTALL_PATH", ""' + '\n )') if old1 in src and 'STEAM_COMPAT_CLIENT_INSTALL_PATH"] = os.environ' not in src: src = src.replace(old1, new1) old2 = ' os.environ[key] = val' new2 = (old2 + '\n\n # GE-Proton treats games with UMU_ID as non-Steam titles and skips' + '\n # the steam.exe DRM bridge. Remove it for Steam games.' + '\n if "UMU_ID" in os.environ and env.get("SteamAppId", "0") != "0":' + '\n del os.environ["UMU_ID"]') if old2 in src and 'del os.environ["UMU_ID"]' not in src: src = src.replace(old2, new2, 1) run_py.write_text(src) PATCHEOF python3 -m zipapp src -o /app/lib/fluorine/umu-run -p '/usr/bin/env python3' chmod +x /app/lib/fluorine/umu-run cd /run/build/fluorine rm -rf "${UMU_PATCH_DIR}" fi # VFS helper (standalone, runs on host for Flatpak FUSE support). # Statically linked against libfuse3 — no runtime deps beyond glibc. - test -f _build/src/src/mo2-vfs-helper && cp -f _build/src/src/mo2-vfs-helper /app/lib/fluorine/ || true # lootcli. - test -f _build/libs/lootcli/src/lootcli && cp -f _build/libs/lootcli/src/lootcli /app/lib/fluorine/ || true # icoutils (symlink from /app/bin where the icoutils module installed them). - ln -sf /app/bin/wrestool /app/lib/fluorine/wrestool - ln -sf /app/bin/icotool /app/lib/fluorine/icotool - ln -sf /app/bin/fusermount3 /app/lib/fluorine/fusermount3 - ln -sf /app/bin/cabextract /app/lib/fluorine/cabextract # fluorine-manager CLI helper. - test -f src/fluorine-manager && cp -f src/fluorine-manager /app/lib/fluorine/ || true # MO2 plugins (.so). - | find _build/libs -type f \( \ -name "libgame_*.so" -o \ -name "libinstaller_*.so" -o \ -name "libpreview_*.so" -o \ -name "libdiagnose_*.so" -o \ -name "libcheck_*.so" -o \ -name "libtool_*.so" -o \ -name "libinieditor.so" -o \ -name "libinibakery.so" -o \ -name "libbsa_extractor.so" -o \ -name "libbsa_packer.so" -o \ -name "libproxy.so" \ \) -exec cp -f {} /app/lib/fluorine/plugins/ \; # Python plugin payload. - | for f in libplugin_python.so lzokay.py winreg.py pyCfg.py \ DDSPreview.py Form43Checker.py ScriptExtenderPluginChecker.py; do [ -f "_build/src/src/plugins/${f}" ] && cp -f "_build/src/src/plugins/${f}" /app/lib/fluorine/plugins/ done for d in basic_games data libs dlls; do [ -d "_build/src/src/plugins/${d}" ] && cp -a "_build/src/src/plugins/${d}" /app/lib/fluorine/plugins/ done rm -f /app/lib/fluorine/plugins/FNIS*.py # Source-tree Python plugins (rootbuilder, omod installer, etc.). - | for f in src/plugins/*.py; do [ -f "${f}" ] && cp -f "${f}" /app/lib/fluorine/plugins/ done # 7z runtime. - | if [ -f _build/src/src/dlls/7z.so ]; then cp -f _build/src/src/dlls/7z.so /app/lib/fluorine/dlls/7z.so cp -f _build/src/src/dlls/7z.so /app/lib/fluorine/dlls/7zip.dll fi # Project shared libraries. - cp -f _build/libs/uibase/src/libuibase.so /app/lib/fluorine/lib/ - cp -f _build/libs/libbsarch/liblibbsarch.so /app/lib/fluorine/lib/ - cp -f _build/libs/archive/src/libarchive.so /app/lib/fluorine/lib/ - test -f _build/libs/plugin_python/src/runner/librunner.so && cp -f _build/libs/plugin_python/src/runner/librunner.so /app/lib/fluorine/lib/ || true - test -f libs/bsa_ffi/target/release/libbsa_ffi.so && cp -f libs/bsa_ffi/target/release/libbsa_ffi.so /app/lib/fluorine/lib/ || true - test -f libs/nak_ffi/target/release/libnak_ffi.so && cp -f libs/nak_ffi/target/release/libnak_ffi.so /app/lib/fluorine/lib/ || true # Boost libraries (from /app/lib where the boost module installed them). - cp -Lf /app/lib/libboost_program_options.so* /app/lib/fluorine/lib/ 2>/dev/null || true - cp -Lf /app/lib/libboost_thread.so* /app/lib/fluorine/lib/ 2>/dev/null || true # libloot (from /app/lib where the libloot module installed it). - | if [ -f /app/lib/libloot.so.0 ]; then cp -Lf /app/lib/libloot.so.0 /app/lib/fluorine/lib/ ln -sf libloot.so.0 /app/lib/fluorine/lib/libloot.so elif [ -f /app/lib/libloot.so ]; then cp -Lf /app/lib/libloot.so /app/lib/fluorine/lib/ fi # Build-tree Python plugin payload. - test -d _build/src/src/python && cp -a _build/src/src/python/. /app/lib/fluorine/python/ || true # Strip binaries. - strip --strip-unneeded /app/lib/fluorine/ModOrganizer-core 2>/dev/null || true - find /app/lib/fluorine/plugins -name "*.so" -exec strip --strip-unneeded {} \; 2>/dev/null || true - find /app/lib/fluorine/dlls \( -name "*.so" -o -name "*.dll" \) -exec strip --strip-unneeded {} \; 2>/dev/null || true - find /app/lib/fluorine/lib -name "*.so*" -exec strip --strip-unneeded {} \; 2>/dev/null || true - test -f /app/lib/fluorine/lootcli && strip --strip-unneeded /app/lib/fluorine/lootcli 2>/dev/null || true - test -f /app/lib/fluorine/mo2-vfs-helper && strip --strip-unneeded /app/lib/fluorine/mo2-vfs-helper 2>/dev/null || true # Install wrapper script, desktop file, metainfo, and icon. - install -Dm755 flatpak/fluorine-manager-wrapper.sh /app/bin/fluorine-manager - install -Dm644 flatpak/com.fluorine.manager.desktop /app/share/applications/com.fluorine.manager.desktop - install -Dm644 flatpak/com.fluorine.manager.metainfo.xml /app/share/metainfo/com.fluorine.manager.metainfo.xml - install -Dm644 flatpak/com.fluorine.manager.png /app/share/icons/hicolor/256x256/apps/com.fluorine.manager.png sources: - type: dir path: ..