Skip to content

libraries: guard TinyUSB include under PlatformIO so SAMD builds compile - #396

Open
tyeth-ai-assisted wants to merge 1 commit into
adafruit:masterfrom
tyeth-ai-assisted:platformio-tinyusb-include-guards
Open

libraries: guard TinyUSB include under PlatformIO so SAMD builds compile#396
tyeth-ai-assisted wants to merge 1 commit into
adafruit:masterfrom
tyeth-ai-assisted:platformio-tinyusb-include-guards

Conversation

@tyeth-ai-assisted

@tyeth-ai-assisted tyeth-ai-assisted commented Aug 28, 2026

Copy link
Copy Markdown

Problem

Every PlatformIO SAMD build with USE_TINYUSB fails to compile:

libraries/Servo/src/samd/Servo.cpp:26:10: fatal error: Adafruit_TinyUSB.h: No such file or directory
   26 | #include <Adafruit_TinyUSB.h>

arduino-cli is unaffected, which is why this has gone unnoticed.

Why

Adafruit_TinyUSB.h lives in the library's src/, and only src/arduino is ever put on the include path — by both toolchains:

  • platform.txt (line 80) for arduino-cli:
    "-I{runtime.platform.path}/libraries/Adafruit_TinyUSB_Arduino/src/arduino"
  • PlatformIO's atmelsam platform appends that same .../Adafruit_TinyUSB_Arduino/src/arduino to the global CPPPATH for the Adafruit vendor core (builder/frameworks/arduino/arduino-samd.py).

So Adafruit_USBD_CDC.h resolves everywhere, while <Adafruit_TinyUSB.h> resolves from neither.

Under arduino-cli it works anyway, because that unresolved include is itself what drives the Arduino builder's library discovery: it finds Adafruit_TinyUSB_Arduino, adds its src/ 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 receive lib_deps include 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:

#ifdef USE_TINYUSB
// For Serial when selecting TinyUSB (also causes the Arduino builder to link
// the TinyUSB library). Outside PlatformIO this include must stay plain and
// unconditional: the Arduino builder discovers the library from it, and its
// dependency-detection pass cannot parse a __has_include() expression.
#ifdef PLATFORMIO
// PlatformIO does not give framework-bundled libraries the lib_deps include
// paths, so the header can be unreachable here; skip it instead of failing.
#if !defined(__has_include) || __has_include(<Adafruit_TinyUSB.h>)
#include <Adafruit_TinyUSB.h>
#endif
#else
#include <Adafruit_TinyUSB.h>
#endif
#endif

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 both usbstack=tinyusb legs red with undefined reference to 'Serial' and Adafruit_USBD_CDC::begin(unsigned long) in the Wire and SAMD_AnalogCorrection examples. 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 PLATFORMIO puts it in a branch arduino-cli skips without evaluating, leaving that toolchain untouched.

Servo.cpp is 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

PlatformIOAdafruit WipperSnapper (TinyUSB + SPI + Wire + ~60 sensor libraries), env adafruit_pyportal_m4, PlatformIO 6, USE_TINYUSB=1. Each tree was built from the installed platformio/framework-arduino-samd-adafruit@1.10716.0 package (= this repo at 1.7.16) consumed via platform_packages = framework-arduino-samd-adafruit@symlink://<tree>, so these five files are the only variable. They are byte-identical between master and 1.7.16, so the result applies directly to this branch.

framework tree PlatformIO
unmodified FAILEDServo.cpp:26: fatal error: Adafruit_TinyUSB.h: No such file or directory
this PR SUCCESS — Flash 44.7% (468,924 B), RAM 5.9% (15,400 B)

arduino-cli — this repo's own matrix, run three times on this PR:

branch state tinyusb legs other 10 legs
baseline (master tree, guards not applied) pass pass
flat __has_include guard fail (undefined Serial) pass
nested guard (this PR) pass pass

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 the Adafruit_USBD_CDC.h include to "arduino/Adafruit_USBD_CDC.h" under PLATFORMIO. That rested on the inverted premise that PlatformIO exposes src but not src/arduino; it breaks every translation unit that includes Arduino.h, and it was never compiled. Closed.

@tyeth

tyeth commented Aug 28, 2026

Copy link
Copy Markdown
Member

Hey @hathach could you have a review of this please?
Testing this PR (https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/tree/fix-ws-v2-global-to-pointer adafruit/Adafruit_Wippersnapper_Arduino#974) it was found that Servo.cpp failed to compile due to platformIO LDF being awkward compared to arduino-cli.

This rectifies the include error that occurs otherwise.

@tyeth
tyeth requested a review from hathach August 28, 2026 12:52
@tyeth

tyeth commented Aug 28, 2026

Copy link
Copy Markdown
Member

Oops, failing CI on untested path, will fix

@tyeth-ai-assisted
tyeth-ai-assisted force-pushed the platformio-tinyusb-include-guards branch from 8904a30 to a9a2c20 Compare August 28, 2026 12:56
@tyeth-ai-assisted tyeth-ai-assisted changed the title libraries: guard TinyUSB include with __has_include so PlatformIO SAMD builds compile libraries: guard TinyUSB include under PlatformIO so SAMD builds compile Aug 28, 2026
@tyeth-ai-assisted
tyeth-ai-assisted force-pushed the platformio-tinyusb-include-guards branch from a9a2c20 to 26c39c0 Compare August 28, 2026 13:06
@tyeth-ai-assisted
tyeth-ai-assisted force-pushed the platformio-tinyusb-include-guards branch 2 times, most recently from 00da71d to 06da1e0 Compare August 28, 2026 13:16
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
@tyeth-ai-assisted
tyeth-ai-assisted force-pushed the platformio-tinyusb-include-guards branch from 06da1e0 to c63ec86 Compare August 28, 2026 13:25

@tyeth tyeth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments are a bit long, worth a replacement possibly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants