Conversation
This reverts commit 7e40d67e9f85d1752272ad956176179f39ce5da4.
This reverts commit 58ea439225153769594300ceb045bc159eb0aa6a.
There was a problem hiding this comment.
Pull request overview
This PR introduces an alternate (“alt”) Arduino Core API implementation path that is enabled via CONFIG_USE_ARDUINO_API_RUST_IMPLEMENTATION, including a Rust staticlib and a protobuf/IDL-driven C++ header generation toolchain.
Changes:
- Add
alt_core_api/build integration (Rust staticlib + IDL-driven C++ interface header generation). - Introduce
protoc-gen-arduinoifgenerator (models, renderers, templates) with unit tests and documentation. - Adjust samples/config/build plumbing (e.g., enable alt implementation in Arduino samples, gate Wire on
CONFIG_I2C).
Reviewed changes
Copilot reviewed 44 out of 47 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/hello_arduino/prj.conf | Enables alt Rust implementation and disables I2C for the sample. |
| samples/blinky_arduino/prj.conf | Enables alt Rust implementation for the sample. |
| libraries/Wire/CMakeLists.txt | Only builds Wire.cpp when CONFIG_I2C is enabled. |
| documentation/idl_codegen.md | Adds an IDL/codegen manual for protoc-gen-arduinoif. |
| cores/arduino/zephyrPrint.h | Removes the prior Zephyr Print inline header implementation. |
| cores/arduino/zephyrCommon.cpp | Fixes integer type in tone() count clamp. |
| cores/arduino/apiCommon.cpp | Removes C++ wrappers for map() / makeWord() used by the alt path previously. |
| cores/arduino/CMakeLists.txt | Removes Rust-path include/source wiring (now moved under alt_core_api/). |
| alt_core_api/tools/protoc_gen_arduinoif/tests/test_service_model.py | Adds service model builder/renderer tests. |
| alt_core_api/tools/protoc_gen_arduinoif/tests/test_request_context.py | Adds RequestContext tests. |
| alt_core_api/tools/protoc_gen_arduinoif/tests/test_renderer_templates.py | Adds template rendering tests. |
| alt_core_api/tools/protoc_gen_arduinoif/tests/test_options_reader.py | Adds tests for reading proto extensions into the model. |
| alt_core_api/tools/protoc_gen_arduinoif/templates/service_impl_header.hpp.j2 | Adds Jinja template for *_service_impl.hpp. |
| alt_core_api/tools/protoc_gen_arduinoif/templates/service_header.hpp.j2 | Adds Jinja template for *_service.hpp. |
| alt_core_api/tools/protoc_gen_arduinoif/templates/ifc_header.hpp.j2 | Adds Jinja template for *_interface.hpp. |
| alt_core_api/tools/protoc_gen_arduinoif/templates/api_header.hpp.j2 | Adds Jinja template for *_api.hpp. |
| alt_core_api/tools/protoc_gen_arduinoif/service_renderer.py | Implements header rendering over multiple “surfaces” with stable iteration order. |
| alt_core_api/tools/protoc_gen_arduinoif/service_model_builder.py | Builds ServiceModel from descriptors + options + lineage. |
| alt_core_api/tools/protoc_gen_arduinoif/service_model.py | Adds IR types (ServiceModel, MethodSpec, PlannedMethod). |
| alt_core_api/tools/protoc_gen_arduinoif/request_context.py | Adds request indexing (messages, services) + requested-file filtering. |
| alt_core_api/tools/protoc_gen_arduinoif/options_runtime.py | Generates/loads arduino_opts_pb2 at runtime for extension access. |
| alt_core_api/tools/protoc_gen_arduinoif/enum_renderer.py | Adds enum-only proto renderer to *_types.h. |
| alt_core_api/tools/protoc_gen_arduinoif/core.py | Implements protoc plugin entrypoint logic. |
| alt_core_api/tools/protoc_gen_arduinoif/init.py | Adds package marker. |
| alt_core_api/tools/protoc-gen-arduinoif | Adds the executable protoc plugin script. |
| alt_core_api/rust/src/lib.rs | Adds no_std Rust library entry with a panic handler. |
| alt_core_api/rust/src/common.rs | Adds Rust C ABI exports (map_i32, makeWord_*). |
| alt_core_api/rust/Cargo.toml | Defines the Rust staticlib crate. |
| alt_core_api/idl/proto/stream.proto | Adds Stream service IDL. |
| alt_core_api/idl/proto/print.proto | Adds Print service IDL. |
| alt_core_api/idl/proto/hardware_serial.proto | Adds HardwareSerial service IDL and options. |
| alt_core_api/idl/proto/google/protobuf/wrappers.proto | Adds minimal wrappers proto used by IDL. |
| alt_core_api/idl/proto/google/protobuf/empty.proto | Adds minimal Empty proto used by IDL. |
| alt_core_api/idl/proto/common.proto | Adds shared enums proto (types header generation). |
| alt_core_api/idl/proto/arduino_opts.proto | Adds custom option extensions for codegen. |
| alt_core_api/idl/CMakeLists.txt | Integrates nanopb + custom interface header generation into the build. |
| alt_core_api/api/zephyrPrint.cpp | Adjusts Print helpers (removes default write(buffer,size) impl from .cpp). |
| alt_core_api/api/Stream.h | Adds alt Stream wrapper type (currently has a syntax issue). |
| alt_core_api/api/Print.h | Adds alt Print wrapper type + inline helpers. |
| alt_core_api/api/HardwareSerial.h | Adds alt HardwareSerial wrapper type. |
| alt_core_api/api/Common.h | Adds a stub compat header. |
| alt_core_api/api/CMakeLists.txt | Adds Zephyr sources for alt API. |
| alt_core_api/api/ArduinoAPI.h | Adds alt Arduino API header (currently disables most declarations). |
| alt_core_api/CMakeLists.txt | Adds alt_core_api build (Rust + IDL + API). |
| README.md | Links new IDL codegen documentation. |
| Kconfig | Selects NANOPB when the alt Rust implementation is enabled. |
| CMakeLists.txt | Switches Rust-enabled builds to use alt_core_api/ subdirectory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| inline size_t Print::println(const String &s) { | ||
| return print(s) + println(); | ||
| } |
There was a problem hiding this comment.
println(const String&) is inline and calls print(s), but Print::print(const String&) has no active definition in this header (the only implementation is commented out above). This will compile but fail to link if println(String) is used. Restore/provide the print(const String&) (and similarly print(const Printable&)) implementation, or remove/disable the dependent println(...) overloads.
| inline size_t Print::println(double d, int perception) { | ||
| return print(d, perception) + println(); | ||
| } | ||
|
|
There was a problem hiding this comment.
println(const Printable&) is inline and calls print(printable), but Print::print(const Printable&) has no active definition in this header (the only implementation is commented out above). This will lead to undefined references when println(Printable) is used. Provide/restore the missing print(const Printable&) implementation or remove/disable the dependent println(...) overload.
| inline size_t Print::print(const Printable &printable) { | |
| return printable.printTo(*this); | |
| } |
| option (arduino.generate_api_class) = true; | ||
| option (arduino.generate_service_class) = true; | ||
| option (arduino.generate_service_impl_class) = true; | ||
| option (arduino.api_class_name) = "arduino::HardwareSerial"; |
There was a problem hiding this comment.
api_class_name is set to a fully-qualified C++ name (arduino::HardwareSerial) while the generator also wraps output in namespace arduino { ... }. That combination will produce an invalid class declaration like namespace arduino { class arduino::HardwareSerial ... } in the generated headers. Either change the option to an unqualified name (e.g., HardwareSerial) or update the generator to detect qualified names and avoid emitting the surrounding namespace / strip the prefix.
| option (arduino.api_class_name) = "arduino::HardwareSerial"; | |
| option (arduino.api_class_name) = "HardwareSerial"; |
| missing_api = [ | ||
| spec.decl | ||
| for spec in lineage_method_specs | ||
| if spec.emit_service and spec.call_name not in api_callable |
There was a problem hiding this comment.
The service-impl delegate validation checks spec.call_name not in api_callable. Because api_callable is name-only, this can miss missing overloads (one overload present in API makes all overloads look present), and codegen can still emit an impl method that fails to compile. Make this validation compare overload-unique identifiers (e.g., spec.decl or a computed signature) rather than just call_name.
| if spec.emit_service and spec.call_name not in api_callable | |
| if spec.emit_service and spec.decl not in api_callable |
| Defined as `google.protobuf.ServiceOptions` extensions: | ||
|
|
||
| 1. `generate_ifc_class` | ||
| 2. `generate_api_class` | ||
| 3. `generate_service_class` |
There was a problem hiding this comment.
This section lists a generate_ifc_class service option, but arduino_opts.proto doesn’t define such an extension (and the generator currently always emits the Ifc header/class). Either update the documentation to match the implemented options, or add the missing option + implementation so the documented configuration is actually supported.
| The generator intentionally fails when configuration is inconsistent: | ||
|
|
||
| 1. `generate_api_class = true` requires `generate_ifc_class = true` | ||
| 2. `generate_service_impl_class = true` requires `generate_service_class = true` | ||
| 3. `generate_api_class = true` rejects methods with `emit_api = true` and `source_virtual = false` |
There was a problem hiding this comment.
The validation rules and example here refer to generate_ifc_class / (arduino.generate_ifc_class), but those extensions are not present in arduino_opts.proto and the generator doesn’t implement that toggle (Ifc is always emitted). Update this section (and the example proto snippet) to reflect the actual supported options, otherwise readers will try options that have no effect / fail to compile.
| #if 0 | ||
| void yield(void); | ||
|
|
||
| void init(void); | ||
| void initVariant(void); |
There was a problem hiding this comment.
Almost the entire Arduino API surface is currently disabled by #if 0 (starting here), including core declarations like pinMode, digitalWrite, interrupt APIs, and the map/makeWord helpers. With the Rust implementation enabled, sketches including <Arduino.h> will not see these declarations and will fail to compile (e.g., the blinky sample calls pinMode/digitalWrite). Replace #if 0 with the intended feature guard or remove it so these APIs are declared.
| spec.call_name | ||
| for spec in lineage_specs | ||
| if spec.emit_api and spec.source_virtual | ||
| } | ||
| service_callable = { | ||
| spec.call_name for spec in lineage_specs if spec.emit_service |
There was a problem hiding this comment.
api_callable is tracked as a set of spec.call_name, but overloads share the same call_name (e.g., write, begin). Downstream checks/gating then treat all overloads as callable if any overload is present, which can generate service_impl methods that call API overloads that were never emitted. Track callability using an overload-unique key (e.g., spec.decl or a dedicated signature field) instead of just call_name.
| spec.call_name | |
| for spec in lineage_specs | |
| if spec.emit_api and spec.source_virtual | |
| } | |
| service_callable = { | |
| spec.call_name for spec in lineage_specs if spec.emit_service | |
| spec.decl | |
| for spec in lineage_specs | |
| if spec.emit_api and spec.source_virtual | |
| } | |
| service_callable = { | |
| spec.decl for spec in lineage_specs if spec.emit_service |
| in_api=spec.emit_api and spec.source_virtual, | ||
| in_service=in_service, | ||
| in_service_impl=in_service | ||
| and spec.call_name in service_impl_callable, |
There was a problem hiding this comment.
in_service_impl is enabled based on spec.call_name in service_impl_callable, which is not overload-safe (multiple overloads share the same call_name). This can cause the generator to emit service_impl overrides for overloads that are not actually callable on the delegate API class. Use an overload-unique key (e.g., full signature/decl) for service_impl_callable membership checks.
| and spec.call_name in service_impl_callable, | |
| and spec.decl in service_impl_callable, |
zephyrproject-rtos@8f7c7db