mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
ci: add nix build package
This commit is contained in:
7 files changed
+402
-2
No files matched your search
@@ -8,3 +8,6 @@ plan/
|
||||
.DS_Store
|
||||
.claude/
|
||||
/craft-fonts
|
||||
# nix build / nix develop
|
||||
/result
|
||||
/result-*
|
||||
@@ -119,4 +119,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
|
||||
## Map of the code
|
||||
`geom`, `color`, `raster`, `tiff` (L0) → `raw`, `codecs`, `meta`, `develop` (L1) → `pipeline` → `catalog` → `engine`
|
||||
→ `ui-egui`, `mcp` (L5) → apps `lightcraft` (desktop), `lightcraft-cli` (render/commands/MCP). `scenes` generates demo
|
||||
photos. `xtask` = tooling (`ci`, `layers`, `assets`, `parity`, `wasm`, `corpus`, `stats`).
|
||||
photos. `xtask` = tooling (`ci`, `layers`, `assets`, `parity`, `wasm`, `corpus`, `stats`). `flake.nix` +
|
||||
`nix/package.nix` = the Nix package (`nix build` builds both binaries with the craft-fonts input, installs the
|
||||
desktop file/icons/AppStream metadata and runs `cargo test --workspace`; `nix develop` = dev shell). Keep it building
|
||||
when the workspace layout, `Cargo.lock`, the packaging files or the fonts change; see README → Quick start.
|
||||
@@ -119,4 +119,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
|
||||
## Map of the code
|
||||
`geom`, `color`, `raster`, `tiff` (L0) → `raw`, `codecs`, `meta`, `develop` (L1) → `pipeline` → `catalog` → `engine`
|
||||
→ `ui-egui`, `mcp` (L5) → apps `lightcraft` (desktop), `lightcraft-cli` (render/commands/MCP). `scenes` generates demo
|
||||
photos. `xtask` = tooling (`ci`, `layers`, `assets`, `parity`, `wasm`, `corpus`, `stats`).
|
||||
photos. `xtask` = tooling (`ci`, `layers`, `assets`, `parity`, `wasm`, `corpus`, `stats`). `flake.nix` +
|
||||
`nix/package.nix` = the Nix package (`nix build` builds both binaries with the craft-fonts input, installs the
|
||||
desktop file/icons/AppStream metadata and runs `cargo test --workspace`; `nix develop` = dev shell). Keep it building
|
||||
when the workspace layout, `Cargo.lock`, the packaging files or the fonts change; see README → Quick start.
|
||||
@@ -347,6 +347,31 @@ repo; see [craftrules `standards/fonts.md`](https://github.com/storytold/craftru
|
||||
The web build needs the `wasm32-unknown-unknown` target and the matching `wasm-bindgen` CLI
|
||||
(`cargo xtask web` prints the exact install command); see [docs/web.md](docs/web.md).
|
||||
|
||||
**Nix** builds the desktop app and `lightcraft-cli` (the Nix build always includes the craft-fonts input, so Japanese
|
||||
text has glyphs):
|
||||
|
||||
```sh
|
||||
nix run github:storytold/lightcraft # the desktop app
|
||||
nix build github:storytold/lightcraft # → ./result/bin/{lightcraft,lightcraft-cli}
|
||||
nix develop github:storytold/lightcraft # rust toolchain + native deps + fonts
|
||||
```
|
||||
|
||||
In a flake configuration (NixOS, home-manager, nix-darwin):
|
||||
|
||||
```nix
|
||||
# flake.nix
|
||||
inputs.lightcraft.url = "github:storytold/lightcraft";
|
||||
# optional: build against your own nixpkgs instead of the one LightCraft pins
|
||||
# inputs.lightcraft.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
# then, in a NixOS or home-manager module (where `inputs` is in scope):
|
||||
nixpkgs.overlays = [ inputs.lightcraft.overlays.default ]; # makes `pkgs.lightcraft` available
|
||||
environment.systemPackages = [ pkgs.lightcraft ]; # home-manager: home.packages = [ pkgs.lightcraft ];
|
||||
```
|
||||
|
||||
`nix build` installs the same desktop file, hicolor icons and AppStream metadata as the .deb/.rpm, and runs
|
||||
`cargo test --workspace` as its check phase (skip it with `pkgs.lightcraft.overrideAttrs { doCheck = false; }`).
|
||||
|
||||
**Keyboard:** <kbd>G</kbd> grid · <kbd>D</kbd> detail · <kbd>E</kbd> edit · <kbd>C</kbd> crop · <kbd>M</kbd> masking ·
|
||||
<kbd>Shift</kbd>+<kbd>P</kbd> presets · <kbd>\\</kbd> original · <kbd>Y</kbd> before/after · <kbd>Z</kbd> zoom ·
|
||||
<kbd>J</kbd> clipping · <kbd>⌘Z</kbd> undo · <kbd>⌘/</kbd> all shortcuts.
|
||||
|
||||
Generated
+45
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"nodes": {
|
||||
"craft-fonts": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1791295400,
|
||||
"narHash": "sha256-e+6HpOYoAFTh6mNfGBBw2njnBzcE8W8+Jybv1+dKOCA=",
|
||||
"owner": "storytold",
|
||||
"repo": "craft-fonts",
|
||||
"rev": "abb83316d96aa59c1cf64784289e378fe9fa5695",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "storytold",
|
||||
"repo": "craft-fonts",
|
||||
"rev": "abb83316d96aa59c1cf64784289e378fe9fa5695",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1791260994,
|
||||
"narHash": "sha256-Miqqk/ammqnTUxaoCyvtwoPeLbI1ksFyLxi/CazZpWY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "151fa4e8ddfdd8dd25d945ad94ed54a13de9f6e4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"craft-fonts": "craft-fonts",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
{
|
||||
description = "LightCraft — photo library and non-destructive raw developer, in pure Rust";
|
||||
|
||||
inputs = {
|
||||
# LightCraft needs edition 2024 and rustc ≥ 1.90, so the flake tracks unstable. Consumers on a
|
||||
# release channel can retarget it: `inputs.lightcraft.inputs.nixpkgs.follows = "nixpkgs";`
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
# Optional build input of the repository: the Japanese fonts (BIZ UDPGothic, BIZ UDMincho,
|
||||
# Shippori Mincho) that `crates/engine/build.rs` embeds when `CRAFT_FONTS_DIR` is set. Official
|
||||
# releases always build with them; without them everything works but Japanese text has no
|
||||
# glyphs. Pinned to the revision the release workflow uses — bump deliberately
|
||||
# (`nix flake update craft-fonts`); craftrules standards/fonts.md.
|
||||
craft-fonts = {
|
||||
url = "github:storytold/craft-fonts/abb83316d96aa59c1cf64784289e378fe9fa5695";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
craft-fonts,
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
# No x86_64-darwin: the pinned nixpkgs (26.11, "nixos-unstable") dropped support for it. On
|
||||
# an Intel Mac, retarget nixpkgs at a release that still has it, or install the .dmg from the
|
||||
# GitHub release.
|
||||
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 craft-fonts buildDate;
|
||||
};
|
||||
|
||||
forAllSystems = f: lib.genAttrs systems (system: f (import nixpkgs { inherit system; }) system);
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (
|
||||
pkgs: _:
|
||||
let
|
||||
lightcraft = pkgs.callPackage ./nix/package.nix packageArgs;
|
||||
in
|
||||
{
|
||||
inherit lightcraft;
|
||||
default = lightcraft;
|
||||
}
|
||||
);
|
||||
|
||||
# `nixpkgs.overlays = [ inputs.lightcraft.overlays.default ]` → `pkgs.lightcraft` everywhere.
|
||||
overlays.default = final: _prev: {
|
||||
lightcraft = final.callPackage ./nix/package.nix packageArgs;
|
||||
};
|
||||
|
||||
devShells = forAllSystems (
|
||||
pkgs: system:
|
||||
let
|
||||
lightcraft = self.packages.${system}.lightcraft;
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell (
|
||||
{
|
||||
# The package's own native inputs, so `cargo build` in the shell needs no extra setup.
|
||||
inputsFrom = [ lightcraft ];
|
||||
packages = with pkgs; [
|
||||
cargo
|
||||
rustc
|
||||
clippy
|
||||
rustfmt
|
||||
rust-analyzer
|
||||
];
|
||||
# `cargo run` dlopens the Vulkan loader and libxkbcommon like the packaged binary.
|
||||
LD_LIBRARY_PATH = lib.makeLibraryPath lightcraft.passthru.runtimeLibs;
|
||||
RUST_BACKTRACE = "1";
|
||||
}
|
||||
// lib.optionalAttrs (craft-fonts != null) {
|
||||
# Japanese glyphs in `cargo run` too; build.rs reads this as CRAFT_FONTS_DIR.
|
||||
CRAFT_FONTS_DIR = toString craft-fonts;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
checks = forAllSystems (
|
||||
pkgs: system: {
|
||||
lightcraft = self.packages.${system}.lightcraft;
|
||||
|
||||
# `nix fmt` formats the flake; this keeps it formatted.
|
||||
formatting = pkgs.runCommand "lightcraft-nix-formatting" { nativeBuildInputs = [ pkgs.nixfmt ]; } ''
|
||||
nixfmt --check ${./flake.nix} ${./nix}
|
||||
touch $out
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
formatter = forAllSystems (pkgs: _: pkgs.nixfmt);
|
||||
};
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
# LightCraft — the desktop app (`lightcraft`) and the CLI/MCP server (`lightcraft-cli`), built from
|
||||
# this repository:
|
||||
#
|
||||
# nix build # → ./result/bin/lightcraft, ./result/bin/lightcraft-cli
|
||||
#
|
||||
# Kept in step with the release workflow (.github/workflows/release.yml) and packaging/env.sh:
|
||||
# * `Cargo.lock` drives the dependencies (`cargoLock`), so there is no vendorHash to bump — only
|
||||
# crates.io, no git sources;
|
||||
# * `CRAFT_FONTS_DIR` embeds the Japanese fonts from storytold/craft-fonts (the `craft-fonts`
|
||||
# flake input); without it everything builds and runs, but Japanese text has no glyphs;
|
||||
# * the desktop file, hicolor icons and AppStream metadata are the same files the .deb/.rpm ship
|
||||
# (packaging/linux/), so `apt` and NixOS users see one identical LightCraft;
|
||||
# * `doCheck` runs `cargo test --workspace`, what `cargo xtask ci` runs. `nix build` runs it too;
|
||||
# `pkgs.lightcraft.overrideAttrs { doCheck = false; }` skips it for a faster, build-only install.
|
||||
#
|
||||
# Everything in the product is pure Rust (no C/C++ dependencies), so this needs no build system
|
||||
# beyond cargo plus the windowing headers winit's build scripts look for.
|
||||
{
|
||||
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,
|
||||
# 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 driver on NixOS. Optional: harmless on nixpkgs without it.
|
||||
autoAddDriverRunpath ? null,
|
||||
# storytold/craft-fonts checkout (flake input). `null` builds without Japanese glyphs.
|
||||
craft-fonts ? 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.lightcraft";
|
||||
|
||||
# Libraries the binaries open at run time with dlopen(): nothing links them, so no RPATH points at
|
||||
# them — winit loads libxkbcommon/libxcb, wgpu the Vulkan loader (NixOS patches that 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
|
||||
];
|
||||
|
||||
# The two native binaries. The wasm app (apps/lightcraft-web) is built by `cargo xtask web`, not
|
||||
# here; xtask is tooling.
|
||||
binaries = [
|
||||
"lightcraft"
|
||||
"lightcraft-cli"
|
||||
];
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "lightcraft";
|
||||
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";
|
||||
|
||||
# Both binaries, as the .deb/.rpm install them (`packaging/linux/package.sh`).
|
||||
cargoBuildFlags = [
|
||||
"--locked"
|
||||
"-p"
|
||||
"lightcraft"
|
||||
"-p"
|
||||
"lightcraft-cli"
|
||||
];
|
||||
|
||||
# `cargo xtask ci` runs `cargo test --workspace`. Tests that need what the sandbox cannot have
|
||||
# skip themselves with a note: corpus/ (gitignored, `cargo xtask corpus --download`) and the GPU
|
||||
# equivalence tests (no adapter).
|
||||
cargoTestFlags = [ "--workspace" ];
|
||||
|
||||
# One test thread at a time (RUST_TEST_THREADS=1). `lightcraft-catalog`'s `tests_lock` tests share
|
||||
# a process-global resource: `flock` is inherited across `fork`, so a child process that another
|
||||
# test forked keeps the parent's just-released library lock alive until its `execve` closes the
|
||||
# (CLOEXEC) fd — long enough that the sibling test sees a spurious `LockError::InUse`, on ~30 % of
|
||||
# runs on a many-core machine. Serializing the tests keeps `nix build` deterministic without
|
||||
# touching the repository. The underlying window (any `fork` in a process holding the library
|
||||
# lock — the app spawns `xdg-open`/`open`/`explorer` and `sysctl`) is a product-level question:
|
||||
# `LibraryLock::acquire` could retry on `WouldBlock` for a few hundred ms before reporting InUse.
|
||||
dontUseCargoParallelTests = true;
|
||||
|
||||
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
|
||||
];
|
||||
|
||||
env = lib.optionalAttrs (craft-fonts != null) {
|
||||
CRAFT_FONTS_DIR = toString craft-fonts;
|
||||
# A malformed font input must fail the build rather than silently ship without glyphs.
|
||||
CRAFT_FONTS_REQUIRED = "1";
|
||||
};
|
||||
|
||||
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: Inter, the app icon, each craft font.
|
||||
doc=$out/share/doc/lightcraft
|
||||
install -Dm644 -t "$doc" \
|
||||
README.md LICENSE-MIT LICENSE-APACHE NOTICE \
|
||||
assets/ATTRIBUTION.md assets/fonts/OFL-Inter.txt
|
||||
''
|
||||
+ lib.optionalString (craft-fonts != null) ''
|
||||
for lic in ${toString craft-fonts}/fonts/*/OFL.txt; do
|
||||
install -Dm644 "$lic" "$doc/OFL-$(basename "$(dirname "$lic")").txt"
|
||||
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 = "Photo library and non-destructive raw developer";
|
||||
longDescription = ''
|
||||
LightCraft organises a photo library and develops raw files non-destructively: masks,
|
||||
presets, colour grading, local adjustments and batch export, driven by the same command
|
||||
layer as its UI. It ships with lightcraft-cli, a headless renderer, command runner and MCP
|
||||
server for AI agents.
|
||||
'';
|
||||
homepage = "https://getartcraft.com/apps/lightcraft";
|
||||
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 = "lightcraft";
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user