Package Penpot 2.18.3 for Nix with NixOS module; afk devShell
Vendored from debling/nix-penpot (MIT). Fixes on top: - pnpm fetchers: drop --config.fetch-* flags (UND_ERR_INVALID_ARG on pnpm 11.27) and pin real store hashes (frontend x3, exporter, mcp) - deps-lock.json: add transitive funcool/beicon2 v2.0 so fake-git resolves potok2's pin during the shadow-cljs classpath build - expose frontend pnpm stores via passthru for rehashing - x86_64-linux only; devShell ships afk, jj, git
This commit is contained in:
commit
08b5496980
21 files changed
+7840
No files matched your search
@@ -0,0 +1,294 @@
|
||||
# Penpot's WebAssembly canvas renderer (the upstream `render-wasm/` tree):
|
||||
# Rust compiled for wasm32-unknown-emscripten with emscripten, statically
|
||||
# linking Penpot's prebuilt Skia binary distribution.
|
||||
#
|
||||
# Packaging notes (why this is not a plain buildRustPackage):
|
||||
#
|
||||
# - nixpkgs cannot cross-compile Rust for wasm32-unknown-emscripten: the
|
||||
# target is absent from `pkgsCross` and nixpkgs' `rustc` ships no std for
|
||||
# it. The Rust toolchain is therefore assembled from the official dist
|
||||
# tarballs (version pinned to the one upstream's devenv uses), and
|
||||
# nixpkgs' emscripten provides emcc/em++/emar. Upstream devenv pins
|
||||
# emsdk 4.0.6; nixpkgs' emscripten (6.0.8) accepts the same flag set.
|
||||
# - The skia-bindings build script normally downloads a prebuilt Skia
|
||||
# tarball from GitHub at build time. We fetch that tarball with fetchurl
|
||||
# and hand it to the build script via SKIA_BINARIES_URL=file://, so the
|
||||
# build itself runs sandboxed with no network access.
|
||||
# - Cargo dependencies are vendored with rustPlatform.fetchCargoVendor
|
||||
# (itself a fixed-output derivation). When penpot (or its
|
||||
# render-wasm/Cargo.lock) changes, recompute the hash: set it to
|
||||
# lib.fakeHash, run `nix build .#penpot-render-wasm`, and copy the
|
||||
# "got: sha256-..." value from the hash-mismatch error back into this
|
||||
# file. If the Rust toolchain version (rustVersion below) or the Skia
|
||||
# tarball URL changes, the corresponding fetchurl hashes must be
|
||||
# recomputed as well (same procedure: set to lib.fakeHash, read `got:`).
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
runCommand,
|
||||
fetchurl,
|
||||
patchelf,
|
||||
libgcc,
|
||||
rustPlatform,
|
||||
emscripten,
|
||||
esbuild,
|
||||
zlib,
|
||||
penpot,
|
||||
version,
|
||||
}:
|
||||
let
|
||||
# Rust version pinned by upstream's devenv (1.91.0, released 2025-10-30).
|
||||
rustVersion = "1.91.0";
|
||||
rustDistDate = "2025-10-30";
|
||||
wasmTarget = "wasm32-unknown-emscripten";
|
||||
|
||||
# Only the two systems this flake targets are wired up; the official dist
|
||||
# tarballs are host-specific, hence the per-system hashes.
|
||||
hostTriple =
|
||||
{
|
||||
x86_64-linux = "x86_64-unknown-linux-gnu";
|
||||
aarch64-linux = "aarch64-unknown-linux-gnu";
|
||||
}
|
||||
.${stdenv.hostPlatform.system}
|
||||
or (throw "penpot-render-wasm: unsupported system ${stdenv.hostPlatform.system}");
|
||||
|
||||
rustDistHashes =
|
||||
{
|
||||
x86_64-linux = {
|
||||
rustc = "sha256-pxaejLYXSvL0VxdwM3A2PY3oLOVfbMuhhYkwRbk3CHQ=";
|
||||
cargo = "sha256-cQPAP7ir6FsjMHAFqd/k8ByCaomUXYS5b6LQP9TS0Tg=";
|
||||
hostStd = "sha256-ieZSCxbBK0NSZEApjS2g3LcHR8XMLQuOR9ObXamu70k=";
|
||||
};
|
||||
aarch64-linux = {
|
||||
rustc = "sha256-8+o8lkt/O4hDN/LUEXZAMrvRci1/VVkqVHy7Ka/YfAM=";
|
||||
cargo = "sha256-AD1wCCGcoNIlrR36MB98B5sSNJlDDuB4DIV4Lgh47v8=";
|
||||
hostStd = "sha256-/yPcgfeW1k406GakT9C8rnJuNAFINTabj5OTpUQWfso=";
|
||||
};
|
||||
}
|
||||
.${stdenv.hostPlatform.system};
|
||||
|
||||
rustDist =
|
||||
name: hash:
|
||||
fetchurl {
|
||||
url = "https://static.rust-lang.org/dist/${rustDistDate}/${name}.tar.xz";
|
||||
inherit hash;
|
||||
};
|
||||
|
||||
# Minimal rustc + cargo installation (laid out like a rustup toolchain):
|
||||
# std for the host is needed for build scripts and proc-macros, std for
|
||||
# the emscripten target for the actual build. The dist binaries are
|
||||
# unpatched upstream builds, so the ELF files get their interpreter and
|
||||
# rpath pointed at nixpkgs' glibc/libgcc/zlib explicitly (autoPatchelfHook
|
||||
# proved unreliable for these binaries).
|
||||
rustToolchain =
|
||||
runCommand "penpot-render-wasm-rust-toolchain-${rustVersion}"
|
||||
{
|
||||
nativeBuildInputs = [ patchelf ];
|
||||
# The dist binaries ship pre-stripped; stripping them corrupts them.
|
||||
dontStrip = true;
|
||||
}
|
||||
''
|
||||
mkdir -p $out
|
||||
tar -xJf ${rustDist "rustc-${rustVersion}-${hostTriple}" rustDistHashes.rustc} \
|
||||
--strip-components=2 -C $out "rustc-${rustVersion}-${hostTriple}/rustc"
|
||||
tar -xJf ${rustDist "cargo-${rustVersion}-${hostTriple}" rustDistHashes.cargo} \
|
||||
--strip-components=2 -C $out "cargo-${rustVersion}-${hostTriple}/cargo"
|
||||
tar -xJf ${rustDist "rust-std-${rustVersion}-${hostTriple}" rustDistHashes.hostStd} \
|
||||
--strip-components=2 -C $out "rust-std-${rustVersion}-${hostTriple}/rust-std-${hostTriple}"
|
||||
tar -xJf ${rustDist "rust-std-${rustVersion}-${wasmTarget}" "sha256-0oJ49jPVYxTJAE058YHy1vz7kdqXSTgOsp9Hzo+Bfk4="} \
|
||||
--strip-components=2 -C $out "rust-std-${rustVersion}-${wasmTarget}/rust-std-${wasmTarget}"
|
||||
|
||||
rpath="$out/lib:${stdenv.cc.libc}/lib:${libgcc}/lib:${zlib}/lib"
|
||||
interp="$(cat ${stdenv.cc.bintools}/nix-support/dynamic-linker)"
|
||||
find $out -type f \( \
|
||||
-path "*/bin/cargo" -o -path "*/bin/rustc" -o -path "*/bin/rustdoc" \
|
||||
-o -name "librustc_driver*.so" -o -name "libLLVM.so.*" \
|
||||
-o -name rust-analyzer-proc-macro-srv -o -path "*rustlib/*/bin/*" \
|
||||
\) -print0 | while IFS= read -r -d "" f; do
|
||||
# Skip linker scripts / completions etc. that match the globs.
|
||||
[ "$(head -c 4 "$f" | od -An -tx1 | tr -d " \n")" = "7f454c46" ] || continue
|
||||
if [ -n "$(patchelf --print-interpreter "$f" 2>/dev/null)" ]; then
|
||||
patchelf --set-interpreter "$interp" "$f"
|
||||
fi
|
||||
patchelf --set-rpath "$rpath" "$f"
|
||||
done
|
||||
|
||||
# Fail the derivation if the toolchain cannot actually run sandboxed.
|
||||
$out/bin/cargo --version
|
||||
$out/bin/rustc --version
|
||||
'';
|
||||
|
||||
# Prebuilt Skia for wasm32/emscripten, pinned by the SKIA_BINARIES_URL
|
||||
# default in penpot/render-wasm/_build_env. Handed to the skia-bindings
|
||||
# build script, which accepts file:// URLs for exactly this purpose.
|
||||
skiaBinaries = fetchurl {
|
||||
url = "https://github.com/penpot/skia-binaries/releases/download/0.93.1/skia-binaries-319323662b1685a112f5-${wasmTarget}-gl-svg-textlayout-binary-cache-webp.tar.gz";
|
||||
hash = "sha256-kwqI/Cg2c0JrzDKOAnselx+JgSPjhn/y7wS40DpDTx0=";
|
||||
};
|
||||
|
||||
cargoDeps = rustPlatform.fetchCargoVendor {
|
||||
name = "penpot-render-wasm-cargo-deps";
|
||||
src = penpot + "/render-wasm";
|
||||
hash = "sha256-Qxv3feKgQe/0+43rEehoRWibCJoxvOAHFDLY3Sq5jWs=";
|
||||
};
|
||||
|
||||
# Common emcc flags, verbatim from penpot/render-wasm/_build_env, plus
|
||||
# -sDEFAULT_TO_CXX: the prebuilt Skia libraries are C++ objects that need
|
||||
# libc++abi at link time, which newer emcc no longer pulls in when the
|
||||
# linker is invoked as plain emcc (as rustc does).
|
||||
emccCommonFlags = [
|
||||
"-sDEFAULT_TO_CXX=1"
|
||||
"--no-entry"
|
||||
"--js-library"
|
||||
"src/js/wapi.js"
|
||||
"-sMALLOC=dlmalloc"
|
||||
"-sINVOKE_RUN=0"
|
||||
"-sALLOW_TABLE_GROWTH=0"
|
||||
"-sALLOW_MEMORY_GROWTH=1"
|
||||
"-sINITIAL_HEAP=268435456" # upstream: $((256 * 1024 * 1024))
|
||||
"-sMEMORY_GROWTH_GEOMETRIC_STEP=0.8"
|
||||
"-sERROR_ON_UNDEFINED_SYMBOLS=0"
|
||||
"-sMAX_WEBGL_VERSION=2"
|
||||
"-sEXPORT_NAME=createRustSkiaModule"
|
||||
"-sEXPORTED_RUNTIME_METHODS=GL,UTF8ToString,stringToUTF8,HEAPU8,HEAP32,HEAPU32,HEAPF32"
|
||||
# Upstream 2.17.x builds with -sENVIRONMENT=web; the extra node/worker
|
||||
# environments are only needed by the (post-2.17) wasm-based exporter.
|
||||
"-sENVIRONMENT=web"
|
||||
"-sMODULARIZE=1"
|
||||
"-sDISABLE_EXCEPTION_CATCHING=1"
|
||||
"-sFILESYSTEM=0"
|
||||
"-sEXPORT_ES6=1"
|
||||
];
|
||||
|
||||
# Builds one flavor of the renderer. Upstream drives both flavors through
|
||||
# render-wasm/build + _build_env; the differences are the cargo profile
|
||||
# (--release vs --profile size) and the emcc optimization level.
|
||||
buildRenderWasm =
|
||||
flavor:
|
||||
assert lib.assertOneOf "flavor" flavor [
|
||||
"frontend"
|
||||
"export"
|
||||
];
|
||||
let
|
||||
isFrontend = flavor == "frontend";
|
||||
cargoProfileFlag = if isFrontend then "--release" else "--profile size";
|
||||
profileDir = if isFrontend then "release" else "size";
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "penpot-render-wasm" + lib.optionalString (!isFrontend) "-export";
|
||||
inherit version;
|
||||
|
||||
src = penpot + "/render-wasm";
|
||||
|
||||
nativeBuildInputs = [
|
||||
rustToolchain
|
||||
emscripten
|
||||
esbuild
|
||||
];
|
||||
|
||||
env = {
|
||||
CARGO_BUILD_TARGET = wasmTarget;
|
||||
SKIA_BINARIES_URL = "file://${skiaBinaries}";
|
||||
EMCC_CFLAGS = lib.concatStringsSep " " (
|
||||
(if isFrontend then [ "-O3" ] else [ "-Oz" ]) ++ [ "-sASSERTIONS=0" ] ++ emccCommonFlags
|
||||
);
|
||||
};
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export CARGO_TARGET_DIR="$PWD/cargo-target"
|
||||
export CARGO_HOME="$PWD/cargo-home"
|
||||
export CC_wasm32_unknown_emscripten=emcc
|
||||
export AR_wasm32_unknown_emscripten=emar
|
||||
|
||||
# emcc needs a writable cache; seed it from the store copy so the
|
||||
# sysroot libraries are not rebuilt.
|
||||
export EM_CACHE="$PWD/emcc-cache"
|
||||
cp -r --reflink=auto "${emscripten}/share/emscripten/cache/." "$EM_CACHE/"
|
||||
chmod -R u+w "$EM_CACHE/"
|
||||
|
||||
# Point cargo at the vendored crates; fetchCargoVendor's config
|
||||
# ships with a @vendor@ placeholder to fill in.
|
||||
cp -r --reflink=auto "${cargoDeps}" ./cargo-vendor
|
||||
chmod -R u+w ./cargo-vendor
|
||||
mkdir -p .cargo
|
||||
substitute "${cargoDeps}/.cargo/config.toml" .cargo/config.toml \
|
||||
--subst-var-by vendor "$PWD/cargo-vendor"
|
||||
|
||||
${lib.optionalString (!isFrontend) ''
|
||||
# Penpot 2.17.2 predates the size-optimized profile that newer
|
||||
# upstream uses for the exporter flavor; add it (same definition
|
||||
# as current upstream render-wasm/Cargo.toml).
|
||||
if ! grep -q "^\[profile\.size\]" Cargo.toml; then
|
||||
cat >> Cargo.toml <<'PROFILES'
|
||||
|
||||
[profile.size]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
PROFILES
|
||||
fi
|
||||
''}
|
||||
|
||||
cargo build ${cargoProfileFlag} --offline
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
artifacts="$PWD/cargo-target/${wasmTarget}/${profileDir}"
|
||||
|
||||
mkdir -p "$out"${lib.optionalString isFrontend "/worker"}
|
||||
cp "$artifacts/render_wasm.js" "$out/render-wasm.js"
|
||||
cp "$artifacts/render_wasm.wasm" "$out/render-wasm.wasm"
|
||||
|
||||
# Match upstream copy_artifacts(): rename the wasm reference and add
|
||||
# a version query for cache busting.
|
||||
sed -i "s/render_wasm\.wasm/render-wasm.wasm?version=${version}/g" \
|
||||
"$out/render-wasm.js"
|
||||
|
||||
shared="$(find "$PWD/cargo-target/${wasmTarget}" -name render_wasm_shared.js | head -n 1)"
|
||||
cp "$shared" "$out/shared.js"
|
||||
|
||||
${lib.optionalString isFrontend ''
|
||||
# Worker bundle, exactly as upstream copy_artifacts() builds it
|
||||
# (the exporter imports the ESM module directly instead).
|
||||
esbuild "$artifacts/render_wasm.js" \
|
||||
--log-level=error \
|
||||
--outfile="$out/worker/render.js" \
|
||||
--platform=neutral \
|
||||
--format=iife \
|
||||
--global-name=WasmModule
|
||||
''}
|
||||
|
||||
# Fail loudly instead of shipping an empty/broken artifact.
|
||||
test -s "$out/render-wasm.js"
|
||||
test -s "$out/render-wasm.wasm"
|
||||
test -s "$out/shared.js"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description =
|
||||
if isFrontend then
|
||||
"Penpot WebAssembly canvas renderer (frontend flavor)"
|
||||
else
|
||||
"Penpot WebAssembly canvas renderer (exporter flavor)";
|
||||
homepage = "https://penpot.app";
|
||||
license = lib.licenses.mpl20;
|
||||
platforms = lib.platforms.linux;
|
||||
};
|
||||
};
|
||||
in
|
||||
let
|
||||
frontend = buildRenderWasm "frontend";
|
||||
in
|
||||
frontend
|
||||
// {
|
||||
passthru = frontend.passthru or { } // {
|
||||
export = buildRenderWasm "export";
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user