libraries: guard TinyUSB include under PlatformIO so SAMD builds compile - #396
tyeth-ai-assisted wants to merge 4 commits into
Conversation
|
Hey @hathach could you have a review of this please? This rectifies the include error that occurs otherwise. |
|
Oops, failing CI on untested path, will fix |
8904a30 to
a9a2c20
Compare
a9a2c20 to
26c39c0
Compare
00da71d to
06da1e0
Compare
Under PlatformIO, framework-bundled libraries do not receive lib_deps include paths, so the unguarded #include <Adafruit_TinyUSB.h> hard-fails every SAMD build with USE_TINYUSB. Adafruit_TinyUSB.h lives in the library's src/, and only src/arduino is ever added to the include path (platform.txt does this for arduino-cli; PlatformIO's atmelsam builder does the same), so the header is unreachable from these five sources. Skip the include under PlatformIO when it is unreachable, keeping it plain and unconditional everywhere else. The nesting is deliberate: the include is what the Arduino builder discovers the TinyUSB library from, and its dependency-detection pass cannot parse a __has_include() expression -- it then finds no dependency, TinyUSB is never linked, and the usbstack=tinyusb examples fail with undefined references to Serial and Adafruit_USBD_CDC::begin. Putting __has_include() inside #ifdef PLATFORMIO leaves it in a branch arduino-cli skips without evaluating, so arduino-cli behaviour is unchanged. Verified both ways in CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJa4WYfEHBFUhnJVRLja7A
06da1e0 to
c63ec86
Compare
There was a problem hiding this comment.
look good, sorry for the delay. I think we can simplify this
// PlatformIO does not give framework-bundled libraries the lib_deps include
// paths, so the header can be unreachable there; skip it instead of failing.
#if !defined(PLATFORMIO) || !defined(__has_include) || __has_include(<Adafruit_TinyUSB.h>)
#include <Adafruit_TinyUSB.h>
#endifApply hathach's review suggestion from adafruit#396: one #if covering PLATFORMIO and __has_include, replacing the nested #ifdef PLATFORMIO form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ng for it
Replaces the __has_include probe with a plain #ifndef PLATFORMIO. The
include exists only so the Arduino builder discovers and links
Adafruit_TinyUSB_Arduino; none of these five translation units references
Serial or any TinyUSB symbol, so under PlatformIO -- where the header is
unreachable and the discovery side effect does not exist -- it can simply
be left out.
Background
----------
Only src/arduino of Adafruit_TinyUSB_Arduino is ever placed on the include
path: platform.txt line 80 does it for arduino-cli, and PlatformIO's
atmelsam platform appends the same path to CPPPATH for the Adafruit vendor
core. So Adafruit_USBD_CDC.h resolves everywhere while
<Adafruit_TinyUSB.h> resolves from neither. arduino-cli survives that
because the unresolved include is itself what drives library discovery;
PlatformIO gives framework-bundled libraries no equivalent step, so every
SAMD build with USE_TINYUSB dies with
libraries/Servo/src/samd/Servo.cpp:26:10: fatal error:
Adafruit_TinyUSB.h: No such file or directory
Why not __has_include
---------------------
Both previous attempts used __has_include, and the flat form has now been
measured red four times on this repo's matrix -- three runs before opening
the PR, and run 35356063732 on the previous commit, which applied the
review suggestion
#if !defined(PLATFORMIO) || !defined(__has_include) || __has_include(<Adafruit_TinyUSB.h>)
verbatim. Both usbstack=tinyusb legs failed while the other ten passed:
CorrectADCResponse.ino:43: undefined reference to
`Adafruit_USBD_CDC::begin(unsigned long)'
CorrectADCResponse.ino:144: undefined reference to `Serial'
collect2: error: ld returned 1 exit status
The failures are link-time only, never compile errors: GCC 9 handles
__has_include fine, but the Arduino builder's dependency-detection pass
cannot, so it finds no dependency and TinyUSB is never linked. Placing
!defined(PLATFORMIO) first does not help, because __has_include is
resolved while the #if line is expanded, before its operands are
evaluated -- short-circuiting never gets the chance to hide it. The
earlier nested form worked only by burying the expression inside an
#ifdef PLATFORMIO branch that arduino-cli skips without reading.
#ifndef PLATFORMIO avoids the construct altogether: arduino-cli's branch
holds a byte-identical unconditional include, and PlatformIO takes no
branch at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Update: the
|
| guard | arduino-cli (usbstack=tinyusb) |
PlatformIO (USE_TINYUSB) |
|---|---|---|
| none (master) | passes | compile error — Servo.cpp:26:10: fatal error: Adafruit_TinyUSB.h: No such file or directory |
flat __has_include, with or without a leading !defined(PLATFORMIO) |
link error — undefined reference to 'Serial', undefined reference to 'Adafruit_USBD_CDC::begin(unsigned long)' |
passes |
__has_include nested inside #ifdef PLATFORMIO |
passes | passes |
#ifndef PLATFORMIO (current) |
passes | passes |
Why each cell is what it is:
PlatformIO's compile error. Only Adafruit_TinyUSB_Arduino/src/arduino is ever put on the include path — platform.txt line 80 for arduino-cli, and the atmelsam platform appends the same directory to the global CPPPATH for this core. Adafruit_USBD_CDC.h lives there and resolves everywhere; Adafruit_TinyUSB.h lives one level up in src/ and resolves from neither. PlatformIO gives framework-bundled libraries no lib_deps include paths, so there is no second chance and the include is a hard error.
Why arduino-cli survives that same unresolved include. The unresolved include is the discovery mechanism: the Arduino builder uses it to find Adafruit_TinyUSB_Arduino, add its src/ to the include path, and link the library. Remove or hide the include and nothing links.
The link errors, and why the flat form can't work. The failures are link-time only — never compile errors. GCC 9 handles __has_include fine, so the real compile is clean; it is the builder's dependency-detection pass that cannot parse the expression, finds no dependency, and never links TinyUSB. Ordering the clauses so !defined(PLATFORMIO) short-circuits first does not rescue it, because __has_include is resolved while the #if line is being expanded — before its operands are evaluated — so short-circuiting never gets the chance to hide it. Measured red four times: three runs before this PR was opened, and run 35356063732 on the flat form, where both tinyusb legs failed in the Wire and SAMD_AnalogCorrection examples while the other ten passed. Nesting it inside #ifdef PLATFORMIO worked only because arduino-cli skips that branch without reading it.
Why dropping the include outright is safe. None of the five files — I2S.cpp, SAMD_AnalogCorrection.cpp, SPI.cpp, Servo.cpp, Wire.cpp — references Serial or any TinyUSB symbol. The include exists purely for the arduino-cli discovery side effect, so where that side effect doesn't exist there is nothing to preserve. That is what lets the guard be a plain #ifndef rather than a probe, and it makes the nesting unnecessary rather than merely tidier.
PlatformIO side, unchanged from the original report: WipperSnapper, env adafruit_pyportal_m4, USE_TINYUSB=1, built against the installed framework-arduino-samd-adafruit@1.10716.0 with only these five files varied — fails unpatched, builds patched, identical firmware size across every working guard variant, confirming the guard only ever removes an include PlatformIO could not resolve.
Tyeth will squash the three commits on this branch into one before merge and release.
|
@hathach thanks for the review and suggestion. I had a try and unfortunately still fails so this simpler version should be sufficient. I'll squash the PR before merge if you're happy with that. |
Problem
Every PlatformIO SAMD build with
USE_TINYUSBfails to compile:arduino-cliis unaffected, which is why this has gone unnoticed.Why
Adafruit_TinyUSB.hlives in the library'ssrc/, and onlysrc/arduinois ever put on the include path — by both toolchains:platform.txt(line 80) forarduino-cli:"-I{runtime.platform.path}/libraries/Adafruit_TinyUSB_Arduino/src/arduino"atmelsamplatform appends that same.../Adafruit_TinyUSB_Arduino/src/arduinoto the globalCPPPATHfor the Adafruit vendor core (builder/frameworks/arduino/arduino-samd.py).So
Adafruit_USBD_CDC.hresolves everywhere, while<Adafruit_TinyUSB.h>resolves from neither.Under
arduino-cliit works anyway, because that unresolved include is itself what drives the Arduino builder's library discovery: it findsAdafruit_TinyUSB_Arduino, adds itssrc/to the include path, and links the library — the side effect the existing comment refers to. PlatformIO has no equivalent step for framework-bundled libraries (they never receivelib_depsinclude paths), so there the include is simply a hard error.Fix
Skip the include under PlatformIO when the header is unreachable, and leave it plain and unconditional everywhere else:
The nesting is load-bearing, and this repo's CI is what proved it. A flat guard —
#if !defined(__has_include) || __has_include(<Adafruit_TinyUSB.h>)around the include, with or without a!defined(PLATFORMIO)clause — turns bothusbstack=tinyusblegs red withundefined reference to 'Serial'andAdafruit_USBD_CDC::begin(unsigned long)in theWireandSAMD_AnalogCorrectionexamples. The failures are link-time only, never compile errors: the Arduino builder's dependency-detection pass cannot parse a__has_include()expression, so it finds no dependency and TinyUSB is never linked, while the real compile (GCC 9, which does support__has_include) goes through fine. Short-circuiting does not help, because the expression still has to be parsed. Nesting it inside#ifdef PLATFORMIOputs it in a brancharduino-cliskips without evaluating, leaving that toolchain untouched.Servo.cppis the only file that actually fails in the configuration I tested; the other four carry the identical unguarded include and the identical hazard, so they get the same treatment rather than surfacing one at a time.Testing
PlatformIO — Adafruit WipperSnapper (TinyUSB + SPI + Wire + ~60 sensor libraries), env
adafruit_pyportal_m4, PlatformIO 6,USE_TINYUSB=1. Each tree was built from the installedplatformio/framework-arduino-samd-adafruit@1.10716.0package (= this repo at 1.7.16) consumed viaplatform_packages = framework-arduino-samd-adafruit@symlink://<tree>, so these five files are the only variable. They are byte-identical betweenmasterand 1.7.16, so the result applies directly to this branch.Servo.cpp:26: fatal error: Adafruit_TinyUSB.h: No such file or directoryarduino-cli — this repo's own matrix, run three times on this PR:
__has_includeguardSerial)The PlatformIO firmware is byte-for-byte the same size across every guard variant, confirming the guard only ever removes an include PlatformIO could not resolve in the first place.
Note
This supersedes a Copilot-authored attempt (
tyeth/adafruit_ArduinoCore-samd#1) that rerouted theAdafruit_USBD_CDC.hinclude to"arduino/Adafruit_USBD_CDC.h"underPLATFORMIO. That rested on the inverted premise that PlatformIO exposessrcbut notsrc/arduino; it breaks every translation unit that includesArduino.h, and it was never compiled. Closed.