Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,18 @@ As an alternative, one can also specify remote builder as usual in
`/etc/nix/machines` or via the `nix.buildMachines` nixos options in
`configuration.nix`. This allows to parallelize builds across multiple machines.

## Setting arbitrary Nix options

Use `--option NAME VALUE` to pass any Nix configuration setting through to every
`nix`/`nix-env` invocation nixpkgs-review makes, the same way `nix --option`
works. It can be given multiple times:

```console
$ nixpkgs-review pr --option cores 4 --option sandbox false 37244
```

See `man nix.conf` for the full list of available settings.

## Cross compile, static, cuda

If you want to cross compile you can do that with the `--pkgs=` flag:
Expand Down
12 changes: 12 additions & 0 deletions nixpkgs_review/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,18 @@ def common_flags() -> list[CommonFlag]:
default=None,
help="Alternative package set to use for building, e.g. pkgsMusl, pkgsStatic, or pkgsCross.aarch64-multiplatform",
),
CommonFlag(
"--option",
dest="option",
type=str,
nargs=2,
metavar=("NAME", "VALUE"),
action="append",
default=[],
help="Set a Nix configuration option, e.g. --option cores 4 "
"(can be passed multiple times). Passed through as-is to every "
"nix/nix-env invocation; see `man nix.conf` for available settings",
),
]


Expand Down
30 changes: 26 additions & 4 deletions nixpkgs_review/nix.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class BuildConfig:
num_eval_workers: int = 1
max_memory_size: int = 4096
pkgs: str | None = None
options: tuple[tuple[str, str], ...] = ()


@dataclass
Expand Down Expand Up @@ -86,7 +87,18 @@ def serialize(self) -> dict:
REVIEW_SHELL: Final[str] = str(ROOT.joinpath("nix/review-shell.nix"))


def _nix_common_flags(allow: AllowedFeatures, nix_path: str) -> list[str]:
def _option_flags(options: tuple[tuple[str, str], ...]) -> list[str]:
flags: list[str] = []
for name, value in options:
flags += ["--option", name, value]
return flags


def _nix_common_flags(
allow: AllowedFeatures,
nix_path: str,
options: tuple[tuple[str, str], ...] = (),
) -> list[str]:
return [
"--extra-experimental-features",
"nix-command",
Expand All @@ -96,6 +108,7 @@ def _nix_common_flags(allow: AllowedFeatures, nix_path: str) -> list[str]:
"--allow-import-from-derivation"
if allow.ifd
else "--no-allow-import-from-derivation",
*_option_flags(options),
]


Expand All @@ -112,6 +125,7 @@ class ShellConfig:
run: str | None = None
sandbox: bool = False
pkgs: str | None = None
options: tuple[tuple[str, str], ...] = ()


def nix_shell(
Expand Down Expand Up @@ -145,6 +159,7 @@ def nix_shell(
*shell_file_args,
"--nix-path",
config.nix_path,
*_option_flags(config.options),
REVIEW_SHELL,
]
if config.run:
Expand Down Expand Up @@ -240,6 +255,7 @@ def tmpfs(path: Path | str, *, is_dir: bool = True) -> list[str]:
*shell_file_args,
"--nix-path",
config.nix_path,
*_option_flags(config.options),
REVIEW_SHELL,
]

Expand Down Expand Up @@ -327,7 +343,9 @@ def multi_system_eval(
"--max-memory-size",
str(build_config.max_memory_size),
"--no-instantiate",
*_nix_common_flags(build_config.allow, build_config.nix_path),
*_nix_common_flags(
build_config.allow, build_config.nix_path, build_config.options
),
"--expr",
f"(import {eval_script} {{ attr-json = {attr_json.name}; }})",
"--apply",
Expand Down Expand Up @@ -399,7 +417,9 @@ def nix_build(
"build",
"--file",
REVIEW_SHELL,
*_nix_common_flags(build_config.allow, build_config.nix_path),
*_nix_common_flags(
build_config.allow, build_config.nix_path, build_config.options
),
"--no-link",
"--keep-going",
]
Expand All @@ -424,6 +444,7 @@ def nix_build(
shell_file_args=shell_file_args,
allow=build_config.allow,
nix_path=build_config.nix_path,
options=build_config.options,
)

command += shell_file_args + shlex.split(args)
Expand Down Expand Up @@ -471,12 +492,13 @@ def _write_review_shell_drv(
shell_file_args: list[str],
allow: AllowedFeatures,
nix_path: str,
options: tuple[tuple[str, str], ...] = (),
) -> None:
review_drv_link: Path = cache_directory / "review-shell.drv"

cmd: list[str] = [
"nix-instantiate",
*_nix_common_flags(allow, nix_path),
*_nix_common_flags(allow, nix_path, options),
*shell_file_args,
REVIEW_SHELL,
]
Expand Down
12 changes: 10 additions & 2 deletions nixpkgs_review/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ def build_commit(
self.systems,
self.build_config.allow,
self.build_config.pkgs,
self.build_config.options,
)

if head_commit is None:
Expand All @@ -434,6 +435,7 @@ def build_commit(
self.systems,
self.build_config.allow,
self.build_config.pkgs,
self.build_config.options,
check_meta=True,
)

Expand Down Expand Up @@ -687,6 +689,7 @@ def start_review(
run=self.shell_options.run,
sandbox=self.shell_options.sandbox,
pkgs=self.build_config.pkgs,
options=self.build_config.options,
)
nix_shell(report.built_packages(), shell_config)

Expand Down Expand Up @@ -764,11 +767,12 @@ def parse_packages_xml(stdout: IO[str]) -> list[Package]:
return packages


def _list_packages_system(
def _list_packages_system( # noqa: PLR0913
system: System,
nix_path: str,
allow: AllowedFeatures,
pkgs: str | None = None,
options: tuple[tuple[str, str], ...] = (),
*,
check_meta: bool = False,
) -> list[Package]:
Expand All @@ -789,6 +793,7 @@ def _list_packages_system(
"--allow-import-from-derivation"
if allow.ifd
else "--no-allow-import-from-derivation",
*[token for name, value in options for token in ("--option", name, value)],
*(["-A", pkgs] if pkgs else []),
]
if check_meta:
Expand All @@ -804,11 +809,12 @@ def _list_packages_system(
return parse_packages_xml(f)


def list_packages(
def list_packages( # noqa: PLR0913
nix_path: str,
systems: set[System],
allow: AllowedFeatures,
pkgs: str | None = None,
options: tuple[tuple[str, str], ...] = (),
*,
check_meta: bool = False,
) -> dict[System, list[Package]]:
Expand All @@ -820,6 +826,7 @@ def list_packages(
allow=allow,
check_meta=check_meta,
pkgs=pkgs,
options=options,
)

return results
Expand Down Expand Up @@ -1018,6 +1025,7 @@ def build_config_from_args(
num_eval_workers=args.num_eval_workers,
max_memory_size=args.max_memory_size,
pkgs=args.pkgs,
options=tuple((name, value) for name, value in args.option),
)


Expand Down
109 changes: 109 additions & 0 deletions tests/test_option_flag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from __future__ import annotations

import subprocess
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch

from nixpkgs_review.allow import AllowedFeatures
from nixpkgs_review.cli import main, parse_args
from nixpkgs_review.nix import BuildConfig, _nix_common_flags, _option_flags
from nixpkgs_review.review import build_config_from_args

if TYPE_CHECKING:
import pytest

from .conftest import Helpers


def test_option_flags_expansion() -> None:
assert _option_flags((("cores", "4"), ("max-jobs", "2"))) == [
"--option",
"cores",
"4",
"--option",
"max-jobs",
"2",
]


def test_option_flags_empty() -> None:
assert _option_flags(()) == []


def test_option_flags_preserves_order_on_repeated_name() -> None:
# Nix applies --option left-to-right, last occurrence wins; we must not
# dedupe or reorder, so the same last-wins behavior falls out naturally.
assert _option_flags((("cores", "4"), ("cores", "8"))) == [
"--option",
"cores",
"4",
"--option",
"cores",
"8",
]


def test_nix_common_flags_includes_extra_options() -> None:
flags = _nix_common_flags(AllowedFeatures([]), "", (("cores", "4"),))
assert flags[-3:] == ["--option", "cores", "4"]


def test_nix_common_flags_no_options_by_default() -> None:
flags = _nix_common_flags(AllowedFeatures([]), "")
assert "cores" not in flags


def test_parse_args_option_flag() -> None:
args = parse_args(
"nixpkgs-review",
["rev", "HEAD", "--option", "cores", "4", "--option", "max-jobs", "2"],
)
assert args.option == [["cores", "4"], ["max-jobs", "2"]]


def test_parse_args_option_flag_defaults_empty() -> None:
args = parse_args("nixpkgs-review", ["rev", "HEAD"])
assert args.option == []


@patch("nixpkgs_review.review.current_system", return_value="x86_64-linux")
def test_build_config_from_args_carries_options(
mock_current_system: MagicMock,
) -> None:
args = parse_args(
"nixpkgs-review",
["rev", "HEAD", "--option", "cores", "4", "--option", "max-jobs", "2"],
)
build_config: BuildConfig = build_config_from_args(
args, AllowedFeatures([]), nix_path="", nixpkgs_config=Path("/dev/null")
)
assert build_config.options == (("cores", "4"), ("max-jobs", "2"))
assert build_config.local_system == mock_current_system.return_value


def test_option_flag_end_to_end(
helpers: Helpers, capsys: pytest.CaptureFixture[str]
) -> None:
with helpers.nixpkgs() as nixpkgs:
nixpkgs.path.joinpath("pkg1.txt").write_text("foo")
subprocess.run(["git", "add", "."], check=True)
subprocess.run(["git", "commit", "-m", "example-change"], check=True)
path = main(
"nixpkgs-review",
[
"rev",
"HEAD",
"--remote",
str(nixpkgs.remote),
"--run",
"exit 0",
"--build-graph",
"nix",
"--option",
"cores",
"1",
],
)
helpers.assert_built(path, "pkg1")
assert "--option cores 1" in capsys.readouterr().out
Loading