diff options
author | Alberto Sartori <alberto.sartori@huawei.com> | 2024-07-23 17:24:16 +0200 |
---|---|---|
committer | Alberto Sartori <alberto.sartori@huawei.com> | 2024-07-24 13:06:28 +0200 |
commit | e7f2fc92c8fd64485d635b1cc7b0c2897d78bc69 (patch) | |
tree | a464901b31e83daafcbfb8049debeab732d4e6d0 | |
parent | d5426a260d88d90c8f275557d8f5ae8e1a55d367 (diff) | |
download | rules-rust-e7f2fc92c8fd64485d635b1cc7b0c2897d78bc69.tar.gz |
rules-rust: add interoperability tutorials
-rw-r--r-- | doc/README.md | 9 | ||||
-rw-r--r-- | doc/interoperability/README.md | 25 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/README.md | 150 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/ROOT | 0 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/TARGETS | 7 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/etc/repos.json | 26 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/foo/TARGETS | 10 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/foo/foo.c | 7 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/foo/foo.h | 3 | ||||
-rw-r--r-- | doc/interoperability/c-from-rust/main.rs | 21 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/README.md | 202 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/ROOT | 0 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/TARGETS | 8 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/etc/repos.json | 26 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/foo/TARGETS | 9 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/foo/foo.h | 2 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/foo/foo.rs | 4 | ||||
-rw-r--r-- | doc/interoperability/rust-from-c/main.c | 14 |
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; +} |