Skip to content

Commit 74e1a46

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: Taylor Blau <[email protected]>
1 parent f135c23 commit 74e1a46

File tree

9 files changed

+228
-0
lines changed

9 files changed

+228
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,4 @@ Release/
248248
/git.VC.db
249249
*.dSYM
250250
/contrib/buildsystems/out
251+
/contrib/libgit-rs/libgit-sys/target

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,8 @@ CURL_CONFIG = curl-config
653653
GCOV = gcov
654654
STRIP = strip
655655
SPATCH = spatch
656+
LD = ld
657+
OBJCOPY = objcopy
656658

657659
export TCL_PATH TCLTK_PATH
658660

@@ -2713,6 +2715,7 @@ OBJECTS += $(XDIFF_OBJS)
27132715
OBJECTS += $(FUZZ_OBJS)
27142716
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
27152717
OBJECTS += $(UNIT_TEST_OBJS)
2718+
OBJECTS += contrib/libgit-rs/libgit-sys/public_symbol_export.o
27162719

27172720
ifndef NO_CURL
27182721
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3720,6 +3723,10 @@ clean: profile-clean coverage-clean cocciclean
37203723
$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
37213724
$(MAKE) -C Documentation/ clean
37223725
$(RM) Documentation/GIT-EXCLUDED-PROGRAMS
3726+
$(RM) -r contrib/libgit-rs/libgit-sys/target
3727+
$(RM) -r contrib/libgit-rs/libgit-sys/partial_symbol_export.o
3728+
$(RM) -r contrib/libgit-rs/libgit-sys/hidden_symbol_export.o
3729+
$(RM) -r contrib/libgit-rs/libgit-sys/libgitpub.a
37233730
ifndef NO_PERL
37243731
$(RM) -r perl/build/
37253732
endif
@@ -3865,3 +3872,12 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
38653872
build-unit-tests: $(UNIT_TEST_PROGS)
38663873
unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
38673874
$(MAKE) -C t/ unit-tests
3875+
3876+
contrib/libgit-rs/libgit-sys/partial_symbol_export.o: contrib/libgit-rs/libgit-sys/public_symbol_export.o libgit.a reftable/libreftable.a xdiff/lib.a
3877+
$(LD) -r $^ -o $@
3878+
3879+
contrib/libgit-rs/libgit-sys/hidden_symbol_export.o: contrib/libgit-rs/libgit-sys/partial_symbol_export.o
3880+
$(OBJCOPY) --localize-hidden $^ $@
3881+
3882+
contrib/libgit-rs/libgit-sys/libgitpub.a: contrib/libgit-rs/libgit-sys/hidden_symbol_export.o
3883+
$(AR) $(ARFLAGS) $@ $^

contrib/libgit-rs/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.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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"
8+
9+
[lib]
10+
path = "src/lib.rs"
11+
12+
[dependencies]
13+
libz-sys = "1.1.19"
14+
15+
[build-dependencies]
16+
autocfg = "1.4.0"
17+
make-cmd = "0.1.0"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# cgit-info
2+
3+
A small hacky proof-of-concept showing how to provide a Rust FFI for the Git
4+
library.
5+
6+
## Building
7+
8+
`cargo build` automatically builds and picks up on changes made to both
9+
the Rust wrapper and git.git code so there is no need to run `make`
10+
beforehand.
11+
12+
## Running
13+
14+
Assuming you don't make any changes to the Git source, you can just work from
15+
`contrib/cgit-rs` and use `cargo build` or `cargo run` as usual.
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+
"CFLAGS=-fvisibility=hidden",
18+
"contrib/libgit-rs/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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
#include "git-compat-util.h"
6+
#include "contrib/libgit-rs/libgit-sys/public_symbol_export.h"
7+
#include "version.h"
8+
9+
#pragma GCC visibility push(default)
10+
11+
const char *libgit_user_agent(void)
12+
{
13+
return git_user_agent();
14+
}
15+
16+
const char *libgit_user_agent_sanitized(void)
17+
{
18+
return git_user_agent_sanitized();
19+
}
20+
21+
#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 */
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+
}

0 commit comments

Comments
 (0)