- Clojure 76.9%
- Starlark 23.1%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| bazel | ||
| design | ||
| resources | ||
| src | ||
| test | ||
| .bazeliskrc | ||
| .bazelrc | ||
| .gitignore | ||
| BUILD.bazel | ||
| deps.edn | ||
| LICENCE | ||
| MODULE.bazel | ||
| MODULE.bazel.lock | ||
| README.md | ||
| TODO.md | ||
Independent
Independent is a tool for developers using Bazel, designed to detect and prevent transitive dependencies forming where they are not wanted.
Introduction
You may have several beliefs about your codebase:
- "the release binary does not contain the development tooling scripts"
- "the backend tests do not depend on the fonts used in the frontend"
- "the source code doesn't depend on any tests"
How are these beliefs enforced? To that end, what ensures that a transitive dependency never forms that violates these? This quickly becomes difficult to reason about after any change, especially in large and complex repositories.
Bazel is already a great tool for management and analysis of dependencies. Independent aims to leverage your use of Bazel to make it the best tool for enforcing these kinds of statements.
Alternatives
Every tool must justify itself, so we should first consider other methods of achieving this.
Package Grouping and Visibility
Bazel has a few built-in mechanisms for preventing immediate dependencies.
- package_groups
- package_visibility
These are great for preventing immediate dependencies where they are not wanted, but do not prevent transitive dependencies.
For example, we can use package_visibility to say A is visible to all except Z. This does not prevent Z depending on B, which depends on A, violating our desired behaviour.
Bazel Query Language
Bazel also has a query language for detecting immediate and transitive dependencies.
- deps, rdeps, somepath, allpaths
These allow you to detect the existence of paths between two packages, however somepath will only display a single path, and allpaths will clobber together many different paths into one chain rather than listing each of the paths separately. This makes understanding the paths and removing them difficult, which is the immediate next step after detecting an error.
Usage
Independent takes two main inputs, a config file and a dependency graph file.
Config File Format
The config file is structured like the following.
{
"version": "0",
"bans": [
{
"depender": {
"pattern": "//src/...",
"exclusions": ["//src/debug/..."]
},
"dependees": [
{
"pattern": "//test/...",
}
]
}
]
}
Each ban is made up of a single depender and multiple dependees.
{
"depender": {
"pattern": "//src/...",
"exclusions": ["//src/debug/..."]
},
"dependees": [
{
"pattern": "//test/...",
}
]
}
means any target under '//src' may not depend on any target under '//test', with an 'exclusion' that targets under '//src/debug/' are ignored.
Note that the elipses syntax is the same as when invoking Bazel via the command line - '//src/...' expands to include all targets in all packages under '//src', not just '//src'.
We do not yet have support for the ':all' syntax e.g. '//src:all'.
Dependency Graph
A dependency graph file is produced by running a Bazel query command.
From the Bazel documentation:
Traditional Bazel query runs on the post-loading phase target graph and therefore has no concept of configurations and their related concepts. Notably, it doesn't correctly resolve select statements and instead returns all possible resolutions of selects. However, the configurable query environment, cquery, properly handles configurations but doesn't provide all of the functionality of this original query. https://bazel.build/query/language#cquery
For our uses, 'all possible resolution of selects' is quite suitable. Therefore, we recommend producing the graph file by running the following.
bazel query //... --output=streamed_jsonproto > dependency-graph.json
If you find yourself in need of cquery, you can produce a suitable dependency graph file by running the output through (jq)[https://jqlang.org/] first as follows.
bazel cquery //... --output=jsonproto | jq --compact-output .results.[].target > dependency-graph.json
The '--compact-output' is necessary as Independent expects each line in the dependency graph file to be a full JSON object.