Desktop: Bundle for Linux with Nix (#3569)

bundle for linux with nix
This commit is contained in:
Timon 2026-01-06 00:32:55 +00:00 committed by GitHub
parent c2a29470c3
commit cf85ec736f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 225 additions and 122 deletions

View File

@ -0,0 +1,28 @@
name: Build Linux Bundle
on:
workflow_dispatch: {}
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Free disk space
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
- name: Build Linux Bundle
run: nix build .nix#graphite-bundle.tar.xz && cp ./result ./graphite-bundle.tar.xz
- name: Upload Linux Bundle
uses: actions/upload-artifact@v4
with:
path: graphite-bundle.tar.xz

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
branding/ branding/
target/ target/
result/ result/
.flatpak-builder/
*.spv *.spv
*.exrc *.exrc
perf.data* perf.data*

View File

@ -1,11 +1,11 @@
{ pkgs, inputs, ... }: { pkgs, inputs, ... }:
let let
cef = pkgs.cef-binary.overrideAttrs (_: _: { cef = pkgs.cef-binary.overrideAttrs {
postInstall = '' postInstall = ''
strip $out/Release/*.so* strip $out/Release/*.so*
''; '';
}); };
cefPath = pkgs.runCommand "cef-path" { } '' cefPath = pkgs.runCommand "cef-path" { } ''
mkdir -p $out mkdir -p $out
@ -14,11 +14,13 @@ let
find ${cef}/Release -name "*" -type f -exec ln -s {} $out/ \; find ${cef}/Release -name "*" -type f -exec ln -s {} $out/ \;
find ${cef}/Resources -name "*" -maxdepth 1 -exec ln -s {} $out/ \; find ${cef}/Resources -name "*" -maxdepth 1 -exec ln -s {} $out/ \;
echo '${builtins.toJSON { echo '${
builtins.toJSON {
type = "minimal"; type = "minimal";
name = builtins.baseNameOf cef.src.url; name = builtins.baseNameOf cef.src.url;
sha1 = ""; sha1 = "";
}}' > $out/archive.json }
}' > $out/archive.json
''; '';
in in
{ {

View File

@ -33,7 +33,10 @@
info = { info = {
pname = "graphite"; pname = "graphite";
version = "unstable"; version = "unstable";
src = pkgs.lib.cleanSourceWith {
src = ./..; src = ./..;
filter = path: type: !(type == "directory" && builtins.baseNameOf path == ".nix");
};
}; };
pkgs = import inputs.nixpkgs { pkgs = import inputs.nixpkgs {
@ -129,6 +132,13 @@
embeddedResources = false; embeddedResources = false;
dev = true; dev = true;
}; };
graphite-bundle = import ./pkgs/graphite-bundle.nix {
inherit pkgs graphite;
};
graphite-flatpak-manifest = import ./pkgs/graphite-flatpak-manifest.nix {
inherit pkgs;
archive = graphite-bundle.tar;
};
#TODO: graphene-cli = import ./pkgs/graphene-cli.nix { inherit info pkgs inputs deps libs tools; }; #TODO: graphene-cli = import ./pkgs/graphene-cli.nix { inherit info pkgs inputs deps libs tools; };
raster-nodes-shaders = import ./pkgs/raster-nodes-shaders.nix { raster-nodes-shaders = import ./pkgs/raster-nodes-shaders.nix {
inherit inherit

View File

@ -0,0 +1,91 @@
{
pkgs,
graphite,
}:
let
bundle =
{
pkgs,
graphite,
archive ? false,
compression ? null,
passthru ? {},
}:
(
let
tar = if compression == null then archive else true;
nameArchiveSuffix = if tar then ".tar" else "";
nameCompressionSuffix = if compression == null then "" else "." + compression;
name = "graphite-bundle${nameArchiveSuffix}${nameCompressionSuffix}";
build = ''
mkdir -p out
mkdir -p out/bin
cp ${graphite}/bin/.graphite-wrapped out/bin/graphite
chmod -v +w out/bin/graphite
patchelf --set-rpath '$ORIGIN/../lib:$ORIGIN/../lib/cef' --set-interpreter '/lib64/ld-linux-x86-64.so.2' out/bin/graphite
mkdir -p out/lib/cef
mkdir -p ./cef
tar -xvf ${pkgs.cef-binary.src} -C ./cef --strip-components=1
cp -r ./cef/Release/* out/lib/cef/
cp -r ./cef/Resources/* out/lib/cef/
find "out/lib/cef/locales" -type f ! -name 'en-US*' -delete
${pkgs.bintools}/bin/strip out/lib/cef/*.so*
cp -r ${graphite}/share out/share
'';
install =
if tar then
''
cd out
tar -c \
--sort=name \
--mtime='@1' --clamp-mtime \
--owner=0 --group=0 --numeric-owner \
--mode='u=rwX,go=rX' \
--format=posix \
--pax-option=delete=atime,delete=ctime \
--no-acls --no-xattrs --no-selinux \
* ${
if compression == "xz" then
"| xz "
else if compression == "gz" then
"| gzip -n "
else
""
}> $out
''
else
''
mkdir -p $out
cp -r out/* $out/
'';
in
pkgs.runCommand name
{
inherit passthru;
}
''
${build}
${install}
''
);
in
bundle {
inherit pkgs graphite;
passthru = {
tar = bundle {
inherit pkgs graphite;
archive = true;
passthru = {
gz = bundle {
inherit pkgs graphite;
compression = "gz";
};
xz = bundle {
inherit pkgs graphite;
compression = "xz";
};
};
};
};
}

View File

@ -0,0 +1,37 @@
{
pkgs,
archive,
}:
(pkgs.formats.json { }).generate "art.graphite.Graphite.json" {
app-id = "art.graphite.Graphite";
runtime = "org.freedesktop.Platform";
runtime-version = "25.08";
sdk = "org.freedesktop.Sdk";
command = "graphite";
finish-args = [
"--device=dri"
"--share=ipc"
"--socket=wayland"
"--socket=fallback-x11"
"--share=network"
];
modules = [
{
name = "app";
buildsystem = "simple";
build-commands = [
"mkdir -p /app"
"cp -r ./* /app/"
"chmod +x /app/bin/*"
];
sources = [
{
type = "archive";
path = archive;
strip-components = 0;
}
];
}
];
}

View File

@ -124,7 +124,7 @@ deps.crane.lib.buildPackage (
cp $src/desktop/assets/*.desktop $out/share/applications/ cp $src/desktop/assets/*.desktop $out/share/applications/
mkdir -p $out/share/icons/hicolor/scalable/apps mkdir -p $out/share/icons/hicolor/scalable/apps
cp ${branding}/app-icons/graphite.svg $out/share/icons/hicolor/scalable/apps/ cp ${branding}/app-icons/graphite.svg $out/share/icons/hicolor/scalable/apps/art.graphite.Graphite.svg
''; '';
postFixup = '' postFixup = ''

116
Cargo.lock generated
View File

@ -774,7 +774,7 @@ dependencies = [
"thiserror 2.0.16", "thiserror 2.0.16",
"tracing", "tracing",
"wgpu", "wgpu",
"windows 0.58.0", "windows",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@ -1685,6 +1685,17 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fd-lock"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "fdeflate" name = "fdeflate"
version = "0.3.7" version = "0.3.7"
@ -1829,7 +1840,7 @@ dependencies = [
"read-fonts 0.35.0", "read-fonts 0.35.0",
"roxmltree", "roxmltree",
"smallvec", "smallvec",
"windows 0.58.0", "windows",
"windows-core 0.58.0", "windows-core 0.58.0",
"yeslogic-fontconfig-sys", "yeslogic-fontconfig-sys",
] ]
@ -2147,7 +2158,7 @@ dependencies = [
"log", "log",
"presser", "presser",
"thiserror 1.0.69", "thiserror 1.0.69",
"windows 0.58.0", "windows",
] ]
[[package]] [[package]]
@ -2330,6 +2341,7 @@ dependencies = [
"ctrlc", "ctrlc",
"derivative", "derivative",
"dirs", "dirs",
"fd-lock",
"futures", "futures",
"glam", "glam",
"graphite-desktop-embedded-resources", "graphite-desktop-embedded-resources",
@ -2339,7 +2351,6 @@ dependencies = [
"objc2-app-kit 0.3.2", "objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
"open", "open",
"pidlock",
"rand 0.9.2", "rand 0.9.2",
"rfd", "rfd",
"ron", "ron",
@ -2350,7 +2361,7 @@ dependencies = [
"vello", "vello",
"wgpu", "wgpu",
"window_clipboard", "window_clipboard",
"windows 0.58.0", "windows",
"winit", "winit",
] ]
@ -4370,17 +4381,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pidlock"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f837924d5368f9f35a1c404699de3c074311358035c77d7164f5948c08b31382"
dependencies = [
"nix",
"thiserror 1.0.69",
"windows 0.62.2",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.10" version = "1.1.10"
@ -7224,7 +7224,7 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
"wgpu-types", "wgpu-types",
"windows 0.58.0", "windows",
"windows-core 0.58.0", "windows-core 0.58.0",
] ]
@ -7297,27 +7297,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core 0.62.2",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core 0.62.2",
]
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.58.0" version = "0.58.0"
@ -7344,30 +7323,6 @@ dependencies = [
"windows-strings 0.4.2", "windows-strings 0.4.2",
] ]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-threading",
]
[[package]] [[package]]
name = "windows-implement" name = "windows-implement"
version = "0.58.0" version = "0.58.0"
@ -7424,16 +7379,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-registry" name = "windows-registry"
version = "0.5.3" version = "0.5.3"
@ -7463,15 +7408,6 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-strings" name = "windows-strings"
version = "0.1.0" version = "0.1.0"
@ -7491,15 +7427,6 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.45.0"
@ -7593,15 +7520,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.0", "windows_x86_64_msvc 0.53.0",
] ]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.2" version = "0.42.2"

View File

@ -43,7 +43,7 @@ open = { workspace = true }
rand = { workspace = true, features = ["thread_rng"] } rand = { workspace = true, features = ["thread_rng"] }
serde = { workspace = true } serde = { workspace = true }
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive"] }
pidlock = "0.2.2" fd-lock = "4.0.4"
ctrlc = "3.5.1" ctrlc = "3.5.1"
window_clipboard = "0.5" window_clipboard = "0.5"

View File

@ -5,7 +5,7 @@ Comment=Open-source vector & raster graphics editor. Featuring node based proced
Exec=graphite Exec=graphite
Terminal=false Terminal=false
Type=Application Type=Application
Icon=graphite Icon=art.graphite.Graphite
Categories=Graphics;VectorGraphics;RasterGraphics; Categories=Graphics;VectorGraphics;RasterGraphics;
Keywords=graphite;editor;vector;raster;procedural;design; Keywords=graphite;editor;vector;raster;procedural;design;
StartupWMClass=art.graphite.Graphite StartupWMClass=art.graphite.Graphite

View File

@ -111,6 +111,8 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
let settings = Settings { let settings = Settings {
multi_threaded_message_loop: 1, multi_threaded_message_loop: 1,
#[cfg(target_os = "linux")]
no_sandbox: 1,
..Self::common_settings(&instance_dir) ..Self::common_settings(&instance_dir)
}; };

View File

@ -34,6 +34,7 @@ impl<H: CefEventHandler> ImplApp for BrowserProcessAppImpl<H> {
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) { fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
if let Some(cmd) = command_line { if let Some(cmd) = command_line {
cmd.append_switch_with_value(Some(&CefString::from("renderer-process-limit")), Some(&CefString::from("1"))); cmd.append_switch_with_value(Some(&CefString::from("renderer-process-limit")), Some(&CefString::from("1")));
cmd.append_switch_with_value(Some(&CefString::from("password-store")), Some(&CefString::from("basic")));
cmd.append_switch_with_value(Some(&CefString::from("disk-cache-size")), Some(&CefString::from("0"))); cmd.append_switch_with_value(Some(&CefString::from("disk-cache-size")), Some(&CefString::from("0")));
cmd.append_switch(Some(&CefString::from("incognito"))); cmd.append_switch(Some(&CefString::from("incognito")));
cmd.append_switch(Some(&CefString::from("no-first-run"))); cmd.append_switch(Some(&CefString::from("no-first-run")));

View File

@ -1,4 +1,5 @@
use clap::Parser; use clap::Parser;
use std::io::Write;
use std::process::exit; use std::process::exit;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use winit::event_loop::EventLoop; use winit::event_loop::EventLoop;
@ -38,26 +39,35 @@ pub fn start() {
return; return;
} }
let mut lock = pidlock::Pidlock::new_validated(dirs::app_data_dir().join(APP_LOCK_FILE_NAME)).unwrap(); let cli = Cli::parse();
match lock.acquire() {
Ok(lock) => { let Ok(lock_file) = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME))
else {
tracing::error!("Failed to open lock file, Exiting.");
exit(1);
};
let mut lock = fd_lock::RwLock::new(lock_file);
let lock = match lock.try_write() {
Ok(mut guard) => {
tracing::info!("Acquired application lock"); tracing::info!("Acquired application lock");
lock let _ = guard.set_len(0);
let _ = write!(guard, "{}", std::process::id());
let _ = guard.sync_all();
guard
} }
Err(pidlock::PidlockError::LockExists) => { Err(_) => {
tracing::error!("Another instance is already running, Exiting."); tracing::error!("Another instance is already running, Exiting.");
exit(0);
}
Err(err) => {
tracing::error!("Failed to acquire application lock: {err}");
exit(1); exit(1);
} }
}; };
App::init(); App::init();
let cli = Cli::parse();
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context()); let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
@ -94,6 +104,9 @@ pub fn start() {
event_loop.run_app(&mut app).unwrap(); event_loop.run_app(&mut app).unwrap();
// Explicitly drop the instance lock
drop(lock);
// Workaround for a Windows-specific exception that occurs when `app` is dropped. // Workaround for a Windows-specific exception that occurs when `app` is dropped.
// The issue causes the window to hang for a few seconds before closing. // The issue causes the window to hang for a few seconds before closing.
// Appears to be related to CEF object destruction order. // Appears to be related to CEF object destruction order.