Commit e5db80ae authored by Robert Hensing's avatar Robert Hensing
Browse files

nixosModules.pkgsReadOnly: init

parent 693e2c32
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -57,6 +57,19 @@

      nixosModules = {
        notDetected = ./nixos/modules/installer/scan/not-detected.nix;

        /*
          Make the `nixpkgs.*` configuration read-only. Guarantees that `pkgs`
          is the way you initialize it.

          Example:

              {
                imports = [ nixpkgs.nixosModules.readOnlyPkgs ];
                nixpkgs.pkgs = nixpkgs.legacyPackages.x86_64-linux;
              }
        */
        readOnlyPkgs = ./nixos/modules/misc/nixpkgs/read-only.nix;
      };
    };
}
+74 −0
Original line number Diff line number Diff line
# A replacement for the traditional nixpkgs module, such that none of the modules
# can add their own configuration. This ensures that the Nixpkgs configuration is
# exactly as the user intends.
# This may also be used as a performance optimization when evaluating multiple
# configurations at once, with a shared `pkgs`.

# This is a separate module, because merging this logic into the nixpkgs module
# is too burdensome, considering that it is already burdened with legacy.
# Moving this logic into a module does not lose any composition benefits, because
# its purpose is not something that composes anyway.

{ lib, config, ... }:

let
  cfg = config.nixpkgs;
  inherit (lib) mkOption types;

in
{
  disabledModules = [
    ../nixpkgs.nix
  ];
  options = {
    nixpkgs = {
      pkgs = mkOption {
        type = lib.types.pkgs;
        description = lib.mdDoc ''The pkgs module argument.'';
      };
      config = mkOption {
        internal = true;
        type = types.unique { message = "nixpkgs.config is set to read-only"; } types.anything;
        description = lib.mdDoc ''
          The Nixpkgs `config` that `pkgs` was initialized with.
        '';
      };
      overlays = mkOption {
        internal = true;
        type = types.unique { message = "nixpkgs.overlays is set to read-only"; } types.anything;
        description = lib.mdDoc ''
          The Nixpkgs overlays that `pkgs` was initialized with.
        '';
      };
      hostPlatform = mkOption {
        internal = true;
        readOnly = true;
        description = lib.mdDoc ''
          The platform of the machine that is running the NixOS configuration.
        '';
      };
      buildPlatform = mkOption {
        internal = true;
        readOnly = true;
        description = lib.mdDoc ''
          The platform of the machine that built the NixOS configuration.
        '';
      };
      # NOTE: do not add the legacy options such as localSystem here. Let's keep
      #       this module simple and let module authors upgrade their code instead.
    };
  };
  config = {
    _module.args.pkgs =
      # find mistaken definitions
      builtins.seq cfg.config
      builtins.seq cfg.overlays
      builtins.seq cfg.hostPlatform
      builtins.seq cfg.buildPlatform
      cfg.pkgs;
    nixpkgs.config = cfg.pkgs.config;
    nixpkgs.overlays = cfg.pkgs.overlays;
    nixpkgs.hostPlatform = cfg.pkgs.stdenv.hostPlatform;
    nixpkgs.buildPlatform = cfg.pkgs.stdenv.buildPlatform;
  };
}
+59 −0
Original line number Diff line number Diff line
# [nixpkgs]$ nix-build -A nixosTests.nixpkgs --show-trace

{ evalMinimalConfig, pkgs, lib, stdenv }:
let
  eval = mod: evalMinimalConfig {
@@ -27,6 +29,47 @@ let
    let
      uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; };
    in map (ass: ass.message) (lib.filter (ass: !ass.assertion) uncheckedEval.config.assertions);

  readOnlyUndefined = evalMinimalConfig {
    imports = [ ./read-only.nix ];
  };

  readOnlyBad = evalMinimalConfig {
    imports = [ ./read-only.nix ];
    nixpkgs.pkgs = { };
  };

  readOnly = evalMinimalConfig {
    imports = [ ./read-only.nix ];
    nixpkgs.pkgs = pkgs;
  };

  readOnlyBadConfig = evalMinimalConfig {
    imports = [ ./read-only.nix ];
    nixpkgs.pkgs = pkgs;
    nixpkgs.config.allowUnfree = true; # do in pkgs instead!
  };

  readOnlyBadOverlays = evalMinimalConfig {
    imports = [ ./read-only.nix ];
    nixpkgs.pkgs = pkgs;
    nixpkgs.overlays = [ (_: _: {}) ]; # do in pkgs instead!
  };

  readOnlyBadHostPlatform = evalMinimalConfig {
    imports = [ ./read-only.nix ];
    nixpkgs.pkgs = pkgs;
    nixpkgs.hostPlatform = "foo-linux"; # do in pkgs instead!
  };

  readOnlyBadBuildPlatform = evalMinimalConfig {
    imports = [ ./read-only.nix ];
    nixpkgs.pkgs = pkgs;
    nixpkgs.buildPlatform = "foo-linux"; # do in pkgs instead!
  };

  throws = x: ! (builtins.tryEval x).success;

in
lib.recurseIntoAttrs {
  invokeNixpkgsSimple =
@@ -65,5 +108,21 @@ lib.recurseIntoAttrs {
        nixpkgs.pkgs = pkgs;
      } == [];


    # Tests for the read-only.nix module
    assert readOnly._module.args.pkgs.stdenv.hostPlatform.system == pkgs.stdenv.hostPlatform.system;
    assert throws readOnlyBad._module.args.pkgs.stdenv;
    assert throws readOnlyUndefined._module.args.pkgs.stdenv;
    assert throws readOnlyBadConfig._module.args.pkgs.stdenv;
    assert throws readOnlyBadOverlays._module.args.pkgs.stdenv;
    assert throws readOnlyBadHostPlatform._module.args.pkgs.stdenv;
    assert throws readOnlyBadBuildPlatform._module.args.pkgs.stdenv;
    # read-only.nix does not provide legacy options, for the sake of simplicity
    # If you're bothered by this, upgrade your configs to use the new *Platform
    # options.
    assert !readOnly.options.nixpkgs?system;
    assert !readOnly.options.nixpkgs?localSystem;
    assert !readOnly.options.nixpkgs?crossSystem;

    pkgs.emptyFile;
}