Skip to content

Commit 9857a64

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 8f96245 commit 9857a64

File tree

10 files changed

+252
-0
lines changed

10 files changed

+252
-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: 38 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

@@ -2236,6 +2241,12 @@ ifdef FSMONITOR_OS_SETTINGS
22362241
COMPAT_OBJS += compat/fsmonitor/fsm-path-utils-$(FSMONITOR_OS_SETTINGS).o
22372242
endif
22382243

2244+
ifdef INCLUDE_LIBGIT_RS
2245+
# Enable symbol hiding in contrib/libgit-sys/libgitpub.a without making
2246+
# us rebuild the whole tree every time we run a Rust build.
2247+
BASIC_CFLAGS += -fvisibility=hidden
2248+
endif
2249+
22392250
ifeq ($(TCLTK_PATH),)
22402251
NO_TCLTK = NoThanks
22412252
endif
@@ -2732,6 +2743,10 @@ OBJECTS += $(UNIT_TEST_OBJS)
27322743
OBJECTS += $(CLAR_TEST_OBJS)
27332744
OBJECTS += $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
27342745

2746+
ifdef INCLUDE_LIBGIT_RS
2747+
OBJECTS += contrib/libgit-sys/public_symbol_export.o
2748+
endif
2749+
27352750
ifndef NO_CURL
27362751
OBJECTS += http.o http-walker.o remote-curl.o
27372752
endif
@@ -3726,6 +3741,10 @@ clean: profile-clean coverage-clean cocciclean
37263741
$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
37273742
$(MAKE) -C Documentation/ clean
37283743
$(RM) Documentation/GIT-EXCLUDED-PROGRAMS
3744+
$(RM) -r contrib/libgit-sys/target
3745+
$(RM) contrib/libgit-sys/partial_symbol_export.o
3746+
$(RM) contrib/libgit-sys/hidden_symbol_export.o
3747+
$(RM) contrib/libgit-sys/libgitpub.a
37293748
ifndef NO_PERL
37303749
$(RM) -r perl/build/
37313750
endif
@@ -3887,3 +3906,22 @@ $(CLAR_TEST_PROG): $(UNIT_TEST_DIR)/clar.suite $(CLAR_TEST_OBJS) $(GITLIBS) GIT-
38873906
build-unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG)
38883907
unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X
38893908
$(MAKE) -C t/ unit-tests
3909+
3910+
.PHONY: libgit-sys
3911+
libgit-sys:
3912+
$(QUIET)(\
3913+
cd contrib/libgit-sys && \
3914+
cargo build \
3915+
)
3916+
ifdef INCLUDE_LIBGIT_RS
3917+
all:: libgit-sys
3918+
endif
3919+
3920+
contrib/libgit-sys/partial_symbol_export.o: contrib/libgit-sys/public_symbol_export.o libgit.a reftable/libreftable.a xdiff/lib.a
3921+
$(LD) -r $^ -o $@
3922+
3923+
contrib/libgit-sys/hidden_symbol_export.o: contrib/libgit-sys/partial_symbol_export.o
3924+
$(OBJCOPY) --localize-hidden $^ $@
3925+
3926+
contrib/libgit-sys/libgitpub.a: contrib/libgit-sys/hidden_symbol_export.o
3927+
$(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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* Shim to publicly export Git symbols. These must be renamed so that the
2+
* original symbols can be hidden. Renaming these with a "libgit_" prefix also
3+
* avoids conflicts with other libraries such as libgit2.
4+
*/
5+
6+
#include "git-compat-util.h"
7+
#include "contrib/libgit-sys/public_symbol_export.h"
8+
#include "version.h"
9+
10+
#pragma GCC visibility push(default)
11+
12+
const char *libgit_user_agent(void)
13+
{
14+
return git_user_agent();
15+
}
16+
17+
const char *libgit_user_agent_sanitized(void)
18+
{
19+
return git_user_agent_sanitized();
20+
}
21+
22+
#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)