From cb85ca0ad86a2220610921ab1b803a0102b4edb5 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 26 Feb 2026 15:45:21 +0000 Subject: [PATCH 01/23] move nix flake to root --- .github/workflows/build-linux-bundle.yml | 8 +- .github/workflows/build-nix-package.yml | 2 +- .github/workflows/provide-shaders.yml | 2 +- .nix/default.nix | 81 +++++++++++++++++ .nix/flake.nix | 110 ----------------------- .nix/shell.nix | 28 ------ .nix/flake.lock => flake.lock | 15 ---- flake.nix | 25 ++++++ 8 files changed, 112 insertions(+), 159 deletions(-) create mode 100644 .nix/default.nix delete mode 100644 .nix/flake.nix delete mode 100644 .nix/shell.nix rename .nix/flake.lock => flake.lock (72%) create mode 100644 flake.nix diff --git a/.github/workflows/build-linux-bundle.yml b/.github/workflows/build-linux-bundle.yml index 9ca2334ade..d5584f3542 100644 --- a/.github/workflows/build-linux-bundle.yml +++ b/.github/workflows/build-linux-bundle.yml @@ -25,7 +25,7 @@ jobs: run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache - name: Build Nix Package - run: nix build .nix --no-link --print-out-paths + run: nix build --no-link --print-out-paths - name: Push to Nix Cache if: github.ref == 'refs/heads/master' || inputs.push_to_cache == true @@ -33,10 +33,10 @@ jobs: NIX_CACHE_AUTH_TOKEN: ${{ secrets.NIX_CACHE_AUTH_TOKEN }} run: | nix run nixpkgs#cachix -- authtoken $NIX_CACHE_AUTH_TOKEN - nix build .nix --no-link --print-out-paths | nix run nixpkgs#cachix -- push graphite + nix build --no-link --print-out-paths | nix run nixpkgs#cachix -- push graphite - name: Build Linux Bundle - run: nix build .nix#graphite-bundle.tar.xz && cp ./result ./graphite-linux-bundle.tar.xz + run: nix build .#graphite-bundle.tar.xz && cp ./result ./graphite-linux-bundle.tar.xz - name: Upload Linux Bundle uses: actions/upload-artifact@v4 @@ -53,7 +53,7 @@ jobs: - name: Build Flatpak run: | - nix build .nix#graphite-flatpak-manifest + nix build .#graphite-flatpak-manifest rm -rf .flatpak mkdir -p .flatpak diff --git a/.github/workflows/build-nix-package.yml b/.github/workflows/build-nix-package.yml index 2da86b2d34..45669d0309 100644 --- a/.github/workflows/build-nix-package.yml +++ b/.github/workflows/build-nix-package.yml @@ -14,4 +14,4 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@main - name: Build Nix Package Dev - run: nix build .nix#graphite-dev --print-build-logs + run: nix build .#graphite-dev --print-build-logs diff --git a/.github/workflows/provide-shaders.yml b/.github/workflows/provide-shaders.yml index 7e38ecf303..b6006a7b24 100644 --- a/.github/workflows/provide-shaders.yml +++ b/.github/workflows/provide-shaders.yml @@ -17,7 +17,7 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@main - name: Build graphene raster nodes shaders - run: nix build .nix#graphite-raster-nodes-shaders && cp result raster_nodes_shaders_entrypoint.wgsl + run: nix build .#graphite-raster-nodes-shaders && cp result raster_nodes_shaders_entrypoint.wgsl - name: Upload graphene raster nodes shaders to artifacts repository run: | diff --git a/.nix/default.nix b/.nix/default.nix new file mode 100644 index 0000000000..e77cc4a60d --- /dev/null +++ b/.nix/default.nix @@ -0,0 +1,81 @@ +inputs: + +let + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forAllSystems = f: inputs.nixpkgs.lib.genAttrs systems (system: f system); + args = + system: + ( + let + lib = inputs.nixpkgs.lib // { + call = p: import p args; + }; + + pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ (import inputs.rust-overlay) ]; + }; + + info = { + pname = "graphite"; + version = "unstable"; + src = inputs.nixpkgs.lib.cleanSourceWith { + src = ./..; + filter = path: type: !(type == "directory" && builtins.baseNameOf path == ".nix"); + }; + cargoVendored = deps.crane.lib.vendorCargoDeps { inherit (info) src; }; + }; + + deps = { + crane = lib.call ./deps/crane.nix; + cef = lib.call ./deps/cef.nix; + rustGPU = lib.call ./deps/rust-gpu.nix; + }; + + args = { + inherit system; + inherit (inputs) self; + inherit inputs; + inherit pkgs; + inherit lib; + inherit info; + inherit deps; + } + // inputs; + in + args + ); + withArgs = f: forAllSystems (system: f (args system)); +in +{ + packages = withArgs ( + { lib, ... }: + rec { + default = graphite; + graphite = (lib.call ./pkgs/graphite.nix) { }; + graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; }; + graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.nix; + graphite-branding = lib.call ./pkgs/graphite-branding.nix; + graphite-bundle = lib.call ./pkgs/graphite-bundle.nix; + graphite-flatpak-manifest = lib.call ./pkgs/graphite-flatpak-manifest.nix; + + # TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix; + + tools = { + third-party-licenses = lib.call ./pkgs/tools/third-party-licenses.nix; + }; + } + ); + + devShells = withArgs ( + { lib, ... }: + { + default = lib.call ./dev.nix; + } + ); + + formatter = withArgs ({ pkgs, ... }: pkgs.nixfmt-tree); +} diff --git a/.nix/flake.nix b/.nix/flake.nix deleted file mode 100644 index 270bb870b2..0000000000 --- a/.nix/flake.nix +++ /dev/null @@ -1,110 +0,0 @@ -# This is a helper file for people using NixOS as their operating system. -# If you don't know what this file does, you can safely ignore it. -# This file defines the reproducible development environment for the project. -# -# Development Environment: -# - Provides all necessary tools for Rust/Wasm development -# - Includes dependencies for desktop app development -# - Sets up profiling and debugging tools -# - Configures mold as the default linker for faster builds -# -# Usage: -# - Development shell: `nix develop .nix` from the project root -# - Run in dev shell with direnv: add `use flake` to .envrc -{ - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - rust-overlay = { - url = "github:oxalica/rust-overlay"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - crane.url = "github:ipetkov/crane"; - - # This is used to provide a identical development shell at `shell.nix` for users that do not use flakes - flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"; - }; - - outputs = - inputs: - ( - let - systems = [ - "x86_64-linux" - "aarch64-linux" - ]; - forAllSystems = f: inputs.nixpkgs.lib.genAttrs systems (system: f system); - args = - system: - ( - let - lib = inputs.nixpkgs.lib // { - call = p: import p args; - }; - - pkgs = import inputs.nixpkgs { - inherit system; - overlays = [ (import inputs.rust-overlay) ]; - }; - - info = { - pname = "graphite"; - version = "unstable"; - src = inputs.nixpkgs.lib.cleanSourceWith { - src = ./..; - filter = path: type: !(type == "directory" && builtins.baseNameOf path == ".nix"); - }; - cargoVendored = deps.crane.lib.vendorCargoDeps { inherit (info) src; }; - }; - - deps = { - crane = lib.call ./deps/crane.nix; - cef = lib.call ./deps/cef.nix; - rustGPU = lib.call ./deps/rust-gpu.nix; - }; - - args = { - inherit system; - inherit (inputs) self; - inherit inputs; - inherit pkgs; - inherit lib; - inherit info; - inherit deps; - } - // inputs; - in - args - ); - withArgs = f: forAllSystems (system: f (args system)); - in - { - packages = withArgs ( - { lib, ... }: - rec { - default = graphite; - graphite = (lib.call ./pkgs/graphite.nix) { }; - graphite-dev = (lib.call ./pkgs/graphite.nix) { dev = true; }; - graphite-raster-nodes-shaders = lib.call ./pkgs/graphite-raster-nodes-shaders.nix; - graphite-branding = lib.call ./pkgs/graphite-branding.nix; - graphite-bundle = lib.call ./pkgs/graphite-bundle.nix; - graphite-flatpak-manifest = lib.call ./pkgs/graphite-flatpak-manifest.nix; - - # TODO: graphene-cli = lib.call ./pkgs/graphene-cli.nix; - - tools = { - third-party-licenses = lib.call ./pkgs/tools/third-party-licenses.nix; - }; - } - ); - - devShells = withArgs ( - { lib, ... }: - { - default = lib.call ./dev.nix; - } - ); - - formatter = withArgs ({ pkgs, ... }: pkgs.nixfmt-tree); - } - ); -} diff --git a/.nix/shell.nix b/.nix/shell.nix deleted file mode 100644 index 5b13af2e20..0000000000 --- a/.nix/shell.nix +++ /dev/null @@ -1,28 +0,0 @@ -# This is a helper file for people using NixOS as their operating system. -# If you don't know what this file does, you can safely ignore it. - -# If you are using Nix as your package manager, you can run 'nix-shell .nix' -# in the root directory of the project and Nix will open a bash shell -# with all the packages needed to build and run Graphite installed. -# A shell.nix file is used in the Nix ecosystem to define a development -# environment with specific dependencies. When you enter a Nix shell using -# this file, it ensures that all the specified tools and libraries are -# available regardless of the host system's configuration. This provides -# a reproducible development environment across different machines and developers. - -# You can enter the Nix shell and run Graphite like normal with: -# > npm start -# Or you can run it like this without needing to first enter the Nix shell: -# > nix-shell .nix --command "npm start" - -# Uses flake compat to provide a development shell that is identical to the one defined in the flake -(import ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - nodeName = lock.nodes.root.inputs.flake-compat; - in - fetchTarball { - url = lock.nodes.${nodeName}.locked.url; - sha256 = lock.nodes.${nodeName}.locked.narHash; - } -) { src = ./.; }).shellNix diff --git a/.nix/flake.lock b/flake.lock similarity index 72% rename from .nix/flake.lock rename to flake.lock index 7bb7e2f254..fbc34b8adb 100644 --- a/.nix/flake.lock +++ b/flake.lock @@ -15,20 +15,6 @@ "type": "github" } }, - "flake-compat": { - "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", - "revCount": 69, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, "nixpkgs": { "locked": { "lastModified": 1770197578, @@ -48,7 +34,6 @@ "root": { "inputs": { "crane": "crane", - "flake-compat": "flake-compat", "nixpkgs": "nixpkgs", "rust-overlay": "rust-overlay" } diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..04f0e80fea --- /dev/null +++ b/flake.nix @@ -0,0 +1,25 @@ +# This is a helper file for people using NixOS as their operating system. +# If you don't know what this file does, you can safely ignore it. +# This file defines the reproducible development environment for the project. +# +# Development Environment: +# - Provides all necessary tools for Rust/Wasm development +# - Includes dependencies for desktop app development +# - Sets up profiling and debugging tools +# - Configures mold as the default linker for faster builds +# +# Usage: +# - Development shell: `nix develop` from the project root +# - Run in dev shell with direnv: add `use flake` to .envrc +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + crane.url = "github:ipetkov/crane"; + }; + + outputs = inputs: import ./.nix inputs; +} From 9836250455be69c260bc014e33d862e26b443290 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 26 Feb 2026 16:02:23 +0000 Subject: [PATCH 02/23] cargo run tool --- .github/workflows/build-mac-bundle.yml | 2 +- .github/workflows/build-production.yml | 4 +- .github/workflows/build-win-bundle.yml | 2 +- .github/workflows/ci.yml | 6 +- .github/workflows/comment-!build-commands.yml | 10 +- .github/workflows/deploy-master.yml | 6 +- Cargo.lock | 4 + Cargo.toml | 3 +- package-lock.json | 6 -- package.json | 20 ---- tools/building/Cargo.toml | 8 ++ tools/building/src/checks.rs | 93 +++++++++++++++++++ tools/building/src/lib.rs | 56 +++++++++++ tools/building/src/main.rs | 84 +++++++++++++++++ .../volunteer/guide/project-setup/_index.md | 8 +- 15 files changed, 262 insertions(+), 50 deletions(-) delete mode 100644 package-lock.json delete mode 100644 package.json create mode 100644 tools/building/Cargo.toml create mode 100644 tools/building/src/checks.rs create mode 100644 tools/building/src/lib.rs create mode 100644 tools/building/src/main.rs diff --git a/.github/workflows/build-mac-bundle.yml b/.github/workflows/build-mac-bundle.yml index 826e5f0aa8..08f9dd043f 100644 --- a/.github/workflows/build-mac-bundle.yml +++ b/.github/workflows/build-mac-bundle.yml @@ -67,7 +67,7 @@ jobs: - name: Build Mac Bundle env: CARGO_TERM_COLOR: always - run: npm run build-desktop + run: cargo run desktop build - name: Stage Artifacts shell: bash diff --git a/.github/workflows/build-production.yml b/.github/workflows/build-production.yml index 1be2e3e947..b01f090e92 100644 --- a/.github/workflows/build-production.yml +++ b/.github/workflows/build-production.yml @@ -52,9 +52,7 @@ jobs: - name: 🌐 Build Graphite web code env: NODE_ENV: production - run: | - cd frontend - mold -run npm run build + run: mold -run cargo run web build - name: 📤 Publish to Cloudflare Pages id: cloudflare diff --git a/.github/workflows/build-win-bundle.yml b/.github/workflows/build-win-bundle.yml index ee72140db8..49983af141 100644 --- a/.github/workflows/build-win-bundle.yml +++ b/.github/workflows/build-win-bundle.yml @@ -73,7 +73,7 @@ jobs: shell: bash # `cargo-about` refuses to run in powershell env: CARGO_TERM_COLOR: always - run: npm run build-desktop + run: cargo run desktop build - name: Stage Artifacts shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63621a0f1e..1a7558887f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: if: steps.skip-check.outputs.skip-check != 'true' uses: actions/setup-node@v4 with: - node-version: 'latest' + node-version: "latest" - name: 🚧 Install build dependencies if: steps.skip-check.outputs.skip-check != 'true' @@ -98,9 +98,7 @@ jobs: if: steps.skip-check.outputs.skip-check != 'true' env: NODE_ENV: production - run: | - cd frontend - mold -run npm run build + run: mold -run cargo run web build - name: 📤 Publish to Cloudflare Pages if: steps.skip-check.outputs.skip-check != 'true' diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml index f2ec321758..e7cbdd93f8 100644 --- a/.github/workflows/comment-!build-commands.yml +++ b/.github/workflows/comment-!build-commands.yml @@ -82,10 +82,10 @@ jobs: - name: ⌨ Set build command based on comment id: build_command run: | - if [[ "${{ github.event.comment.body }}" == "!build-dev" ]]; then - echo "command=build-dev" >> $GITHUB_OUTPUT + if [[ "${{ github.event.comment.body }}" == "!build-debug" ]]; then + echo "command=build debug" >> $GITHUB_OUTPUT elif [[ "${{ github.event.comment.body }}" == "!build-profiling" ]]; then - echo "command=build-profiling" >> $GITHUB_OUTPUT + echo "command=build profiling" >> $GITHUB_OUTPUT elif [[ "${{ github.event.comment.body }}" == "!build" ]]; then echo "command=build" >> $GITHUB_OUTPUT else @@ -108,9 +108,7 @@ jobs: env: NODE_ENV: production if: ${{ success() || failure()}} - run: | - cd frontend - mold -run npm run ${{ steps.build_command.outputs.command }} + run: mold -run cargo run web ${{ steps.build_command.outputs.command }} - name: ❗ Warn on build failure if: ${{ failure() }} diff --git a/.github/workflows/deploy-master.yml b/.github/workflows/deploy-master.yml index 852af0ad03..57f5cc2676 100644 --- a/.github/workflows/deploy-master.yml +++ b/.github/workflows/deploy-master.yml @@ -31,7 +31,7 @@ jobs: - name: 🟢 Install the latest Node.js uses: actions/setup-node@v4 with: - node-version: 'latest' + node-version: latest - name: 🚧 Install build dependencies run: | @@ -49,9 +49,7 @@ jobs: - name: 🌐 Build Graphite web code env: NODE_ENV: production - run: | - cd frontend - mold -run npm run build + run: mold -run cargo run web build - name: 📤 Publish to Cloudflare Pages id: cloudflare diff --git a/Cargo.lock b/Cargo.lock index 6bf8977a92..36dd53d6dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,6 +609,10 @@ dependencies = [ "tokio", ] +[[package]] +name = "building" +version = "0.0.0" + [[package]] name = "built" version = "0.7.7" diff --git a/Cargo.toml b/Cargo.toml index 012978fb17..893cdda80d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "node-graph/node-macro", "node-graph/preprocessor", "proc-macros", + "tools/building", "tools/crate-hierarchy-viz", "tools/third-party-licenses", "tools/editor-message-tree", @@ -35,12 +36,12 @@ default-members = [ "libraries/path-bool", "libraries/math-parser", "node-graph/graph-craft", - "node-graph/graphene-cli", "node-graph/interpreted-executor", "node-graph/node-macro", "node-graph/preprocessor", # blocked by https://github.com/rust-lang/cargo/issues/16000 # "proc-macros", + "tools/building", ] resolver = "2" diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index a8f7238008..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Graphite", - "lockfileVersion": 2, - "requires": true, - "packages": {} -} diff --git a/package.json b/package.json deleted file mode 100644 index b49c6ca203..0000000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "description": "A convenience package for calling the real package.json in ./frontend", - "private": true, - "scripts": { - "---------- DEV SERVER ----------": "", - "start": "cd frontend && npm start", - "start-desktop": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses --features desktop && cargo run -p graphite-desktop-bundle -- open", - "profiling": "cd frontend && npm run profiling", - "production": "cd frontend && npm run production", - "---------- BUILDS ----------": "", - "build-dev": "cd frontend && npm run build-dev", - "build-profiling": "cd frontend && npm run build-profiling", - "build": "cd frontend && npm run build", - "build-desktop": "cd frontend && npm run build-native && cargo run -p third-party-licenses --features desktop && cargo run -r -p graphite-desktop-bundle", - "build-desktop-dev": "cd frontend && npm run build-native-dev && cargo run -p third-party-licenses --features desktop && cargo run -p graphite-desktop-bundle", - "---------- UTILITIES ----------": "", - "lint": "cd frontend && npm run lint", - "lint-fix": "cd frontend && npm run lint-fix" - } -} diff --git a/tools/building/Cargo.toml b/tools/building/Cargo.toml new file mode 100644 index 0000000000..fce23d009b --- /dev/null +++ b/tools/building/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "building" +edition.workspace = true +version.workspace = true +license.workspace = true +authors.workspace = true + +default-run = "building" diff --git a/tools/building/src/checks.rs b/tools/building/src/checks.rs new file mode 100644 index 0000000000..42cc8bcbaa --- /dev/null +++ b/tools/building/src/checks.rs @@ -0,0 +1,93 @@ +use std::process::Command; + +struct Dependency { + command: &'static str, + args: &'static [&'static str], + name: &'static str, + expected_version: Option<&'static str>, +} + +const DEPENDENCIES: &[Dependency] = &[ + Dependency { + command: "rustc", + args: &["--version"], + name: "Rust", + expected_version: None, + }, + Dependency { + command: "cargo-about", + args: &["--version"], + name: "cargo-about", + expected_version: None, + }, + Dependency { + command: "cargo-watch", + args: &["--version"], + name: "cargo-watch", + expected_version: None, + }, + Dependency { + command: "wasm-bindgen", + args: &["--version"], + name: "wasm-bindgen-cli", + expected_version: Some("0.2.100"), + }, + Dependency { + command: "wasm-pack", + args: &["--version"], + name: "wasm-pack", + expected_version: None, + }, + Dependency { + command: "node", + args: &["--version"], + name: "Node.js", + expected_version: None, + }, +]; + +pub fn check_dependencies() { + let mut failures = Vec::new(); + + for dep in DEPENDENCIES { + match Command::new(dep.command).args(dep.args).output() { + Ok(output) if output.status.success() => { + let version_output = String::from_utf8_lossy(&output.stdout); + let version = version_output.trim(); + + if let Some(expected) = dep.expected_version { + if version.contains(expected) { + eprintln!(" ✓ {} ({})", dep.name, version); + } else { + eprintln!(" ✗ {} (found {}, expected {})", dep.name, version, expected); + failures.push(format!("{}: version mismatch (found {}, expected {})", dep.name, version, expected)); + } + } else { + eprintln!(" ✓ {} ({})", dep.name, version); + } + } + Ok(output) => { + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!(" ✗ {} — command failed: {}", dep.name, stderr.trim()); + failures.push(format!("{}: not installed or not working", dep.name)); + } + Err(_) => { + eprintln!(" ✗ {} — not found", dep.name); + failures.push(format!("{}: not found in PATH", dep.name)); + } + } + } + + eprintln!(); + + if failures.is_empty() { + eprintln!("All dependencies are installed."); + } else { + eprintln!("{} missing or misconfigured dependenc{}:", failures.len(), if failures.len() == 1 { "y" } else { "ies" }); + for failure in &failures { + eprintln!(" - {failure}"); + } + eprintln!(); + eprintln!("See: https://graphite.rs/volunteer/guide/project-setup/"); + } +} diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs new file mode 100644 index 0000000000..e784a00ae9 --- /dev/null +++ b/tools/building/src/lib.rs @@ -0,0 +1,56 @@ +pub mod checks; + +use std::process; + +pub enum Profile { + Default, + Release, + Debug, + Profiling, + Error, +} + +impl From<&[&str]> for Profile { + fn from(arg: &[&str]) -> Self { + arg.first().map(|s| s.to_string()).as_deref().unwrap_or_default().into() + } +} + +impl From<&str> for Profile { + fn from(arg: &str) -> Self { + match arg { + "release" => Profile::Release, + "debug" => Profile::Debug, + "profiling" => Profile::Profiling, + _ if arg.is_empty() => Profile::Default, + _ => Profile::Error, + } + } +} + +pub fn run(comand: &str) { + run_from(comand, None); +} + +pub fn run_in_frontend_dir(comand: &str) { + run_from(comand, Some("frontend")); +} + +pub fn run_from(comand: &str, dir: Option<&str>) { + let workspace_dir = std::path::PathBuf::from(env!("CARGO_WORKSPACE_DIR")); + let dir = if let Some(dir) = dir { workspace_dir.join(dir) } else { workspace_dir }; + let comand = comand.split_whitespace().collect::>(); + let mut cmd = process::Command::new(comand[0]); + if comand.len() > 1 { + cmd.args(&comand[1..]); + } + cmd.current_dir(dir); + cmd.spawn() + .unwrap_or_else(|e| { + panic!("Failed to run command '{}': {e}", comand.join(" ")); + }) + .wait() + .unwrap_or_else(|e| { + panic!("Failed to wait for command '{}': {e}", comand.join(" ")); + }); +} diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs new file mode 100644 index 0000000000..98feae72aa --- /dev/null +++ b/tools/building/src/main.rs @@ -0,0 +1,84 @@ +use building::*; + +fn usage() { + eprintln!("usage: cargo run [] [release|debug|profiling]"); + eprintln!(); + eprintln!("commands:"); + eprintln!(" web Run the dev server"); + eprintln!(" web build Build the web version"); + eprintln!(" desktop Run the desktop app"); + eprintln!(" desktop build Build the desktop version"); + eprintln!(" check Check that all required dependencies are installed"); +} + +fn main() { + let args: Vec = std::env::args().collect(); + let args: Vec<&str> = args.iter().skip(1).map(String::as_str).collect(); + + match args.as_slice() { + ["desktop", rest @ ..] => match rest { + ["build", rest @ ..] => build_desktop(rest.into()), + _ => run_desktop(rest.into()), + }, + ["web", rest @ ..] => match rest { + ["build", rest @ ..] => build_web(rest.into()), + _ => run_web(rest.into()), + }, + rest => match rest { + ["build", rest @ ..] => build_web(rest.into()), + _ => run_web(rest.into()), + }, + } +} + +fn run_web(profile: Profile) { + match profile { + Profile::Debug | Profile::Default => run_in_frontend_dir("npm run start"), + Profile::Release => run_in_frontend_dir("npm run production"), + Profile::Profiling => run_in_frontend_dir("npm run profiling"), + Profile::Error => usage(), + } +} + +fn run_desktop(profile: Profile) { + match profile { + Profile::Debug | Profile::Default => { + run_in_frontend_dir("npm run build-native-dev"); + run("cargo run -p third-party-licenses --features desktop"); + run("cargo run -p graphite-desktop-bundle -- open"); + } + Profile::Release => { + run_in_frontend_dir("npm run build-native"); + run("cargo run -p third-party-licenses --features desktop"); + run("cargo run -r -p graphite-desktop-bundle -- open"); + } + Profile::Profiling => todo!("profiling run for desktop"), + Profile::Error => usage(), + } +} + +fn build_web(profile: Profile) { + match profile { + Profile::Debug => run_in_frontend_dir("npm run build-dev"), + Profile::Release | Profile::Default => run_in_frontend_dir("npm run build"), + Profile::Profiling => run_in_frontend_dir("npm run build-profiling"), + Profile::Error => usage(), + } +} + +fn build_desktop(profile: Profile) { + match profile { + Profile::Debug => { + run_in_frontend_dir("npm run build-native-dev"); + run("cargo run -p third-party-licenses --features desktop"); + run("cargo run -p graphite-desktop-bundle"); + } + Profile::Release | Profile::Default => { + run_in_frontend_dir("npm run build-native"); + run("cargo run -p third-party-licenses --features desktop"); + run("cargo run -r -p graphite-desktop-bundle"); + } + Profile::Profiling => todo!("profiling build for desktop"), + Profile::Error => usage(), + } +} diff --git a/website/content/volunteer/guide/project-setup/_index.md b/website/content/volunteer/guide/project-setup/_index.md index 3934df3a20..bb4b32c8d7 100644 --- a/website/content/volunteer/guide/project-setup/_index.md +++ b/website/content/volunteer/guide/project-setup/_index.md @@ -40,7 +40,7 @@ git clone https://github.com/GraphiteEditor/Graphite.git From either the `/` (root) or `/frontend` directories, you can run the project by executing: ```sh -npm start +cargo run ``` This spins up the dev server at with a file watcher that performs hot reloading of the web page. You should be able to start the server, edit and save web and Rust code, and shut it down by double pressing CtrlC. TypeScript and HTML changes require a manual page reload to fix broken state. @@ -53,13 +53,13 @@ This method compiles Graphite code in debug mode which includes debug symbols fo On rare occasions (like while running advanced performance profiles or proxying the dev server connection over a slow network where the >100 MB unoptimized binary size would pose an issue), you may need to run the dev server with release optimizations. To do that while keeping debug symbols: ```sh -npm run profiling +cargo run profiling ``` To run the dev server without debug symbols, using the same release optimizations as production builds: ```sh -npm run production +cargo run release ``` @@ -70,7 +70,7 @@ npm run production You'll rarely need to compile your own production builds because our CI/CD system takes care of deployments. However, you can compile a production build with full optimizations by running: ```sh -npm run build +cargo run web build ``` This produces the `/frontend/dist` directory containing the static site files that must be served by your own web server. From 90b07908347c53b7c4ea9444f97ce90de9210970 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 26 Feb 2026 16:17:23 +0000 Subject: [PATCH 03/23] use thiserror in third-party-licenses tool --- Cargo.lock | 1 + tools/third-party-licenses/Cargo.toml | 1 + tools/third-party-licenses/src/cargo.rs | 25 ++++---- tools/third-party-licenses/src/cef.rs | 43 +++++--------- tools/third-party-licenses/src/main.rs | 76 ++++++++++++++----------- tools/third-party-licenses/src/npm.rs | 24 +++----- 6 files changed, 78 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36dd53d6dd..9f41a56e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6254,6 +6254,7 @@ dependencies = [ "scraper", "serde", "serde_json", + "thiserror 2.0.18", ] [[package]] diff --git a/tools/third-party-licenses/Cargo.toml b/tools/third-party-licenses/Cargo.toml index 4a59967389..fe348963c9 100644 --- a/tools/third-party-licenses/Cargo.toml +++ b/tools/third-party-licenses/Cargo.toml @@ -13,6 +13,7 @@ desktop = ["dep:cef-dll-sys", "dep:scraper"] serde = { workspace = true } serde_json = { workspace = true } lzma-rust2 = { workspace = true } +thiserror = { workspace = true } # Optional workspace dependencies cef-dll-sys = { workspace = true, optional = true } diff --git a/tools/third-party-licenses/src/cargo.rs b/tools/third-party-licenses/src/cargo.rs index 49d6731dd4..7b10a4fa43 100644 --- a/tools/third-party-licenses/src/cargo.rs +++ b/tools/third-party-licenses/src/cargo.rs @@ -1,9 +1,9 @@ -use crate::{LicenceSource, LicenseEntry, Package}; +use crate::{Error, LicenceSource, LicenseEntry, Package}; use serde::Deserialize; use std::fs; use std::hash::{Hash, Hasher}; use std::path::PathBuf; -use std::process::{self, Command}; +use std::process::Command; pub struct CargoLicenseSource {} @@ -14,8 +14,8 @@ impl CargoLicenseSource { } impl LicenceSource for CargoLicenseSource { - fn licenses(&self) -> Vec { - parse(run()) + fn licenses(&self) -> Result, Error> { + Ok(parse(run()?)) } } @@ -84,23 +84,18 @@ fn parse(parsed: Output) -> Vec { .collect() } -fn run() -> Output { +fn run() -> Result { let output = Command::new("cargo") .args(["about", "generate", "--format", "json", "--frozen"]) .current_dir(env!("CARGO_WORKSPACE_DIR")) .output() - .unwrap_or_else(|e| { - eprintln!("Failed to run cargo about generate: {e}"); - process::exit(1) - }); + .map_err(|e| Error::Io(e, "Failed to run cargo about generate".into()))?; if !output.status.success() { - eprintln!("cargo about generate failed:\n{}", String::from_utf8_lossy(&output.stderr)); - process::exit(1) + return Err(Error::Command(format!("cargo about generate failed:\n{}", String::from_utf8_lossy(&output.stderr)))); } - serde_json::from_str(&String::from_utf8(output.stdout).expect("cargo about generate should return valid UTF-8")).unwrap_or_else(|e| { - eprintln!("Failed to parse cargo about generate JSON: {e}"); - process::exit(1) - }) + let stdout = String::from_utf8(output.stdout).map_err(|e| Error::Utf8(e, "cargo about generate returned invalid UTF-8".into()))?; + + serde_json::from_str(&stdout).map_err(|e| Error::Json(e, "Failed to parse cargo about generate JSON".into())) } diff --git a/tools/third-party-licenses/src/cef.rs b/tools/third-party-licenses/src/cef.rs index 94fe45d08d..b2eaa82755 100644 --- a/tools/third-party-licenses/src/cef.rs +++ b/tools/third-party-licenses/src/cef.rs @@ -1,11 +1,11 @@ use lzma_rust2::XzReader; use scraper::{Html, Selector}; +use std::fs; use std::hash::Hash; use std::io::Read; use std::path::PathBuf; -use std::{fs, process}; -use crate::{LicenceSource, LicenseEntry, Package}; +use crate::{Error, LicenceSource, LicenseEntry, Package}; pub struct CefLicenseSource; @@ -16,15 +16,15 @@ impl CefLicenseSource { } impl LicenceSource for CefLicenseSource { - fn licenses(&self) -> Vec { - let html = read(); - parse(&html) + fn licenses(&self) -> Result, Error> { + let html = read()?; + Ok(parse(&html)) } } impl Hash for CefLicenseSource { fn hash(&self, state: &mut H) { - read().hash(state) + read().unwrap().hash(state) } } @@ -64,42 +64,29 @@ fn parse(html: &str) -> Vec { .collect() } -fn read() -> String { +fn read() -> Result { let cef_path = PathBuf::from(env!("CEF_PATH")); let cef_credits = std::fs::read_dir(&cef_path) - .unwrap_or_else(|e| { - eprintln!("Failed to read CEF_PATH directory {}: {e}", cef_path.display()); - process::exit(1); - }) + .map_err(|e| Error::Io(e, format!("Failed to read CEF_PATH directory {}", cef_path.display())))? .filter_map(|entry| entry.ok()) .find(|entry| { let name = entry.file_name(); name.eq_ignore_ascii_case("credits.html") || name.eq_ignore_ascii_case("credits.html.xz") }) .map(|entry| entry.path()) - .unwrap_or_else(|| { - eprintln!("Could not find CREDITS.html or CREDITS.html.xz in {}", cef_path.display()); - process::exit(1); - }); + .ok_or_else(|| Error::CefCreditsNotFound(cef_path.clone()))?; let decompress_xz = cef_credits.extension().map(|ext| ext.eq_ignore_ascii_case("xz")).unwrap_or(false); if decompress_xz { - let file = fs::File::open(&cef_credits).unwrap_or_else(|e| { - eprintln!("Failed to open CEF credits file {}: {e}", cef_credits.display()); - process::exit(1); - }); + let file = fs::File::open(&cef_credits).map_err(|e| Error::Io(e, format!("Failed to open CEF credits file {}", cef_credits.display())))?; let mut reader = XzReader::new(file, false); let mut html = String::new(); - reader.read_to_string(&mut html).unwrap_or_else(|e| { - eprintln!("Failed to decompress CEF credits file {}: {e}", cef_credits.display()); - process::exit(1); - }); - html + reader + .read_to_string(&mut html) + .map_err(|e| Error::Io(e, format!("Failed to decompress CEF credits file {}", cef_credits.display())))?; + Ok(html) } else { - fs::read_to_string(&cef_credits).unwrap_or_else(|e| { - eprintln!("Failed to read CEF credits file {}: {e}", cef_credits.display()); - process::exit(1); - }) + fs::read_to_string(&cef_credits).map_err(|e| Error::Io(e, format!("Failed to read CEF credits file {}", cef_credits.display()))) } } diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs index afa1b501f0..6f5ca2f2ff 100644 --- a/tools/third-party-licenses/src/main.rs +++ b/tools/third-party-licenses/src/main.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; +use std::fs; use std::hash::{DefaultHasher, Hash, Hasher}; use std::path::PathBuf; -use std::{fs, process}; mod cargo; #[cfg(feature = "desktop")] @@ -13,8 +13,27 @@ use crate::cargo::CargoLicenseSource; use crate::cef::CefLicenseSource; use crate::npm::NpmLicenseSource; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{1}: {0}")] + Io(#[source] std::io::Error, String), + + #[error("{1}: {0}")] + Json(#[source] serde_json::Error, String), + + #[error("{1}: {0}")] + Utf8(#[source] std::string::FromUtf8Error, String), + + #[error("{0}")] + Command(String), + + #[cfg(feature = "desktop")] + #[error("Could not find CREDITS.html or CREDITS.html.xz in {0}")] + CefCreditsNotFound(PathBuf), +} + pub trait LicenceSource: std::hash::Hash { - fn licenses(&self) -> Vec; + fn licenses(&self) -> Result, Error>; } pub struct LicenseEntry { @@ -39,6 +58,13 @@ struct Run<'a> { } fn main() { + if let Err(e) = run() { + eprintln!("Error: {e}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), Error> { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let workspace_dir = PathBuf::from(env!("CARGO_WORKSPACE_DIR")); @@ -71,32 +97,26 @@ fn main() { if current_hash == fs::read_to_string(¤t_hash_path).unwrap_or_default() { eprintln!("No changes in licenses detected, skipping generation."); - return; + return Ok(()); } eprintln!("Changes in licenses detected, generating new license file."); let licenses = merge_filter_dedup_and_sort(vec![ - cargo_source.licenses(), - npm_source.licenses(), + cargo_source.licenses()?, + npm_source.licenses()?, #[cfg(feature = "desktop")] - cef_source.licenses(), + cef_source.licenses()?, ]); let formatted = format_credits(&licenses); #[cfg(feature = "desktop")] - let output = compress(&formatted); + let output = compress(&formatted)?; #[cfg(not(feature = "desktop"))] let output = formatted.as_bytes().to_vec(); if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent).unwrap_or_else(|e| { - eprintln!("Failed to create directory {}: {e}", parent.display()); - std::process::exit(1); - }); + fs::create_dir_all(parent).map_err(|e| Error::Io(e, format!("Failed to create directory {}", parent.display())))?; } - fs::write(&output_path, &output).unwrap_or_else(|e| { - eprintln!("Failed to write {}: {e}", &output_path.display()); - std::process::exit(1); - }); + fs::write(&output_path, &output).map_err(|e| Error::Io(e, format!("Failed to write {}", output_path.display())))?; run.output = &output; let hash = { @@ -105,10 +125,9 @@ fn main() { format!("{:016x}", hasher.finish()) }; - fs::write(¤t_hash_path, hash).unwrap_or_else(|e| { - eprintln!("Failed to write hash file {}: {e}", current_hash_path.display()); - process::exit(1); - }); + fs::write(¤t_hash_path, hash).map_err(|e| Error::Io(e, format!("Failed to write hash file {}", current_hash_path.display())))?; + + Ok(()) } fn format_credits(licenses: &Vec) -> String { @@ -210,20 +229,11 @@ fn dedup_by_licence_text(vec: Vec) -> Vec { } #[cfg(feature = "desktop")] -fn compress(content: &str) -> Vec { +fn compress(content: &str) -> Result, Error> { use std::io::Write; let mut buf = Vec::new(); - let mut writer = lzma_rust2::XzWriter::new(&mut buf, lzma_rust2::XzOptions::default()).unwrap_or_else(|e| { - eprintln!("Failed to create XZ writer: {e}"); - std::process::exit(1); - }); - writer.write_all(content.as_bytes()).unwrap_or_else(|e| { - eprintln!("Failed to write compressed credits: {e}"); - std::process::exit(1); - }); - writer.finish().unwrap_or_else(|e| { - eprintln!("Failed to finish XZ compression: {e}"); - std::process::exit(1); - }); - buf + let mut writer = lzma_rust2::XzWriter::new(&mut buf, lzma_rust2::XzOptions::default()).map_err(|e| Error::Io(e, "Failed to create XZ writer".into()))?; + writer.write_all(content.as_bytes()).map_err(|e| Error::Io(e, "Failed to write compressed credits".into()))?; + writer.finish().map_err(|e| Error::Io(e, "Failed to finish XZ compression".into()))?; + Ok(buf) } diff --git a/tools/third-party-licenses/src/npm.rs b/tools/third-party-licenses/src/npm.rs index aac94f5cad..01db5003dc 100644 --- a/tools/third-party-licenses/src/npm.rs +++ b/tools/third-party-licenses/src/npm.rs @@ -1,10 +1,9 @@ use std::collections::HashMap; use std::fs; use std::path::PathBuf; -use std::process; use std::process::Command; -use crate::{LicenceSource, LicenseEntry, Package}; +use crate::{Error, LicenceSource, LicenseEntry, Package}; pub struct NpmLicenseSource { dir: PathBuf, @@ -16,8 +15,8 @@ impl NpmLicenseSource { } impl LicenceSource for NpmLicenseSource { - fn licenses(&self) -> Vec { - parse(run(&self.dir)) + fn licenses(&self) -> Result, Error> { + Ok(parse(run(&self.dir)?)) } } @@ -66,7 +65,7 @@ fn parse(parsed: Output) -> Vec { .collect() } -fn run(dir: &std::path::Path) -> Output { +fn run(dir: &std::path::Path) -> Result { #[cfg(not(target_os = "windows"))] let mut cmd = Command::new("npx"); #[cfg(target_os = "windows")] @@ -74,20 +73,13 @@ fn run(dir: &std::path::Path) -> Output { cmd.args(["license-checker-rseidelsohn", "--production", "--json"]); cmd.current_dir(dir); - let output = cmd.output().unwrap_or_else(|e| { - eprintln!("Failed to run npx license-checker-rseidelsohn: {e}"); - process::exit(1); - }); + let output = cmd.output().map_err(|e| Error::Io(e, "Failed to run npx license-checker-rseidelsohn".into()))?; if !output.status.success() { - eprintln!("npx license-checker-rseidelsohn failed:\n{}", String::from_utf8_lossy(&output.stderr)); - process::exit(1); + return Err(Error::Command(format!("npx license-checker-rseidelsohn failed:\n{}", String::from_utf8_lossy(&output.stderr)))); } - let json_str = String::from_utf8(output.stdout).expect("Invalid UTF-8 from license-checker"); + let json_str = String::from_utf8(output.stdout).map_err(|e| Error::Utf8(e, "Invalid UTF-8 from license-checker".into()))?; - serde_json::from_str(&json_str).unwrap_or_else(|e| { - eprintln!("Failed to parse license-checker JSON: {e}"); - process::exit(1) - }) + serde_json::from_str(&json_str).map_err(|e| Error::Json(e, "Failed to parse license-checker JSON".into())) } From a6afca490188994ac737a1c9a9edb81f5469f2e8 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 26 Feb 2026 16:24:59 +0000 Subject: [PATCH 04/23] prefere panic over exit --- desktop/bundle/src/common.rs | 4 +++- desktop/bundle/src/linux.rs | 6 ++---- desktop/src/cef/context/builder.rs | 3 +-- desktop/src/lib.rs | 20 +++++++------------- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/desktop/bundle/src/common.rs b/desktop/bundle/src/common.rs index 3e0523b878..4ae8a5df26 100644 --- a/desktop/bundle/src/common.rs +++ b/desktop/bundle/src/common.rs @@ -1,3 +1,5 @@ +#![cfg_attr(target_os = "linux", allow(unused))] // TODO: Remove this when bundling for linux is implemented + use std::error::Error; use std::fs; use std::path::{Path, PathBuf}; @@ -45,7 +47,7 @@ pub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result Result<(), Box> { let status = Command::new(program).args(args).stdout(Stdio::inherit()).stderr(Stdio::inherit()).status()?; if !status.success() { - std::process::exit(1); + panic!("Command '{}' with args {:?} failed with status: {}", program, args, status); } Ok(()) } diff --git a/desktop/bundle/src/linux.rs b/desktop/bundle/src/linux.rs index 94cd480ee3..fb5c462ffe 100644 --- a/desktop/bundle/src/linux.rs +++ b/desktop/bundle/src/linux.rs @@ -1,8 +1,6 @@ -use std::error::Error; - use crate::common::*; -pub fn main() -> Result<(), Box> { +pub fn main() -> Result<(), Box> { let app_bin = build_bin("graphite-desktop-platform-linux", None)?; // TODO: Implement bundling for linux @@ -11,7 +9,7 @@ pub fn main() -> Result<(), Box> { if std::env::args().any(|a| a == "open") { run_command(&app_bin.to_string_lossy(), &[]).expect("failed to open app"); } else { - println!("Binary built and placed at {}", app_bin.to_string_lossy()); + eprintln!("Binary built and placed at {}", app_bin.to_string_lossy()); eprintln!("Bundling for Linux is not yet implemented."); eprintln!("You can still start the app with the `open` subcommand. `cargo run -p graphite-desktop-bundle -- open`"); std::process::exit(1); diff --git a/desktop/src/cef/context/builder.rs b/desktop/src/cef/context/builder.rs index e6ad318688..972a4fa382 100644 --- a/desktop/src/cef/context/builder.rs +++ b/desktop/src/cef/context/builder.rs @@ -138,8 +138,7 @@ impl CefContextBuilder { }); } Err(e) => { - tracing::error!("Failed to initialize CEF context: {:?}", e); - std::process::exit(1); + panic!("Failed to initialize CEF context: {:?}", e); } }); diff --git a/desktop/src/lib.rs b/desktop/src/lib.rs index f56245e03f..9fa83cbd64 100644 --- a/desktop/src/lib.rs +++ b/desktop/src/lib.rs @@ -5,7 +5,6 @@ use crate::consts::APP_LOCK_FILE_NAME; use crate::event::CreateAppEventSchedulerEventLoopExt; use clap::Parser; use std::io::Write; -use std::process::exit; use tracing_subscriber::EnvFilter; use winit::event_loop::EventLoop; @@ -46,8 +45,7 @@ pub fn start() { .truncate(true) .open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME)) else { - tracing::error!("Failed to open lock file, Exiting."); - exit(1); + panic!("Failed to open lock file.") }; let mut lock = fd_lock::RwLock::new(lock_file); let lock = match lock.try_write() { @@ -60,7 +58,7 @@ pub fn start() { } Err(_) => { tracing::error!("Another instance is already running, Exiting."); - exit(1); + std::process::exit(1); } }; @@ -87,20 +85,16 @@ pub fn start() { context } Err(cef::InitError::AlreadyRunning) => { - tracing::error!("Another instance is already running, Exiting."); - exit(1); + panic!("Another instance is already running."); } Err(cef::InitError::InitializationFailed(code)) => { - tracing::error!("Cef initialization failed with code: {code}"); - exit(1); + panic!("Cef initialization failed with code: {code}"); } Err(cef::InitError::BrowserCreationFailed) => { - tracing::error!("Failed to create CEF browser"); - exit(1); + panic!("Failed to create CEF browser"); } Err(cef::InitError::RequestContextCreationFailed) => { - tracing::error!("Failed to create CEF request context"); - exit(1); + panic!("Failed to create CEF request context"); } }; @@ -139,7 +133,7 @@ pub fn start() { // Calling `exit` bypasses rust teardown and lets Windows perform process cleanup. // TODO: Identify and fix the underlying CEF shutdown issue so this workaround can be removed. #[cfg(target_os = "windows")] - exit(0); + std::process::exit(0); } pub fn start_helper() { From a1560b7f6ce2ed441c96672aa42f1290a39c51d5 Mon Sep 17 00:00:00 2001 From: Timon Date: Mon, 2 Mar 2026 13:06:26 +0000 Subject: [PATCH 05/23] Add automatic dependency check to cargo run tool --- node-graph/nodes/raster/shaders/build.rs | 2 +- tools/building/src/checks.rs | 93 ------------ tools/building/src/deps.rs | 176 +++++++++++++++++++++++ tools/building/src/lib.rs | 2 +- tools/building/src/main.rs | 3 +- 5 files changed, 180 insertions(+), 96 deletions(-) delete mode 100644 tools/building/src/checks.rs create mode 100644 tools/building/src/deps.rs diff --git a/node-graph/nodes/raster/shaders/build.rs b/node-graph/nodes/raster/shaders/build.rs index a6afe8f22f..29901d2b7e 100644 --- a/node-graph/nodes/raster/shaders/build.rs +++ b/node-graph/nodes/raster/shaders/build.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; pub fn main() -> Result<(), Box> { env_logger::builder().filter_level(log::LevelFilter::Debug).init(); - // Skip building the shader if they are provided externally + // Skip building the shaders if they are provided externally println!("cargo:rerun-if-env-changed=RASTER_NODES_SHADER_PATH"); if !std::env::var("RASTER_NODES_SHADER_PATH").unwrap_or_default().is_empty() { return Ok(()); diff --git a/tools/building/src/checks.rs b/tools/building/src/checks.rs deleted file mode 100644 index 42cc8bcbaa..0000000000 --- a/tools/building/src/checks.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::process::Command; - -struct Dependency { - command: &'static str, - args: &'static [&'static str], - name: &'static str, - expected_version: Option<&'static str>, -} - -const DEPENDENCIES: &[Dependency] = &[ - Dependency { - command: "rustc", - args: &["--version"], - name: "Rust", - expected_version: None, - }, - Dependency { - command: "cargo-about", - args: &["--version"], - name: "cargo-about", - expected_version: None, - }, - Dependency { - command: "cargo-watch", - args: &["--version"], - name: "cargo-watch", - expected_version: None, - }, - Dependency { - command: "wasm-bindgen", - args: &["--version"], - name: "wasm-bindgen-cli", - expected_version: Some("0.2.100"), - }, - Dependency { - command: "wasm-pack", - args: &["--version"], - name: "wasm-pack", - expected_version: None, - }, - Dependency { - command: "node", - args: &["--version"], - name: "Node.js", - expected_version: None, - }, -]; - -pub fn check_dependencies() { - let mut failures = Vec::new(); - - for dep in DEPENDENCIES { - match Command::new(dep.command).args(dep.args).output() { - Ok(output) if output.status.success() => { - let version_output = String::from_utf8_lossy(&output.stdout); - let version = version_output.trim(); - - if let Some(expected) = dep.expected_version { - if version.contains(expected) { - eprintln!(" ✓ {} ({})", dep.name, version); - } else { - eprintln!(" ✗ {} (found {}, expected {})", dep.name, version, expected); - failures.push(format!("{}: version mismatch (found {}, expected {})", dep.name, version, expected)); - } - } else { - eprintln!(" ✓ {} ({})", dep.name, version); - } - } - Ok(output) => { - let stderr = String::from_utf8_lossy(&output.stderr); - eprintln!(" ✗ {} — command failed: {}", dep.name, stderr.trim()); - failures.push(format!("{}: not installed or not working", dep.name)); - } - Err(_) => { - eprintln!(" ✗ {} — not found", dep.name); - failures.push(format!("{}: not found in PATH", dep.name)); - } - } - } - - eprintln!(); - - if failures.is_empty() { - eprintln!("All dependencies are installed."); - } else { - eprintln!("{} missing or misconfigured dependenc{}:", failures.len(), if failures.len() == 1 { "y" } else { "ies" }); - for failure in &failures { - eprintln!(" - {failure}"); - } - eprintln!(); - eprintln!("See: https://graphite.rs/volunteer/guide/project-setup/"); - } -} diff --git a/tools/building/src/deps.rs b/tools/building/src/deps.rs new file mode 100644 index 0000000000..3c47a8571f --- /dev/null +++ b/tools/building/src/deps.rs @@ -0,0 +1,176 @@ +use std::io::IsTerminal; +use std::process::Command; + +struct Dependency { + command: &'static str, + args: &'static [&'static str], + name: &'static str, + version: Option<&'static str>, + install: Option<&'static str>, +} + +const DEPENDENCIES: &[Dependency] = &[ + Dependency { + command: "rustc", + args: &["--version"], + name: "Rust", + version: None, + install: None, + }, + Dependency { + command: "cargo-about", + args: &["--version"], + name: "cargo-about", + version: None, + install: Some("cargo install cargo-about"), + }, + Dependency { + command: "cargo-watch", + args: &["--version"], + name: "cargo-watch", + version: None, + install: Some("cargo install cargo-watch"), + }, + Dependency { + command: "wasm-bindgen", + args: &["--version"], + name: "wasm-bindgen-cli", + version: Some("0.2.100"), + install: Some("cargo install -f wasm-bindgen-cli@0.2.100"), + }, + Dependency { + command: "wasm-pack", + args: &["--version"], + name: "wasm-pack", + version: None, + install: Some("cargo install wasm-pack"), + }, + Dependency { + command: "node", + args: &["--version"], + name: "Node.js", + version: None, + install: None, + }, +]; + +const DESKTOP_DEPENDENCIES: &[Dependency] = &[ + Dependency { + command: "cmake", + args: &["--version"], + name: "CMake", + version: None, + install: None, + }, + #[cfg(target_os = "windows")] + Dependency { + command: "ninja", + args: &["--version"], + name: "Ninja", + version: None, + install: None, + }, +]; + +pub fn check(desktop: bool) { + eprintln!(); + eprintln!("Checking dependencies:"); + + let mut installable: Vec<&Dependency> = Vec::new(); + let mut failures: Vec = Vec::new(); + + let deps: Box> = if desktop { + Box::new(DEPENDENCIES.iter().chain(DESKTOP_DEPENDENCIES.iter())) + } else { + Box::new(DEPENDENCIES.iter()) + }; + + for dep in deps { + match Command::new(dep.command).args(dep.args).output() { + Ok(output) if output.status.success() => { + let version = String::from_utf8_lossy(&output.stdout); + let version = version.lines().next().unwrap_or_default().trim(); + + if let Some(expected) = dep.version { + if version.contains(expected) { + eprintln!(" ✓ {} ({})", dep.name, version); + } else { + eprintln!(" ✗ {} (found {}, expected {})", dep.name, version, expected); + if dep.install.is_some() { + installable.push(dep); + } else { + failures.push(format!("{}: version mismatch (found {version}, expected {expected})", dep.name)); + } + } + } else { + eprintln!(" ✓ {} ({})", dep.name, version); + } + } + Ok(output) => { + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!(" ✗ {} - command failed: {}", dep.name, stderr.trim()); + if dep.install.is_some() { + installable.push(dep); + } else { + failures.push(format!("{}: not installed or not working", dep.name)); + } + } + Err(_) => { + eprintln!(" ✗ {} - not found", dep.name); + if dep.install.is_some() { + installable.push(dep); + } else { + failures.push(format!("{}: not found in PATH", dep.name)); + } + } + } + } + + eprintln!(); + + if installable.is_empty() && failures.is_empty() { + return; + } + + let total = installable.len() + failures.len(); + eprintln!("{total} missing or misconfigured dependenc{}:", if total == 1 { "y" } else { "ies" }); + for dep in &installable { + eprintln!(" - {}: {}", dep.name, dep.install.unwrap()); + } + for msg in &failures { + eprintln!(" - {msg}"); + } + + if !failures.is_empty() { + eprintln!(); + eprintln!("See: https://graphite.art/volunteer/guide/project-setup/"); + } + + if !installable.is_empty() && std::io::stdout().is_terminal() { + eprintln!(); + eprintln!("The following can be installed automatically:"); + for dep in &installable { + eprintln!(" {}", dep.install.unwrap()); + } + eprintln!(); + eprint!("Install them now? [Y/n] "); + + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let input = input.trim(); + + if input.is_empty() || input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("yes") { + for dep in &installable { + let parts: Vec<&str> = dep.install.unwrap().split_whitespace().collect(); + eprintln!("Running: {}...", dep.install.unwrap()); + let status = Command::new(parts[0]) + .args(&parts[1..]) + .status() + .unwrap_or_else(|e| panic!("Failed to run '{}': {e}", dep.install.unwrap())); + if !status.success() { + eprintln!("Failed to install {}", dep.name); + } + } + } + } +} diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index e784a00ae9..709f7d908e 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -1,4 +1,4 @@ -pub mod checks; +pub mod deps; use std::process; diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index 98feae72aa..8bfd84ad55 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -8,13 +8,14 @@ fn usage() { eprintln!(" web build Build the web version"); eprintln!(" desktop Run the desktop app"); eprintln!(" desktop build Build the desktop version"); - eprintln!(" check Check that all required dependencies are installed"); } fn main() { let args: Vec = std::env::args().collect(); let args: Vec<&str> = args.iter().skip(1).map(String::as_str).collect(); + deps::check(matches!(args.first(), Some(&"desktop"))); + match args.as_slice() { ["desktop", rest @ ..] => match rest { ["build", rest @ ..] => build_desktop(rest.into()), From c6a7cef4d7a4359e73ef7f7f8bae96547c53be1d Mon Sep 17 00:00:00 2001 From: Timon Date: Mon, 2 Mar 2026 13:51:18 +0000 Subject: [PATCH 06/23] Skip dependecies that are not needed for the current task --- tools/building/src/deps.rs | 151 ++++++++++++++++++++----------------- tools/building/src/lib.rs | 44 ++++++++--- tools/building/src/main.rs | 66 +++++----------- 3 files changed, 132 insertions(+), 129 deletions(-) diff --git a/tools/building/src/deps.rs b/tools/building/src/deps.rs index 3c47a8571f..33ea58d3b6 100644 --- a/tools/building/src/deps.rs +++ b/tools/building/src/deps.rs @@ -1,91 +1,100 @@ use std::io::IsTerminal; use std::process::Command; +use crate::*; + +#[derive(Default, Clone)] struct Dependency { command: &'static str, args: &'static [&'static str], name: &'static str, version: Option<&'static str>, install: Option<&'static str>, + skip: Option<&'static dyn Fn(&Task) -> bool>, } -const DEPENDENCIES: &[Dependency] = &[ - Dependency { - command: "rustc", - args: &["--version"], - name: "Rust", - version: None, - install: None, - }, - Dependency { - command: "cargo-about", - args: &["--version"], - name: "cargo-about", - version: None, - install: Some("cargo install cargo-about"), - }, - Dependency { - command: "cargo-watch", - args: &["--version"], - name: "cargo-watch", - version: None, - install: Some("cargo install cargo-watch"), - }, - Dependency { - command: "wasm-bindgen", - args: &["--version"], - name: "wasm-bindgen-cli", - version: Some("0.2.100"), - install: Some("cargo install -f wasm-bindgen-cli@0.2.100"), - }, - Dependency { - command: "wasm-pack", - args: &["--version"], - name: "wasm-pack", - version: None, - install: Some("cargo install wasm-pack"), - }, - Dependency { - command: "node", - args: &["--version"], - name: "Node.js", - version: None, - install: None, - }, -]; - -const DESKTOP_DEPENDENCIES: &[Dependency] = &[ - Dependency { - command: "cmake", - args: &["--version"], - name: "CMake", - version: None, - install: None, - }, - #[cfg(target_os = "windows")] - Dependency { - command: "ninja", - args: &["--version"], - name: "Ninja", - version: None, - install: None, - }, -]; +fn dependencies(task: &Task) -> Vec { + [ + Dependency { + command: "rustc", + args: &["--version"], + name: "Rust", + ..Default::default() + }, + Dependency { + command: "cargo-about", + args: &["--version"], + name: "cargo-about", + install: Some("cargo install cargo-about"), + ..Default::default() + }, + Dependency { + command: "cargo-watch", + args: &["--version"], + name: "cargo-watch", + install: Some("cargo install cargo-watch"), + skip: Some(&|task| { + !matches!( + task, + Task { + target: Target::Web, + action: Action::Run, + profile: _ + } + ) + }), + ..Default::default() + }, + Dependency { + command: "wasm-bindgen", + args: &["--version"], + name: "wasm-bindgen-cli", + version: Some("0.2.100"), + install: Some("cargo install -f wasm-bindgen-cli@0.2.100"), + ..Default::default() + }, + Dependency { + command: "wasm-pack", + args: &["--version"], + name: "wasm-pack", + install: Some("cargo install wasm-pack"), + ..Default::default() + }, + Dependency { + command: "node", + args: &["--version"], + name: "Node.js", + ..Default::default() + }, + Dependency { + command: "cmake", + args: &["--version"], + name: "CMake", + skip: Some(&|task| !matches!(task.target, Target::Desktop)), + ..Default::default() + }, + Dependency { + command: "ninja", + args: &["--version"], + name: "Ninja", + skip: Some(&|task| !matches!(task.target, Target::Desktop) || !cfg!(target_os = "windows")), + ..Default::default() + }, + ] + .iter() + .filter(|d| if let Some(skip) = d.skip { !skip(task) } else { true }) + .cloned() + .collect() +} -pub fn check(desktop: bool) { +pub fn check(task: &Task) { eprintln!(); eprintln!("Checking dependencies:"); - let mut installable: Vec<&Dependency> = Vec::new(); + let mut installable: Vec = Vec::new(); let mut failures: Vec = Vec::new(); - let deps: Box> = if desktop { - Box::new(DEPENDENCIES.iter().chain(DESKTOP_DEPENDENCIES.iter())) - } else { - Box::new(DEPENDENCIES.iter()) - }; - - for dep in deps { + for dep in dependencies(task) { match Command::new(dep.command).args(dep.args).output() { Ok(output) if output.status.success() => { let version = String::from_utf8_lossy(&output.stdout); diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index 709f7d908e..5589f8fae6 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -2,29 +2,51 @@ pub mod deps; use std::process; +pub enum Target { + Web, + Desktop, +} + +pub enum Action { + Run, + Build, +} + pub enum Profile { Default, Release, Debug, Profiling, - Error, } -impl From<&[&str]> for Profile { - fn from(arg: &[&str]) -> Self { - arg.first().map(|s| s.to_string()).as_deref().unwrap_or_default().into() - } +pub struct Task { + pub target: Target, + pub action: Action, + pub profile: Profile, } -impl From<&str> for Profile { - fn from(arg: &str) -> Self { - match arg { +impl Task { + pub fn parse(args: &[&str]) -> Option { + let (target, rest) = match args.first() { + Some(&"desktop") => (Target::Desktop, &args[1..]), + Some(&"web") => (Target::Web, &args[1..]), + _ => (Target::Web, args), + }; + + let (action, rest) = match rest.first() { + Some(&"build") => (Action::Build, &rest[1..]), + _ => (Action::Run, rest), + }; + + let profile = match rest.first().copied().unwrap_or_default() { + "" => Profile::Default, "release" => Profile::Release, "debug" => Profile::Debug, "profiling" => Profile::Profiling, - _ if arg.is_empty() => Profile::Default, - _ => Profile::Error, - } + _ => return None, + }; + + Some(Task { target, action, profile }) } } diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index 8bfd84ad55..5cd93d339f 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -14,72 +14,44 @@ fn main() { let args: Vec = std::env::args().collect(); let args: Vec<&str> = args.iter().skip(1).map(String::as_str).collect(); - deps::check(matches!(args.first(), Some(&"desktop"))); + let task = match Task::parse(&args) { + Some(run) => run, + None => return usage(), + }; - match args.as_slice() { - ["desktop", rest @ ..] => match rest { - ["build", rest @ ..] => build_desktop(rest.into()), - _ => run_desktop(rest.into()), - }, - ["web", rest @ ..] => match rest { - ["build", rest @ ..] => build_web(rest.into()), - _ => run_web(rest.into()), - }, - rest => match rest { - ["build", rest @ ..] => build_web(rest.into()), - _ => run_web(rest.into()), - }, - } -} + deps::check(&task); -fn run_web(profile: Profile) { - match profile { - Profile::Debug | Profile::Default => run_in_frontend_dir("npm run start"), - Profile::Release => run_in_frontend_dir("npm run production"), - Profile::Profiling => run_in_frontend_dir("npm run profiling"), - Profile::Error => usage(), - } -} + match (&task.target, &task.action, &task.profile) { + (Target::Web, Action::Run, Profile::Debug | Profile::Default) => run_in_frontend_dir("npm run start"), + (Target::Web, Action::Run, Profile::Release) => run_in_frontend_dir("npm run production"), + (Target::Web, Action::Run, Profile::Profiling) => run_in_frontend_dir("npm run profiling"), + + (Target::Web, Action::Build, Profile::Debug) => run_in_frontend_dir("npm run build-dev"), + (Target::Web, Action::Build, Profile::Release | Profile::Default) => run_in_frontend_dir("npm run build"), + (Target::Web, Action::Build, Profile::Profiling) => run_in_frontend_dir("npm run build-profiling"), -fn run_desktop(profile: Profile) { - match profile { - Profile::Debug | Profile::Default => { + (Target::Desktop, Action::Run, Profile::Debug | Profile::Default) => { run_in_frontend_dir("npm run build-native-dev"); run("cargo run -p third-party-licenses --features desktop"); run("cargo run -p graphite-desktop-bundle -- open"); } - Profile::Release => { + (Target::Desktop, Action::Run, Profile::Release) => { run_in_frontend_dir("npm run build-native"); run("cargo run -p third-party-licenses --features desktop"); run("cargo run -r -p graphite-desktop-bundle -- open"); } - Profile::Profiling => todo!("profiling run for desktop"), - Profile::Error => usage(), - } -} - -fn build_web(profile: Profile) { - match profile { - Profile::Debug => run_in_frontend_dir("npm run build-dev"), - Profile::Release | Profile::Default => run_in_frontend_dir("npm run build"), - Profile::Profiling => run_in_frontend_dir("npm run build-profiling"), - Profile::Error => usage(), - } -} + (Target::Desktop, Action::Run, Profile::Profiling) => todo!("profiling run for desktop"), -fn build_desktop(profile: Profile) { - match profile { - Profile::Debug => { + (Target::Desktop, Action::Build, Profile::Debug) => { run_in_frontend_dir("npm run build-native-dev"); run("cargo run -p third-party-licenses --features desktop"); run("cargo run -p graphite-desktop-bundle"); } - Profile::Release | Profile::Default => { + (Target::Desktop, Action::Build, Profile::Release | Profile::Default) => { run_in_frontend_dir("npm run build-native"); run("cargo run -p third-party-licenses --features desktop"); run("cargo run -r -p graphite-desktop-bundle"); } - Profile::Profiling => todo!("profiling build for desktop"), - Profile::Error => usage(), + (Target::Desktop, Action::Build, Profile::Profiling) => todo!("profiling build for desktop"), } } From 09956a6e9aafda9474ef22cc9a5dc061606ff711 Mon Sep 17 00:00:00 2001 From: Timon Date: Tue, 3 Mar 2026 08:46:29 +0000 Subject: [PATCH 07/23] Fixup --- .github/workflows/comment-!build-commands.yml | 6 +++--- tools/building/src/lib.rs | 6 +++++- tools/third-party-licenses/src/main.rs | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml index e7cbdd93f8..aaadb924d4 100644 --- a/.github/workflows/comment-!build-commands.yml +++ b/.github/workflows/comment-!build-commands.yml @@ -1,6 +1,6 @@ # USAGE: # After reviewing the code, core team members may comment on a PR with the exact text: -# - `!build-dev` to build with debug symbols and optimizations disabled +# - `!build-debug` to build with debug symbols and optimizations disabled # - `!build-profiling` to build with debug symbols and optimizations enabled # - `!build` to build without debug symbols and optimizations enabled # The comment may not contain any other text, not even whitespace or newlines. @@ -21,7 +21,7 @@ jobs: if: > github.event.issue.pull_request && github.event.comment.author_association == 'MEMBER' && - (github.event.comment.body == '!build-dev' || github.event.comment.body == '!build-profiling' || github.event.comment.body == '!build') + (github.event.comment.body == '!build-debug' || github.event.comment.body == '!build-profiling' || github.event.comment.body == '!build') runs-on: self-hosted permissions: contents: read @@ -89,7 +89,7 @@ jobs: elif [[ "${{ github.event.comment.body }}" == "!build" ]]; then echo "command=build" >> $GITHUB_OUTPUT else - echo "Failed to detect if the build command written in the comment should have been '!build-dev', '!build-profiling', or '!build'" >> $GITHUB_OUTPUT + echo "Failed to detect if the build command written in the comment should have been '!build-debug', '!build-profiling', or '!build'" >> $GITHUB_OUTPUT fi - name: 💬 Comment Actions run link diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index 5589f8fae6..463af53dd9 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -67,7 +67,8 @@ pub fn run_from(comand: &str, dir: Option<&str>) { cmd.args(&comand[1..]); } cmd.current_dir(dir); - cmd.spawn() + let exit_code = cmd + .spawn() .unwrap_or_else(|e| { panic!("Failed to run command '{}': {e}", comand.join(" ")); }) @@ -75,4 +76,7 @@ pub fn run_from(comand: &str, dir: Option<&str>) { .unwrap_or_else(|e| { panic!("Failed to wait for command '{}': {e}", comand.join(" ")); }); + if !exit_code.success() { + panic!("Command '{}' exited with code {}", comand.join(" "), exit_code); + } } diff --git a/tools/third-party-licenses/src/main.rs b/tools/third-party-licenses/src/main.rs index 6f5ca2f2ff..797411ad3c 100644 --- a/tools/third-party-licenses/src/main.rs +++ b/tools/third-party-licenses/src/main.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::fs; use std::hash::{DefaultHasher, Hash, Hasher}; use std::path::PathBuf; +use std::process::ExitCode; mod cargo; #[cfg(feature = "desktop")] @@ -57,11 +58,12 @@ struct Run<'a> { cef: &'a CefLicenseSource, } -fn main() { +fn main() -> ExitCode { if let Err(e) = run() { eprintln!("Error: {e}"); - std::process::exit(1); + return ExitCode::FAILURE; } + ExitCode::SUCCESS } fn run() -> Result<(), Error> { From 29f8007a03a9e3d71ca5216e58d66a64117bd4ea Mon Sep 17 00:00:00 2001 From: Timon Date: Tue, 3 Mar 2026 17:32:10 +0000 Subject: [PATCH 08/23] Fixup --- .envrc | 2 +- Cargo.lock | 3 ++ tools/building/Cargo.toml | 3 ++ tools/building/src/deps.rs | 58 ++++++++++++++++++++++---------------- tools/building/src/lib.rs | 38 ++++++++++++++----------- tools/building/src/main.rs | 56 ++++++++++++++++++++++-------------- 6 files changed, 97 insertions(+), 63 deletions(-) diff --git a/.envrc b/.envrc index 930f30ba2a..3550a30f2d 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -use flake .nix +use flake diff --git a/Cargo.lock b/Cargo.lock index 9f41a56e25..e42201170b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,6 +612,9 @@ dependencies = [ [[package]] name = "building" version = "0.0.0" +dependencies = [ + "thiserror 2.0.18", +] [[package]] name = "built" diff --git a/tools/building/Cargo.toml b/tools/building/Cargo.toml index fce23d009b..71b73431fe 100644 --- a/tools/building/Cargo.toml +++ b/tools/building/Cargo.toml @@ -6,3 +6,6 @@ license.workspace = true authors.workspace = true default-run = "building" + +[dependencies] +thiserror = { workspace = true } diff --git a/tools/building/src/deps.rs b/tools/building/src/deps.rs index 33ea58d3b6..9540283ab9 100644 --- a/tools/building/src/deps.rs +++ b/tools/building/src/deps.rs @@ -87,7 +87,7 @@ fn dependencies(task: &Task) -> Vec { .collect() } -pub fn check(task: &Task) { +pub fn check(task: &Task) -> Result<(), Error> { eprintln!(); eprintln!("Checking dependencies:"); @@ -138,7 +138,7 @@ pub fn check(task: &Task) { eprintln!(); if installable.is_empty() && failures.is_empty() { - return; + return Ok(()); } let total = installable.len() + failures.len(); @@ -155,31 +155,39 @@ pub fn check(task: &Task) { eprintln!("See: https://graphite.art/volunteer/guide/project-setup/"); } - if !installable.is_empty() && std::io::stdout().is_terminal() { - eprintln!(); - eprintln!("The following can be installed automatically:"); + if installable.is_empty() { + return Ok(()); + } + + // Don't prompt for automatic installation if we're not interactive session + if !std::io::stdout().is_terminal() && !std::io::stderr().is_terminal() && !std::io::stdin().is_terminal() { + return Ok(()); + } + + eprintln!(); + eprintln!("The following can be installed automatically:"); + for dep in &installable { + eprintln!(" {}", dep.install.unwrap()); + } + eprintln!(); + eprint!("Install them now? [Y/n] "); + + let mut input = String::new(); + std::io::stdin().read_line(&mut input).map_err(|e| Error::Io(e, "Failed to read from stdin".into()))?; + let input = input.trim(); + + if input.is_empty() || input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("yes") { for dep in &installable { - eprintln!(" {}", dep.install.unwrap()); - } - eprintln!(); - eprint!("Install them now? [Y/n] "); - - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - let input = input.trim(); - - if input.is_empty() || input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("yes") { - for dep in &installable { - let parts: Vec<&str> = dep.install.unwrap().split_whitespace().collect(); - eprintln!("Running: {}...", dep.install.unwrap()); - let status = Command::new(parts[0]) - .args(&parts[1..]) - .status() - .unwrap_or_else(|e| panic!("Failed to run '{}': {e}", dep.install.unwrap())); - if !status.success() { - eprintln!("Failed to install {}", dep.name); - } + let parts: Vec<&str> = dep.install.unwrap().split_whitespace().collect(); + eprintln!("Running: {}...", dep.install.unwrap()); + let status = Command::new(parts[0]) + .args(&parts[1..]) + .status() + .map_err(|e| Error::Io(e, format!("Failed to run '{}'", dep.install.unwrap())))?; + if !status.success() { + eprintln!("Failed to install {}", dep.name); } } } + Ok(()) } diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index 463af53dd9..0501a6a924 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -50,33 +50,39 @@ impl Task { } } -pub fn run(comand: &str) { - run_from(comand, None); +pub fn run(command: &str) -> Result<(), Error> { + run_from(command, None) } -pub fn run_in_frontend_dir(comand: &str) { - run_from(comand, Some("frontend")); +pub fn run_in_frontend_dir(command: &str) -> Result<(), Error> { + run_from(command, Some("frontend")) } -pub fn run_from(comand: &str, dir: Option<&str>) { +pub fn run_from(command: &str, dir: Option<&str>) -> Result<(), Error> { let workspace_dir = std::path::PathBuf::from(env!("CARGO_WORKSPACE_DIR")); let dir = if let Some(dir) = dir { workspace_dir.join(dir) } else { workspace_dir }; - let comand = comand.split_whitespace().collect::>(); - let mut cmd = process::Command::new(comand[0]); - if comand.len() > 1 { - cmd.args(&comand[1..]); + let command = command.split_whitespace().collect::>(); + let mut cmd = process::Command::new(command[0]); + if command.len() > 1 { + cmd.args(&command[1..]); } cmd.current_dir(dir); let exit_code = cmd .spawn() - .unwrap_or_else(|e| { - panic!("Failed to run command '{}': {e}", comand.join(" ")); - }) + .map_err(|e| Error::Io(e, format!("Failed to spawn command '{}'", command.join(" "))))? .wait() - .unwrap_or_else(|e| { - panic!("Failed to wait for command '{}': {e}", comand.join(" ")); - }); + .map_err(|e| Error::Io(e, format!("Failed to wait for command '{}'", command.join(" "))))?; if !exit_code.success() { - panic!("Command '{}' exited with code {}", comand.join(" "), exit_code); + return Err(Error::Command(command.join(" "), exit_code)); } + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{1}: {0}")] + Io(#[source] std::io::Error, String), + + #[error("Command '{0}' exited with code {1}")] + Command(String, process::ExitStatus), } diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index 5cd93d339f..7e7a151390 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -1,3 +1,5 @@ +use std::process::ExitCode; + use building::*; fn usage() { @@ -10,48 +12,60 @@ fn usage() { eprintln!(" desktop build Build the desktop version"); } -fn main() { +fn main() -> ExitCode { let args: Vec = std::env::args().collect(); let args: Vec<&str> = args.iter().skip(1).map(String::as_str).collect(); let task = match Task::parse(&args) { Some(run) => run, - None => return usage(), + None => { + usage(); + return ExitCode::FAILURE; + } }; - deps::check(&task); + if let Err(e) = run_task(&task) { + eprintln!("Error: {e}"); + return ExitCode::FAILURE; + } + ExitCode::SUCCESS +} + +fn run_task(task: &Task) -> Result<(), Error> { + deps::check(task)?; match (&task.target, &task.action, &task.profile) { - (Target::Web, Action::Run, Profile::Debug | Profile::Default) => run_in_frontend_dir("npm run start"), - (Target::Web, Action::Run, Profile::Release) => run_in_frontend_dir("npm run production"), - (Target::Web, Action::Run, Profile::Profiling) => run_in_frontend_dir("npm run profiling"), + (Target::Web, Action::Run, Profile::Debug | Profile::Default) => run_in_frontend_dir("npm run start")?, + (Target::Web, Action::Run, Profile::Release) => run_in_frontend_dir("npm run production")?, + (Target::Web, Action::Run, Profile::Profiling) => run_in_frontend_dir("npm run profiling")?, - (Target::Web, Action::Build, Profile::Debug) => run_in_frontend_dir("npm run build-dev"), - (Target::Web, Action::Build, Profile::Release | Profile::Default) => run_in_frontend_dir("npm run build"), - (Target::Web, Action::Build, Profile::Profiling) => run_in_frontend_dir("npm run build-profiling"), + (Target::Web, Action::Build, Profile::Debug) => run_in_frontend_dir("npm run build-dev")?, + (Target::Web, Action::Build, Profile::Release | Profile::Default) => run_in_frontend_dir("npm run build")?, + (Target::Web, Action::Build, Profile::Profiling) => run_in_frontend_dir("npm run build-profiling")?, (Target::Desktop, Action::Run, Profile::Debug | Profile::Default) => { - run_in_frontend_dir("npm run build-native-dev"); - run("cargo run -p third-party-licenses --features desktop"); - run("cargo run -p graphite-desktop-bundle -- open"); + run_in_frontend_dir("npm run build-native-dev")?; + run("cargo run -p third-party-licenses --features desktop")?; + run("cargo run -p graphite-desktop-bundle -- open")?; } (Target::Desktop, Action::Run, Profile::Release) => { - run_in_frontend_dir("npm run build-native"); - run("cargo run -p third-party-licenses --features desktop"); - run("cargo run -r -p graphite-desktop-bundle -- open"); + run_in_frontend_dir("npm run build-native")?; + run("cargo run -p third-party-licenses --features desktop")?; + run("cargo run -r -p graphite-desktop-bundle -- open")?; } (Target::Desktop, Action::Run, Profile::Profiling) => todo!("profiling run for desktop"), (Target::Desktop, Action::Build, Profile::Debug) => { - run_in_frontend_dir("npm run build-native-dev"); - run("cargo run -p third-party-licenses --features desktop"); - run("cargo run -p graphite-desktop-bundle"); + run_in_frontend_dir("npm run build-native-dev")?; + run("cargo run -p third-party-licenses --features desktop")?; + run("cargo run -p graphite-desktop-bundle")?; } (Target::Desktop, Action::Build, Profile::Release | Profile::Default) => { - run_in_frontend_dir("npm run build-native"); - run("cargo run -p third-party-licenses --features desktop"); - run("cargo run -r -p graphite-desktop-bundle"); + run_in_frontend_dir("npm run build-native")?; + run("cargo run -p third-party-licenses --features desktop")?; + run("cargo run -r -p graphite-desktop-bundle")?; } (Target::Desktop, Action::Build, Profile::Profiling) => todo!("profiling build for desktop"), } + Ok(()) } From 33db3cf9b90503f9e3493bd6854b7ffc2edde123 Mon Sep 17 00:00:00 2001 From: Timon Date: Tue, 3 Mar 2026 17:53:51 +0000 Subject: [PATCH 09/23] fix windows --- tools/building/src/lib.rs | 20 ++++++++++++-------- tools/building/src/main.rs | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index 0501a6a924..8b5a946127 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -1,7 +1,8 @@ -pub mod deps; - +use std::path::PathBuf; use std::process; +pub mod deps; + pub enum Target { Web, Desktop, @@ -54,19 +55,22 @@ pub fn run(command: &str) -> Result<(), Error> { run_from(command, None) } -pub fn run_in_frontend_dir(command: &str) -> Result<(), Error> { - run_from(command, Some("frontend")) +pub fn npm_run_in_frontend_dir(args: &str) -> Result<(), Error> { + let workspace_dir = std::path::PathBuf::from(env!("CARGO_WORKSPACE_DIR")); + let frontend_dir = workspace_dir.join("frontend"); + let npm = if cfg!(target_os = "windows") { "npm.cmd" } else { "npm" }; + run_from(&format!("{npm} run {args}"), Some(&frontend_dir)) } -pub fn run_from(command: &str, dir: Option<&str>) -> Result<(), Error> { - let workspace_dir = std::path::PathBuf::from(env!("CARGO_WORKSPACE_DIR")); - let dir = if let Some(dir) = dir { workspace_dir.join(dir) } else { workspace_dir }; +fn run_from(command: &str, dir: Option<&PathBuf>) -> Result<(), Error> { let command = command.split_whitespace().collect::>(); let mut cmd = process::Command::new(command[0]); if command.len() > 1 { cmd.args(&command[1..]); } - cmd.current_dir(dir); + if let Some(dir) = dir { + cmd.current_dir(dir); + } let exit_code = cmd .spawn() .map_err(|e| Error::Io(e, format!("Failed to spawn command '{}'", command.join(" "))))? diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index 7e7a151390..b1a4cf5af3 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -35,33 +35,33 @@ fn run_task(task: &Task) -> Result<(), Error> { deps::check(task)?; match (&task.target, &task.action, &task.profile) { - (Target::Web, Action::Run, Profile::Debug | Profile::Default) => run_in_frontend_dir("npm run start")?, - (Target::Web, Action::Run, Profile::Release) => run_in_frontend_dir("npm run production")?, - (Target::Web, Action::Run, Profile::Profiling) => run_in_frontend_dir("npm run profiling")?, + (Target::Web, Action::Run, Profile::Debug | Profile::Default) => npm_run_in_frontend_dir("start")?, + (Target::Web, Action::Run, Profile::Release) => npm_run_in_frontend_dir("production")?, + (Target::Web, Action::Run, Profile::Profiling) => npm_run_in_frontend_dir("profiling")?, - (Target::Web, Action::Build, Profile::Debug) => run_in_frontend_dir("npm run build-dev")?, - (Target::Web, Action::Build, Profile::Release | Profile::Default) => run_in_frontend_dir("npm run build")?, - (Target::Web, Action::Build, Profile::Profiling) => run_in_frontend_dir("npm run build-profiling")?, + (Target::Web, Action::Build, Profile::Debug) => npm_run_in_frontend_dir("build-dev")?, + (Target::Web, Action::Build, Profile::Release | Profile::Default) => npm_run_in_frontend_dir("build")?, + (Target::Web, Action::Build, Profile::Profiling) => npm_run_in_frontend_dir("build-profiling")?, (Target::Desktop, Action::Run, Profile::Debug | Profile::Default) => { - run_in_frontend_dir("npm run build-native-dev")?; + npm_run_in_frontend_dir("build-native-dev")?; run("cargo run -p third-party-licenses --features desktop")?; run("cargo run -p graphite-desktop-bundle -- open")?; } (Target::Desktop, Action::Run, Profile::Release) => { - run_in_frontend_dir("npm run build-native")?; + npm_run_in_frontend_dir("build-native")?; run("cargo run -p third-party-licenses --features desktop")?; run("cargo run -r -p graphite-desktop-bundle -- open")?; } (Target::Desktop, Action::Run, Profile::Profiling) => todo!("profiling run for desktop"), (Target::Desktop, Action::Build, Profile::Debug) => { - run_in_frontend_dir("npm run build-native-dev")?; + npm_run_in_frontend_dir("build-native-dev")?; run("cargo run -p third-party-licenses --features desktop")?; run("cargo run -p graphite-desktop-bundle")?; } (Target::Desktop, Action::Build, Profile::Release | Profile::Default) => { - run_in_frontend_dir("npm run build-native")?; + npm_run_in_frontend_dir("build-native")?; run("cargo run -p third-party-licenses --features desktop")?; run("cargo run -r -p graphite-desktop-bundle")?; } From 1e85244b045ef88b46166d15242e5fd9bf3a8479 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 4 Mar 2026 14:22:58 +0000 Subject: [PATCH 10/23] Fixup --- desktop/src/cef/context/builder.rs | 18 ++++++------------ desktop/src/lib.rs | 7 ++----- tools/building/src/deps.rs | 6 +++--- tools/building/src/lib.rs | 1 + 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/desktop/src/cef/context/builder.rs b/desktop/src/cef/context/builder.rs index 972a4fa382..c47fae6c83 100644 --- a/desktop/src/cef/context/builder.rs +++ b/desktop/src/cef/context/builder.rs @@ -1,11 +1,10 @@ -use std::path::{Path, PathBuf}; - use cef::args::Args; -use cef::sys::{CEF_API_VERSION_LAST, cef_log_severity_t, cef_resultcode_t}; +use cef::sys::{CEF_API_VERSION_LAST, cef_log_severity_t}; use cef::{ App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, ImplRequestContext, LogSeverity, RequestContextSettings, SchemeHandlerFactory, Settings, WindowInfo, api_hash, browser_host_create_browser_sync, execute_process, }; +use std::path::{Path, PathBuf}; use super::CefContext; use super::singlethreaded::SingleThreadedCefContext; @@ -152,10 +151,7 @@ impl CefContextBuilder { let result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut()); if result != 1 { let cef_exit_code = cef::get_exit_code() as u32; - if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 { - return Err(InitError::AlreadyRunning); - } - return Err(InitError::InitializationFailed(cef_exit_code)); + return Err(InitError::InitializationFailureCode(cef_exit_code)); } Ok(()) } @@ -227,18 +223,16 @@ fn create_browser(event_handler: H, instance_dir: PathBuf, d pub(crate) enum SetupError { #[error("This is the sub process should exit immediately")] Subprocess, - #[error("Subprocess returned non zero exit code")] + #[error("Subprocess returned non zero exit code: {0}")] SubprocessFailed(String), } #[derive(thiserror::Error, Debug)] pub(crate) enum InitError { - #[error("Initialization failed")] - InitializationFailed(u32), + #[error("Initialization failed with code: {0}")] + InitializationFailureCode(u32), #[error("Browser creation failed")] BrowserCreationFailed, #[error("Request context creation failed")] RequestContextCreationFailed, - #[error("Another instance is already running")] - AlreadyRunning, } diff --git a/desktop/src/lib.rs b/desktop/src/lib.rs index 9fa83cbd64..bc29f729cb 100644 --- a/desktop/src/lib.rs +++ b/desktop/src/lib.rs @@ -84,11 +84,8 @@ pub fn start() { tracing::info!("CEF initialized successfully"); context } - Err(cef::InitError::AlreadyRunning) => { - panic!("Another instance is already running."); - } - Err(cef::InitError::InitializationFailed(code)) => { - panic!("Cef initialization failed with code: {code}"); + Err(cef::InitError::InitializationFailureCode(code)) => { + panic!("CEF initialization failed with code: {code}"); } Err(cef::InitError::BrowserCreationFailed) => { panic!("Failed to create CEF browser"); diff --git a/tools/building/src/deps.rs b/tools/building/src/deps.rs index 9540283ab9..3dd06871df 100644 --- a/tools/building/src/deps.rs +++ b/tools/building/src/deps.rs @@ -155,12 +155,12 @@ pub fn check(task: &Task) -> Result<(), Error> { eprintln!("See: https://graphite.art/volunteer/guide/project-setup/"); } - if installable.is_empty() { + // Don't prompt for automatic installation if we're not interactive session + if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() || !std::io::stdin().is_terminal() { return Ok(()); } - // Don't prompt for automatic installation if we're not interactive session - if !std::io::stdout().is_terminal() && !std::io::stderr().is_terminal() && !std::io::stdin().is_terminal() { + if installable.is_empty() { return Ok(()); } diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index 8b5a946127..d1f402cecb 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -36,6 +36,7 @@ impl Task { let (action, rest) = match rest.first() { Some(&"build") => (Action::Build, &rest[1..]), + Some(&"run") => (Action::Run, &rest[1..]), _ => (Action::Run, rest), }; From 73dd9aa5b322f5df865e1b763e343fca2de8a18b Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 4 Mar 2026 15:06:38 +0000 Subject: [PATCH 11/23] improve usage text --- tools/building/src/main.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index b1a4cf5af3..f6a1309d4d 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -3,13 +3,15 @@ use std::process::ExitCode; use building::*; fn usage() { - eprintln!("usage: cargo run [] [release|debug|profiling]"); eprintln!(); - eprintln!("commands:"); - eprintln!(" web Run the dev server"); - eprintln!(" web build Build the web version"); - eprintln!(" desktop Run the desktop app"); - eprintln!(" desktop build Build the desktop version"); + eprintln!("Usage: cargo run [] [release|debug|profiling]"); + eprintln!(); + eprintln!("Commands:"); + eprintln!(" web [run] Run the web app on local dev server"); + eprintln!(" web build Build the web app"); + eprintln!(" desktop [run] Run the desktop app"); + eprintln!(" desktop build Build the desktop app"); + eprintln!(); } fn main() -> ExitCode { From a6dc4468c0af3ef7fcb8a007b6fe53ffc272ea4e Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 4 Mar 2026 16:52:21 +0000 Subject: [PATCH 12/23] Fix linux bundle --- desktop/bundle/src/linux.rs | 1 - tools/building/src/deps.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/desktop/bundle/src/linux.rs b/desktop/bundle/src/linux.rs index fb5c462ffe..02cf06391a 100644 --- a/desktop/bundle/src/linux.rs +++ b/desktop/bundle/src/linux.rs @@ -12,7 +12,6 @@ pub fn main() -> Result<(), Box> { eprintln!("Binary built and placed at {}", app_bin.to_string_lossy()); eprintln!("Bundling for Linux is not yet implemented."); eprintln!("You can still start the app with the `open` subcommand. `cargo run -p graphite-desktop-bundle -- open`"); - std::process::exit(1); } Ok(()) diff --git a/tools/building/src/deps.rs b/tools/building/src/deps.rs index 3dd06871df..536864a3b9 100644 --- a/tools/building/src/deps.rs +++ b/tools/building/src/deps.rs @@ -70,14 +70,14 @@ fn dependencies(task: &Task) -> Vec { command: "cmake", args: &["--version"], name: "CMake", - skip: Some(&|task| !matches!(task.target, Target::Desktop)), + skip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = "linux")), ..Default::default() }, Dependency { command: "ninja", args: &["--version"], name: "Ninja", - skip: Some(&|task| !matches!(task.target, Target::Desktop) || !cfg!(target_os = "windows")), + skip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = "linux")), ..Default::default() }, ] From 9bc512d5db3a1f9d15d207936de24bfeb3d1aadc Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 5 Mar 2026 16:41:29 +0000 Subject: [PATCH 13/23] add graphen-cli --- desktop/bundle/build.rs | 8 ++++++- desktop/bundle/src/common.rs | 3 +-- desktop/bundle/src/linux.rs | 6 +++-- desktop/bundle/src/mac.rs | 6 +++-- desktop/bundle/src/win.rs | 6 +++-- tools/building/src/deps.rs | 7 ++++-- tools/building/src/lib.rs | 31 +++++++++++++++++------- tools/building/src/main.rs | 46 ++++++++++++++++++++---------------- 8 files changed, 73 insertions(+), 40 deletions(-) diff --git a/desktop/bundle/build.rs b/desktop/bundle/build.rs index 01b6ec29b5..de85404ff0 100644 --- a/desktop/bundle/build.rs +++ b/desktop/bundle/build.rs @@ -1,7 +1,13 @@ fn main() { println!("cargo:rerun-if-env-changed=CARGO_PROFILE"); println!("cargo:rerun-if-env-changed=PROFILE"); - let profile = std::env::var("CARGO_PROFILE").or_else(|_| std::env::var("PROFILE")).unwrap(); + let mut profile = std::env::var("CARGO_PROFILE").or_else(|_| std::env::var("PROFILE")).unwrap(); + if profile == "release" + && let Ok(debug_str) = std::env::var("DEBUG") + && debug_str.parse().unwrap_or(false) + { + profile = "profiling".to_string(); + } println!("cargo:rustc-env=CARGO_PROFILE={profile}"); println!("cargo:rerun-if-env-changed=DEP_CEF_DLL_WRAPPER_CEF_DIR"); diff --git a/desktop/bundle/src/common.rs b/desktop/bundle/src/common.rs index 4ae8a5df26..6b11ab532f 100644 --- a/desktop/bundle/src/common.rs +++ b/desktop/bundle/src/common.rs @@ -29,8 +29,7 @@ pub(crate) fn cef_path() -> PathBuf { } pub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result> { - let profile = &profile_name(); - let mut args = vec!["build", "--package", package, "--profile", profile]; + let mut args = vec!["build", "--package", package, "--profile", profile_name()]; if let Some(bin) = bin { args.push("--bin"); args.push(bin); diff --git a/desktop/bundle/src/linux.rs b/desktop/bundle/src/linux.rs index 02cf06391a..f1511d8b02 100644 --- a/desktop/bundle/src/linux.rs +++ b/desktop/bundle/src/linux.rs @@ -6,8 +6,10 @@ pub fn main() -> Result<(), Box> { // TODO: Implement bundling for linux // TODO: Consider adding more useful cli - if std::env::args().any(|a| a == "open") { - run_command(&app_bin.to_string_lossy(), &[]).expect("failed to open app"); + let args: Vec = std::env::args().collect(); + if let Some(pos) = args.iter().position(|a| a == "open") { + let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); + run_command(&app_bin.to_string_lossy(), &extra_args).expect("failed to open app"); } else { eprintln!("Binary built and placed at {}", app_bin.to_string_lossy()); eprintln!("Bundling for Linux is not yet implemented."); diff --git a/desktop/bundle/src/mac.rs b/desktop/bundle/src/mac.rs index a3b74d0153..f516826a69 100644 --- a/desktop/bundle/src/mac.rs +++ b/desktop/bundle/src/mac.rs @@ -22,9 +22,11 @@ pub fn main() -> Result<(), Box> { let app_dir = bundle(&profile_path, &app_bin, &helper_bin); // TODO: Consider adding more useful cli - if std::env::args().any(|a| a == "open") { + let args: Vec = std::env::args().collect(); + if let Some(pos) = args.iter().position(|a| a == "open") { + let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); let executable_path = app_dir.join(EXEC_PATH).join(APP_NAME); - run_command(&executable_path.to_string_lossy(), &[]).expect("failed to open app"); + run_command(&executable_path.to_string_lossy(), &extra_args).expect("failed to open app"); } Ok(()) diff --git a/desktop/bundle/src/win.rs b/desktop/bundle/src/win.rs index 1b7a7279cd..52d603b48a 100644 --- a/desktop/bundle/src/win.rs +++ b/desktop/bundle/src/win.rs @@ -12,9 +12,11 @@ pub fn main() -> Result<(), Box> { let executable = bundle(&profile_path(), &app_bin); // TODO: Consider adding more useful cli - if std::env::args().any(|a| a == "open") { + let args: Vec = std::env::args().collect(); + if let Some(pos) = args.iter().position(|a| a == "open") { let executable_path = executable.to_string_lossy(); - run_command(&executable_path, &[]).expect("failed to open app") + let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); + run_command(&executable_path, &extra_args).expect("failed to open app") } Ok(()) diff --git a/tools/building/src/deps.rs b/tools/building/src/deps.rs index 536864a3b9..bbe1647177 100644 --- a/tools/building/src/deps.rs +++ b/tools/building/src/deps.rs @@ -26,6 +26,7 @@ fn dependencies(task: &Task) -> Vec { args: &["--version"], name: "cargo-about", install: Some("cargo install cargo-about"), + skip: Some(&|task| matches!(task.target, Target::Cli)), ..Default::default() }, Dependency { @@ -39,7 +40,7 @@ fn dependencies(task: &Task) -> Vec { Task { target: Target::Web, action: Action::Run, - profile: _ + .. } ) }), @@ -51,19 +52,21 @@ fn dependencies(task: &Task) -> Vec { name: "wasm-bindgen-cli", version: Some("0.2.100"), install: Some("cargo install -f wasm-bindgen-cli@0.2.100"), - ..Default::default() + skip: Some(&|task| matches!(task.target, Target::Cli)), }, Dependency { command: "wasm-pack", args: &["--version"], name: "wasm-pack", install: Some("cargo install wasm-pack"), + skip: Some(&|task| matches!(task.target, Target::Cli)), ..Default::default() }, Dependency { command: "node", args: &["--version"], name: "Node.js", + skip: Some(&|task| matches!(task.target, Target::Cli)), ..Default::default() }, Dependency { diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index d1f402cecb..b946af5363 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -6,6 +6,7 @@ pub mod deps; pub enum Target { Web, Desktop, + Cli, } pub enum Action { @@ -24,31 +25,45 @@ pub struct Task { pub target: Target, pub action: Action, pub profile: Profile, + pub args: Vec, } impl Task { pub fn parse(args: &[&str]) -> Option { + let split = args.iter().position(|a| *a == "--").unwrap_or(args.len()); + let passthru_args = args[split..].iter().skip(1).map(|s| s.to_string()).collect(); + let args = &args[..split]; + let (target, rest) = match args.first() { Some(&"desktop") => (Target::Desktop, &args[1..]), Some(&"web") => (Target::Web, &args[1..]), - _ => (Target::Web, args), + Some(&"cli") => (Target::Cli, &args[1..]), + Some(&"help") => return None, + None => (Target::Web, args), + _ => return None, }; let (action, rest) = match rest.first() { Some(&"build") => (Action::Build, &rest[1..]), Some(&"run") => (Action::Run, &rest[1..]), - _ => (Action::Run, rest), + None => (Action::Run, rest), + _ => return None, }; - let profile = match rest.first().copied().unwrap_or_default() { - "" => Profile::Default, - "release" => Profile::Release, - "debug" => Profile::Debug, - "profiling" => Profile::Profiling, + let profile = match rest.first() { + Some(&"release") => Profile::Release, + Some(&"debug") => Profile::Debug, + Some(&"profiling") => Profile::Profiling, + None => Profile::Default, _ => return None, }; - Some(Task { target, action, profile }) + Some(Task { + target, + action, + profile, + args: passthru_args, + }) } } diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index f6a1309d4d..1c5b0c6a96 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -4,13 +4,16 @@ use building::*; fn usage() { eprintln!(); - eprintln!("Usage: cargo run [] [release|debug|profiling]"); + eprintln!("Usage: cargo run [] [release|debug|profiling] -- [args...]"); eprintln!(); eprintln!("Commands:"); eprintln!(" web [run] Run the web app on local dev server"); eprintln!(" web build Build the web app"); eprintln!(" desktop [run] Run the desktop app"); eprintln!(" desktop build Build the desktop app"); + eprintln!(" cli [run] Run the Graphen CLI"); + eprintln!(" cli build Build the Graphen CLI"); + eprintln!(" help Show this message"); eprintln!(); } @@ -45,29 +48,30 @@ fn run_task(task: &Task) -> Result<(), Error> { (Target::Web, Action::Build, Profile::Release | Profile::Default) => npm_run_in_frontend_dir("build")?, (Target::Web, Action::Build, Profile::Profiling) => npm_run_in_frontend_dir("build-profiling")?, - (Target::Desktop, Action::Run, Profile::Debug | Profile::Default) => { - npm_run_in_frontend_dir("build-native-dev")?; + (Target::Desktop, action, profile) => { + if matches!((action, profile), (_, Profile::Release) | (Action::Build, Profile::Default)) { + npm_run_in_frontend_dir("build-native")?; + } else { + npm_run_in_frontend_dir("build-native-dev")?; + }; run("cargo run -p third-party-licenses --features desktop")?; - run("cargo run -p graphite-desktop-bundle -- open")?; - } - (Target::Desktop, Action::Run, Profile::Release) => { - npm_run_in_frontend_dir("build-native")?; - run("cargo run -p third-party-licenses --features desktop")?; - run("cargo run -r -p graphite-desktop-bundle -- open")?; - } - (Target::Desktop, Action::Run, Profile::Profiling) => todo!("profiling run for desktop"), - (Target::Desktop, Action::Build, Profile::Debug) => { - npm_run_in_frontend_dir("build-native-dev")?; - run("cargo run -p third-party-licenses --features desktop")?; - run("cargo run -p graphite-desktop-bundle")?; + let cargo_profile = match profile { + Profile::Debug | Profile::Default => "dev", + Profile::Release => "release", + Profile::Profiling => "profiling", + }; + let args = if let Action::Run = action { format!(" -- open {}", task.args.join(" ")) } else { "".to_string() }; + run(&format!("cargo run --profile {cargo_profile} -p graphite-desktop-bundle{args}"))?; } - (Target::Desktop, Action::Build, Profile::Release | Profile::Default) => { - npm_run_in_frontend_dir("build-native")?; - run("cargo run -p third-party-licenses --features desktop")?; - run("cargo run -r -p graphite-desktop-bundle")?; - } - (Target::Desktop, Action::Build, Profile::Profiling) => todo!("profiling build for desktop"), + + (Target::Cli, Action::Run, Profile::Debug | Profile::Default) => run(&format!("cargo run -p graphene-cli -- {}", task.args.join(" ")))?, + (Target::Cli, Action::Run, Profile::Release) => run(&format!("cargo run -r -p graphene-cli -- {}", task.args.join(" ")))?, + (Target::Cli, Action::Run, Profile::Profiling) => run(&format!("cargo run --profile profiling -p graphene-cli -- {}", task.args.join(" ")))?, + + (Target::Cli, Action::Build, Profile::Debug) => run("cargo build -p graphene-cli")?, + (Target::Cli, Action::Build, Profile::Release | Profile::Default) => run("cargo build -r -p graphene-cli")?, + (Target::Cli, Action::Build, Profile::Profiling) => run("cargo build --profile profiling -p graphene-cli")?, } Ok(()) } From 75a0a6ec0a1f8562ed04cb349fb677ae1396911d Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 5 Mar 2026 21:23:52 +0000 Subject: [PATCH 14/23] fix build profile --- tools/building/src/main.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index 1c5b0c6a96..2d1e30ee02 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -48,18 +48,27 @@ fn run_task(task: &Task) -> Result<(), Error> { (Target::Web, Action::Build, Profile::Release | Profile::Default) => npm_run_in_frontend_dir("build")?, (Target::Web, Action::Build, Profile::Profiling) => npm_run_in_frontend_dir("build-profiling")?, - (Target::Desktop, action, profile) => { - if matches!((action, profile), (_, Profile::Release) | (Action::Build, Profile::Default)) { + (Target::Desktop, action, mut profile) => { + if matches!(profile, Profile::Default) { + profile = match action { + Action::Build => &Profile::Release, + Action::Run => &Profile::Debug, + } + } + + if matches!(profile, Profile::Release) { npm_run_in_frontend_dir("build-native")?; } else { npm_run_in_frontend_dir("build-native-dev")?; }; + run("cargo run -p third-party-licenses --features desktop")?; let cargo_profile = match profile { - Profile::Debug | Profile::Default => "dev", + Profile::Debug => "dev", Profile::Release => "release", Profile::Profiling => "profiling", + Profile::Default => unreachable!(), }; let args = if let Action::Run = action { format!(" -- open {}", task.args.join(" ")) } else { "".to_string() }; run(&format!("cargo run --profile {cargo_profile} -p graphite-desktop-bundle{args}"))?; From da2e412f5e4281c9b14da68f2e5ceef77fbe601e Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 5 Mar 2026 21:31:29 +0000 Subject: [PATCH 15/23] fix --- tools/building/src/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index 2d1e30ee02..a70bdfa9c0 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -70,7 +70,11 @@ fn run_task(task: &Task) -> Result<(), Error> { Profile::Profiling => "profiling", Profile::Default => unreachable!(), }; - let args = if let Action::Run = action { format!(" -- open {}", task.args.join(" ")) } else { "".to_string() }; + let args = if matches!(action, Action::Run) { + format!(" -- open {}", task.args.join(" ")) + } else { + "".to_string() + }; run(&format!("cargo run --profile {cargo_profile} -p graphite-desktop-bundle{args}"))?; } From 2a4c5c670ff22d25303475f569a95503abfdb0e9 Mon Sep 17 00:00:00 2001 From: Timon Date: Thu, 5 Mar 2026 22:56:58 +0000 Subject: [PATCH 16/23] release profile should not include debug infos --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 893cdda80d..17dc712c4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -235,7 +235,6 @@ node-macro = { opt-level = 2 } [profile.release] lto = "thin" -debug = true [profile.profiling] inherits = "release" From 71a1b234726311d523e87e9d8362cd05ac87dc14 Mon Sep 17 00:00:00 2001 From: Timon Date: Fri, 6 Mar 2026 03:01:56 +0000 Subject: [PATCH 17/23] Review --- .github/workflows/build-mac-bundle.yml | 2 +- .github/workflows/build-production.yml | 2 +- .github/workflows/build-win-bundle.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/comment-!build-commands.yml | 8 +- .github/workflows/deploy-master.yml | 2 +- tools/building/src/lib.rs | 34 ++++----- tools/building/src/main.rs | 74 ++++++++++++------- .../volunteer/guide/project-setup/_index.md | 26 +------ 9 files changed, 73 insertions(+), 79 deletions(-) diff --git a/.github/workflows/build-mac-bundle.yml b/.github/workflows/build-mac-bundle.yml index 08f9dd043f..b11eba7954 100644 --- a/.github/workflows/build-mac-bundle.yml +++ b/.github/workflows/build-mac-bundle.yml @@ -67,7 +67,7 @@ jobs: - name: Build Mac Bundle env: CARGO_TERM_COLOR: always - run: cargo run desktop build + run: cargo run build desktop - name: Stage Artifacts shell: bash diff --git a/.github/workflows/build-production.yml b/.github/workflows/build-production.yml index b01f090e92..eae2aca9c6 100644 --- a/.github/workflows/build-production.yml +++ b/.github/workflows/build-production.yml @@ -52,7 +52,7 @@ jobs: - name: 🌐 Build Graphite web code env: NODE_ENV: production - run: mold -run cargo run web build + run: mold -run cargo run build web - name: 📤 Publish to Cloudflare Pages id: cloudflare diff --git a/.github/workflows/build-win-bundle.yml b/.github/workflows/build-win-bundle.yml index 49983af141..720bdb63a7 100644 --- a/.github/workflows/build-win-bundle.yml +++ b/.github/workflows/build-win-bundle.yml @@ -73,7 +73,7 @@ jobs: shell: bash # `cargo-about` refuses to run in powershell env: CARGO_TERM_COLOR: always - run: cargo run desktop build + run: cargo run build desktop - name: Stage Artifacts shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a7558887f..4985abfe20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: if: steps.skip-check.outputs.skip-check != 'true' env: NODE_ENV: production - run: mold -run cargo run web build + run: mold -run cargo run build web - name: 📤 Publish to Cloudflare Pages if: steps.skip-check.outputs.skip-check != 'true' diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml index aaadb924d4..8304f81fd3 100644 --- a/.github/workflows/comment-!build-commands.yml +++ b/.github/workflows/comment-!build-commands.yml @@ -83,11 +83,11 @@ jobs: id: build_command run: | if [[ "${{ github.event.comment.body }}" == "!build-debug" ]]; then - echo "command=build debug" >> $GITHUB_OUTPUT + echo "command=build web debug" >> $GITHUB_OUTPUT elif [[ "${{ github.event.comment.body }}" == "!build-profiling" ]]; then - echo "command=build profiling" >> $GITHUB_OUTPUT + echo "command=build web profiling" >> $GITHUB_OUTPUT elif [[ "${{ github.event.comment.body }}" == "!build" ]]; then - echo "command=build" >> $GITHUB_OUTPUT + echo "command=build web" >> $GITHUB_OUTPUT else echo "Failed to detect if the build command written in the comment should have been '!build-debug', '!build-profiling', or '!build'" >> $GITHUB_OUTPUT fi @@ -108,7 +108,7 @@ jobs: env: NODE_ENV: production if: ${{ success() || failure()}} - run: mold -run cargo run web ${{ steps.build_command.outputs.command }} + run: mold -run cargo run ${{ steps.build_command.outputs.command }} - name: ❗ Warn on build failure if: ${{ failure() }} diff --git a/.github/workflows/deploy-master.yml b/.github/workflows/deploy-master.yml index 57f5cc2676..d78e0b47b6 100644 --- a/.github/workflows/deploy-master.yml +++ b/.github/workflows/deploy-master.yml @@ -49,7 +49,7 @@ jobs: - name: 🌐 Build Graphite web code env: NODE_ENV: production - run: mold -run cargo run web build + run: mold -run cargo run build web - name: 📤 Publish to Cloudflare Pages id: cloudflare diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index b946af5363..454ef34c3d 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -3,17 +3,17 @@ use std::process; pub mod deps; +pub enum Action { + Run, + Build, +} + pub enum Target { Web, Desktop, Cli, } -pub enum Action { - Run, - Build, -} - pub enum Profile { Default, Release, @@ -22,8 +22,8 @@ pub enum Profile { } pub struct Task { - pub target: Target, pub action: Action, + pub target: Target, pub profile: Profile, pub args: Vec, } @@ -34,23 +34,21 @@ impl Task { let passthru_args = args[split..].iter().skip(1).map(|s| s.to_string()).collect(); let args = &args[..split]; - let (target, rest) = match args.first() { - Some(&"desktop") => (Target::Desktop, &args[1..]), - Some(&"web") => (Target::Web, &args[1..]), - Some(&"cli") => (Target::Cli, &args[1..]), + let (action, args) = match args.first() { + Some(&"build") => (Action::Build, &args[1..]), + Some(&"run") => (Action::Run, &args[1..]), Some(&"help") => return None, - None => (Target::Web, args), - _ => return None, + _ => (Action::Run, args), }; - let (action, rest) = match rest.first() { - Some(&"build") => (Action::Build, &rest[1..]), - Some(&"run") => (Action::Run, &rest[1..]), - None => (Action::Run, rest), - _ => return None, + let (target, args) = match args.first() { + Some(&"desktop") => (Target::Desktop, &args[1..]), + Some(&"web") => (Target::Web, &args[1..]), + Some(&"cli") => (Target::Cli, &args[1..]), + _ => (Target::Web, args), }; - let profile = match rest.first() { + let profile = match args.first() { Some(&"release") => Profile::Release, Some(&"debug") => Profile::Debug, Some(&"profiling") => Profile::Profiling, diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index a70bdfa9c0..f4bf7becc4 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -3,18 +3,38 @@ use std::process::ExitCode; use building::*; fn usage() { - eprintln!(); - eprintln!("Usage: cargo run [] [release|debug|profiling] -- [args...]"); - eprintln!(); - eprintln!("Commands:"); - eprintln!(" web [run] Run the web app on local dev server"); - eprintln!(" web build Build the web app"); - eprintln!(" desktop [run] Run the desktop app"); - eprintln!(" desktop build Build the desktop app"); - eprintln!(" cli [run] Run the Graphen CLI"); - eprintln!(" cli build Build the Graphen CLI"); - eprintln!(" help Show this message"); - eprintln!(); + println!(); + println!("USAGE:"); + println!(" cargo run [] [] [] [-- [args]...]"); + println!(); + println!("COMMON USAGE:"); + println!(" cargo run Run the web app"); + println!(" cargo run desktop Run the desktop app"); + println!(); + println!("OPTIONS:"); + println!(":"); + println!(" [run] Run the selected target (default)"); + println!(" build Build the selected target"); + println!(" help Show this message"); + println!(":"); + println!(" [web] Web app (default)"); + println!(" desktop Desktop app"); + println!(" cli Graphene CLI"); + println!(":"); + println!(" [debug] Debug symbols enabled,"); + println!(" optimizations disabled"); + println!(" (default for run)"); + println!(" [release] Debug symbols disabled,"); + println!(" optimizations enabled"); + println!(" (default for build)"); + println!(" profiling Debug symbols enabled,"); + println!(" optimizations enabled"); + println!(); + println!("MORE EXAMPLES:"); + println!(" cargo run build desktop"); + println!(" cargo run desktop profiling"); + println!(" cargo run cli -- --help"); + println!() } fn main() -> ExitCode { @@ -39,20 +59,20 @@ fn main() -> ExitCode { fn run_task(task: &Task) -> Result<(), Error> { deps::check(task)?; - match (&task.target, &task.action, &task.profile) { - (Target::Web, Action::Run, Profile::Debug | Profile::Default) => npm_run_in_frontend_dir("start")?, - (Target::Web, Action::Run, Profile::Release) => npm_run_in_frontend_dir("production")?, - (Target::Web, Action::Run, Profile::Profiling) => npm_run_in_frontend_dir("profiling")?, + match (&task.action, &task.target, &task.profile) { + (Action::Run, Target::Web, Profile::Debug | Profile::Default) => npm_run_in_frontend_dir("start")?, + (Action::Run, Target::Web, Profile::Release) => npm_run_in_frontend_dir("production")?, + (Action::Run, Target::Web, Profile::Profiling) => npm_run_in_frontend_dir("profiling")?, - (Target::Web, Action::Build, Profile::Debug) => npm_run_in_frontend_dir("build-dev")?, - (Target::Web, Action::Build, Profile::Release | Profile::Default) => npm_run_in_frontend_dir("build")?, - (Target::Web, Action::Build, Profile::Profiling) => npm_run_in_frontend_dir("build-profiling")?, + (Action::Build, Target::Web, Profile::Debug) => npm_run_in_frontend_dir("build-dev")?, + (Action::Build, Target::Web, Profile::Release | Profile::Default) => npm_run_in_frontend_dir("build")?, + (Action::Build, Target::Web, Profile::Profiling) => npm_run_in_frontend_dir("build-profiling")?, - (Target::Desktop, action, mut profile) => { + (action, Target::Desktop, mut profile) => { if matches!(profile, Profile::Default) { profile = match action { - Action::Build => &Profile::Release, Action::Run => &Profile::Debug, + Action::Build => &Profile::Release, } } @@ -78,13 +98,13 @@ fn run_task(task: &Task) -> Result<(), Error> { run(&format!("cargo run --profile {cargo_profile} -p graphite-desktop-bundle{args}"))?; } - (Target::Cli, Action::Run, Profile::Debug | Profile::Default) => run(&format!("cargo run -p graphene-cli -- {}", task.args.join(" ")))?, - (Target::Cli, Action::Run, Profile::Release) => run(&format!("cargo run -r -p graphene-cli -- {}", task.args.join(" ")))?, - (Target::Cli, Action::Run, Profile::Profiling) => run(&format!("cargo run --profile profiling -p graphene-cli -- {}", task.args.join(" ")))?, + (Action::Run, Target::Cli, Profile::Debug | Profile::Default) => run(&format!("cargo run -p graphene-cli -- {}", task.args.join(" ")))?, + (Action::Run, Target::Cli, Profile::Release) => run(&format!("cargo run -r -p graphene-cli -- {}", task.args.join(" ")))?, + (Action::Run, Target::Cli, Profile::Profiling) => run(&format!("cargo run --profile profiling -p graphene-cli -- {}", task.args.join(" ")))?, - (Target::Cli, Action::Build, Profile::Debug) => run("cargo build -p graphene-cli")?, - (Target::Cli, Action::Build, Profile::Release | Profile::Default) => run("cargo build -r -p graphene-cli")?, - (Target::Cli, Action::Build, Profile::Profiling) => run("cargo build --profile profiling -p graphene-cli")?, + (Action::Build, Target::Cli, Profile::Debug) => run("cargo build -p graphene-cli")?, + (Action::Build, Target::Cli, Profile::Release | Profile::Default) => run("cargo build -r -p graphene-cli")?, + (Action::Build, Target::Cli, Profile::Profiling) => run("cargo build --profile profiling -p graphene-cli")?, } Ok(()) } diff --git a/website/content/volunteer/guide/project-setup/_index.md b/website/content/volunteer/guide/project-setup/_index.md index bb4b32c8d7..103045dd2d 100644 --- a/website/content/volunteer/guide/project-setup/_index.md +++ b/website/content/volunteer/guide/project-setup/_index.md @@ -12,21 +12,10 @@ To begin working with the Graphite codebase, you will need to set up the project ## Dependencies Graphite is built with Rust and web technologies, which means you will need to install: -- [Node.js](https://nodejs.org/) (the latest LTS version) - [Rust](https://www.rust-lang.org/) (the latest stable release) +- [Node.js](https://nodejs.org/) (the latest LTS version) - [Git](https://git-scm.com/) (any recent version) -Next, install the dependencies required for development builds: - -```sh -cargo install -f wasm-bindgen-cli@0.2.100 -cargo install wasm-pack -cargo install cargo-watch -cargo install cargo-about -``` - -Regarding the last one: you'll likely get faster build times if you manually install that specific version of `wasm-bindgen-cli`. It is supposed to be installed automatically but a version mismatch causes it to reinstall every single recompilation. It may need to be manually updated periodically to match the version of the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml). - ## Repository Clone the project to a convenient location: @@ -64,19 +53,6 @@ cargo run release -
-Production build instructions: click here - -You'll rarely need to compile your own production builds because our CI/CD system takes care of deployments. However, you can compile a production build with full optimizations by running: - -```sh -cargo run web build -``` - -This produces the `/frontend/dist` directory containing the static site files that must be served by your own web server. - -
- ## Development tooling We provide default configurations for VS Code users. When you open the project, watch for a prompt to install the project's [suggested extensions](https://github.com/GraphiteEditor/Graphite/blob/master/.vscode/extensions.json). They will provide helpful web and Rust tooling. If you use a different IDE, you won't get default configurations for the project out of the box, so please remember to format your code and check CI for errors. From 7d5e87edac7f0d1e360daa08e8f6f8d5bfb351d0 Mon Sep 17 00:00:00 2001 From: Timon Date: Fri, 6 Mar 2026 12:59:46 +0000 Subject: [PATCH 18/23] remove profiling profile was redundent with release --- .github/workflows/comment-!build-commands.yml | 7 ++----- Cargo.toml | 3 --- desktop/bundle/build.rs | 8 +------- frontend/package.json | 4 ---- frontend/wasm/Cargo.toml | 8 -------- node-graph/graph-craft/.cargo/config.toml | 3 --- tools/building/src/lib.rs | 2 -- tools/building/src/main.rs | 17 +++-------------- .../volunteer/guide/project-setup/_index.md | 6 ------ 9 files changed, 6 insertions(+), 52 deletions(-) delete mode 100644 node-graph/graph-craft/.cargo/config.toml diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml index 8304f81fd3..0224b7224d 100644 --- a/.github/workflows/comment-!build-commands.yml +++ b/.github/workflows/comment-!build-commands.yml @@ -1,7 +1,6 @@ # USAGE: # After reviewing the code, core team members may comment on a PR with the exact text: # - `!build-debug` to build with debug symbols and optimizations disabled -# - `!build-profiling` to build with debug symbols and optimizations enabled # - `!build` to build without debug symbols and optimizations enabled # The comment may not contain any other text, not even whitespace or newlines. name: "!build PR Command" @@ -21,7 +20,7 @@ jobs: if: > github.event.issue.pull_request && github.event.comment.author_association == 'MEMBER' && - (github.event.comment.body == '!build-debug' || github.event.comment.body == '!build-profiling' || github.event.comment.body == '!build') + (github.event.comment.body == '!build-debug' || github.event.comment.body == '!build') runs-on: self-hosted permissions: contents: read @@ -84,12 +83,10 @@ jobs: run: | if [[ "${{ github.event.comment.body }}" == "!build-debug" ]]; then echo "command=build web debug" >> $GITHUB_OUTPUT - elif [[ "${{ github.event.comment.body }}" == "!build-profiling" ]]; then - echo "command=build web profiling" >> $GITHUB_OUTPUT elif [[ "${{ github.event.comment.body }}" == "!build" ]]; then echo "command=build web" >> $GITHUB_OUTPUT else - echo "Failed to detect if the build command written in the comment should have been '!build-debug', '!build-profiling', or '!build'" >> $GITHUB_OUTPUT + echo "Failed to detect if the build command written in the comment should have been '!build-debug', or '!build'" >> $GITHUB_OUTPUT fi - name: 💬 Comment Actions run link diff --git a/Cargo.toml b/Cargo.toml index 17dc712c4e..e5b5e0cca3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -235,9 +235,6 @@ node-macro = { opt-level = 2 } [profile.release] lto = "thin" - -[profile.profiling] -inherits = "release" debug = true [patch.crates-io] diff --git a/desktop/bundle/build.rs b/desktop/bundle/build.rs index de85404ff0..01b6ec29b5 100644 --- a/desktop/bundle/build.rs +++ b/desktop/bundle/build.rs @@ -1,13 +1,7 @@ fn main() { println!("cargo:rerun-if-env-changed=CARGO_PROFILE"); println!("cargo:rerun-if-env-changed=PROFILE"); - let mut profile = std::env::var("CARGO_PROFILE").or_else(|_| std::env::var("PROFILE")).unwrap(); - if profile == "release" - && let Ok(debug_str) = std::env::var("DEBUG") - && debug_str.parse().unwrap_or(false) - { - profile = "profiling".to_string(); - } + let profile = std::env::var("CARGO_PROFILE").or_else(|_| std::env::var("PROFILE")).unwrap(); println!("cargo:rustc-env=CARGO_PROFILE={profile}"); println!("cargo:rerun-if-env-changed=DEP_CEF_DLL_WRAPPER_CEF_DIR"); diff --git a/frontend/package.json b/frontend/package.json index ac5bc4fbfd..fc70d5f72c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,12 +8,10 @@ "scripts": { "---------- DEV SERVER ----------": "", "start": "npm run setup && npm run wasm:build-dev && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-dev\"", - "profiling": "npm run setup && npm run wasm:build-profiling && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-profiling\"", "production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"", "---------- BUILDS ----------": "", "build": "npm run setup && npm run wasm:build-production && vite build", "build-dev": "npm run setup && npm run wasm:build-dev && vite build --mode dev", - "build-profiling": "npm run setup && npm run wasm:build-profiling && vite build --mode dev", "build-native": "npm run setup && npm run native:build-production", "build-native-dev": "npm run setup && npm run native:build-dev", "---------- UTILITIES ----------": "", @@ -24,10 +22,8 @@ "native:build-dev": "wasm-pack build ./wasm --dev --target=web --no-default-features --features native && vite build --mode native", "native:build-production": "wasm-pack build ./wasm --release --target=web --no-default-features --features native && vite build --mode native", "wasm:build-dev": "wasm-pack build ./wasm --dev --target=web", - "wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web", "wasm:build-production": "wasm-pack build ./wasm --release --target=web", "wasm:watch-dev": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --dev --target=web -- --color=always\"", - "wasm:watch-profiling": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --profiling --target=web -- --color=always\"", "wasm:watch-production": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --release --target=web -- --color=always\"" }, "//": "NOTE: `source-sans-pro` is never to be upgraded to 3.x because that renders a pixel above its intended position.", diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index a4d21762b9..c1632a0f80 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -57,14 +57,6 @@ debug-js-glue = false demangle-name-section = false dwarf-debug-info = false -[package.metadata.wasm-pack.profile.profiling] -wasm-opt = ["-Os", "-g"] - -[package.metadata.wasm-pack.profile.profiling.wasm-bindgen] -debug-js-glue = true -demangle-name-section = true -dwarf-debug-info = true - [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(wasm_bindgen_unstable_test_coverage)', diff --git a/node-graph/graph-craft/.cargo/config.toml b/node-graph/graph-craft/.cargo/config.toml deleted file mode 100644 index a4ae00c0ad..0000000000 --- a/node-graph/graph-craft/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[profile.profiling] -inherits = "release" -debug = true diff --git a/tools/building/src/lib.rs b/tools/building/src/lib.rs index 454ef34c3d..376e0baca9 100644 --- a/tools/building/src/lib.rs +++ b/tools/building/src/lib.rs @@ -18,7 +18,6 @@ pub enum Profile { Default, Release, Debug, - Profiling, } pub struct Task { @@ -51,7 +50,6 @@ impl Task { let profile = match args.first() { Some(&"release") => Profile::Release, Some(&"debug") => Profile::Debug, - Some(&"profiling") => Profile::Profiling, None => Profile::Default, _ => return None, }; diff --git a/tools/building/src/main.rs b/tools/building/src/main.rs index f4bf7becc4..3e7359b661 100644 --- a/tools/building/src/main.rs +++ b/tools/building/src/main.rs @@ -21,18 +21,12 @@ fn usage() { println!(" desktop Desktop app"); println!(" cli Graphene CLI"); println!(":"); - println!(" [debug] Debug symbols enabled,"); - println!(" optimizations disabled"); - println!(" (default for run)"); - println!(" [release] Debug symbols disabled,"); - println!(" optimizations enabled"); - println!(" (default for build)"); - println!(" profiling Debug symbols enabled,"); - println!(" optimizations enabled"); + println!(" [debug] Optimizations disabled (default for run)"); + println!(" [release] Optimizations enabled (default for build)"); println!(); println!("MORE EXAMPLES:"); println!(" cargo run build desktop"); - println!(" cargo run desktop profiling"); + println!(" cargo run desktop release"); println!(" cargo run cli -- --help"); println!() } @@ -62,11 +56,9 @@ fn run_task(task: &Task) -> Result<(), Error> { match (&task.action, &task.target, &task.profile) { (Action::Run, Target::Web, Profile::Debug | Profile::Default) => npm_run_in_frontend_dir("start")?, (Action::Run, Target::Web, Profile::Release) => npm_run_in_frontend_dir("production")?, - (Action::Run, Target::Web, Profile::Profiling) => npm_run_in_frontend_dir("profiling")?, (Action::Build, Target::Web, Profile::Debug) => npm_run_in_frontend_dir("build-dev")?, (Action::Build, Target::Web, Profile::Release | Profile::Default) => npm_run_in_frontend_dir("build")?, - (Action::Build, Target::Web, Profile::Profiling) => npm_run_in_frontend_dir("build-profiling")?, (action, Target::Desktop, mut profile) => { if matches!(profile, Profile::Default) { @@ -87,7 +79,6 @@ fn run_task(task: &Task) -> Result<(), Error> { let cargo_profile = match profile { Profile::Debug => "dev", Profile::Release => "release", - Profile::Profiling => "profiling", Profile::Default => unreachable!(), }; let args = if matches!(action, Action::Run) { @@ -100,11 +91,9 @@ fn run_task(task: &Task) -> Result<(), Error> { (Action::Run, Target::Cli, Profile::Debug | Profile::Default) => run(&format!("cargo run -p graphene-cli -- {}", task.args.join(" ")))?, (Action::Run, Target::Cli, Profile::Release) => run(&format!("cargo run -r -p graphene-cli -- {}", task.args.join(" ")))?, - (Action::Run, Target::Cli, Profile::Profiling) => run(&format!("cargo run --profile profiling -p graphene-cli -- {}", task.args.join(" ")))?, (Action::Build, Target::Cli, Profile::Debug) => run("cargo build -p graphene-cli")?, (Action::Build, Target::Cli, Profile::Release | Profile::Default) => run("cargo build -r -p graphene-cli")?, - (Action::Build, Target::Cli, Profile::Profiling) => run("cargo build --profile profiling -p graphene-cli")?, } Ok(()) } diff --git a/website/content/volunteer/guide/project-setup/_index.md b/website/content/volunteer/guide/project-setup/_index.md index 103045dd2d..6c16070a31 100644 --- a/website/content/volunteer/guide/project-setup/_index.md +++ b/website/content/volunteer/guide/project-setup/_index.md @@ -41,12 +41,6 @@ This method compiles Graphite code in debug mode which includes debug symbols fo On rare occasions (like while running advanced performance profiles or proxying the dev server connection over a slow network where the >100 MB unoptimized binary size would pose an issue), you may need to run the dev server with release optimizations. To do that while keeping debug symbols: -```sh -cargo run profiling -``` - -To run the dev server without debug symbols, using the same release optimizations as production builds: - ```sh cargo run release ``` From 3130fd8fb8592cec93ddda1fd155a18139818297 Mon Sep 17 00:00:00 2001 From: Timon Date: Fri, 6 Mar 2026 13:02:42 +0000 Subject: [PATCH 19/23] rename to cargo-run tool --- Cargo.lock | 14 +++++++------- Cargo.toml | 4 ++-- tools/{building => cargo-run}/Cargo.toml | 4 ++-- tools/{building => cargo-run}/src/deps.rs | 0 tools/{building => cargo-run}/src/lib.rs | 0 tools/{building => cargo-run}/src/main.rs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) rename tools/{building => cargo-run}/Cargo.toml (78%) rename tools/{building => cargo-run}/src/deps.rs (100%) rename tools/{building => cargo-run}/src/lib.rs (100%) rename tools/{building => cargo-run}/src/main.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index e42201170b..2fd2e3d2b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,13 +609,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "building" -version = "0.0.0" -dependencies = [ - "thiserror 2.0.18", -] - [[package]] name = "built" version = "0.7.7" @@ -738,6 +731,13 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo-run" +version = "0.0.0" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "cargo-util-schemas" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index e5b5e0cca3..46a5387942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ "node-graph/node-macro", "node-graph/preprocessor", "proc-macros", - "tools/building", + "tools/cargo-run", "tools/crate-hierarchy-viz", "tools/third-party-licenses", "tools/editor-message-tree", @@ -41,7 +41,7 @@ default-members = [ "node-graph/preprocessor", # blocked by https://github.com/rust-lang/cargo/issues/16000 # "proc-macros", - "tools/building", + "tools/cargo-run", ] resolver = "2" diff --git a/tools/building/Cargo.toml b/tools/cargo-run/Cargo.toml similarity index 78% rename from tools/building/Cargo.toml rename to tools/cargo-run/Cargo.toml index 71b73431fe..95385fc410 100644 --- a/tools/building/Cargo.toml +++ b/tools/cargo-run/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "building" +name = "cargo-run" edition.workspace = true version.workspace = true license.workspace = true authors.workspace = true -default-run = "building" +default-run = "cargo-run" [dependencies] thiserror = { workspace = true } diff --git a/tools/building/src/deps.rs b/tools/cargo-run/src/deps.rs similarity index 100% rename from tools/building/src/deps.rs rename to tools/cargo-run/src/deps.rs diff --git a/tools/building/src/lib.rs b/tools/cargo-run/src/lib.rs similarity index 100% rename from tools/building/src/lib.rs rename to tools/cargo-run/src/lib.rs diff --git a/tools/building/src/main.rs b/tools/cargo-run/src/main.rs similarity index 99% rename from tools/building/src/main.rs rename to tools/cargo-run/src/main.rs index 3e7359b661..c35c0247c5 100644 --- a/tools/building/src/main.rs +++ b/tools/cargo-run/src/main.rs @@ -1,6 +1,6 @@ use std::process::ExitCode; -use building::*; +use cargo_run::*; fn usage() { println!(); From e84b2affe979a1601582b8dce8aeac4dc12ef15d Mon Sep 17 00:00:00 2001 From: Timon Date: Fri, 6 Mar 2026 14:02:37 +0000 Subject: [PATCH 20/23] improve consistency --- .github/workflows/deploy-master.yml | 2 +- desktop/bundle/src/common.rs | 2 +- desktop/bundle/src/mac.rs | 2 +- desktop/bundle/src/win.rs | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-master.yml b/.github/workflows/deploy-master.yml index d78e0b47b6..bf6c280b22 100644 --- a/.github/workflows/deploy-master.yml +++ b/.github/workflows/deploy-master.yml @@ -31,7 +31,7 @@ jobs: - name: 🟢 Install the latest Node.js uses: actions/setup-node@v4 with: - node-version: latest + node-version: "latest" - name: 🚧 Install build dependencies run: | diff --git a/desktop/bundle/src/common.rs b/desktop/bundle/src/common.rs index 6b11ab532f..88e16ff2a7 100644 --- a/desktop/bundle/src/common.rs +++ b/desktop/bundle/src/common.rs @@ -46,7 +46,7 @@ pub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result Result<(), Box> { let status = Command::new(program).args(args).stdout(Stdio::inherit()).stderr(Stdio::inherit()).status()?; if !status.success() { - panic!("Command '{}' with args {:?} failed with status: {}", program, args, status); + return Err(format!("Command '{}' with args {:?} failed with status: {}", program, args, status).into()); } Ok(()) } diff --git a/desktop/bundle/src/mac.rs b/desktop/bundle/src/mac.rs index f516826a69..e207ae7ef6 100644 --- a/desktop/bundle/src/mac.rs +++ b/desktop/bundle/src/mac.rs @@ -24,8 +24,8 @@ pub fn main() -> Result<(), Box> { // TODO: Consider adding more useful cli let args: Vec = std::env::args().collect(); if let Some(pos) = args.iter().position(|a| a == "open") { - let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); let executable_path = app_dir.join(EXEC_PATH).join(APP_NAME); + let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); run_command(&executable_path.to_string_lossy(), &extra_args).expect("failed to open app"); } diff --git a/desktop/bundle/src/win.rs b/desktop/bundle/src/win.rs index 52d603b48a..4218d554ff 100644 --- a/desktop/bundle/src/win.rs +++ b/desktop/bundle/src/win.rs @@ -14,9 +14,8 @@ pub fn main() -> Result<(), Box> { // TODO: Consider adding more useful cli let args: Vec = std::env::args().collect(); if let Some(pos) = args.iter().position(|a| a == "open") { - let executable_path = executable.to_string_lossy(); let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); - run_command(&executable_path, &extra_args).expect("failed to open app") + run_command(&executable_path.to_string_lossy(), &extra_args).expect("failed to open app") } Ok(()) From 37e78df8dda2b6c3c0b2e3f33d61c0281a522cc2 Mon Sep 17 00:00:00 2001 From: Timon Date: Fri, 6 Mar 2026 15:27:19 +0000 Subject: [PATCH 21/23] rename deps to requirements --- tools/cargo-run/src/lib.rs | 2 +- tools/cargo-run/src/main.rs | 2 +- .../src/{deps.rs => requirements.rs} | 28 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) rename tools/cargo-run/src/{deps.rs => requirements.rs} (92%) diff --git a/tools/cargo-run/src/lib.rs b/tools/cargo-run/src/lib.rs index 376e0baca9..46de74f6a2 100644 --- a/tools/cargo-run/src/lib.rs +++ b/tools/cargo-run/src/lib.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use std::process; -pub mod deps; +pub mod requirements; pub enum Action { Run, diff --git a/tools/cargo-run/src/main.rs b/tools/cargo-run/src/main.rs index c35c0247c5..67eef7982a 100644 --- a/tools/cargo-run/src/main.rs +++ b/tools/cargo-run/src/main.rs @@ -51,7 +51,7 @@ fn main() -> ExitCode { } fn run_task(task: &Task) -> Result<(), Error> { - deps::check(task)?; + requirements::check(task)?; match (&task.action, &task.target, &task.profile) { (Action::Run, Target::Web, Profile::Debug | Profile::Default) => npm_run_in_frontend_dir("start")?, diff --git a/tools/cargo-run/src/deps.rs b/tools/cargo-run/src/requirements.rs similarity index 92% rename from tools/cargo-run/src/deps.rs rename to tools/cargo-run/src/requirements.rs index bbe1647177..9da2a0e7b6 100644 --- a/tools/cargo-run/src/deps.rs +++ b/tools/cargo-run/src/requirements.rs @@ -4,7 +4,7 @@ use std::process::Command; use crate::*; #[derive(Default, Clone)] -struct Dependency { +struct Requirement { command: &'static str, args: &'static [&'static str], name: &'static str, @@ -13,15 +13,15 @@ struct Dependency { skip: Option<&'static dyn Fn(&Task) -> bool>, } -fn dependencies(task: &Task) -> Vec { +fn requirements(task: &Task) -> Vec { [ - Dependency { + Requirement { command: "rustc", args: &["--version"], name: "Rust", ..Default::default() }, - Dependency { + Requirement { command: "cargo-about", args: &["--version"], name: "cargo-about", @@ -29,7 +29,7 @@ fn dependencies(task: &Task) -> Vec { skip: Some(&|task| matches!(task.target, Target::Cli)), ..Default::default() }, - Dependency { + Requirement { command: "cargo-watch", args: &["--version"], name: "cargo-watch", @@ -46,7 +46,7 @@ fn dependencies(task: &Task) -> Vec { }), ..Default::default() }, - Dependency { + Requirement { command: "wasm-bindgen", args: &["--version"], name: "wasm-bindgen-cli", @@ -54,7 +54,7 @@ fn dependencies(task: &Task) -> Vec { install: Some("cargo install -f wasm-bindgen-cli@0.2.100"), skip: Some(&|task| matches!(task.target, Target::Cli)), }, - Dependency { + Requirement { command: "wasm-pack", args: &["--version"], name: "wasm-pack", @@ -62,21 +62,21 @@ fn dependencies(task: &Task) -> Vec { skip: Some(&|task| matches!(task.target, Target::Cli)), ..Default::default() }, - Dependency { + Requirement { command: "node", args: &["--version"], name: "Node.js", skip: Some(&|task| matches!(task.target, Target::Cli)), ..Default::default() }, - Dependency { + Requirement { command: "cmake", args: &["--version"], name: "CMake", skip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = "linux")), ..Default::default() }, - Dependency { + Requirement { command: "ninja", args: &["--version"], name: "Ninja", @@ -92,12 +92,12 @@ fn dependencies(task: &Task) -> Vec { pub fn check(task: &Task) -> Result<(), Error> { eprintln!(); - eprintln!("Checking dependencies:"); + eprintln!("Checking Requirements:"); - let mut installable: Vec = Vec::new(); + let mut installable: Vec = Vec::new(); let mut failures: Vec = Vec::new(); - for dep in dependencies(task) { + for dep in requirements(task) { match Command::new(dep.command).args(dep.args).output() { Ok(output) if output.status.success() => { let version = String::from_utf8_lossy(&output.stdout); @@ -145,7 +145,7 @@ pub fn check(task: &Task) -> Result<(), Error> { } let total = installable.len() + failures.len(); - eprintln!("{total} missing or misconfigured dependenc{}:", if total == 1 { "y" } else { "ies" }); + eprintln!("{total} requirement{} not met:", if total > 1 { "s" } else { "" }); for dep in &installable { eprintln!(" - {}: {}", dep.name, dep.install.unwrap()); } From 2c16553104448796a78a999a73414019405eb9cb Mon Sep 17 00:00:00 2001 From: Timon Date: Fri, 6 Mar 2026 17:06:13 +0100 Subject: [PATCH 22/23] fix --- desktop/bundle/src/mac.rs | 4 ++-- desktop/bundle/src/win.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/bundle/src/mac.rs b/desktop/bundle/src/mac.rs index e207ae7ef6..3e53195ef6 100644 --- a/desktop/bundle/src/mac.rs +++ b/desktop/bundle/src/mac.rs @@ -24,9 +24,9 @@ pub fn main() -> Result<(), Box> { // TODO: Consider adding more useful cli let args: Vec = std::env::args().collect(); if let Some(pos) = args.iter().position(|a| a == "open") { - let executable_path = app_dir.join(EXEC_PATH).join(APP_NAME); + let executable = app_dir.join(EXEC_PATH).join(APP_NAME); let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); - run_command(&executable_path.to_string_lossy(), &extra_args).expect("failed to open app"); + run_command(&executable.to_string_lossy(), &extra_args).expect("failed to open app"); } Ok(()) diff --git a/desktop/bundle/src/win.rs b/desktop/bundle/src/win.rs index 4218d554ff..6fc46fdda9 100644 --- a/desktop/bundle/src/win.rs +++ b/desktop/bundle/src/win.rs @@ -15,7 +15,7 @@ pub fn main() -> Result<(), Box> { let args: Vec = std::env::args().collect(); if let Some(pos) = args.iter().position(|a| a == "open") { let extra_args: Vec<&str> = args[pos + 1..].iter().map(|s| s.as_str()).collect(); - run_command(&executable_path.to_string_lossy(), &extra_args).expect("failed to open app") + run_command(&executable.to_string_lossy(), &extra_args).expect("failed to open app") } Ok(()) From ac4c9e98004fad9befb920545de6f0c32ab07b5f Mon Sep 17 00:00:00 2001 From: Timon Date: Sat, 7 Mar 2026 12:32:28 +0100 Subject: [PATCH 23/23] return success when showing usage --- tools/cargo-run/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cargo-run/src/main.rs b/tools/cargo-run/src/main.rs index 67eef7982a..7527de06ff 100644 --- a/tools/cargo-run/src/main.rs +++ b/tools/cargo-run/src/main.rs @@ -39,7 +39,7 @@ fn main() -> ExitCode { Some(run) => run, None => { usage(); - return ExitCode::FAILURE; + return ExitCode::SUCCESS; } };