Skip to content

refactor(nixopts): use typed option keys for arg parsing/serialization#176

Merged
water-sucks merged 1 commit intonix-community:mainfrom
water-sucks:redo-nix-option-category-handling
Feb 14, 2026
Merged

refactor(nixopts): use typed option keys for arg parsing/serialization#176
water-sucks merged 1 commit intonix-community:mainfrom
water-sucks:redo-nix-option-category-handling

Conversation

@water-sucks
Copy link
Copy Markdown
Collaborator

@water-sucks water-sucks commented Feb 13, 2026

Command categories were a bit too imprecise and resulted in flags like --refresh getting passed to commands that do not support that flag. This would render some operations impossible with those flags passed.

Instead, each Nix option must now explicitly enumerate which commands it can be passed to; since there are only a handful of commands that are available, we can easily enumerate this ourselves and make this inherent to the option itself in the form of a type and corresponding interface.

This PR refactors the entire Nix passthrough options system to follow this pattern.

Closes #173.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Restructured Nix option configuration and command argument generation throughout the application
    • Consolidated option handling using a new interface-based system
    • Simplified internal helper function signatures and consolidated field dependencies
    • Updated configuration field naming for improved consistency across components
    • Streamlined argument building logic for Nix commands

@water-sucks
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

This PR refactors Nix option handling from a reflection-based, category-filtered system to a command-aware interface-based design. Options are now applied per-command rather than globally, each option declares supported commands, field naming is unified (Includes → Include, NixPathIncludes → Include), and direct Bind() calls replace helper function wiring.

Changes

Cohort / File(s) Summary
Command CLI implementations
cmd/apply/apply.go, cmd/install/install.go, cmd/option/..., cmd/repl/repl.go
Replaced nixopts.AddXOption() helper calls with direct field.Bind(&cmd) on option types; unified field references from Includes/NixPathIncludes to Include; removed cobra.Command parameters from helper functions where argument derivation now uses ArgsForCommand().
Core nixopts interface system
internal/cmd/nixopts/nixopts.go
Introduced public NixOption interface with Args(), Supports(cmd), and Bind() methods; added NixCommand type and constants (CmdBuild, CmdEval, CmdInstantiate, CmdStoreRealise, etc.); created 20+ concrete option types (Quiet, PrintBuildLogs, Include, Option, etc.) each implementing NixOption; added CollectFlags() and ArgsForOptionsSet() for reflective flag gathering and per-command argument generation.
Removed reflection-based converter
internal/cmd/nixopts/convert.go
Eliminated 172-line reflection-driven NixOptionsToArgsList and category-based argument construction; logic now handled by NixOption.Args() and ArgsForOptionsSet().
Option struct definitions
internal/cmd/opts/opts.go
Changed ApplyNixOpts, InstallNixOpts, ReplOpts, and OptionOpts to embed nixopts.* fields instead of explicit bool/int/[]string fields; added public Flags() and ArgsForCommand() methods for delegated flag collection and per-command argument building.
Configuration integration
internal/configuration/configuration.go, internal/configuration/flake.go, internal/configuration/legacy.go
Updated SystemBuildOptions.NixOpts type from any to nixopts.NixOptionsSet; replaced NixOptionsToArgsList calls with command-specific ArgsForCommand() invocations; removed category-filtered argument generation in favor of per-command filtering via option Supports() methods.
Test suite update
internal/cmd/nixopts/convert_test.go
Refactored tests to use new NixOption interface and command-based argument generation; replaced explicit field declarations with embedded nixopts.* fields; added test methods Flags() and ArgsForCommand(); updated test logic from NixOptionsToArgsList to per-command ArgsForCommand().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring: replacing category-based option handling with typed option keys that explicitly enumerate which commands support each flag.
Linked Issues check ✅ Passed The PR directly addresses issue #173 by implementing finer-grained option categorization through typed NixOption interface with Supports(cmd NixCommand) method, ensuring flags are only passed to appropriate commands.
Out of Scope Changes check ✅ Passed All changes align with the stated objective of refactoring Nix options to use typed keys instead of imprecise categories. No unrelated modifications detected across the altered files.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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
Copy Markdown

@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

🤖 Fix all issues with AI agents
In `@internal/cmd/nixopts/nixopts.go`:
- Around line 588-592: The deprecated flag message is currently just the
replacement flag name; update the Deprecated field to a human-readable sentence.
In SubstituteOnDestination.Bind change
cmd.Flags().Lookup("use-substitutes").Deprecated from
"substitute-on-destination" to something like "use --substitute-on-destination
instead" so cobra prints a full sentence; likewise find the other occurrence for
the no-registries flag and set its Deprecated to "use --no-registries instead".
Ensure you reference the exact flag names ("use-substitutes",
"substitute-on-destination", "no-registries") and modify the Deprecated string
assignments accordingly in the Bind methods.

In `@internal/cmd/opts/opts.go`:
- Around line 179-213: InstallNixOpts is missing the nixopts.Builders embed so
the install workflow cannot accept the --builders flag used by CmdLegacyBuild;
update the InstallNixOpts struct to embed nixopts.Builders (matching
ApplyNixOpts), then ensure Flags() and ArgsForCommand() continue to use
nixopts.CollectFlags(o) and nixopts.ArgsForOptionsSet(o.Flags(), cmd) so the new
Builders options are collected and turned into CLI args for InstallNixOpts.
🧹 Nitpick comments (5)
internal/cmd/nixopts/nixopts.go (3)

22-30: Remove commented-out code.

Dead code should be removed rather than left commented out. It's tracked in version control if needed later.


785-818: CollectFlags silently drops fields when two fields share the same type.

The doc comment warns about this, but a silent overwrite in a map[typeName] is easy to miss during future maintenance. Consider returning an error or panicking (consistent with the other precondition checks in this function) when a duplicate type name is detected, so misuse is caught immediately rather than producing subtly wrong argument lists.

Proposed defensive check
 	for i := 0; i < rv.NumField(); i++ {
 		f := rv.Field(i)
 
 		if flag, ok := f.Addr().Interface().(NixOption); ok {
 			name := f.Type().Name()
+			if _, exists := fieldMap[name]; exists {
+				panic("CollectFlags: duplicate NixOption type " + name)
+			}
 			fieldMap[name] = flag
 		}
 	}

524-530: Args() for slice/map-based options returns an empty slice instead of nil when unset.

Bool/int/string options return nil when unset, but Include, Option, UpdateInput, and OverrideInput return []string{}. This is functionally harmless (appending an empty slice is a no-op), but the inconsistency could cause surprises in tests using reflect.DeepEqual. Consider guarding with an early nil/length check:

Example for Include
 func (i Include) Args() []string {
+	if len(i) == 0 {
+		return nil
+	}
 	args := []string{}
 	for _, include := range i {
 		args = append(args, "--include", include)
 	}
 	return args
 }

Same pattern applies to Option.Args(), UpdateInput.Args(), and OverrideInput.Args().

cmd/apply/apply.go (1)

187-187: Completion closure captures Include before flags are parsed.

opts.NixOptions.Include is nil at command construction time. Since CompleteSpecialisationFlagFromConfig receives the slice by value, the completion function will always see a nil includes list regardless of what the user passes via --include. The same issue likely existed with the old Includes field, but worth noting if accurate completions depend on user-supplied includes.

If completions need runtime include values, consider passing a closure or pointer that defers the read until completion time.

internal/configuration/flake.go (1)

184-190: Remove dead code block that attempts to move --impure flag from realiseArgs to evalArgs.

Since Impure.Supports(CmdStoreRealise) returns false (per internal/cmd/nixopts/nixopts.go), the --impure flag is never added to realiseArgs in the first place. This makes slices.Index(realiseArgs, "--impure") always return -1, rendering the entire conditional block unreachable. The flag is already correctly included in evalArgs via ArgsForCommand(CmdEval) since Impure.Supports(CmdEval) returns true.

♻️ Proposed fix
-	// --impure must be part of the eval arguments, rather than the
-	// realisation arguments here, since that is where environment
-	// variables are accessed.
-	if i := slices.Index(realiseArgs, "--impure"); i >= 0 {
-		evalArgs = append(evalArgs, "--impure")
-		realiseArgs = slices.Delete(realiseArgs, i, i+1)
-	}

@water-sucks water-sucks force-pushed the redo-nix-option-category-handling branch from 9c0bd50 to 56cfe37 Compare February 13, 2026 20:34
@water-sucks water-sucks merged commit 9a43dd0 into nix-community:main Feb 14, 2026
2 checks passed
@water-sucks water-sucks deleted the redo-nix-option-category-handling branch February 14, 2026 00:37
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.

Certain experimental nix flags are passed to classic nix commands

1 participant