Skip to content

Commit 66f0524

Browse files
authored
Merge pull request #104 from confio/fix-no-std
Fix no-std compatibility and add check on CI
2 parents b946937 + 7cebdab commit 66f0524

File tree

7 files changed

+283
-5
lines changed

7 files changed

+283
-5
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ jobs:
9595
- run:
9696
name: Run all tests
9797
command: cargo test --all
98+
- run:
99+
name: Check no_std compatibility
100+
command: cd no-std-check/; make setup; make all
98101

99102
lint-rust:
100103
docker:

rust/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@ name = "ics23"
33
version = "0.8.0"
44
authors = ["Ethan Frey <[email protected]>"]
55
edition = "2021"
6-
exclude = ["codegen"]
6+
exclude = ["codegen", "no-std-check"]
77
description = "Merkle proof verification library - implements Cosmos ICS23 Spec"
88
repository = "https://github.com/confio/ics23/tree/master/rust"
99
license = "Apache-2.0"
1010
rust-version = "1.56.1"
1111

1212
[workspace]
13-
members = ["codegen"]
13+
members = ["codegen", "no-std-check"]
1414

1515
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1616
[dependencies]
1717
prost = { version = "0.11", default-features = false, features = ["prost-derive"] }
1818
bytes = { version = "1.0.1", default-features = false }
1919
hex = { version = "0.4.3", default-features = false, features = [ "alloc" ] }
2020
anyhow = { version = "1.0.40", default-features = false }
21-
sha2 = { version = "0.10.2", optional = true }
22-
sha3 = { version = "0.10.2", optional = true }
23-
ripemd = { version = "0.1.1", optional = true }
21+
sha2 = { version = "0.10.2", optional = true, default-features = false }
22+
sha3 = { version = "0.10.2", optional = true, default-features = false }
23+
ripemd = { version = "0.1.1", optional = true, default-features = false }
2424

2525
[dev-dependencies]
2626
sha2 = { version = "0.10.2" }

rust/no-std-check/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target/

rust/no-std-check/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "no-std-check"
3+
version = "0.1.0"
4+
edition = "2021"
5+
resolver = "2"
6+
7+
[dependencies]
8+
ics23 = { path = "../", default-features = false }
9+
10+
[features]
11+
panic-handler = []

rust/no-std-check/Makefile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
NIGHTLY_VERSION=nightly
2+
3+
.DEFAULT_GOAL := help
4+
5+
.PHONY: all setup check-panic-conflict check-cargo-build-std check-wasm help
6+
7+
all: ## Run all checks
8+
$(MAKE) check-panic-conflict
9+
$(MAKE) check-cargo-build-std
10+
$(MAKE) check-wasm
11+
12+
setup: ## Setup the required nightly toolchain and the wasm32 target
13+
rustup install $(NIGHTLY_VERSION)
14+
rustup target add wasm32-unknown-unknown --toolchain $(NIGHTLY_VERSION)
15+
rustup component add rust-src --toolchain $(NIGHTLY_VERSION)
16+
17+
check-panic-conflict: ## Check for `no_std` compliance by installing a panic handler, and any other crate importing `std` will cause a conflict. Runs on default target.
18+
cargo build \
19+
--no-default-features \
20+
--features panic-handler
21+
22+
check-cargo-build-std: ## Check for `no_std` compliance using Cargo nightly's `build-std` feature. Runs on the target `x86_64-unknown-linux-gnu`.
23+
rustup run $(NIGHTLY_VERSION) -- \
24+
cargo build -Z build-std=core,alloc \
25+
--no-default-features \
26+
--target x86_64-unknown-linux-gnu
27+
28+
check-wasm: ## Check for WebAssembly and `no_std` compliance by building on the target `wasm32-unknown-unknown` and installing a panic handler.
29+
rustup run $(NIGHTLY_VERSION) -- \
30+
cargo build \
31+
--features panic-handler \
32+
--target wasm32-unknown-unknown
33+
34+
help: ## Show this help message
35+
@grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
36+

rust/no-std-check/README.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# `no_std` Compliance Check
2+
3+
This crate checks the `no_std` compliance of the `ics23` crate.
4+
5+
Based on the corresponding check in the [`ibc-rs`](https://github.com/informalsystems/ibc-rs) project.
6+
7+
## Make Recipes
8+
9+
- `check-panic-conflict` - Check for `no_std` compliance by installing a panic handler, and any other crate importing `std` will cause a conflict. Runs on default target.
10+
11+
- `check-cargo-build-std` - Check for `no_std` compliance using Cargo nightly's `build-std` feature. Runs on the target `x86_64-unknown-linux-gnu`.
12+
13+
- `check-wasm` - Check for WebAssembly and `no_std` compliance by building on the target `wasm32-unknown-unknown` and installing a panic handler.
14+
15+
## Checking Single Unsupported Dependency
16+
17+
By default, the check scripts try to build all unsupported dependencies and will fail. To test if a particular crate still fails the no_std check, edit the `use-unsupported` list in [Cargo.toml](./Cargo.toml) to uncomment all crates except the crate that we are interested to check. For example, to check for only the `getrandom` crate:
18+
19+
```toml
20+
use-unsupported = [
21+
# "tonic",
22+
# "socket2",
23+
"getrandom",
24+
# "serde",
25+
# ...,
26+
]
27+
```
28+
29+
## Adding New Dependencies
30+
31+
For a crate named `my-package-1.2.3`, first try and add the crate in [Cargo.toml](./Cargo.toml) of this project as:
32+
33+
```toml
34+
my-package = { version = "1.2.3" }
35+
```
36+
37+
Then comment out the `use-unsupported` list in the `[features]` section of Cargo.toml and replace it with an empty list temporarily for testing:
38+
39+
```toml
40+
[features]
41+
...
42+
use-unsupported = []
43+
# use-unsupported = [
44+
# "tonic",
45+
# "socket2",
46+
# "getrandom",
47+
# ...
48+
# ]
49+
```
50+
51+
Then import the package in [src/lib.rs](./src/lib.rs):
52+
53+
```rust
54+
use my_package
55+
```
56+
57+
Note that you must import the package in `lib.rs`, otherwise Cargo will skip linking the crate and fail to check for the panic handler conflicts.
58+
59+
Then run all of the check scripts and see if any of them fails. If the check script fails, try and disable the default features and run the checks again:
60+
61+
```rust
62+
my-package = { version = "1.2.3", default-features = false }
63+
```
64+
65+
You may also need other tweaks such as enable custom features to make it run on Wasm.
66+
At this point if the checks pass, we have verified the no_std compliance of `my-package`. Restore the original `use-unsupported` list and commit the code.
67+
68+
Otherwise if it fails, we have found a dependency that does not support `no_std`. Update Cargo.toml to make the crate optional:
69+
70+
```rust
71+
my-package = { version = "1.2.3", optional = true, default-features = false }
72+
```
73+
74+
Now we have to modify [lib.rs](./src/lib.rs) again and only import the crate if it is enabled:
75+
76+
```rust
77+
#[cfg(feature = "my-package")]
78+
use my_package;
79+
```
80+
81+
Retore the original `use-unsupported` list, and add `my-package` to the end of the list:
82+
83+
```toml
84+
use-unsupported = [
85+
"tonic",
86+
"socket2",
87+
"getrandom",
88+
...,
89+
"my-package",
90+
]
91+
```
92+
93+
Commit the changes so that we will track if newer version of the crate would support no_std in the future.
94+
95+
## Conflict Detection Methods
96+
97+
There are two methods that we use to detect `std` conflict:
98+
99+
### Panic Handler Conflict
100+
101+
This follows the outline of the guide by
102+
[Danilo Bargen](https://blog.dbrgn.ch/2019/12/24/testing-for-no-std-compatibility/)
103+
to register a panic handler in the `no-std-check` crate.
104+
Any crate imported `no-std-check` that uses `std` will cause a compile error that
105+
looks like follows:
106+
107+
```
108+
$ cargo build
109+
Updating crates.io index
110+
Compiling no-std-check v0.1.0 (/data/development/informal/ibc-rs/no-std-check)
111+
error[E0152]: found duplicate lang item `panic_impl`
112+
--> src/lib.rs:31:1
113+
|
114+
31 | fn panic(_info: &PanicInfo) -> ! {
115+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
116+
|
117+
= note: the lang item is first defined in crate `std` (which `prost` depends on)
118+
= note: first definition in `std` loaded from /home/ubuntu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-b6b48477bfa8c673.rlib
119+
= note: second definition in the local crate (`no_std_check`)
120+
121+
error: aborting due to previous error
122+
123+
For more information about this error, try `rustc --explain E0152`.
124+
error: could not compile `no-std-check`
125+
```
126+
127+
- Pros:
128+
- Can be tested using Rust stable.
129+
- Cons:
130+
- Crates must be listed on both `Cargo.toml` and `lib.rs`.
131+
- Crates that are listed in `Cargo.toml` but not imported inside `lib.rs` are not checked.
132+
133+
### Overrride std crates using Cargo Nightly
134+
135+
This uses the unstable `build-std` feature provided by
136+
[Cargo Nightly](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std).
137+
With this we can explicitly pass the std crates we want to support, `core` and `alloc`,
138+
via command line, and exclude the `std` crate.
139+
140+
If any of the dependency uses `std`, they will fail to compile at all, albeit with
141+
confusing error messages:
142+
143+
```
144+
$ rustup run nightly -- cargo build -j1 -Z build-std=core,alloc --target x86_64-unknown-linux-gnu
145+
...
146+
Compiling bytes v1.0.1
147+
error[E0773]: attempted to define built-in macro more than once
148+
--> /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:1201:5
149+
|
150+
1201 | / macro_rules! cfg {
151+
1202 | | ($($cfg:tt)*) => {
152+
1203 | | /* compiler built-in */
153+
1204 | | };
154+
1205 | | }
155+
| |_____^
156+
|
157+
note: previously defined here
158+
--> /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:1201:5
159+
|
160+
1201 | / macro_rules! cfg {
161+
1202 | | ($($cfg:tt)*) => {
162+
1203 | | /* compiler built-in */
163+
1204 | | };
164+
1205 | | }
165+
| |_____^
166+
167+
error: duplicate lang item in crate `core` (which `std` depends on): `bool`.
168+
|
169+
= note: the lang item is first defined in crate `core` (which `bytes` depends on)
170+
= note: first definition in `core` loaded from /data/development/informal/ibc-rs/no-std-check/target/x86_64-unknown-linux-gnu/debug/deps/libcore-c00d94870d25cd7e.rmeta
171+
= note: second definition in `core` loaded from /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-9924c22ae1efcf66.rlib
172+
173+
error: duplicate lang item in crate `core` (which `std` depends on): `char`.
174+
|
175+
= note: the lang item is first defined in crate `core` (which `bytes` depends on)
176+
= note: first definition in `core` loaded from /data/development/informal/ibc-rs/no-std-check/target/x86_64-unknown-linux-gnu/debug/deps/libcore-c00d94870d25cd7e.rmeta
177+
= note: second definition in `core` loaded from /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-9924c22ae1efcf66.rlib
178+
...
179+
```
180+
181+
The above error are shown when building the `bytes` crate. This is caused by `bytes` using imports from `std`,
182+
which causes `std` to be included and produce conflicts with the `core` crate that is explicitly built by Cargo.
183+
This produces very long error messages, so we may want to use tools like `less` to scroll through the errors.
184+
185+
Pros:
186+
- Directly identify use of `std` in dependencies.
187+
- Error is raised on the first dependency that imports `std`.
188+
189+
Cons:
190+
- Nightly-only feature that is subject to change.
191+
- Confusing and long error messages.

rust/no-std-check/src/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// ensure_no_std/src/main.rs
2+
#![no_std]
3+
#![allow(unused_imports)]
4+
5+
extern crate alloc;
6+
7+
// Import the crates that we want to check if they are fully no-std compliance
8+
9+
use ics23;
10+
11+
use core::panic::PanicInfo;
12+
13+
/*
14+
15+
This function definition checks for the compliance of no-std in
16+
dependencies by causing a compile error if this crate is
17+
linked with `std`. When that happens, you should see error messages
18+
such as follows:
19+
20+
```
21+
error[E0152]: found duplicate lang item `panic_impl`
22+
--> no-std-check/src/lib.rs
23+
|
24+
12 | fn panic(_info: &PanicInfo) -> ! {
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: the lang item is first defined in crate `std` (which `offending-crate` depends on)
28+
```
29+
30+
*/
31+
#[cfg(feature = "panic-handler")]
32+
#[panic_handler]
33+
#[no_mangle]
34+
fn panic(_info: &PanicInfo) -> ! {
35+
loop {}
36+
}

0 commit comments

Comments
 (0)