Skip to content

Add flags to tune cores & jobs in build#42

Merged
RossComputerGuy merged 2 commits intomainfrom
RossComputerGuy/build-tuning
Feb 24, 2026
Merged

Add flags to tune cores & jobs in build#42
RossComputerGuy merged 2 commits intomainfrom
RossComputerGuy/build-tuning

Conversation

@RossComputerGuy
Copy link
Member

@RossComputerGuy RossComputerGuy commented Feb 24, 2026

Adds the ability to tune the build's cores & jobs. This is useful so runners with limited resources can be tuned, also helps prevent flakiness for some builds.

Summary by CodeRabbit

  • New Features

    • Added --build-jobs (-j) to control parallel build job count
    • Added --build-cores (-c) to control CPU core allocation for builds
    • Both options can be set via environment variables for persistent configuration
  • Improvements

    • Verbose mode now consistently includes build log output; cached outputs are still skipped when applicable

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

Adds two optional CLI options (build_jobs, build_cores) (sourced from env vars) and refactors build invocation to assemble a single argument vector that conditionally includes cores, max-jobs, and verbose logging flags before invoking the build command; cached-output skipping and logging remain.

Changes

Cohort / File(s) Summary
Build command CLI options & invocation refactor
src/cmd/build.rs
Adds build_jobs and build_cores fields (with env var sources) to the Build struct. Replaces separate verbose/non-verbose invocation paths with a unified mutable argument vector that conditionally appends --cores, --max-jobs, --print-build-logs, and the derivation path; preserves cached-output skipping and per-derivation logging.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble args and count the hops,
Two new flags tidy up my ops.
Cores and jobs now join the race,
One path to build — a tidy trace!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: adding CLI flags to tune cores and jobs in the build process, which matches the file modifications and PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch RossComputerGuy/build-tuning

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cmd/build.rs`:
- Around line 99-118: The verbose flag is ineffective because nix_command
currently runs via .output() which captures stdout/stderr so --print-build-logs
is discarded; update the call site in build.rs where args is built (variables:
cores, max_jobs, args, drv, verbose) to call the variant of nix_command that
streams or pipes output to the terminal when verbose is true (use the "piped" or
"spawn/stream" variant) and retain the current captured/no-output variant when
verbose is false, ensuring nix_command is invoked accordingly and error
handling/wrap_err("failed to build derivation") remains intact.
- Around line 26-32: The flags build_jobs and build_cores currently have
defaults that always pass --max-jobs/--cores and override nix.conf; change their
types to Option<usize> (update the struct fields build_jobs and build_cores and
their CLI arg definitions to be optional—remove default_value) and only push
"--max-jobs" and "--cores" into the nix args when the Option is Some (e.g., if
let Some(max_jobs) = self.build_jobs { ... } and if let Some(cores) =
self.build_cores { ... }). Also switch the invocation that currently uses
nix_command (which calls .output()) to the variant that inherits stderr
(nix_command_pipe_with_output or equivalent) when handling the
--print-build-logs path so build logs are streamed to the console instead of
being captured.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87845b1 and 84c7039.

📒 Files selected for processing (1)
  • src/cmd/build.rs

src/cmd/build.rs Outdated
Comment on lines +26 to +32
/// Number of concurrent Nix builds
#[arg(short = 'j', long, default_value = "1", env = "FLAKE_ITER_BUILD_JOBS")]
build_jobs: usize,

/// Number of cores to utilize per job
#[arg(short = 'c', long, default_value = "0", env = "FLAKE_ITER_BUILD_CORES")]
build_cores: usize,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /repo && find . -name "build.rs" -type f | head -20

Repository: DeterminateSystems/flake-iter

Length of output: 128


🏁 Script executed:

cd /repo && cat -n src/cmd/build.rs | head -50

Repository: DeterminateSystems/flake-iter

Length of output: 128


🏁 Script executed:

cd /repo && cat -n src/cmd/build.rs | sed -n '20,40p'

Repository: DeterminateSystems/flake-iter

Length of output: 128


🏁 Script executed:

cd /repo && cat -n src/cmd/build.rs | sed -n '95,125p'

Repository: DeterminateSystems/flake-iter

Length of output: 128


🌐 Web query:

Nix build --max-jobs -j flag semantics default value auto

💡 Result:

  • nix build -j N / --max-jobs N sets the maximum number of derivations (build jobs) Nix will build in parallel on the local machine. (releases.nixos.org)
  • -j auto means use the number of CPUs on the system. (releases.nixos.org)
  • If you don’t pass -j/--max-jobs, the value comes from the max-jobs configuration setting (same name in nix.conf, and every config setting can also be set via a corresponding CLI flag). (releases.nixos.org)
  • The max-jobs setting defaults to 1 if not configured. (releases.nixos.org)
  • -j 0 means do not build locally (useful if you want builds to happen only on remote builders). (releases.nixos.org)

Citations:


🌐 Web query:

Nix build --cores flag default semantics meaning zero

💡 Result:

nix build --cores N controls per-derivation build parallelism by setting the environment variable NIX_BUILD_CORES for builders (e.g., used like make -jN when a package enables parallel building). It is not the number of simultaneous derivations (that’s --max-jobs / -j). [1][2]

Default: --cores defaults to the Nix config setting cores (aka nix.settings.cores on NixOS); if that isn’t set, it falls back to 1. [1]

Meaning of 0: --cores 0 means “use all available CPU cores on the system” (i.e., NIX_BUILD_CORES becomes the machine’s core count). [1][2]

Note (newer Nix): as of Nix 2.31, when the config build-cores is set to 0, Nix auto-detects cores and passes the detected number via NIX_BUILD_CORES (instead of passing literal 0). [3]

Sources:
[1] Nix Reference Manual (nix-build / --cores) https://releases.nixos.org/nix/nix-2.24.0/manual/command-ref/nix-build.html
[2] Nix Reference Manual (“Tuning Cores and Jobs”) https://releases.nixos.org/nix/nix-2.28.1/manual/advanced-topics/cores-vs-jobs.html
[3] Nix 2.31 release notes (Determinate Systems manual) https://manual.determinate.systems/release-notes/rl-2.31.html


🏁 Script executed:

wc -l src/cmd/build.rs

Repository: DeterminateSystems/flake-iter

Length of output: 93


🏁 Script executed:

cat -n src/cmd/build.rs | sed -n '1,50p'

Repository: DeterminateSystems/flake-iter

Length of output: 2086


🏁 Script executed:

cat -n src/cmd/build.rs | sed -n '20,45p'

Repository: DeterminateSystems/flake-iter

Length of output: 1196


🏁 Script executed:

cat -n src/cmd/build.rs | sed -n '90,125p'

Repository: DeterminateSystems/flake-iter

Length of output: 1492


🏁 Script executed:

cat -n src/cmd/mod.rs | head -100

Repository: DeterminateSystems/flake-iter

Length of output: 3393


🏁 Script executed:

grep -n "fn nix_command" src/cmd/mod.rs

Repository: DeterminateSystems/flake-iter

Length of output: 393


🏁 Script executed:

grep -A 20 "fn nix_command\(" src/cmd/mod.rs

Repository: DeterminateSystems/flake-iter

Length of output: 96


🏁 Script executed:

sed -n '210,235p' src/cmd/mod.rs

Repository: DeterminateSystems/flake-iter

Length of output: 819


🏁 Script executed:

sed -n '223,250p' src/cmd/mod.rs

Repository: DeterminateSystems/flake-iter

Length of output: 955


Address the verbose build log visibility issue and clarify Nix flag behavior.

The --max-jobs 1 default matches Nix's own configuration default, but always passing it overrides a user's nix.conf if they've set a higher value. The --cores 0 flag has special semantics in Nix—it instructs the build system to use all available CPU cores—rather than being an "unset" behavior. However, both flags will suppress user configuration in nix.conf when not explicitly intended.

More critically, the --print-build-logs flag (line 112) won't display build logs to users because nix_command captures all output via .output() (line 212 in src/cmd/mod.rs). To fix this, use nix_command_pipe_with_output or similar that inherits stderr, so build logs appear on the console.

Consider making build_jobs and build_cores optional (Option<usize>) to preserve user settings from nix.conf when not explicitly specified:

♻️ Suggested refactor for optional flags
-    #[arg(short = 'j', long, default_value = "1", env = "FLAKE_ITER_BUILD_JOBS")]
-    build_jobs: usize,
+    #[arg(short = 'j', long, env = "FLAKE_ITER_BUILD_JOBS")]
+    build_jobs: Option<usize>,

-    #[arg(short = 'c', long, default_value = "0", env = "FLAKE_ITER_BUILD_CORES")]
-    build_cores: usize,
+    #[arg(short = 'c', long, env = "FLAKE_ITER_BUILD_CORES")]
+    build_cores: Option<usize>,

Then only append flags when explicitly set:

if let Some(cores) = self.build_cores {
    args.extend(["--cores", &cores.to_string()]);
}
if let Some(max_jobs) = self.build_jobs {
    args.extend(["--max-jobs", &max_jobs.to_string()]);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cmd/build.rs` around lines 26 - 32, The flags build_jobs and build_cores
currently have defaults that always pass --max-jobs/--cores and override
nix.conf; change their types to Option<usize> (update the struct fields
build_jobs and build_cores and their CLI arg definitions to be optional—remove
default_value) and only push "--max-jobs" and "--cores" into the nix args when
the Option is Some (e.g., if let Some(max_jobs) = self.build_jobs { ... } and if
let Some(cores) = self.build_cores { ... }). Also switch the invocation that
currently uses nix_command (which calls .output()) to the variant that inherits
stderr (nix_command_pipe_with_output or equivalent) when handling the
--print-build-logs path so build logs are streamed to the console instead of
being captured.

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.

🧹 Nitpick comments (1)
src/cmd/build.rs (1)

99-120: Consider pre-building the static arg prefix outside the loop.

build_cores, build_jobs, and verbose are loop-invariant; only drv changes. Recomputing the conditional prefix every iteration adds noise without benefit. Hoisting it out and cloning per iteration is cleaner. The args shadow on line 120 also goes away.

♻️ Proposed refactor
+            let mut base_args: Vec<String> =
+                vec![String::from("build"), String::from("--print-out-paths")];
+
+            if let Some(cores) = &self.build_cores {
+                base_args.push(String::from("--cores"));
+                base_args.push(cores.to_string());
+            }
+
+            if let Some(max_jobs) = &self.build_jobs {
+                base_args.push(String::from("--max-jobs"));
+                base_args.push(max_jobs.to_string());
+            }
+
+            if verbose {
+                base_args.push(String::from("--print-build-logs"));
+            }
+
             for (n, (drv, (outputs, output_paths))) in derivations.into_iter().enumerate() {
                 let drv = format!("{}^{}", drv.display(), outputs.join(","));

                 // ... cache-check block unchanged ...

-                let mut args: Vec<String> =
-                    vec![String::from("build"), String::from("--print-out-paths")];
-
-                if let Some(cores) = &self.build_cores {
-                    let cores = cores.to_string();
-                    args.push(String::from("--cores"));
-                    args.push(cores);
-                }
-
-                if let Some(max_jobs) = &self.build_jobs {
-                    let max_jobs = max_jobs.to_string();
-                    args.push(String::from("--max-jobs"));
-                    args.push(max_jobs);
-                }
-
-                if verbose {
-                    args.push(String::from("--print-build-logs"));
-                }
-
-                args.push(drv);
-
-                let args: Vec<&str> = args.iter().map(String::as_str).collect();
+                let mut args = base_args.clone();
+                args.push(drv);
+                let arg_strs: Vec<&str> = args.iter().map(String::as_str).collect();

                 info!("Building derivation {} of {num}", n + 1);
                 if verbose {
-                    nix_command_pipe_no_output(&args).wrap_err("failed to build derivation")?;
+                    nix_command_pipe_no_output(&arg_strs).wrap_err("failed to build derivation")?;
                 } else {
-                    nix_command(&args).wrap_err("failed to build derivation")?;
+                    nix_command(&arg_strs).wrap_err("failed to build derivation")?;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cmd/build.rs` around lines 99 - 120, Hoist construction of the
loop-invariant prefix for the build command out of the loop: create a base
Vec<String> (e.g. base_args) that contains "build", "--print-out-paths" and
conditionally the "--cores"/build_cores, "--max-jobs"/build_jobs and
"--print-build-logs" when verbose is true; then inside the loop clone base_args
to a per-iteration args vector, push the changing drv value, and only then map
to Vec<&str> if needed—this removes repeated work and the args shadowing around
the current args variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cmd/build.rs`:
- Around line 99-120: Hoist construction of the loop-invariant prefix for the
build command out of the loop: create a base Vec<String> (e.g. base_args) that
contains "build", "--print-out-paths" and conditionally the
"--cores"/build_cores, "--max-jobs"/build_jobs and "--print-build-logs" when
verbose is true; then inside the loop clone base_args to a per-iteration args
vector, push the changing drv value, and only then map to Vec<&str> if
needed—this removes repeated work and the args shadowing around the current args
variable.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84c7039 and abb12e7.

⛔ Files ignored due to path filters (1)
  • flake.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • src/cmd/build.rs

@RossComputerGuy RossComputerGuy merged commit 579b8d1 into main Feb 24, 2026
7 checks passed
@RossComputerGuy RossComputerGuy deleted the RossComputerGuy/build-tuning branch February 24, 2026 21:29
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.

2 participants