{"id":28808439,"url":"https://github.com/pistonite/layered-crate","last_synced_at":"2026-05-30T18:00:48.080Z","repository":{"id":294385525,"uuid":"986793315","full_name":"Pistonite/layered-crate","owner":"Pistonite","description":"Manage internal dependencies between modules in your crate","archived":false,"fork":false,"pushed_at":"2025-09-05T02:17:22.000Z","size":89,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-05T04:09:50.880Z","etag":null,"topics":["build-tools","code-architecture","code-quality","compile-time-checks","crate-structure","internal-dependencies","macro","modular-design","module-system","procedural-macros","rust"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/layered-crate","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Pistonite.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-20T06:14:21.000Z","updated_at":"2025-09-05T02:51:56.000Z","dependencies_parsed_at":"2025-08-13T19:47:42.597Z","dependency_job_id":null,"html_url":"https://github.com/Pistonite/layered-crate","commit_stats":null,"previous_names":["pistonite/layered-crate"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/Pistonite/layered-crate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pistonite%2Flayered-crate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pistonite%2Flayered-crate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pistonite%2Flayered-crate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pistonite%2Flayered-crate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Pistonite","download_url":"https://codeload.github.com/Pistonite/layered-crate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pistonite%2Flayered-crate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33703065,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build-tools","code-architecture","code-quality","compile-time-checks","crate-structure","internal-dependencies","macro","modular-design","module-system","procedural-macros","rust"],"created_at":"2025-06-18T12:09:15.892Z","updated_at":"2026-05-30T18:00:48.055Z","avatar_url":"https://github.com/Pistonite.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# layered-crate\n\nEnforce dependencies amongst internal modules in a crate\n\n##### 0.2.0 -\u003e 0.3.0, this tool is changed to a CLI tool rather than a proc-macro crate. See [this issue](https://github.com/Pistonite/layered-crate/issues/8) for details\n\n```bash\n# install with cargo-binstall from binary release\ncargo binstall layered-crate\n# or build the tool from source\ncargo install layered-crate\n\n# check internal dependencies amongst layers, unused dependencies\n# are automatically denied\nlayered-crate\n\nCARGO=/my-cargo layered-crate -- +nightly check --lib --features ... \n# ^ change the cargo binary with env\n#                                ^ pass extra args to cargo after --\n```\n\n## The Problem\nIn a large Rust project, it's common to have modules or subsystems in a crate\nthat depends on other parts of the crate, forming an internal dependency\ngraph amongst modules. Since Rust allows you to import anything anywhere in the same\ncrate, the dependency can become a mess over long time.\n\nSome projects solve this using a workspace with multiple crates and use crate-level\ndependency. That's what happens when you see a bunch of `project-*` crates when searching\nfor something on crates.io. There are several upsides and downsides to this. Just to list a few:\n\n- Upsides:\n  - Uses the standard `Cargo.toml`, which is more stable\n  - Might be better to split large code base, so someone doesn't have to download everything\n  - Might be better for incremental build, but I am clueless if this is true\n\n- Downsides:\n  - Need to publish 50 instead of 1 crate\n  - Need to have a more complicated `Cargo.toml` setup\n  - Cannot have `pub(crate)` visibility or `impl` for types from dependencies\n  - Might be worse for optimization since one of the factor for inlining is if\n    the inlining is across a crate boundary. However, I have no clue what degree of effect this has\n\nThis tool uses a `Layerfile.toml` to specify the internal dependencies, and\nautomatically checks that the dependencies are respected in the code as\nif they were separate crates. This allows you to keep the code in a single crate\nwhile enforcing the internal dependencies without having to split the crate manually.\n\nIt is designed to work out of the box with existing code base by adding\nthe `Layerfile.toml` file. However, there are some limitations and edge cases,\nespecially regarding macros, that you should read about below if you have\nregular or procedural macros in your code.\n\n## Usage\nTo split your crate into layers, this tool expects your entry point (e.g. `src/lib.rs`)\nto contain module definitions that correspond to the layers you want to create.\nFor example:\n```rust,ignore\n// src/lib.rs\nmod layer1 { // inline module\n    pub fn foo() {\n        // ...\n    }\n}\npub use layer1::foo; // re-exporting the function\npub mod layer2; // non-inline module at layer2.rs or layer2/mod.rs\n\n/* ... */\n```\nNote that both private and public items in the module are checked,\n\nThen, create a `Layerfile.toml` next to `Cargo.toml` with the following content:\n```toml\n[crate]\nexclude = [] \n# ^ optional, list of modules to delete when checking layers\n# note this is different from ignoring the layer/module\n# to ignore something, just don't have a [layer.\u003cname\u003e] section for it\n\n[layer.layer1] # for each module you want to check in lib.rs, create a table for it\n#      ^ `layer1` corresponds to `mod layer1` in the code above\ndepends-on = [\"layer2\"] # list of layers that this layer depends on\nimpl = [] # any layer specified here will be checked together, see below for more details\n\n[layer.layer2]\n# ^ if the layer is at the bottom (doesn't depend on any other layer),\n# you still need to create an empty table for it like this\n```\n\nNow, simply run `layered-crate` to check for violations - you will get an error if anything in `layer2` imports from `layer1`!\n\nBy default, unused layers specified in `depends-on` will automatically be denied by \nsetting `RUSTFLAGS=-Dunused-imports`. you can use the `--no-rust-flags` option to prevent this tool from touching `RUSTFLAGS`.\n```bash\nlayered-crate --no-rust-flags\n```\n\nDuring the layer checking, the layer and its dependencies are split\ninto different crates, so features that normally would work for you in the \nsame-crate setup might not work as expected. Please read the limitations below\n\n## `pub(crate)` visibility and `impl` for types from dependencies\nIf one of your layers depends on an item that is `pub(crate)` in a layer below,\nor needs to implement a type for a layer below, you will get an error since\nthe layer and its dependencies are split into different crates during layer checking.\n\nTo workaround this, add the `impl` property to the layer in `Layerfile.toml`:\n\n```toml\n[layer.layer1]\ndepends-on = [\"layer2\"]\nimpl = [\"layer2\"]       # \u003c- add this\n\n[layer.layer2]\n```\nWhen checking `layer1`, the tool will also put `layer2` in the same test crate as `layer1`.\nHowever, the check is loosened in this case, since `layer1` can also import\nfrom `layer2`'s dependencies (i.e. transitive dependencies).\n\n`layer2` still cannot import from `layer1` - you will get an error when checking `layer2`\n\n## Crate name in macro expansion\nMacro expansion can give some nasty errors - especially procedural macros.\nIf your crate uses macros (including procedural macros), please read \n[this issue on GitHub](https://github.com/Pistonite/layered-crate/issues/8#issuecomment-2923598649)\nbefore considering this tool.\n\n## Build Scripts\nIf `build.rs` is found in the working directory (i.e. next to `Cargo.toml`),\nit will be copied to the generated test packages. The build script might need\nsome modification to work when checking the layers.\n\n1. If the build script reads or writes files within the package's source tree\n   (usually implemented by using `CARGO_MANIFEST_DIR` or `CARGO_MANIFEST_PATH`\n   environment variable), they need to be changed using `LAYERED_CRATE_ORIGINAL_` prefixed\n   version to point to the original paths as if you are running the build script\n   in the original location. Note that the build script should still generate the source\n   code to the same location as usual, since the generated package will link to the original\n   package instead of copying all the source.\n   ```rust\n   // change this:\n   let manifest_dir = env!(\"CARGO_MANIFEST_DIR\");\n   // to:\n   let manifest_dir = std::env::var(\"LAYERED_CRATE_ORIGINAL_MANIFEST_DIR\")\n       .unwrap_or(env!(\"CARGO_MANIFEST_DIR\").to_string());\n   ```\n2. If the build script needs to be adjusted depending on which layer(s) are being\n   built and which layer is being tested, you can use `LAYERED_CRATE_DEPS_LAYERS`\n   and `LAYERED_CRATE_TESTING_LAYER` environment variables.\n     - When building the full package initially (before testing any layer),\n       `LAYERED_CRATE_TESTING_LAYER` will be empty and `LAYERED_CRATE_DEPS_LAYERS`\n       will contain all layers.\n   ```rust\n   let testing_layer = std::env::var(\"LAYERED_CRATE_TESTING_LAYER\").unwrap_or_default();\n   let deps_layers: Vec\u003c_\u003e = std::env::var(\"LAYERED_CRATE_DEPS_LAYERS\")\n       .unwrap_or_default()\n       .split(',').collect();\n   let is_running_layered_crate = !testing_layer.is_empty() || !deps_layers.is_empty();\n   if is_running_layered_crate \u0026\u0026 deps_layers.contains(\"foo\") {\n       // the dependencies of the current layer being tested contains the\n       // \"foo\" layer\n       build_for_foo_layer();\n   }\n   if testing_layer.as_str() == \"foo\" {\n       // the current layer being tested is \"foo\"\n   }\n   ```\n3. If the build script is agnostic of the location of the package, then\n   no change is needed. For example, if only generating files to `target`\n   directory and using paths relative to `target`. The target directory will be\n   the one for the test package, not the original package.\n\n## Other Limitations\nHere are some more limitations of the tool other than the ones\nmentioned above:\n\n1. Currently, we can only check library targets. For binary target,\n   you have to declare a library target, then use that in your `main.rs`:\n   ```toml\n   # these are the defaults so you can omit them\n   [lib]\n   name = \"my_lib\"      \n   path = \"src/lib.rs\"\n\n   [[bin]]\n   name = \"my_bin\"\n   path = \"src/main.rs\"\n   ```\n   ```rust\n   // src/main.rs\n   fn main() { my_lib::main_internal() }\n   ```\n   \n2. We do not support modules produced by macros in the entry point, as we purely\n   parse the entry point as syntax tree. Macros in other modules are fine.\n\n3. The artifacts from running this tool are separated from the artifacts\n   of building/checking your package normally with `cargo`. This means a CI pipeline\n   that runs both `cargo check`/`cargo clippy` and `layered-crate` have duplicated\n   checks and may incur additional cost. If this is an issue, consider:\n   - Adding the output directory of this tool to the cache of your pipeline.\n   - Use `layered-crate` instead of `cargo` to run those checks.\n     ```bash\n     # Replace:\n     cargo clippy -D warnings -D clippy::todo ... # rest of clippy flags\n     # With:\n     layered-crate -- clippy -D warnings -D clippy::todo ... # rest of clippy flags\n     ```\n     Since this tool does not copy your source files except for the entry point (`lib.rs`),\n     the diagnostic messages will still be accurate.\n   - Not using this tool and use multiple crates to organize your project.\n   - Use a global cache helper like `sccache`, which I have not used before,\n     so I am not sure if it works with `check` commands\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpistonite%2Flayered-crate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpistonite%2Flayered-crate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpistonite%2Flayered-crate/lists"}