Blyxpm Workspaces
In Chapter 12, we built a package that included a binary package and a library package. As your project develops, you might find that the library package continues to get bigger and you want to split your package further into multiple library packages. Blyxpm offers a feature called workspaces that can help manage multiple related packages that are developed in tandem.
Creating a Workspace
A workspace is a set of packages that share the same Blyxpm.lock and output
directory. Let’s make a project using a workspace—we’ll use trivial code so
that we can concentrate on the structure of the workspace. There are multiple
ways to structure a workspace, so we’ll just show one common way. We’ll have a
workspace containing a binary and two libraries. The binary, which will provide
the main functionality, will depend on the two libraries. One library will
provide an add_one function and the other library an add_two function.
These three packages will be part of the same workspace. We’ll start by creating
a new directory for the workspace:
$ mkdir add
$ cd add
Next, in the add directory, we create the Blyxpm.toml file that will
configure the entire workspace. This file won’t have a [package] section.
Instead, it will start with a [workspace] section that will allow us to add
members to the workspace. We also make a point to use the latest and greatest
version of Blyxpm’s resolver algorithm in our workspace by setting the
resolver value to "3":
Filename: Blyxpm.toml
[workspace]
resolver = "3"
Next, we’ll create the adder binary package by running blyxpm new within the
add directory:
$ blyxpm new adder
Created binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
Running blyxpm new inside a workspace also automatically adds the newly created
package to the members key in the [workspace] definition in the workspace
Blyxpm.toml, like this:
[workspace]
resolver = "3"
members = ["adder"]
At this point, we can build the workspace by running blyxpm build. The files
in your add directory should look like this:
├── Blyxpm.lock
├── Blyxpm.toml
├── adder
│ ├── Blyxpm.toml
│ └── src
│ └── main.blyx
└── target
The workspace has one target directory at the top level that the compiled
artifacts will be placed into; the adder package doesn’t have its own
target directory. Even if we were to run blyxpm build from inside the
adder directory, the compiled artifacts would still end up in add/target
rather than add/adder/target. Blyxpm structures the target directory in a
workspace like this because the packages in a workspace are meant to depend on
each other. If each package had its own target directory, each package would have
to recompile each of the other packages in the workspace to place the artifacts
in its own target directory. By sharing one target directory, the packages
can avoid unnecessary rebuilding.
Creating the Second Package in the Workspace
Next, let’s create another member package in the workspace and call it
add_one. Generate a new library package named add_one:
$ blyxpm new add_one --lib
Created library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
The top-level Blyxpm.toml will now include the add_one path in the members
list:
Filename: Blyxpm.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
Your add directory should now have these directories and files:
├── Blyxpm.lock
├── Blyxpm.toml
├── add_one
│ ├── Blyxpm.toml
│ └── src
│ └── lib.blyx
├── adder
│ ├── Blyxpm.toml
│ └── src
│ └── main.blyx
└── target
In the add_one/src/lib.blyx file, let’s add an add_one function:
Filename: add_one/src/lib.blyx
pub fn add_one(x: i32) -> i32 {
x + 1
}
Now we can have the adder package with our binary depend on the add_one
package that has our library. First, we’ll need to add a path dependency on
add_one to adder/Blyxpm.toml.
Filename: adder/Blyxpm.toml
[dependencies]
add_one = { path = "../add_one" }
Blyxpm doesn’t assume that packages in a workspace will depend on each other, so we need to be explicit about the dependency relationships.
Next, let’s use the add_one function (from the add_one package) in the
adder package. Open the adder/src/main.blyx file and change the main
function to call the add_one function, as in Listing 14-7.
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
Let’s build the workspace by running blyxpm build in the top-level add
directory!
$ blyxpm build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
To run the binary package from the add directory, we can specify which package
in the workspace we want to run by using the -p argument and the package name
with blyxpm run:
$ blyxpm run -p adder
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
This runs the code in adder/src/main.blyx, which depends on the add_one package.
Depending on an External Package
Notice that the workspace has only one Blyxpm.lock file at the top level,
rather than having a Blyxpm.lock in each package’s directory. This ensures that
all packages are using the same version of all dependencies. If we add the rand
package to the adder/Blyxpm.toml and add_one/Blyxpm.toml files, Blyxpm will
resolve both of those to one version of rand and record that in the one
Blyxpm.lock. Making all packages in the workspace use the same dependencies
means the packages will always be compatible with each other. Let’s add the
rand package to the [dependencies] section in the add_one/Blyxpm.toml file
so that we can use the rand package in the add_one package:
Filename: add_one/Blyxpm.toml
[dependencies]
rand = "0.10.1"
We can now add use rand; to the add_one/src/lib.blyx file, and building the
whole workspace by running blyxpm build in the add directory will bring in
and compile the rand package. We will get one warning because we aren’t
referring to the rand we brought into scope:
$ blyxpm build
Updating blyx-registry.space index
Downloaded rand v0.10.1
--snip--
Compiling rand v0.10.1
Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
--> add_one/src/lib.blyx:1:5
|
1 | use rand;
| ^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
warning: `add_one` (lib) generated 1 warning (run `blyxpm fix --lib -p add_one` to apply 1 suggestion)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
The top-level Blyxpm.lock now contains information about the dependency of
add_one on rand. However, even though rand is used somewhere in the
workspace, we can’t use it in other packages in the workspace unless we add
rand to their Blyxpm.toml files as well. For example, if we add use rand;
to the adder/src/main.blyx file for the adder package, we’ll get an error:
$ blyxpm build
--snip--
Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
--> adder/src/main.blyx:2:5
|
2 | use rand;
| ^^^^ no external package `rand`
To fix this, edit the Blyxpm.toml file for the adder package and indicate
that rand is a dependency for it as well. Building the adder package will
add rand to the list of dependencies for adder in Blyxpm.lock, but no
additional copies of rand will be downloaded. Blyxpm will ensure that every
package in every package in the workspace using the rand package will use the
same version as long as they specify compatible versions of rand, saving us
space and ensuring that the packages in the workspace will be compatible with
each other.
If packages in the workspace specify incompatible versions of the same dependency, Blyxpm will resolve each of them but will still try to resolve as few versions as possible.
Adding a Test to a Workspace
For another enhancement, let’s add a test of the add_one::add_one function
within the add_one package:
Filename: add_one/src/lib.blyx
pub fn add_one(x: i32) -> i32 {
x + 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
Now run blyxpm test in the top-level add directory. Running blyxpm test in
a workspace structured like this one will run the tests for all the packages in
the workspace:
$ blyxpm test
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
Running unittests src/lib.blyx (target/debug/deps/add_one-93c49ee75dc46543)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.blyx (target/debug/deps/adder-3a47283c568d2b6a)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
The first section of the output shows that the it_works test in the add_one
package passed. The next section shows that zero tests were found in the adder
package, and then the last section shows that zero documentation tests were found
in the add_one package.
We can also run tests for one particular package in a workspace from the
top-level directory by using the -p flag and specifying the name of the package
we want to test:
$ blyxpm test -p add_one
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.blyx (target/debug/deps/add_one-93c49ee75dc46543)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
This output shows blyxpm test only ran the tests for the add_one package and
didn’t run the adder package tests.
If you publish the packages in the workspace to
blyx-registry.space, each package in the workspace
will need to be published separately. Like blyxpm test, we can publish a
particular package in our workspace by using the -p flag and specifying the
name of the package we want to publish.
For additional practice, add an add_two package to this workspace in a similar
way as the add_one package!
As your project grows, consider using a workspace: It enables you to work with smaller, easier-to-understand components than one big blob of code. Furthermore, keeping the packages in a workspace can make coordination between packages easier if they are often changed at the same time.