refactor(xtask): introduce build options
What changed, and why it matters
This commit is a straightforward internal code reorganization in Trezor's build tooling. It moves build-option definitions from one Rust source file to a new module and adds the ability to load those options from a configuration file. There is no change to the actual firmware behavior, no bug fix, and no security-related change.
No security action required. Treat as routine build-system refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors core/embed/xtask/src/args.rs by extracting BuildOptions, ResolvedBuildArgs, and their resolution logic into a new core/embed/xtask/src/options.rs module. It derives Deserialize on BuildOptions and ConsoleType to support deserialization from config files, introduces defaults_for, overlay, and postfix helpers for layered option resolution, and renames profile_name() to cargo_profile_name(). All other call sites are updated to import from the new module. No firmware logic, cryptographic code, or security boundary is modified.
Changed components
core/embed/xtask/src/args.rscore/embed/xtask/src/options.rs (new)core/embed/xtask/src/artifacts.rscore/embed/xtask/src/cargo.rscore/embed/xtask/src/feature_resolver.rscore/embed/xtask/src/helpers.rscore/embed/xtask/src/lib.rsInspect captured patch +275 / −200
diff --git a/core/embed/xtask/src/args.rs b/core/embed/xtask/src/args.rs
index a20eeb4b..0e5e91dd 100644
--- a/core/embed/xtask/src/args.rs
+++ b/core/embed/xtask/src/args.rs
@@ -1,7 +1,9 @@
use anyhow::{Result, anyhow};
use clap::{Args, Parser, Subcommand, ValueEnum};
+use serde::Deserialize;
pub use crate::model::Model;
+use crate::options::BuildOptions;
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Project {
@@ -101,7 +103,7 @@ impl Project {
}
}
-#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(ValueEnum, Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
pub enum ConsoleType {
Vcp,
Swo,
@@ -155,198 +157,8 @@ pub struct BuildArgs {
#[arg(long, short = 'e')]
pub emulator: bool,
- /// Enable debug build
- #[arg(long, short = 'd', num_args = 0..=1, default_missing_value = "true")]
- pub debug: Option<bool>,
-
- /// Debug console backend
- #[arg(long)]
- pub dbg_console: Option<ConsoleType>,
-
- /// Build Bitcoin-only firmware
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub btc_only: Option<bool>,
-
- /// Enable production build
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub production: Option<bool>,
-
- /// Force bootloader upgrade
- #[arg(long)]
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub force_bootloader_upgrade: Option<bool>,
-
- /// Use dev bootloader
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub bootloader_devel: Option<bool>,
-
- /// Enable unsafe firmware features
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub unsafe_fw: Option<bool>,
-
- /// Embed frozen MicroPython modules
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub frozen: Option<bool>,
-
- /// Include MicroPython source lines
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub source_lines: Option<bool>,
-
- /// Optimize MicroPython bytecode
- #[arg(long, num_args = 0..=1, default_missing_value = "true", overrides_with = "pyopt")]
- pub pyopt: Option<bool>,
-
- /// Enable Micropython memory performance measurements
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub mem_perf: Option<bool>,
-
- /// Enable debug link
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub debug_link: Option<bool>,
-
- /// Enable N4W1 support
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub n4w1: Option<bool>,
-
- /// Disable UI animations
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub disable_animation: Option<bool>,
-
- /// Show UI perf overlay
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub perf_overlay: Option<bool>,
-
- /// Include crypto benchmarks
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub benchmark: Option<bool>,
-
- /// Log stack usage
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub log_stack_usage: Option<bool>,
-
- /// Use blocking VCP writes, in order to allow reliable debug data
- /// transmission over VCP. Disabled by default, to prevent debug
- /// firmware from getting stuck while writing log messages (if the host
- /// is not reading them).
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub block_on_vcp: Option<bool>,
-
- /// Enable Address Sanitizer (ASAN) instrumentation
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub asan: Option<bool>,
-
- /// Enable external app loading
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub apps: Option<bool>,
-
- /// Disable OPTIGA support
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub disable_optiga: Option<bool>,
-
- /// Board revision to build for (defaults to model's default_board)
- #[arg(long, short = 'b')]
- pub board: Option<String>,
-
- /// Disable TROPIC support
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub disable_tropic: Option<bool>,
-
- /// Enable insecure storage test mode
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub storage_insecure_testing_mode: Option<bool>,
-
- /// Emits memory analysis output (type sizes and stack sizes)
- #[arg(long, num_args = 0..=1, default_missing_value = "true")]
- pub emit_memory_analysis: Option<bool>,
-
- /// Output cargo timings
- #[arg(long)]
- pub timings: bool,
-
- /// Enable verbose output
- #[arg(long)]
- pub verbose: bool,
-}
-
-#[derive(Debug, Clone)]
-pub struct ResolvedBuildArgs {
- pub project: Project,
- pub model: Model,
- pub emulator: bool,
- pub debug: bool,
- pub dbg_console: Option<ConsoleType>,
- pub btc_only: bool,
- pub production: bool,
- pub force_bootloader_upgrade: bool,
- pub bootloader_devel: bool,
- pub unsafe_fw: bool,
- pub frozen: bool,
- pub source_lines: bool,
- pub pyopt: bool,
- pub mem_perf: bool,
- pub debug_link: bool,
- pub n4w1: bool,
- pub disable_animation: bool,
- pub perf_overlay: bool,
- pub benchmark: bool,
- pub log_stack_usage: bool,
- pub block_on_vcp: bool,
- pub asan: bool,
- pub apps: bool,
- pub disable_optiga: bool,
- pub board: Option<String>,
- pub disable_tropic: bool,
- pub storage_insecure_testing_mode: bool,
- pub emit_memory_analysis: bool,
- pub timings: bool,
- pub verbose: bool,
-}
-
-impl ResolvedBuildArgs {
- pub fn from_build_args(args: &BuildArgs) -> Self {
- let pyopt = args.pyopt.unwrap_or(true);
- Self {
- project: args.project,
- model: args.model,
- emulator: args.emulator,
- debug: args.debug.unwrap_or(args.emulator),
- dbg_console: args.dbg_console,
- btc_only: args.btc_only.unwrap_or(false),
- production: args.production.unwrap_or(false),
- force_bootloader_upgrade: args.force_bootloader_upgrade.unwrap_or(false),
- bootloader_devel: args.bootloader_devel.unwrap_or(false),
- unsafe_fw: args.unsafe_fw.unwrap_or(false),
- frozen: args.frozen.unwrap_or(false),
- source_lines: args.source_lines.unwrap_or(args.emulator),
- pyopt,
- mem_perf: args.mem_perf.unwrap_or(false),
- debug_link: args.debug_link.unwrap_or(!pyopt),
- n4w1: args.n4w1.unwrap_or(false),
- disable_animation: args.disable_animation.unwrap_or(false),
- perf_overlay: args.perf_overlay.unwrap_or(false),
- benchmark: args.benchmark.unwrap_or(false),
- log_stack_usage: args.log_stack_usage.unwrap_or(false),
- block_on_vcp: args.block_on_vcp.unwrap_or(false),
- asan: args.asan.unwrap_or(false),
- apps: args.apps.unwrap_or(false),
- disable_optiga: args.disable_optiga.unwrap_or(false),
- board: args.board.clone(),
- disable_tropic: args.disable_tropic.unwrap_or(false),
- storage_insecure_testing_mode: args.storage_insecure_testing_mode.unwrap_or(false),
- emit_memory_analysis: args.emit_memory_analysis.unwrap_or(false),
- timings: args.timings,
- verbose: args.verbose,
- }
- }
-
- /// Determines the Cargo profile to use
- pub fn profile_name(&self) -> &'static str {
- if self.debug {
- if self.emulator { "dev" } else { "debug-opt" }
- } else {
- "release"
- }
- }
+ #[command(flatten)]
+ pub options: BuildOptions,
}
#[derive(Args, Debug)]
diff --git a/core/embed/xtask/src/artifacts.rs b/core/embed/xtask/src/artifacts.rs
index cf7f07e9..c94abc73 100644
--- a/core/embed/xtask/src/artifacts.rs
+++ b/core/embed/xtask/src/artifacts.rs
@@ -4,8 +4,8 @@ use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
-use crate::args::ResolvedBuildArgs;
use crate::helpers;
+use crate::options::ResolvedBuildArgs;
/// Returns whether a filesystem entry exists without following symlinks, so
/// broken symlinks are still treated as present and can be replaced.
diff --git a/core/embed/xtask/src/cargo.rs b/core/embed/xtask/src/cargo.rs
index 8b1e0e20..705fb29e 100644
--- a/core/embed/xtask/src/cargo.rs
+++ b/core/embed/xtask/src/cargo.rs
@@ -3,7 +3,8 @@ use std::process;
use anyhow::{Context, Result, ensure};
use owo_colors::OwoColorize;
-use crate::args::{BuildArgs, Project, ResolvedBuildArgs, TestArgs};
+use crate::args::{BuildArgs, Project, TestArgs};
+use crate::options::ResolvedBuildArgs;
use crate::{artifacts, feature_resolver, helpers, memusage, postbuild, prebuild};
pub fn build(args: BuildArgs) -> Result<()> {
diff --git a/core/embed/xtask/src/feature_resolver.rs b/core/embed/xtask/src/feature_resolver.rs
index 53eb17b0..bd67f3c2 100644
--- a/core/embed/xtask/src/feature_resolver.rs
+++ b/core/embed/xtask/src/feature_resolver.rs
@@ -2,7 +2,8 @@ use std::process;
use anyhow::{Result, bail};
-use crate::args::{ConsoleType, Project, ResolvedBuildArgs};
+use crate::args::{ConsoleType, Project};
+use crate::options::ResolvedBuildArgs;
use crate::{config, helpers};
pub struct ResolvedBuildFeatures {
@@ -187,10 +188,10 @@ pub fn configure_cargo(args: &ResolvedBuildArgs, cmd: &mut process::Command) ->
cmd.args(["--package", args.project.package_name(args.emulator)]);
cmd.args(["--features", &resolved.features.join(",")]);
- cmd.args(["--profile", args.profile_name()]);
+ cmd.args(["--profile", args.cargo_profile_name()]);
cmd.env("TREZOR_BOARD_HEADER", &resolved.board_header);
- if args.profile_name() == "release" {
+ if args.cargo_profile_name() == "release" {
// Required by panic-immediate-abort in the release profile
rebuild_std = true;
}
diff --git a/core/embed/xtask/src/helpers.rs b/core/embed/xtask/src/helpers.rs
index d6e6a0a8..511ee4a2 100644
--- a/core/embed/xtask/src/helpers.rs
+++ b/core/embed/xtask/src/helpers.rs
@@ -4,7 +4,8 @@ use std::{env, fs};
use anyhow::{Context, Result, anyhow};
use cargo_metadata::MetadataCommand;
-use crate::args::{Model, Project, ResolvedBuildArgs};
+use crate::args::{Model, Project};
+use crate::options::ResolvedBuildArgs;
/// Returns the path to the built ELF file for the given build arguments.
pub fn elf_path(args: &ResolvedBuildArgs) -> Result<PathBuf> {
@@ -21,7 +22,7 @@ pub fn profile_dir(args: &ResolvedBuildArgs) -> Result<PathBuf> {
path = path.join(model_config.target_triple()?);
}
- let name = match args.profile_name() {
+ let name = match args.cargo_profile_name() {
"dev" => "debug", // Cargo uses "debug" folder for dev profile
name => name,
};
diff --git a/core/embed/xtask/src/lib.rs b/core/embed/xtask/src/lib.rs
index 2f5abfec..81177814 100644
--- a/core/embed/xtask/src/lib.rs
+++ b/core/embed/xtask/src/lib.rs
@@ -8,6 +8,7 @@ pub mod flash;
pub mod helpers;
pub mod memusage;
pub mod model;
+pub mod options;
pub mod postbuild;
pub mod prebuild;
pub mod upload;
diff --git a/core/embed/xtask/src/options.rs b/core/embed/xtask/src/options.rs
new file mode 100644
index 00000000..488bf18a
--- /dev/null
+++ b/core/embed/xtask/src/options.rs
@@ -0,0 +1,259 @@
+use clap::Args;
+use serde::Deserialize;
+
+use crate::args::{BuildArgs, ConsoleType, Model, Project};
+
+#[derive(Args, Deserialize, Debug, Clone, Default)]
+#[serde(deny_unknown_fields)]
+pub struct BuildOptions {
+ /// Enable debug build
+ #[arg(long, short = 'd', num_args = 0..=1, default_missing_value = "true")]
+ pub debug: Option<bool>,
+
+ /// Debug console backend
+ #[arg(long)]
+ pub dbg_console: Option<ConsoleType>,
+
+ /// Build Bitcoin-only firmware
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub btc_only: Option<bool>,
+
+ /// Enable production build
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub production: Option<bool>,
+
+ /// Force bootloader upgrade
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub force_bootloader_upgrade: Option<bool>,
+
+ /// Use dev bootloader
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub bootloader_devel: Option<bool>,
+
+ /// Enable unsafe firmware features
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub unsafe_fw: Option<bool>,
+
+ /// Embed frozen MicroPython modules
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub frozen: Option<bool>,
+
+ /// Include MicroPython source lines
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub source_lines: Option<bool>,
+
+ /// Optimize MicroPython bytecode
+ #[arg(long, num_args = 0..=1, default_missing_value = "true", overrides_with = "pyopt")]
+ pub pyopt: Option<bool>,
+
+ /// Enable Micropython memory performance measurements
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub mem_perf: Option<bool>,
+
+ /// Enable debug link
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub debug_link: Option<bool>,
+
+ /// Enable N4W1 support
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub n4w1: Option<bool>,
+
+ /// Disable UI animations
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub disable_animation: Option<bool>,
+
+ /// Show UI perf overlay
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub perf_overlay: Option<bool>,
+
+ /// Include crypto benchmarks
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub benchmark: Option<bool>,
+
+ /// Log stack usage
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub log_stack_usage: Option<bool>,
+
+ /// Use blocking VCP writes, in order to allow reliable debug data
+ /// transmission over VCP. Disabled by default, to prevent debug
+ /// firmware from getting stuck while writing log messages (if the host
+ /// is not reading them).
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub block_on_vcp: Option<bool>,
+
+ /// Enable Address Sanitizer (ASAN) instrumentation
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub asan: Option<bool>,
+
+ /// Enable external app loading
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub apps: Option<bool>,
+
+ /// Disable OPTIGA support
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub disable_optiga: Option<bool>,
+
+ /// Board revision to build for (defaults to model's default_board)
+ #[arg(long, short = 'b')]
+ pub board: Option<String>,
+
+ /// Disable TROPIC support
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub disable_tropic: Option<bool>,
+
+ /// Enable insecure storage test mode
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub storage_insecure_testing_mode: Option<bool>,
+
+ /// Emits memory analysis output (type sizes and stack sizes)
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub emit_memory_analysis: Option<bool>,
+
+ /// Output cargo timings
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub timings: Option<bool>,
+
+ /// Enable verbose output
+ #[arg(long, num_args = 0..=1, default_missing_value = "true")]
+ pub verbose: Option<bool>,
+}
+
+impl BuildOptions {
+ pub fn defaults_for(_project: Project, _model: Model, emulator: bool) -> Self {
+ Self {
+ debug: Some(emulator),
+ pyopt: Some(true),
+ source_lines: Some(emulator),
+ ..BuildOptions::default()
+ }
+ }
+
+ pub fn overlay(self, opt: Self) -> Self {
+ Self {
+ debug: opt.debug.or(self.debug),
+ dbg_console: opt.dbg_console.or(self.dbg_console),
+ btc_only: opt.btc_only.or(self.btc_only),
+ production: opt.production.or(self.production),
+ force_bootloader_upgrade: opt
+ .force_bootloader_upgrade
+ .or(self.force_bootloader_upgrade),
+ bootloader_devel: opt.bootloader_devel.or(self.bootloader_devel),
+ unsafe_fw: opt.unsafe_fw.or(self.unsafe_fw),
+ frozen: opt.frozen.or(self.frozen),
+ source_lines: opt.source_lines.or(self.source_lines),
+ pyopt: opt.pyopt.or(self.pyopt),
+ mem_perf: opt.mem_perf.or(self.mem_perf),
+ debug_link: opt.debug_link.or(self.debug_link),
+ n4w1: opt.n4w1.or(self.n4w1),
+ disable_animation: opt.disable_animation.or(self.disable_animation),
+ perf_overlay: opt.perf_overlay.or(self.perf_overlay),
+ benchmark: opt.benchmark.or(self.benchmark),
+ log_stack_usage: opt.log_stack_usage.or(self.log_stack_usage),
+ block_on_vcp: opt.block_on_vcp.or(self.block_on_vcp),
+ asan: opt.asan.or(self.asan),
+ apps: opt.apps.or(self.apps),
+ disable_optiga: opt.disable_optiga.or(self.disable_optiga),
+ board: opt.board.or(self.board),
+ disable_tropic: opt.disable_tropic.or(self.disable_tropic),
+ storage_insecure_testing_mode: opt
+ .storage_insecure_testing_mode
+ .or(self.storage_insecure_testing_mode),
+ emit_memory_analysis: opt.emit_memory_analysis.or(self.emit_memory_analysis),
+ timings: opt.timings.or(self.timings),
+ verbose: opt.verbose.or(self.verbose),
+ }
+ }
+
+ pub fn postfix(self) -> Self {
+ let pyopt = self.pyopt.unwrap_or(true);
+ Self {
+ debug_link: self.debug_link.or(Some(!pyopt)),
+ pyopt: Some(pyopt),
+ ..self
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct ResolvedBuildArgs {
+ pub project: Project,
+ pub model: Model,
+ pub emulator: bool,
+ pub debug: bool,
+ pub dbg_console: Option<ConsoleType>,
+ pub btc_only: bool,
+ pub production: bool,
+ pub force_bootloader_upgrade: bool,
+ pub bootloader_devel: bool,
+ pub unsafe_fw: bool,
+ pub frozen: bool,
+ pub source_lines: bool,
+ pub pyopt: bool,
+ pub mem_perf: bool,
+ pub debug_link: bool,
+ pub n4w1: bool,
+ pub disable_animation: bool,
+ pub perf_overlay: bool,
+ pub benchmark: bool,
+ pub log_stack_usage: bool,
+ pub block_on_vcp: bool,
+ pub asan: bool,
+ pub apps: bool,
+ pub disable_optiga: bool,
+ pub board: Option<String>,
+ pub disable_tropic: bool,
+ pub storage_insecure_testing_mode: bool,
+ pub emit_memory_analysis: bool,
+ pub timings: bool,
+ pub verbose: bool,
+}
+
+impl ResolvedBuildArgs {
+ pub fn from_build_args(args: &BuildArgs) -> Self {
+ let o = BuildOptions::defaults_for(args.project, args.model, args.emulator)
+ .overlay(args.options.clone())
+ .postfix();
+
+ Self {
+ project: args.project,
+ model: args.model,
+ emulator: args.emulator,
+ debug: o.debug.unwrap_or_default(),
+ dbg_console: o.dbg_console,
+ btc_only: o.btc_only.unwrap_or_default(),
+ production: o.production.unwrap_or_default(),
+ force_bootloader_upgrade: o.force_bootloader_upgrade.unwrap_or_default(),
+ bootloader_devel: o.bootloader_devel.unwrap_or_default(),
+ unsafe_fw: o.unsafe_fw.unwrap_or_default(),
+ frozen: o.frozen.unwrap_or_default(),
+ source_lines: o.source_lines.unwrap_or_default(),
+ pyopt: o.pyopt.unwrap_or_default(),
+ mem_perf: o.mem_perf.unwrap_or_default(),
+ debug_link: o.debug_link.unwrap_or_default(),
+ n4w1: o.n4w1.unwrap_or_default(),
+ disable_animation: o.disable_animation.unwrap_or_default(),
+ perf_overlay: o.perf_overlay.unwrap_or_default(),
+ benchmark: o.benchmark.unwrap_or_default(),
+ log_stack_usage: o.log_stack_usage.unwrap_or_default(),
+ block_on_vcp: o.block_on_vcp.unwrap_or_default(),
+ asan: o.asan.unwrap_or_default(),
+ apps: o.apps.unwrap_or_default(),
+ disable_optiga: o.disable_optiga.unwrap_or_default(),
+ board: o.board,
+ disable_tropic: o.disable_tropic.unwrap_or_default(),
+ storage_insecure_testing_mode: o.storage_insecure_testing_mode.unwrap_or_default(),
+ emit_memory_analysis: o.emit_memory_analysis.unwrap_or_default(),
+ timings: o.timings.unwrap_or_default(),
+ verbose: o.verbose.unwrap_or_default(),
+ }
+ }
+
+ /// Determines the Cargo profile to use
+ pub fn cargo_profile_name(&self) -> &'static str {
+ if self.debug {
+ if self.emulator { "dev" } else { "debug-opt" }
+ } else {
+ "release"
+ }
+ }
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.