Skip to content

Commit 8056492

Browse files
committed
Merge remote-tracking branch 'origin/main' into ElasticAndTwinkle
2 parents 3b45da9 + e321514 commit 8056492

48 files changed

Lines changed: 1503 additions & 789 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
; ----------------------------------------------------------------------------
2+
; platformio_release.ini.template
3+
; ----------------------------------------------------------------------------
4+
; Copied to platformio_release.ini by the release CI workflow
5+
; (.github/workflows/release.yml -> build.yml with `release: true`)
6+
; in order to extend the matrix of `default_envs` built and published
7+
; for tagged releases.
8+
;
9+
; This file overrides `[platformio].default_envs` from platformio.ini via
10+
; `extra_configs`. It MUST list every env that should be released - including
11+
; the regular CI default_envs - because it fully replaces the parent value.
12+
;
13+
; Do NOT commit a generated platformio_release.ini (it's in .gitignore).
14+
; ----------------------------------------------------------------------------
15+
16+
[platformio]
17+
default_envs = nodemcuv2
18+
esp8266_2m
19+
esp01_1m_full
20+
nodemcuv2_160
21+
esp8266_2m_160
22+
esp01_1m_full_160
23+
nodemcuv2_compat
24+
esp8266_2m_compat
25+
esp01_1m_full_compat
26+
esp32dev
27+
esp32dev_debug
28+
esp32_eth
29+
esp32_wrover
30+
lolin_s2_mini
31+
esp32c3dev
32+
esp32c3dev_qio
33+
esp32S3_wroom2
34+
esp32s3dev_16MB_opi
35+
esp32s3dev_8MB_opi
36+
esp32s3dev_8MB_qspi
37+
esp32s3_4M_qspi
38+
usermods
39+
; HUB75 release-only envs
40+
esp32dev_hub75
41+
esp32dev_hub75_forum_pinout
42+
adafruit_matrixportal_esp32s3
43+
esp32s3dev_16MB_opi_hub75 ;; MoonHub
44+
esp32s3dev_4MB_qspi_hub75 ;; HD-WF2

.github/workflows/build.yml

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@ name: WLED Build
33
# Only included into other workflows
44
on:
55
workflow_call:
6-
6+
inputs:
7+
release:
8+
description: 'Build the release env matrix (uses .github/platformio_release.ini.template)'
9+
type: boolean
10+
default: false
11+
12+
env:
13+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
14+
715
jobs:
816

917
get_default_envs:
1018
name: Gather Environments
1119
runs-on: ubuntu-latest
1220
steps:
1321
- uses: actions/checkout@v4
22+
- name: Apply release config
23+
if: inputs.release
24+
run: cp .github/platformio_release.ini.template platformio_release.ini
1425
- uses: actions/setup-python@v5
1526
with:
1627
python-version: '3.12'
@@ -35,6 +46,9 @@ jobs:
3546
environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
3647
steps:
3748
- uses: actions/checkout@v4
49+
- name: Apply release config
50+
if: inputs.release
51+
run: cp .github/platformio_release.ini.template platformio_release.ini
3852
- name: Set up Node.js
3953
uses: actions/setup-node@v4
4054
with:
@@ -51,8 +65,8 @@ jobs:
5165
~/.platformio/.cache
5266
~/.buildcache
5367
build_output
54-
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
55-
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', 'pio-scripts/output_bins.py') }}-
68+
key: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', '.github/platformio_release.ini.template', 'pio-scripts/output_bins.py') }}-${{ hashFiles('wled00/**', 'usermods/**') }}
69+
restore-keys: pio-${{ runner.os }}-${{ matrix.environment }}-${{ hashFiles('platformio.ini', '.github/platformio_release.ini.template', 'pio-scripts/output_bins.py') }}-
5670
- name: Set up Python
5771
uses: actions/setup-python@v5
5872
with:
@@ -63,9 +77,21 @@ jobs:
6377

6478
- name: Build firmware
6579
run: pio run -e ${{ matrix.environment }}
80+
- name: Get artifact name from bin filename
81+
id: artifact_name
82+
run: |
83+
bin=$(ls build_output/release/*.bin 2>/dev/null | head -1)
84+
if [ -n "$bin" ]; then
85+
# Strip WLED_<version>_ prefix from WLED_<version>_<RELEASE_NAME>.bin
86+
base=$(basename "$bin" .bin)
87+
release_name=$(echo "$base" | sed 's/^[^_]*_[^_]*_//')
88+
echo "name=firmware-$release_name" >> $GITHUB_OUTPUT
89+
else
90+
echo "name=firmware-${{ matrix.environment }}" >> $GITHUB_OUTPUT
91+
fi
6692
- uses: actions/upload-artifact@v4
6793
with:
68-
name: firmware-${{ matrix.environment }}
94+
name: ${{ steps.artifact_name.outputs.name }}
6995
path: |
7096
build_output/release/*.bin
7197
build_output/release/*_ESP02*.bin.gz

.github/workflows/nightly.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ on:
77
# This can be used to allow manually triggering nightlies from the web interface
88
workflow_dispatch:
99

10+
env:
11+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
12+
1013
jobs:
1114
wled_build:
1215
uses: ./.github/workflows/build.yml
@@ -26,7 +29,7 @@ jobs:
2629
uses: janheinrichmerker/action-github-changelog-generator@v2.4
2730
with:
2831
token: ${{ secrets.GITHUB_TOKEN }}
29-
sinceTag: v0.15.0
32+
sinceTag: v16.0.0
3033
output: CHANGELOG_NIGHTLY.md
3134
# Exclude issues that were closed without resolution from changelog
3235
excludeLabels: 'stale,wontfix,duplicate,invalid,external,question,use-as-is,not_planned'

.github/workflows/release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ on:
55
tags:
66
- '*'
77

8+
env:
9+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
10+
811
jobs:
912

1013
wled_build:
1114
uses: ./.github/workflows/build.yml
15+
with:
16+
release: true
1217

1318
release:
1419
name: Create Release

.github/workflows/usermods.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ on:
44
pull_request:
55
paths:
66
- usermods/**
7-
7+
8+
env:
9+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
10+
811
jobs:
912

1013
get_usermod_envs:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ __pycache__/
1616

1717
esp01-update.sh
1818
platformio_override.ini
19+
platformio_release.ini
1920
replace_fs.py
2021
wled-update.sh
2122

AGENTS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ See also: `.github/copilot-instructions.md`, `.github/agent-build.instructions.m
1414
| `npm run build` | Build web UI into `wled00/html_*.h` / `wled00/js_*.h` | 30s |
1515
| `npm test` | Run test suite (Node.js built-in `node --test`) | 2 min |
1616
| `npm run dev` | Watch mode — auto-rebuilds web UI on changes | continuous |
17-
| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 30 min |
18-
| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 30 min |
17+
| `pio run -e esp32dev` | Build firmware (ESP32, most common target) | 5 min |
18+
| `pio run -e nodemcuv2` | Build firmware (ESP8266) | 5 min |
1919

2020
**Always run `npm ci && npm run build` before `pio run`.** The web UI build generates
2121
required C headers for firmware compilation.
@@ -176,7 +176,9 @@ No automated linting is configured. Match existing code style in files you edit.
176176
## General Rules
177177
178178
- Repository language is English
179+
- The `docs/` folder is for developer/contributor information (coding conventions, architecture, etc.). User documentation is maintained in the [wled/WLED-Docs](https://github.com/wled/WLED-Docs) repository.
179180
- Never edit or commit auto-generated `wled00/html_*.h` / `wled00/js_*.h`
181+
- When updating an existing PR, retain the original description. Only modify it to ensure technical accuracy. Add change logs after the existing description.
180182
- No force-push on open PRs
181183
- Changes to `platformio.ini` require maintainer approval
182184
- Remove dead/unused code — justify or delete it

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ This lets you update your PR if needed, while you can work on other tasks in 'ma
3030
3131
### Target branch for pull requests
3232

33-
Please make all PRs against the `main` branch.
33+
> [!IMPORTANT]
34+
> Please make all PRs against the `main` branch.
3435
3536
### Describing your PR
3637

pio-scripts/dynarray.py

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,85 @@
22
# This is implemented as a pio post-script to ensure that we can
33
# place our linker script at the correct point in the command arguments.
44
Import("env")
5+
import shutil
56
from pathlib import Path
67

7-
platform = env.get("PIOPLATFORM")
8-
script_file = Path(f"tools/dynarray_{platform}.ld")
9-
if script_file.is_file():
10-
linker_script = f"-T{script_file}"
11-
if platform == "espressif32":
12-
# For ESP32, the script must be added at the right point in the list
13-
linkflags = env.get("LINKFLAGS", [])
14-
idx = linkflags.index("memory.ld")
15-
linkflags.insert(idx+1, linker_script)
16-
env.Replace(LINKFLAGS=linkflags)
8+
# Linker script fragment injected into the rodata output section of whichever
9+
# platform we're building for. Placed just before the end-of-rodata marker so
10+
# that the dynarray entries land in flash rodata and are correctly sorted.
11+
DYNARRAY_INJECTION = (
12+
"\n /* dynarray: WLED dynamic module arrays */\n"
13+
" . = ALIGN(0x10);\n"
14+
" KEEP(*(SORT_BY_INIT_PRIORITY(.dynarray.*)))\n"
15+
" "
16+
)
17+
18+
19+
def inject_before_marker(path, marker):
20+
"""Patch a linker script file in-place, inserting DYNARRAY_INJECTION before marker."""
21+
original = path.read_text()
22+
marker_pos = original.find(marker)
23+
if marker_pos < 0:
24+
raise RuntimeError(
25+
f"DYNARRAY injection marker not found in linker script: path={path}, marker={marker!r}"
26+
)
27+
patched = original[:marker_pos] + DYNARRAY_INJECTION + original[marker_pos:]
28+
path.write_text(patched)
29+
30+
31+
if env.get("PIOPLATFORM") == "espressif32":
32+
# Find sections.ld on the linker search path (LIBPATH).
33+
sections_ld_path = None
34+
for ld_dir in env.get("LIBPATH", []):
35+
candidate = Path(str(ld_dir)) / "sections.ld"
36+
if candidate.exists():
37+
sections_ld_path = candidate
38+
break
39+
40+
if sections_ld_path is not None:
41+
# Inject inside the existing .flash.rodata output section, just before
42+
# _rodata_end. IDF v5 enforces zero gaps between adjacent output
43+
# sections via ASSERT statements, so INSERT AFTER .flash.rodata would
44+
# fail. Injecting inside the section creates no new output section and
45+
# leaves the ASSERTs satisfied.
46+
build_dir = Path(env.subst("$BUILD_DIR"))
47+
patched_path = build_dir / "dynarray_sections.ld"
48+
shutil.copy(sections_ld_path, patched_path)
49+
inject_before_marker(patched_path, "_rodata_end = ABSOLUTE(.);")
50+
51+
# Replace "sections.ld" in LINKFLAGS with an absolute path to our
52+
# patched copy. The flag may appear as a bare token, combined as
53+
# "-Tsections.ld", or split across two tokens ("-T", "sections.ld").
54+
patched_str = str(patched_path)
55+
new_flags = []
56+
skip_next = False
57+
for flag in env.get("LINKFLAGS", []):
58+
if skip_next:
59+
new_flags.append(patched_str if flag == "sections.ld" else flag)
60+
skip_next = False
61+
elif flag == "-T":
62+
new_flags.append(flag)
63+
skip_next = True
64+
else:
65+
new_flags.append(flag.replace("sections.ld", patched_str))
66+
env.Replace(LINKFLAGS=new_flags)
1767
else:
18-
# For other platforms, put it in last
19-
env.Append(LINKFLAGS=[linker_script])
68+
# Assume sections.ld will be built (ESP-IDF format); add a post-action to patch it
69+
# TODO: consider using ESP-IDF linker fragment (https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/linker-script-generation.html)
70+
# For now, patch after building
71+
sections_ld = Path(env.subst("$BUILD_DIR")) / "sections.ld"
72+
def patch_sections_ld(target, source, env):
73+
inject_before_marker(sections_ld, "_rodata_end = ABSOLUTE(.);")
74+
env.AddPostAction(str(sections_ld), patch_sections_ld)
75+
76+
elif env.get("PIOPLATFORM") == "espressif8266":
77+
# The ESP8266 framework preprocesses eagle.app.v6.common.ld.h into
78+
# local.eagle.app.v6.common.ld in $BUILD_DIR/ld/ at build time. Register
79+
# a post-action on that generated file so the injection happens after
80+
# C-preprocessing but before linking.
81+
build_ld = Path(env.subst("$BUILD_DIR")) / "ld" / "local.eagle.app.v6.common.ld"
82+
83+
def patch_esp8266_ld(target, source, env):
84+
inject_before_marker(build_ld, "_irom0_text_end = ABSOLUTE(.);")
85+
86+
env.AddPostAction(str(build_ld), patch_esp8266_ld)

0 commit comments

Comments
 (0)