Contributing to Picodroid
Getting Set Up
See Build & flash for full prerequisites (Rust toolchain, ARM cross-compiler, JDK 11+, probe-rs).
Quick version:
git clone --recurse-submodules https://github.com/shivrajora/picodroid-rscd picodroid-rsln -s ../../scripts/pre-commit .git/hooks/pre-commitRunning Tests
Always use the test script — bare cargo test fails because the default target is bare-metal ARM:
./scripts/test.shPre-commit Hook
The pre-commit hook runs automatically on git commit and checks:
- Java formatting (
google-java-format) - Rust formatting (
cargo fmt) - Clippy (RP2040, RP2350, and simulator targets)
- Embedded firmware build
- All tests
Install it after cloning:
ln -s ../../scripts/pre-commit .git/hooks/pre-commitYou can also run it manually at any time:
./scripts/pre-commitCode Style
Rust
- Format with
cargo fmtbefore committing - Clippy must pass with
--deny=warningson all targets
Java
- All Java sources follow Google Java Style
- Reformat in-place:
./scripts/format_java.sh format - Check without modifying:
./scripts/format_java.sh check
Adding a New Example App
- Create the directory structure:
examples/myapp/ java/myapp/MyApp.java PicodroidManifest.xml- Write your Java source as an
Applicationsubclass with anonCreate()entry point:
package myapp;
import picodroid.app.Application;import picodroid.util.Log;
public class MyApp extends Application { public void onCreate() { Log.i("MyApp", "Hello from MyApp!"); }}- Create
PicodroidManifest.xml(note: the attribute isapplication, notmain-class):
<?xml version="1.0" encoding="utf-8"?><manifest package="myapp" version="1.0"> <application application="myapp/MyApp" /></manifest>- Build and test:
./scripts/build.sh --app myapp./scripts/sim.sh --app myapp # test on host first./scripts/flash.sh --app myapp # flash to hardware- Add your app to the Examples catalog in the appropriate category.
See Your first app for supported language features and the full Java API.
Adding a New Native Java Method
When adding a new native method that the JVM dispatches to Rust:
- Add the native implementation in
picodroid-core/src/native_handler/under the appropriate module - Register it: a new native class goes in
PICODROID_NATIVE_CLASSES(picodroid-core/src/native_handler/class_registry.rs), and every dispatch arm needs a matching(class, method, descriptor)row inpicodroid-core/src/native_handler/method_tables.rs— tests cross-check both. Use the original internal class name in the match arm (e.g."picodroid/pio/Gpio") — the dispatcher callsshrink_names::unshrink_classat entry so names stay readable in source regardless of the active shrink map. Arms onjava/**owners (e.g.System.currentTimeMillis) are the same: pico-jvm reverse-translates itsb/namespace at the class-file boundary, so dispatch never sees a shrunkjava/**name. See Class-name shrinker for details. - If adding a new class to
BuiltinHandler, also register it inclass_name_to_static_ininjvm/src/interpreter/helpers.rs— otherwise virtual dispatch will silently break - Add the Java API stub in
sdk/java/picodroid/. The class will be picked up automatically by the next release cut; between releases its name stays un-shrunk. - Update the relevant API reference page (e.g. Peripherals for a new PIO method, Graphics & UI for a new widget) with the new API surface
Docs are mirrored. This page is a copy of the repository’s root
CONTRIBUTING.md— edit both together so they don’t drift. Likewise, if you change a board memory value (board.toml,FreeRTOSConfig.h, or the MCU.tomls), re-check Limits & memory budgets, which quotes those numbers.
Cutting a New Release
Shrink maps are tied 1:1 to picodroid package versions and are immutable
once committed. Shrinking itself is off by default (opt-in per build
via --shrink), but every release ships a committed map so
--shrink-enabled builds have something to resolve against. When you
bump the version in platforms/rp/Cargo.toml, cut a fresh map in the
same commit:
TMP=$(mktemp -d)find sdk/java -name '*.java' -print0 \ | xargs -0 javac --release 8 -Xlint:-options -d "$TMP"
cargo run -p class-shrink -- cut-release \ --classes-dir "$TMP" \ --keep sdk/keep.toml \ --extra-names sdk/api-contract.tsv \ --base sdk/shrink-maps/v<previous>.toml \ --out sdk/shrink-maps/v<new>.toml--base copies the previous map verbatim — existing entries never get
renamed. --extra-names adds the java/** names the framework never
references itself, so apps’ RuntimeException / Iterator / … shrink
too. See docs/shrinker.md for the full design.
Submitting Changes
- Make sure
./scripts/pre-commitpasses with==> All checks passed. - Test your changes with the simulator (
./scripts/sim.sh) and on hardware if possible - Keep commits focused — one logical change per commit
- Open a pull request with a clear description of what changed and why
License
picodroid-rs is dual-licensed: it is available to the public under the GPL-3.0-only license (see LICENSE), and separately under a proprietary commercial license for customers who need to distribute closed-source derivatives. See Licensing.
To preserve the project’s ability to offer the commercial license, every contribution must be made under the terms of CLA. By opening a pull request, you grant the project maintainer a perpetual, worldwide, non-exclusive, irrevocable, royalty-free license to reproduce, prepare derivative works of, and distribute your contribution as part of picodroid-rs under the GPL-3.0-only license and under any other license the maintainer chooses (including the proprietary commercial license).
You retain copyright in your contribution and may continue to use, license, or relicense your own contribution however you wish. The grant above is non-exclusive — it does not transfer ownership and does not prevent you from distributing your standalone contribution under any other terms you choose.