nuttx-apps icon indicating copy to clipboard operation
nuttx-apps copied to clipboard

examples: New app to build Rust with Cargo

Open no1wudi opened this issue 1 year ago • 1 comments

Build Rust applictions with cargo is the most commn way, and it's more easy to cooporate with Rust ecosystem.

This example shows how to use cargo to build a simple hello world application.

And please notice that you need to install nighly version of rustc to support this feature, any version after https://github.com/rust-lang/rust/pull/127755 is merged, can use NuttX as cargo target directly.

Build

To build hello_rust_cargo application, you can use any target that based on RISCV32IMAC, for example:

cmake -B build -DBOARD_CONFIG=rv-virt:nsh -GNinja .

And disable ARCH_FPU in menuconfig, since the hard coded target triple in this demo is riscv32imac.

TODO

  1. Add support for Rust in CMake based system
  2. Upstream https://github.com/no1wudi/libc to rust-lang/libc
  3. Port libstd of Rust to NuttX, which blocked by 2.

no1wudi avatar Aug 18 '24 06:08 no1wudi

[Experimental Bot, please feedback here]

PR Requirements Review

Meeting Requirements? Partially

Summary:

  • Why? Provides a more common and ecosystem-friendly way to build Rust applications for NuttX.
  • What? Introduces cargo as a build method for Rust applications.
  • How? Leverages nightly Rust versions supporting NuttX as a direct target. Provides a hello world example.
  • Issues/PRs: None linked.

Impact:

  • New Feature: YES (cargo-based build)
  • User Impact: YES (Need nightly Rust, specific target config, potential future changes due to TODOs)
  • Build Impact: YES (Requires cargo, nightly Rust toolchain)
  • Hardware Impact: Potentially, depending on target triple support.
  • Documentation Impact: YES (Required for new build process, Rust support)
  • Security Impact: Unclear, requires further analysis of Rust integration.
  • Compatibility Impact: Potential forward compatibility issues due to reliance on nightly Rust.

Testing:

  • Incomplete: Only mentions target architecture, not specific boards or configurations tested on.
  • Logs: Missing.

Specific Concerns

  • Rust Integration Maturity: Heavy reliance on nightly Rust and upstreaming efforts (libc, libstd) indicates this is an early-stage integration. PR should clearly communicate this.
  • TODOs: The extensive TODO list suggests this is a work in progress. It's important to clarify the scope of this specific PR versus future work.
  • Target Specificity: The PR should clearly document supported target triples and any limitations.
  • Testing: More comprehensive testing is needed across various architectures, boards, and configurations. Provide detailed logs.
  • Documentation: This is crucial for a new build system and language integration. Detailed instructions, configuration options, and limitations should be included.

Recommendation

This PR addresses a desirable feature but is not yet ready for merging in its current state. Address the concerns outlined above to ensure a robust, well-documented, and well-tested addition to NuttX.

nuttxpr avatar Oct 23 '24 02:10 nuttxpr

ping @no1wudi

acassis avatar Nov 11 '24 18:11 acassis

I'll continue this work next week

no1wudi avatar Nov 14 '24 06:11 no1wudi

@no1wudi Thanks for the great job! What command shall I run to build this for QEMU RISC-V 64-bit? Might be good to include some minimal docs for this release thanks :-)

lupyuen avatar Jan 08 '25 09:01 lupyuen

@lupyuen I'll perpare a doc for it soon 😄

no1wudi avatar Jan 08 '25 10:01 no1wudi

Hi @no1wudi,

I'm really looking forward to this PR—great job! I’d be happy to help if you need an extra hand, especially with documentation.

I do have a few questions, and apologies if they’re obvious:

  • Do we use a specific crate to interact with NuttX? For instance, how should we handle NuttX interfaces? I’m asking because I know there’s a crate called rustix for POSIX-like systems, but I’m not sure how it applies here.
  • How far along are you with TODOs # 2 and # 3? Also, could you clarify which parts currently prevent using the NuttX POSIX API?

Thanks again for your hard work. I’m excited to try it out!

lvanasse avatar Jan 08 '25 14:01 lvanasse

@lvanasse Thank you for offering your help. Regarding your questions, here's the current situation:

Do we use a specific crate to interact with NuttX? For instance, how should we handle NuttX interfaces? I’m asking because I know there’s a crate called rustix for POSIX-like systems, but I’m not sure how it applies here.

For most standard system (POSIX) interfaces, the functionality provided by the Rust standard library is sufficient, and the porting work for this part has already been completed on the Rust side. If you need to use NuttX-specific system interfaces, such as SPI/I2C/LCD drivers, you will need to create additional crates for those, as they fall outside the scope of the Rust standard library.

How far along are you with TODOs # 2 and # 3? Also, could you clarify which parts currently prevent using the NuttX POSIX API?

TODO #2 and #3 are now completed. As you can see, the current demo program can utilize the standard library, and even third-party native Rust libraries like serde_json and tokio, which heavily rely on the standard library, are now supported.

I will provide a document as soon as possible to describe how to use the Rust standard library on NuttX. If possible, please help test it out and assist me in refining the related documentation.

no1wudi avatar Jan 09 '25 02:01 no1wudi

@lvanasse @lupyuen Please refer to this document to test it: https://github.com/apache/nuttx/pull/15476 😄

no1wudi avatar Jan 09 '25 02:01 no1wudi

@no1wudi Sorry did I miss something? This is for Ubuntu 24.04.1 LTS x86_64: https://gist.github.com/lupyuen/b81596576e393be0abac2b7687740052

$ rustup toolchain install nightly
$ rustup default nightly
$ git clone https://github.com/no1wudi/nuttx-apps apps --branch rust
$ git clone https://github.com/no1wudi/nuttx --branch build
$ cd nuttx
$ cmake -B build -DBOARD_CONFIG=rv-virt:nsh -GNinja .
$ cmake --build build -t menuconfig
## Enable CONFIG_SYSTEM_TIME64 / CONFIG_FS_LARGEFILE / CONFIG_TLS_NELEM = 16 / CONFIG_DEV_URANDOM / CONFIG_EXAMPLES_HELLO_RUST_CARGO = Y
## Disable CONFIG_ARCH_FPU

$ cmake --build build
error: "/home/luppy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/Cargo.lock" does not exist, unable to build with the standard library, try:
        rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu

$ rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
$ cmake --build build
[4/6] Building Rust crate hello
FAILED: apps/examples/hello_rust_cargo/hello/riscv32imac-unknown-nuttx-elf/release/libhello.a /home/luppy/rust/nuttx/build/apps/examples/hello_rust_cargo/hello/riscv32imac-unknown-nuttx-elf/release/libhello.a 
cd /home/luppy/rust/nuttx/build/apps/examples/hello_rust_cargo && cargo build --release -Zbuild-std=std,panic_abort --manifest-path /home/luppy/rust/apps/examples/hello_rust_cargo/hello/Cargo.toml --target riscv32imac-unknown-nuttx-elf --target-dir /home/luppy/rust/nuttx/build/apps/examples/hello_rust_cargo/hello
   Compiling gimli v0.31.1
   Compiling object v0.36.7
   Compiling addr2line v0.24.2
   Compiling std v0.0.0 (/home/luppy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std)
error[E0308]: mismatched types
    --> /home/luppy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/pal/unix/fs.rs:1047:33
     |
1047 |         unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) }
     |                  -------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `*const u8`, found `*const i8`
     |                  |
     |                  arguments to this function are incorrect
     |
     = note: expected raw pointer `*const u8`
                found raw pointer `*const i8`
note: associated function defined here
    --> /home/luppy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ffi/c_str.rs:276:25
     |
276  |     pub const unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr {
     |                         ^^^^^^^^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `std` (lib) due to 1 previous error
ninja: build stopped: subcommand failed.
$ rustc --version
rustc 1.86.0-nightly (a580b5c37 2025-01-08)

Here's my .config thanks :-) https://gist.github.com/lupyuen/7c646132ce0c81533203d6d6ccdbfb72

lupyuen avatar Jan 09 '25 03:01 lupyuen

@lupyuen I had submitted a fix for this issue: https://github.com/rust-lang/libc/pull/4222

For now, please modify the file, add a force cast locally: /home/luppy/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/pal/unix/fs.rs:1047:33:

    #[cfg(not(any(
        target_os = "android",
        target_os = "linux",
        target_os = "solaris",
        target_os = "illumos",
        target_os = "fuchsia",
        target_os = "redox",
        target_os = "aix",
        target_os = "nto",
        target_os = "vita",
        target_os = "hurd",
    )))]
    fn name_cstr(&self) -> &CStr {
        **unsafe { CStr::from_ptr(self.entry.d_name.as_ptr() as *const u8) }**
    }

no1wudi avatar Jan 09 '25 03:01 no1wudi

Sorry @no1wudi why is hello_rust_cargo missing from the NuttX Image? https://gist.github.com/lupyuen/b81596576e393be0abac2b7687740052#file-gistfile1-txt-L870

$ cmake --build build -t clean
$ grep HELLO build/.config
CONFIG_EXAMPLES_HELLO_RUST_CARGO=y
CONFIG_EXAMPLES_HELLO_RUST_CARGO_PROGNAME="hello_rust_cargo"
CONFIG_EXAMPLES_HELLO_RUST_CARGO_PRIORITY=100
CONFIG_EXAMPLES_HELLO_RUST_CARGO_STACKSIZE=2048

$ cmake --build build
[1281/1284] Building Rust crate hello
    Finished `release` profile [optimized] target(s) in 0.30s
[1283/1284] Linking C executable nuttx
/home/luppy/xpack-riscv-none-elf-gcc-13.2.0-2/bin/../lib/gcc/riscv-none-elf/13.2.0/../../../../riscv-none-elf/bin/ld: warning: nuttx has a LOAD segment with RWX permissions
[1284/1284] Generating System.map

$ qemu-system-riscv32 -semihosting -M virt,aclint=on -cpu rv32 -smp 8 -bios build/nuttx -nographic
ABC
NuttShell (NSH) NuttX-10.0.1
nsh> help
Builtin Apps:
    nsh         sh          getprime    ostest      hello       

nsh> hello_rust_cargo
nsh: hello_rust_cargo: command not found

$ grep hello_rust_cargo build/nuttx.map
LOAD apps/examples/hello_rust_cargo/hello/riscv32imac-unknown-nuttx-elf/release/libhello.a

lupyuen avatar Jan 09 '25 04:01 lupyuen

@lupyuen Please try build with make for now, I'll look into the issue with cmake first.

no1wudi avatar Jan 09 '25 04:01 no1wudi

Sorry @no1wudi why is hello_rust_cargo missing from the NuttX Image? https://gist.github.com/lupyuen/b81596576e393be0abac2b7687740052#file-gistfile1-txt-L870

$ cmake --build build -t clean
$ grep HELLO build/.config
CONFIG_EXAMPLES_HELLO_RUST_CARGO=y
CONFIG_EXAMPLES_HELLO_RUST_CARGO_PROGNAME="hello_rust_cargo"
CONFIG_EXAMPLES_HELLO_RUST_CARGO_PRIORITY=100
CONFIG_EXAMPLES_HELLO_RUST_CARGO_STACKSIZE=2048

$ cmake --build build
[1281/1284] Building Rust crate hello
    Finished `release` profile [optimized] target(s) in 0.30s
[1283/1284] Linking C executable nuttx
/home/luppy/xpack-riscv-none-elf-gcc-13.2.0-2/bin/../lib/gcc/riscv-none-elf/13.2.0/../../../../riscv-none-elf/bin/ld: warning: nuttx has a LOAD segment with RWX permissions
[1284/1284] Generating System.map

$ qemu-system-riscv32 -semihosting -M virt,aclint=on -cpu rv32 -smp 8 -bios build/nuttx -nographic
ABC
NuttShell (NSH) NuttX-10.0.1
nsh> help
Builtin Apps:
    nsh         sh          getprime    ostest      hello       

nsh> hello_rust_cargo
nsh: hello_rust_cargo: command not found

$ grep hello_rust_cargo build/nuttx.map
LOAD apps/examples/hello_rust_cargo/hello/riscv32imac-unknown-nuttx-elf/release/libhello.a

@xuxin930 We must set a fake source (https://github.com/apache/nuttx-apps/pull/2487/files#diff-9032963d33348a63e0ddf876c0f994b71375b5c62c5938f05a69466a8bdb464b) for nuttx_add_application then it can create a app in nsh correctly:

https://github.com/apache/nuttx/blob/cb980cc9776f865b88268936a3a1d1fc525eddac/cmake/nuttx_add_application.cmake#L98-L105

Do you have any suggestions?

no1wudi avatar Jan 09 '25 04:01 no1wudi

@lupyuen Rust build with cmake need this PR: https://github.com/apache/nuttx-apps/pull/2478, but it reverted: https://github.com/apache/nuttx-apps/pull/2480

Let's wait it get merged again.

no1wudi avatar Jan 09 '25 07:01 no1wudi

Sorry @no1wudi why is hello_rust_cargo missing from the NuttX Image? https://gist.github.com/lupyuen/b81596576e393be0abac2b7687740052#file-gistfile1-txt-L870

$ cmake --build build -t clean
$ grep HELLO build/.config
CONFIG_EXAMPLES_HELLO_RUST_CARGO=y
CONFIG_EXAMPLES_HELLO_RUST_CARGO_PROGNAME="hello_rust_cargo"
CONFIG_EXAMPLES_HELLO_RUST_CARGO_PRIORITY=100
CONFIG_EXAMPLES_HELLO_RUST_CARGO_STACKSIZE=2048

$ cmake --build build
[1281/1284] Building Rust crate hello
    Finished `release` profile [optimized] target(s) in 0.30s
[1283/1284] Linking C executable nuttx
/home/luppy/xpack-riscv-none-elf-gcc-13.2.0-2/bin/../lib/gcc/riscv-none-elf/13.2.0/../../../../riscv-none-elf/bin/ld: warning: nuttx has a LOAD segment with RWX permissions
[1284/1284] Generating System.map

$ qemu-system-riscv32 -semihosting -M virt,aclint=on -cpu rv32 -smp 8 -bios build/nuttx -nographic
ABC
NuttShell (NSH) NuttX-10.0.1
nsh> help
Builtin Apps:
    nsh         sh          getprime    ostest      hello       

nsh> hello_rust_cargo
nsh: hello_rust_cargo: command not found

$ grep hello_rust_cargo build/nuttx.map
LOAD apps/examples/hello_rust_cargo/hello/riscv32imac-unknown-nuttx-elf/release/libhello.a

@xuxin930 We must set a fake source (https://github.com/apache/nuttx-apps/pull/2487/files#diff-9032963d33348a63e0ddf876c0f994b71375b5c62c5938f05a69466a8bdb464b) for nuttx_add_application then it can create a app in nsh correctly:

https://github.com/apache/nuttx/blob/cb980cc9776f865b88268936a3a1d1fc525eddac/cmake/nuttx_add_application.cmake#L98-L105

Do you have any suggestions?

@no1wudi let's try this https://github.com/apache/nuttx-apps/pull/2934 allow application only do register

xuxin930 avatar Jan 09 '25 12:01 xuxin930

@no1wudi let's try this #2934 allow application only do register

Yes, it works!

no1wudi avatar Jan 09 '25 12:01 no1wudi

If you need to use NuttX-specific system interfaces, such as SPI/I2C/LCD drivers, you will need to create additional crates for those

@no1wudi would you suggest use your https://github.com/no1wudi/nuttx.rs repo? Or should it be the NuttX project to have a nuttx-rs repository?

I'm also seeing @lupyuen crate: https://crates.io/crates/nuttx-embedded-hal, could we utilise that?

lvanasse avatar Jan 09 '25 14:01 lvanasse

@lvanasse nuttx-embedded-hal is a very old crate, we might need to revamp it :-)

lupyuen avatar Jan 09 '25 14:01 lupyuen

@lvanasse nuttx-embedded-hal is a very old crate, we might need to revamp it :-)

That's fine :). Just wanted to know what would be the way forward for having the NuttX API in Rust.

@no1wudi I'll try the documentation this week-end :), I'm a bit caught up with work.

lvanasse avatar Jan 09 '25 14:01 lvanasse

@no1wudi would you suggest use your https://github.com/no1wudi/nuttx.rs repo? Or should it be the NuttX project to have a nuttx-rs repository?

This repository is quite old and was merely an attempt to implement APIs similar to those in Rust's standard library. Now that we have the actual Rust std library, there is no longer a need for it.

Maybe we should create a new crate or continue nuttx-embedded-hal to expose the native NuttX API or provide an intermediary layer to integrate with the existing embedded Rust ecosystem.

no1wudi avatar Jan 09 '25 14:01 no1wudi

Actually I'm not too clear if we should stick to the Official Rust Embedded HAL for NuttX. Here's why...

To blink an LED in Rust Embedded HAL: We fetch the GPIO, then switch it off and on: https://docs.rust-embedded.org/discovery/microbit/05-led-roulette/light-it-up.html

But there's no need to do this in NuttX! We simply open the LED Device /dev/userleds. And control it via ioctl: https://lupyuen.github.io/articles/rust6#blink-the-led

Embedded HAL feels strange when we force-fit it into NuttX. I think I2C will have similar issues: https://lupyuen.github.io/articles/rusti2c#nuttx-embedded-hal

If we look at Rust on Zephyr: They are proposing to use a Native Zephyr API to blink the LED https://github.com/zephyrproject-rtos/zephyr/issues/65837

#![no_std]
#![no_main]

#[zephyr::entry]
fn main() {
    let dt = zephyr::devicetree::take().unwrap();
    let led = dt.chosen.led0;
    loop {
        led.toggle().unwrap();
        zephyr::sys::sleep(1000);
    }
}

My gut feel is that we should do it the Zephyr way. But make it POSIX-like. Thus we should drop nuttx-embedded-hal altogether :-)

Update: These look interesting...

  • https://github.com/zephyrproject-rtos/zephyr/issues/75900
  • https://github.com/zephyrproject-rtos/zephyr/pull/76199

lupyuen avatar Jan 09 '25 15:01 lupyuen

Actually I'm not too clear if we should stick to the Official Rust Embedded HAL for NuttX. Here's why...

To blink an LED in Rust Embedded HAL: We fetch the GPIO, then switch it off and on: https://docs.rust-embedded.org/discovery/microbit/05-led-roulette/light-it-up.html

But there's no need to do this in NuttX! We simply open the LED Device /dev/userleds. And control it via ioctl: https://lupyuen.github.io/articles/rust6#blink-the-led

Embedded HAL feels strange when we force-fit it into NuttX. I think I2C will have similar issues: https://lupyuen.github.io/articles/rusti2c#nuttx-embedded-hal

If we look at Rust on Zephyr: They are proposing to use a Native Zephyr API to blink the LED zephyrproject-rtos/zephyr#65837

#![no_std]
#![no_main]

#[zephyr::entry]
fn main() {
    let dt = zephyr::devicetree::take().unwrap();
    let led = dt.chosen.led0;
    loop {
        led.toggle().unwrap();
        zephyr::sys::sleep(1000);
    }
}

My gut feel is that we should do it the Zephyr way. But make it POSIX-like. Thus we should drop nuttx-embedded-hal altogether :-)

Thank you @lupyuen for the great research as always! What is your opinion on the idea of restarting the development of the NuttX API in the rustix crate? Would that make sense?

And if not, we can always create our own nuttx-rs crate, maybe based on @no1wudi repo :)?

Edit: And I agree the rust embedded hal is a bit awkward when it comes to supporting POSIX systems. I feel it is more meant to be used in bare-metal application.

lvanasse avatar Jan 09 '25 15:01 lvanasse

My gut feel is that we should do it the Zephyr way. But make it POSIX-like. Thus we should drop nuttx-embedded-hal altogether :-)

Thanks for your information, personally, I also lean towards providing NuttX APIs for Rust programs in a manner similar to Zephyr. This approach allows for full utilization of existing implementations and is based on POSIX-like APIs (entirely file operation-based).

no1wudi avatar Jan 09 '25 15:01 no1wudi

What is your opinion on the idea of restarting the development of the NuttX API in the rustix crate?

@lvanasse rustix is kinda heavy I think? It does Managed File Descriptors that will auto-close when they go out of scope. There might be a lot of dependencies inside?

I wrote about it here: https://lupyuen.github.io/articles/rust6#handle-errors-safely

Can we auto-close the File Descriptor when it goes out of scope? Probably, if we do Managed File Descriptors. But that's way beyond the size, scope and scale of GSoC.

lupyuen avatar Jan 09 '25 15:01 lupyuen

What is your opinion on the idea of restarting the development of the NuttX API in the rustix crate?

@lvanasse rustix is kinda heavy I think? It does Managed File Descriptors that will auto-close when they go out of scope. There might be a lot of dependencies inside?

I wrote about it here: https://lupyuen.github.io/articles/rust6#handle-errors-safely

Can we auto-close the File Descriptor when it goes out of scope? Probably, if we do Managed File Descriptors. But that's way beyond the size, scope and scale of GSoC.

Sounds good, I'll follow your lead then :) I'll happyly help out where I can for this.

lvanasse avatar Jan 09 '25 15:01 lvanasse

Sorry I have other questions @no1wudi, what if we want to use cargo test to run test on the program? Would it be at runtime that we maybe instead launch the test?

And what about the development experience? When you write your code for the Rust application do you have access to the linter and etc?

It's OK if that's not possible in either case. I can of want to understand if there's limitation and what we can improve :)?

lvanasse avatar Jan 09 '25 16:01 lvanasse

@lvanasse

Sorry I have other questions @no1wudi, what if we want to use cargo test to run test on the program? Would it be at runtime that we maybe instead launch the test?

If your application solely utilizes APIs from the Rust standard library, executing cargo test will run the tests on your local PC. However, as of now, there is no automated way to conduct these tests directly on the development board. This capability requires further development and engineering efforts to achieve.

And what about the development experience? When you write your code for the Rust application do you have access to the linter and etc?

I guess the development experience is akin to that of regular Rust programs on a PC, as we are developing against the standard library. It's possible that we may introduce an additional crate in the future to provide interfaces for NuttX. Even if issues related to target settings arise, they can be resolved by adjusting the parameters of the linter, especially since support for NuttX has now been integrated into rustc itself.

no1wudi avatar Jan 10 '25 01:01 no1wudi

However, as of now, there is no automated way to conduct these tests directly on the development board

Sounds like a fun challenge :-)

lupyuen avatar Jan 10 '25 01:01 lupyuen

However, as of now, there is no automated way to conduct these tests directly on the development board

Sounds like a fun challenge :-)

I think it's possible to support cargo test on NuttX, then we can run full test from Rust itself to ensure the quality of Rust std porting

no1wudi avatar Jan 10 '25 01:01 no1wudi

Hi @no1wudi: I'm writing an article to introduce Rust Std Library on NuttX. Today rv-virt:nsh works fine, would you be supporting nsh64 and knsh64?

rv-virt:nsh64 crashes with a Load Access Fault: https://gist.github.com/lupyuen/fba254be9ef64d3856c52e1a14a1b9ec#file-gistfile1-txt-L476

nsh> hello_rust_cargo
{"name":"John","age":30}
{"name":"Jane","age":25}
Deserialized: Alice is 28 years old
Pretty JSON:
{
  "name": "Alice",
  "age": 28
}
�riscv_exception: EXCEPTION: Load access fault. MCAUSE: 0000000000000005, EPC: 0000000080013cc4, MTVAL: 0000000010000008

rv-virt:knsh64 fails at make import with Missing Target. Thanks :-) https://gist.github.com/lupyuen/cc4ec45a9d1342a1b367d792284e65fe#file-gistfile1-txt-L178

$ make -j import
cargo build --release -Zbuild-std=std,panic_abort --manifest-path /home/luppy/rust/apps/examples/rust/hello/Cargo.toml --target 
error: "--target" takes a target architecture as an argument.

lupyuen avatar Jan 20 '25 01:01 lupyuen