rustscript-embedded 0.3.2

Embedded RustScript VMBC runtime with a no_std host-function ABI
Documentation
Import("env")

import os
import pathlib
import subprocess
import sys

PROJECT_DIR = pathlib.Path(env.subst("$PROJECT_DIR")).resolve()
TARGET_DIR = PROJECT_DIR / ".pio" / "rust-target"
GENERATED_DIR = PROJECT_DIR / ".pio" / "generated"
SOURCE = PROJECT_DIR / "programs" / "esp32-blinky.rss"
VMBC = GENERATED_DIR / "esp32-blinky.vmbc"
SCRIPT_IMAGE = GENERATED_DIR / "rustscript.partition.bin"
PARTITIONS = PROJECT_DIR / "partitions.csv"
RUST_TARGET = "riscv32imc-unknown-none-elf"
ARCHIVE = TARGET_DIR / RUST_TARGET / "release" / "librustscript_embedded.a"
RUST_FEATURES = [
    feature.strip()
    for feature in env.GetProjectOption("custom_rust_features", "esp32c3").split(",")
    if feature.strip()
]

sys.path.insert(0, str(PROJECT_DIR / "scripts"))
from vmbc_image import find_partition, pack_vmbc


def run(command, cwd, environment=None):
    print("micro-rustscript:", " ".join(str(item) for item in command))
    clean_env = os.environ.copy() if environment is None else environment.copy()
    clean_env.pop("RUSTFLAGS", None)
    subprocess.run(command, cwd=cwd, env=clean_env, check=True)


def command_output(command, cwd, environment=None):
    clean_env = os.environ.copy() if environment is None else environment.copy()
    clean_env.pop("RUSTFLAGS", None)
    return subprocess.check_output(command, cwd=cwd, env=clean_env, text=True).strip()


GENERATED_DIR.mkdir(parents=True, exist_ok=True)
build_environment = os.environ.copy()
build_environment["CARGO_TARGET_DIR"] = str(TARGET_DIR)
build_environment.pop("RUSTFLAGS", None)

run(
    [
        "cargo",
        "build",
        "--release",
        "--target",
        RUST_TARGET,
        "--no-default-features",
        "--features",
        ",".join(RUST_FEATURES),
    ],
    PROJECT_DIR,
    build_environment,
)

if not ARCHIVE.is_file() or ARCHIVE.stat().st_size == 0:
    raise RuntimeError(f"missing Rust static library: {ARCHIVE}")

rust_sysroot = pathlib.Path(
    command_output(["rustc", "--print", "sysroot"], PROJECT_DIR, build_environment)
)
rust_host = command_output(["rustc", "--print", "host-tuple"], PROJECT_DIR, build_environment)
llvm_objcopy = rust_sysroot / "lib" / "rustlib" / rust_host / "bin" / "llvm-objcopy"
if not llvm_objcopy.is_file():
    raise RuntimeError(
        "llvm-objcopy is required; install it with `rustup component add llvm-tools-preview`"
    )
run(
    [str(llvm_objcopy), "--remove-section=.riscv.attributes", str(ARCHIVE)],
    PROJECT_DIR,
    build_environment,
)

run(
    [
        "cargo",
        "run",
        "--quiet",
        "--bin",
        "rustscript-compile-vmbc",
        "--",
        str(SOURCE),
        str(VMBC),
    ],
    PROJECT_DIR,
    build_environment,
)

if not VMBC.is_file() or VMBC.stat().st_size == 0:
    raise RuntimeError(f"missing VMBC output: {VMBC}")
script_offset, script_size = find_partition(PARTITIONS)
SCRIPT_IMAGE.write_bytes(pack_vmbc(VMBC.read_bytes(), script_size))
print(
    "micro-rustscript: packed default VMBC partition",
    SCRIPT_IMAGE,
    f"(offset 0x{script_offset:x})",
)

env.Append(CPPPATH=[str(PROJECT_DIR / "include")])
if "wifi" in RUST_FEATURES:
    env.Append(CPPDEFINES=["RUSTSCRIPT_FEATURE_WIFI"])
if "bluetooth" in RUST_FEATURES:
    env.Append(CPPDEFINES=["RUSTSCRIPT_FEATURE_BLUETOOTH"])
env.Append(LIBS=[env.File(str(ARCHIVE))])
env.Append(FLASH_EXTRA_IMAGES=[(hex(script_offset), env.File(str(SCRIPT_IMAGE)))])