From 2b268fe1684d76ed5c968ac76cc9747c79b62d3c Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sun, 5 Jul 2026 21:28:47 +0200 Subject: [PATCH] feat(firmware): wire libgit2 component + bind git2 in system mode Adds the extra_components metadata, git2 as an optional dep behind the `git` feature (default-features off -> no openssl-sys/libssh2-sys), and a git_smoke bin gated on it. libgit2-sys/libz-sys run in system mode against fake pkg-config files (empty Libs) so they emit no link flags -- symbols come from the esp-idf component. Proven on device: git2 version + a blob SHA1 through the mbedTLS backend. --- firmware/Cargo.toml | 24 +++++++++++++++++++ firmware/pkgconfig/libgit2.pc | 14 +++++++++++ firmware/pkgconfig/zlib.pc | 16 +++++++++++++ firmware/src/bin/git_smoke.rs | 45 +++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 firmware/pkgconfig/libgit2.pc create mode 100644 firmware/pkgconfig/zlib.pc create mode 100644 firmware/src/bin/git_smoke.rs diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index f74816d..660c119 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -29,6 +29,15 @@ name = "sd_fat" path = "src/bin/sd_fat.rs" harness = false +# Spike 7 Path 2 — libgit2 link/run smoke via the git2 safe API. Gated behind +# the `git` feature so the editor build never pulls libgit2-sys/pkg-config. +# Build: cargo build --release --bin git_smoke --features git (env in justfile). +[[bin]] +name = "git_smoke" +path = "src/bin/git_smoke.rs" +harness = false +required-features = ["git"] + [profile.release] opt-level = "s" @@ -40,9 +49,17 @@ esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys.git" } esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal.git" } esp-idf-svc = { git = "https://github.com/esp-rs/esp-idf-svc.git" } +[features] +# Pulls the git2 safe API. libgit2 itself is built by the esp-idf component +# (firmware/components/libgit2/) with mbedTLS; git2/libgit2-sys are used in +# system mode (LIBGIT2_NO_VENDOR=1) purely for the Rust bindings, so we disable +# their default features to avoid dragging in openssl-sys/libssh2-sys. +git = ["dep:git2"] + [dependencies] anyhow = "1" log = "0.4" +git2 = { version = "0.20", default-features = false, optional = true } esp-idf-svc = { version = "0.52.1", features = ["critical-section", "embassy-time-driver", "embassy-sync"] } # Remove `generic-queue-8` if you plan to use `embassy-time` WITH `embassy-executor` embassy-time = { version = "0.5", features = ["generic-queue-8"] } @@ -53,3 +70,10 @@ embedded-svc = "0.29" [build-dependencies] embuild = "0.33" + +# TEMPORARY — Spike 7 Path 2 Gate A probe. Builds libgit2 as an esp-idf +# component (see firmware/components/libgit2/). Compiled against on-disk source +# via LIBGIT2_SRC. Revert if the probe fails; promote (vendor the source) if it +# passes. +[[package.metadata.esp-idf-sys.extra_components]] +component_dirs = ["components/libgit2"] diff --git a/firmware/pkgconfig/libgit2.pc b/firmware/pkgconfig/libgit2.pc new file mode 100644 index 0000000..46a453d --- /dev/null +++ b/firmware/pkgconfig/libgit2.pc @@ -0,0 +1,14 @@ +# Fake pkg-config file for the libgit2 esp-idf component (Spike 7, Path 2). +# +# libgit2-sys in system mode (LIBGIT2_NO_VENDOR=1) probes for a system libgit2 +# via pkg-config; without one it aborts, with one it emits link flags. We don't +# want it to build OR link anything — esp-idf already builds liblibgit2.a and +# puts it in the final link group (that's how git_smoke's 538 symbols resolved). +# So this .pc makes the probe *succeed* (right version, in range [1.9.4,1.10.0)) +# while emitting NOTHING (empty Libs/Cflags). The symbols come from the +# component; libgit2-sys supplies only its hand-written Rust bindings. +Name: libgit2 +Description: libgit2 built as an esp-idf component (mbedTLS) +Version: 1.9.4 +Libs: +Cflags: diff --git a/firmware/pkgconfig/zlib.pc b/firmware/pkgconfig/zlib.pc new file mode 100644 index 0000000..adaedc6 --- /dev/null +++ b/firmware/pkgconfig/zlib.pc @@ -0,0 +1,16 @@ +# Fake pkg-config file for zlib (Spike 7, Path 2). +# +# libz-sys is a non-optional dependency of libgit2-sys. Left to itself it would +# either grab the host (macOS) zlib or vendor-build its own libz.a — and either +# way its zlib symbols would collide with the zlib we bundle *inside* the +# libgit2 component (deps/zlib, which lives in esp-idf's link group where the +# ordering is safe). This .pc makes libz-sys's probe succeed while emitting +# NOTHING, so it contributes no symbols and the component's bundled zlib wins. +# +# PKG_CONFIG_LIBDIR is pointed at this directory only, so neither probe can fall +# through to the host's real zlib.pc. +Name: zlib +Description: zlib bundled into the libgit2 esp-idf component +Version: 1.3.1 +Libs: +Cflags: diff --git a/firmware/src/bin/git_smoke.rs b/firmware/src/bin/git_smoke.rs new file mode 100644 index 0000000..0831312 --- /dev/null +++ b/firmware/src/bin/git_smoke.rs @@ -0,0 +1,45 @@ +//! Spike 7 — Path 2 Gate D: the `git2` safe API on device. +//! +//! Gates A/B/C proved libgit2 compiles, links, and runs via hand externs. This +//! replaces those with the real integration path: the `git2` crate's safe Rust +//! API, bound to our esp-idf-built libgit2 through `libgit2-sys` in system mode +//! (LIBGIT2_NO_VENDOR=1 + the fake pkg-config in firmware/pkgconfig/). If this +//! links and runs, the desktop spike's add/commit/push code transfers to device. +//! +//! Two checks, both in-memory (no filesystem or network needed yet): +//! 1. git2::Version — proves the safe wrapper reaches the linked library. +//! 2. Oid::hash_object — computes a blob SHA1 through libgit2's ODB, which +//! runs the mbedTLS hash backend. The expected hash is known, so a correct +//! value proves the whole path (git2 -> libgit2 -> mbedtls) end to end. + +use esp_idf_svc::sys; +use git2::{ObjectType, Oid}; + +fn main() { + sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + + log::info!("Typoena — Spike 7 Path 2 Gate D (git2 safe API on device)"); + + let (major, minor, patch) = git2::Version::get().libgit2_version(); + log::info!("git2 crate is talking to libgit2 {major}.{minor}.{patch}"); + + // `git hash-object` of the 5 bytes "hello" is a fixed, well-known value. + match Oid::hash_object(ObjectType::Blob, b"hello") { + Ok(oid) => { + log::info!("sha1(blob \"hello\") = {oid}"); + if oid.to_string() == "b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0" { + log::info!("hash matches `git hash-object` — mbedTLS SHA1 backend correct"); + } else { + log::warn!("hash MISMATCH — mbedTLS SHA1 backend produced the wrong digest"); + } + } + Err(e) => log::error!("Oid::hash_object failed: {e}"), + } + + log::info!("✅ git2 safe API linked and ran on device"); + + loop { + std::thread::sleep(std::time::Duration::from_secs(60)); + } +}