Merge pull request #1069 from untypequicode/nix-flake

Add a Nix flake (NixOS, GNOME and Hyprland)
This commit is contained in:
BFlat
2026-10-30 01:19:45 +07:00
committed by GitHub
3 changed files with 333 additions and 0 deletions
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1791594108,
"narHash": "sha256-XK4KceonkwD8LuMAt/vNrz982mXJM5jeHAxMwsUXXAQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6510408d147d8c5e6d76043e899e9251756f6a75",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+106
View File
@@ -0,0 +1,106 @@
{
description = "EffectCraft — motion graphics and visual effects, a free alternative to After Effects, in pure Rust";
inputs = {
# EffectCraft needs edition 2024 and rustc ≥ 1.95, so the flake tracks unstable. Consumers on a
# release channel can retarget it: `inputs.effectcraft.inputs.nixpkgs.follows = "nixpkgs";`
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
{
self,
nixpkgs,
}:
let
inherit (nixpkgs) lib;
systems = [
"x86_64-linux"
"aarch64-linux"
"aarch64-darwin"
];
# The AppStream metadata carries a release date. The flake source's own commit date is
# reproducible, unlike "now"; a tarball or dirty tree without one falls back to the epoch.
buildDate =
let
d = self.lastModifiedDate or "";
in
if builtins.stringLength d >= 8 then
"${builtins.substring 0 4 d}-${builtins.substring 4 2 d}-${builtins.substring 6 2 d}"
else
"1970-01-01";
packageArgs = {
inherit buildDate;
};
forAllSystems = f: lib.genAttrs systems (system: f (import nixpkgs { inherit system; }) system);
in
{
packages = forAllSystems (
pkgs: _:
let
effectcraft = pkgs.callPackage ./nix/package.nix packageArgs;
in
{
inherit effectcraft;
default = effectcraft;
}
);
# `nixpkgs.overlays = [ inputs.effectcraft.overlays.default ]` → `pkgs.effectcraft` everywhere.
overlays.default = final: _prev: {
effectcraft = final.callPackage ./nix/package.nix packageArgs;
};
apps = forAllSystems (
_: system: {
default = {
type = "app";
program = lib.getExe self.packages.${system}.effectcraft;
};
}
);
devShells = forAllSystems (
pkgs: system:
let
effectcraft = self.packages.${system}.effectcraft;
in
{
default = pkgs.mkShell ({
# The package's own native inputs, so `cargo build` in the shell needs no extra setup.
inputsFrom = [ effectcraft ];
packages = with pkgs; [
cargo
rustc
clippy
rustfmt
rust-analyzer
];
# `cargo run` dlopens the Vulkan loader, libwayland and libxkbcommon like the packaged binary.
LD_LIBRARY_PATH = lib.makeLibraryPath effectcraft.passthru.runtimeLibs;
RUST_BACKTRACE = "1";
});
}
);
checks = forAllSystems (
pkgs: system: {
effectcraft = self.packages.${system}.effectcraft;
# `nix fmt` formats the flake; this keeps it formatted.
formatting =
pkgs.runCommand "effectcraft-nix-formatting" { nativeBuildInputs = [ pkgs.nixfmt ]; }
''
nixfmt --check ${./flake.nix} ${./nix}
touch $out
'';
}
);
formatter = forAllSystems (pkgs: _: pkgs.nixfmt);
};
}
+200
View File
@@ -0,0 +1,200 @@
# EffectCraft — the desktop app (`effectcraft`) and the CLI/MCP server (`effectcraft-cli`), built from this
# repository:
#
# nix build # → ./result/bin/effectcraft, ./result/bin/effectcraft-cli
#
# Kept in step with the release workflow (.github/workflows/release.yml) and packaging/:
# * `Cargo.lock` drives the dependencies (`cargoLock`), so there is no vendorHash to bump. The
# git sources (storytold/craft-libs, storytold/filmcraft) are fetched at the revision the lock file pins;
# * the desktop file, hicolor icons, MIME types and AppStream metadata are the same files the
# .deb/.rpm ship (packaging/linux/), so `apt` and NixOS users see one identical EffectCraft;
# * `doCheck` is off: the test suite needs corpora and a GPU the Nix sandbox cannot provide
# (`cargo xtask ci` is the gate). `pkgs.effectcraft.overrideAttrs { doCheck = true; }` runs it.
#
# Everything in the product is pure Rust, so this needs no build system beyond cargo plus the
# libraries winit/wgpu `dlopen` at run time (see `runtimeLibs`).
#
# Desktop sessions: the window opens natively on Wayland (GNOME/Mutter, Hyprland and other
# wlroots-style compositors) and falls back to X11/XWayland. File dialogs go through
# xdg-desktop-portal, so the session needs a portal backend: GNOME ships one
# (`xdg-desktop-portal-gnome`); on Hyprland enable
# xdg.portal = { enable = true; extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; };
# next to `programs.hyprland.enable = true;`.
{
lib,
stdenv,
rustPlatform,
pkg-config,
makeWrapper,
# Windowing/GPU libraries: needed at build time by the crates that link them, at run time by the
# ones that open them with dlopen() (see `runtimeLibs`), and by the dev shell (flake.nix).
wayland,
wayland-scanner,
libxkbcommon,
libx11,
libxcb,
libxcursor,
libxi,
libxrandr,
vulkan-loader,
libglvnd,
# rfd opens libdbus-1 at run time to reach the xdg-desktop-portal file chooser (Open/Save dialogs).
dbus,
# Audio: cpal links ALSA, which reaches PipeWire/PulseAudio through the ALSA plug-ins.
alsa-lib,
# Validates the desktop file and the AppStream metadata we install (as CI does).
desktop-file-utils,
appstream,
# Adds the driver search path (/run/opengl-driver/lib) to the binaries' RPATH so wgpu finds the
# installed Vulkan/GL driver on NixOS. Optional: harmless on nixpkgs without it.
autoAddDriverRunpath ? null,
# Release date for the AppStream metadata (YYYY-MM-DD); the flake derives it from its own commit.
buildDate ? "1970-01-01",
}:
let
root = ../.;
# The version lives in exactly one place: `[workspace.package] version` in the root Cargo.toml.
version = (builtins.fromTOML (builtins.readFile (root + "/Cargo.toml"))).workspace.package.version;
isLinux = stdenv.hostPlatform.isLinux;
# AppStream id: also the desktop file name, and the icon name in share/icons/hicolor.
appId = "ai.storyteller.effectcraft";
# Libraries the binaries open at run time with dlopen(): nothing links them, so no RPATH points at
# them. winit loads libwayland-client, libxkbcommon(-x11) and the X11 libraries; wgpu loads the
# Vulkan loader or libEGL (libglvnd); rfd loads libdbus-1 for the file chooser portal. NixOS patches the loader to find the drivers in
# /run/opengl-driver. The wrapper below points LD_LIBRARY_PATH here.
runtimeLibs = lib.optionals isLinux [
libxkbcommon
wayland
libx11
libxcb
libxcursor
libxi
libxrandr
vulkan-loader
libglvnd
dbus
];
# The two native binaries. The wasm app (apps/effectcraft-web) is built by `cargo xtask`, not here.
binaries = [
"effectcraft"
"effectcraft-cli"
];
in
rustPlatform.buildRustPackage {
pname = "effectcraft";
inherit version;
src = lib.cleanSourceWith {
src = lib.cleanSource root;
# The Nix files are not part of the build: editing them must not rebuild any Rust.
filter =
path: _type:
!(builtins.elem (baseNameOf path) [
"flake.nix"
"flake.lock"
"nix"
]);
};
cargoLock = {
lockFile = root + "/Cargo.lock";
# The git dependencies are pinned by revision in Cargo.lock.
allowBuiltinFetchGit = true;
};
# Both binaries, as the .deb/.rpm install them (`packaging/linux/package.sh`).
cargoBuildFlags = [
"--locked"
"-p"
"effectcraft"
"-p"
"effectcraft-cli"
];
doCheck = false;
strictDeps = true;
nativeBuildInputs = [
pkg-config
]
++ lib.optionals isLinux [
makeWrapper
wayland-scanner
desktop-file-utils
appstream
]
++ lib.optionals (isLinux && autoAddDriverRunpath != null) [ autoAddDriverRunpath ];
buildInputs = lib.optionals isLinux [
wayland
libxkbcommon
libx11
libxcb
libxcursor
libxi
libxrandr
alsa-lib
];
postInstall = ''
# Desktop integration: byte-for-byte the files the .deb/.rpm/.AppImage install.
install -Dm644 packaging/linux/${appId}.desktop $out/share/applications/${appId}.desktop
install -Dm644 packaging/linux/${appId}.mime.xml $out/share/mime/packages/${appId}.xml
cp -r assets/app-icon/hicolor $out/share/icons/
install -d $out/share/metainfo
substitute packaging/linux/${appId}.metainfo.xml.in \
$out/share/metainfo/${appId}.metainfo.xml \
--subst-var-by VERSION ${version} \
--subst-var-by DATE ${buildDate}
# Licences for everything embedded in the binaries.
doc=$out/share/doc/effectcraft
for f in README.md LICENSE-MIT LICENSE-APACHE NOTICE ATTRIBUTION.md assets/ATTRIBUTION.md; do
if [ -f "$f" ]; then install -Dm644 "$f" "$doc/$(basename "$f")"; fi
done
for f in assets/fonts/OFL-*.txt; do
if [ -f "$f" ]; then install -Dm644 "$f" "$doc/$(basename "$f")"; fi
done
''
+ lib.optionalString isLinux ''
desktop-file-validate $out/share/applications/${appId}.desktop
XDG_CACHE_HOME=$TMPDIR/.cache appstreamcli validate --no-net \
$out/share/metainfo/${appId}.metainfo.xml
# makeWrapper keeps the binaries working when the libs above are only dlopen'd.
for bin in ${lib.concatStringsSep " " (map (b: "$out/bin/" + b) binaries)}; do
wrapProgram "$bin" --prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath runtimeLibs}"
done
'';
passthru = {
# Shared with the dev shell so `cargo run` inside `nix develop` finds the same dlopen'd libs.
inherit runtimeLibs;
};
meta = {
description = "Motion graphics and visual effects application";
longDescription = ''
EffectCraft is a motion graphics and visual effects application: compositions, keyframes,
effects and rendering. It ships with effectcraft-cli, a headless renderer, command runner and
MCP server for AI agents.
'';
homepage = "https://getartcraft.com/apps/effectcraft";
license = with lib.licenses; [
mit
asl20
];
sourceProvenance = with lib.sourceTypes; [ fromSource ];
platforms = lib.platforms.linux ++ lib.platforms.darwin;
# `nix run` and the desktop entry both start the GUI.
mainProgram = "effectcraft";
};
}