Skip to content

Move penv setup out of main.py / invert mcu detection logic #255

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

Merged
merged 26 commits into from
Aug 11, 2025

Conversation

Jason2866
Copy link

@Jason2866 Jason2866 commented Aug 10, 2025

Refactor penv setup out of main.py

Checklist:

  • The pull request is done against the latest develop branch
  • Only relevant files were touched
  • Only one feature/fix was added per PR, more changes are allowed when changing boards.json
  • I accept the CLA

Summary by CodeRabbit

Here are the updated release notes based on the provided changes:

  • Improvements
    • Enhanced the build process by automating Python virtual environment setup and dependency management through a new dedicated module, improving reliability and maintainability.
    • Streamlined toolchain and environment configuration with refined logic for ESP32 chip architecture detection and tool selection.
    • Simplified build scripts by removing inline environment setup and consolidating commands, resulting in cleaner and more robust build workflows.

Copy link

coderabbitai bot commented Aug 10, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This update centralizes Python virtual environment and dependency setup into a new penv_setup.py module, replacing inline logic in builder/main.py. It also inverts the MCU architecture detection logic for toolchain selection in several builder scripts, affecting how RISC-V and Xtensa toolchains are chosen for ESP32 variants. No public API signatures are changed.

Changes

Cohort / File(s) Change Summary
Python Environment Refactor
builder/main.py, builder/penv_setup.py
Moves all Python virtual environment and dependency installation logic from main.py to a new penv_setup.py module. Introduces robust functions for environment setup, dependency management, and esptool installation. Main script now delegates environment setup to this module, simplifying main.py.
Toolchain Architecture Logic Update
builder/frameworks/_embed_files.py, builder/frameworks/ulp.py, builder/main.py
Inverts the conditional logic for selecting RISC-V vs. Xtensa toolchains: now uses the RISC-V toolchain unless the MCU is one of the Xtensa-based ESP32 variants (esp32, esp32s2, esp32s3). Updates related parameters and tool selection accordingly for objcopy, toolchain packages, and GDB tool selection.

Sequence Diagram(s)

sequenceDiagram
    participant Main as builder/main.py
    participant PEnv as builder/penv_setup.py
    participant System as System/Python

    Main->>PEnv: setup_python_environment(env, platform, platformio_dir)
    PEnv->>System: Check Python version (>=3.10)
    PEnv->>System: Create virtual environment if missing
    PEnv->>System: Install dependencies via uv (if needed)
    PEnv->>System: Install esptool from local package
    PEnv-->>Main: Return penv Python executable and esptool path
    Main->>Main: Continue build with environment set up
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

In the warren where Python hops anew,
A penv now builds with dependencies true.
Toolchains sorted, RISC-V or Xtensa in line,
The builder’s path is now more fine.
With every hop, this code refines—
🐇✨ PlatformIO, now streamlined, shines!

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch move_env_install

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
  • 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 explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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.

@Jason2866
Copy link
Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

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: 1

🧹 Nitpick comments (1)
builder/penv_setup.py (1)

104-109: Consider improving URL version extraction robustness.

The regex pattern for extracting version from URLs could be made more robust to handle edge cases like pre-release versions or different URL formats.

Consider using a more comprehensive pattern or a URL parsing library:

-            m = re.search(r'/v?(\d+\.\d+\.\d+(?:\.\d+)?)(?:\.(?:zip|tar\.gz|tar\.bz2))?$', spec)
+            # Handle more version formats including pre-releases
+            m = re.search(r'/v?(\d+\.\d+\.\d+(?:[.-]\w+)?(?:\.\d+)?)(?:\.(?:zip|tar\.gz|tar\.bz2))?$', spec)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5adc2 and 82b291d.

📒 Files selected for processing (4)
  • builder/frameworks/_embed_files.py (1 hunks)
  • builder/frameworks/ulp.py (1 hunks)
  • builder/main.py (4 hunks)
  • builder/penv_setup.py (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.
📚 Learning: 2025-07-13T20:03:29.695Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#225
File: builder/main.py:99-108
Timestamp: 2025-07-13T20:03:29.695Z
Learning: In the ESP32 platform builder, the subprocess call to get site-packages directory in setup_python_paths() function is intentionally left without error handling because if this basic Python operation fails, it indicates a fundamental system issue that would cause the entire build process to fail anyway.

Applied to files:

  • builder/frameworks/ulp.py
  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-05T11:47:41.756Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
🪛 Ruff (0.12.2)
builder/frameworks/ulp.py

40-40: Undefined name idf_variant

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: build (macos-15, examples/espidf-arduino-matter-light)
  • GitHub Check: build (macos-15, examples/espidf-peripherals-usb)
  • GitHub Check: build (macos-15, examples/tasmota)
  • GitHub Check: build (macos-15, examples/arduino-wifiscan)
  • GitHub Check: build (ubuntu-latest, examples/arduino-matter-light)
  • GitHub Check: build (windows-latest, examples/espidf-arduino-littlefs)
  • GitHub Check: build (windows-latest, examples/arduino-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-ulp-riscv)
  • GitHub Check: build (windows-latest, examples/arduino-zigbee-switch)
  • GitHub Check: build (ubuntu-latest, examples/espidf-ulp)
  • GitHub Check: build (ubuntu-latest, examples/espidf-storage-sdcard)
  • GitHub Check: build (ubuntu-latest, examples/espidf-http-request)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-C6-ULP-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-matter-light)
  • GitHub Check: build (ubuntu-latest, examples/tasmota)
  • GitHub Check: build (ubuntu-latest, examples/espidf-blink)
  • GitHub Check: build (ubuntu-latest, examples/arduino-wifiscan)
  • GitHub Check: build (ubuntu-latest, examples/arduino-blink)
🔇 Additional comments (5)
builder/frameworks/ulp.py (1)

39-42: LGTM! MCU detection logic correctly inverted.

The condition now explicitly checks for Xtensa-based variants (esp32, esp32s2, esp32s3) to select the appropriate toolchain, which aligns with the similar changes in other build files.

builder/frameworks/_embed_files.py (1)

112-121: MCU detection logic correctly inverted for objcopy toolchain selection.

The three conditions consistently check for non-Xtensa variants to select RISC-V toolchain parameters, maintaining alignment with the overall refactoring pattern.

builder/main.py (2)

35-35: Clean extraction of Python environment setup.

The complex virtual environment and dependency management logic has been successfully consolidated into the penv_setup module, improving code organization and maintainability.

Also applies to: 45-46


421-422: MCU detection logic correctly inverted for toolchain selection.

Both the toolchain architecture and GDB tool selection now consistently check for non-Xtensa variants, aligning with the changes in other build files.

Also applies to: 449-453

builder/penv_setup.py (1)

294-344: Well-structured main setup function.

The function properly orchestrates the environment setup with good error handling, version checks, and clear return values. The extraction of this logic from main.py significantly improves code organization.

@Jason2866
Copy link
Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

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: 1

🔭 Outside diff range comments (1)
builder/main.py (1)

596-607: CLI args appended twice to esp-idf-size command.

cli_args are extended into cmd in Lines 601-603 and again in Lines 605-607. This duplicates args.

Apply either of the following (remove one of the extensions). Example: keep the later block and drop the earlier:

-        if "--" in sys.argv:
-            dash_index = sys.argv.index("--")
-            if dash_index + 1 < len(sys.argv):
-                cli_args = sys.argv[dash_index + 1:]
-                cmd.extend(cli_args)
+        if "--" in sys.argv:
+            dash_index = sys.argv.index("--")
+            if dash_index + 1 < len(sys.argv):
+                cli_args = sys.argv[dash_index + 1:]
♻️ Duplicate comments (1)
builder/penv_setup.py (1)

265-275: Optional: avoid f-string interpolation into Python snippet.

Passing esptool_repo_path via argv (json) avoids quoting edge cases and is easier to read. Prior discussion concluded inputs are trusted, so this is optional here.

-        result = subprocess.run(
-            [python_exe, "-c", 
-             "import esptool; "
-             "import os; "
-             f"expected_path = os.path.normpath(r'{esptool_repo_path}'); "
-             "actual_path = os.path.normpath(os.path.dirname(esptool.__file__)); "
-             "print('MATCH' if expected_path in actual_path else 'MISMATCH')"],
+        import json
+        result = subprocess.run(
+            [python_exe, "-c", 
+             "import esptool, os, sys, json; "
+             "expected_path = os.path.normpath(json.loads(sys.argv[1])); "
+             "actual_path = os.path.normpath(os.path.dirname(esptool.__file__)); "
+             "print('MATCH' if expected_path in actual_path else 'MISMATCH')",
+             json.dumps(esptool_repo_path)],
             capture_output=True,
             text=True,
             timeout=5
         )
🧹 Nitpick comments (4)
builder/penv_setup.py (4)

280-281: Tighten exception handling for subprocess.run.

subprocess.run doesn't raise CalledProcessError unless check=True is used. Either add check=True or drop that exception branch.

-    except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
+    except (subprocess.TimeoutExpired, FileNotFoundError):
         pass

139-144: Increase install timeouts for first-time setups.

30s is often insufficient for cold installs (e.g., cryptography wheels, slow CI mirrors). Bump to a more forgiving window.

-                timeout=30  # 30 second timeout
+                timeout=180  # allow slower mirrors/cold caches
...
-                timeout=30  # 30 second timeout
+                timeout=120  # listing should be quick but allow headroom
...
-                timeout=30  # 30 second timeout for package installation
+                timeout=300  # first-time installs can take longer

Also applies to: 175-176, 223-224


69-83: Consider avoiding adding the entire penv dir to sys.path.

Adding the venv root via site.addsitedir(penv_dir) is unusual and can shadow modules. Adding only site-packages is typically sufficient.

-    # Add penv_dir to module search path
-    site.addsitedir(penv_dir)
+    # Only add site-packages; avoid injecting venv root itself

19-19: Dependency availability at import time.

semantic_version is imported at module load before penv is prepared. PlatformIO’s host Python typically includes it, but if this module is reused outside PIO, consider lazy importing inside the function that needs it.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5adc2 and 487b0a5.

📒 Files selected for processing (4)
  • builder/frameworks/_embed_files.py (1 hunks)
  • builder/frameworks/ulp.py (1 hunks)
  • builder/main.py (4 hunks)
  • builder/penv_setup.py (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.
📚 Learning: 2025-08-10T16:59:15.510Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:265-275
Timestamp: 2025-08-10T16:59:15.510Z
Learning: In the ESP32 platform builder's penv_setup.py, the esptool_repo_path used in subprocess calls comes from PlatformIO's internal package management system (platform.get_package_dir()) which is a trusted source. Command injection concerns are not applicable here since this is build infrastructure code with controlled inputs, not user-facing code accepting arbitrary input.

Applied to files:

  • builder/frameworks/ulp.py
  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-07-13T20:03:29.695Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#225
File: builder/main.py:99-108
Timestamp: 2025-07-13T20:03:29.695Z
Learning: In the ESP32 platform builder, the subprocess call to get site-packages directory in setup_python_paths() function is intentionally left without error handling because if this basic Python operation fails, it indicates a fundamental system issue that would cause the entire build process to fail anyway.

Applied to files:

  • builder/frameworks/ulp.py
  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-05T11:47:41.756Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
🧬 Code Graph Analysis (1)
builder/main.py (1)
builder/penv_setup.py (1)
  • setup_python_environment (294-344)
🪛 Ruff (0.12.2)
builder/frameworks/ulp.py

40-40: Undefined name idf_variant

(F821)

🔇 Additional comments (4)
builder/main.py (2)

421-423: MCU detection logic inversion looks consistent.

Switching to “not in Xtensa set => RISC-V” simplifies maintenance as new RISC-V targets appear. GDB package selection aligns with the new condition.

Please confirm boards outside ("esp32","esp32s2","esp32s3") are all RISC-V in your supported matrix; unknown MCUs will default to RISC-V with this logic.

Also applies to: 447-455


45-47: Good refactor: centralized penv setup.

Replacing inline penv logic with setup_python_environment reduces complexity in main and standardizes tool setup.

builder/frameworks/ulp.py (1)

38-42: Toolchain selection inversion aligns with main.py.

Selecting Xtensa tools only for ("esp32","esp32s2","esp32s3") and RISC-V otherwise matches the unified rule elsewhere.

Static analysis may flag idf_variant as undefined; it’s provided via SCons.Script.Import at Line 24 and is valid at runtime.

builder/frameworks/_embed_files.py (1)

112-121: Objcopy tool/arch inversion is consistent.

RISC-V chosen when MCU not in ("esp32","esp32s2","esp32s3") for executable, output target, and binary arch. Matches the new global rule.

Also applies to: 118-121

@Jason2866
Copy link
Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

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: 2

🔭 Outside diff range comments (1)
builder/frameworks/ulp.py (1)

96-99: Bug: include lists concatenated without a separator; paths will be mangled

comp_includes = comp_includes + plain_includes produces e.g. "incA;incBincC;incD". Join after combining lists instead.

-        comp_includes = ";".join(get_component_includes(target_config))
-        plain_includes = ";".join(app_includes["plain_includes"])
-        comp_includes = comp_includes + plain_includes
+        comp_includes_list = list(get_component_includes(target_config))
+        comp_includes_list.extend(app_includes["plain_includes"])
+        comp_includes = ";".join(comp_includes_list)
♻️ Duplicate comments (2)
builder/penv_setup.py (2)

265-276: Addressed: subprocess path passing avoids injection and quoting pitfalls

Switching to sys.argv[1] and realpath/normcase comparisons is correct and safe. This resolves the earlier injection concern.


283-284: Addressed: exact match removes false positive from "MISMATCH" containing "MATCH"

Comparing result.stdout.strip() == "MATCH" fixes the earlier bug.

🧹 Nitpick comments (7)
builder/penv_setup.py (6)

69-83: Nit: unnecessary addsitedir(penv_dir); site-packages is sufficient

Adding the venv root to sys.path isn’t required and can slightly slow path scanning. Consider limiting to site-packages.

-    # Add penv_dir to module search path
-    site.addsitedir(penv_dir)
-
     # Add site-packages directory

118-158: UV bootstrap fallback is reasonable; consider a slightly longer timeout

30s can be tight on slow mirrors/CI. Bumping to 120s reduces flaky failures without harming fast paths.

-                timeout=30  # 30 second timeout
+                timeout=120

218-241: Good: all subprocess args are passed as list; errors surfaced; minor: increase install timeout

Dependency installs can often exceed 30s (wheels build/large deps). Consider 180s.

-                timeout=30  # 30 second timeout for package installation
+                timeout=180

259-262: Silent exit on missing tool-esptoolpy obscures root cause; add an error message

Emit a helpful error to stderr before exiting.

-    if not esptool_repo_path or not os.path.isdir(esptool_repo_path)):
-        sys.exit(1)
+    if not esptool_repo_path or not os.path.isdir(esptool_repo_path):
+        sys.stderr.write(
+            f"Error: 'tool-esptoolpy' package directory not found: {esptool_repo_path!r}\n"
+        )
+        sys.exit(1)

289-297: Enhance error reporting for esptool reinstall failures

Surface stderr to aid triage.

-    except subprocess.CalledProcessError:
-        sys.exit(1)
+    except subprocess.CalledProcessError as e:
+        sys.stderr.write(
+            f"Error: Failed to install esptool from {esptool_repo_path} (exit {e.returncode})\n"
+        )
+        sys.exit(1)

315-323: Confirm intentional Python >= 3.10 requirement (potentially breaking change)

Bumping the host interpreter requirement to 3.10 may break existing users (PlatformIO host Python is often <3.10). If this is intended, document it prominently in release notes. Otherwise consider relaxing to 3.8/3.9.

Would you like a patch that relaxes this to 3.8+ while still supporting your dependency set?

builder/main.py (1)

588-629: Unreachable ImportError handler; firmware_metrics uses subprocess, not import

Since esp_idf_size is executed via subprocess.run, ImportError in this context will never be raised. Rely on return code handling and remove the dead except ImportError.

-    except ImportError:
-        print("Error: esp-idf-size module not found.")
-        print("Install with: pip install esp-idf-size")
     except FileNotFoundError:
         print("Error: Python executable not found.")
         print("Check your Python installation.")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5adc2 and be3388b.

📒 Files selected for processing (4)
  • builder/frameworks/_embed_files.py (1 hunks)
  • builder/frameworks/ulp.py (1 hunks)
  • builder/main.py (5 hunks)
  • builder/penv_setup.py (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.
📚 Learning: 2025-08-10T16:59:15.510Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:265-275
Timestamp: 2025-08-10T16:59:15.510Z
Learning: In the ESP32 platform builder's penv_setup.py, the esptool_repo_path used in subprocess calls comes from PlatformIO's internal package management system (platform.get_package_dir()) which is a trusted source. Command injection concerns are not applicable here since this is build infrastructure code with controlled inputs, not user-facing code accepting arbitrary input.

Applied to files:

  • builder/penv_setup.py
  • builder/main.py
  • builder/frameworks/ulp.py
📚 Learning: 2025-08-05T11:47:41.756Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.

Applied to files:

  • builder/penv_setup.py
  • builder/main.py
📚 Learning: 2025-07-13T20:03:29.695Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#225
File: builder/main.py:99-108
Timestamp: 2025-07-13T20:03:29.695Z
Learning: In the ESP32 platform builder, the subprocess call to get site-packages directory in setup_python_paths() function is intentionally left without error handling because if this basic Python operation fails, it indicates a fundamental system issue that would cause the entire build process to fail anyway.

Applied to files:

  • builder/penv_setup.py
  • builder/main.py
  • builder/frameworks/ulp.py
🧬 Code Graph Analysis (1)
builder/main.py (1)
builder/penv_setup.py (1)
  • setup_python_environment (300-350)
🪛 Ruff (0.12.2)
builder/frameworks/ulp.py

40-40: Undefined name idf_variant

(F821)

🔇 Additional comments (12)
builder/penv_setup.py (4)

43-50: LGTM: cross-platform executable path resolution

Clean handling of Windows vs POSIX Scripts/bin and .exe suffix is correct.


53-67: LGTM: venv creation with SCons action and basic sanity check

Creation via "$PYTHONEXE" -m venv --clear and asserting presence of pip binary is sound in this context.


169-176: LGTM: enumerate installed packages via uv pip list with JSON

Good use of --format=json and lowercasing names; errors downgraded to warnings are appropriate here.


339-350: LGTM: esptool and uv executables derived from penv; deps installed before esptool; clear exit on failure

Overall flow is correct and consistent with prior learnings about passing the venv Python explicitly in subprocess calls.

builder/frameworks/ulp.py (2)

39-42: MCU toolchain selection inversion looks correct

Selecting Xtensa toolchain only for ("esp32","esp32s2","esp32s3") and RISC‑V otherwise aligns with the refactor across the codebase.


40-40: Ruff F821 “idf_variant” is a false positive (SCons injects it via Import)

idf_variant is provided by Import("env sdk_config project_config app_includes idf_variant"). Safe to ignore this linter warning here.

If you want to appease static analysis locally, add a type comment near the Import line:
idf_variant: str # type: ignore[F821] (injected by SCons)

builder/frameworks/_embed_files.py (1)

112-121: LGTM: objcopy triplet selection matches inverted MCU condition

  • RISC‑V: riscv32-esp-elf-objcopy, elf32-littleriscv, "riscv".
  • Xtensa: xtensa-{mcu}-elf-objcopy, elf32-xtensa-le, "xtensa".

Consistent with toolchain packages.

builder/main.py (5)

35-47: LGTM: centralized penv setup via setup_python_environment

Clean separation of concerns and correct handling of the returned Python/esptool paths.


420-423: LGTM: toolchain arch inversion consistent with other modules

RISC‑V default unless MCU is in the Xtensa trio.


447-459: GDB package selection aligns with toolchain arch; path construction looks correct

Builds <pkg>/bin/<triplet>-elf-gdb appropriately for both Xtensa and RISC‑V.


428-433: Correct handling of spaces in esptool path for $OBJCOPY

Pre-quoting only when needed avoids nested quoting issues in later command strings.


724-775: LGTM: esptool upload commands—moved quoting to the variable; flags and offsets preserved

UPLOADCMD uses pre-quoted $UPLOADER when needed; flags mirror the ELF2IMAGE path. FS upload path also updated accordingly.

@Jason2866
Copy link
Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

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: 1

♻️ Duplicate comments (1)
builder/penv_setup.py (1)

271-285: Path verification and exact match handling look good

Passing esptool_repo_path via argv and checking stdout.strip() == "MATCH" avoids the earlier false-positive and injection concerns in this controlled environment.

Also applies to: 291-295

🧹 Nitpick comments (5)
builder/penv_setup.py (2)

326-334: Python >= 3.10 requirement: confirm intended support floor and update docs

Raising the minimum host Python to 3.10 is a user-facing breaking change (e.g., Ubuntu 20.04 ships 3.8). Please confirm project policy and update docs/release notes accordingly or consider relaxing to 3.8/3.9 if feasible.

If you decide to relax the requirement:

-    if sys.version_info < (3, 10):
+    if sys.version_info < (3, 8):
         sys.stderr.write(
-            f"Error: Python 3.10 or higher is required. "
+            f"Error: Python 3.8 or higher is required. "

Alternatively, check only for venv availability:

-    if sys.version_info < (3, 10):
+    try:
+        import venv  # noqa
+    except Exception:
         sys.stderr.write(
-            f"Error: Python 3.10 or higher is required. "
+            "Error: A Python version with the 'venv' module is required.\n"

Would you like me to draft a README/release-note snippet for this?


345-346: Use explicit error instead of assert for user-facing failure

assert may be stripped with -O and isn’t user-friendly. Replace with an explicit check and message.

-    assert os.path.isfile(penv_python), f"Python executable not found: {penv_python}"
+    if not os.path.isfile(penv_python):
+        sys.stderr.write(f"Error: Python executable not found: {penv_python}\n")
+        sys.exit(1)
builder/frameworks/ulp.py (1)

39-42: Xtensa allowlist inversion looks correct; consider centralizing chip lists

Selecting Xtensa toolchain only for ("esp32","esp32s2","esp32s3") aligns with the PR’s inversion. To avoid drift across files, centralize this allowlist in one module (e.g., a helper in builder/) and import it where needed.

builder/frameworks/_embed_files.py (1)

112-121: Minor readability: compute architecture flag once

You repeat the same condition 3x. Precompute a boolean to reduce duplication.

-mcu = board.get("build.mcu", "esp32")
+mcuv = board.get("build.mcu", "esp32")
+is_riscv = mcuv not in ("esp32","esp32s2","esp32s3")
@@
-                        "riscv32-esp-elf-objcopy"
-                        if mcu not in ("esp32","esp32s2","esp32s3")
-                        else "xtensa-%s-elf-objcopy" % mcu,
+                        "riscv32-esp-elf-objcopy" if is_riscv else f"xtensa-{mcuv}-elf-objcopy",
@@
-                        "elf32-littleriscv" if mcu not in ("esp32","esp32s2","esp32s3") else "elf32-xtensa-le",
+                        "elf32-littleriscv" if is_riscv else "elf32-xtensa-le",
@@
-                        "riscv" if mcu not in ("esp32","esp32s2","esp32s3") else "xtensa",
+                        "riscv" if is_riscv else "xtensa",

Also applies to: 118-121

builder/main.py (1)

588-619: esp-idf-size invocation is robust; consider surfacing stderr on nonzero exit (optional)

Using PYTHON_EXE -m esp_idf_size is correct. As an optional improvement, print stderr when the command fails to aid troubleshooting.

-        result = subprocess.run(cmd, check=False, capture_output=False, env=os.environ)
+        result = subprocess.run(cmd, check=False, capture_output=True, text=True, env=os.environ)
         if result.returncode != 0:
             print(f"Warning: esp-idf-size exited with code {result.returncode}")
+            if result.stderr:
+                print(result.stderr.strip())
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5adc2 and aaaccf6.

📒 Files selected for processing (4)
  • builder/frameworks/_embed_files.py (1 hunks)
  • builder/frameworks/ulp.py (1 hunks)
  • builder/main.py (6 hunks)
  • builder/penv_setup.py (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.
📚 Learning: 2025-08-10T16:59:15.510Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:265-275
Timestamp: 2025-08-10T16:59:15.510Z
Learning: In the ESP32 platform builder's penv_setup.py, the esptool_repo_path used in subprocess calls comes from PlatformIO's internal package management system (platform.get_package_dir()) which is a trusted source. Command injection concerns are not applicable here since this is build infrastructure code with controlled inputs, not user-facing code accepting arbitrary input.

Applied to files:

  • builder/frameworks/ulp.py
  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-07-13T20:03:29.695Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#225
File: builder/main.py:99-108
Timestamp: 2025-07-13T20:03:29.695Z
Learning: In the ESP32 platform builder, the subprocess call to get site-packages directory in setup_python_paths() function is intentionally left without error handling because if this basic Python operation fails, it indicates a fundamental system issue that would cause the entire build process to fail anyway.

Applied to files:

  • builder/frameworks/ulp.py
  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T19:13:25.569Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:26-40
Timestamp: 2025-08-10T19:13:25.569Z
Learning: In the pioarduino/platform-espressif32 repository's builder/penv_setup.py file, semantic_version is imported but intentionally not listed in python_deps because it's a transitive dependency of platformio-core (version 2.10.*). When platformio is installed from the GitHub URL, semantic_version is automatically installed as one of its dependencies.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-05T11:47:41.756Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T19:12:17.965Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:19-19
Timestamp: 2025-08-10T19:12:17.965Z
Learning: In PlatformIO ESP32 platform builds, `semantic_version` is a dependency provided by PlatformIO itself and is guaranteed to be available when the build scripts run. The module is already imported at the top level in existing files like `builder/frameworks/espidf.py`, so lazy-loading it is unnecessary.

Applied to files:

  • builder/penv_setup.py
🧬 Code Graph Analysis (1)
builder/main.py (1)
builder/penv_setup.py (1)
  • setup_python_environment (311-363)
🪛 Ruff (0.12.2)
builder/frameworks/ulp.py

40-40: Undefined name idf_variant

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: build (windows-latest, examples/arduino-matter-light)
  • GitHub Check: build (windows-latest, examples/espidf-storage-sdcard)
  • GitHub Check: build (windows-latest, examples/espidf-peripherals-usb)
  • GitHub Check: build (macos-15, examples/arduino-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-storage-sdcard)
  • GitHub Check: build (ubuntu-latest, examples/espidf-exceptions)
  • GitHub Check: build (windows-latest, examples/arduino-zigbee-light)
  • GitHub Check: build (windows-latest, examples/arduino-wifiscan)
  • GitHub Check: build (windows-latest, examples/arduino-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-ulp-riscv)
  • GitHub Check: build (ubuntu-latest, examples/espidf-peripherals-uart)
  • GitHub Check: build (ubuntu-latest, examples/espidf-ulp-lp)
  • GitHub Check: build (ubuntu-latest, examples/espidf-coap-server)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-littlefs)
  • GitHub Check: build (ubuntu-latest, examples/arduino-usb-keyboard)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-matter-light)
  • GitHub Check: build (ubuntu-latest, examples/tasmota)
  • GitHub Check: build (ubuntu-latest, examples/arduino-wifiscan)
  • GitHub Check: build (ubuntu-latest, examples/arduino-rmt-blink)
  • GitHub Check: build (ubuntu-latest, examples/arduino-blink)
🔇 Additional comments (6)
builder/penv_setup.py (2)

19-19: Top-level import of semantic_version is acceptable in this context

PlatformIO provides semantic_version as a transitive dependency. Keeping the top-level import is fine here and aligns with existing builder modules.


31-45: Do not add semantic_version to python_deps (transitive via PlatformIO)

semantic_version is installed via the pinned PlatformIO core package. No need to duplicate it in python_deps.

builder/main.py (4)

35-47: Good refactor: penv setup is cleanly encapsulated

Importing and delegating penv/deps to setup_python_environment simplifies main.py and reduces duplication. The returned PYTHON_EXE usage in subprocesses is correct per prior best practices.


420-423: MCU inversion consistent with other files

Using RISC‑V when MCU is not in ("esp32","esp32s2","esp32s3") keeps Xtensa handling explicit and matches changes in _embed_files.py and ulp.py.

Also applies to: 447-455


429-433: Quoting esptool path via OBJCOPY is correct and cross-platform friendly

Conditionally quoting $OBJCOPY avoids issues with spaces in paths, and using it for both elf2image and erase aligns with existing conventions.

Also applies to: 435-463


771-772: UPLOADCMD change (uploadfs): unquoted $UPLOADER is fine due to pre-quoting

Since $UPLOADER now contains quotes when needed (path with spaces), removing quotes here avoids double-quoting pitfalls.

@Jason2866
Copy link
Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

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

🔭 Outside diff range comments (2)
builder/frameworks/ulp.py (2)

44-49: Guard PATH entries and prefer penv Python dir over where_is_program('python')

Avoid adding empty/None entries to PATH and prefer the already-prepared penv Python for consistency with the refactor. This also avoids a potential TypeError if where_is_program("python") returns None.

-    toolchain_path_ulp = platform.get_package_dir(
-        "toolchain-esp32ulp"
-        if sdk_config.get("ULP_COPROC_TYPE_FSM", False)
-        else ""
-    )
+    toolchain_path_ulp = platform.get_package_dir("toolchain-esp32ulp") \
+        if sdk_config.get("ULP_COPROC_TYPE_FSM", False) else None
@@
-    additional_packages = [
-        toolchain_path,
-        toolchain_path_ulp,
-        platform.get_package_dir("tool-ninja"),
-        os.path.join(platform.get_package_dir("tool-cmake"), "bin"),
-        os.path.dirname(where_is_program("python")),
-    ]
-
-    for package in additional_packages:
-        ulp_env.PrependENVPath("PATH", package)
+    python_dir = os.path.dirname(ulp_env.subst("$PYTHONEXE")) or ""
+    additional_packages = (
+        toolchain_path,
+        toolchain_path_ulp,
+        platform.get_package_dir("tool-ninja"),
+        os.path.join(platform.get_package_dir("tool-cmake"), "bin"),
+        python_dir,
+    )
+    for package in additional_packages:
+        if package:
+            ulp_env.PrependENVPath("PATH", package)

Also applies to: 58-60


96-99: Fix include concatenation: currently missing the delimiter

comp_includes = comp_includes + plain_includes concatenates two strings without a semicolon. This will merge the last and first include paths together.

-        comp_includes = ";".join(get_component_includes(target_config))
-        plain_includes = ";".join(app_includes["plain_includes"])
-        comp_includes = comp_includes + plain_includes
+        comp_includes_list = get_component_includes(target_config)
+        plain_includes_list = app_includes["plain_includes"]
+        comp_includes = ";".join(comp_includes_list + plain_includes_list)
♻️ Duplicate comments (1)
builder/penv_setup.py (1)

19-19: Top-level semantic_version import is acceptable in this environment

Per prior discussion, PlatformIO guarantees this dependency; lazy-loading not required.

🧹 Nitpick comments (5)
builder/frameworks/ulp.py (2)

38-42: Toolchain selection inversion aligns with Xtensa/RISC-V split — LGTM

Condition correctly restricts xtensa toolchain to esp32/esp32s2/esp32s3, defaulting others to riscv32. Nit: add spaces after commas for readability.

-        if idf_variant in ("esp32","esp32s2","esp32s3")
+        if idf_variant in ("esp32", "esp32s2", "esp32s3")

88-95: Style: prefer if not ... over explicit == False

Pythonic and clearer.

-        if lp_core_ulp_enabled == False:
+        if not lp_core_ulp_enabled:
builder/frameworks/_embed_files.py (2)

24-26: Centralized Xtensa detection — LGTM

Consistent is_xtensa check improves readability and aligns with main.py/ulp.py. Nit: spacing.

-is_xtensa = mcu in ("esp32","esp32s2","esp32s3")
+is_xtensa = mcu in ("esp32", "esp32s2", "esp32s3")

106-123: Objcopy selection inversion is correct and complete

Switching on is_xtensa cleanly adjusts executable name, output format, and architecture flags. Minor readability nit: prefer f-strings for the xtensa objcopy name.

-                        else "xtensa-%s-elf-objcopy" % mcu,
+                        else f"xtensa-{mcu}-elf-objcopy",
builder/penv_setup.py (1)

74-88: Optional: avoid adding penv root to sys.path

Adding site-packages is needed; adding penv_dir itself is not. Avoids polluting sys.path with a directory that isn't a module root.

-    # Add penv_dir to module search path
-    site.addsitedir(penv_dir)
-
     # Add site-packages directory
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5adc2 and bab33a8.

📒 Files selected for processing (4)
  • builder/frameworks/_embed_files.py (2 hunks)
  • builder/frameworks/ulp.py (1 hunks)
  • builder/main.py (9 hunks)
  • builder/penv_setup.py (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.
📚 Learning: 2025-08-10T16:59:15.510Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:265-275
Timestamp: 2025-08-10T16:59:15.510Z
Learning: In the ESP32 platform builder's penv_setup.py, the esptool_repo_path used in subprocess calls comes from PlatformIO's internal package management system (platform.get_package_dir()) which is a trusted source. Command injection concerns are not applicable here since this is build infrastructure code with controlled inputs, not user-facing code accepting arbitrary input.

Applied to files:

  • builder/frameworks/ulp.py
  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-07-13T20:03:29.695Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#225
File: builder/main.py:99-108
Timestamp: 2025-07-13T20:03:29.695Z
Learning: In the ESP32 platform builder, the subprocess call to get site-packages directory in setup_python_paths() function is intentionally left without error handling because if this basic Python operation fails, it indicates a fundamental system issue that would cause the entire build process to fail anyway.

Applied to files:

  • builder/frameworks/ulp.py
  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-05T11:47:41.756Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T19:13:25.569Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:26-40
Timestamp: 2025-08-10T19:13:25.569Z
Learning: In the pioarduino/platform-espressif32 repository's builder/penv_setup.py file, semantic_version is imported but intentionally not listed in python_deps because it's a transitive dependency of platformio-core (version 2.10.*). When platformio is installed from the GitHub URL, semantic_version is automatically installed as one of its dependencies.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T20:18:20.676Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:141-161
Timestamp: 2025-08-10T20:18:20.676Z
Learning: In the pioarduino/platform-espressif32 repository's builder/penv_setup.py file, the subprocess timeouts (30s for pip install, 90s for listing, 600s for package installation) are intentionally kept at their current values. When timeouts occur, it's typically due to server-side connection aborts rather than slow networks, so increasing timeout values doesn't resolve the issue.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T19:12:17.965Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:19-19
Timestamp: 2025-08-10T19:12:17.965Z
Learning: In PlatformIO ESP32 platform builds, `semantic_version` is a dependency provided by PlatformIO itself and is guaranteed to be available when the build scripts run. The module is already imported at the top level in existing files like `builder/frameworks/espidf.py`, so lazy-loading it is unnecessary.

Applied to files:

  • builder/penv_setup.py
🧬 Code Graph Analysis (1)
builder/main.py (1)
builder/penv_setup.py (1)
  • setup_python_environment (311-361)
🪛 Ruff (0.12.2)
builder/frameworks/_embed_files.py

107-107: Undefined name env

(F821)


110-110: Undefined name env

(F821)

builder/frameworks/ulp.py

40-40: Undefined name idf_variant

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: build (macos-15, examples/espidf-ulp-lp)
  • GitHub Check: build (macos-15, examples/espidf-storage-sdcard)
  • GitHub Check: build (macos-15, examples/espidf-arduino-matter-light)
  • GitHub Check: build (windows-latest, examples/espidf-peripherals-uart)
  • GitHub Check: build (windows-latest, examples/espidf-peripherals-usb)
  • GitHub Check: build (macos-15, examples/espidf-arduino-littlefs)
  • GitHub Check: build (windows-latest, examples/espidf-exceptions)
  • GitHub Check: build (windows-latest, examples/espidf-blink)
  • GitHub Check: build (windows-latest, examples/arduino-matter-light)
  • GitHub Check: build (ubuntu-latest, examples/espidf-ulp-lp)
  • GitHub Check: build (ubuntu-latest, examples/espidf-ulp-riscv)
  • GitHub Check: build (windows-latest, examples/arduino-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-exceptions)
  • GitHub Check: build (ubuntu-latest, examples/tasmota)
  • GitHub Check: build (ubuntu-latest, examples/espidf-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-blink)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-matter-light)
  • GitHub Check: build (ubuntu-latest, examples/arduino-blink)
  • GitHub Check: build (ubuntu-latest, examples/arduino-wifiscan)
🔇 Additional comments (13)
builder/frameworks/ulp.py (1)

40-40: Ruff F821 false positive: idf_variant is imported by SCons

idf_variant is provided by Import("env sdk_config project_config app_includes idf_variant") on Line 24. Safe to ignore the linter warning in this SCons context.

builder/frameworks/_embed_files.py (1)

107-110: Ruff F821 false positive: env is defined via SCons Import

Import("env") on Line 21 provides env. The linter doesn't understand SCons’ dynamic import.

builder/main.py (5)

35-47: Great refactor: penv setup extracted and single entrypoint — LGTM

Importing setup_python_environment and wiring returned paths keeps main lean and consistent. No public API changes, aligns with PR goals.


417-424: Architecture detection via is_xtensa is consistent with other files

Clear separation between Xtensa and RISC-V; toolchain_arch fallback is correct.


447-456: GDB tool package selection now matches MCU arch — LGTM

Correctly selects xtensa vs riscv GDB and composes binary name from toolchain_arch.


561-616: Firmware metrics cleanup — LGTM

Removes duplication, uses penv Python, handles args sanely, and preserves diagnostics. Nice.


430-434: Quoting strategy for $OBJCOPY is robust

Pre-quoting the uploader path only when it contains spaces removes unnecessary quotes while ensuring correctness.

  • Verified via rg that OBJCOPY is defined only as the upload tool (esptool) and never used elsewhere to invoke binutils’ objcopy.
  • No occurrences of OBJCOPY found outside of this context (e.g. lines 455–459, 465–469, 511–515).

All checks pass—no further changes required.

builder/penv_setup.py (6)

48-56: Executable path helper — LGTM

Correctly handles Scripts/bin and .exe suffix on Windows.


58-72: Venv creation is clear; minor note on invoking Python

Using "$PYTHONEXE" -m venv is fine in PIO builds. Assert on pip existence is a good sanity check.


90-121: Dependency resolution logic — LGTM

  • Handles URL-pinned platformio with semantic version check.
  • Case-insensitive matching and semver spec for others.

123-148: uv-first install flow with bounded timeouts — LGTM

Checks uv availability, installs when missing, lists installed pkgs, computes missing/upgrades, and installs with uv. Timeouts match earlier design decisions.

Also applies to: 164-247


264-292: Esptool install from package path with robust verification — LGTM

  • Path comes from PlatformIO package dir (trusted).
  • Avoids false positive by comparing exact MATCH string and using realpaths with normcase and startswith.
  • Editing mode install ensures reusability.

Also applies to: 297-309


326-361: Entry-point API is cohesive and safe

Enforces Python >= 3.10, wires env.PYTHONEXE, installs deps and esptool, returns both paths. Clear failure modes via stderr + exit.

@Jason2866
Copy link
Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

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: 1

🧹 Nitpick comments (1)
builder/penv_setup.py (1)

74-88: Python path setup: consider skipping addsitedir(penv_dir)

Minor: addsitedir(penv_dir) is typically unnecessary; only site-packages matters. Safe to keep, but you could omit it to reduce sys.path noise.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5adc2 and a0f25f4.

📒 Files selected for processing (4)
  • builder/frameworks/_embed_files.py (2 hunks)
  • builder/frameworks/ulp.py (2 hunks)
  • builder/main.py (9 hunks)
  • builder/penv_setup.py (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.
📚 Learning: 2025-08-05T11:47:41.756Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#246
File: builder/main.py:0-0
Timestamp: 2025-08-05T11:47:41.756Z
Learning: In the ESP32 platform builder, subprocess calls correctly use the virtual environment Python by explicitly passing PYTHON_EXE (which is set to the venv python path) as the first argument in the command array, rather than relying on environment variable propagation. The env=os.environ parameter only affects environment variables, not the executable path itself.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
  • builder/frameworks/ulp.py
📚 Learning: 2025-08-10T19:13:25.569Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:26-40
Timestamp: 2025-08-10T19:13:25.569Z
Learning: In the pioarduino/platform-espressif32 repository's builder/penv_setup.py file, semantic_version is imported but intentionally not listed in python_deps because it's a transitive dependency of platformio-core (version 2.10.*). When platformio is installed from the GitHub URL, semantic_version is automatically installed as one of its dependencies.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T16:59:15.510Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:265-275
Timestamp: 2025-08-10T16:59:15.510Z
Learning: In the ESP32 platform builder's penv_setup.py, the esptool_repo_path used in subprocess calls comes from PlatformIO's internal package management system (platform.get_package_dir()) which is a trusted source. Command injection concerns are not applicable here since this is build infrastructure code with controlled inputs, not user-facing code accepting arbitrary input.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-07-13T20:03:29.695Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#225
File: builder/main.py:99-108
Timestamp: 2025-07-13T20:03:29.695Z
Learning: In the ESP32 platform builder, the subprocess call to get site-packages directory in setup_python_paths() function is intentionally left without error handling because if this basic Python operation fails, it indicates a fundamental system issue that would cause the entire build process to fail anyway.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T20:18:20.676Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:141-161
Timestamp: 2025-08-10T20:18:20.676Z
Learning: In the pioarduino/platform-espressif32 repository's builder/penv_setup.py file, the subprocess timeouts (30s for pip install, 90s for listing, 600s for package installation) are intentionally kept at their current values. When timeouts occur, it's typically due to server-side connection aborts rather than slow networks, so increasing timeout values doesn't resolve the issue.

Applied to files:

  • builder/main.py
  • builder/penv_setup.py
📚 Learning: 2025-08-10T19:12:17.965Z
Learnt from: Jason2866
PR: pioarduino/platform-espressif32#255
File: builder/penv_setup.py:19-19
Timestamp: 2025-08-10T19:12:17.965Z
Learning: In PlatformIO ESP32 platform builds, `semantic_version` is a dependency provided by PlatformIO itself and is guaranteed to be available when the build scripts run. The module is already imported at the top level in existing files like `builder/frameworks/espidf.py`, so lazy-loading it is unnecessary.

Applied to files:

  • builder/penv_setup.py
🧬 Code Graph Analysis (1)
builder/main.py (1)
builder/penv_setup.py (1)
  • setup_python_environment (311-361)
🪛 Ruff (0.12.2)
builder/frameworks/_embed_files.py

107-107: Undefined name env

(F821)


110-110: Undefined name env

(F821)

builder/frameworks/ulp.py

34-34: Undefined name idf_variant

(F821)


47-47: Undefined name sdk_config

(F821)


92-92: Undefined name idf_variant

(F821)


99-99: Undefined name app_includes

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: build (macos-15, examples/espidf-ulp)
  • GitHub Check: build (macos-15, examples/espidf-arduino-littlefs)
  • GitHub Check: build (macos-15, examples/espidf-blink)
  • GitHub Check: build (macos-15, examples/espidf-arduino-blink)
  • GitHub Check: build (windows-latest, examples/espidf-blink)
  • GitHub Check: build (windows-latest, examples/arduino-NimBLE-ext_client)
  • GitHub Check: build (macos-15, examples/arduino-NimBLE-ext_client)
  • GitHub Check: build (windows-latest, examples/espidf-peripherals-uart)
  • GitHub Check: build (windows-latest, examples/espidf-http-request)
  • GitHub Check: build (windows-latest, examples/espidf-peripherals-usb)
  • GitHub Check: build (windows-latest, examples/espidf-arduino-blink)
  • GitHub Check: build (windows-latest, examples/espidf-hello-world)
  • GitHub Check: build (windows-latest, examples/arduino-wifiscan)
  • GitHub Check: build (ubuntu-latest, examples/espidf-blink)
  • GitHub Check: build (windows-latest, examples/arduino-zigbee-switch)
  • GitHub Check: build (windows-latest, examples/arduino-matter-light)
  • GitHub Check: build (ubuntu-latest, examples/espidf-arduino-matter-light)
  • GitHub Check: build (ubuntu-latest, examples/arduino-wifiscan)
  • GitHub Check: build (ubuntu-latest, examples/tasmota)
🔇 Additional comments (21)
builder/frameworks/_embed_files.py (2)

24-26: Unify MCU detection with is_xtensa looks good

The boolean flag simplifies logic across the file and aligns with other builders. No functional concerns.


114-122: Objcopy/toolchain target selection is correct

  • Executable names: xtensa-{mcu}-elf-objcopy for esp32/esp32s2/esp32s3 and riscv32-esp-elf-objcopy for RISC‑V look right.
  • Targets/arch: elf32-xtensa-le vs elf32-littleriscv and “xtensa” vs “riscv” are appropriate.

No action required.

builder/frameworks/ulp.py (2)

34-34: Consistent Xtensa/RISC‑V detection

Using is_xtensa keeps this builder consistent with main.py and _embed_files.py. Good change.


98-101: Include path concatenation fix is cleaner

Joining component and plain includes before “;” join avoids malformed paths. Looks good.

builder/main.py (8)

45-47: Centralizing Python env setup is a solid refactor

Single call to setup_python_environment greatly reduces complexity and keeps the builder lean. Returning both PYTHON_EXE and esptool path is practical.


417-424: MCU arch inversion reads clearer and reduces duplication

is_xtensa plus a single negation for RISC‑V is consistent with other files. Toolchain arch resolution is correct for esp32/esp32s2/esp32s3 vs RISC‑V parts.


430-434: Safe quoting for paths with spaces

Pre-quoting uploader_path only when needed avoids double-quoting and makes command construction robust. Good detail.


457-468: OBJCOPY repurposed to esptool path: acceptable here

In this build system OBJCOPY is used to run esptool (elf2image/erase). The rename to uploader_path improves readability; assignment is correct. ERASEFLAGS quoting for $UPLOAD_PORT prevents argument splitting issues.


585-616: Firmware metrics: correct use of venv Python

Using PYTHON_EXE explicitly (rather than relying on PATH) matches platform conventions. Error handling and optional verbosity are reasonable.


719-741: Esptool upload commands: quoting and flags look right

  • UPLOADER now uses the pre-quoted uploader_path, and $UPLOADCMD dropped extra quotes — avoids double-quoting.
  • Keeping "$UPLOAD_PORT" quoted is correct.
    Looks good.

Also applies to: 745-769


784-795: DFU upload: keep $SOURCE quoted

Retaining quotes around "$SOURCE" is needed for paths with spaces. Change is correct.


35-36: Import path verified: penv_setup.py resides alongside main.py and the import works as expected under SCons. No fallback or changes needed.

builder/penv_setup.py (9)

31-45: Dependency pinning looks intentional and sufficient

  • platformio pinned to v6.1.18 via URL; semantic_version comes transitively from platformio-core (per prior context), so no need to list it explicitly.
  • Specs for other deps are reasonable.

No change requested.


58-72: venv creation via SCons action is fine; assert ensures integrity

Creating the venv with "$PYTHONEXE" and asserting the pip binary exists is a good safeguard. No issues spotted.


102-121: Version comparison logic is solid; URL version pin handled correctly

  • Using PLATFORMIO_URL_VERSION_RE + pepver_to_semver avoids false positives.
  • SimpleSpec matching for other deps is appropriate.

Looks good.


130-163: UV bootstrap flow is reasonable

Detect uv first, then install via pip if missing. Timeouts/readability are fine; messages are actionable.


172-203: Package listing via uv pip list (JSON) is correct

Parsing into a lowercase-name dict with pepver_to_semver normalization is the right approach. Good warnings on failures.


264-293: Path verification for esptool source is robust

  • Using sys.argv to pass the path avoids interpolation issues.
  • Comparing realpath with startswith on normalized case is sufficient for editable installs.

Good guard; early return when MATCH is printed is precise.


297-307: Editable install via uv pip is appropriate

Force-reinstall editable from tool-esptoolpy dir ensures alignment with the packaged source. Error handling is clear.


335-361: End-to-end env setup is clean and idempotent

  • Version gate (>=3.10), venv setup, env.Replace(PYTHONEXE), site paths, deps install, and esptool install are well sequenced.
  • Returning both penv python and esptool path matches main.py expectations.

LGTM.


223-231: Ignore timeout increase—30s is intentional
The 30-second timeout for subprocess.run() in builder/penv_setup.py (lines 223–231) matches our established defaults (30 s for pip installs, 90 s for listings, 600 s for full package installations). These values are deliberate to fail fast on server-side aborts rather than slow networks. No change needed.

Likely an incorrect or invalid review comment.

@Jason2866
Copy link
Author

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 11, 2025

✅ Actions performed

Full review triggered.

@Jason2866 Jason2866 merged commit aa97eb0 into develop Aug 11, 2025
1 check passed
@Jason2866 Jason2866 deleted the move_env_install branch August 11, 2025 09:17
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.

1 participant