Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add example demonstrating advanced PlatformIO environment inheritance #4618

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

Arcitec
Copy link

@Arcitec Arcitec commented Mar 27, 2025

Adds a self-documented example demonstrating how a user can inherit from a core WLED configuration and add their own values without needing to copy all of the upstream values manually. This new method simplifies configuration maintenance for the users.

Summary by CodeRabbit

  • New Features
    • Introduced a new configurable environment for ESP32 Ethernet devices that inherits settings from the default configuration.
    • Updated build settings now allow the customization of the release name and relay pin, ensuring optimal configuration without conflicts.

Copy link

coderabbitai bot commented Mar 27, 2025

Walkthrough

The changes introduce a new environment configuration [env:esp32_eth_custom] in the platformio_override.sample.ini file. This environment extends the existing [env:esp32_eth] by inheriting its settings while providing the ability to override specific build flags. The configuration uses the build_unflags directive to remove predefined flags and the build_flags directive to set new definitions for the release name, relay pin, default Ethernet board ID, and LED output channels. Included comments explain the inheritance mechanism and the need to specify exact upstream flag values.

Changes

File Change Summary
platformio_override.sample.ini Added a new [env:esp32_eth_custom] environment that extends [env:esp32_eth], removes conflicting flags with build_unflags, and sets new build flags in build_flags for release name, relay pin, default Ethernet board ID, and LED output channels.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3615b95 and f0bcbaa.

📒 Files selected for processing (1)
  • platformio_override.sample.ini (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • platformio_override.sample.ini

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
platformio_override.sample.ini (1)

539-544: Typo in Documentation Comments

There's a small typo in the header comment at line 540: “Inhering” should be corrected to “Inheriting” for clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e76e9a3 and ab69673.

📒 Files selected for processing (1)
  • platformio_override.sample.ini (1 hunks)
🔇 Additional comments (1)
platformio_override.sample.ini (1)

546-570: Advanced Environment Inheritance Configuration

The new [env:esp32_eth_custom] block is well implemented and thoroughly documented. It correctly uses the extends directive to inherit from the upstream env:esp32_eth environment, and the separation between build_unflags (to remove conflicting upstream definitions) and build_flags (to add custom overrides) is clear and appropriately commented. This approach will help users override specific build flags without manually copying all upstream values, aligning well with the PR objectives.

@Arcitec Arcitec force-pushed the advanced-pio-overrides branch from ab69673 to 378fa59 Compare March 27, 2025 19:14
@Arcitec
Copy link
Author

Arcitec commented Mar 27, 2025

Thanks Mr. Bot for finding a small comment typo. Fixed. :)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platformio_override.sample.ini (2)

552-561: Appropriate Use of build_unflags to Prevent Duplicate Flags
The configuration uses build_unflags to remove specific upstream flag definitions (i.e. -D WLED_RELEASE_NAME="ESP32_Ethernet" and -D RLYPIN=-1). The detailed warning comments also help future maintainers understand the importance of specifying exact flag values. Consider adding a brief inline note indicating that any upstream changes must be reviewed here.


562-568: Overriding Upstream Flags with build_flags
The use of build_flags to append custom definitions (changing the release name to "ESP32_Ethernet_Custom" and setting RLYPIN to 24) correctly demonstrates the override approach. For added clarity, you might consider an inline comment explaining that these new definitions are intended to replace the unflagged upstream values. For example:

-build_flags = ${env:esp32_eth.build_flags}
-  -D WLED_RELEASE_NAME=\"ESP32_Ethernet_Custom\"
-  -D RLYPIN=24
+build_flags = ${env:esp32_eth.build_flags}
+  # Override upstream release name and relay pin settings:
+  -D WLED_RELEASE_NAME=\"ESP32_Ethernet_Custom\"
+  -D RLYPIN=24
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab69673 and 378fa59.

📒 Files selected for processing (1)
  • platformio_override.sample.ini (1 hunks)
🔇 Additional comments (2)
platformio_override.sample.ini (2)

539-544: Clear Documentation for Advanced Example Description
The added comment block clearly explains the advanced inheritance mechanism. It effectively informs users how to override specific upstream parameters without manual duplication.


546-551: New Environment Declaration and Inheritance
The new [env:esp32_eth_custom] environment is correctly declared and set to inherit from env:esp32_eth. This provides a solid basis for users to build upon the core configuration while applying custom overrides.

Adds a self-documented example demonstrating how a user can inherit from a core WLED configuration and add their own values without needing to copy all of the upstream values manually. This new method simplifies configuration maintenance for the users.
@Arcitec Arcitec force-pushed the advanced-pio-overrides branch from 378fa59 to c0d6cfc Compare March 27, 2025 19:21
@DedeHai
Copy link
Collaborator

DedeHai commented Mar 27, 2025

thanks,
please don't force push branches in PR, it can mess up reviews (nothing bad happened just as a general rule).

@willmmiles can you check the example is correct please? I am not too familiar with the inner workings of envs.

@Arcitec
Copy link
Author

Arcitec commented Mar 27, 2025

Hey Dede! :) I agreed with Mr. Bot about adding two more comment lines to super clearly delineate where the old values should be unset/set. Unfortunately I had pushed like 10 seconds before I saw your comment. Apologies. Well, the diff is here (GitHub adds "Compare" links to the PR discussion history for any force pushes which is why I usually force push for minor changes):

https://github.com/wled/WLED/compare/378fa59596976a83d0977dfde34aedf06307c2a7..c0d6cfcfb2d87e24a934bfbfc2959a928fe9d59b

As for checking if the example is correct, first go into VSCode preferences (Ctrl + ,) and search for "scrollback" and set it to 100000 lines. Then open the PlatformIO sidebar (left side-panel of VSCode) and go to "Advanced: Verbose Build". This will build with output showing all the flags that are being used for each file being compiled.

You can then see that the old flags are removed and the new flags are set.

I spent about 2 hours going through PlatformIO's code (seeing how build_unflags is being processed by PlatformIO) and deeply testing this to ensure the final result is correct. Nobody had documented this usage anywhere even in PlatformIO's own discussions or docs. Took a lot of time but I definitely ensured it works correctly. :)

@Arcitec
Copy link
Author

Arcitec commented Mar 27, 2025

Here are some of the references I used (useful for history/PR documentation):

@blazoncek
Copy link
Collaborator

For what it's worth, my own opinion:
platformio.ini provides base settings for each of the supported chips in [esp8266] or [esp32], etc. sections. These sections are provided with intent to be used in custom environments (in platformio_override.ini) as well as in environments provided by WLED (for the purposes of CI and official releases) and include minimum amount of settings to compile for certain chip.
These are merely sections not entire environments and don't do anything on their own.
The idea to reuse one of the (CI/official) build environments is flawed as environments may change (as @willmmiles pointed out) without prior notice while base sections are guaranteed to include minimum amount of settings for successful compile. I also fully agree with what @willmmiles wrote regarding cleanness or readability.
IMO suggesting users to reuse official (full) environments and overriding certain settings is not for first-time comers but for experienced developers that know the implications. Those would already know the technique.

That being said, the change is benign and only boils down to the question: Are we willing to deal with users complaining/asking why their custom builds fail out of the sudden when they pull from upstream since they followed instructions provided by WLED?

If the answer is yes then I'd suggest to add a disclaimer that environments may change without notice and such change may render derived environment non-functional.

@Arcitec
Copy link
Author

Arcitec commented Mar 28, 2025

@blazoncek It's already marked as an advanced method in the first line of the example. :)

I agree 100% that there should be a clear warning in the example code which explains that the chosen upstream environment may change or become unmaintained in the future. I will add that!

That "feature" is the entire reason for its existence though: Seeing a good release build (such as "ESP32_Ethernet") and tweaking a few WLED defaults for yourself. For most users, such a usage will not need any maintenance at all because they'll only be setting a few pin numbers, default ethernet board model, etc.

The alternative is to copy-paste an entire definition and maintain it manually and relentlessly. That's a lot more work for the user and will still break identically when there's big compiler variable name/value changes in future WLED updates. So there's no improvement to be gained by doing the manual copy-paste method, in this use-case (taking a good release build and just tweaking a few flags).

I just need to explain more clearly the upsides and downsides of this approach in the comments itself.

@blazoncek
Copy link
Collaborator

The alternative is to copy-paste an entire definition and maintain it manually and relentlessly.

What is the difference compared to extending existing core environment? You will still need to "maintain it manually and relentlessly".

I understand that this approach may work for you (until we change esp32_eth) but I am against suggesting such approach in general.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
platformio_override.sample.ini (1)

562-570: Comprehensive Documentation for build_unflags
The detailed comments clearly explain how the build_unflags directive operates — emphasizing the need to specify EXACT values to unset and warning about potential duplicate definitions if upstream flags change. This level of documentation is helpful for advanced users. Consider including a brief note or reference link to PlatformIO’s documentation on build_unflags for additional clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0d6cfc and 3615b95.

📒 Files selected for processing (1)
  • platformio_override.sample.ini (1 hunks)
🔇 Additional comments (3)
platformio_override.sample.ini (3)

556-560: Environment Inheritance: Valid Use of extends
The new [env:esp32_eth_custom] environment correctly extends env:esp32_eth, ensuring that all upstream settings are inherited while allowing overrides. This helps reduce duplication and future maintenance by automatically incorporating upstream changes.


571-574: Accurate Unsetting of Conflicting Flags
The unflag directives correctly list the flags to be removed:

  • -D WLED_RELEASE_NAME="ESP32_Ethernet"
  • -D RLYPIN=-1
    Ensure that these values precisely match those defined in env:esp32_eth so that only the conflicting instances are removed. This review action might be verified by comparing with the upstream settings during a verbose build.

575-584: Correct Overriding of Build Flags
The overriding of build flags is well structured. By inheriting all upstream flags via ${env:esp32_eth.build_flags} and then appending new customizations:

  • -D WLED_RELEASE_NAME="ESP32_Ethernet_Custom"
  • -D RLYPIN=24 (indicating the new relay pin)
  • -D WLED_ETH_DEFAULT=4
  • -D DATA_PINS=16,3,1,4
    the configuration provides clear and maintainable customizations. The inline comments further aid users in understanding the purpose of each flag. Verify these values against your hardware setup and intended customization via a verbose build process.

@Arcitec
Copy link
Author

Arcitec commented Mar 28, 2025

What is the difference compared to extending existing core environment? You will still need to "maintain it manually and relentlessly".

There's a huge difference in user maintenance burden.

Here's the upstream release config that this technique is based on:

[env:esp32_eth]
board = esp32-poe
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
upload_speed = 921600
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1
;  -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only
lib_deps = ${esp32.lib_deps}
board_build.partitions = ${esp32.default_partitions}

The user can copy-paste that huge text blob and maintain it themselves, sure:

  • But then they will need to watch platformio.ini for esp32_eth changes after every single time they pull down WLED code changes, to see what the new board tweaks/new defaults/added flags etc are.
  • WLED refactorings and new modules, tweaks for improved board settings, new default flags, adding of audioreactive by default, conversion of modules into custom_usermods libraries, etc, all happens via changes to the platformio.ini release configs.
  • The user would have to relentlessly watch that for changes. Because if they copy-paste the code block, and they don't watch the changes relentlessly, then their builds would suddenly break (such as when the usermods were all suddenly converted into PlatformIO libraries, or when any other important flag changes/library changes happen).
  • And every time they compare changes, they would have to painfully compare their tweaked config against the upstream, and when they see differences they have to remember what settings they changed, and what settings you've changed.

By instead deriving from your known-good release config, the user only has to maintain the exact flags they are modifying. That's great, as long as they are happy about being automatically derived from your release config and any future changes it gets - which is... the entire point of this technique. So I am definitely happy about being derived from esp32_eth and not having to watch platformio.ini "manually and relentlessly".

  • This new technique boils down to simple flag injection into a known-good release config, by deriving the entire config from upstream release environments, and injecting a few flag modifications, to create a low-maintenance derived config.
  • Maintenance burden still exists, of course, but if the user sticks to simple flags such as ethernet board model and pinouts, the flags are unlikely to ever change name or datatypes, making maintenance extremely low-burden as long as the upstream release config still exists.
  • If WLED changes the name of a variable, the user will notice that the setting is no longer being applied by default.
  • If WLED changes the datatype of a variable, the user will most of the time receive compiler errors since the C++ #defines will no longer create compilable code (such as a string suddenly being in the place of an int etc).
  • If the upstream config changes the value of a default that has been overridden, the "unflags" will no longer remove the duplicate and therefore the user immediately gets a compiler warning and just has to update build_unflags to the new value, as documented in this example.
  • If the upstream config is deleted, the user gets compiler errors immediately since the derived config won't build anymore.
  • If the upstream config becomes unmaintained, the user would have to learn about that themselves, but that is what the new warning covers. It now clearly mentions the downsides to deriving from a WLED release config and about their need to periodically verify that the chosen upstream config is still being maintained by WLED.
  • The example's code comments explains all of this for the users who choose to use this technique.

I understand that this approach may work for you (until we change esp32_eth) but I am against suggesting such approach in general.

Me too. I have never proposed that this is a "general" suggestion for people. It's marked as ADVANCED in the first word of the first line of the example. I've also added a big, thick warning now to explain the user's own responsibilities if they use this technique.

For those who know what this technique is for, it's a perfect technique. It automatically follows the WLED release settings and lets users tweak a handful of values they wanted to change, to set new "firmware flash" defaults for things like pin numbers, ethernet board (which is very useful to write into the firmware, to not have to manually connect to the WiFi to change the board model every time you flash an update via USB). And if WLED ever changes the settings in esp32_eth, I will be happy, because that's the entire point of the technique - I won't have to monitor your upstream changes until my build suddenly stops applying my new defaults, and then I will know that the flags I've injected (pin numbers, ethernet) have been renamed and will need some minor maintenance updates. But that will be very rare.

I have expanded the "add flags" example to clearly demonstrate the kind of flags users like me are setting with this technique. It's all harmless stuff like pin numbers and ethernet board model. Just minor default-tweaks for upstream releases.

And with the new warning that explains that the user cannot complain to you if upstream defaults change in the future, I think it's ready for inclusion. :)

@Arcitec Arcitec force-pushed the advanced-pio-overrides branch from 3615b95 to f0bcbaa Compare March 28, 2025 16:40
@netmindz
Copy link
Member

Thank you for raising this @Arcitec

I agree that we want to make it easy for users to base their builds of a known good set of values, which may change and we don't want them to have to track these changes so extends is the right approach

I need to take some time to review our current config as well as your pr so that the best path for the user is also the easy path

@netmindz netmindz self-assigned this Mar 28, 2025
@Arcitec
Copy link
Author

Arcitec commented Mar 30, 2025

@netmindz That's excellent to hear! If you have any concerns or questions, I'm here for you and ready to revise anything! :)

The general approach requires all 3 parts, due to how PlatformIO is designed:

  • extends inherits 100% of the settings from the upstream config. But any settings-keys that are set manually in the derived config will overwrite the upstream config.
  • build_flags therefore uses interpolation, which just means that we refer to the upstream config as our first line, which then recursively resolves the entire upstream config's field value.
    • So in short, build_flags = ${env:esp32_eth.build_flags} means "Set it to the exact same, fully resolved build_flags value as upstream". This brings in all of the upstream flags.
    • After that, we append our own flags/overrides after that line.
    • This means we still properly inherit the upstream build flags.
    • But it also means that we'll inherit duplicates of various flags if we wanted to change anything that the upstream already configured (such as wanting to change upstream's -D RLYPIN=-1 to another number), which brings us to the next and final step...
  • build_unflags uses interpolation too, meaning that build_unflags = ${env:esp32_eth.build_unflags} sets it to the exact same value as upstream.
    • We then append the extra flags that we want to unset: Values that we wanted to change but already existed in the upstream build_flags, which we must unset since the compiler doesn't allow people to define a flag multiple times and would spit out "variable redefined" warnings endlessly.
    • So we must unset the conflicting (old) values, such as -D RLYPIN=-1.
    • The "unflags" in PlatformIO works by scanning for the flag's name (or scanning for the exact value if provided) and deleting all matches from the final compiler flags.
    • This is the only way to get rid of the duplicate flags in PlatformIO. (Since it doesn't have any "automatically use the final value from the duplicate flags" mode, unfortunately.)
    • It's easy to populate the "unflags" list though, because the user can just open the upstream config and copy-paste the specific build_flags values they want to remove, so it's a quick job to just select the upstream flag and Ctrl-C Ctrl-V into unflags.
    • The PlatformIO "unflags" parser doesn't care about whitespace/formatting. So for example, it doesn't care about the space symbol in -D<space>FLAG=VALUE. The space can be there or not, it doesn't matter.
    • And if those upstream configs ever change to another value in the future, the compiler will again start complaining about duplicate flags (since the unflag will no longer delete upstream's exact value anymore), which is a very visible alert that the user just needs to copy-paste the new upstream value into unflags (this fact is documented in the example). Upstream changes for the kinds of flags that users typically change are very rare though, so most users won't have to deal with this ever happening.

To verify that the correct flags are being applied by this technique, here's some instructions to do a verbose build to see all flags being applied onto each file by the compiler:

#4618 (comment)

Also remember to erase your buildcache to force rebuilds after each test. I think it defaulted to ~/.buildcache in your platformio.ini.

Hope this helps! Let me know if there's any questions. :)

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.

5 participants