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.
This commit is contained in:
Julien Calixte
2026-07-05 21:28:47 +02:00
parent bea3058004
commit 2b268fe168
4 changed files with 99 additions and 0 deletions

View File

@@ -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"]

View File

@@ -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:

View File

@@ -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:

View File

@@ -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));
}
}