diff options
author | Alberto Sartori <alberto.sartori@huawei.com> | 2024-06-25 16:26:10 +0200 |
---|---|---|
committer | Alberto Sartori <alberto.sartori@huawei.com> | 2024-06-28 20:28:37 +0200 |
commit | 4d18d2723a28bb8e262920330335f046d94dac97 (patch) | |
tree | 399178f6e5ff82e6600ae2c90d7020e5cf5e7c89 | |
parent | 20fab4540271abf9216bb22f604e7d48800e4e0b (diff) | |
download | rules-rust-4d18d2723a28bb8e262920330335f046d94dac97.tar.gz |
rules-rust: add "getting-started" tutorial
Through this tutorial the user will learn how to define Rust binaries,
libraries, tests and the default toolchain configuration.
-rw-r--r-- | doc/README.md | 8 | ||||
-rw-r--r-- | doc/getting-started/README.md | 423 | ||||
-rw-r--r-- | doc/getting-started/ROOT | 0 | ||||
-rw-r--r-- | doc/getting-started/TARGETS | 6 | ||||
-rw-r--r-- | doc/getting-started/etc/repos.json | 17 | ||||
-rw-r--r-- | doc/getting-started/main.rs | 3 |
6 files changed, 457 insertions, 0 deletions
diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..ddd9fea --- /dev/null +++ b/doc/README.md @@ -0,0 +1,8 @@ +# Prerequisites + +The tutorials aim to explain the rules' usage and not learn Rust or +the `Justbuild` tool. Therefore, the user is assumed to know the Rust +programming language and `Justbuild`. + +A `rustc` compiler must be installed and accessible in your +environment. diff --git a/doc/getting-started/README.md b/doc/getting-started/README.md new file mode 100644 index 0000000..6717707 --- /dev/null +++ b/doc/getting-started/README.md @@ -0,0 +1,423 @@ +# Getting started + +In this example, we will gradually modify the simple "Hello, World!" +Rust program to become familiar with the major features of the Rust +rules for Justbuild. By the end of this tutorial, you will know how to +define Rust binaries, libraries, and tests and configure the project, +for example, using specific compile flags. + +## Project structure + +Let's start with the following structure. + +```sh +$ tree +. +├── etc +│ └── repos.json +├── main.rs +├── README.md +├── ROOT +└── TARGETS + +0 directories, 5 files +``` + +Apart from the `README.md` (this file) and `main.rs`, which should be expected, three additional files are required: + - `ROOT`, which is just an empty file that sets the worspace root of the given project; + - `etc/repos.json` contains all the repositories involved - can you guess how many repos we need? + - `TARGETS`, which contains the target descriptions. + +For the sake of completeness, this is how the `main.rs` looks like + +```rust +// file: main.rs + +fn main() { + println!("Hello, World!"); +} +``` + +## Multi-repository setup + +`Justbuild` is a truly language-agnostic tool tailored to +multi-repository projects. This project requires _two_ repositories: + + - the repository containing all the source files for this example + (therefore, only the `main.rs`, for now) + + - the repository that instructs `Justbuild` on how to deal with the Rust + programming language + +The `etc/repos.json` file is as follows: +``` jsonc +// file: etc/repos.json + +{ "main": "example" +, "repositories": + { "example": + { "repository": {"type": "file", "path": "."} + , "bindings": {"rules": "rules-rust"} + } + , "rules-rust": + { "repository": + { "type": "git" + , "repository": "https://github.com/just-buildsystem/rules-rust" + , "branch": "master" + , "commit": "1a8211bda5c14ef0b2bff3af062c049d5598f02f" + , "subdir": "rules" + } + } + } +} +``` + +## Target description + +For this example, we can define one `hello` target which will generate +a _binary_ from the `main.rs` source file. +``` jsonc +// file: TARGETS + +{ "hello": + { "type": ["@", "rules", "rust", "binary"] + , "name": ["hello"] + , "crate_root": ["main.rs"] + } +} +``` + +As you might know, the entry point of a crate must be passed to the +`rustc` compiler. In these rules, the entry point is defined by the +key `"crate_root"`. If the project contained _additional_ files, +they would have been listed under the key `srcs`. For example, + +``` jsonc +// file: TARGETS + +{ "hello": + { "type": ["@", "rules", "rust", "binary"] + , "name": ["hello"] + , "crate_root": ["main.rs"] + , "srcs": ["foo.rs", "bar.rs"] + } +} +``` + +It is important to stress that _ALL_ of the required inputs must be +explicitly declared because `Justbuild` compiles in isolation, and +only the listed source files are staged where the compile action is +executed. Coming from `cargo`, where everything is discovered +automatically (because everything must be locally available), this +listing of all and only required inputs might sound like an annoying +and pointless burden on the developer's shoulders. This is not true; +it helps to understand the dependencies between crates better and thus +avoid spaghetti code, which typically means a long compilation time. + +## Let's build + +Finally, we are ready to compile. If you have manually installed Rust +by running + +```sh +$ curl ... https://sh.rustup.rs | ... +``` + +the artifacts are installed under `$RUSTUP_HOME`, which defaults to +`/home/<user_name>/.rustup/`. Since the path contains the username and +the rules don't try to infer this information, we have to provide the +path as well as which CPU architecture we are targeting. + +```sh +$ just-mr build -D'{"ARCH":"x86_64","TOOLCHAIN_CONFIG":{"RUST":{"RUSTUP_HOME":"/home/<user_name>/.rustup"}}}' hello +INFO: Performing repositories setup +INFO: Found 2 repositories to set up +... +INFO: Processed 1 actions, 0 cache hits. +INFO: Artifacts built, logical paths are: + hello [0a747e71e6a525a0d938c704108d2b014f59fdc7:3781216:x] +$ +``` + +Alternatively, the full path to the `rustc` compiler can be provided: +```sh +$ just-mr build -D'{"TOOLCHAIN_CONFIG":{"RUST":{"RUSTC":"/home/<user_name>/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc"}}}' hello +``` + +Please note that the `rustc` to be used must not be under the +`$CARGO_HOME/bin` directory, which is a wrapper around the `rustc` +under the `$RUSTUP_HOME` directory. + +Typing `-D'{...}'` at every tool invocation can be tedious and +error-prone. We now discuss two possible alternatives to leverage the +command line. + +### `~/.just-mrrc` + +The first option is to have a `just-mr` configuration file under your +`$HOME` directory that defines the above variables so the user no +longer needs to type them. For example, a minimal `~/.just-mrrc` can +look as follows: + +``` jsonc +// file: ~/.just-mrrc + +{ "just files": + {"config": [{"root": "home", "path": ".just_file_rust.json"}]} +} +``` +and `~/.just_file_rust.json` can be + +``` jsonc +// file: ~/.just_file_rust.json + +{ "ARCH": "x86_64" +, "TOOLCHAIN_CONFIG": {"RUST": {"RUSTUP_HOME": "/home/<user_name>/.rustup"}} +} +``` +In this way, the example can be compiled by simply typing + +```sh +$ just-mr build hello +``` + +Please refer to the [man page of +just-mrrc(5)](https://github.com/just-buildsystem/justbuild/blob/master/share/man/just-mrrc.5.md) +for more details on the `just-mr` configuration file. + +### Providing defaults + +Specific project configurations, like compile flags, can also be +provided by defining a `defaults` target, which the rules will pick +up. The user can completely overwrite the default `defaults`, which is +defined [here](../../rules/rust/TARGETS), or amend a few entries. It +is worth mentioning that the handling of the variable +`TOOLCHAIN_CONFIG` is defined in the `defaults` target; if needed, it +can also be changed. + +For this example, let's change the default compile flags while leaving +the rest untouched. To do so, the `defaults` we write must "inherit" +the values from the upstream `defaults` target. + +First of all, create the directory where we have to put the `TARGETS` +file containing the `defaults` target: + +```sh +$ mkdir -p etc/defaults/rust +``` + +and add there the following `TARGETS` file + +``` jsonc +// file: etc/defaults/rust/TARGETS + +{ "defaults": + { "type": "defaults" + , "base": [["@", "upstream-rules", "rust", "defaults"]] + , "RUSTC_FLAGS": ["--color=always", "-Copt-level=3"] + } +} +``` + +Finally, amend the `repos.json` file to take into account the new `defaults`: +``` jsonc +//file: etc/repos.json + +{ "main": "example" +, "repositories": + { "example": + { "repository": {"type": "file", "path": "."} + , "bindings": {"rules": "rules-rust"} + } + , "rules-rust-defaults": + {"repository": {"type": "file", "path": "etc/defaults"}} + , "rules-rust-root": + { "repository": + { "type": "git" + , "repository": "https://github.com/just-buildsystem/rules-rust" + , "branch": "master" + , "commit": "1a8211bda5c14ef0b2bff3af062c049d5598f02f" + , "subdir": "rules" + } + } + , "rules-rust": + { "repository": "rules-rust-root" + , "target_root": "rules-rust-defaults" + , "rule_root": "rules-rust-root" + , "bindings": {"upstream-rules": "rules-rust-root"} + } + } +} +``` + +Now, to double-check that new flags are actually used we can compile +with a higher verbosity level + +```sh +$ just-mr build --log-limit 5 hello +... +DEBUG (action:8c992667005bb014e548936f12a9ed77f643f649): + ["sh","-c","'/home/.../rustc' 'main.rs' ... '--color=always' '-Copt-level=3' ...] in environment {...} +... +INFO: Artifacts built, logical paths are: + hello [0a747e71e6a525a0d938c704108d2b014f59fdc7:3781216:x] +... +``` + +## Adding libraries and tests + +Now, let's introduce a library `foo`, with only one public function, +that implements the Heaviside function. + +Let's keep the source files of the library in the directory `foo` + +```sh +$ mkdir foo +``` + +and write `foo/foo.rs` as follows + +```rust +// file: foo/foo.rs + +pub fn heaviside(x: i32) -> i32 { + if x < 0 { + return 0; + } + return 1; +} + +``` +The `foo/TARGETS` file can be as simple as + +``` jsonc +// file: foo/TARGETS + +{ "foo": + { "type": ["@", "rules", "rust", "library"] + , "name": ["foo"] + , "crate_root": ["foo.rs"] + , "stage": ["foo"] + } +} +``` + +As for the `"binary"` type, if the `"library"` requires additional +modules that are not meant to be compiled in dedicated libraries, they +can be listed with the key `"srcs"`. + +Let's implement a simple unit test for `heaviside` function. + +```sh +$ mkdir test +``` + +```rust +// file: test/foo_test.rs + +extern crate foo; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_foo(){ + assert_eq!(foo::heaviside(-7),0); + assert_eq!(foo::heaviside(0),1); + assert_eq!(foo::heaviside(7),1); + } +} +``` + +and add a dedicated target in the `test/TARGETS` file + +``` jsonc +// file: test/TARGETS + +{ "foo_test": + { "type": ["@", "rules", "rust", "test"] + , "name": ["foo_test"] + , "crate_root": ["foo_test.rs"] + , "stage": ["foo"] + , "deps": [["foo", "foo"]] + } +} +``` + +We can check that the function works as expected by building the test report for `"foo_test"` + +```sh +$ just-mr build test foo_test +``` + +Once we are confident with the implementation, let's use it in the `main`: + +```rust +// file: main.rs + +extern crate foo; + +fn main() { + println!("Hello, World!"); + println!("H(-5) == {}", foo::heaviside(-5)); +} +``` + +``` jsonc +// file: TARGETS + +{ "hello": + { "type": ["@", "rules", "rust", "binary"] + , "name": ["hello"] + , "crate_root": ["main.rs"] + , "deps": [["foo", "foo"]] + } +} +``` + +For the sake of completeness, the project tree now looks as follows: + +```sh +$ tree +. +├── etc +│ ├── defaults +│ │ └── rust +│ │ └── TARGETS +│ └── repos.json +├── foo +│ ├── foo.rs +│ └── TARGETS +├── main.rs +├── README.md +├── ROOT +├── TARGETS +└── test + ├── foo_test.rs + └── TARGETS + +5 directories, 10 files +``` + +## Conclusions and additional exercises + +Congratulations! You have learned how to define a Rust binary and a +library, link against it, test, and tune/configure the current project +with the `defaults` target. + +It is important to note that these rules do not force users to agree +to the Cargo requirements (e.g., one library per package, source files +only under the `src` directory, etc.). + +To become more acquainted with the Rust rules, you may want to: + + - reorganize the project layout, for example, moving all source files + under a directory called `src`; + + - let `foo` be a module instead of a separate library (hint: + `foo/foo.rs` must be added to the `"srcs"` field of the `"hello"` + target; + + - set the `rustc` edition for all the crates within this project. +
\ No newline at end of file diff --git a/doc/getting-started/ROOT b/doc/getting-started/ROOT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/doc/getting-started/ROOT diff --git a/doc/getting-started/TARGETS b/doc/getting-started/TARGETS new file mode 100644 index 0000000..2d8cbb0 --- /dev/null +++ b/doc/getting-started/TARGETS @@ -0,0 +1,6 @@ +{ "hello": + { "type": ["@", "rules", "rust", "binary"] + , "name": ["hello"] + , "crate_root": ["main.rs"] + } +} diff --git a/doc/getting-started/etc/repos.json b/doc/getting-started/etc/repos.json new file mode 100644 index 0000000..57ee597 --- /dev/null +++ b/doc/getting-started/etc/repos.json @@ -0,0 +1,17 @@ +{ "main": "example" +, "repositories": + { "example": + { "repository": {"type": "file", "path": "."} + , "bindings": {"rules": "rules-rust"} + } + , "rules-rust": + { "repository": + { "type": "git" + , "repository": "https://github.com/just-buildsystem/rules-rust" + , "branch": "master" + , "commit": "1a8211bda5c14ef0b2bff3af062c049d5598f02f" + , "subdir": "rules" + } + } + } +} diff --git a/doc/getting-started/main.rs b/doc/getting-started/main.rs new file mode 100644 index 0000000..e56f3ba --- /dev/null +++ b/doc/getting-started/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, World!"); +}
\ No newline at end of file |