Tutorial: Adding Dependencies
Welcome back! In our previous tutorial, we created a simple "Hello, World!" application. Now, we'll take it a step further by learning how to create and adding dependencies in our project. This is a common scenario where you separate concerns into different modules or libraries.
Our goal is to understand how Buck2 manages dependencies.
What We'll Do:
- Create a new library directory
- Create a library with a simple function
- Define a
rust_binary
target for our library - Build the library independently
- Update our binary to depend on an use this new library
- Add a logging dependency to our application
- Build and run the binary
Prerequisites
- You should follow the previous tutorial
Step 1: Adding a Library Directory to Your Project
- Navigate to your project's root directory
Navigate to the buck2_lab
folder we created in the
previous tutorial.
- Create the folders needed for your library
mkdir greeter_lib
mkdir greeter_lib/src
Your project should be like this
buck2_lab
├── greeter_bin
│ ├── BUCK
│ └── src
│ └── main.rs
└── greeter_lib
└── src
Step 2: Creating a library
- Write the library code:
Inside greeter_lib/src/
, create a file named lib.rs
. Add the following Rust
code:
pub fn greet(name: &str) -> String {
let greeting = format!("Hello, {}!", name);
greeting
}
This is a simple public function greet that takes a name and returns a greeting message.
- Define the library's BUCK file: In the
greeter_lib
directory (i.e.,greeter_lib/
), create aBUCK
file. Add the following content:
rust_library(
name = "library",
srcs = ["src/lib.rs"],
visibility = ["PUBLIC"],
)
rust_library
: This Buck2 rule is used for compiling Rust libraries.name = "library"
: We're naming our library target "library". This name will also be used by default as the crate name for Rust.srcs = ["src/lib.rs"]
: Specifies the source file for this library.visibility = ["PUBLIC"]
: This makes the library visible to all other targets.
Step 3: Building the Library (Optional)
You can build the library by running the following command, in the greeter_lib
folder
cd greeter_lib
buck2 build :library --show-full-output
You will see an output like this:
...
BUILD SUCCEEDED
root//buck2_lab/greeter_lib:library /.../greeter_lib/__library__/out/LPPMD/liblibrary-1527a50c.rmeta
Now, navigate back to the project root:
cd ..
Step 4: Adding a Dependency to the Binary
Next, we'll modify our binary application to use the greeter_lib
.
- Write the binary code:
For greeter_bin/src/main.rs
, update the code to use greet
function in
greeter_lib
:
fn main() {
let s = library::greet("buck2");
println!("{}", s);
}
- Update the binary's BUCK file:
In the greeter_bin
directory (i.e., buck2_lab/greeter_bin/BUCK
), update the
BUCK file.
rust_binary(
name = "main",
srcs = ["src/main.rs"],
# Add the dep to our library
deps = ["root//buck2_lab/greeter_lib:library"],
)
You can also use buck2 targets :
command in greeter_lib
folder to get the
full target name of the library.
deps = ["root//buck2_lab/greeter_lib:library"]
: This is the crucial new part!deps
declares dependencies for this target. It accpets a list of targets.
Step 5: Run the Binary
Now, let's build and run our binary application, which dependencies root//buck2_lab/greeter_lib:library
.
- Run the binary:
buck2 run root//buck2_lab/greeter_bin:main
- Expected output:
You should see the following output:
...
BUILD SUCCEEDED - starting your binary
Hello, buck2!
Step 6: Adding a logging dependency to our application
Imagine that as our application grows, we realize we want to log information
about what's happening inside our functions. This is a common need for debugging
or just understanding the flow. To help with this, let's say we've prepared a
simple, shared logging library for you.
You can find it at
https://github.com/facebook/buck2/tree/main/docs/buck2_lab/logging_lib and copy
the folder into buck2_lab
folder.
Our first step is to make our existing greeter_lib use this new logging_lib.
- Update
greeter_lib/BUCK
:
Now, modify buck2_lab/greeter_lib/BUCK
to declare a dependency on
logging_lib
.
rust_library(
name = "library",
srcs = ["src/lib.rs"],
visibility = ["PUBLIC"],
deps = [
# Add the dep to our logging_lib
"root//buck2_lab/logging_lib:logging_lib",
],
)
- Update
greeter_lib/src/lib.rs
:
pub fn greet(name: &str) -> String {
// Let's use our new logging library!
logging_lib::info("Entered greet function in library");
let greeting = format!("Hello, {}!", name);
logging_lib::info("Exiting greet function in library");
greeting
}
- Update
greeter_bin/src/main.rs
:
fn main() {
logging_lib::info("Starting...");
let message = library::greet("Buck2");
println!("{}", message);
logging_lib::info("Exit.");
}
Step 7: The Expected Failure - Understanding Direct vs. Transitive Dependencies
Our dependencies realationship is now like this:
main
depends on library
, and library
depends on logging_lib
.
Let's try to run main
:
- Attemp to run the binary:
buck2 run root//buck2_lab/greeter_bin:main
- Expected Outcome: Build Failure!
You will encounter a compile-time error like this:
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `logging_lib`
--> fbcode/scripts/<unixname>/buck2_lab/greeter_bin/src/main.rs:4:5
|
4 | logging_lib::info("Starting...");
| ^^^^^^^^^^^ use of unresolved module or unlinked crate `logging_lib`
|
= help: you might be missing a crate named `logging_lib`
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `logging_lib`
--> fbcode/scripts/<unixname>/buck2_lab/greeter_bin/src/main.rs:9:5
|
9 | logging_lib::info("Exit.");
| ^^^^^^^^^^^ use of unresolved module or unlinked crate `logging_lib`
|
= help: you might be missing a crate named `logging_lib`
error: aborting due to 2 previous errors
This means that logging_lib
cannot be found in our main
binary.
Why did this fail?
greeter_bin/src/main.rs
directly callslogging_lib::info()
.- However,
greeter_bin/BUCK
only listsgreeter_lib:library
as a direct dependency. - Even though
greeter_lib:library
depends onlogging_lib
, this dependency is not automatically "passed through" (transitive) or made directly available to greeter_bin's source code for compilation by Rust. - For
greeter_bin/src/main.rs
to directly use symbols fromlogging_lib
, thelogging_lib
crate needs to be explicitly available togreeter_bin:main
during its compilation.
Step 9: Fixing the Build - Declaring the Direct Dependency
To fix this, we need to tell Buck2 that greeter_bin
also has a direct
dependency on logging_lib
.
- Update
greeter_bin/BUCK
:
rust_binary(
name = "main",
srcs = ["src/main.rs"],
deps = [
"root//buck2_lab/greeter_lib:library",
"root//buck2_lab/logging_lib:logging_lib", # Add this line
],
)
- Run the binary:
buck2 run root//buck2_lab/greeter_bin:main
You should see the output like this:
...
BUILD SUCCEEDED - starting your binary
[INFO] Starting...
[INFO] Entered greet function in library
[INFO] Exiting greet function in library
Hello, Buck2!
[INFO] Exit.
The final dependency graph should be like this:
Conclusion:
Congratulations! 🥳
You've successfully created a multi-target Rust project with Buck2, where a binary depends multiple targets!
We've covered:
- Structuring a project with separate library and binary components.
- Defining
rust_library
andrust_binary
targets. - Declaring dependencies between targets using the deps attribute.
- Building and running an application that uses libraries dependencies.
This foundational skill of managing dependencies is key to building larger, more complex applications.