diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml new file mode 100644 index 0000000..3bf72be --- /dev/null +++ b/.github/workflows/dev-build.yml @@ -0,0 +1,35 @@ +name: dev-build + +on: + push: + +env: + CARGO_TERM_COLOR: always + MACOSX_DEPLOYMENT_TARGET: "13.0" + CARGO_HUSKY_DONT_INSTALL_HOOKS: "1" + RUST_BACKTRACE: short + +jobs: + build: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.88.0 + with: + targets: aarch64-apple-darwin + - uses: Swatinem/rust-cache@v2 + with: + shared-key: dev-build + - name: Install build tools + run: | + brew install create-dmg librsvg + cargo install cargo-bundle@0.10.0 --locked + - name: Build xtask + run: cargo build --release --manifest-path xtask/Cargo.toml + - name: Build DMG (arm64) + run: ./xtask/target/release/xtask dmg + - uses: actions/upload-artifact@v4 + with: + name: Vector-arm64-${{ github.ref_name }} + path: target/dmg/Vector-*.dmg + retention-days: 14 diff --git a/Cargo.lock b/Cargo.lock index 70d674d..739f84a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1025,6 +1025,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -1228,11 +1234,24 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + [[package]] name = "glob" version = "0.3.3" @@ -1281,13 +1300,22 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -1568,6 +1596,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1597,6 +1631,8 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", + "serde", + "serde_core", ] [[package]] @@ -1771,9 +1807,9 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "285efcf12ef41bec907b3000d5ffaeb54191d4d9d83c0d6157e6cbc2db255e64" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ "bitflags 2.11.1", "libc", @@ -1788,6 +1824,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.186" @@ -2670,18 +2712,18 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -2831,6 +2873,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -2943,6 +2995,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.6" @@ -3706,7 +3764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -3879,7 +3937,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3918,7 +3976,7 @@ dependencies = [ "indexmap", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -3927,7 +3985,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -4082,6 +4140,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -4214,6 +4278,7 @@ dependencies = [ "anyhow", "crossfont", "parking_lot", + "tracing", "unicode-width", ] @@ -4385,7 +4450,16 @@ version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -4443,6 +4517,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" version = "0.3.98" @@ -4910,9 +5018,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -4958,12 +5066,100 @@ dependencies = [ "url", ] +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.3" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f42ba6d --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +.PHONY: build lint test run dmg help _xtask + +export CARGO_TERM_COLOR := always +export MACOSX_DEPLOYMENT_TARGET := 13.0 + +build: + cargo build --release -p vector-app + +lint: + cargo fmt --all -- --check + cargo clippy --all-targets --all-features -- -D warnings + +test: + cargo test --workspace --tests + +run: + cargo run --release -p vector-app + +dmg: _xtask + ./xtask/target/release/xtask dmg + +_xtask: + cargo build --release --manifest-path xtask/Cargo.toml + +help: + @echo "build build vector-app (host arch, release)" + @echo "lint cargo fmt --check + clippy" + @echo "test cargo test --workspace --tests" + @echo "run run vector-app (release)" + @echo "dmg build a local .dmg via xtask" diff --git a/crates/vector-fonts/Cargo.toml b/crates/vector-fonts/Cargo.toml index 2c8a83f..b2391aa 100644 --- a/crates/vector-fonts/Cargo.toml +++ b/crates/vector-fonts/Cargo.toml @@ -10,6 +10,7 @@ description = "Font discovery + rasterization — Phase 3 (crossfont / cosmic-te anyhow.workspace = true crossfont.workspace = true parking_lot.workspace = true +tracing.workspace = true unicode-width.workspace = true [lints] diff --git a/crates/vector-fonts/src/loader.rs b/crates/vector-fonts/src/loader.rs index 30264a5..3b1898a 100644 --- a/crates/vector-fonts/src/loader.rs +++ b/crates/vector-fonts/src/loader.rs @@ -35,19 +35,16 @@ impl FontStack { /// `dpr` is reserved for future per-DPR rasterizers — crossfont 0.9's CoreText backend takes /// no DPR at construction; we pre-multiply into `size_pt` so the CoreText pixel grid lines up. pub fn load_bundled(dpr: f32, size_pt: f32) -> Result { - let _ttf_path = locate_bundled_font()?; + if locate_bundled_font().is_none() { + tracing::warn!( + "JetBrains Mono not found in bundle; will fall back to system monospace" + ); + } let mut rasterizer = Rasterizer::new().context("Rasterizer::new")?; - let desc = FontDesc::new( - "JetBrains Mono", - Style::Description { - slant: Slant::Normal, - weight: Weight::Normal, - }, - ); let size = Size::new(size_pt * dpr.max(1.0)); - let font_key = rasterizer - .load_font(&desc, size) - .context("load_font JetBrains Mono")?; + // Try JetBrains Mono first; fall back to Menlo so the terminal is never + // blank just because the font file is missing from the .app bundle. + let font_key = load_font_with_fallback(&mut rasterizer, size)?; let metrics: Metrics = rasterizer.metrics(font_key, size).context("font metrics")?; let cell_metrics = CellMetrics { width_px: f_to_u32(metrics.average_advance), @@ -101,6 +98,29 @@ impl FontStack { } } +fn load_font_with_fallback(rasterizer: &mut Rasterizer, size: Size) -> Result { + let preferred = FontDesc::new( + "JetBrains Mono", + Style::Description { + slant: Slant::Normal, + weight: Weight::Normal, + }, + ); + rasterizer.load_font(&preferred, size).or_else(|_| { + tracing::warn!("JetBrains Mono unavailable; falling back to Menlo"); + let fallback = FontDesc::new( + "Menlo", + Style::Description { + slant: Slant::Normal, + weight: Weight::Normal, + }, + ); + rasterizer + .load_font(&fallback, size) + .context("load_font: neither JetBrains Mono nor Menlo available") + }) +} + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn f_to_u32(v: f64) -> u32 { v.round().clamp(1.0, f64::from(u32::MAX)) as u32 @@ -111,33 +131,40 @@ fn f_to_i32(v: f64) -> i32 { v.round().clamp(f64::from(i32::MIN), f64::from(i32::MAX)) as i32 } -/// Bundle path first (`Vector.app/Contents/Resources/Fonts/`), dev workspace path second. -fn locate_bundled_font() -> Result { +/// Best-effort lookup — returns `None` instead of erroring so a missing font +/// never breaks rendering. Checks two cargo-bundle layouts plus the dev path. +fn locate_bundled_font() -> Option { if let Ok(exe) = std::env::current_exe() { if let Some(parent) = exe.parent() { if let Some(grandparent) = parent.parent() { - let bundled = grandparent + // cargo-bundle 0.10 preserves the source `resources/` prefix. + let with_prefix = grandparent + .join("Resources") + .join("resources") + .join("Fonts") + .join("JetBrainsMono-Regular.ttf"); + if with_prefix.exists() { + return Some(with_prefix); + } + let flat = grandparent .join("Resources") .join("Fonts") .join("JetBrainsMono-Regular.ttf"); - if bundled.exists() { - return Ok(bundled); + if flat.exists() { + return Some(flat); } } } } let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let dev_path = manifest - .parent() - .ok_or_else(|| anyhow!("CARGO_MANIFEST_DIR has no parent"))? + .parent()? .join("vector-app") .join("resources") .join("Fonts") .join("JetBrainsMono-Regular.ttf"); if dev_path.exists() { - return Ok(dev_path); + return Some(dev_path); } - Err(anyhow!( - "JetBrains Mono not found at bundle or dev path: {dev_path:?}" - )) + None } diff --git a/xtask/src/dmg.rs b/xtask/src/dmg.rs index 787012d..e132259 100644 --- a/xtask/src/dmg.rs +++ b/xtask/src/dmg.rs @@ -59,7 +59,12 @@ pub fn dmg_universal( fn finalize(sh: &Shell, version: &str, tip: bool, _staged_bin: &str) -> Result<()> { super::icon::generate_icns(sh).context("generate icon.icns")?; - cmd!(sh, "cargo bundle --release -p vector-app").run()?; + // cargo-bundle 0.10 only copies [package.metadata.bundle].resources when + // invoked from the crate's own directory. Running from the workspace root + // silently drops them — causing fonts to be missing and the terminal blank. + sh.change_dir("crates/vector-app"); + cmd!(sh, "cargo bundle --release").run()?; + sh.change_dir("../.."); let app_path = "target/release/bundle/osx/Vector.app"; let bundled_bin = format!("{app_path}/Contents/MacOS/vector-app");