Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build-std feature excludes use of cargo test (at least when no_main in use) #13146

Open
mcclure opened this issue Dec 9, 2023 · 1 comment
Open
Labels
C-bug Category: bug S-triage Status: This issue is waiting on initial triage.

Comments

@mcclure
Copy link

mcclure commented Dec 9, 2023

Problem

Short version: I am writing an application for a slightly unusual embedded platform, which entails using a platform json and no-std. It worked great until I decided I wanted to write a #[test] function for one of my files. At this point I got a series of very unhelpful error messages, ending with learning that our custom platform setup excluded use of cargo test entirely.

I got a lot of help from a nice frog person on the Rust Discord who explained why this was happening; when I asked if I should file an issue they said

I think all of this is covered by the cargo build-std working group issues. But you can file a usecase report if you want

So here is my filing of "a use case I would consider reasonable but which the current combination of planned Cargo features does not allow", for the consideration of the people working on build-std. This may be too much detail but I wasn't sure what to include.

Steps

I have a project: https://github.com/mcclure/pocket-riscv-rs/tree/minibreak

Which is a sample code/test app for a bespoke RISC-V based platform a friend is making. The platform is in one sense exotic (it is a softcore CPU for a FPGA-based video game system) and in another sense very ordinary (it is a standard RISC-V configuration, based on litex which is a common toolkit for building SoCs). I believe the problems below would apply to any program targeting a 32-bit RISC-V CPU with an FPU.

The project is set up in this way: Because our CPU arch is "tier 3" for Rust (there are other RISC-Vs which are tier 2, such as riscv32imac and riscv64imafdc [aka riscv64gc], but riscv32imafdc— in other words, riscv32imac with a FPU— is tier 3), we must describe the arch to the Rust compiler using a json file. Because we are running in an embedded configuration where there is no OS and booting the program entails just jumping to an address, we run with no_main and riscv_rt::entry on "main", and we objcopy the binary out of the ELF for raw loading as a second step after build (we have a minimal Makefile for executing the two cargo invocations). To tell the compiler about our platform json, as well as our "region" linker scripts, we use .cargo/config.toml. We use core (but not std), and in order to use core on a tier 3 arch we rely on build-std, which requires running in nightly. Everything about this configuration works, and we are not aware of a simpler way to do it (except maybe "possible solution 3" below).

My test app has a src/irect2.rs containing a miniscule set of math helpers. I wound up writing brief unit tests for these math helpers, which live inline in irect2.rs as the #[cfg(test)] mod tests { use super::*; #[test] fn… construction that the docs recommend. When I tried running this with cargo +nightly test I got an error "can't find crate" on test. (In my view, this error is "correct" and probably adequately clear, because test is in std and our .cargo/config.toml is configured only for build-std = ["core", "compiler_builtins", "alloc"].) At this point I started getting help from madfrog on Discord (can link conversation if helpful), who explained this error and showed me the way around this. This is where things get less "correct".

What I next tried was cargo +nightly test --target x86_64-unknown-linux-gnu. What I understand this to mean is that instead of building the tests for our custom riscv platform (which, in this case, I didn't want anyway— once built I'd have no way to execute them) builds them to run on the laptop where I'm executing the commands. This gave hundreds of errors, all of the form:

error[E0152]: duplicate lang item in crate `core` (which `std` depends on): `sized`.
  |
  = note: the lang item is first defined in crate `core` (which `minibreak` depends on)
  = note: first definition in `core` loaded from /home/mcc/work/r/v/riscv-pocket-app/target/x86_64-unknown-linux-gnu/debug/deps/libcore-37638ee60b32e126.rlib
  = note: second definition in `core` loaded from /home/mcc/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-ec02dd343723da85.rlib

I got one of these for what seems like every symbol in core, including things like Err, fn_once and String. Frog believed that this was my use of no_main/custom entry messing up cargo build-std. As a last thing to try Frog suggested cargo +nightly test --target x86_64-unknown-linux-gnu -Zpanic-abort-tests, which did not make any difference.

I do not know what my "expected behavior" here is, exactly. I am not blocked on anything because running my unit tests through cargo is not a high priority (and I have stopgap options such as pasting the tests into the main() body). But I think my expectation is that "I want to cross-compile to this embedded, no-std platform, but have cargo test run normally on the machine that does the builds" is a reasonable use case, and there should be some way to communicate it to Cargo.

Possible Solution(s)

I was given three possible solutions to my problem on the Discord. Two of them would have actually worked, but I still suspect there is a gap in Cargo functionality here.

  1. Other people have solved this by manually importing std for just the test submodule/ part of the file containing #[test]. I don't know if this would have worked but I am resisting it because my tests should actually not have std in scope. Part of what I am testing is that my code works when run against core only (eg I didn't accidentally rely on something from std).
  2. My code would have worked if instead of irect2.rs being a single file I made it a lib subcrate. As a lib crate, it could have had its own platform targets and the unit tests could have been run independently of the main program. This is a very clean solution but in this case (not counting the tests, irect2.rs is under 100 lines) the overhead is enough I would probably just not bother running the unit tests if this were the only way.
  3. The Discord consensus was everything I specified in my .cargo/config.toml probably could have been specified as command-line flags instead. So I could have thrown out the config.toml, then had one Makefile target that builds for the unusual riscv arch and one Makefile target that builds normally on the build pc. This is a solution I will probably explore at some point, because it would provide other advantages like allowing a single app to build for several platforms (say, our exotic litex platform from one makefile target, and pc or wasm from another). However, it would be nice to be able to solve as much as the problem within Cargo as possible rather than relying on external build tools (especially since Makefile is not cross-platform; the more complexity we put in our Makefile, the harder it will be to someday support building on Windows).

It may be that what I am trying to do (specify a complex custom cross-compilation platform, while also expecting manually-specified amd64 linux target to continue working) is just too weird. If this is the case, I would ask whether the error messages could be improved (getting a duplicate error for every symbol in core is confusing to diagnose).

Notes

No response

Version

cargo 1.76.0-nightly (6790a5127 2023-11-10)
release: 1.76.0-nightly
commit-hash: 6790a5127895debec95c24aefaeb18e059270df3
commit-date: 2023-11-10
host: x86_64-unknown-linux-gnu
libgit2: 1.7.1 (sys:0.18.1 vendored)
libcurl: 8.4.0-DEV (sys:0.4.68+curl-8.4.0 vendored ssl:OpenSSL/1.1.1u)
ssl: OpenSSL 1.1.1u  30 May 2023
os: Ubuntu 23.10 (mantic) [64-bit]
@mcclure mcclure added C-bug Category: bug S-triage Status: This issue is waiting on initial triage. labels Dec 9, 2023
@mcclure
Copy link
Author

mcclure commented Dec 10, 2023

Note, I followed up with madfrog who suggested I link this repo in this issue:

https://gitlab.com/konnorandrews/rust-embedded-tests-demo

In my testing, cargo test with this reproduces the same problem I see with my litex/riscv example, but this example is much simpler and targets a more mainstream embedded target (AVR)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: bug S-triage Status: This issue is waiting on initial triage.
Projects
None yet
Development

No branches or pull requests

1 participant