Skip to content

Commit

Permalink
[wip] initial rust ffi support (fails to build)
Browse files Browse the repository at this point in the history
  • Loading branch information
delan committed Nov 30, 2024
1 parent f6ccc39 commit ea25238
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build]
target = "thumbv6m-none-eabi"

[unstable]
build-std = ["core", "panic_abort"]
build-std-features = ["panic_immediate_abort"]
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
.pio
/.pio/
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "usb3sun"
edition = "2021"
version = "2.0.0"
readme = "README.md"

[lib]
path = "src/lib.rs"
name = "usb3sun"
edition = "2021"
crate-type = ["staticlib"]
required-features = []
90 changes: 90 additions & 0 deletions platformio.cargo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Cargo <-> PlatformIO integration script (autogenerated by cargo-pio)
# Calling 'pio run' will also build the Rust library crate by invoking Cargo
#
# How to use: Insert/update the following line in one of platformio.ini's environments:
# extra_scripts = platformio.cargo.py

import os

Import("env")

class Cargo:
def run(self, env):
self.__init_props(env)

if self.__cargo_run_before_project:
# Attach as a pre-action to all source files so that in case CBindgen is used
# the C headers are generated before the files are compiled
env.AddPreAction(Glob(os.path.join(env.subst("$BUILD_DIR"), "src/*.o")), self.__run_cargo)

# Hack. Need to always run when a C file from the src directory is built, or else the include directories
# passed to Cargo will not contain the includes coming from libraries imported with PlatformIO's Library Manager
env.AlwaysBuild(os.path.join(env.subst("$BUILD_DIR"), "src/dummy.o"))

env.AddPreAction("$BUILD_DIR/$PROGNAME$PROGSUFFIX", [self.__run_cargo, self.__link_cargo])

def __init_props(self, env):
self.__cargo_ran = False

self.__rust_lib = env.GetProjectOption("rust_lib")
self.__rust_target = env.GetProjectOption("rust_target")

self.__rust_bindgen_enabled = env.GetProjectOption("rust_bindgen_enabled", default = "false").lower() == "true"
self.__rust_bindgen_extra_clang_args = env.GetProjectOption("rust_bindgen_extra_clang_args", default = "")

self.__cargo_run_before_project = env.GetProjectOption("cargo_run_before_project", default = "false").lower() == "true"
self.__cargo_options = env.GetProjectOption("cargo_options", default = "")
self.__cargo_profile = env.GetProjectOption(
"cargo_profile",
default = "release" if env.GetProjectOption("build_type") == "release" else "debug")
self.__cargo_target_dir = env.GetProjectOption(
"cargo_target_dir",
default = os.path.join(env.subst("$PROJECT_BUILD_DIR"), "cargo")
if env.GetProjectOption("cargo_pio_common_build_dir", default = "").lower() == "true"
else os.path.join(env.subst("$PROJECT_DIR"), "target"))

def __run_cargo(self, source, target, env):
if self.__cargo_ran:
return 0

print(">>> CARGO")

board_mcu = env.get("BOARD_MCU")
if not board_mcu and "BOARD" in env:
board_mcu = env.BoardConfig().get("build.mcu")

env["ENV"]["CARGO_BUILD_TARGET_DIR"] = self.__cargo_target_dir
env["ENV"]["CARGO_PIO_BUILD_PROJECT_DIR"] = env.subst("$PROJECT_DIR")
env["ENV"]["CARGO_PIO_BUILD_RELEASE_BUILD"] = str(env.GetProjectOption("build_type", default = "release") == "release")

env["ENV"]["CARGO_PIO_BUILD_PATH"] = env["ENV"]["PATH"]
env["ENV"]["CARGO_PIO_BUILD_ACTIVE"] = "1"
env["ENV"]["CARGO_PIO_BUILD_INC_FLAGS"] = env.subst("$_CPPINCFLAGS")
env["ENV"]["CARGO_PIO_BUILD_LIB_FLAGS"] = env.subst("$_LIBFLAGS")
env["ENV"]["CARGO_PIO_BUILD_LIB_DIR_FLAGS"] = env.subst("$_LIBDIRFLAGS")
env["ENV"]["CARGO_PIO_BUILD_LIBS"] = env.subst("$LIBS")
env["ENV"]["CARGO_PIO_BUILD_LINK_FLAGS"] = env.subst("$LINKFLAGS")
env["ENV"]["CARGO_PIO_BUILD_LINK"] = env.subst("$LINK")
env["ENV"]["CARGO_PIO_BUILD_LINKCOM"] = env.subst("$LINKCOM")
env["ENV"]["CARGO_PIO_BUILD_MCU"] = board_mcu

if self.__rust_bindgen_enabled:
env["ENV"]["CARGO_PIO_BUILD_BINDGEN_RUN"] = "True"
env["ENV"]["CARGO_PIO_BUILD_BINDGEN_EXTRA_CLANG_ARGS"] = self.__rust_bindgen_extra_clang_args

env["ENV"]["CARGO_PIO_BUILD_PIO_PLATFORM_DIR"] = env.PioPlatform().get_dir()[0]
env["ENV"]["CARGO_PIO_BUILD_PIO_FRAMEWORK_DIR"] = env.PioPlatform().get_package_dir(env.PioPlatform().frameworks[env.GetProjectOption("framework")[0]]["package"])

self.__cargo_ran = True
result = env.Execute(f"cargo build {'--release' if self.__cargo_profile == 'release' else ''} --lib --target {self.__rust_target} {self.__cargo_options}")

print("<<< CARGO")

return result

def __link_cargo(self, source, target, env):
env.Prepend(LINKFLAGS = ["-Wl,--allow-multiple-definition"]) # A hack to workaround this issue with Rust's compiler intrinsics: https://github.com/rust-lang/compiler-builtins/issues/353
env.Prepend(LIBPATH = [env.subst(os.path.join(self.__cargo_target_dir, self.__rust_target, self.__cargo_profile))])
env.Prepend(LIBS = [self.__rust_lib])

Cargo().run(env)
41 changes: 41 additions & 0 deletions platformio.git.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# A small PlatformIO integration script (autogenerated by cargo-pio) that provides the capability to clone
# user-specified Git projects (platformio.ini git_repos = "...") before the build is triggered
#
# How to use:
# Insert/update the following line in one of platformio.ini's environments:
# extra_scripts = pre:platformio.git.py
# Specify a newline-separated list of Git repos to check out:
# git_repos = [dir-name1]@<repo1> \n [dir-name2]@<repo2>...

import os

Import("env")

class GitRepos:
def run(self, env):
self.__git_repos = env.GetProjectOption("git_repos", default = "")
self.__materialize_git_repos()

def __materialize_git_repos(self):
for (directory, repo) in self.__git_repos_list():
if not os.path.exists(env.subst(directory)):
print(f"Cloning {repo} as directory {directory}")
if env.Execute(f"git clone {repo} {directory}"):
Exit(1)

def __git_repos_list(self):
git_repos_dir = os.path.join("$PROJECT_WORKSPACE_DIR", "git-repos")

def get_repo(item):
repo = item
repo_directory = repo.split("/")[-1]
if "@" in item:
repo_directory, repo = item.split("@", 1)

return (
os.path.join(git_repos_dir, repo_directory.strip()),
repo.strip())

return [get_repo(item) for item in self.__git_repos.split("\n") if len(item.strip()) > 0]

GitRepos().run(env)
6 changes: 6 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

; cargo pio init -b pico -f arduino -t thumbv6m-none-eabi
[env]
extra_scripts = pre:platformio.git.py, pre:platformio.patch.py, platformio.cargo.py
rust_lib = usb3sun
rust_target = thumbv6m-none-eabi

[env:pico]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = pico
Expand Down
78 changes: 78 additions & 0 deletions platformio.patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# A small PlatformIO integration script (autogenerated by cargo-pio) that provides the capability to patch
# PlatformIO packages (or the platform itself) before the build is triggered
#
# How to use:
# Insert/update the following line in one of platformio.ini's environments:
# extra_scripts = pre:platformio.patch.py
# Specify a newline-separated list of patches to apply:
# patches = <pio-package-directory1>@<patch-file1> \n <pio-package-directory2>@<patch-file2>...
# ... where <patch-file-1>, <patch-file-2> etc. are expected to be placed in a 'patches/' folder of your project
#
# When <pio-package-directory> is equal to "__platform__", the platform itself will be patched

import os
import shutil

Import("env")

class Patch:
def run(self, env):
for (patch, patch_name, dir) in self.__patches_list(env):
self.__patch(env, patch, patch_name, dir)

def __patch(self, env, patch, patch_name, dir):
patch_flag = os.path.join(dir, f"{patch_name}.applied")

if not os.path.isfile(patch_flag):
# In recent PlatformIO, framework_espidf is no longer a true GIT repository
# (i.e. it does not contain a .git subfolder)
# As a result, "git apply" does not really work
#
# One workaround would've been to use `patch` instead of `git apply`,
# however `patch` does not seem to be available on Windows out of the box
#
# Therefore, instead we do a nasty hack here: we check if `dir`
# contains a `.git` folder, and if not we create it using "git init"
#
# Once the patch is applied, we remove the `.git` folder
git_dir = os.path.join(dir, ".git")
git_dir_exists = True

if not os.path.exists(git_dir):
if env.Execute("git init", chdir = dir):
env.Exit(1)
git_dir_exists = False

res = env.Execute(f"git apply {patch}", chdir = dir)

if not git_dir_exists:
shutil.rmtree(git_dir)

if res:
env.Exit(1)

self.__touch(patch_flag)

def __patches_list(self, env):
patches_dir = os.path.join(env.subst("$PROJECT_DIR"), "patches")

def get_patch(item):
dir, patch = item.split("@", 1)

package = dir.strip()
if package == "__platform__":
package_dir = env.PioPlatform().get_dir()
else:
package_dir = env.PioPlatform().get_package_dir(package)

return (
os.path.join(patches_dir, patch.strip()),
patch.strip(),
package_dir)

return [get_patch(item) for item in env.GetProjectOption("patches", default = "").split("\n") if len(item.strip()) > 0]

def __touch(self, path):
os.open(path, os.O_CREAT | os.O_RDWR)

Patch().run(env)
10 changes: 10 additions & 0 deletions src/dummy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// Cargo <-> PlatformIO helper C file (autogenerated by cargo-pio)
// This file is intentionally empty. Please DO NOT change it or delete it!
//
// Two reasons why this file is necessary:
// - PlatformIO complains if the src directory is empty with an error message
// 'Nothing to build. Please put your source code files to '../src' folder'. So we have to provide at least one C/C++ source file
// - The Cargo invocation is attached as a post-action to building this file. This is necessary, or else
// Cargo crates will not see the extra include directories of all libraries downloaded via the PlatformIO Library Manager
//
20 changes: 20 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Remove if STD is supported for your platform and you plan to use it
#![no_std]

// Remove if STD is supported for your platform and you plan to use it
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}

//
// Entry points
//

#[no_mangle]
extern "C" fn arduino_setup() {
}

#[no_mangle]
extern "C" fn arduino_loop() {
}

0 comments on commit ea25238

Please sign in to comment.