From 0e5b4c40771888cc226f13874d7d4552ef3c91db Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Fri, 12 Jun 2026 08:58:09 +0100 Subject: [PATCH 1/6] =?UTF-8?q?ci:=20discover=20and=20build=20custom=20Pla?= =?UTF-8?q?tformIO=20envs=20from=20usermods=20platformi=E2=80=A6=20(#5649)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: discover and build custom PlatformIO envs from usermods platformio_override.ini.sample files Adds two new jobs to the Usermod CI workflow to address issue #5648: - get_custom_build_envs: scans all usermods/*/platformio_override.ini.sample files and emits a matrix of {usermod, env} pairs by extracting [env:*] section names - build_custom: builds each discovered environment by copying the .ini.sample as platformio_override.ini and running pio run -e This allows PRs introducing usermods with custom build environments (such as pixels_dice_tray) to have those environments validated in CI without committing platformio_override.ini to the repository. * ci: consolidate usermod build envs into per-usermod platformio_override.ini.sample files Move usermod-specific PlatformIO environments out of the root platformio_override.sample.ini and into dedicated files within each usermod's own directory, making them discoverable by CI: * run on push * Fix AHT10_v2 example * no d1_mini env * no d1_mini env * ci: filter usermod matrix to only build changed usermod directories Instead of building every usermod with a library.json on each PR, use git diff to identify which usermods/ subdirectories were actually touched and intersect that with the known-good library.json list. This reduces CI time significantly for PRs that only modify one or two usermods (previously every PR triggered ~40 usermods × 4 chipsets). Also removes the unnecessary PlatformIO install from get_usermod_envs (the step only uses shell/jq, not pio) and adds fetch-depth: 0 to ensure the base branch is available for the diff. * SN_Photoresistor * fix(ci): use PR base SHA for diff and guard jobs against push events github.base_ref is empty on push events, causing 'ambiguous argument origin/...HEAD'. Fix by: - Adding github.event_name == 'pull_request' guard to both jobs so they never run on push events where pull_request context is absent - Replacing origin/${{ github.base_ref }}...HEAD with ${{ github.event.pull_request.base.sha }} HEAD which uses the concrete base commit SHA provided by GitHub directly * ESP32 builds all V4 * fix: rename/fix platformio_override sample files across usermods - Rename *.ini (gitignored) and *.sample.ini (wrong extension order) to the correct platformio_override.ini.sample convention so CI discovers them - Fix AHT10_v2: custom_usermods AHT10 → AHT10_v2 (match library.json name) - Fix INA226_v2: custom_usermods INA226 → INA226_v2 (both envs) - Fix TTGO-T-Display: replace direct [env:esp32dev] override with a named env that extends esp32dev; add note that library.json is absent so custom_usermods is not available for this usermod Affected usermods: AHT10_v2, DHT, INA226_v2, SN_Photoresistor, TTGO-T-Display, Temperature, four_line_display_ALT, rotary_encoder_ui_ALT * perf: filter custom build matrix to changed usermods on PRs On pull_request events, get_custom_build_envs now only scans usermod directories that changed in the PR (matching the behaviour of the get_usermod_envs job). On push events (e.g. merging a PR) it still scans all usermods to validate the full set. * fix release name * fix release name * fix: resolve 8 CI build failures in custom usermod environments - BME280_v2: fix default_envs typo (usermod_bme280_esp8266_2m → usermod_esp8266_2m to match actual [env:] section name) - DHT: replace extends env:custom32_LEDPIN_16 (not in this fork) with env:esp32dev + -D LEDPIN=16; rename env accordingly - SN_Photoresistor: replace ${common.build_flags_esp8266} (key absent in this fork) with ${env:esp8266_2m.build_flags} / lib_deps equivalent - sht: guard ESP.getChipModel() behind #ifdef ARDUINO_ARCH_ESP32 — method does not exist on ESP8266 (fixes custom_esp8266_2m_usermod_sht build) - EleksTube_IPS: delete platformio_override.ini.sample — library.json is disabled so the env cannot build; delete rather than ship a broken sample - pixels_dice_tray: exclude from build_custom matrix (same as standard matrix) — BLE library incompatibility with current IDF causes build failure - workflow: add PWM_fan / BME68X_v2 to exclusion list in get_custom_build_envs for consistency with get_usermod_envs * Do not add platformio_override.ini.sample just because example was in readme --------- Co-authored-by: Frank Möhle <91616163+softhack007@users.noreply.github.com> --- .github/workflows/usermods.yml | 116 ++++++++++++++++-- platformio_override.sample.ini | 31 +---- usermods/AHT10_v2/platformio_override.ini | 5 - ...ide.ini => platformio_override.ini.sample} | 16 +-- usermods/INA226_v2/platformio_override.ini | 6 - ...ide.ini => platformio_override.ini.sample} | 12 +- .../TTGO-T-Display/platformio_override.ini | 8 -- usermods/Temperature/platformio_override.ini | 5 - .../platformio_override.ini.sample | 6 +- usermods/sht/sht.cpp | 6 + ...ple.ini => platformio_override.ini.sample} | 6 +- ...ple.ini => platformio_override.ini.sample} | 6 +- 12 files changed, 137 insertions(+), 86 deletions(-) delete mode 100644 usermods/AHT10_v2/platformio_override.ini rename usermods/DHT/{platformio_override.ini => platformio_override.ini.sample} (65%) delete mode 100644 usermods/INA226_v2/platformio_override.ini rename usermods/SN_Photoresistor/{platformio_override.ini => platformio_override.ini.sample} (72%) delete mode 100644 usermods/TTGO-T-Display/platformio_override.ini delete mode 100644 usermods/Temperature/platformio_override.ini rename usermods/usermod_v2_four_line_display_ALT/{platformio_override.sample.ini => platformio_override.ini.sample} (50%) rename usermods/usermod_v2_rotary_encoder_ui_ALT/{platformio_override.sample.ini => platformio_override.ini.sample} (58%) diff --git a/.github/workflows/usermods.yml b/.github/workflows/usermods.yml index 24eda32ece..c00064ee77 100644 --- a/.github/workflows/usermods.yml +++ b/.github/workflows/usermods.yml @@ -4,6 +4,9 @@ on: pull_request: paths: - usermods/** + push: + paths: + - usermods/** env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -12,28 +15,40 @@ jobs: get_usermod_envs: # Only run for pull requests from forks (not from branches within wled/WLED) - if: github.event.pull_request.head.repo.full_name != github.repository + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository name: Gather Usermods runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 with: - python-version: '3.12' - cache: 'pip' - - name: Install PlatformIO - run: pip install -r requirements.txt - - name: Get default environments + fetch-depth: 0 + - name: Get changed usermod environments id: envs run: | - echo "usermods=$(find usermods/ -name library.json | xargs dirname | xargs -n 1 basename | jq -R | grep -v PWM_fan | grep -v BME68X_v2| grep -v pixels_dice_tray | jq --slurp -c)" >> $GITHUB_OUTPUT + # Usermods whose directories changed in this PR + changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} HEAD \ + | grep '^usermods/' | cut -d/ -f2 | sort -u || true) + + # All usermods with a library.json (excluding known-incompatible ones) + all=$(find usermods/ -name library.json \ + | xargs dirname | xargs -n 1 basename \ + | grep -v PWM_fan | grep -v BME68X_v2 | grep -v pixels_dice_tray \ + | sort || true) + + if [ -z "$changed" ] || [ -z "$all" ]; then + echo "usermods=[]" >> $GITHUB_OUTPUT + else + usermods=$(comm -12 <(echo "$all") <(echo "$changed") | jq -R | jq --slurp -c) + echo "usermods=$usermods" >> $GITHUB_OUTPUT + fi outputs: usermods: ${{ steps.envs.outputs.usermods }} build: # Only run for pull requests from forks (not from branches within wled/WLED) - if: github.event.pull_request.head.repo.full_name != github.repository + # Skip when no changed usermods were found (e.g. only non-library changes) + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && needs.get_usermod_envs.outputs.usermods != '[]' name: Build Enviornments runs-on: ubuntu-latest needs: get_usermod_envs @@ -74,4 +89,85 @@ jobs: cat platformio_override.ini - name: Build firmware - run: pio run -e ${{ matrix.environment }} + run: pio run -e ${{ matrix.environment }} + + + get_custom_build_envs: + name: Gather Custom Build Environments + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Find usermods with custom build environments + id: custom_envs + run: | + # On PRs: only scan usermods whose directories changed. + # On push: scan all usermods (validates the full set on merge). + if [ "${{ github.event_name }}" = "pull_request" ]; then + changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} HEAD \ + | grep '^usermods/' | cut -d/ -f2 | sort -u || true) + if [ -z "$changed" ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + exit 0 + fi + samples=$(for mod in $changed; do + f="usermods/$mod/platformio_override.ini.sample" + [ -f "$f" ] && echo "$f" + done | sort) + else + samples=$(find usermods/ -name "platformio_override.ini.sample" | sort) + fi + + result='[]' + for sample in $samples; do + usermod=$(dirname "$sample" | xargs basename) + # Skip usermods known to be incompatible (same list as get_usermod_envs) + case "$usermod" in PWM_fan|BME68X_v2|pixels_dice_tray) continue ;; esac + envs=$(grep -E '^\[env:[^]]+\]' "$sample" | sed 's/^\[env:\(.*\)\]$/\1/') + for env in $envs; do + result=$(echo "$result" | jq --arg u "$usermod" --arg e "$env" '. + [{usermod: $u, env: $e}]') + done + done + echo "matrix=$(echo "$result" | jq -c '.')" >> $GITHUB_OUTPUT + outputs: + matrix: ${{ steps.custom_envs.outputs.matrix }} + + + build_custom: + name: Build Custom Env (${{ matrix.usermod }} / ${{ matrix.env }}) + runs-on: ubuntu-latest + needs: get_custom_build_envs + if: needs.get_custom_build_envs.outputs.matrix != '[]' + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.get_custom_build_envs.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + - run: npm ci + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + path: | + ~/.platformio/.cache + ~/.buildcache + build_output + key: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }} + restore-keys: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}- + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + - name: Install PlatformIO + run: pip install -r requirements.txt + - name: Apply custom build environment + run: cp -v "usermods/${{ matrix.usermod }}/platformio_override.ini.sample" platformio_override.ini + - name: Build firmware + run: pio run -e ${{ matrix.env }} diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 956262b970..484005bad8 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -518,42 +518,15 @@ lib_deps = ${esp8266.lib_deps} # ------------------------------------------------------------------------------ # EleksTube-IPS +# See usermods/EleksTube_IPS/platformio_override.ini.sample # ------------------------------------------------------------------------------ -[env:elekstube_ips] -extends = esp32 ;; use default esp32 platform -board = esp32dev -upload_speed = 921600 -custom_usermods = ${env:esp32dev.custom_usermods} RTC EleksTube_IPS -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED - -D DATA_PINS=12 - -D RLYPIN=27 - -D BTNPIN=34 - -D PIXEL_COUNTS=6 - # Display config - -D ST7789_DRIVER - -D TFT_WIDTH=135 - -D TFT_HEIGHT=240 - -D CGRAM_OFFSET - -D TFT_SDA_READ - -D TFT_MOSI=23 - -D TFT_SCLK=18 - -D TFT_DC=25 - -D TFT_RST=26 - -D SPI_FREQUENCY=40000000 - -D USER_SETUP_LOADED -monitor_filters = esp32_exception_decoder # ------------------------------------------------------------------------------ # Usermod examples # ------------------------------------------------------------------------------ -# 433MHz RF remote example for esp32dev -[env:esp32dev_usermod_RF433] -extends = env:esp32dev -custom_usermods = - ${env:esp32dev.custom_usermods} - RF433 +# 433MHz RF remote example: see usermods/usermod_v2_RF433/platformio_override.ini.sample # External usermod from a git repository. # The library's `library.json` must include `"build": {"libArchive": false}`. diff --git a/usermods/AHT10_v2/platformio_override.ini b/usermods/AHT10_v2/platformio_override.ini deleted file mode 100644 index 74dcd659bb..0000000000 --- a/usermods/AHT10_v2/platformio_override.ini +++ /dev/null @@ -1,5 +0,0 @@ -[env:aht10_example] -extends = env:esp32dev -build_flags = - ${common.build_flags} ${esp32.build_flags} - ; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal diff --git a/usermods/DHT/platformio_override.ini b/usermods/DHT/platformio_override.ini.sample similarity index 65% rename from usermods/DHT/platformio_override.ini rename to usermods/DHT/platformio_override.ini.sample index 6ec2fb9992..c991d3d831 100644 --- a/usermods/DHT/platformio_override.ini +++ b/usermods/DHT/platformio_override.ini.sample @@ -8,13 +8,13 @@ ; USERMOD_DHT_MQTT - publish measurements to the MQTT broker ; USERMOD_DHT_STATS - For debug, report delay stats -[env:d1_mini_usermod_dht_C] -extends = env:d1_mini -custom_usermods = ${env:d1_mini.custom_usermods} DHT -build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT_CELSIUS +[env:esp8266_2m_usermod_dht_C] +extends = env:esp8266_2m +custom_usermods = ${env:esp8266_2m.custom_usermods} DHT +build_flags = ${env:esp8266_2m.build_flags} -D USERMOD_DHT_CELSIUS -[env:custom32_LEDPIN_16_usermod_dht_C] -extends = env:custom32_LEDPIN_16 -custom_usermods = ${env:custom32_LEDPIN_16.custom_usermods} DHT -build_flags = ${env:custom32_LEDPIN_16.build_flags} -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS +[env:esp32dev_LEDPIN_16_usermod_dht_C] +extends = env:esp32dev +custom_usermods = ${env:esp32dev.custom_usermods} DHT +build_flags = ${env:esp32dev.build_flags} -D LEDPIN=16 -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS diff --git a/usermods/INA226_v2/platformio_override.ini b/usermods/INA226_v2/platformio_override.ini deleted file mode 100644 index 9968cbf721..0000000000 --- a/usermods/INA226_v2/platformio_override.ini +++ /dev/null @@ -1,6 +0,0 @@ -[env:ina226_example] -extends = env:esp32dev -custom_usermods = ${env:esp32dev.custom_usermods} INA226_v2 -build_flags = - ${env:esp32dev.build_flags} - ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal diff --git a/usermods/SN_Photoresistor/platformio_override.ini b/usermods/SN_Photoresistor/platformio_override.ini.sample similarity index 72% rename from usermods/SN_Photoresistor/platformio_override.ini rename to usermods/SN_Photoresistor/platformio_override.ini.sample index 91bc5de2a6..d0588ded5c 100644 --- a/usermods/SN_Photoresistor/platformio_override.ini +++ b/usermods/SN_Photoresistor/platformio_override.ini.sample @@ -1,6 +1,5 @@ ; Options ; ------- -; USERMOD_SN_PHOTORESISTOR - define this to have this user mod included wled00\usermods_list.cpp ; USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds ; USERMOD_SN_PHOTORESISTOR_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 20 seconds ; USERMOD_SN_PHOTORESISTOR_REFERENCE_VOLTAGE - the voltage supplied to the sensor, defaults to 5v @@ -8,9 +7,10 @@ ; USERMOD_SN_PHOTORESISTOR_RESISTOR_VALUE - the resistor size, defaults to 10000.0 (10K hms) ; USERMOD_SN_PHOTORESISTOR_OFFSET_VALUE - the offset value to report on, defaults to 25 ; -[env:usermod_sn_photoresistor_d1_mini] -extends = env:d1_mini +[env:usermod_sn_photoresistor_esp8266_2m] +extends = env:esp8266_2m +custom_usermods = ${env:esp8266_2m.custom_usermods} SN_Photoresistor build_flags = - ${common.build_flags_esp8266} - -D USERMOD_SN_PHOTORESISTOR -lib_deps = ${env.lib_deps} + ${env:esp8266_2m.build_flags} + -D USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL=60 +lib_deps = ${env:esp8266_2m.lib_deps} diff --git a/usermods/TTGO-T-Display/platformio_override.ini b/usermods/TTGO-T-Display/platformio_override.ini deleted file mode 100644 index 7e42d9a54a..0000000000 --- a/usermods/TTGO-T-Display/platformio_override.ini +++ /dev/null @@ -1,8 +0,0 @@ -[env:esp32dev] -build_flags = ${common.build_flags_esp32} -; PIN defines - uncomment and change, if needed: -; -D LEDPIN=2 - -D BTNPIN=35 -; -D IRPIN=4 -; -D RLYPIN=12 -; -D RLYMDE=1 diff --git a/usermods/Temperature/platformio_override.ini b/usermods/Temperature/platformio_override.ini deleted file mode 100644 index a53b5974d9..0000000000 --- a/usermods/Temperature/platformio_override.ini +++ /dev/null @@ -1,5 +0,0 @@ -; Options -; ------- -; USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds -; - diff --git a/usermods/pixels_dice_tray/platformio_override.ini.sample b/usermods/pixels_dice_tray/platformio_override.ini.sample index 6b4fa7768e..f51c2190b3 100644 --- a/usermods/pixels_dice_tray/platformio_override.ini.sample +++ b/usermods/pixels_dice_tray/platformio_override.ini.sample @@ -13,7 +13,7 @@ board_build.partitions = ${esp32.large_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=T-QT-PRO-8MB +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"T-QT-PRO-8MB_dice\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -75,7 +75,7 @@ board_build.partitions = ${esp32.large_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_qspi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_qspi_dice\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -105,7 +105,7 @@ lib_deps = ${esp32s3.lib_deps} # https://github.com/wled-dev/WLED/issues/1382 ; [env:esp32dev_dice] ; extends = env:esp32dev -; build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 +; build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_dice\" ; ; Enable Pixels dice mod ; -D USERMOD_PIXELS_DICE_TRAY ; lib_deps = ${esp32.lib_deps} diff --git a/usermods/sht/sht.cpp b/usermods/sht/sht.cpp index e1eb9e885f..f7f4be4bab 100644 --- a/usermods/sht/sht.cpp +++ b/usermods/sht/sht.cpp @@ -143,7 +143,13 @@ void ShtUsermod::appendDeviceToMqttDiscoveryMessage(JsonDocument& root) { device[F("ids")] = escapedMac.c_str(); device[F("name")] = serverDescription; device[F("sw")] = versionString; + // AI: below section was generated by an AI + #ifdef ARDUINO_ARCH_ESP32 device[F("mdl")] = ESP.getChipModel(); + #else + device[F("mdl")] = F("ESP8266"); + #endif + // AI: end device[F("mf")] = F("espressif"); } diff --git a/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini b/usermods/usermod_v2_four_line_display_ALT/platformio_override.ini.sample similarity index 50% rename from usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini rename to usermods/usermod_v2_four_line_display_ALT/platformio_override.ini.sample index f4fa8c9d8b..ef0e00848f 100644 --- a/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini +++ b/usermods/usermod_v2_four_line_display_ALT/platformio_override.ini.sample @@ -2,10 +2,10 @@ default_envs = esp32dev_fld [env:esp32dev_fld] -extends = env:esp32dev_V4 -custom_usermods = ${env:esp32dev_V4.custom_usermods} four_line_display_ALT +extends = env:esp32dev +custom_usermods = ${env:esp32dev.custom_usermods} four_line_display_ALT build_flags = - ${env:esp32dev_V4.build_flags} + ${env:esp32dev.build_flags} -D FLD_TYPE=SH1106 -D I2CSCLPIN=27 -D I2CSDAPIN=26 diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.ini.sample similarity index 58% rename from usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini rename to usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.ini.sample index 2511d2fa38..c943bdbe25 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.ini.sample @@ -2,10 +2,10 @@ default_envs = esp32dev_re [env:esp32dev_re] -extends = env:esp32dev_V4 -custom_usermods = ${env:esp32dev_V4.custom_usermods} rotary_encoder_ui_ALT +extends = env:esp32dev +custom_usermods = ${env:esp32dev.custom_usermods} rotary_encoder_ui_ALT build_flags = - ${env:esp32dev_V4.build_flags} + ${env:esp32dev.build_flags} -D USERMOD_ROTARY_ENCODER_GPIO=INPUT -D ENCODER_DT_PIN=21 -D ENCODER_CLK_PIN=23 From a6fbf388e4aabdf3261d76886d0c6f9dabecddb1 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 6 Jul 2026 03:00:55 +0000 Subject: [PATCH 2/6] CI: Build usermod environments with WLED_DEBUG Catch any incorrect debug prints for all targets. --- usermods/platformio_override.usermods.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usermods/platformio_override.usermods.ini b/usermods/platformio_override.usermods.ini index fbcb9d3d0c..ccdc9b2856 100644 --- a/usermods/platformio_override.usermods.ini +++ b/usermods/platformio_override.usermods.ini @@ -7,6 +7,7 @@ build_flags = ${env:esp32dev.build_flags} -DTOUCH_CS=9 -D WLED_USE_SD_SPI ;; help a few usermods that require special flags to compile custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:esp32dev.build_flags} -D WLED_DEBUG [env:usermods_esp32c3] @@ -16,6 +17,7 @@ build_flags = ${env:esp32c3dev.build_flags} board = esp32-c3-devkitm-1 custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:esp32c3dev.build_flags} -D WLED_DEBUG [env:usermods_esp32s2] @@ -24,6 +26,7 @@ build_flags = ${env:lolin_s2_mini.build_flags} -DTOUCH_CS=9 -D WLED_USE_SD_SPI ;; help a few usermods that require special flags to compile custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:lolin_s2_mini.build_flags} -D WLED_DEBUG [env:usermods_esp32s3] @@ -33,6 +36,7 @@ build_flags = ${env:esp32s3dev_16MB_opi.build_flags} -D WLED_DEBUG ;; try to catch broken DEBUG_PRINT statements custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:esp32s3dev_16MB_opi.build_flags} -D WLED_DEBUG From e10fa37709c3b45662e471be40702443f30ae10f Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 6 Jul 2026 14:01:46 +0000 Subject: [PATCH 3/6] ci(usermods): fold custom-env builds into the standard build job The usermod CI previously ran two parallel tracks: a "plain" build (get_usermod_envs + build) and a separate "custom env" build (get_custom_build_envs + build_custom) for usermods needing bespoke PlatformIO settings. This duplicated most of the gather/build logic. Merge them into a single get_usermod_envs + build pipeline. For each changed usermod, if it supplies a sample ini, use it; otherwise build with the reference platformio_override.usermods.ini. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/usermods.yml | 144 +++++++++------------------------ 1 file changed, 40 insertions(+), 104 deletions(-) diff --git a/.github/workflows/usermods.yml b/.github/workflows/usermods.yml index c00064ee77..ce7b8795ef 100644 --- a/.github/workflows/usermods.yml +++ b/.github/workflows/usermods.yml @@ -22,41 +22,51 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Get changed usermod environments + - name: Get changed usermod build matrix id: envs run: | # Usermods whose directories changed in this PR changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} HEAD \ | grep '^usermods/' | cut -d/ -f2 | sort -u || true) - # All usermods with a library.json (excluding known-incompatible ones) - all=$(find usermods/ -name library.json \ - | xargs dirname | xargs -n 1 basename \ - | grep -v PWM_fan | grep -v BME68X_v2 | grep -v pixels_dice_tray \ - | sort || true) + # Shared override defining the standard build environments + default_ini="usermods/platformio_override.usermods.ini" - if [ -z "$changed" ] || [ -z "$all" ]; then - echo "usermods=[]" >> $GITHUB_OUTPUT - else - usermods=$(comm -12 <(echo "$all") <(echo "$changed") | jq -R | jq --slurp -c) - echo "usermods=$usermods" >> $GITHUB_OUTPUT - fi + matrix='[]' + for mod in $changed; do + # Skip usermods known to be incompatible + case "$mod" in PWM_fan|BME68X_v2|pixels_dice_tray) continue ;; esac + # Only build usermods that ship a library.json + [ -f "usermods/$mod/library.json" ] || continue + + # A usermod may provide its own build environments via a sample override; + # otherwise the standard environments from the shared override are used. + # Either way, the env list comes from the ini file rather than being + # duplicated here. + sample="usermods/$mod/platformio_override.ini.sample" + [ -f "$sample" ] && ini="$sample" || ini="$default_ini" + envs=$(grep -oE '^\[env:[^]]+\]' "$ini" | sed 's/^\[env:\(.*\)\]$/\1/') + + for env in $envs; do + matrix=$(echo "$matrix" | jq --arg u "$mod" --arg e "$env" -c '. + [{usermod: $u, env: $e}]') + done + done + echo "matrix=$matrix" >> $GITHUB_OUTPUT outputs: - usermods: ${{ steps.envs.outputs.usermods }} + matrix: ${{ steps.envs.outputs.matrix }} build: # Only run for pull requests from forks (not from branches within wled/WLED) - # Skip when no changed usermods were found (e.g. only non-library changes) - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && needs.get_usermod_envs.outputs.usermods != '[]' - name: Build Enviornments + # Skip when no buildable usermods were found (e.g. only non-library changes) + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && needs.get_usermod_envs.outputs.matrix != '[]' + name: Build (${{ matrix.usermod }} / ${{ matrix.env }}) runs-on: ubuntu-latest needs: get_usermod_envs strategy: fail-fast: false matrix: - usermod: ${{ fromJSON(needs.get_usermod_envs.outputs.usermods) }} - environment: [usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3] + include: ${{ fromJSON(needs.get_usermod_envs.outputs.matrix) }} steps: - uses: actions/checkout@v4 - name: Set up Node.js @@ -72,8 +82,8 @@ jobs: ~/.platformio/.cache ~/.buildcache build_output - key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }} - restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}- + key: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }} + restore-keys: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}- - name: Set up Python uses: actions/setup-python@v5 with: @@ -81,93 +91,19 @@ jobs: cache: 'pip' - name: Install PlatformIO run: pip install -r requirements.txt - - name: Add usermods environment + - name: Configure build environment run: | - cp -v usermods/platformio_override.usermods.ini platformio_override.ini - echo >> platformio_override.ini - echo "custom_usermods = ${{ matrix.usermod }}" >> platformio_override.ini - cat platformio_override.ini - - - name: Build firmware - run: pio run -e ${{ matrix.environment }} - - - get_custom_build_envs: - name: Gather Custom Build Environments - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Find usermods with custom build environments - id: custom_envs - run: | - # On PRs: only scan usermods whose directories changed. - # On push: scan all usermods (validates the full set on merge). - if [ "${{ github.event_name }}" = "pull_request" ]; then - changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} HEAD \ - | grep '^usermods/' | cut -d/ -f2 | sort -u || true) - if [ -z "$changed" ]; then - echo "matrix=[]" >> $GITHUB_OUTPUT - exit 0 - fi - samples=$(for mod in $changed; do - f="usermods/$mod/platformio_override.ini.sample" - [ -f "$f" ] && echo "$f" - done | sort) + # Use the usermod's own override when it provides one, otherwise apply + # the shared usermods override with this usermod enabled. + sample="usermods/${{ matrix.usermod }}/platformio_override.ini.sample" + if [ -f "$sample" ]; then + cp -v "$sample" platformio_override.ini else - samples=$(find usermods/ -name "platformio_override.ini.sample" | sort) + cp -v usermods/platformio_override.usermods.ini platformio_override.ini + echo >> platformio_override.ini + echo "custom_usermods = ${{ matrix.usermod }}" >> platformio_override.ini fi + cat platformio_override.ini - result='[]' - for sample in $samples; do - usermod=$(dirname "$sample" | xargs basename) - # Skip usermods known to be incompatible (same list as get_usermod_envs) - case "$usermod" in PWM_fan|BME68X_v2|pixels_dice_tray) continue ;; esac - envs=$(grep -E '^\[env:[^]]+\]' "$sample" | sed 's/^\[env:\(.*\)\]$/\1/') - for env in $envs; do - result=$(echo "$result" | jq --arg u "$usermod" --arg e "$env" '. + [{usermod: $u, env: $e}]') - done - done - echo "matrix=$(echo "$result" | jq -c '.')" >> $GITHUB_OUTPUT - outputs: - matrix: ${{ steps.custom_envs.outputs.matrix }} - - - build_custom: - name: Build Custom Env (${{ matrix.usermod }} / ${{ matrix.env }}) - runs-on: ubuntu-latest - needs: get_custom_build_envs - if: needs.get_custom_build_envs.outputs.matrix != '[]' - strategy: - fail-fast: false - matrix: - include: ${{ fromJSON(needs.get_custom_build_envs.outputs.matrix) }} - steps: - - uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'npm' - - run: npm ci - - name: Cache PlatformIO - uses: actions/cache@v4 - with: - path: | - ~/.platformio/.cache - ~/.buildcache - build_output - key: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }} - restore-keys: pio-${{ runner.os }}-${{ matrix.env }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}- - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - cache: 'pip' - - name: Install PlatformIO - run: pip install -r requirements.txt - - name: Apply custom build environment - run: cp -v "usermods/${{ matrix.usermod }}/platformio_override.ini.sample" platformio_override.ini - name: Build firmware run: pio run -e ${{ matrix.env }} From 502a1e5d09e06a5cdc6e0a04dedc678606eebaab Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 6 Jul 2026 14:05:17 +0000 Subject: [PATCH 4/6] Enable PWM_fan test build --- .github/workflows/usermods.yml | 2 +- .../PWM_fan/platformio_override.ini.sample | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 usermods/PWM_fan/platformio_override.ini.sample diff --git a/.github/workflows/usermods.yml b/.github/workflows/usermods.yml index ce7b8795ef..b18802299a 100644 --- a/.github/workflows/usermods.yml +++ b/.github/workflows/usermods.yml @@ -35,7 +35,7 @@ jobs: matrix='[]' for mod in $changed; do # Skip usermods known to be incompatible - case "$mod" in PWM_fan|BME68X_v2|pixels_dice_tray) continue ;; esac + case "$mod" in BME68X_v2|pixels_dice_tray) continue ;; esac # Only build usermods that ship a library.json [ -f "usermods/$mod/library.json" ] || continue diff --git a/usermods/PWM_fan/platformio_override.ini.sample b/usermods/PWM_fan/platformio_override.ini.sample new file mode 100644 index 0000000000..864f87d0a2 --- /dev/null +++ b/usermods/PWM_fan/platformio_override.ini.sample @@ -0,0 +1,38 @@ +# CI sample override file: this usermod has dependencies. +# Based on the CI standard but adds the Temperature usermod too. + +[platformio] +default_envs = usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp32s3 + +[env:usermods_esp32] +extends = env:esp32dev +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:esp32dev.build_flags} -D WLED_DEBUG + + +[env:usermods_esp32c3] +extends = env:esp32c3dev +board = esp32-c3-devkitm-1 +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:esp32c3dev.build_flags} -D WLED_DEBUG + + +[env:usermods_esp32s2] +extends = env:lolin_s2_mini +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:lolin_s2_mini.build_flags} -D WLED_DEBUG + + +[env:usermods_esp32s3] +extends = env:esp32s3dev_16MB_opi +custom_usermods = ${usermods.custom_usermods} +board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat +build_flags = ${env:esp32s3dev_16MB_opi.build_flags} -D WLED_DEBUG + + +[usermods] +custom_usermods = PWM_fan + Temperature From 3ff8afc7488c44838faf82238ca3920eb8cc6452 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 6 Jul 2026 23:19:38 +0000 Subject: [PATCH 5/6] CI: Fix injection vulnerability Avoid interpolating GitHub Actions variables directly in to scripts. H/t @coderabbitai --- .github/workflows/build.yml | 8 ++++++-- .github/workflows/usermods.yml | 14 ++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc3003358d..fdd26aaa76 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,9 +76,13 @@ jobs: run: pip install -r requirements.txt - name: Build firmware - run: pio run -e ${{ matrix.environment }} + env: + BUILD_ENV: ${{ matrix.environment }} + run: pio run -e "$BUILD_ENV" - name: Get artifact name from bin filename id: artifact_name + env: + BUILD_ENV: ${{ matrix.environment }} run: | bin=$(ls build_output/release/*.bin 2>/dev/null | head -1) if [ -n "$bin" ]; then @@ -87,7 +91,7 @@ jobs: release_name=$(echo "$base" | sed 's/^[^_]*_[^_]*_//') echo "name=firmware-$release_name" >> $GITHUB_OUTPUT else - echo "name=firmware-${{ matrix.environment }}" >> $GITHUB_OUTPUT + echo "name=firmware-${BUILD_ENV}" >> $GITHUB_OUTPUT fi - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/usermods.yml b/.github/workflows/usermods.yml index b18802299a..93305c843d 100644 --- a/.github/workflows/usermods.yml +++ b/.github/workflows/usermods.yml @@ -24,9 +24,11 @@ jobs: fetch-depth: 0 - name: Get changed usermod build matrix id: envs + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | # Usermods whose directories changed in this PR - changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }} HEAD \ + changed=$(git diff --name-only "$BASE_SHA" HEAD \ | grep '^usermods/' | cut -d/ -f2 | sort -u || true) # Shared override defining the standard build environments @@ -92,18 +94,22 @@ jobs: - name: Install PlatformIO run: pip install -r requirements.txt - name: Configure build environment + env: + USERMOD: ${{ matrix.usermod }} run: | # Use the usermod's own override when it provides one, otherwise apply # the shared usermods override with this usermod enabled. - sample="usermods/${{ matrix.usermod }}/platformio_override.ini.sample" + sample="usermods/${USERMOD}/platformio_override.ini.sample" if [ -f "$sample" ]; then cp -v "$sample" platformio_override.ini else cp -v usermods/platformio_override.usermods.ini platformio_override.ini echo >> platformio_override.ini - echo "custom_usermods = ${{ matrix.usermod }}" >> platformio_override.ini + echo "custom_usermods = ${USERMOD}" >> platformio_override.ini fi cat platformio_override.ini - name: Build firmware - run: pio run -e ${{ matrix.env }} + env: + BUILD_ENV: ${{ matrix.env }} + run: pio run -e "$BUILD_ENV" From 37e7de359e23ec595e342856f1030e6334057cb3 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 6 Jul 2026 02:44:57 +0000 Subject: [PATCH 6/6] Revert bad usermod fixes Usermod environment fixes belong to the usermods -- never the global build environment. This reverts commit 8650188b9577ed59756f195dc0d37ff9ead1b171, 7346ba1a93d6ad9394049510473cca83272fadc2, and a5656705368269ff30247ca1246b386093f164d4 --- platformio.ini | 2 +- usermods/platformio_override.usermods.ini | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index 637cd46483..d764bc286e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -776,7 +776,7 @@ platform = ${esp32_idf_V4.platform} platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_USERMODS\" - -DTOUCH_CS=9 -DWLED_USE_SD_SPI ;; help a few usermods that require special flags to compile + -DTOUCH_CS=9 lib_deps = ${esp32_idf_V4.lib_deps} monitor_filters = esp32_exception_decoder board_build.flash_mode = dio diff --git a/usermods/platformio_override.usermods.ini b/usermods/platformio_override.usermods.ini index ccdc9b2856..ab193520d5 100644 --- a/usermods/platformio_override.usermods.ini +++ b/usermods/platformio_override.usermods.ini @@ -3,8 +3,6 @@ default_envs = usermods_esp32, usermods_esp32c3, usermods_esp32s2, usermods_esp3 [env:usermods_esp32] extends = env:esp32dev -build_flags = ${env:esp32dev.build_flags} - -DTOUCH_CS=9 -D WLED_USE_SD_SPI ;; help a few usermods that require special flags to compile custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat build_flags = ${env:esp32dev.build_flags} -D WLED_DEBUG @@ -12,8 +10,6 @@ build_flags = ${env:esp32dev.build_flags} -D WLED_DEBUG [env:usermods_esp32c3] extends = env:esp32c3dev -build_flags = ${env:esp32c3dev.build_flags} - -DTOUCH_CS=9 -D WLED_USE_SD_SPI ;; help a few usermods that require special flags to compile board = esp32-c3-devkitm-1 custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat @@ -22,8 +18,6 @@ build_flags = ${env:esp32c3dev.build_flags} -D WLED_DEBUG [env:usermods_esp32s2] extends = env:lolin_s2_mini -build_flags = ${env:lolin_s2_mini.build_flags} - -DTOUCH_CS=9 -D WLED_USE_SD_SPI ;; help a few usermods that require special flags to compile custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat build_flags = ${env:lolin_s2_mini.build_flags} -D WLED_DEBUG @@ -31,9 +25,6 @@ build_flags = ${env:lolin_s2_mini.build_flags} -D WLED_DEBUG [env:usermods_esp32s3] extends = env:esp32s3dev_16MB_opi -build_flags = ${env:esp32s3dev_16MB_opi.build_flags} - -DTOUCH_CS=9 -D WLED_USE_SD_SPI ;; help a few usermods that require special flags to compile - -D WLED_DEBUG ;; try to catch broken DEBUG_PRINT statements custom_usermods = ${usermods.custom_usermods} board_build.partitions = ${esp32.extreme_partitions} ; We're gonna need a bigger boat build_flags = ${env:esp32s3dev_16MB_opi.build_flags} -D WLED_DEBUG