Cackle is configured via a cackle.toml
, which by default is located in the package or workspace
root.
Example:
[api.process]
include = [
"std::process",
]
exclude = [
"std::process::abort",
"std::process::exit",
]
Here we define an API called "process". Any package that references symbols in std::process
is
considered to use this API except if the symbol referenced is std::process::abort
or
std::process::exit
, which are excluded from the process
API.
We can define as many APIs as we like. If an API is declared, then packages need permission in order to use those APIs.
Cackle has some built-in API definitions for the Rust standard library that can optionally be used.
import_std = [
"fs",
"net",
"process",
"env",
"terminate",
]
We can grant permissions to a package to use APIs or use unsafe. e.g.:
[pkg.crab1]
allow_unsafe = true
allow_apis = [
"fs",
"process",
]
Here we declare a package called crab1
and say that it is allowed to use the fs
and process
APIs. We also say that it's allowed to use unsafe code.
We can also conditionally grant permissions to use APIs only from particular kinds of binaries. For
example, if we wanted to allow crab1
to use the fs
API, but only in code that is only reachable
from test code, we can do that as follows:
[pkg.crab1]
from.test.allow_apis = [
"fs",
]
Similarly, we can allow the API to be used, but only in code that is reachable from build scripts:
[pkg.crab1]
from.build.allow_apis = [
"fs",
]
If we want to allow an API to be used specifically by crab1
's build script, we can do that as follows:
[pkg.crab1]
build.allow_apis = [
"fs",
]
Allowed APIs inherit as follows:
- pkg.N
- pkg.N.from.build (any build script)
- pkg.N.build (N's build script)
- pkg.N.from.test (any test)
- pkg.N.test (N's tests)
- pkg.N.from.build (any build script)
So granting an API usage to pkg.N
means it can be used in any kind of binary.
[sandbox]
kind = "Bubblewrap"
Here we declare that we'd like to use Bubblewrap
(installed as bwrap
) as our sandbox. Bubblewrap
is currently the only supported kind of sandbox. The sandbox will be used for running build scripts
(build.rs), running tests (with cargo acl test
) and optionally for sandboxing rustc.
If for some reason you don't want to sandbox a particular build script, you can disable the sandbox just for that build script.
[pkg.foo]
build.sandbox.kind = "Disabled"
If a build script needs network access, you can relax the sandbox to allow it as follows:
[pkg.foo]
build.sandbox.allow_network = true
Tests can also be run in a sandbox using the test
subcommand, for example:
cargo acl test
This builds the tests, checking the built test binaries against the permissions granted in cackle.toml, then runs the tests, with a sandbox if one is configured.
The sandbox used for tests is configured under pkg.{pkg-name}.test
. e.g.:
[pkg.foo]
test.sandbox.kind = "Disabled"
Tests and build scripts already have write access to a temporary directory, however, if for some reason they need to write to some directory in your source folder, this can be permitted as follows:
[pkg.foo]
test.sandbox.bind_writable = [
"test_outputs",
]
This will allow tests to write to the "test_outputs" subdirectory within the directory containing
your Cargo.toml
. All directories listed in bind_writable
must exist.
If you'd like to automatically create a writable directory if it doesn't already exist, then
make_writable
behaves the same, but will create the directory before starting the sandbox.
[pkg.foo]
test.sandbox.make_writable = [
"test_outputs",
]
If you need to pass particular environment variables into a sandboxed process, you can list them as follows:
[pkg.foo.test.sandbox]
pass_env = [
"VAR1",
"VAR2",
]
This will cause the variables "VAR1" and "VAR2", if set, to be passed to the sandboxed process - in
this case the tests for the package foo
.
If you have a sandbox configuration, then from config version 2 onwards, rustc will be run in a sandbox. This means that all proc macros get sandboxed. Controlling the sandbox on a per-proc-macro basis unfortunately isn't supported yet, but hopefully will in future. This means that if you have for example one proc macro that needs network access, you'd need to enable network access for the whole rustc sandbox, which means that all proc macros would have network access.
If you need to enable networking from the rustc sandbox, you can do so as follows:
[rustc.sandbox]
allow_network = true
Or if you need to completely disable the rustc sandbox:
[rustc.sandbox]
kind = "Disable"
If you depend on a crate that publishes cackle/export.toml
, you can import API definitions from
this as follows:
[pkg.some-dependency]
import = [
"fs",
]
API definitions imported like this will be namespaced by prefixing them with the crate that exported them. For example:
[pkg.my-bin]
allow_apis = [
"some-dependency::fs",
]
If you're the owner of a crate that provides APIs that you'd like classified, you can create
cackle/export.toml
in your crate.
Features to be be passed to cargo build
can be specified in cackle.toml
as follows:
features = ["feature1", "feature2"]
Arbitrary build flags can be passed to cargo build
using the build_flags
option. The default is
to pass --all-targets
.
[common]
build_flags = ["--all-targets"]
If you'd like to not analyse tests, examples etc, you might override this to just the empty array
[]
. Or if you want to analyse tests, but not examples you might set it to ["--tests"]
. For
available options run cargo build --help
.
By default, Cackle builds with a custom profile named "cackle" which inherits from the "dev" profile. If you'd like to use a different profile, you can override the profile in the configuration file. e.g.
[common]
profile = "cackle-release"
You can also override with the --profile
flag, which takes precedence over the config file.
Cackle supports analysing references even when inlining occurs, so it can work to some extent even
with optimisations enabled, however it's more likely that you'll run into false attribution bugs,
where an API usage is attributed to the wrong package. So unless you really need optimisation for
some reason, it's recommended to set opt-level = 0
.
Split debug info is not yet supported, so you should turn it off.
Here's an example of what you might put in your Cargo.toml
:
[profile.cackle-release]
inherits = "release"
opt-level = 0
split-debuginfo = "off"
strip = false
debug = 2
lto = "off"
The field common.version
is the only required field in the config file.
[common]
version = 1
At present, the only supported version is 1. If we decide to change the default values for any
fields in future, we'll add a new supported version number and 1 will continue to have the defaults
it has now. In this regard, common.version
is a bit like package.edition
in Cargo.toml
. It's
intended as a way to preserve old behaviour while making breaking changes, in particular breaking
changes that might otherwise go unnoticed.