diff --git a/.gitattributes b/.gitattributes index 9ebeba4883..32985d0606 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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. diff --git a/Makefile.include b/Makefile.include index 68be673d70..a91c125ecc 100644 --- a/Makefile.include +++ b/Makefile.include @@ -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 diff --git a/cpu/cortexm_common/Kconfig b/cpu/cortexm_common/Kconfig index b9eb32b938..0ba3666603 100644 --- a/cpu/cortexm_common/Kconfig +++ b/cpu/cortexm_common/Kconfig @@ -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 diff --git a/cpu/cortexm_common/Makefile.features b/cpu/cortexm_common/Makefile.features index 7992bc8c8b..3ca730d9f1 100644 --- a/cpu/cortexm_common/Makefile.features +++ b/cpu/cortexm_common/Makefile.features @@ -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 diff --git a/cpu/native/Kconfig b/cpu/native/Kconfig index 4e9365a899..268185bd0a 100644 --- a/cpu/native/Kconfig +++ b/cpu/native/Kconfig @@ -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 diff --git a/cpu/native/Makefile.features b/cpu/native/Makefile.features index 0e237bea44..14f89d59fb 100644 --- a/cpu/native/Makefile.features +++ b/cpu/native/Makefile.features @@ -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) diff --git a/cpu/native/Makefile.include b/cpu/native/Makefile.include index 0dbbe715fb..279ff4f412 100644 --- a/cpu/native/Makefile.include +++ b/cpu/native/Makefile.include @@ -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 diff --git a/cpu/riscv_common/Kconfig b/cpu/riscv_common/Kconfig index 3a0c8d46fe..707cf33278 100644 --- a/cpu/riscv_common/Kconfig +++ b/cpu/riscv_common/Kconfig @@ -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 diff --git a/cpu/riscv_common/Makefile.features b/cpu/riscv_common/Makefile.features index 30a1b8a5f6..39d2d1c083 100644 --- a/cpu/riscv_common/Makefile.features +++ b/cpu/riscv_common/Makefile.features @@ -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 diff --git a/dist/tools/codespell/ignored_words.txt b/dist/tools/codespell/ignored_words.txt index 45eae96e10..1b9c188706 100644 --- a/dist/tools/codespell/ignored_words.txt +++ b/dist/tools/codespell/ignored_words.txt @@ -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 diff --git a/dist/tools/compile_commands/compile_commands.py b/dist/tools/compile_commands/compile_commands.py index 92137bf93e..bbb0ca89e1 100755 --- a/dist/tools/compile_commands/compile_commands.py +++ b/dist/tools/compile_commands/compile_commands.py @@ -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) diff --git a/examples/rust-gcoap/Cargo.lock b/examples/rust-gcoap/Cargo.lock new file mode 100644 index 0000000000..024687b936 Binary files /dev/null and b/examples/rust-gcoap/Cargo.lock differ diff --git a/examples/rust-gcoap/Cargo.toml b/examples/rust-gcoap/Cargo.toml new file mode 100644 index 0000000000..58d66cf76a --- /dev/null +++ b/examples/rust-gcoap/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rust-gcoap" +version = "0.1.0" +authors = ["Christian Amsüss "] +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" ] } diff --git a/examples/rust-gcoap/Makefile b/examples/rust-gcoap/Makefile new file mode 100644 index 0000000000..1cdf7b43c1 --- /dev/null +++ b/examples/rust-gcoap/Makefile @@ -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 diff --git a/examples/rust-gcoap/Makefile.ci b/examples/rust-gcoap/Makefile.ci new file mode 100644 index 0000000000..136960a4e9 --- /dev/null +++ b/examples/rust-gcoap/Makefile.ci @@ -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 \ + # diff --git a/examples/rust-gcoap/README.md b/examples/rust-gcoap/README.md new file mode 100644 index 0000000000..5d8a5b42f1 --- /dev/null +++ b/examples/rust-gcoap/README.md @@ -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' +``` diff --git a/examples/rust-gcoap/src/lib.rs b/examples/rust-gcoap/src/lib.rs new file mode 100644 index 0000000000..0059b4523c --- /dev/null +++ b/examples/rust-gcoap/src/lib.rs @@ -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(); } + }) +} diff --git a/examples/rust-gcoap/vfs.c b/examples/rust-gcoap/vfs.c new file mode 100644 index 0000000000..ce25ee0c51 --- /dev/null +++ b/examples/rust-gcoap/vfs.c @@ -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 +#include "fs/constfs.h" +#include + +#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"); + } +} diff --git a/examples/rust-hello-world/Cargo.lock b/examples/rust-hello-world/Cargo.lock new file mode 100644 index 0000000000..353d9fbe43 Binary files /dev/null and b/examples/rust-hello-world/Cargo.lock differ diff --git a/examples/rust-hello-world/Cargo.toml b/examples/rust-hello-world/Cargo.toml new file mode 100644 index 0000000000..443cf57cf2 --- /dev/null +++ b/examples/rust-hello-world/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-hello-world" +version = "0.1.0" +authors = ["Christian Amsüss "] +edition = "2018" +resolver = "2" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +riot-wrappers = "0.7" diff --git a/examples/rust-hello-world/Makefile b/examples/rust-hello-world/Makefile new file mode 100644 index 0000000000..c411d26d26 --- /dev/null +++ b/examples/rust-hello-world/Makefile @@ -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 diff --git a/examples/rust-hello-world/README.md b/examples/rust-hello-world/README.md new file mode 100644 index 0000000000..d76953389a --- /dev/null +++ b/examples/rust-hello-world/README.md @@ -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. diff --git a/examples/rust-hello-world/src/lib.rs b/examples/rust-hello-world/src/lib.rs new file mode 100644 index 0000000000..f4ad7a8fc4 --- /dev/null +++ b/examples/rust-hello-world/src/lib.rs @@ -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!"); +} diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index ed995187e5..6743fae381 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -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 diff --git a/makefiles/arch/riscv.inc.mk b/makefiles/arch/riscv.inc.mk index 800e9460e2..557b376416 100644 --- a/makefiles/arch/riscv.inc.mk +++ b/makefiles/arch/riscv.inc.mk @@ -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 diff --git a/makefiles/cargo-settings.inc.mk b/makefiles/cargo-settings.inc.mk new file mode 100644 index 0000000000..d5e3f7ec3d --- /dev/null +++ b/makefiles/cargo-settings.inc.mk @@ -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 diff --git a/makefiles/cargo-targets.inc.mk b/makefiles/cargo-targets.inc.mk new file mode 100644 index 0000000000..f9343e1ddf --- /dev/null +++ b/makefiles/cargo-targets.inc.mk @@ -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/) diff --git a/makefiles/info-global.inc.mk b/makefiles/info-global.inc.mk index 59150a34a6..68be99dab4 100644 --- a/makefiles/info-global.inc.mk +++ b/makefiles/info-global.inc.mk @@ -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 diff --git a/makefiles/vars.inc.mk b/makefiles/vars.inc.mk index f7808cae23..c8607d295b 100644 --- a/makefiles/vars.inc.mk +++ b/makefiles/vars.inc.mk @@ -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 diff --git a/tests/rust_minimal/Cargo.lock b/tests/rust_minimal/Cargo.lock new file mode 100644 index 0000000000..10e357c086 Binary files /dev/null and b/tests/rust_minimal/Cargo.lock differ diff --git a/tests/rust_minimal/Cargo.toml b/tests/rust_minimal/Cargo.toml new file mode 100644 index 0000000000..14142f8a2a --- /dev/null +++ b/tests/rust_minimal/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-minimal" +version = "0.1.0" +authors = ["Christian Amsüss "] +edition = "2018" +resolver = "2" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +riot-wrappers = "0.7" diff --git a/tests/rust_minimal/Makefile b/tests/rust_minimal/Makefile new file mode 100644 index 0000000000..37ef882eee --- /dev/null +++ b/tests/rust_minimal/Makefile @@ -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 diff --git a/tests/rust_minimal/README.md b/tests/rust_minimal/README.md new file mode 100644 index 0000000000..f784f98e8c --- /dev/null +++ b/tests/rust_minimal/README.md @@ -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. + + diff --git a/tests/rust_minimal/src/lib.rs b/tests/rust_minimal/src/lib.rs new file mode 100644 index 0000000000..358a332ab2 --- /dev/null +++ b/tests/rust_minimal/src/lib.rs @@ -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"); +} diff --git a/tests/rust_minimal/tests/01-run.py b/tests/rust_minimal/tests/01-run.py new file mode 100755 index 0000000000..026e7e6293 --- /dev/null +++ b/tests/rust_minimal/tests/01-run.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 Freie Universität Berlin +# 2021 Inria +# 2021 Kaspar Schleiser +# +# 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))