summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlberto Sartori <alberto.sartori@huawei.com>2024-07-23 17:24:16 +0200
committerAlberto Sartori <alberto.sartori@huawei.com>2024-07-24 13:06:28 +0200
commite7f2fc92c8fd64485d635b1cc7b0c2897d78bc69 (patch)
treea464901b31e83daafcbfb8049debeab732d4e6d0
parentd5426a260d88d90c8f275557d8f5ae8e1a55d367 (diff)
downloadrules-rust-e7f2fc92c8fd64485d635b1cc7b0c2897d78bc69.tar.gz
rules-rust: add interoperability tutorials
-rw-r--r--doc/README.md9
-rw-r--r--doc/interoperability/README.md25
-rw-r--r--doc/interoperability/c-from-rust/README.md150
-rw-r--r--doc/interoperability/c-from-rust/ROOT0
-rw-r--r--doc/interoperability/c-from-rust/TARGETS7
-rw-r--r--doc/interoperability/c-from-rust/etc/repos.json26
-rw-r--r--doc/interoperability/c-from-rust/foo/TARGETS10
-rw-r--r--doc/interoperability/c-from-rust/foo/foo.c7
-rw-r--r--doc/interoperability/c-from-rust/foo/foo.h3
-rw-r--r--doc/interoperability/c-from-rust/main.rs21
-rw-r--r--doc/interoperability/rust-from-c/README.md202
-rw-r--r--doc/interoperability/rust-from-c/ROOT0
-rw-r--r--doc/interoperability/rust-from-c/TARGETS8
-rw-r--r--doc/interoperability/rust-from-c/etc/repos.json26
-rw-r--r--doc/interoperability/rust-from-c/foo/TARGETS9
-rw-r--r--doc/interoperability/rust-from-c/foo/foo.h2
-rw-r--r--doc/interoperability/rust-from-c/foo/foo.rs4
-rw-r--r--doc/interoperability/rust-from-c/main.c14
18 files changed, 523 insertions, 0 deletions
diff --git a/doc/README.md b/doc/README.md
index ddd9fea..aae3a1d 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -6,3 +6,12 @@ programming language and `Justbuild`.
A `rustc` compiler must be installed and accessible in your
environment.
+
+- [Getting started](./getting-started/README.md) tutorial teaches you
+ how to define Rust binaries, libraries, and tests and configure the
+ project, for example, using specific compile flags.
+
+- [Interoperability](./interoperability/README.md) proposes two
+ tutorials on how to mix Rust and C. On the one hand, a Rust library
+ is consumed by a C binary, on the other hand, a C library is
+ consumed by a Rust binary.
diff --git a/doc/interoperability/README.md b/doc/interoperability/README.md
new file mode 100644
index 0000000..8f401e4
--- /dev/null
+++ b/doc/interoperability/README.md
@@ -0,0 +1,25 @@
+# Interoperability
+
+The majority of the modern projects combine different programming
+languages. These rules, and of course `justbuild`, have all that is
+needed to easily mix different programming languages.
+
+In particular, we propose two simple tutorials to demonstrate how Rust
+and C can work together.
+
+## How to call Rust from C
+
+The tutorial [rust-from-c](./rust-from-c/README.md) showcases how a C
+binary can link against a Rust library `foo`. It is worth mentioning
+that all the details required to let `foo` be consumed by a C target
+are confined within the `foo` definition and implementation. The C
+consumer doesn't even need to know how the library is implemented, as
+long as the interface is kept the same.
+
+## How to call C from Rust
+
+The tutorial [c-from-rust](./c-from-rust/README.md) showcases how to
+call a C function from Rust. All the details are Rust-specific, and
+must be implemented in the consuming Rust target. From the rules'
+viewpoint, nothing special is happening, but we think it is still
+worth to have this tutorial for the sake of completeness.
diff --git a/doc/interoperability/c-from-rust/README.md b/doc/interoperability/c-from-rust/README.md
new file mode 100644
index 0000000..b67b827
--- /dev/null
+++ b/doc/interoperability/c-from-rust/README.md
@@ -0,0 +1,150 @@
+# Call C code from Rust
+
+This tutorial demonstrates how C code can be called from a Rust
+binary. We will write a program that reads a number from `stdin` and
+prints its absolute value, which is computed via a call to a function
+implemented in C.
+
+## Project structure
+
+```sh
+$ tree
+.
+├── etc
+│ └── repos.json
+├── foo
+│ ├── foo.c
+│ ├── foo.h
+│ └── TARGETS
+├── main.rs
+├── README.md
+├── ROOT
+└── TARGETS
+
+2 directories, 8 files
+```
+
+## Required repositories
+
+In the `repos.json` file we need to import both the rules for
+compiling Rust and C code
+
+```jsonc
+// file: etc/repos.json
+
+{ "main": "example"
+, "repositories":
+ { "example":
+ { "repository": {"type": "file", "path": "."}
+ , "bindings": {"rules-rust": "rules-rust", "rules-cc": "rules-cc"}
+ }
+ , "rules-rust":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-rust"
+ , "branch": "master"
+ , "commit": "ed652442176aea086104479bb31aced501df48a2"
+ , "subdir": "rules"
+ }
+ }
+ , "rules-cc":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-cc"
+ , "branch": "master"
+ , "commit": "fac7e7680e00dfc63eec41a33dff86d31571eb4b"
+ , "subdir": "rules"
+ }
+ }
+ }
+}
+```
+
+## Target definition
+
+No special attention should be given in the target definition --
+differently from the [rust-from-c tutorial](../rust-from-c/README.md)
+-- therefore the C library is defined as a normal C library
+
+``` jsonc
+// file: foo/TARGETS
+
+{ "foo":
+ { "type": ["@", "rules-cc", "CC", "library"]
+ , "pure C": ["true"]
+ , "name": ["foo"]
+ , "srcs": ["foo.c"]
+ , "hdrs": ["foo.h"]
+ , "stage": ["foo"]
+ , "ldflags": ["-lm"]
+ }
+}
+```
+
+and the `"main"` is defined as a simple Rust binary that depends on a
+library
+
+``` jsonc
+//file: TARGETS
+
+{ "main":
+ { "type": ["@", "rules-rust", "rust", "binary"]
+ , "name": ["main"]
+ , "crate_root": ["main.rs"]
+ , "deps": [["foo", "foo"]]
+ }
+}
+```
+
+## The `main`
+
+For the sake of completeness, this is how the `main.rs` crate looks
+
+```rust
+// file: main.rs
+
+use std::env;
+
+// declaration of the function implemented in the C library
+extern "C" {
+ fn c_func(input: i32) -> i32;
+}
+
+// wrapper to call the C function
+fn c_call(i: i32) -> i32 {
+ unsafe {
+ return c_func(i);
+ }
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ match args[1].parse::<i32>() {
+ Ok(i) => println!("Absolute value of {} is {}", i, c_call(i)),
+ Err(..) => println!("Wrong argument {}", args[1]),
+ };
+}
+```
+
+## How to compile
+
+The `"main"` target can be built with the following command
+
+```sh
+$ just-mr build main
+```
+
+Please refer to the "Let's build" section of the [getting-started
+tutorial](../../getting-started/README.md) for more details on how to
+find/select the `rustc` compiler.
+
+## Additional exercises
+
+To get more familiarity with the rules, you may want to:
+
+ - write a Rust library that wraps the C library, and add a
+ `["@","rules-rust", "rust", "test"]` to test the library. Of course,
+ the main will only have the new Rust library as a dependency.
+
+ - add a `["@", "rules-cc", "shell/script", "test"]` to test that the
+ main actually works.
diff --git a/doc/interoperability/c-from-rust/ROOT b/doc/interoperability/c-from-rust/ROOT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/interoperability/c-from-rust/ROOT
diff --git a/doc/interoperability/c-from-rust/TARGETS b/doc/interoperability/c-from-rust/TARGETS
new file mode 100644
index 0000000..821f46c
--- /dev/null
+++ b/doc/interoperability/c-from-rust/TARGETS
@@ -0,0 +1,7 @@
+{ "main":
+ { "type": ["@", "rules-rust", "rust", "binary"]
+ , "name": ["main"]
+ , "crate_root": ["main.rs"]
+ , "deps": [["foo", "foo"]]
+ }
+}
diff --git a/doc/interoperability/c-from-rust/etc/repos.json b/doc/interoperability/c-from-rust/etc/repos.json
new file mode 100644
index 0000000..4a3d134
--- /dev/null
+++ b/doc/interoperability/c-from-rust/etc/repos.json
@@ -0,0 +1,26 @@
+{ "main": "example"
+, "repositories":
+ { "example":
+ { "repository": {"type": "file", "path": "."}
+ , "bindings": {"rules-rust": "rules-rust", "rules-cc": "rules-cc"}
+ }
+ , "rules-rust":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-rust"
+ , "branch": "master"
+ , "commit": "ed652442176aea086104479bb31aced501df48a2"
+ , "subdir": "rules"
+ }
+ }
+ , "rules-cc":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-cc"
+ , "branch": "master"
+ , "commit": "fac7e7680e00dfc63eec41a33dff86d31571eb4b"
+ , "subdir": "rules"
+ }
+ }
+ }
+}
diff --git a/doc/interoperability/c-from-rust/foo/TARGETS b/doc/interoperability/c-from-rust/foo/TARGETS
new file mode 100644
index 0000000..804b9e8
--- /dev/null
+++ b/doc/interoperability/c-from-rust/foo/TARGETS
@@ -0,0 +1,10 @@
+{ "foo":
+ { "type": ["@", "rules-cc", "CC", "library"]
+ , "pure C": ["true"]
+ , "name": ["foo"]
+ , "srcs": ["foo.c"]
+ , "hdrs": ["foo.h"]
+ , "stage": ["foo"]
+ , "ldflags": ["-lm"]
+ }
+}
diff --git a/doc/interoperability/c-from-rust/foo/foo.c b/doc/interoperability/c-from-rust/foo/foo.c
new file mode 100644
index 0000000..e419fa4
--- /dev/null
+++ b/doc/interoperability/c-from-rust/foo/foo.c
@@ -0,0 +1,7 @@
+#include <math.h>
+
+#include "foo/foo.h"
+
+int c_func(int x){
+ return sqrt(x*x);
+}
diff --git a/doc/interoperability/c-from-rust/foo/foo.h b/doc/interoperability/c-from-rust/foo/foo.h
new file mode 100644
index 0000000..6ce2745
--- /dev/null
+++ b/doc/interoperability/c-from-rust/foo/foo.h
@@ -0,0 +1,3 @@
+#pragma once
+int c_func(int);
+
diff --git a/doc/interoperability/c-from-rust/main.rs b/doc/interoperability/c-from-rust/main.rs
new file mode 100644
index 0000000..ff27dd7
--- /dev/null
+++ b/doc/interoperability/c-from-rust/main.rs
@@ -0,0 +1,21 @@
+use std::env;
+
+// declaration of the function implemented in the C library
+extern "C" {
+ fn c_func(input: i32) -> i32;
+}
+
+// wrapper to call the C function
+fn c_call(i: i32) -> i32 {
+ unsafe {
+ return c_func(i);
+ }
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ match args[1].parse::<i32>() {
+ Ok(i) => println!("Absolute value of {} is {}", i, c_call(i)),
+ Err(..) => println!("Wrong argument {}", args[1]),
+ };
+}
diff --git a/doc/interoperability/rust-from-c/README.md b/doc/interoperability/rust-from-c/README.md
new file mode 100644
index 0000000..19661db
--- /dev/null
+++ b/doc/interoperability/rust-from-c/README.md
@@ -0,0 +1,202 @@
+# Call Rust code from a C binary
+
+The purpose of this tutorial is to showcase how to mix different
+programming languages using `justbuild`, along with the features that
+these Rust rules have to foster the interoperability. For the sake of
+simplicity, in this example we are going to write a library in Rust
+that is consumed by a C binary. In particular, the program will read a
+number from `stdin` and will print the absolute value, which is
+computed via the call to a Rust function.
+
+Spoiler: developers should pay attention to the definition of the
+target for the `foo` library.
+
+## Project structure
+
+Let's start with the following structure.
+
+```sh
+$ tree
+.
+├── etc
+│ └── repos.json
+├── foo
+│ ├── foo.h
+│ ├── foo.rs
+│ └── TARGETS
+├── main.c
+├── README.md
+├── ROOT
+└── TARGETS
+
+2 directories, 8 files
+```
+
+## Required repositories
+
+In the `repos.json` file we need import both the rules for compiling Rust and C code
+
+```jsonc
+// file: etc/repos.json
+
+{ "main": "example"
+, "repositories":
+ { "example":
+ { "repository": {"type": "file", "path": "."}
+ , "bindings": {"rules-rust": "rules-rust", "rules-cc": "rules-cc"}
+ }
+ , "rules-rust":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-rust"
+ , "branch": "master"
+ , "commit": "ed652442176aea086104479bb31aced501df48a2"
+ , "subdir": "rules"
+ }
+ }
+ , "rules-cc":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-cc"
+ , "branch": "master"
+ , "commit": "fac7e7680e00dfc63eec41a33dff86d31571eb4b"
+ , "subdir": "rules"
+ }
+ }
+ }
+}
+```
+
+## The `foo` library
+
+This library is made by a simple function that returns the absolute value of a number
+
+```rust
+// file: foo/foo.rs
+
+#[no_mangle]
+pub extern "C" fn foo(x: i32) -> i32 {
+ return x.abs();
+}
+```
+
+As you may know, in C, before calling a function its signature must be
+declared. Typically, this kind of information is written in header
+files. The `justbuild` Rust rules allow for the definition of
+companion header files, that can be maintained by the Rust library
+developers. In this case, `foo.h` looks as follows
+
+```C
+/* file: foo/foo.h */
+
+#pragma once
+int foo(int);
+```
+
+Of course, we are not forced to write the corresponding header file,
+and we could, for example, put the declaration of the functions we
+want to use in the `main.c` file. However, having the headers make the
+usage of the library much easier. Therefore, the Rust rules feature a
+dedicated field `"c_hdrs"`, so, the target definition for the library
+can be as follows
+
+```jsonc
+// file foo/TARGETS
+
+{ "foo":
+ { "type": ["@", "rules-rust", "rust", "library"]
+ , "name": ["foo"]
+ , "crate_root": ["foo.rs"]
+ , "native": ["true"]
+ , "c_hdrs": ["foo.h"]
+ , "stage": ["foo"]
+ }
+}
+```
+
+With respect to the example of the [getting started
+tutorial](../../getting-started/README.md), the above target
+definition contains two new fields: `"native"` and `"c_hdrs"`. When
+the field `"native"` evaluates to `true`, a native library is
+generated instead of a Rust one. In the field `"c_hdrs"`, there are
+listed the headers required to use the library. Since it makes sense
+to set the field `"c_hdrs"` only for a native library, when `"c_hdrs"`
+evaluates to a true value, a native library is implied. Therefore, for
+this case, we can drop the `"native"` field
+
+```jsonc
+// file: foo/TARGETS
+
+{ "foo":
+ { "type": ["@", "rules-rust", "rust", "library"]
+ , "name": ["foo"]
+ , "crate_root": ["foo.rs"]
+ , "c_hdrs": ["foo.h"]
+ , "stage": ["foo"]
+ }
+}
+```
+
+## The main
+
+From the C consumer, the `foo` library can be seen as a normal C library
+
+```C
+/* file: main.c */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "foo/foo.h"
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "Please provide one number as argument\n");
+ exit(1);
+ }
+ int x = atoi(argv[1]);
+ printf("absolute value of %d is %d\n", x, foo(x));
+ return 0;
+}
+```
+
+and the target definition is as simple as follows:
+
+```jsonc
+// file: TARGETS
+
+{ "main":
+ { "type": ["@", "rules-cc", "CC", "binary"]
+ , "pure C": ["true"]
+ , "name": ["main"]
+ , "srcs": ["main.c"]
+ , "private-deps": [["foo", "foo"]]
+ }
+}
+```
+
+It' worth to highlight that we simply stated that `"main"` depends on
+the target `["foo", "foo"]`, and, at this level, we don't care how it
+is defined.
+
+## How to compile
+
+The `"main"` target can be built issuing the following command
+
+```sh
+$ just-mr build main
+```
+
+Please refer to the "Let's build" section of the [getting-started
+tutorial](../../getting-started/README.md) for more details on how to
+find/select the `rustc` compiler.
+
+## Additional exercises
+
+To get more familiarity with the rules, you may want to:
+
+ - add a `["@", "rules-rust", "rust", "test"]`, which tests the `foo`
+ library.
+
+ - add a `["@", "rules-cc", "shell/script", "test"]` to test that the
+ main actually works.
diff --git a/doc/interoperability/rust-from-c/ROOT b/doc/interoperability/rust-from-c/ROOT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/interoperability/rust-from-c/ROOT
diff --git a/doc/interoperability/rust-from-c/TARGETS b/doc/interoperability/rust-from-c/TARGETS
new file mode 100644
index 0000000..76681d7
--- /dev/null
+++ b/doc/interoperability/rust-from-c/TARGETS
@@ -0,0 +1,8 @@
+{ "main":
+ { "type": ["@", "rules-cc", "CC", "binary"]
+ , "pure C": ["true"]
+ , "name": ["main"]
+ , "srcs": ["main.c"]
+ , "private-deps": [["foo", "foo"]]
+ }
+}
diff --git a/doc/interoperability/rust-from-c/etc/repos.json b/doc/interoperability/rust-from-c/etc/repos.json
new file mode 100644
index 0000000..4a3d134
--- /dev/null
+++ b/doc/interoperability/rust-from-c/etc/repos.json
@@ -0,0 +1,26 @@
+{ "main": "example"
+, "repositories":
+ { "example":
+ { "repository": {"type": "file", "path": "."}
+ , "bindings": {"rules-rust": "rules-rust", "rules-cc": "rules-cc"}
+ }
+ , "rules-rust":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-rust"
+ , "branch": "master"
+ , "commit": "ed652442176aea086104479bb31aced501df48a2"
+ , "subdir": "rules"
+ }
+ }
+ , "rules-cc":
+ { "repository":
+ { "type": "git"
+ , "repository": "https://github.com/just-buildsystem/rules-cc"
+ , "branch": "master"
+ , "commit": "fac7e7680e00dfc63eec41a33dff86d31571eb4b"
+ , "subdir": "rules"
+ }
+ }
+ }
+}
diff --git a/doc/interoperability/rust-from-c/foo/TARGETS b/doc/interoperability/rust-from-c/foo/TARGETS
new file mode 100644
index 0000000..8d19b35
--- /dev/null
+++ b/doc/interoperability/rust-from-c/foo/TARGETS
@@ -0,0 +1,9 @@
+{ "foo":
+ { "type": ["@", "rules-rust", "rust", "library"]
+ , "name": ["foo"]
+ , "crate_root": ["foo.rs"]
+ , "native": ["true"]
+ , "c_hdrs": ["foo.h"]
+ , "stage": ["foo"]
+ }
+}
diff --git a/doc/interoperability/rust-from-c/foo/foo.h b/doc/interoperability/rust-from-c/foo/foo.h
new file mode 100644
index 0000000..88deb2e
--- /dev/null
+++ b/doc/interoperability/rust-from-c/foo/foo.h
@@ -0,0 +1,2 @@
+#pragma once
+int foo(int);
diff --git a/doc/interoperability/rust-from-c/foo/foo.rs b/doc/interoperability/rust-from-c/foo/foo.rs
new file mode 100644
index 0000000..65cbffc
--- /dev/null
+++ b/doc/interoperability/rust-from-c/foo/foo.rs
@@ -0,0 +1,4 @@
+#[no_mangle]
+pub extern "C" fn foo(x: i32) -> i32 {
+ return x.abs();
+}
diff --git a/doc/interoperability/rust-from-c/main.c b/doc/interoperability/rust-from-c/main.c
new file mode 100644
index 0000000..fb29b38
--- /dev/null
+++ b/doc/interoperability/rust-from-c/main.c
@@ -0,0 +1,14 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "foo/foo.h"
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "Please provide one number as argument\n");
+ exit(1);
+ }
+ int x = atoi(argv[1]);
+ printf("absolute value of %d is %d\n", x, foo(x));
+ return 0;
+}