Replace `npm` build script with new `cargo run` tool (#3832)
* move nix flake to root * cargo run tool * use thiserror in third-party-licenses tool * prefere panic over exit * Add automatic dependency check to cargo run tool * Skip dependecies that are not needed for the current task * Fixup * Fixup * fix windows * Fixup * improve usage text * Fix linux bundle * add graphen-cli * fix build profile * fix * release profile should not include debug infos * Review * remove profiling profile was redundent with release * rename to cargo-run tool * improve consistency * rename deps to requirements * fix * return success when showing usage
This commit is contained in:
parent
50ef6e15cb
commit
5d22292072
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
|
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
|
||||||
|
|
||||||
- name: Build Nix Package
|
- 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
|
- name: Push to Nix Cache
|
||||||
if: github.ref == 'refs/heads/master' || inputs.push_to_cache == true
|
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 }}
|
NIX_CACHE_AUTH_TOKEN: ${{ secrets.NIX_CACHE_AUTH_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
nix run nixpkgs#cachix -- authtoken $NIX_CACHE_AUTH_TOKEN
|
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
|
- 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
|
- name: Upload Linux Bundle
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|
@ -53,7 +53,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build Flatpak
|
- name: Build Flatpak
|
||||||
run: |
|
run: |
|
||||||
nix build .nix#graphite-flatpak-manifest
|
nix build .#graphite-flatpak-manifest
|
||||||
|
|
||||||
rm -rf .flatpak
|
rm -rf .flatpak
|
||||||
mkdir -p .flatpak
|
mkdir -p .flatpak
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ jobs:
|
||||||
- name: Build Mac Bundle
|
- name: Build Mac Bundle
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
run: npm run build-desktop
|
run: cargo run build desktop
|
||||||
|
|
||||||
- name: Stage Artifacts
|
- name: Stage Artifacts
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,4 @@ jobs:
|
||||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
- name: Build Nix Package Dev
|
- name: Build Nix Package Dev
|
||||||
run: nix build .nix#graphite-dev --print-build-logs
|
run: nix build .#graphite-dev --print-build-logs
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,7 @@ jobs:
|
||||||
- name: 🌐 Build Graphite web code
|
- name: 🌐 Build Graphite web code
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
run: |
|
run: mold -run cargo run build web
|
||||||
cd frontend
|
|
||||||
mold -run npm run build
|
|
||||||
|
|
||||||
- name: 📤 Publish to Cloudflare Pages
|
- name: 📤 Publish to Cloudflare Pages
|
||||||
id: cloudflare
|
id: cloudflare
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ jobs:
|
||||||
shell: bash # `cargo-about` refuses to run in powershell
|
shell: bash # `cargo-about` refuses to run in powershell
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
run: npm run build-desktop
|
run: cargo run build desktop
|
||||||
|
|
||||||
- name: Stage Artifacts
|
- name: Stage Artifacts
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ jobs:
|
||||||
if: steps.skip-check.outputs.skip-check != 'true'
|
if: steps.skip-check.outputs.skip-check != 'true'
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 'latest'
|
node-version: "latest"
|
||||||
|
|
||||||
- name: 🚧 Install build dependencies
|
- name: 🚧 Install build dependencies
|
||||||
if: steps.skip-check.outputs.skip-check != 'true'
|
if: steps.skip-check.outputs.skip-check != 'true'
|
||||||
|
|
@ -98,9 +98,7 @@ jobs:
|
||||||
if: steps.skip-check.outputs.skip-check != 'true'
|
if: steps.skip-check.outputs.skip-check != 'true'
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
run: |
|
run: mold -run cargo run build web
|
||||||
cd frontend
|
|
||||||
mold -run npm run build
|
|
||||||
|
|
||||||
- name: 📤 Publish to Cloudflare Pages
|
- name: 📤 Publish to Cloudflare Pages
|
||||||
if: steps.skip-check.outputs.skip-check != 'true'
|
if: steps.skip-check.outputs.skip-check != 'true'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# USAGE:
|
# USAGE:
|
||||||
# After reviewing the code, core team members may comment on a PR with the exact text:
|
# 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
|
# - `!build` to build without debug symbols and optimizations enabled
|
||||||
# The comment may not contain any other text, not even whitespace or newlines.
|
# The comment may not contain any other text, not even whitespace or newlines.
|
||||||
name: "!build PR Command"
|
name: "!build PR Command"
|
||||||
|
|
@ -21,7 +20,7 @@ jobs:
|
||||||
if: >
|
if: >
|
||||||
github.event.issue.pull_request &&
|
github.event.issue.pull_request &&
|
||||||
github.event.comment.author_association == 'MEMBER' &&
|
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')
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
@ -82,14 +81,12 @@ jobs:
|
||||||
- name: ⌨ Set build command based on comment
|
- name: ⌨ Set build command based on comment
|
||||||
id: build_command
|
id: build_command
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ github.event.comment.body }}" == "!build-dev" ]]; then
|
if [[ "${{ github.event.comment.body }}" == "!build-debug" ]]; then
|
||||||
echo "command=build-dev" >> $GITHUB_OUTPUT
|
echo "command=build web debug" >> $GITHUB_OUTPUT
|
||||||
elif [[ "${{ github.event.comment.body }}" == "!build-profiling" ]]; then
|
|
||||||
echo "command=build-profiling" >> $GITHUB_OUTPUT
|
|
||||||
elif [[ "${{ github.event.comment.body }}" == "!build" ]]; then
|
elif [[ "${{ github.event.comment.body }}" == "!build" ]]; then
|
||||||
echo "command=build" >> $GITHUB_OUTPUT
|
echo "command=build web" >> $GITHUB_OUTPUT
|
||||||
else
|
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', or '!build'" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: 💬 Comment Actions run link
|
- name: 💬 Comment Actions run link
|
||||||
|
|
@ -108,9 +105,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
if: ${{ success() || failure()}}
|
if: ${{ success() || failure()}}
|
||||||
run: |
|
run: mold -run cargo run ${{ steps.build_command.outputs.command }}
|
||||||
cd frontend
|
|
||||||
mold -run npm run ${{ steps.build_command.outputs.command }}
|
|
||||||
|
|
||||||
- name: ❗ Warn on build failure
|
- name: ❗ Warn on build failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
- name: 🟢 Install the latest Node.js
|
- name: 🟢 Install the latest Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 'latest'
|
node-version: "latest"
|
||||||
|
|
||||||
- name: 🚧 Install build dependencies
|
- name: 🚧 Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -49,9 +49,7 @@ jobs:
|
||||||
- name: 🌐 Build Graphite web code
|
- name: 🌐 Build Graphite web code
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
run: |
|
run: mold -run cargo run build web
|
||||||
cd frontend
|
|
||||||
mold -run npm run build
|
|
||||||
|
|
||||||
- name: 📤 Publish to Cloudflare Pages
|
- name: 📤 Publish to Cloudflare Pages
|
||||||
id: cloudflare
|
id: cloudflare
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
- name: Build graphene raster nodes shaders
|
- 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
|
- name: Upload graphene raster nodes shaders to artifacts repository
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
110
.nix/flake.nix
110
.nix/flake.nix
|
|
@ -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);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -731,6 +731,13 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cargo-run"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cargo-util-schemas"
|
name = "cargo-util-schemas"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
@ -6250,6 +6257,7 @@ dependencies = [
|
||||||
"scraper",
|
"scraper",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ members = [
|
||||||
"node-graph/node-macro",
|
"node-graph/node-macro",
|
||||||
"node-graph/preprocessor",
|
"node-graph/preprocessor",
|
||||||
"proc-macros",
|
"proc-macros",
|
||||||
|
"tools/cargo-run",
|
||||||
"tools/crate-hierarchy-viz",
|
"tools/crate-hierarchy-viz",
|
||||||
"tools/third-party-licenses",
|
"tools/third-party-licenses",
|
||||||
"tools/editor-message-tree",
|
"tools/editor-message-tree",
|
||||||
|
|
@ -35,12 +36,12 @@ default-members = [
|
||||||
"libraries/path-bool",
|
"libraries/path-bool",
|
||||||
"libraries/math-parser",
|
"libraries/math-parser",
|
||||||
"node-graph/graph-craft",
|
"node-graph/graph-craft",
|
||||||
"node-graph/graphene-cli",
|
|
||||||
"node-graph/interpreted-executor",
|
"node-graph/interpreted-executor",
|
||||||
"node-graph/node-macro",
|
"node-graph/node-macro",
|
||||||
"node-graph/preprocessor",
|
"node-graph/preprocessor",
|
||||||
# blocked by https://github.com/rust-lang/cargo/issues/16000
|
# blocked by https://github.com/rust-lang/cargo/issues/16000
|
||||||
# "proc-macros",
|
# "proc-macros",
|
||||||
|
"tools/cargo-run",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|
@ -236,10 +237,6 @@ node-macro = { opt-level = 2 }
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
[profile.profiling]
|
|
||||||
inherits = "release"
|
|
||||||
debug = true
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Force cargo to use only one version of the dpi crate (vendoring breaks without this)
|
# Force cargo to use only one version of the dpi crate (vendoring breaks without this)
|
||||||
dpi = { git = "https://github.com/rust-windowing/winit.git" }
|
dpi = { git = "https://github.com/rust-windowing/winit.git" }
|
||||||
|
|
|
||||||
|
|
@ -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::error::Error;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
@ -27,8 +29,7 @@ pub(crate) fn cef_path() -> PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result<PathBuf, Box<dyn Error>> {
|
pub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result<PathBuf, Box<dyn Error>> {
|
||||||
let profile = &profile_name();
|
let mut args = vec!["build", "--package", package, "--profile", profile_name()];
|
||||||
let mut args = vec!["build", "--package", package, "--profile", profile];
|
|
||||||
if let Some(bin) = bin {
|
if let Some(bin) = bin {
|
||||||
args.push("--bin");
|
args.push("--bin");
|
||||||
args.push(bin);
|
args.push(bin);
|
||||||
|
|
@ -45,7 +46,7 @@ pub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result<PathBuf, Box
|
||||||
pub(crate) fn run_command(program: &str, args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
|
pub(crate) fn run_command(program: &str, args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let status = Command::new(program).args(args).stdout(Stdio::inherit()).stderr(Stdio::inherit()).status()?;
|
let status = Command::new(program).args(args).stdout(Stdio::inherit()).stderr(Stdio::inherit()).status()?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
std::process::exit(1);
|
return Err(format!("Command '{}' with args {:?} failed with status: {}", program, args, status).into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
pub fn main() -> Result<(), Box<dyn Error>> {
|
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let app_bin = build_bin("graphite-desktop-platform-linux", None)?;
|
let app_bin = build_bin("graphite-desktop-platform-linux", None)?;
|
||||||
|
|
||||||
// TODO: Implement bundling for linux
|
// TODO: Implement bundling for linux
|
||||||
|
|
||||||
// TODO: Consider adding more useful cli
|
// TODO: Consider adding more useful cli
|
||||||
if std::env::args().any(|a| a == "open") {
|
let args: Vec<String> = std::env::args().collect();
|
||||||
run_command(&app_bin.to_string_lossy(), &[]).expect("failed to open app");
|
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 {
|
} 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!("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`");
|
eprintln!("You can still start the app with the `open` subcommand. `cargo run -p graphite-desktop-bundle -- open`");
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@ pub fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let app_dir = bundle(&profile_path, &app_bin, &helper_bin);
|
let app_dir = bundle(&profile_path, &app_bin, &helper_bin);
|
||||||
|
|
||||||
// TODO: Consider adding more useful cli
|
// TODO: Consider adding more useful cli
|
||||||
if std::env::args().any(|a| a == "open") {
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let executable_path = app_dir.join(EXEC_PATH).join(APP_NAME);
|
if let Some(pos) = args.iter().position(|a| a == "open") {
|
||||||
run_command(&executable_path.to_string_lossy(), &[]).expect("failed to open app");
|
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.to_string_lossy(), &extra_args).expect("failed to open app");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@ pub fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let executable = bundle(&profile_path(), &app_bin);
|
let executable = bundle(&profile_path(), &app_bin);
|
||||||
|
|
||||||
// TODO: Consider adding more useful cli
|
// TODO: Consider adding more useful cli
|
||||||
if std::env::args().any(|a| a == "open") {
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let executable_path = executable.to_string_lossy();
|
if let Some(pos) = args.iter().position(|a| a == "open") {
|
||||||
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.to_string_lossy(), &extra_args).expect("failed to open app")
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use cef::args::Args;
|
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::{
|
use cef::{
|
||||||
App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, ImplRequestContext, LogSeverity, RequestContextSettings, SchemeHandlerFactory, Settings, WindowInfo, api_hash,
|
App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, ImplRequestContext, LogSeverity, RequestContextSettings, SchemeHandlerFactory, Settings, WindowInfo, api_hash,
|
||||||
browser_host_create_browser_sync, execute_process,
|
browser_host_create_browser_sync, execute_process,
|
||||||
};
|
};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::CefContext;
|
use super::CefContext;
|
||||||
use super::singlethreaded::SingleThreadedCefContext;
|
use super::singlethreaded::SingleThreadedCefContext;
|
||||||
|
|
@ -138,8 +137,7 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Failed to initialize CEF context: {:?}", e);
|
panic!("Failed to initialize CEF context: {:?}", e);
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -153,10 +151,7 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
|
||||||
let result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
|
let result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
|
||||||
if result != 1 {
|
if result != 1 {
|
||||||
let cef_exit_code = cef::get_exit_code() as u32;
|
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::InitializationFailureCode(cef_exit_code));
|
||||||
return Err(InitError::AlreadyRunning);
|
|
||||||
}
|
|
||||||
return Err(InitError::InitializationFailed(cef_exit_code));
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -228,18 +223,16 @@ fn create_browser<H: CefEventHandler>(event_handler: H, instance_dir: PathBuf, d
|
||||||
pub(crate) enum SetupError {
|
pub(crate) enum SetupError {
|
||||||
#[error("This is the sub process should exit immediately")]
|
#[error("This is the sub process should exit immediately")]
|
||||||
Subprocess,
|
Subprocess,
|
||||||
#[error("Subprocess returned non zero exit code")]
|
#[error("Subprocess returned non zero exit code: {0}")]
|
||||||
SubprocessFailed(String),
|
SubprocessFailed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub(crate) enum InitError {
|
pub(crate) enum InitError {
|
||||||
#[error("Initialization failed")]
|
#[error("Initialization failed with code: {0}")]
|
||||||
InitializationFailed(u32),
|
InitializationFailureCode(u32),
|
||||||
#[error("Browser creation failed")]
|
#[error("Browser creation failed")]
|
||||||
BrowserCreationFailed,
|
BrowserCreationFailed,
|
||||||
#[error("Request context creation failed")]
|
#[error("Request context creation failed")]
|
||||||
RequestContextCreationFailed,
|
RequestContextCreationFailed,
|
||||||
#[error("Another instance is already running")]
|
|
||||||
AlreadyRunning,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use crate::consts::APP_LOCK_FILE_NAME;
|
||||||
use crate::event::CreateAppEventSchedulerEventLoopExt;
|
use crate::event::CreateAppEventSchedulerEventLoopExt;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::exit;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
|
|
@ -46,8 +45,7 @@ pub fn start() {
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME))
|
.open(dirs::app_data_dir().join(APP_LOCK_FILE_NAME))
|
||||||
else {
|
else {
|
||||||
tracing::error!("Failed to open lock file, Exiting.");
|
panic!("Failed to open lock file.")
|
||||||
exit(1);
|
|
||||||
};
|
};
|
||||||
let mut lock = fd_lock::RwLock::new(lock_file);
|
let mut lock = fd_lock::RwLock::new(lock_file);
|
||||||
let lock = match lock.try_write() {
|
let lock = match lock.try_write() {
|
||||||
|
|
@ -60,7 +58,7 @@ pub fn start() {
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
tracing::error!("Another instance is already running, Exiting.");
|
tracing::error!("Another instance is already running, Exiting.");
|
||||||
exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -86,21 +84,14 @@ pub fn start() {
|
||||||
tracing::info!("CEF initialized successfully");
|
tracing::info!("CEF initialized successfully");
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
Err(cef::InitError::AlreadyRunning) => {
|
Err(cef::InitError::InitializationFailureCode(code)) => {
|
||||||
tracing::error!("Another instance is already running, Exiting.");
|
panic!("CEF initialization failed with code: {code}");
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Err(cef::InitError::InitializationFailed(code)) => {
|
|
||||||
tracing::error!("Cef initialization failed with code: {code}");
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
Err(cef::InitError::BrowserCreationFailed) => {
|
Err(cef::InitError::BrowserCreationFailed) => {
|
||||||
tracing::error!("Failed to create CEF browser");
|
panic!("Failed to create CEF browser");
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
Err(cef::InitError::RequestContextCreationFailed) => {
|
Err(cef::InitError::RequestContextCreationFailed) => {
|
||||||
tracing::error!("Failed to create CEF request context");
|
panic!("Failed to create CEF request context");
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -139,7 +130,7 @@ pub fn start() {
|
||||||
// Calling `exit` bypasses rust teardown and lets Windows perform process cleanup.
|
// 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.
|
// TODO: Identify and fix the underlying CEF shutdown issue so this workaround can be removed.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_helper() {
|
pub fn start_helper() {
|
||||||
|
|
|
||||||
|
|
@ -15,20 +15,6 @@
|
||||||
"type": "github"
|
"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": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770197578,
|
"lastModified": 1770197578,
|
||||||
|
|
@ -48,7 +34,6 @@
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"crane": "crane",
|
"crane": "crane",
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -8,12 +8,10 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"---------- DEV SERVER ----------": "",
|
"---------- DEV SERVER ----------": "",
|
||||||
"start": "npm run setup && npm run wasm:build-dev && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-dev\"",
|
"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\"",
|
"production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"",
|
||||||
"---------- BUILDS ----------": "",
|
"---------- BUILDS ----------": "",
|
||||||
"build": "npm run setup && npm run wasm:build-production && vite build",
|
"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-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": "npm run setup && npm run native:build-production",
|
||||||
"build-native-dev": "npm run setup && npm run native:build-dev",
|
"build-native-dev": "npm run setup && npm run native:build-dev",
|
||||||
"---------- UTILITIES ----------": "",
|
"---------- 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-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",
|
"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-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: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-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\""
|
"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.",
|
"//": "NOTE: `source-sans-pro` is never to be upgraded to 3.x because that renders a pixel above its intended position.",
|
||||||
|
|
|
||||||
|
|
@ -57,14 +57,6 @@ debug-js-glue = false
|
||||||
demangle-name-section = false
|
demangle-name-section = false
|
||||||
dwarf-debug-info = 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]
|
[lints.rust]
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||||
'cfg(wasm_bindgen_unstable_test_coverage)',
|
'cfg(wasm_bindgen_unstable_test_coverage)',
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[profile.profiling]
|
|
||||||
inherits = "release"
|
|
||||||
debug = true
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::path::PathBuf;
|
||||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::builder().filter_level(log::LevelFilter::Debug).init();
|
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");
|
println!("cargo:rerun-if-env-changed=RASTER_NODES_SHADER_PATH");
|
||||||
if !std::env::var("RASTER_NODES_SHADER_PATH").unwrap_or_default().is_empty() {
|
if !std::env::var("RASTER_NODES_SHADER_PATH").unwrap_or_default().is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Graphite",
|
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {}
|
|
||||||
}
|
|
||||||
20
package.json
20
package.json
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "cargo-run"
|
||||||
|
edition.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
default-run = "cargo-run"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
pub mod requirements;
|
||||||
|
|
||||||
|
pub enum Action {
|
||||||
|
Run,
|
||||||
|
Build,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Target {
|
||||||
|
Web,
|
||||||
|
Desktop,
|
||||||
|
Cli,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Profile {
|
||||||
|
Default,
|
||||||
|
Release,
|
||||||
|
Debug,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Task {
|
||||||
|
pub action: Action,
|
||||||
|
pub target: Target,
|
||||||
|
pub profile: Profile,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
pub fn parse(args: &[&str]) -> Option<Self> {
|
||||||
|
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 (action, args) = match args.first() {
|
||||||
|
Some(&"build") => (Action::Build, &args[1..]),
|
||||||
|
Some(&"run") => (Action::Run, &args[1..]),
|
||||||
|
Some(&"help") => return None,
|
||||||
|
_ => (Action::Run, args),
|
||||||
|
};
|
||||||
|
|
||||||
|
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 args.first() {
|
||||||
|
Some(&"release") => Profile::Release,
|
||||||
|
Some(&"debug") => Profile::Debug,
|
||||||
|
None => Profile::Default,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Task {
|
||||||
|
target,
|
||||||
|
action,
|
||||||
|
profile,
|
||||||
|
args: passthru_args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(command: &str) -> Result<(), Error> {
|
||||||
|
run_from(command, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_from(command: &str, dir: Option<&PathBuf>) -> Result<(), Error> {
|
||||||
|
let command = command.split_whitespace().collect::<Vec<_>>();
|
||||||
|
let mut cmd = process::Command::new(command[0]);
|
||||||
|
if command.len() > 1 {
|
||||||
|
cmd.args(&command[1..]);
|
||||||
|
}
|
||||||
|
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(" "))))?
|
||||||
|
.wait()
|
||||||
|
.map_err(|e| Error::Io(e, format!("Failed to wait for command '{}'", command.join(" "))))?;
|
||||||
|
if !exit_code.success() {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use cargo_run::*;
|
||||||
|
|
||||||
|
fn usage() {
|
||||||
|
println!();
|
||||||
|
println!("USAGE:");
|
||||||
|
println!(" cargo run [<command>] [<target>] [<profile>] [-- [args]...]");
|
||||||
|
println!();
|
||||||
|
println!("COMMON USAGE:");
|
||||||
|
println!(" cargo run Run the web app");
|
||||||
|
println!(" cargo run desktop Run the desktop app");
|
||||||
|
println!();
|
||||||
|
println!("OPTIONS:");
|
||||||
|
println!("<command>:");
|
||||||
|
println!(" [run] Run the selected target (default)");
|
||||||
|
println!(" build Build the selected target");
|
||||||
|
println!(" help Show this message");
|
||||||
|
println!("<target>:");
|
||||||
|
println!(" [web] Web app (default)");
|
||||||
|
println!(" desktop Desktop app");
|
||||||
|
println!(" cli Graphene CLI");
|
||||||
|
println!("<profile>:");
|
||||||
|
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 release");
|
||||||
|
println!(" cargo run cli -- --help");
|
||||||
|
println!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
let args: Vec<String> = 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 => {
|
||||||
|
usage();
|
||||||
|
return ExitCode::SUCCESS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = run_task(&task) {
|
||||||
|
eprintln!("Error: {e}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_task(task: &Task) -> Result<(), Error> {
|
||||||
|
requirements::check(task)?;
|
||||||
|
|
||||||
|
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::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, Target::Desktop, mut profile) => {
|
||||||
|
if matches!(profile, Profile::Default) {
|
||||||
|
profile = match action {
|
||||||
|
Action::Run => &Profile::Debug,
|
||||||
|
Action::Build => &Profile::Release,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => "dev",
|
||||||
|
Profile::Release => "release",
|
||||||
|
Profile::Default => unreachable!(),
|
||||||
|
};
|
||||||
|
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}"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
(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::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")?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
use std::io::IsTerminal;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct Requirement {
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requirements(task: &Task) -> Vec<Requirement> {
|
||||||
|
[
|
||||||
|
Requirement {
|
||||||
|
command: "rustc",
|
||||||
|
args: &["--version"],
|
||||||
|
name: "Rust",
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Requirement {
|
||||||
|
command: "cargo-about",
|
||||||
|
args: &["--version"],
|
||||||
|
name: "cargo-about",
|
||||||
|
install: Some("cargo install cargo-about"),
|
||||||
|
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Requirement {
|
||||||
|
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,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Requirement {
|
||||||
|
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"),
|
||||||
|
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||||
|
},
|
||||||
|
Requirement {
|
||||||
|
command: "wasm-pack",
|
||||||
|
args: &["--version"],
|
||||||
|
name: "wasm-pack",
|
||||||
|
install: Some("cargo install wasm-pack"),
|
||||||
|
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Requirement {
|
||||||
|
command: "node",
|
||||||
|
args: &["--version"],
|
||||||
|
name: "Node.js",
|
||||||
|
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Requirement {
|
||||||
|
command: "cmake",
|
||||||
|
args: &["--version"],
|
||||||
|
name: "CMake",
|
||||||
|
skip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = "linux")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Requirement {
|
||||||
|
command: "ninja",
|
||||||
|
args: &["--version"],
|
||||||
|
name: "Ninja",
|
||||||
|
skip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = "linux")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.filter(|d| if let Some(skip) = d.skip { !skip(task) } else { true })
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(task: &Task) -> Result<(), Error> {
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("Checking Requirements:");
|
||||||
|
|
||||||
|
let mut installable: Vec<Requirement> = Vec::new();
|
||||||
|
let mut failures: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
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);
|
||||||
|
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 Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let total = installable.len() + failures.len();
|
||||||
|
eprintln!("{total} requirement{} not met:", if total > 1 { "s" } else { "" });
|
||||||
|
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/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if installable.is_empty() {
|
||||||
|
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 {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ desktop = ["dep:cef-dll-sys", "dep:scraper"]
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
lzma-rust2 = { workspace = true }
|
lzma-rust2 = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
# Optional workspace dependencies
|
# Optional workspace dependencies
|
||||||
cef-dll-sys = { workspace = true, optional = true }
|
cef-dll-sys = { workspace = true, optional = true }
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{LicenceSource, LicenseEntry, Package};
|
use crate::{Error, LicenceSource, LicenseEntry, Package};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{self, Command};
|
use std::process::Command;
|
||||||
|
|
||||||
pub struct CargoLicenseSource {}
|
pub struct CargoLicenseSource {}
|
||||||
|
|
||||||
|
|
@ -14,8 +14,8 @@ impl CargoLicenseSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LicenceSource for CargoLicenseSource {
|
impl LicenceSource for CargoLicenseSource {
|
||||||
fn licenses(&self) -> Vec<LicenseEntry> {
|
fn licenses(&self) -> Result<Vec<LicenseEntry>, Error> {
|
||||||
parse(run())
|
Ok(parse(run()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,23 +84,18 @@ fn parse(parsed: Output) -> Vec<LicenseEntry> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> Output {
|
fn run() -> Result<Output, Error> {
|
||||||
let output = Command::new("cargo")
|
let output = Command::new("cargo")
|
||||||
.args(["about", "generate", "--format", "json", "--frozen"])
|
.args(["about", "generate", "--format", "json", "--frozen"])
|
||||||
.current_dir(env!("CARGO_WORKSPACE_DIR"))
|
.current_dir(env!("CARGO_WORKSPACE_DIR"))
|
||||||
.output()
|
.output()
|
||||||
.unwrap_or_else(|e| {
|
.map_err(|e| Error::Io(e, "Failed to run cargo about generate".into()))?;
|
||||||
eprintln!("Failed to run cargo about generate: {e}");
|
|
||||||
process::exit(1)
|
|
||||||
});
|
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
eprintln!("cargo about generate failed:\n{}", String::from_utf8_lossy(&output.stderr));
|
return Err(Error::Command(format!("cargo about generate failed:\n{}", String::from_utf8_lossy(&output.stderr))));
|
||||||
process::exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_json::from_str(&String::from_utf8(output.stdout).expect("cargo about generate should return valid UTF-8")).unwrap_or_else(|e| {
|
let stdout = String::from_utf8(output.stdout).map_err(|e| Error::Utf8(e, "cargo about generate returned invalid UTF-8".into()))?;
|
||||||
eprintln!("Failed to parse cargo about generate JSON: {e}");
|
|
||||||
process::exit(1)
|
serde_json::from_str(&stdout).map_err(|e| Error::Json(e, "Failed to parse cargo about generate JSON".into()))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use lzma_rust2::XzReader;
|
use lzma_rust2::XzReader;
|
||||||
use scraper::{Html, Selector};
|
use scraper::{Html, Selector};
|
||||||
|
use std::fs;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs, process};
|
|
||||||
|
|
||||||
use crate::{LicenceSource, LicenseEntry, Package};
|
use crate::{Error, LicenceSource, LicenseEntry, Package};
|
||||||
|
|
||||||
pub struct CefLicenseSource;
|
pub struct CefLicenseSource;
|
||||||
|
|
||||||
|
|
@ -16,15 +16,15 @@ impl CefLicenseSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LicenceSource for CefLicenseSource {
|
impl LicenceSource for CefLicenseSource {
|
||||||
fn licenses(&self) -> Vec<LicenseEntry> {
|
fn licenses(&self) -> Result<Vec<LicenseEntry>, Error> {
|
||||||
let html = read();
|
let html = read()?;
|
||||||
parse(&html)
|
Ok(parse(&html))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for CefLicenseSource {
|
impl Hash for CefLicenseSource {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
read().hash(state)
|
read().unwrap().hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,42 +64,29 @@ fn parse(html: &str) -> Vec<LicenseEntry> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read() -> String {
|
fn read() -> Result<String, Error> {
|
||||||
let cef_path = PathBuf::from(env!("CEF_PATH"));
|
let cef_path = PathBuf::from(env!("CEF_PATH"));
|
||||||
let cef_credits = std::fs::read_dir(&cef_path)
|
let cef_credits = std::fs::read_dir(&cef_path)
|
||||||
.unwrap_or_else(|e| {
|
.map_err(|e| Error::Io(e, format!("Failed to read CEF_PATH directory {}", cef_path.display())))?
|
||||||
eprintln!("Failed to read CEF_PATH directory {}: {e}", cef_path.display());
|
|
||||||
process::exit(1);
|
|
||||||
})
|
|
||||||
.filter_map(|entry| entry.ok())
|
.filter_map(|entry| entry.ok())
|
||||||
.find(|entry| {
|
.find(|entry| {
|
||||||
let name = entry.file_name();
|
let name = entry.file_name();
|
||||||
name.eq_ignore_ascii_case("credits.html") || name.eq_ignore_ascii_case("credits.html.xz")
|
name.eq_ignore_ascii_case("credits.html") || name.eq_ignore_ascii_case("credits.html.xz")
|
||||||
})
|
})
|
||||||
.map(|entry| entry.path())
|
.map(|entry| entry.path())
|
||||||
.unwrap_or_else(|| {
|
.ok_or_else(|| Error::CefCreditsNotFound(cef_path.clone()))?;
|
||||||
eprintln!("Could not find CREDITS.html or CREDITS.html.xz in {}", cef_path.display());
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
let decompress_xz = cef_credits.extension().map(|ext| ext.eq_ignore_ascii_case("xz")).unwrap_or(false);
|
let decompress_xz = cef_credits.extension().map(|ext| ext.eq_ignore_ascii_case("xz")).unwrap_or(false);
|
||||||
|
|
||||||
if decompress_xz {
|
if decompress_xz {
|
||||||
let file = fs::File::open(&cef_credits).unwrap_or_else(|e| {
|
let file = fs::File::open(&cef_credits).map_err(|e| Error::Io(e, format!("Failed to open CEF credits file {}", cef_credits.display())))?;
|
||||||
eprintln!("Failed to open CEF credits file {}: {e}", cef_credits.display());
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
let mut reader = XzReader::new(file, false);
|
let mut reader = XzReader::new(file, false);
|
||||||
let mut html = String::new();
|
let mut html = String::new();
|
||||||
reader.read_to_string(&mut html).unwrap_or_else(|e| {
|
reader
|
||||||
eprintln!("Failed to decompress CEF credits file {}: {e}", cef_credits.display());
|
.read_to_string(&mut html)
|
||||||
process::exit(1);
|
.map_err(|e| Error::Io(e, format!("Failed to decompress CEF credits file {}", cef_credits.display())))?;
|
||||||
});
|
Ok(html)
|
||||||
html
|
|
||||||
} else {
|
} else {
|
||||||
fs::read_to_string(&cef_credits).unwrap_or_else(|e| {
|
fs::read_to_string(&cef_credits).map_err(|e| Error::Io(e, format!("Failed to read CEF credits file {}", cef_credits.display())))
|
||||||
eprintln!("Failed to read CEF credits file {}: {e}", cef_credits.display());
|
|
||||||
process::exit(1);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs, process};
|
use std::process::ExitCode;
|
||||||
|
|
||||||
mod cargo;
|
mod cargo;
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
|
|
@ -13,8 +14,27 @@ use crate::cargo::CargoLicenseSource;
|
||||||
use crate::cef::CefLicenseSource;
|
use crate::cef::CefLicenseSource;
|
||||||
use crate::npm::NpmLicenseSource;
|
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 {
|
pub trait LicenceSource: std::hash::Hash {
|
||||||
fn licenses(&self) -> Vec<LicenseEntry>;
|
fn licenses(&self) -> Result<Vec<LicenseEntry>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LicenseEntry {
|
pub struct LicenseEntry {
|
||||||
|
|
@ -38,7 +58,15 @@ struct Run<'a> {
|
||||||
cef: &'a CefLicenseSource,
|
cef: &'a CefLicenseSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> ExitCode {
|
||||||
|
if let Err(e) = run() {
|
||||||
|
eprintln!("Error: {e}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> Result<(), Error> {
|
||||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
let workspace_dir = PathBuf::from(env!("CARGO_WORKSPACE_DIR"));
|
let workspace_dir = PathBuf::from(env!("CARGO_WORKSPACE_DIR"));
|
||||||
|
|
||||||
|
|
@ -71,32 +99,26 @@ fn main() {
|
||||||
|
|
||||||
if current_hash == fs::read_to_string(¤t_hash_path).unwrap_or_default() {
|
if current_hash == fs::read_to_string(¤t_hash_path).unwrap_or_default() {
|
||||||
eprintln!("No changes in licenses detected, skipping generation.");
|
eprintln!("No changes in licenses detected, skipping generation.");
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
eprintln!("Changes in licenses detected, generating new license file.");
|
eprintln!("Changes in licenses detected, generating new license file.");
|
||||||
|
|
||||||
let licenses = merge_filter_dedup_and_sort(vec![
|
let licenses = merge_filter_dedup_and_sort(vec![
|
||||||
cargo_source.licenses(),
|
cargo_source.licenses()?,
|
||||||
npm_source.licenses(),
|
npm_source.licenses()?,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
cef_source.licenses(),
|
cef_source.licenses()?,
|
||||||
]);
|
]);
|
||||||
let formatted = format_credits(&licenses);
|
let formatted = format_credits(&licenses);
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
let output = compress(&formatted);
|
let output = compress(&formatted)?;
|
||||||
#[cfg(not(feature = "desktop"))]
|
#[cfg(not(feature = "desktop"))]
|
||||||
let output = formatted.as_bytes().to_vec();
|
let output = formatted.as_bytes().to_vec();
|
||||||
if let Some(parent) = output_path.parent() {
|
if let Some(parent) = output_path.parent() {
|
||||||
fs::create_dir_all(parent).unwrap_or_else(|e| {
|
fs::create_dir_all(parent).map_err(|e| Error::Io(e, format!("Failed to create directory {}", parent.display())))?;
|
||||||
eprintln!("Failed to create directory {}: {e}", parent.display());
|
|
||||||
std::process::exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
fs::write(&output_path, &output).unwrap_or_else(|e| {
|
fs::write(&output_path, &output).map_err(|e| Error::Io(e, format!("Failed to write {}", output_path.display())))?;
|
||||||
eprintln!("Failed to write {}: {e}", &output_path.display());
|
|
||||||
std::process::exit(1);
|
|
||||||
});
|
|
||||||
run.output = &output;
|
run.output = &output;
|
||||||
|
|
||||||
let hash = {
|
let hash = {
|
||||||
|
|
@ -105,10 +127,9 @@ fn main() {
|
||||||
format!("{:016x}", hasher.finish())
|
format!("{:016x}", hasher.finish())
|
||||||
};
|
};
|
||||||
|
|
||||||
fs::write(¤t_hash_path, hash).unwrap_or_else(|e| {
|
fs::write(¤t_hash_path, hash).map_err(|e| Error::Io(e, format!("Failed to write hash file {}", current_hash_path.display())))?;
|
||||||
eprintln!("Failed to write hash file {}: {e}", current_hash_path.display());
|
|
||||||
process::exit(1);
|
Ok(())
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_credits(licenses: &Vec<LicenseEntry>) -> String {
|
fn format_credits(licenses: &Vec<LicenseEntry>) -> String {
|
||||||
|
|
@ -210,20 +231,11 @@ fn dedup_by_licence_text(vec: Vec<LicenseEntry>) -> Vec<LicenseEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
fn compress(content: &str) -> Vec<u8> {
|
fn compress(content: &str) -> Result<Vec<u8>, Error> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let mut writer = lzma_rust2::XzWriter::new(&mut buf, lzma_rust2::XzOptions::default()).unwrap_or_else(|e| {
|
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()))?;
|
||||||
eprintln!("Failed to create XZ writer: {e}");
|
writer.write_all(content.as_bytes()).map_err(|e| Error::Io(e, "Failed to write compressed credits".into()))?;
|
||||||
std::process::exit(1);
|
writer.finish().map_err(|e| Error::Io(e, "Failed to finish XZ compression".into()))?;
|
||||||
});
|
Ok(buf)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::{LicenceSource, LicenseEntry, Package};
|
use crate::{Error, LicenceSource, LicenseEntry, Package};
|
||||||
|
|
||||||
pub struct NpmLicenseSource {
|
pub struct NpmLicenseSource {
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
|
|
@ -16,8 +15,8 @@ impl NpmLicenseSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LicenceSource for NpmLicenseSource {
|
impl LicenceSource for NpmLicenseSource {
|
||||||
fn licenses(&self) -> Vec<LicenseEntry> {
|
fn licenses(&self) -> Result<Vec<LicenseEntry>, Error> {
|
||||||
parse(run(&self.dir))
|
Ok(parse(run(&self.dir)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +65,7 @@ fn parse(parsed: Output) -> Vec<LicenseEntry> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(dir: &std::path::Path) -> Output {
|
fn run(dir: &std::path::Path) -> Result<Output, Error> {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let mut cmd = Command::new("npx");
|
let mut cmd = Command::new("npx");
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
|
@ -74,20 +73,13 @@ fn run(dir: &std::path::Path) -> Output {
|
||||||
cmd.args(["license-checker-rseidelsohn", "--production", "--json"]);
|
cmd.args(["license-checker-rseidelsohn", "--production", "--json"]);
|
||||||
cmd.current_dir(dir);
|
cmd.current_dir(dir);
|
||||||
|
|
||||||
let output = cmd.output().unwrap_or_else(|e| {
|
let output = cmd.output().map_err(|e| Error::Io(e, "Failed to run npx license-checker-rseidelsohn".into()))?;
|
||||||
eprintln!("Failed to run npx license-checker-rseidelsohn: {e}");
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
eprintln!("npx license-checker-rseidelsohn failed:\n{}", String::from_utf8_lossy(&output.stderr));
|
return Err(Error::Command(format!("npx license-checker-rseidelsohn failed:\n{}", String::from_utf8_lossy(&output.stderr))));
|
||||||
process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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| {
|
serde_json::from_str(&json_str).map_err(|e| Error::Json(e, "Failed to parse license-checker JSON".into()))
|
||||||
eprintln!("Failed to parse license-checker JSON: {e}");
|
|
||||||
process::exit(1)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,10 @@ To begin working with the Graphite codebase, you will need to set up the project
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
Graphite is built with Rust and web technologies, which means you will need to install:
|
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)
|
- [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)
|
- [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
|
## Repository
|
||||||
|
|
||||||
Clone the project to a convenient location:
|
Clone the project to a convenient location:
|
||||||
|
|
@ -40,7 +29,7 @@ git clone https://github.com/GraphiteEditor/Graphite.git
|
||||||
From either the `/` (root) or `/frontend` directories, you can run the project by executing:
|
From either the `/` (root) or `/frontend` directories, you can run the project by executing:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm start
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
This spins up the dev server at <http://localhost:8080> 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 <kbd>Ctrl</kbd><kbd>C</kbd>. TypeScript and HTML changes require a manual page reload to fix broken state.
|
This spins up the dev server at <http://localhost:8080> 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 <kbd>Ctrl</kbd><kbd>C</kbd>. TypeScript and HTML changes require a manual page reload to fix broken state.
|
||||||
|
|
@ -53,28 +42,9 @@ 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:
|
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
|
```sh
|
||||||
npm run profiling
|
cargo run release
|
||||||
```
|
```
|
||||||
|
|
||||||
To run the dev server without debug symbols, using the same release optimizations as production builds:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run production
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Production build instructions: click here</summary>
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
This produces the `/frontend/dist` directory containing the static site files that must be served by your own web server.
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Development tooling
|
## Development tooling
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue