1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #16274 from chrysn-pull-requests/rust-application

Add some Rust building infrastructure and example
This commit is contained in:
chrysn 2021-12-16 16:17:28 +01:00 committed by GitHub
commit afdabcf9b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 614 additions and 1 deletions

1
.gitattributes vendored
View File

@ -1,5 +1,6 @@
*.a binary
*.patch binary
Cargo.lock binary
# Default conflict marker size is 7 which causes some of the headings in
# release-notes.txt to trigger git diff --check: 'leftover conflict marker'
# when the heading is exactly 7 characters long.

View File

@ -216,6 +216,8 @@ ifeq (,$(TOOLCHAIN))
override TOOLCHAIN := gnu
endif
include $(RIOTMAKE)/cargo-settings.inc.mk
GLOBAL_GOALS += buildtest \
buildtest-indocker \
info-boards-features-blacklisted \
@ -1033,6 +1035,8 @@ endif
endif
include $(RIOTMAKE)/cargo-targets.inc.mk
# include RIOT_MAKEFILES_GLOBAL_POST configuration files
# allows setting user specific system wide configuration parsed after the body
# of $(RIOTBASE)/Makefile.include

View File

@ -66,37 +66,44 @@ config CPU_CORE_CORTEX_M0
bool
select CPU_ARCH_ARMV6M
select CPU_CORE_CORTEX_M
select HAS_RUST_TARGET
config CPU_CORE_CORTEX_M0PLUS
bool
select CPU_ARCH_ARMV6M
select CPU_CORE_CORTEX_M
select HAS_RUST_TARGET
config CPU_CORE_CORTEX_M23
bool
select CPU_ARCH_ARMV8M
select CPU_CORE_CORTEX_M
#select HAS_RUST_TARGET
config CPU_CORE_CORTEX_M3
bool
select CPU_ARCH_ARMV7M
select CPU_CORE_CORTEX_M
select HAS_RUST_TARGET
config CPU_CORE_CORTEX_M33
bool
select CPU_ARCH_ARMV8M
select CPU_CORE_CORTEX_M
#select HAS_RUST_TARGET
config CPU_CORE_CORTEX_M4
bool
select CPU_ARCH_ARMV7M
select CPU_CORE_CORTEX_M
select HAS_RUST_TARGET
config CPU_CORE_CORTEX_M4F
bool
select CPU_ARCH_ARMV7M
select CPU_CORE_CORTEX_M
select HAS_CORTEXM_FPU
select HAS_RUST_TARGET
config CPU_CORE_CORTEX_M7
bool

View File

@ -18,24 +18,38 @@ ifneq (,$(filter $(CPU_CORE),cortex-m4f cortex-m7))
endif
# Set CPU_ARCH depending on the CPU_CORE
#
# RUST_TARGET is only used when building Rust code; any users need to require
# the `rust_target` feature to esnure things are checked properly.
ifeq ($(CPU_CORE),cortex-m0)
CPU_ARCH := armv6m
RUST_TARGET = thumbv6m-none-eabi
else ifeq ($(CPU_CORE),cortex-m0plus)
CPU_ARCH := armv6m
RUST_TARGET = thumbv6m-none-eabi
else ifeq ($(CPU_CORE),cortex-m23)
CPU_ARCH := armv8m
#RUST_TARGET = thumbv8m.base-none-eabi
else ifeq ($(CPU_CORE),cortex-m3)
CPU_ARCH := armv7m
RUST_TARGET = thumbv7m-none-eabi
else ifeq ($(CPU_CORE),cortex-m33)
CPU_ARCH := armv8m
#RUST_TARGET = thumbv8m.main-none-eabi
else ifeq ($(CPU_CORE),cortex-m4)
CPU_ARCH := armv7m
RUST_TARGET = thumbv7em-none-eabi
else ifeq ($(CPU_CORE),cortex-m4f)
CPU_ARCH := armv7m
RUST_TARGET = thumbv7em-none-eabihf
else ifeq ($(CPU_CORE),cortex-m7)
CPU_ARCH := armv7m
else
$(error Unkwnown cortexm core: $(CPU_CORE))
endif
ifneq (,$(RUST_TARGET))
FEATURES_PROVIDED += rust_target
endif
FEATURES_PROVIDED += no_idle_thread

View File

@ -62,6 +62,7 @@ config NATIVE_OS_LINUX
select HAS_PERIPH_GPIO
select HAS_PERIPH_GPIO_IRQ
select HAS_PERIPH_SPI
select HAS_RUST_TARGET if "$(OS_ARCH)" = "x86_64"
config NATIVE_OS_FREEBSD
bool

View File

@ -18,6 +18,9 @@ FEATURES_PROVIDED += periph_hwrng
FEATURES_PROVIDED += periph_pm
FEATURES_PROVIDED += periph_pwm
FEATURES_PROVIDED += periph_timer_periodic
ifeq ($(OS) $(OS_ARCH),Linux x86_64)
FEATURES_PROVIDED += rust_target
endif
FEATURES_PROVIDED += ssp
ifeq ($(OS),Linux)

View File

@ -10,3 +10,8 @@ ifneq (,$(filter periph_can,$(USEMODULE)))
endif
TOOLCHAINS_SUPPORTED = gnu llvm afl
# Platform triple as used by Rust
ifeq ($(OS) $(OS_ARCH),Linux x86_64)
RUST_TARGET = i686-unknown-linux-gnu
endif

View File

@ -12,6 +12,7 @@ config CPU_ARCH_RISCV
select HAS_NEWLIB
select HAS_PERIPH_CORETIMER
select HAS_PICOLIBC if '$(RIOT_CI_BUILD)' != '1'
#select HAS_RUST_TARGET
select HAS_SSP
select MODULE_MALLOC_THREAD_SAFE if TEST_KCONFIG

View File

@ -8,6 +8,7 @@ FEATURES_PROVIDED += cpp
FEATURES_PROVIDED += libstdcpp
FEATURES_PROVIDED += newlib
FEATURES_PROVIDED += periph_coretimer
#FEATURES_PROVIDED += rust_target
FEATURES_PROVIDED += ssp
# RISC-V toolchain on CI does not work properly with picolibc yet

View File

@ -113,3 +113,6 @@ filp
# Ether (Scapy class name and means Ethernet in some parts of the code) => Either
ether
# crate (Rust's package format) => create
crate

View File

@ -191,6 +191,12 @@ def write_compile_command(state, compiler, src, flags, cdetails, path):
arguments = [compiler, '-DRIOT_FILE_RELATIVE="' + os.path.join(cdetails.dir, src) + '"',
'-DRIOT_FILE_NOPATH="' + src + '"']
arguments += flags
if '-c' in arguments:
# bindgen is unhappy with multiple -c (that would be created by the -c
# added later) and even with the -c showing up anywhere between other
# arguments.
assert arguments.count('-c') == 1, "Spurious duplicate -c arguments"
arguments.remove('-c')
arguments += ['-MQ', obj, '-MD', '-MP', '-c', '-o', obj, src]
entry = {
'arguments': arguments,
@ -288,5 +294,8 @@ if __name__ == '__main__':
if _args.clangd:
_args.add_built_in_includes = True
_args.add_libstdcxx_includes = True
_args.filter_out = ['-Wformat-truncation', '-Wformat-overflow', '-mno-thumb-interwork']
_args.filter_out = ['-Wformat-truncation', '-Wformat-overflow', '-mno-thumb-interwork',
# Only even included for versions of GCC that support it
'-malign-data=natural',
]
generate_compile_commands(_args)

BIN
examples/rust-gcoap/Cargo.lock generated Normal file

Binary file not shown.

View File

@ -0,0 +1,16 @@
[package]
name = "rust-gcoap"
version = "0.1.0"
authors = ["Christian Amsüss <chrysn@fsfe.org>"]
edition = "2018"
resolver = "2"
[lib]
crate-type = ["staticlib"]
[dependencies]
riot-wrappers = { version = "^0.7", features = [ "with_coap_message", "with_coap_handler" ] }
coap-message-demos = { git = "https://gitlab.com/chrysn/coap-message-demos/", default-features = false }
coap-handler-implementations = "0.1"
riot-coap-handler-demos = { git = "https://gitlab.com/etonomy/riot-module-examples/", features = [ "vfs" ] }

View File

@ -0,0 +1,39 @@
# name of your application
APPLICATION = rust_gcoap
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Basic networking, and gcoap
USEMODULE += gcoap
USEMODULE += netdev_default
USEMODULE += auto_init_gnrc_netif
USEMODULE += gnrc_ipv6_default
USEMODULE += gnrc_icmpv6_echo
USEMODULE += ztimer
USEMODULE += ztimer_usec
USEMODULE += ztimer_msec
USEMODULE += ztimer_sec
USEMODULE += vfs
USEMODULE += constfs
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
# The name of crate (as per Cargo.toml package name, but with '-' replaced with '_')
APPLICATION_RUST_MODULE = rust_gcoap
BASELIBS += $(APPLICATION_RUST_MODULE).module
FEATURES_REQUIRED += rust_target
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,25 @@
BOARD_INSUFFICIENT_MEMORY := \
blackpill \
bluepill \
bluepill-stm32f030c8 \
i-nucleo-lrwan1 \
nucleo-f030r8 \
nucleo-f031k6 \
nucleo-f042k6 \
nucleo-f302r8 \
nucleo-f303k8 \
nucleo-f334r8 \
nucleo-l011k4 \
nucleo-l031k6 \
nucleo-l053r8 \
samd10-xmini \
saml10-xpro \
saml11-xpro \
slstk3400a \
stk3200 \
stm32f030f4-demo \
stm32f0discovery \
stm32g0316-disco \
stm32l0538-disco \
stm32mp157c-dk2 \
#

View File

@ -0,0 +1,53 @@
gcoap used with Rust
====================
This is the advanced Rust example; see ../rust-hello-world/ for the basics.
In extension to the basic example, it shows:
* C code can be mixed with Rust code easily; any C file is built and linked
as in applications without Rust.
While it's technically possible to have header files for that code,
it is easier (and likewise often done in C applications)
to just translate the entry function's signature manually,
as is done with the `do_vfs_init()` function.
* Code of Rust applications can be spread out into modules,
even if it builds on RIOT components.
The CoAP handler built in the main function
combines generic CoAP components (from `coap_message_demos`)
with RIOT specific components (from `riot-coap-handler-demos`).
* Many features of RIOT are exposed to Rust through the riot-wrappers crate,
which provides safe wrappers around RIOT structures.
In this example, the abovementioned CoAP handler is run on the gcoap server,
for which the wrappers provide adaptation to the platform independent handler interface.
Then, ztimer is used to sleep until the network interfaces are expected to be ready.
Finally, the available network interfaces are iterated over
and queried for their IP addresses,
which makes it easier (in absence of an interactive shell) to find which address CoAP requests can be directed at.
How to use
----------
```
$ make all flash term
[...]
main(): This is RIOT! (Version: 2022.01-devel-560-g7f8ed-rust-application)
constfs mounted successfully
CoAP server ready; waiting for interfaces to settle before reporting addresses...
Active interface from PID KernelPID(6) ("gnrc_netdev_tap")
Address fe80:0000:0000:0000:1234:56ff:fe78:90ab
Address 2a02:0b18:c13b:8018:1234:56ff:fe78:90ab
```
Once that is ready, in a parallel shell, run:
```
$ aiocoap-client 'coap://[2a02:0b18:c13b:8018:1234:56ff:fe78:90ab]/.well-known/core'
```

View File

@ -0,0 +1,57 @@
// Copyright (C) 2020 Christian Amsüss
//
// This file is subject to the terms and conditions of the GNU Lesser
// General Public License v2.1. See the file LICENSE in the top level
// directory for more details.
#![no_std]
use riot_wrappers::{riot_main, println};
use riot_wrappers::{gcoap, thread, ztimer, gnrc};
use coap_handler_implementations::{ReportingHandlerBuilder, HandlerBuilder};
riot_main!(main);
fn main() {
extern "C" {
fn do_vfs_init();
}
unsafe { do_vfs_init() };
let handler = coap_message_demos::full_application_tree(None)
.below(&["ps"], riot_coap_handler_demos::ps::ps_tree())
.below(&["vfs"], riot_coap_handler_demos::vfs::vfs("/const"))
.with_wkc()
;
let mut handler = riot_wrappers::coap_handler::GcoapHandler(handler);
let mut listener = gcoap::SingleHandlerListener::new_catch_all(&mut handler);
gcoap::scope(|greg| {
greg.register(&mut listener);
println!("CoAP server ready; waiting for interfaces to settle before reporting addresses...");
let sectimer = ztimer::ZTimer::sec();
sectimer.sleep_ticks(2);
for netif in gnrc::Netif::all() {
println!("Active interface from PID {:?} ({:?})", netif.pid(), netif.pid().get_name().unwrap_or("unnamed"));
match netif.ipv6_addrs() {
Ok(addrs) => {
for a in addrs.addresses() {
println!(" Address {:?}", a);
}
}
_ => {
println!(" Does not support IPv6.");
}
}
}
// Sending main thread to sleep; can't return or the Gcoap handler would need to be
// deregistered (which it can't).
loop { thread::sleep(); }
})
}

72
examples/rust-gcoap/vfs.c Normal file
View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 OTA keys S.A.
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* This file demonstrates how C code can be mixed with Rust code in an
* application in an ad-hoc fashion.
*/
#include <vfs.h>
#include "fs/constfs.h"
#include <stdio.h>
#define HELLO_WORLD_CONTENT "Hello World!\n"
#define HELLO_RIOT_CONTENT "Hello RIOT!\n"
#define LARGE "1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n" \
"1234567890---------\n"
static constfs_file_t constfs_files[] = {
{
.path = "/hello-world",
.size = sizeof(HELLO_WORLD_CONTENT),
.data = (const uint8_t *)HELLO_WORLD_CONTENT,
},
{
.path = "/hello-riot",
.size = sizeof(HELLO_RIOT_CONTENT),
.data = (const uint8_t *)HELLO_RIOT_CONTENT,
},
{
.path = "/large",
.size = sizeof(LARGE),
.data = (const uint8_t *)LARGE,
}
};
static constfs_t constfs_desc = {
.nfiles = ARRAY_SIZE(constfs_files),
.files = constfs_files,
};
static vfs_mount_t const_mount = {
.fs = &constfs_file_system,
.mount_point = "/const",
.private_data = &constfs_desc,
};
void do_vfs_init(void) {
int res = vfs_mount(&const_mount);
if (res < 0) {
puts("Error while mounting constfs");
}
else {
puts("constfs mounted successfully");
}
}

BIN
examples/rust-hello-world/Cargo.lock generated Normal file

Binary file not shown.

View File

@ -0,0 +1,12 @@
[package]
name = "rust-hello-world"
version = "0.1.0"
authors = ["Christian Amsüss <chrysn@fsfe.org>"]
edition = "2018"
resolver = "2"
[lib]
crate-type = ["staticlib"]
[dependencies]
riot-wrappers = "0.7"

View File

@ -0,0 +1,24 @@
# name of your application
APPLICATION = hello-world
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
# The name of crate (as per Cargo.toml package name, but with '-' replaced with '_')
APPLICATION_RUST_MODULE = rust_hello_world
BASELIBS += $(APPLICATION_RUST_MODULE).module
FEATURES_REQUIRED += rust_target
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,24 @@
Hello World!
============
This is a basic example how to use Rust to write your RIOT application.
It prints out the famous text `Hello World!`.
This example should foremost give you an overview how an application built
completely in Rust is structured:
* The Makefile resembles the regular application Makefile, see ../hello-world/
for more introduction to that.
* The Cargo.toml file describes the Rust code, and declares its dependencies.
Prominently, it contains a `[lib]` / `crate-type = ["staticlib"]` section,
which is necessary for how RIOT later links together the C and Rust portions.
* The file src/lib.rs (and any modules referenced by it) contain Rust code to
be run.
It uses the `riot_main!` macro provided by the riot-wrappers crate to declare
the entry point of the program.
The code itself looks like the usual Rust hello-world example.

View File

@ -0,0 +1,15 @@
// Copyright (C) 2020 Christian Amsüss
//
// This file is subject to the terms and conditions of the GNU Lesser
// General Public License v2.1. See the file LICENSE in the top level
// directory for more details.
#![no_std]
use riot_wrappers::riot_main;
use riot_wrappers::println;
riot_main!(main);
fn main() {
println!("Hello Rust!");
}

View File

@ -42,6 +42,11 @@ config HAS_BACKUP_RAM
help
Indicates that Backup RAM is supported.
config HAS_RUST_TARGET
bool
help
Indicates that a Rust target definition ("triple") is known.
config HAS_CPP
bool
help

View File

@ -54,3 +54,8 @@ CFLAGS += $(CFLAGS_CPU) $(CFLAGS_DBG) $(CFLAGS_OPT) $(CFLAGS_LINK)
ASFLAGS += $(CFLAGS_CPU) $(CFLAGS_DBG)
# export linker flags
LINKFLAGS += $(CFLAGS_CPU) $(CFLAGS_LINK) $(CFLAGS_DBG) $(CFLAGS_OPT) -nostartfiles -Wl,--gc-sections -static -lgcc
# Platform triple as used by Rust
RUST_TARGET = riscv32imac-unknown-none-elf
# Workaround for https://github.com/rust-lang/rust-bindgen/issues/1555
CARGO_EXTRACFLAGS += --target=riscv32

View File

@ -0,0 +1,43 @@
# Rust's own version of the target triple / quadruple.
#
# This does not have a sane default, and needs to be set in the architecture
# files.
# RUST_TARGET = ...
# Flags that need to be added to the RIOT_CFLAGS passed to cargo in order to
# make bindgen happy
CARGO_EXTRACFLAGS ?=
# Setting anything other than "debug" or "release" will necessitate additional
# -Z unstable-options as of 2021-03 nightlies.
CARGO_PROFILE ?= release
# The Rust version to use.
#
# As long as C2Rust and riot-wrappers require nightly, the only alternative
# here is to pick a particular nightly when something breaks.
#
# (Default is empty, because the riotbuild container picks a particular nightly
# and sets it as a default; users without a nightly default need to either
# override this here or in rustup)
CARGO_CHANNEL ?=
# Note that if we did not set this explicitly, CARGO_LIB would have to
# understand which value cargo uses in absence of CARGO_TARGET_DIR, which would
# be $(APPDIR)/target.
#
# For many cases, it would be beneficial to base this on BINDIRBASE rather than
# BINDIR, for that would allow different boards using the same CPU to share
# compiled code (unless they they build conditionally on environment variables,
# like riot-sys does). This is not done for two reasons:
#
# * Overriding BINDIR (like is done in Murdock) would not take effect,
# requiring additional overrides to enable out-of-tree building.
#
# * Switching back and forth between two boards of the same CPU requires
# riot-sys rebuilds. (On its own, this would be outweighed by the shared
# compilation of other modules).
CARGO_TARGET_DIR = $(BINDIR)/target
# The single Rust library to be built.
CARGO_LIB = $(CARGO_TARGET_DIR)/$(RUST_TARGET)/${CARGO_PROFILE}/lib$(APPLICATION_RUST_MODULE).a

View File

@ -0,0 +1,34 @@
CARGO_COMPILE_COMMANDS = $(BINDIR)/cargo-compile-commands.json
CARGO_COMPILE_COMMANDS_FLAGS = --clang
# This is duplicating the compile-commands rule because unlike in the use case
# when a $(RIOTBASE)/compile_commands.json is built, we *want* this to be
# per-board and per-application. (The large mechanisms are shared anyway).
#
# Changes relative to the compile-commands rule: This uses lazysponge to keep
# Rust from rebuilding, and uses a custom output file and
# CARGO_COMPILE_COMMAND_FLAGS.
$(CARGO_COMPILE_COMMANDS): $(BUILDDEPS)
$(Q)DIRS="$(DIRS)" APPLICATION_BLOBS="$(BLOBS)" \
"$(MAKE)" -C $(APPDIR) -f $(RIOTMAKE)/application.inc.mk compile-commands
$(Q)$(RIOTTOOLS)/compile_commands/compile_commands.py $(CARGO_COMPILE_COMMANDS_FLAGS) $(BINDIR) \
| $(LAZYSPONGE) $@
$(CARGO_LIB): $(RIOTBUILD_CONFIG_HEADER_C) $(BUILDDEPS) $(CARGO_COMPILE_COMMANDS) FORCE
$(Q)[ x"${RUST_TARGET}" != x"" ] || (echo "Error: No RUST_TARGET was set for this platform. (Set FEATURES_REQUIRED+=rust_target to catch this earlier)."; exit 1)
$(Q)CC= CFLAGS= CPPFLAGS= CXXFLAGS= RIOT_COMPILE_COMMANDS_JSON="$(CARGO_COMPILE_COMMANDS)" RIOT_USEMODULE="$(USEMODULE)" cargo $(patsubst +,,+${CARGO_CHANNEL}) build --target $(RUST_TARGET) `if [ x$(CARGO_PROFILE) = xrelease ]; then echo --release; else if [ x$(CARGO_PROFILE) '!=' xdebug ]; then echo "--profile $(CARGO_PROFILE)"; fi; fi` $(CARGO_OPTIONS)
$(APPLICATION_RUST_MODULE).module: $(CARGO_LIB) FORCE
$(Q)# Ensure no old object files persist. These would lead to duplicate
$(Q)# symbols, or worse, lingering behaivor of XFA entries.
$(Q)rm -rf $(BINDIR)/$(APPLICATION_RUST_MODULE)/
$(Q)mkdir -p $(BINDIR)/$(APPLICATION_RUST_MODULE)/
$(Q)# On cortex-m0 boards like airfy-beacon, the archive contains a
$(Q)# bin/thumbv6m-none-eabi.o file; the directory must be present for
$(Q)# ar to unpack it...
$(Q)mkdir -p $(BINDIR)/$(APPLICATION_RUST_MODULE)/bin/
$(Q)cd $(BINDIR)/$(APPLICATION_RUST_MODULE)/ && $(AR) x $<
$(Q)# ... and move them back if any exist, careful to err if anything is duplicate
$(Q)rmdir $(BINDIR)/$(APPLICATION_RUST_MODULE)/bin/ || (mv -n $(BINDIR)/$(APPLICATION_RUST_MODULE)/bin/* $(BINDIR)/$(APPLICATION_RUST_MODULE)/ && rmdir $(BINDIR)/$(APPLICATION_RUST_MODULE)/bin/)

View File

@ -45,6 +45,7 @@ define board_unsatisfied_features
undefine CPU_ARCH
undefine CPU_CORE
undefine CPU_FAM
undefine RUST_TARGET
include $(RIOTBASE)/Makefile.features
# always select provided architecture features

View File

@ -6,6 +6,7 @@ export QQ # as Q, but be more quiet
export QUIET # The parameter to use whether to show verbose makefile commands or not.
export OS # The operating system of the build host
export OS_ARCH # The build host's hardware architecture
export APPLICATION # The application, set in the Makefile which is run by the user.
export APPLICATION_MODULE # The application module name.
@ -41,6 +42,7 @@ export RIOTMAKE # Location of all supplemental Makefiles (such as t
export RIOTKCONFIG # Location of all supplemental Kconfig files
export BINDIRBASE # This is the folder where the application should be built in. For each BOARD a different subfolder is used.
export BINDIR # This is the folder where the application should be built in.
export CARGO_TARGET_DIR # This is the folder where Rust parts of the application should be built in.
export BUILD_DIR # This is the base folder to store common build files and artifacts, e.g. test results.
export APPDIR # The base folder containing the application
export PKGDIRBASE # The base folder for building packages

BIN
tests/rust_minimal/Cargo.lock generated Normal file

Binary file not shown.

View File

@ -0,0 +1,12 @@
[package]
name = "rust-minimal"
version = "0.1.0"
authors = ["Christian Amsüss <chrysn@fsfe.org>"]
edition = "2018"
resolver = "2"
[lib]
crate-type = ["staticlib"]
[dependencies]
riot-wrappers = "0.7"

View File

@ -0,0 +1,8 @@
include ../Makefile.tests_common
APPLICATION_RUST_MODULE = rust_minimal
BASELIBS += $(APPLICATION_RUST_MODULE).module
FEATURES_REQUIRED += rust_target
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,82 @@
Test description
----------------
This test ensures that code produced through Rust can actually run;
it does that by simply printing "SUCCESS".
When things fail here
---------------------
This section is aimed at RIOT developers who are taken aback by this test failing
after they changed something completely unrelated.
That can happen for two reasons:
For one, RIOT's API is not as strict as library APIs
(e.g. it is accepted that functions move between being `static inline` and not being),
and then, Rust is sometimes stricter than C when it comes to APIs
(e.g. no implicit numeric casting).
The general strategy for addressing build failures resulting from this is:
* Identify what breaks.
* Update the `riot-sys` and `riot-wrappers` crates as necessary
to support both the old and the new version of the breaking piece.
If actual workarounds (like an explicit cast) are needed to accommodate the old version,
a comment should indicate that this is for versions up to the current release,
to be eventually cleaned out when the crate bumps its minimum supported RIOT version.
* Update the examples to use the new versions in a separate PR that just does
`cargo update` in the examples, and consequentally contains only Cargo.lock changes.
Such a PR should be easy to get ACKed, as "CI says GO" is usually suffient for them.
* Rebase the breaking PR on the one updating Cargo.lock.
If old and new version can *not* be supported,
the affected crate should get a major release --
but it's also grounds to revisit whether the change is actually as small as originally thought.
Then, the the Cargo.lock file can be updated in the very PR that introduces the breaking change.
When,
for the above reasons or others,
a major release of creates is done,
their new versions should have a `.0-alpha.0` or similar release.
and a full release of the crate is done after the changing PR is merged.
Common failure modes are:
* Primitive types changed.
This can trigger a change in Rust numeric internals,
which should then just take the larger of the different versions' types.
If the type is publicly visible, it is usually already `usize` or a similar maximal type;
if not, and if using the new RIOT version means that the crate's API needs to change,
the crate needs to undergo a breaking release.
* Functions are moved between static inline and linked.
For the functions themselves, this is caught automatically because `riot-sys`
presents a unified view on symbols produced from linked and static functions.
If any such function takes a struct (even through a pointer), that struct's type changes.
The `inline_cast` function can be used to transmute the pointers with some safety checks.
* Atomics are introduced.
The C2Rust transpiler that allows using all the non-linked parts of the RIOT API
is incapable of handling atomics;
a workaround in `riot-sys`'s `riot-c2rust.h` file contains a list of headers
that do depend on `stdatomic.h` but don't really expose anything of that as their API;
they get included with a crude fake of atomics.
If the addition of a board makes this test fail on that board only,
it is likely because that board's `board.h` pulls in stdatomic through one of the not yet cleared headers.
That file needs to be vetted for visible use of atomics,
and added to that list.
<!-- The alternative strategy, of course,
is to @-mention known Rust users in the issue
and ask them to update the Rust side... -->

View File

@ -0,0 +1,15 @@
// Copyright (C) 2020 Christian Amsüss
//
// This file is subject to the terms and conditions of the GNU Lesser
// General Public License v2.1. See the file LICENSE in the top level
// directory for more details.
#![no_std]
use riot_wrappers::riot_main;
use riot_wrappers::println;
riot_main!(main);
fn main() {
println!("SUCCESS");
}

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# Copyright (C) 2021 Freie Universität Berlin
# 2021 Inria
# 2021 Kaspar Schleiser <kaspar@schleiser.de>
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import sys
from testrunner import run
def testfunc(child):
child.expect_exact('SUCCESS')
if __name__ == "__main__":
sys.exit(run(testfunc))