diff mbox series

[v3,2/6] libgit-sys: introduce Rust wrapper for libgit.a

Message ID 20240906222116.270196-2-calvinwan@google.com (mailing list archive)
State New
Headers show
Series Introduce libgit-rs, a Rust wrapper around libgit.a | expand

Commit Message

Calvin Wan Sept. 6, 2024, 10:21 p.m. UTC
From: Josh Steadmon <steadmon@google.com>

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 <spectral@google.com>
Co-authored-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Kyle Lippincott <spectral@google.com>
Change-Id: Ibb98a3907e69839231355c098b2488ef06d1ba45
---
 .gitignore                                    |  1 +
 Makefile                                      | 13 +++++
 contrib/libgit-rs/libgit-sys/Cargo.lock       | 55 +++++++++++++++++++
 contrib/libgit-rs/libgit-sys/Cargo.toml       | 12 ++++
 contrib/libgit-rs/libgit-sys/README.md        | 15 +++++
 contrib/libgit-rs/libgit-sys/build.rs         | 31 +++++++++++
 .../libgit-sys/public_symbol_export.c         | 20 +++++++
 .../libgit-sys/public_symbol_export.h         |  8 +++
 contrib/libgit-rs/libgit-sys/src/lib.rs       | 42 ++++++++++++++
 9 files changed, 197 insertions(+)
 create mode 100644 contrib/libgit-rs/libgit-sys/Cargo.lock
 create mode 100644 contrib/libgit-rs/libgit-sys/Cargo.toml
 create mode 100644 contrib/libgit-rs/libgit-sys/README.md
 create mode 100644 contrib/libgit-rs/libgit-sys/build.rs
 create mode 100644 contrib/libgit-rs/libgit-sys/public_symbol_export.c
 create mode 100644 contrib/libgit-rs/libgit-sys/public_symbol_export.h
 create mode 100644 contrib/libgit-rs/libgit-sys/src/lib.rs

Comments

Eric Sunshine Sept. 6, 2024, 10:39 p.m. UTC | #1
On Fri, Sep 6, 2024 at 6:21 PM Calvin Wan <calvinwan@google.com> wrote:
> From: Josh Steadmon <steadmon@google.com>

This is curious...

> Co-authored-by: Kyle Lippincott <spectral@google.com>
> Co-authored-by: Calvin Wan <calvinwan@google.com>
> Signed-off-by: Calvin Wan <calvinwan@google.com>
> Signed-off-by: Kyle Lippincott <spectral@google.com>

... since there is no mention of Josh here.

> diff --git a/contrib/libgit-rs/libgit-sys/Cargo.toml b/contrib/libgit-rs/libgit-sys/Cargo.toml
> @@ -0,0 +1,12 @@
> +[package]
> +name = "libgit-sys"
> [...]
> +[dependencies]
> +libz-sys = "1.1.19"
> \ No newline at end of file

Let's give this file a proper line terminator.

> diff --git a/contrib/libgit-rs/libgit-sys/build.rs b/contrib/libgit-rs/libgit-sys/build.rs
> @@ -0,0 +1,31 @@
> +pub fn main() -> std::io::Result<()> {
> +    let crate_root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
> +    let git_root = crate_root.join("../../..");
> +    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
> +
> +    let make_output = std::process::Command::new("make")

Providing a mechanism for people to override this hardcoded spelling
of "make" could be another item for your NEEDSWORK list; in
particular, I'm thinking about platforms on which GNU "make" is
installed as "gmake".

> diff --git a/contrib/libgit-rs/libgit-sys/public_symbol_export.c b/contrib/libgit-rs/libgit-sys/public_symbol_export.c
> @@ -0,0 +1,20 @@
> +// Shim to publicly export Git symbols. These must be renamed so that the
> +// original symbols can be hidden. Renaming these with a "libgit_" prefix also
> +// avoid conflicts with other libraries such as libgit2.

s/avoid/avoids/
Mike Hommey Sept. 6, 2024, 11:04 p.m. UTC | #2
On Fri, Sep 06, 2024 at 06:39:17PM -0400, Eric Sunshine wrote:
> > diff --git a/contrib/libgit-rs/libgit-sys/build.rs b/contrib/libgit-rs/libgit-sys/build.rs
> > @@ -0,0 +1,31 @@
> > +pub fn main() -> std::io::Result<()> {
> > +    let crate_root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
> > +    let git_root = crate_root.join("../../..");
> > +    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
> > +
> > +    let make_output = std::process::Command::new("make")
> 
> Providing a mechanism for people to override this hardcoded spelling
> of "make" could be another item for your NEEDSWORK list; in
> particular, I'm thinking about platforms on which GNU "make" is
> installed as "gmake".

And/or, use the `make_cmd` crate.

Mike
Junio C Hamano Sept. 8, 2024, 9:32 p.m. UTC | #3
Mike Hommey <mh@glandium.org> writes:

> On Fri, Sep 06, 2024 at 06:39:17PM -0400, Eric Sunshine wrote:
>> > diff --git a/contrib/libgit-rs/libgit-sys/build.rs b/contrib/libgit-rs/libgit-sys/build.rs
>> > @@ -0,0 +1,31 @@
>> > +pub fn main() -> std::io::Result<()> {
>> > +    let crate_root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
>> > +    let git_root = crate_root.join("../../..");
>> > +    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
>> > +
>> > +    let make_output = std::process::Command::new("make")
>> 
>> Providing a mechanism for people to override this hardcoded spelling
>> of "make" could be another item for your NEEDSWORK list; in
>> particular, I'm thinking about platforms on which GNU "make" is
>> installed as "gmake".
>
> And/or, use the `make_cmd` crate.

Thanks for helping.
Calvin Wan Sept. 10, 2024, 7:04 p.m. UTC | #4
Thanks for the catches -- will fix in the next reroll
Josh Steadmon Sept. 18, 2024, 9:14 p.m. UTC | #5
On 2024.09.06 18:39, Eric Sunshine wrote:
> On Fri, Sep 6, 2024 at 6:21 PM Calvin Wan <calvinwan@google.com> wrote:
> > From: Josh Steadmon <steadmon@google.com>
> 
> This is curious...
> 
> > Co-authored-by: Kyle Lippincott <spectral@google.com>
> > Co-authored-by: Calvin Wan <calvinwan@google.com>
> > Signed-off-by: Calvin Wan <calvinwan@google.com>
> > Signed-off-by: Kyle Lippincott <spectral@google.com>
> 
> ... since there is no mention of Josh here.
> 
> > diff --git a/contrib/libgit-rs/libgit-sys/Cargo.toml b/contrib/libgit-rs/libgit-sys/Cargo.toml
> > @@ -0,0 +1,12 @@
> > +[package]
> > +name = "libgit-sys"
> > [...]
> > +[dependencies]
> > +libz-sys = "1.1.19"
> > \ No newline at end of file
> 
> Let's give this file a proper line terminator.

Fixed in V4.

> > diff --git a/contrib/libgit-rs/libgit-sys/build.rs b/contrib/libgit-rs/libgit-sys/build.rs
> > @@ -0,0 +1,31 @@
> > +pub fn main() -> std::io::Result<()> {
> > +    let crate_root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
> > +    let git_root = crate_root.join("../../..");
> > +    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
> > +
> > +    let make_output = std::process::Command::new("make")
> 
> Providing a mechanism for people to override this hardcoded spelling
> of "make" could be another item for your NEEDSWORK list; in
> particular, I'm thinking about platforms on which GNU "make" is
> installed as "gmake".

Ack, will use make_cmd as Mike suggests.

> > diff --git a/contrib/libgit-rs/libgit-sys/public_symbol_export.c b/contrib/libgit-rs/libgit-sys/public_symbol_export.c
> > @@ -0,0 +1,20 @@
> > +// Shim to publicly export Git symbols. These must be renamed so that the
> > +// original symbols can be hidden. Renaming these with a "libgit_" prefix also
> > +// avoid conflicts with other libraries such as libgit2.
> 
> s/avoid/avoids/

Fixed in V4.

Thanks for the review!
Josh Steadmon Sept. 18, 2024, 9:14 p.m. UTC | #6
On 2024.09.07 08:04, Mike Hommey wrote:
> On Fri, Sep 06, 2024 at 06:39:17PM -0400, Eric Sunshine wrote:
> > > diff --git a/contrib/libgit-rs/libgit-sys/build.rs b/contrib/libgit-rs/libgit-sys/build.rs
> > > @@ -0,0 +1,31 @@
> > > +pub fn main() -> std::io::Result<()> {
> > > +    let crate_root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
> > > +    let git_root = crate_root.join("../../..");
> > > +    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
> > > +
> > > +    let make_output = std::process::Command::new("make")
> > 
> > Providing a mechanism for people to override this hardcoded spelling
> > of "make" could be another item for your NEEDSWORK list; in
> > particular, I'm thinking about platforms on which GNU "make" is
> > installed as "gmake".
> 
> And/or, use the `make_cmd` crate.

Will do, thanks!
diff mbox series

Patch

diff --git a/.gitignore b/.gitignore
index 8caf3700c2..dfd72820fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -248,3 +248,4 @@  Release/
 /git.VC.db
 *.dSYM
 /contrib/buildsystems/out
+/contrib/libgit-rs/libgit-sys/target
diff --git a/Makefile b/Makefile
index 7caeb3c872..0090514e55 100644
--- a/Makefile
+++ b/Makefile
@@ -653,6 +653,8 @@  CURL_CONFIG = curl-config
 GCOV = gcov
 STRIP = strip
 SPATCH = spatch
+LD = ld
+OBJCOPY = objcopy
 
 export TCL_PATH TCLTK_PATH
 
@@ -2713,6 +2715,7 @@  OBJECTS += $(XDIFF_OBJS)
 OBJECTS += $(FUZZ_OBJS)
 OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
 OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += contrib/libgit-rs/libgit-sys/public_symbol_export.o
 
 ifndef NO_CURL
 	OBJECTS += http.o http-walker.o remote-curl.o
@@ -3720,6 +3723,7 @@  clean: profile-clean coverage-clean cocciclean
 	$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
 	$(MAKE) -C Documentation/ clean
 	$(RM) Documentation/GIT-EXCLUDED-PROGRAMS
+	$(RM) -r contrib/libgit-rs/libgit-sys/target
 ifndef NO_PERL
 	$(RM) -r perl/build/
 endif
@@ -3865,3 +3869,12 @@  $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
 build-unit-tests: $(UNIT_TEST_PROGS)
 unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
 	$(MAKE) -C t/ unit-tests
+
+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
+	$(LD) -r $^ -o $@
+
+contrib/libgit-rs/libgit-sys/hidden_symbol_export.o: contrib/libgit-rs/libgit-sys/partial_symbol_export.o
+	$(OBJCOPY) --localize-hidden $^ $@
+
+contrib/libgit-rs/libgit-sys/libgitpub.a: contrib/libgit-rs/libgit-sys/hidden_symbol_export.o
+	$(AR) $(ARFLAGS) $@ $^
diff --git a/contrib/libgit-rs/libgit-sys/Cargo.lock b/contrib/libgit-rs/libgit-sys/Cargo.lock
new file mode 100644
index 0000000000..af8676ab42
--- /dev/null
+++ b/contrib/libgit-rs/libgit-sys/Cargo.lock
@@ -0,0 +1,55 @@ 
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cc"
+version = "1.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.158"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+
+[[package]]
+name = "libgit-sys"
+version = "0.1.0"
+dependencies = [
+ "libz-sys",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
diff --git a/contrib/libgit-rs/libgit-sys/Cargo.toml b/contrib/libgit-rs/libgit-sys/Cargo.toml
new file mode 100644
index 0000000000..905fd6774f
--- /dev/null
+++ b/contrib/libgit-rs/libgit-sys/Cargo.toml
@@ -0,0 +1,12 @@ 
+[package]
+name = "libgit-sys"
+version = "0.1.0"
+edition = "2021"
+build = "build.rs"
+links = "gitpub"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+libz-sys = "1.1.19"
\ No newline at end of file
diff --git a/contrib/libgit-rs/libgit-sys/README.md b/contrib/libgit-rs/libgit-sys/README.md
new file mode 100644
index 0000000000..7a59602c30
--- /dev/null
+++ b/contrib/libgit-rs/libgit-sys/README.md
@@ -0,0 +1,15 @@ 
+# cgit-info
+
+A small hacky proof-of-concept showing how to provide a Rust FFI for the Git
+library.
+
+## Building
+
+`cargo build` automatically builds and picks up on changes made to both
+the Rust wrapper and git.git code so there is no need to run `make`
+beforehand.
+
+## Running
+
+Assuming you don't make any changes to the Git source, you can just work from
+`contrib/cgit-rs` and use `cargo build` or `cargo run` as usual.
diff --git a/contrib/libgit-rs/libgit-sys/build.rs b/contrib/libgit-rs/libgit-sys/build.rs
new file mode 100644
index 0000000000..c187ab6cd0
--- /dev/null
+++ b/contrib/libgit-rs/libgit-sys/build.rs
@@ -0,0 +1,31 @@ 
+use std::env;
+use std::path::PathBuf;
+
+pub fn main() -> std::io::Result<()> {
+    let crate_root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
+    let git_root = crate_root.join("../../..");
+    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+
+    let make_output = std::process::Command::new("make")
+        .env_remove("PROFILE")
+        .current_dir(git_root.clone())
+        .args([
+            "CFLAGS=-fvisibility=hidden",
+            "contrib/libgit-rs/libgit-sys/libgitpub.a",
+        ])
+        .output()
+        .expect("Make failed to run");
+    if !make_output.status.success() {
+        panic!(
+            "Make failed:\n  stdout = {}\n  stderr = {}\n",
+            String::from_utf8(make_output.stdout).unwrap(),
+            String::from_utf8(make_output.stderr).unwrap()
+        );
+    }
+    std::fs::copy(crate_root.join("libgitpub.a"), dst.join("libgitpub.a"))?;
+    println!("cargo::rustc-link-search=native={}", dst.display());
+    println!("cargo::rustc-link-lib=gitpub");
+    println!("cargo::rerun-if-changed={}", git_root.display());
+
+    Ok(())
+}
diff --git a/contrib/libgit-rs/libgit-sys/public_symbol_export.c b/contrib/libgit-rs/libgit-sys/public_symbol_export.c
new file mode 100644
index 0000000000..39c27d9c1a
--- /dev/null
+++ b/contrib/libgit-rs/libgit-sys/public_symbol_export.c
@@ -0,0 +1,20 @@ 
+// Shim to publicly export Git symbols. These must be renamed so that the
+// original symbols can be hidden. Renaming these with a "libgit_" prefix also
+// avoid conflicts with other libraries such as libgit2.
+
+#include "contrib/libgit-rs/libgit-sys/public_symbol_export.h"
+#include "version.h"
+
+#pragma GCC visibility push(default)
+
+const char *libgit_user_agent(void)
+{
+	return git_user_agent();
+}
+
+const char *libgit_user_agent_sanitized(void)
+{
+	return git_user_agent_sanitized();
+}
+
+#pragma GCC visibility pop
diff --git a/contrib/libgit-rs/libgit-sys/public_symbol_export.h b/contrib/libgit-rs/libgit-sys/public_symbol_export.h
new file mode 100644
index 0000000000..a3372f93fa
--- /dev/null
+++ b/contrib/libgit-rs/libgit-sys/public_symbol_export.h
@@ -0,0 +1,8 @@ 
+#ifndef PUBLIC_SYMBOL_EXPORT_H
+#define PUBLIC_SYMBOL_EXPORT_H
+
+const char *libgit_user_agent(void);
+
+const char *libgit_user_agent_sanitized(void);
+
+#endif /* PUBLIC_SYMBOL_EXPORT_H */
diff --git a/contrib/libgit-rs/libgit-sys/src/lib.rs b/contrib/libgit-rs/libgit-sys/src/lib.rs
new file mode 100644
index 0000000000..346b26083d
--- /dev/null
+++ b/contrib/libgit-rs/libgit-sys/src/lib.rs
@@ -0,0 +1,42 @@ 
+use std::ffi::c_char;
+
+extern crate libz_sys;
+
+extern "C" {
+    // From version.c
+    pub fn libgit_user_agent() -> *const c_char;
+    pub fn libgit_user_agent_sanitized() -> *const c_char;
+}
+
+#[cfg(test)]
+mod tests {
+    use std::ffi::CStr;
+
+    use super::*;
+
+    #[test]
+    fn user_agent_starts_with_git() {
+        let c_str = unsafe { CStr::from_ptr(libgit_user_agent()) };
+        let agent = c_str
+            .to_str()
+            .expect("User agent contains invalid UTF-8 data");
+        assert!(
+            agent.starts_with("git/"),
+            r#"Expected user agent to start with "git/", got: {}"#,
+            agent
+        );
+    }
+
+    #[test]
+    fn sanitized_user_agent_starts_with_git() {
+        let c_str = unsafe { CStr::from_ptr(libgit_user_agent_sanitized()) };
+        let agent = c_str
+            .to_str()
+            .expect("Sanitized user agent contains invalid UTF-8 data");
+        assert!(
+            agent.starts_with("git/"),
+            r#"Expected user agent to start with "git/", got: {}"#,
+            agent
+        );
+    }
+}