diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..553f69224 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include build_backend.py diff --git a/build_backend.py b/build_backend.py new file mode 100644 index 000000000..ebbf0662b --- /dev/null +++ b/build_backend.py @@ -0,0 +1,91 @@ +"""PEP 517 adapter for fbuild's setuptools backend. + +Pip cannot append arbitrary Cargo arguments to a PEP 517 build. It can, +however, pass backend configuration through ``--config-settings``. Translate +the fbuild-specific profile setting into the environment variable consumed by +``setup.py`` and delegate all other behavior to setuptools. +""" + +from __future__ import annotations + +import os +from contextlib import contextmanager +from typing import Iterator + +from setuptools import build_meta as _setuptools + + +def _setting(config_settings: dict[str, object] | None, name: str) -> str | None: + if not config_settings or name not in config_settings: + return None + value = config_settings[name] + if isinstance(value, list): + value = value[-1] if value else "" + return str(value).strip().lower() + + +@contextmanager +def _profile_environment(config_settings: dict[str, object] | None) -> Iterator[None]: + """Apply the requested fbuild profile only while setuptools builds.""" + profile = _setting(config_settings, "fbuild-profile") + release = _setting(config_settings, "fbuild-release") + if profile is not None and profile not in {"dev", "debug", "release"}: + raise ValueError("fbuild-profile must be 'dev'/'debug' or 'release'") + if release is not None and release not in {"0", "1", "false", "true", "no", "yes"}: + raise ValueError("fbuild-release must be a boolean value") + + requested_release = ( + profile == "release" + if profile is not None + else release in {"1", "true", "yes"} + if release is not None + else None + ) + if requested_release is None: + yield + return + + previous = os.environ.get("FBUILD_BUILD_RELEASE") + os.environ["FBUILD_BUILD_RELEASE"] = "1" if requested_release else "0" + try: + yield + finally: + if previous is None: + os.environ.pop("FBUILD_BUILD_RELEASE", None) + else: + os.environ["FBUILD_BUILD_RELEASE"] = previous + + +def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): + with _profile_environment(config_settings): + return _setuptools.build_wheel(wheel_directory, config_settings, metadata_directory) + + +def build_editable(wheel_directory, config_settings=None, metadata_directory=None): + with _profile_environment(config_settings): + return _setuptools.build_editable(wheel_directory, config_settings, metadata_directory) + + +def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): + with _profile_environment(config_settings): + return _setuptools.prepare_metadata_for_build_wheel(metadata_directory, config_settings) + + +def get_requires_for_build_wheel(config_settings=None): + with _profile_environment(config_settings): + return _setuptools.get_requires_for_build_wheel(config_settings) + + +def build_sdist(sdist_directory, config_settings=None): + with _profile_environment(config_settings): + return _setuptools.build_sdist(sdist_directory, config_settings) + + +def get_requires_for_build_sdist(config_settings=None): + with _profile_environment(config_settings): + return _setuptools.get_requires_for_build_sdist(config_settings) + + +def prepare_metadata_for_build_editable(metadata_directory, config_settings=None): + with _profile_environment(config_settings): + return _setuptools.prepare_metadata_for_build_editable(metadata_directory, config_settings) diff --git a/crates/fbuild-build-esp/src/esp32/esp32_linker.rs b/crates/fbuild-build-esp/src/esp32/esp32_linker.rs index 296c7ff66..3a9713793 100644 --- a/crates/fbuild-build-esp/src/esp32/esp32_linker.rs +++ b/crates/fbuild-build-esp/src/esp32/esp32_linker.rs @@ -178,6 +178,16 @@ impl Esp32Linker { } } + // Keep section-level dead-code elimination enabled even when the SDK + // supplies a complete `flags/ld_flags` file. The SDK flags replace + // the JSON fallback above, and older SDK packages do not all include + // `--gc-sections`. This is the important size guard for quick/no-LTO + // builds: every function/data section can still be removed when it is + // unreachable from the firmware roots. + if !flags.iter().any(|flag| flag == "-Wl,--gc-sections") { + flags.push("-Wl,--gc-sections".to_string()); + } + flags } @@ -590,6 +600,7 @@ mod tests { assert!(flags.contains(&"-nostartfiles".to_string())); assert!(flags.contains(&"-u".to_string())); assert!(flags.contains(&"app_main".to_string())); + assert!(flags.contains(&"-Wl,--gc-sections".to_string())); // Profile link flags should NOT be present when SDK flags are used assert!(!flags.contains(&"-flto=auto".to_string())); } diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index 8fc68c9f3..1d6ddfee3 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -19,6 +19,18 @@ cd fbuild pip install -e . ``` +Source installs use Rust's fast dev profile by default (no Rust LTO), which +keeps local rebuilds quick. To explicitly build an optimized Rust wheel, pass +the PEP 517 backend setting: + +```bash +pip install . --config-settings fbuild-profile=release +``` + +`pip install . -- --release` is not supported: pip does not forward arbitrary +arguments after `--` to a PEP 517 backend. The `fbuild-profile` setting (or +`FBUILD_BUILD_RELEASE=1`) is the supported release override. + ## First Project Create a project with a PlatformIO-compatible layout: diff --git a/pyproject.toml b/pyproject.toml index 50595338a..0df9c07a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,14 @@ cache-keys = [ [build-system] requires = ["setuptools>=64"] -build-backend = "setuptools.build_meta" +build-backend = "build_backend" +backend-path = ["."] + +# Local/source installs use Cargo's dev profile (no LTO) by default. To +# request an optimized Rust wheel through pip, use: +# pip install . --config-settings fbuild-profile=release +# A literal `pip install . -- --release` cannot be forwarded through the PEP +# 517 interface; `--config-settings` is the standard backend override. [tool.setuptools] packages = ["fbuild", "fbuild.api"] diff --git a/setup.py b/setup.py index 35d7d7d63..0162670d3 100644 --- a/setup.py +++ b/setup.py @@ -267,7 +267,8 @@ def _use_release_profile() -> bool: 3 via `[profile.dev.package."*"]`, only our own crates compile unoptimized). Set `FBUILD_BUILD_RELEASE=1` to opt into a release build when you actually want a fast binary (CI, packaging, perf - tests). + tests). PEP 517 callers can use `--config-settings fbuild-profile=release`; + `build_backend.py` translates that setting to this environment variable. """ return os.environ.get("FBUILD_BUILD_RELEASE", "").lower() in ("1", "true", "yes")