feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+97
@@ -0,0 +1,97 @@
|
||||
<!-- Copyright 2025 The Fuchsia Authors
|
||||
|
||||
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
|
||||
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
|
||||
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
|
||||
This file may not be copied, modified, or distributed except according to
|
||||
those terms. -->
|
||||
|
||||
# Development Guidelines
|
||||
|
||||
This document covers guidelines for developing code changes.
|
||||
|
||||
## Build and Test
|
||||
|
||||
This repository uses a wrapper script (`cargo.sh`) to ensure consistent
|
||||
toolchain usage and configuration.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **NEVER** run `cargo` directly.
|
||||
> **ALWAYS** use `./cargo.sh` for all cargo sub-commands.
|
||||
>
|
||||
> **Why?** `cargo.sh` ensures that the toolchains used in development match
|
||||
> those in CI, which is important because some features are only available on
|
||||
> specific toolchains, and because UI tests rely on the text of compiler errors,
|
||||
> which changes between toolchain versions.
|
||||
|
||||
### Syntax
|
||||
|
||||
`./cargo.sh +<toolchain> <command> [args]`
|
||||
|
||||
This is equivalent to:
|
||||
|
||||
`cargo +1.2.3 <command> [args]`
|
||||
|
||||
...where `1.2.3` is the toolchain version named by `<toolchain>` (e.g., `msrv` ->
|
||||
`1.56.0`).
|
||||
|
||||
### Toolchains
|
||||
|
||||
The `<toolchain>` argument is mandatory:
|
||||
- `msrv`: Minimum Supported Rust Version.
|
||||
- `stable`: Stable toolchain.
|
||||
- `nightly`: Nightly toolchain.
|
||||
- `all`: Runs on `msrv`, `stable`, and `nightly` sequentially.
|
||||
- Version-gated: e.g., `no-zerocopy-core-error-1-81-0` (see `Cargo.toml`).
|
||||
|
||||
|
||||
## MSRV (Minimum Supported Rust Version)
|
||||
|
||||
The MSRV is **1.56.0**.
|
||||
|
||||
- **Do NOT** use features stabilized after 1.56.0 unless version-gated.
|
||||
- **Requirement:** Ask for user approval before introducing new version-gated
|
||||
behavior.
|
||||
- **Verify**: Ensure code compiles on 1.56.0 (`./cargo.sh +msrv ...`).
|
||||
|
||||
### Version Gating Convention
|
||||
|
||||
We use `[package.metadata.build-rs]` in `Cargo.toml` to gate features by Rust version.
|
||||
|
||||
1. **Define**: Add `no-zerocopy-<feature>-<version> = "<version>"` to `Cargo.toml`.
|
||||
2. **Use**: Use `#[cfg(not(no_zerocopy_<feature>_<version>))]` (note underscores).
|
||||
3. **Document**: For public items, use `#[cfg_attr(doc_cfg, doc(cfg(rust = "<version>")))]`.
|
||||
|
||||
**Important:** The toolchains listed in `.github/workflows/ci.yml` and
|
||||
`Cargo.toml` (under `[package.metadata.build-rs]`) must be kept in sync. If you
|
||||
add a new version-gated feature, ensure it is reflected in both places.
|
||||
|
||||
## UI Tests
|
||||
|
||||
For advice on how to add, modify, or remove UI tests (in `tests/ui-*` or
|
||||
`zerocopy-derive/tests/ui-*`), refer to [agent_docs/ui_tests.md](./ui_tests.md).
|
||||
|
||||
## Macro Development
|
||||
|
||||
- **Shared Logic:** Put shared macro logic in `src/util/macro_util.rs` to avoid
|
||||
code duplication in generated code.
|
||||
- **Lints:** Generated code often triggers lints. Use `#[allow(...)]` liberally
|
||||
in generated code to suppress them.
|
||||
```rust
|
||||
// GOOD: Suppress lints that might be triggered by generated names.
|
||||
// Example: Using a variant name (PascalCase) as a field name (snake_case).
|
||||
// Input: `enum MyEnum { VariantA }`
|
||||
// Generated: `union Variants { __field_VariantA: ... }`
|
||||
quote! {
|
||||
#[allow(non_snake_case)]
|
||||
union ___ZerocopyVariants {
|
||||
#(#fields)*
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Unsafe Code
|
||||
|
||||
`unsafe` code is extremely dangerous and should be avoided unless absolutely
|
||||
necessary. For guidelines on writing unsafe code, including pointer casts and
|
||||
safety comments, refer to [agent_docs/unsafe_code.md](./unsafe_code.md).
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<!-- Copyright 2025 The Fuchsia Authors
|
||||
|
||||
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
|
||||
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
|
||||
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
|
||||
This file may not be copied, modified, or distributed except according to
|
||||
those terms. -->
|
||||
|
||||
# Reviewing
|
||||
|
||||
This document outlines the protocols and standards for AI agents performing code
|
||||
reviews in the `zerocopy` repository.
|
||||
|
||||
## 1. The "Analyze-First" Mandate
|
||||
|
||||
Prevent hallucination by grounding your review in reality.
|
||||
|
||||
* **Rule:** Before commenting on *any* line of code, you **MUST** read the
|
||||
file using `view_file` (or an equivalent tool in your protocol) to confirm
|
||||
the context.
|
||||
* **Why:** Diffs often miss surrounding context (e.g., `cfg` gates, trait
|
||||
bounds, imports) that changes the validity of the code.
|
||||
* **Protocol:**
|
||||
1. Review is requested (manually by a user or automatically via CI/PR).
|
||||
2. **YOU** call `view_file` (or equivalent) on the relevant files.
|
||||
3. **YOU** analyze the code in strict steps (Safety -> Logic -> Style).
|
||||
4. **YOU** generate the review.
|
||||
|
||||
## 2. Reviewer Personas
|
||||
|
||||
You are not just a "helper"; you are a multi-disciplinary expert. Switch between
|
||||
these personas as you review:
|
||||
|
||||
### A. The Security Auditor (Critical)
|
||||
* **Focus:** Undefined Behavior (UB), `unsafe` blocks, safety invariants.
|
||||
* **Reference:** You **MUST** verify compliance with
|
||||
[`unsafe_code.md`](unsafe_code.md).
|
||||
* **Checklist:**
|
||||
* [ ] Does every `unsafe` block have a `// SAFETY:` comment?
|
||||
* [ ] Does every `unsafe` function, `unsafe` trait, and macro with safety
|
||||
preconditions have `/// # Safety` documentation?
|
||||
* [ ] Do safety comments comply with each rule in
|
||||
[`unsafe_code.md`](unsafe_code.md)?
|
||||
|
||||
### B. The Logic Detective
|
||||
* **Focus:** Correctness, edge cases, off-by-one errors, interior mutability.
|
||||
* **Checklist:**
|
||||
* [ ] Does the code panic on valid input?
|
||||
* [ ] Are unwrap/expect calls justified?
|
||||
* [ ] Does the logic handle ZSTs (Zero-Sized Types) correctly?
|
||||
* [ ] Are generics properly bounded?
|
||||
|
||||
### C. The Style Cop
|
||||
* **Focus:** Readability, idiomatic Rust, project standards.
|
||||
* **Reference:** [`style.md`](style.md)
|
||||
* **Checklist:**
|
||||
* [ ] Are each of the style guidelines in [`style.md`](style.md) followed?
|
||||
* [ ] Is there unnecessary complexity?
|
||||
|
||||
### D. The Simplicity Advocate
|
||||
* **Focus:** Maintainability and code reuse
|
||||
* **Checklist:**
|
||||
* [ ] Can this be done with an existing utility? (Search the codebase for
|
||||
similar patterns.)
|
||||
* [ ] Is the implementation surprisingly complex for what it does?
|
||||
* [ ] Are there "clever" one-liners that should be expanded for
|
||||
readability?
|
||||
* [ ] Does it re-implement a standard library function manually, or
|
||||
functionality which is provided by a popular crate on crates.io?
|
||||
|
||||
## 3. Operational Protocols
|
||||
|
||||
### Chain-of-Thought (CoT) Requirement
|
||||
You **MUST** output your reasoning before your final verdict.
|
||||
* **Bad:** "This looks good."
|
||||
* **Good:** "I checked the `unsafe` block on line 42. It casts `*mut T` to
|
||||
`*mut u8`. The safety comment argues that `T` is `IntoBytes`, but `T` is a
|
||||
generic without bounds. This is unsound. **Finding:** Unsound `unsafe`
|
||||
block."
|
||||
|
||||
### Actionable Feedback
|
||||
Every critique **MUST** be actionable.
|
||||
* **Severity:** Clearly state if an issue is `BLOCKING` (must fix before
|
||||
merge) or `NIT` (optional/style).
|
||||
* **Fix:** Provide the exact code snippet to fix the issue.
|
||||
|
||||
<!-- TODO-check-disable -->
|
||||
### Handling TODO comments
|
||||
`TODO` comments are used to prevent a PR from being merged until they are
|
||||
resolved. When you encounter a `TODO` comment:
|
||||
1. **Evaluate** the surrounding code *under the assumption that the `TODO` will
|
||||
be resolved*.
|
||||
2. **Critique** only if the `TODO` is insufficient (i.e., the code would still
|
||||
be problematic *even if* the `TODO` were resolved).
|
||||
3. **Safety Placeholders:** A `// SAFETY: TODO` comment is a valid placeholder
|
||||
for a safety comment, and a `/// TODO` comment in a `/// # Safety` doc
|
||||
section is a valid placeholder for safety documentation. **DO NOT** flag
|
||||
the first as a missing safety justification or a critical issue, and **DO
|
||||
NOT** flag the second as missing safety documentation. You must assume the
|
||||
author will write a sound justification or accurate safety documentation
|
||||
before merging.
|
||||
|
||||
<!-- TODO-check-enable -->
|
||||
|
||||
## 4. Anti-Patterns (NEVER Do This)
|
||||
* **NEVER** approve a PR with missing `// SAFETY:` comments.
|
||||
* **NEVER** assume a function works as named; check its definition.
|
||||
* **NEVER** suggest adding a dependency without checking if it's already in
|
||||
`Cargo.toml`.
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
<!-- Copyright 2025 The Fuchsia Authors
|
||||
|
||||
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
|
||||
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
|
||||
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
|
||||
This file may not be copied, modified, or distributed except according to
|
||||
those terms. -->
|
||||
|
||||
# Style Guidelines
|
||||
|
||||
This document covers code style and formatting guidelines for the project, as
|
||||
well as commit message requirements.
|
||||
|
||||
## File Headers
|
||||
|
||||
Each file must contain a copyright header (see `src/lib.rs` for example) which is
|
||||
based on that file's creation year.
|
||||
|
||||
## Formatting
|
||||
|
||||
Refer to `ci/check_fmt.sh`.
|
||||
|
||||
## Comments
|
||||
|
||||
- Wrap all comments (`//`, `///`, `//!`) at **80 columns** from the left margin,
|
||||
taking into account any preceding code or comments.
|
||||
- **Exceptions:** Markdown tables, ASCII diagrams, long URLs, code blocks, or
|
||||
other cases where wrapping would impair readability.
|
||||
|
||||
## Markdown Files
|
||||
|
||||
- Wrap paragraphs and bulleted lists at **80 columns** from the left margin,
|
||||
taking into account any preceding code or comments. For example, a markdown
|
||||
block inside of a `/// Lorem ipsum...` comment should have lines no more than
|
||||
76 columns wide.
|
||||
- In bulleted lists, indent subsequent lines by 2 spaces.
|
||||
- Do not wrap links if it breaks them.
|
||||
- Always put a blank line between a section header and the beginning of the section.
|
||||
|
||||
## Pull Requests and Commit Messages
|
||||
|
||||
Use GitHub issue syntax in commit messages:
|
||||
|
||||
- Resolves issue: `Closes #123`
|
||||
- Progress on issue: `Makes progress on #123`
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<!-- Copyright 2025 The Fuchsia Authors
|
||||
|
||||
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
|
||||
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
|
||||
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
|
||||
This file may not be copied, modified, or distributed except according to
|
||||
those terms. -->
|
||||
|
||||
# UI & Output Tests
|
||||
|
||||
When updating UI test files (`tests/ui-*` or `zerocopy-derive/tests/ui-*`) or
|
||||
functionality which could affect compiler error output or derive output, run:
|
||||
`./tools/update-expected-test-output.sh`.
|
||||
|
||||
**Note:** We maintain separate UI tests for different toolchains (`ui-msrv`,
|
||||
`ui-stable`, `ui-nightly`) because compiler output varies. The script handles
|
||||
this automatically.
|
||||
|
||||
### Symlink Pattern
|
||||
|
||||
To share test code across toolchains while allowing for different error output,
|
||||
we use a symlink pattern:
|
||||
|
||||
1. **Canonical Source:** The `ui-nightly` directory holds the actual source
|
||||
files (`.rs`).
|
||||
2. **Symlinks:** The `ui-stable` and `ui-msrv` directories contain *symlinks*
|
||||
to the `.rs` files in `ui-nightly`.
|
||||
- **Example:** `tests/ui-stable/foo.rs` -> `../ui-nightly/foo.rs`
|
||||
3. **Unique Output:** Each directory contains its own `.stderr` files.
|
||||
|
||||
### Workflow Rules
|
||||
|
||||
- **Adding a Test:**
|
||||
1. Create the `.rs` file in `ui-nightly`.
|
||||
2. Create relative symlinks in `ui-stable` and `ui-msrv` pointing to the
|
||||
new file in `ui-nightly`.
|
||||
3. Run `./tools/update-expected-test-output.sh` to generate the `.stderr` files.
|
||||
- **Modifying a Test:**
|
||||
1. Edit the `.rs` file in `ui-nightly`.
|
||||
2. Run `./tools/update-expected-test-output.sh` to update the `.stderr` files.
|
||||
- **Removing a Test:**
|
||||
1. Delete the `.rs` file from `ui-nightly`.
|
||||
2. Delete the symlinks from `ui-stable` and `ui-msrv`.
|
||||
3. Delete the corresponding `.stderr` files from all three directories.
|
||||
|
||||
**NEVER** edit `.stderr` files directly. Only update them via the script or the
|
||||
commands it runs. If a `.stderr` file is causing a test failure and updating it
|
||||
via tooling does not fix the failure, that indicates a bug.
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<!-- Copyright 2025 The Fuchsia Authors
|
||||
|
||||
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
|
||||
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
|
||||
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
|
||||
This file may not be copied, modified, or distributed except according to
|
||||
those terms. -->
|
||||
|
||||
# Unsafe Code Guidelines
|
||||
|
||||
`unsafe` code is extremely dangerous and should be avoided unless absolutely
|
||||
necessary. When it is absolutely necessary to write `unsafe` code, it should be
|
||||
done extremely carefully.
|
||||
|
||||
This document covers guidelines for writing unsafe code, including pointer casts
|
||||
and safety comments.
|
||||
|
||||
## Pointer Casts
|
||||
|
||||
- **Avoid `&slice[0] as *const T`**: Use `slice.as_ptr()`. Accessing subsequent
|
||||
elements via pointer arithmetic on a single-element pointer is UB.
|
||||
```rust
|
||||
let slice = &[1, 2];
|
||||
|
||||
// BAD: Derived from reference to single element.
|
||||
let ptr = &slice[0] as *const i32;
|
||||
// SAFETY: UB! `ptr` has provenance only for the first element.
|
||||
// Accessing `ptr.add(1)` is out of bounds for this provenance.
|
||||
let val = unsafe { *ptr.add(1) };
|
||||
|
||||
// GOOD: Derived from the slice itself.
|
||||
let ptr = slice.as_ptr();
|
||||
// SAFETY: Safe because `ptr` has provenance for the entire slice.
|
||||
let val = unsafe { *ptr.add(1) };
|
||||
```
|
||||
- **Avoid converting `&mut T` to `*const T`**: This reborrows as a shared
|
||||
reference, restricting permissions. Cast `&mut T` to `*mut T` first.
|
||||
```rust
|
||||
let mut val = 42;
|
||||
let r = &mut val;
|
||||
|
||||
// BAD: `r as *const i32` creates a shared reborrow.
|
||||
// The resulting pointer loses write provenance.
|
||||
let ptr = r as *const i32 as *mut i32;
|
||||
// SAFETY: UB! Writing to a pointer derived from a shared reborrow.
|
||||
unsafe { *ptr = 0 };
|
||||
|
||||
// GOOD: `r as *mut i32` preserves write provenance.
|
||||
let ptr = r as *mut i32;
|
||||
// SAFETY: Safe because `ptr` retains mutable provenance.
|
||||
unsafe { *ptr = 0 };
|
||||
```
|
||||
|
||||
## Safety Comments
|
||||
|
||||
Every `unsafe` block must be documented with a `// SAFETY:` comment.
|
||||
- **Requirement:** The comment must prove soundness using *only* text from the
|
||||
stable [Rust Reference](https://doc.rust-lang.org/reference/) or [standard
|
||||
library documentation](https://doc.rust-lang.org/std/).
|
||||
- **Citation:** You must cite and quote the relevant text from the
|
||||
documentation. Citations must cite a specific version of the documentation
|
||||
(e.g. https://doc.rust-lang.org/1.91.0/reference/ or
|
||||
https://doc.rust-lang.org/1.91.0/std/).
|
||||
- **Prohibition:** Do not rely on "common sense" or behavior not guaranteed by
|
||||
the docs.
|
||||
```rust
|
||||
// BAD: Missing justification for "obvious" properties.
|
||||
// SAFETY: `ptr` and `field` are from the same object.
|
||||
let offset = unsafe { field.cast::<u8>().offset_from(ptr.cast::<u8>()) };
|
||||
|
||||
// GOOD: Explicitly justifies every requirement, even trivial ones.
|
||||
// SAFETY:
|
||||
// - `ptr` and `field` are derived from the same allocated object [1].
|
||||
// - The distance between them is trivially a multiple of `u8`'s size (1) [2],
|
||||
// satisfying `offset_from`'s alignment requirement [1].
|
||||
//
|
||||
// [1] Per https://doc.rust-lang.org/1.91.0/std/primitive.pointer.html#method.offset_from:
|
||||
//
|
||||
// Both pointers must be derived from the same allocated object, and the
|
||||
// distance between them must be a multiple of the element size.
|
||||
//
|
||||
// [2] https://doc.rust-lang.org/1.91.0/reference/type-layout.html#primitive-data-layout
|
||||
let offset = unsafe { field.cast::<u8>().offset_from(ptr.cast::<u8>()) };
|
||||
```
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<!-- Copyright 2025 The Fuchsia Authors
|
||||
|
||||
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
|
||||
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
|
||||
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
|
||||
This file may not be copied, modified, or distributed except according to
|
||||
those terms. -->
|
||||
|
||||
# Validating Changes
|
||||
|
||||
This document covers the procedures and requirements for validating changes to
|
||||
the project, including linting, testing, and pre-submission checks.
|
||||
|
||||
## Linting
|
||||
|
||||
Clippy should **always** be run on the `nightly` toolchain.
|
||||
|
||||
```bash
|
||||
./cargo.sh +nightly clippy
|
||||
./cargo.sh +nightly clippy --tests
|
||||
```
|
||||
|
||||
### Strict Linting
|
||||
|
||||
- We deny warnings in CI. Even warnings not explicitly listed in `lib.rs` will
|
||||
cause CI to fail.
|
||||
- **Why:** We maintain a zero-warning policy so that new warnings (which often
|
||||
indicate bugs) are immediately obvious and not obscured by existing ones.
|
||||
- Do not introduce new warnings.
|
||||
- Respect the strict `deny` list in `src/lib.rs`.
|
||||
|
||||
## Validating Changes
|
||||
|
||||
Ensure the library builds on all supported toolchains and that Clippy passes.
|
||||
|
||||
```bash
|
||||
./cargo.sh +msrv check --tests --features __internal_use_only_features_that_work_on_stable
|
||||
./cargo.sh +stable check --tests --features __internal_use_only_features_that_work_on_stable
|
||||
./cargo.sh +nightly check --tests --all-features
|
||||
./cargo.sh +nightly clippy --tests --all-features --workspace
|
||||
```
|
||||
|
||||
**Note:** Tests are rarely toolchain-sensitive. Running tests on `nightly` is
|
||||
usually sufficient.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- **Unit Tests:** Place unit tests in a `mod tests` module within the source
|
||||
file they test.
|
||||
- **UI/Compile-Fail Tests:**
|
||||
- **`zerocopy`:** Place in `tests/ui-*` (top-level). The top-level `tests`
|
||||
directory contains *only* UI tests.
|
||||
- **`zerocopy-derive`:** Place in `zerocopy-derive/tests/ui-*`.
|
||||
- **Derive Integration Tests:** Place integration tests for derive macros in
|
||||
`zerocopy-derive/tests`.
|
||||
- **Derive Output Tests:** Place unit tests that verify the *generated code*
|
||||
(token streams) in `zerocopy-derive/src/output_tests.rs`.
|
||||
- **Formal Verification (Kani):** Place Kani proofs in a `mod proofs` module
|
||||
within the source file they test.
|
||||
- **Purpose:** Use the
|
||||
[Kani Rust Verifier](https://model-checking.github.io/kani/) to prove the
|
||||
soundness of `unsafe` code or code relied upon by `unsafe` blocks. Unlike
|
||||
testing, which checks specific inputs, Kani proves properties for *all*
|
||||
possible inputs.
|
||||
- **How to Write Proofs:**
|
||||
- **Harnesses:** Mark proof functions with `#[kani::proof]`.
|
||||
- **Inputs:** Use `kani::any()` to generate arbitrary inputs.
|
||||
- **Assumptions:** Use `kani::assume(condition)` to constrain inputs to
|
||||
valid states (e.g., `align.is_power_of_two()`).
|
||||
- **Assertions:** Use `assert!(condition)` to verify the properties you
|
||||
want to prove.
|
||||
- **CI:** Kani runs in CI using the `model-checking/kani-github-action` with
|
||||
specific feature flags to ensure compatibility.
|
||||
|
||||
<!-- FIXME: Describe how to ensure that a Kani proof is "total" (esp wrt function inputs). -->
|
||||
|
||||
## Feature Gates
|
||||
|
||||
When editing code gated by a feature, compile **with and without** that feature.
|
||||
|
||||
```bash
|
||||
./cargo.sh +stable check --tests
|
||||
./cargo.sh +stable check --tests --feature foo
|
||||
```
|
||||
Reference in new issue
Block a user