Skip to content

Commit e7f8bf1

Browse files
steadmonspectral54calvin-wan-google
authored andcommitted
libgit-sys: introduce Rust wrapper for libgit.a
Introduce libgit-sys, a Rust wrapper crate that allows Rust code to call functions in libgit.a. This initial patch defines build rules and an interface that exposes user agent string getter functions as a proof of concept. This library can be tested with `cargo test`. In later commits, a higher-level library containing a more Rust-friendly interface will be added at `contrib/libgit-rs`. Symbols in libgit can collide with symbols from other libraries such as libgit2. We avoid this by first exposing library symbols in public_symbol_export.[ch]. These symbols are prepended with "libgit_" to avoid collisions and set to visible using a visibility pragma. In build.rs, Rust builds contrib/libgit-rs/libgit-sys/libgitpub.a, which also contains libgit.a and other dependent libraries, with -fvisibility=hidden to hide all symbols within those libraries that haven't been exposed with a visibility pragma. Co-authored-by: Kyle Lippincott <[email protected]> Co-authored-by: Calvin Wan <[email protected]> Signed-off-by: Calvin Wan <[email protected]> Signed-off-by: Kyle Lippincott <[email protected]> Signed-off-by: Josh Steadmon <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3f8f2ab commit e7f8bf1

File tree

10 files changed

+263
-0
lines changed

10 files changed

+263
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,4 @@ Release/
250250
/git.VC.db
251251
*.dSYM
252252
/contrib/buildsystems/out
253+
/contrib/libgit-sys/target

Makefile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ include shared.mak
416416
# Define LINK_FUZZ_PROGRAMS if you want `make all` to also build the fuzz test
417417
# programs in oss-fuzz/.
418418
#
419+
# Define INCLUDE_LIBGIT_RS if you want `make all` and `make test` to build and
420+
# test the Rust crate in contrib/libgit-sys.
421+
#
419422
# === Optional library: libintl ===
420423
#
421424
# Define NO_GETTEXT if you don't want Git output to be translated.
@@ -657,6 +660,8 @@ CURL_CONFIG = curl-config
657660
GCOV = gcov
658661
STRIP = strip
659662
SPATCH = spatch
663+
LD = ld
664+
OBJCOPY = objcopy
660665

661666
export TCL_PATH TCLTK_PATH
662667

@@ -675,6 +680,7 @@ FUZZ_OBJS =
675680
FUZZ_PROGRAMS =
676681
GIT_OBJS =
677682
LIB_OBJS =
683+
LIBGIT_PUB_OBJS =
678684
SCALAR_OBJS =
679685
OBJECTS =
680686
OTHER_PROGRAMS =
@@ -2236,6 +2242,12 @@ ifdef FSMONITOR_OS_SETTINGS
22362242
COMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o
22372243
endif
22382244

2245+
ifdef INCLUDE_LIBGIT_RS
2246+
# Enable symbol hiding in contrib/libgit-sys/libgitpub.a without making
2247+
# us rebuild the whole tree every time we run a Rust build.
2248+
BASIC_CFLAGS += -fvisibility=hidden
2249+
endif
2250+
22392251
ifeq ($(TCLTK_PATH),)
22402252
NO_TCLTK = NoThanks
22412253
endif
@@ -2732,6 +2744,10 @@ OBJECTS += $(UNIT_TEST_OBJS)
27322744
OBJECTS += $(CLAR_TEST_OBJS)
27332745
OBJECTS += $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
27342746

2747+
ifdef INCLUDE_LIBGIT_RS
2748+
OBJECTS += contrib/libgit-sys/public_symbol_export.o
2749+
endif
2750+
27352751
ifndef NO_CURL
27362752
OBJECTS += http.o http-walker.o remote-curl.o
27372753
endif
@@ -3726,6 +3742,10 @@ clean: profile-clean coverage-clean cocciclean
37263742
$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
37273743
$(MAKE) -C Documentation/ clean
37283744
$(RM) Documentation/GIT-EXCLUDED-PROGRAMS
3745+
$(RM) -r contrib/libgit-sys/target
3746+
$(RM) contrib/libgit-sys/partial_symbol_export.o
3747+
$(RM) contrib/libgit-sys/hidden_symbol_export.o
3748+
$(RM) contrib/libgit-sys/libgitpub.a
37293749
ifndef NO_PERL
37303750
$(RM) -r perl/build/
37313751
endif
@@ -3887,3 +3907,31 @@ $(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-
38873907
build-unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG)
38883908
unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X
38893909
$(MAKE) -C t/ unit-tests
3910+
3911+
.PHONY: libgit-sys
3912+
libgit-sys:
3913+
$(QUIET)(\
3914+
cd contrib/libgit-sys && \
3915+
cargo build \
3916+
)
3917+
ifdef INCLUDE_LIBGIT_RS
3918+
all:: libgit-sys
3919+
endif
3920+
3921+
LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o
3922+
LIBGIT_PUB_OBJS += libgit.a
3923+
LIBGIT_PUB_OBJS += reftable/libreftable.a
3924+
LIBGIT_PUB_OBJS += xdiff/lib.a
3925+
3926+
LIBGIT_PARTIAL_EXPORT = contrib/libgit-sys/partial_symbol_export.o
3927+
3928+
LIBGIT_HIDDEN_EXPORT = contrib/libgit-sys/hidden_symbol_export.o
3929+
3930+
$(LIBGIT_PARTIAL_EXPORT): $(LIBGIT_PUB_OBJS)
3931+
$(LD) -r $^ -o $@
3932+
3933+
$(LIBGIT_HIDDEN_EXPORT): $(LIBGIT_PARTIAL_EXPORT)
3934+
$(OBJCOPY) --localize-hidden $^ $@
3935+
3936+
contrib/libgit-sys/libgitpub.a: $(LIBGIT_HIDDEN_EXPORT)
3937+
$(AR) $(ARFLAGS) $@ $^

contrib/libgit-sys/Cargo.lock

Lines changed: 69 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contrib/libgit-sys/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "libgit-sys"
3+
version = "0.1.0"
4+
edition = "2021"
5+
build = "build.rs"
6+
links = "gitpub"
7+
rust-version = "1.63" # TODO: Once we hit 1.84 or newer, we may want to remove Cargo.lock from
8+
# version control. See https://lore.kernel.org/git/[email protected]/
9+
description = "Native bindings to a portion of libgit"
10+
11+
[lib]
12+
path = "src/lib.rs"
13+
14+
[dependencies]
15+
libz-sys = "1.1.19"
16+
17+
[build-dependencies]
18+
autocfg = "1.4.0"
19+
make-cmd = "0.1.0"

contrib/libgit-sys/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# libgit-sys
2+
3+
A small proof-of-concept crate showing how to provide a Rust FFI to Git
4+
internals.

contrib/libgit-sys/build.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
4+
pub fn main() -> std::io::Result<()> {
5+
let ac = autocfg::new();
6+
ac.emit_has_path("std::ffi::c_char");
7+
8+
let crate_root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
9+
let git_root = crate_root.join("../..");
10+
let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
11+
12+
let make_output = make_cmd::gnu_make()
13+
.env("DEVELOPER", "1")
14+
.env_remove("PROFILE")
15+
.current_dir(git_root.clone())
16+
.args([
17+
"INCLUDE_LIBGIT_RS=YesPlease",
18+
"contrib/libgit-sys/libgitpub.a",
19+
])
20+
.output()
21+
.expect("Make failed to run");
22+
if !make_output.status.success() {
23+
panic!(
24+
"Make failed:\n stdout = {}\n stderr = {}\n",
25+
String::from_utf8(make_output.stdout).unwrap(),
26+
String::from_utf8(make_output.stderr).unwrap()
27+
);
28+
}
29+
std::fs::copy(crate_root.join("libgitpub.a"), dst.join("libgitpub.a"))?;
30+
println!("cargo:rustc-link-search=native={}", dst.display());
31+
println!("cargo:rustc-link-lib=gitpub");
32+
println!("cargo:rerun-if-changed={}", git_root.display());
33+
34+
Ok(())
35+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Shim to publicly export Git symbols. These must be renamed so that the
3+
* original symbols can be hidden. Renaming these with a "libgit_" prefix also
4+
* avoids conflicts with other libraries such as libgit2.
5+
*/
6+
7+
#include "git-compat-util.h"
8+
#include "contrib/libgit-sys/public_symbol_export.h"
9+
#include "version.h"
10+
11+
#pragma GCC visibility push(default)
12+
13+
const char *libgit_user_agent(void)
14+
{
15+
return git_user_agent();
16+
}
17+
18+
const char *libgit_user_agent_sanitized(void)
19+
{
20+
return git_user_agent_sanitized();
21+
}
22+
23+
#pragma GCC visibility pop
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#ifndef PUBLIC_SYMBOL_EXPORT_H
2+
#define PUBLIC_SYMBOL_EXPORT_H
3+
4+
const char *libgit_user_agent(void);
5+
6+
const char *libgit_user_agent_sanitized(void);
7+
8+
#endif /* PUBLIC_SYMBOL_EXPORT_H */

contrib/libgit-sys/src/lib.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#[cfg(has_std__ffi__c_char)]
2+
use std::ffi::c_char;
3+
4+
#[cfg(not(has_std__ffi__c_char))]
5+
#[allow(non_camel_case_types)]
6+
pub type c_char = i8;
7+
8+
extern crate libz_sys;
9+
10+
extern "C" {
11+
pub fn libgit_user_agent() -> *const c_char;
12+
pub fn libgit_user_agent_sanitized() -> *const c_char;
13+
}
14+
15+
#[cfg(test)]
16+
mod tests {
17+
use std::ffi::CStr;
18+
19+
use super::*;
20+
21+
#[test]
22+
fn user_agent_starts_with_git() {
23+
let c_str = unsafe { CStr::from_ptr(libgit_user_agent()) };
24+
let agent = c_str
25+
.to_str()
26+
.expect("User agent contains invalid UTF-8 data");
27+
assert!(
28+
agent.starts_with("git/"),
29+
r#"Expected user agent to start with "git/", got: {}"#,
30+
agent
31+
);
32+
}
33+
34+
#[test]
35+
fn sanitized_user_agent_starts_with_git() {
36+
let c_str = unsafe { CStr::from_ptr(libgit_user_agent_sanitized()) };
37+
let agent = c_str
38+
.to_str()
39+
.expect("Sanitized user agent contains invalid UTF-8 data");
40+
assert!(
41+
agent.starts_with("git/"),
42+
r#"Expected user agent to start with "git/", got: {}"#,
43+
agent
44+
);
45+
}
46+
}

t/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,13 @@ perf:
177177

178178
.PHONY: pre-clean $(T) aggregate-results clean valgrind perf \
179179
check-chainlint clean-chainlint test-chainlint $(UNIT_TESTS)
180+
181+
.PHONY: libgit-sys-test
182+
libgit-sys-test:
183+
$(QUIET)(\
184+
cd ../contrib/libgit-sys && \
185+
cargo test \
186+
)
187+
ifdef INCLUDE_LIBGIT_RS
188+
all:: libgit-sys-test
189+
endif

0 commit comments

Comments
 (0)