diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4f58d8880..9b9222b2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,11 +11,11 @@ jobs: steps: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v31 - - run: cd tests && nix fmt .. -- --fail-on-change + - run: nix build .#checks.x86_64-linux.formatting tests: needs: nixfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v31 - - run: nix run ./tests#run . \ No newline at end of file + - run: nix run .#run-tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63a147a70..93564142c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Link the profile in the table in README.md and in flake.nix. ## 3. Testing -Run `nix run ./tests#run .` to evaluate all hardware profiles. +Run `nix run .#run-tests` to evaluate all hardware profiles. Because profiles can only be tested with the appropriate hardware, quality assurance is up to *you*. diff --git a/flake.lock b/flake.lock index 8f2f2e35d..5999137c9 100644 --- a/flake.lock +++ b/flake.lock @@ -1,26 +1,6 @@ { "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1751290770, - "narHash": "sha256-u4s8yKAqTzPGY3vTcDyAIet11uXaNCM//93/0O0NlbA=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "0620a50e9a847851bf802c59a4202552ed79b821", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-25.05-small", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } + "root": {} }, "root": "root", "version": 7 diff --git a/flake.nix b/flake.nix index 3cd9e3551..2fb5494e4 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,35 @@ description = "nixos-hardware"; outputs = - { ... }: + { self, ... }: + let + # Import private inputs (for development) + privateInputs = + (import ./tests/flake-compat.nix { + src = ./tests; + }).defaultNix; + + systems = [ + "aarch64-linux" + "riscv64-linux" + "x86_64-linux" + ]; + + formatSystems = [ "aarch64-linux" "x86_64-linux" ]; + + # Helper to iterate over systems + eachSystem = + f: + privateInputs.nixos-unstable-small.lib.genAttrs systems ( + system: f privateInputs.nixos-unstable-small.legacyPackages.${system} system + ); + + eachSystemFormat = + f: + privateInputs.nixos-unstable-small.lib.genAttrs formatSystems ( + system: f privateInputs.nixos-unstable-small.legacyPackages.${system} system + ); + in { nixosModules = @@ -395,5 +423,40 @@ common-pc-laptop-ssd = import ./common/pc/ssd; common-pc-ssd = import ./common/pc/ssd; }; + + # Add formatter for `nix fmt` + formatter = eachSystemFormat ( + pkgs: _system: + (privateInputs.treefmt-nix.lib.evalModule pkgs ./tests/treefmt.nix).config.build.wrapper + ); + + # Add packages + packages = eachSystem ( + pkgs: _system: { + run-tests = pkgs.callPackage ./tests/run-tests.nix { + inherit self; + }; + } + ); + + # Add checks for `nix run .#run-tests` + checks = eachSystem ( + pkgs: system: + let + treefmtEval = privateInputs.treefmt-nix.lib.evalModule pkgs ./tests/treefmt.nix; + nixosTests = import ./tests/nixos-tests.nix { + inherit + self + privateInputs + system + pkgs + ; + }; + in + pkgs.lib.optionalAttrs (self.formatter ? system) { + formatting = treefmtEval.config.build.check self; + } + // nixosTests + ); }; } diff --git a/framework/13-inch/common/amd.nix b/framework/13-inch/common/amd.nix index ae35fffe9..4a89f8dae 100644 --- a/framework/13-inch/common/amd.nix +++ b/framework/13-inch/common/amd.nix @@ -6,19 +6,18 @@ ../../../common/gpu/amd ]; - boot.kernelParams = - [ - # There seems to be an issue with panel self-refresh (PSR) that - # causes hangs for users. - # - # https://community.frame.work/t/fedora-kde-becomes-suddenly-slow/58459 - # https://gitlab.freedesktop.org/drm/amd/-/issues/3647 - "amdgpu.dcdebugmask=0x10" - ] - # Workaround for SuspendThenHibernate: https://lore.kernel.org/linux-kernel/20231106162310.85711-1-mario.limonciello@amd.com/ - ++ lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "6.8") [ - "rtc_cmos.use_acpi_alarm=1" - ]; + boot.kernelParams = [ + # There seems to be an issue with panel self-refresh (PSR) that + # causes hangs for users. + # + # https://community.frame.work/t/fedora-kde-becomes-suddenly-slow/58459 + # https://gitlab.freedesktop.org/drm/amd/-/issues/3647 + "amdgpu.dcdebugmask=0x10" + ] + # Workaround for SuspendThenHibernate: https://lore.kernel.org/linux-kernel/20231106162310.85711-1-mario.limonciello@amd.com/ + ++ lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "6.8") [ + "rtc_cmos.use_acpi_alarm=1" + ]; # AMD has better battery life with PPD over TLP: # https://community.frame.work/t/responded-amd-7040-sleep-states/38101/13 diff --git a/framework/13-inch/common/intel.nix b/framework/13-inch/common/intel.nix index 6248e75f9..c46652cb1 100644 --- a/framework/13-inch/common/intel.nix +++ b/framework/13-inch/common/intel.nix @@ -9,16 +9,15 @@ ../../../common/cpu/intel ]; - boot.kernelParams = - [ - # For Power consumption - # https://community.frame.work/t/linux-battery-life-tuning/6665/156 - "nvme.noacpi=1" - ] - # Fixes a regression in s2idle, making it more power efficient than deep sleep - # Update 04/2024: It appears that s2idle-regression got fixed in newer kernel-versions (SebTM) - # (see: https://github.com/NixOS/nixos-hardware/pull/903#discussion_r1556096657) - ++ lib.lists.optional (lib.versionOlder config.boot.kernelPackages.kernel.version "6.8") "acpi_osi=\"!Windows 2020\""; + boot.kernelParams = [ + # For Power consumption + # https://community.frame.work/t/linux-battery-life-tuning/6665/156 + "nvme.noacpi=1" + ] + # Fixes a regression in s2idle, making it more power efficient than deep sleep + # Update 04/2024: It appears that s2idle-regression got fixed in newer kernel-versions (SebTM) + # (see: https://github.com/NixOS/nixos-hardware/pull/903#discussion_r1556096657) + ++ lib.lists.optional (lib.versionOlder config.boot.kernelPackages.kernel.version "6.8") "acpi_osi=\"!Windows 2020\""; # Requires at least 5.16 for working wi-fi and bluetooth. # https://community.frame.work/t/using-the-ax210-with-linux-on-the-framework-laptop/1844/89 diff --git a/framework/16-inch/common/amd.nix b/framework/16-inch/common/amd.nix index ae35fffe9..4a89f8dae 100644 --- a/framework/16-inch/common/amd.nix +++ b/framework/16-inch/common/amd.nix @@ -6,19 +6,18 @@ ../../../common/gpu/amd ]; - boot.kernelParams = - [ - # There seems to be an issue with panel self-refresh (PSR) that - # causes hangs for users. - # - # https://community.frame.work/t/fedora-kde-becomes-suddenly-slow/58459 - # https://gitlab.freedesktop.org/drm/amd/-/issues/3647 - "amdgpu.dcdebugmask=0x10" - ] - # Workaround for SuspendThenHibernate: https://lore.kernel.org/linux-kernel/20231106162310.85711-1-mario.limonciello@amd.com/ - ++ lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "6.8") [ - "rtc_cmos.use_acpi_alarm=1" - ]; + boot.kernelParams = [ + # There seems to be an issue with panel self-refresh (PSR) that + # causes hangs for users. + # + # https://community.frame.work/t/fedora-kde-becomes-suddenly-slow/58459 + # https://gitlab.freedesktop.org/drm/amd/-/issues/3647 + "amdgpu.dcdebugmask=0x10" + ] + # Workaround for SuspendThenHibernate: https://lore.kernel.org/linux-kernel/20231106162310.85711-1-mario.limonciello@amd.com/ + ++ lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "6.8") [ + "rtc_cmos.use_acpi_alarm=1" + ]; # AMD has better battery life with PPD over TLP: # https://community.frame.work/t/responded-amd-7040-sleep-states/38101/13 diff --git a/lenovo/legion/15ach6h/nvidia/default.nix b/lenovo/legion/15ach6h/nvidia/default.nix index 37dd11483..25bf31bf4 100644 --- a/lenovo/legion/15ach6h/nvidia/default.nix +++ b/lenovo/legion/15ach6h/nvidia/default.nix @@ -10,12 +10,11 @@ # remove all packages for amd igpu. I only removed amdgpu from # services.xserver.videoDrivers by overriding. This is because the specialization # of nix cannot implement such an operation as canceling an import. - hardware = - { - nvidia.prime.offload.enable = false; - } - // lib.optionalAttrs (options ? amdgpu.opencl.enable) { - # introduced in https://github.com/NixOS/nixpkgs/pull/319865 - amdgpu.opencl.enable = lib.mkDefault false; - }; + hardware = { + nvidia.prime.offload.enable = false; + } + // lib.optionalAttrs (options ? amdgpu.opencl.enable) { + # introduced in https://github.com/NixOS/nixpkgs/pull/319865 + amdgpu.opencl.enable = lib.mkDefault false; + }; } diff --git a/lenovo/legion/16ach6h/nvidia/default.nix b/lenovo/legion/16ach6h/nvidia/default.nix index 37dd11483..25bf31bf4 100644 --- a/lenovo/legion/16ach6h/nvidia/default.nix +++ b/lenovo/legion/16ach6h/nvidia/default.nix @@ -10,12 +10,11 @@ # remove all packages for amd igpu. I only removed amdgpu from # services.xserver.videoDrivers by overriding. This is because the specialization # of nix cannot implement such an operation as canceling an import. - hardware = - { - nvidia.prime.offload.enable = false; - } - // lib.optionalAttrs (options ? amdgpu.opencl.enable) { - # introduced in https://github.com/NixOS/nixpkgs/pull/319865 - amdgpu.opencl.enable = lib.mkDefault false; - }; + hardware = { + nvidia.prime.offload.enable = false; + } + // lib.optionalAttrs (options ? amdgpu.opencl.enable) { + # introduced in https://github.com/NixOS/nixpkgs/pull/319865 + amdgpu.opencl.enable = lib.mkDefault false; + }; } diff --git a/lenovo/legion/16achg6/nvidia/default.nix b/lenovo/legion/16achg6/nvidia/default.nix index d279b6a27..58b7ad230 100644 --- a/lenovo/legion/16achg6/nvidia/default.nix +++ b/lenovo/legion/16achg6/nvidia/default.nix @@ -3,12 +3,11 @@ { imports = [ ../hybrid ]; services.xserver.videoDrivers = [ "nvidia" ]; - hardware = - { - nvidia.prime.offload.enable = false; - } - // lib.optionalAttrs (options ? amdgpu.opencl.enable) { - # introduced in https://github.com/NixOS/nixpkgs/pull/319865 - amdgpu.opencl.enable = lib.mkDefault false; - }; + hardware = { + nvidia.prime.offload.enable = false; + } + // lib.optionalAttrs (options ? amdgpu.opencl.enable) { + # introduced in https://github.com/NixOS/nixpkgs/pull/319865 + amdgpu.opencl.enable = lib.mkDefault false; + }; } diff --git a/lenovo/thinkpad/p14s/amd/gen2/default.nix b/lenovo/thinkpad/p14s/amd/gen2/default.nix index b79e1cf37..a1cd731cd 100644 --- a/lenovo/thinkpad/p14s/amd/gen2/default.nix +++ b/lenovo/thinkpad/p14s/amd/gen2/default.nix @@ -14,9 +14,10 @@ # amdgpu.backlight=0 makes the backlight work # acpi_backlight=none allows the backlight save/load systemd service to work on older kernel versions - boot.kernelParams = - [ "amdgpu.backlight=0" ] - ++ lib.optional (lib.versionOlder config.boot.kernelPackages.kernel.version "6.1.6") "acpi_backlight=none"; + boot.kernelParams = [ + "amdgpu.backlight=0" + ] + ++ lib.optional (lib.versionOlder config.boot.kernelPackages.kernel.version "6.1.6") "acpi_backlight=none"; # For mainline support of rtw89 wireless networking boot.kernelPackages = lib.mkIf (lib.versionOlder pkgs.linux.version "5.16") pkgs.linuxPackages_latest; diff --git a/microsoft/surface/common/kernel/linux-package.nix b/microsoft/surface/common/kernel/linux-package.nix index a66aabc4e..f9f96184e 100644 --- a/microsoft/surface/common/kernel/linux-package.nix +++ b/microsoft/surface/common/kernel/linux-package.nix @@ -24,19 +24,18 @@ let let inherit (builtins) removeAttrs; - args' = - { - inherit - src - version - modDirVersion - kernelPatches - ; - } - // removeAttrs args [ - "url" - "sha256" - ]; + args' = { + inherit + src + version + modDirVersion + kernelPatches + ; + } + // removeAttrs args [ + "url" + "sha256" + ]; linuxPackage = buildLinux args'; linuxPackages' = recurseIntoAttrs (linuxPackagesFor linuxPackage); in diff --git a/nxp/common/bsp/imx-firmware.nix b/nxp/common/bsp/imx-firmware.nix index 46e905f80..c2d6a8ed2 100644 --- a/nxp/common/bsp/imx-firmware.nix +++ b/nxp/common/bsp/imx-firmware.nix @@ -47,21 +47,20 @@ pkgs.stdenv.mkDerivation rec { ${firmwareSeco} --auto-accept --force ''; - filesToInstall = - [ - "firmware-imx-${fwHdmiVersion}/firmware/hdmi/cadence/dpfw.bin" - "firmware-imx-${fwHdmiVersion}/firmware/hdmi/cadence/hdmi?xfw.bin" - ] - ++ pkgs.lib.optional (targetBoard == "imx8qm") ( - "imx-sc-firmware-${fwScVersion}/mx8qm-mek-scfw-tcm.bin" - + " " - + "imx-seco-${fwSecoVersion}/firmware/seco/mx8qmb0-ahab-container.img" - ) - ++ pkgs.lib.optional (targetBoard == "imx8qxp") ( - "imx-sc-firmware-${fwScVersion}/mx8qx-mek-scfw-tcm.bin" - + " " - + "imx-seco-${fwSecoVersion}/firmware/seco/mx8qxc0-ahab-container.img" - ); + filesToInstall = [ + "firmware-imx-${fwHdmiVersion}/firmware/hdmi/cadence/dpfw.bin" + "firmware-imx-${fwHdmiVersion}/firmware/hdmi/cadence/hdmi?xfw.bin" + ] + ++ pkgs.lib.optional (targetBoard == "imx8qm") ( + "imx-sc-firmware-${fwScVersion}/mx8qm-mek-scfw-tcm.bin" + + " " + + "imx-seco-${fwSecoVersion}/firmware/seco/mx8qmb0-ahab-container.img" + ) + ++ pkgs.lib.optional (targetBoard == "imx8qxp") ( + "imx-sc-firmware-${fwScVersion}/mx8qx-mek-scfw-tcm.bin" + + " " + + "imx-seco-${fwSecoVersion}/firmware/seco/mx8qxc0-ahab-container.img" + ); installPhase = '' mkdir -p $out diff --git a/pine64/star64/linux-5.15.nix b/pine64/star64/linux-5.15.nix index 28d9adbb9..39d8cedcc 100644 --- a/pine64/star64/linux-5.15.nix +++ b/pine64/star64/linux-5.15.nix @@ -45,7 +45,8 @@ let }; } { patch = ./irq-desc-to-data.patch; } - ] ++ kernelPatches; + ] + ++ kernelPatches; structuredExtraConfig = with lib.kernel; { # A ton of stuff just does not build. We disable it all. diff --git a/tests/flake-compat.nix b/tests/flake-compat.nix new file mode 100644 index 000000000..ddc1cd0e3 --- /dev/null +++ b/tests/flake-compat.nix @@ -0,0 +1,299 @@ +# Compatibility function to allow flakes to be used by +# non-flake-enabled Nix versions. Given a source tree containing a +# 'flake.nix' and 'flake.lock' file, it fetches the flake inputs and +# calls the flake's 'outputs' function. It then returns an attrset +# containing 'defaultNix' (to be used in 'default.nix'), 'shellNix' +# (to be used in 'shell.nix'). + +{ + src, + system ? builtins.currentSystem or "unknown-system", +}: + +let + + lockFilePath = src + "/flake.lock"; + + lockFile = builtins.fromJSON (builtins.readFile lockFilePath); + + fetchTree = + builtins.fetchTree or ( + info: + if info.type == "github" then + { + outPath = fetchTarball ( + { + url = "https://api.${info.host or "github.com"}/repos/${info.owner}/${info.repo}/tarball/${info.rev}"; + } + // (if info ? narHash then { sha256 = info.narHash; } else { }) + ); + rev = info.rev; + shortRev = builtins.substring 0 7 info.rev; + lastModified = info.lastModified; + lastModifiedDate = formatSecondsSinceEpoch info.lastModified; + narHash = info.narHash; + } + else if info.type == "git" then + { + outPath = builtins.fetchGit ( + { + url = info.url; + } + // (if info ? rev then { inherit (info) rev; } else { }) + // (if info ? ref then { inherit (info) ref; } else { }) + // (if info ? submodules then { inherit (info) submodules; } else { }) + ); + lastModified = info.lastModified; + lastModifiedDate = formatSecondsSinceEpoch info.lastModified; + narHash = info.narHash; + revCount = info.revCount or 0; + } + // ( + if info ? rev then + { + rev = info.rev; + shortRev = builtins.substring 0 7 info.rev; + } + else + { } + ) + else if info.type == "path" then + { + outPath = builtins.path { + path = info.path; + sha256 = info.narHash; + }; + narHash = info.narHash; + } + else if info.type == "tarball" then + { + outPath = fetchTarball ( + { inherit (info) url; } // (if info ? narHash then { sha256 = info.narHash; } else { }) + ); + } + else if info.type == "gitlab" then + { + inherit (info) rev narHash lastModified; + outPath = fetchTarball ( + { + url = "https://${info.host or "gitlab.com"}/api/v4/projects/${info.owner}%2F${info.repo}/repository/archive.tar.gz?sha=${info.rev}"; + } + // (if info ? narHash then { sha256 = info.narHash; } else { }) + ); + shortRev = builtins.substring 0 7 info.rev; + } + else if info.type == "sourcehut" then + { + inherit (info) rev narHash lastModified; + outPath = fetchTarball ( + { + url = "https://${info.host or "git.sr.ht"}/${info.owner}/${info.repo}/archive/${info.rev}.tar.gz"; + } + // (if info ? narHash then { sha256 = info.narHash; } else { }) + ); + shortRev = builtins.substring 0 7 info.rev; + } + else + # FIXME: add Mercurial, tarball inputs. + throw "flake input has unsupported input type '${info.type}'" + ); + + callFlake4 = + flakeSrc: locks: + let + flake = import (flakeSrc + "/flake.nix"); + + inputs = builtins.mapAttrs ( + _n: v: + if v.flake or true then + callFlake4 (fetchTree (v.locked // v.info)) v.inputs + else + fetchTree (v.locked // v.info) + ) locks; + + outputs = flakeSrc // (flake.outputs (inputs // { self = outputs; })); + in + assert flake.edition == 201909; + outputs; + + callLocklessFlake = + flakeSrc: + let + flake = import (flakeSrc + "/flake.nix"); + outputs = flakeSrc // (flake.outputs ({ self = outputs; })); + in + outputs; + + rootSrc = + let + # Try to clean the source tree by using fetchGit, if this source + # tree is a valid git repository. + tryFetchGit = + src: + if isGit && !isShallow then + let + res = builtins.fetchGit src; + in + if res.rev == "0000000000000000000000000000000000000000" then + removeAttrs res [ + "rev" + "shortRev" + ] + else + res + else + { + outPath = + # Massage `src` into a store path. + if builtins.isPath src then + if + dirOf (toString src) == builtins.storeDir + # `builtins.storePath` is not available in pure-eval mode. + && builtins ? currentSystem + then + # If it's already a store path, don't copy it again. + builtins.storePath src + else + "${src}" + else + src; + }; + # NB git worktrees have a file for .git, so we don't check the type of .git + isGit = builtins.pathExists (src + "/.git"); + isShallow = builtins.pathExists (src + "/.git/shallow"); + + in + { + lastModified = 0; + lastModifiedDate = formatSecondsSinceEpoch 0; + } + // (if src ? outPath then src else tryFetchGit src); + + # Format number of seconds in the Unix epoch as %Y%m%d%H%M%S. + formatSecondsSinceEpoch = + t: + let + rem = x: y: x - x / y * y; + days = t / 86400; + secondsInDay = rem t 86400; + hours = secondsInDay / 3600; + minutes = (rem secondsInDay 3600) / 60; + seconds = rem t 60; + + # Courtesy of https://stackoverflow.com/a/32158604. + z = days + 719468; + era = (if z >= 0 then z else z - 146096) / 146097; + doe = z - era * 146097; + yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + y = yoe + era * 400; + doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + mp = (5 * doy + 2) / 153; + d = doy - (153 * mp + 2) / 5 + 1; + m = mp + (if mp < 10 then 3 else -9); + y' = y + (if m <= 2 then 1 else 0); + + pad = s: if builtins.stringLength s < 2 then "0" + s else s; + in + "${toString y'}${pad (toString m)}${pad (toString d)}${pad (toString hours)}${pad (toString minutes)}${pad (toString seconds)}"; + + allNodes = builtins.mapAttrs ( + key: node: + let + sourceInfo = + if key == lockFile.root then + rootSrc + else + fetchTree (node.info or { } // removeAttrs node.locked [ "dir" ]); + + subdir = if key == lockFile.root then "" else node.locked.dir or ""; + + outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir); + + flake = import (outPath + "/flake.nix"); + + inputs = builtins.mapAttrs (_inputName: inputSpec: allNodes.${resolveInput inputSpec}) ( + node.inputs or { } + ); + + # Resolve a input spec into a node name. An input spec is + # either a node name, or a 'follows' path from the root + # node. + resolveInput = + inputSpec: if builtins.isList inputSpec then getInputByPath lockFile.root inputSpec else inputSpec; + + # Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the + # root node, returning the final node. + getInputByPath = + nodeName: path: + if path == [ ] then + nodeName + else + getInputByPath + # Since this could be a 'follows' input, call resolveInput. + (resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path}) + (builtins.tail path); + + outputs = flake.outputs (inputs // { self = result; }); + + result = + outputs + # We add the sourceInfo attribute for its metadata, as they are + # relevant metadata for the flake. However, the outPath of the + # sourceInfo does not necessarily match the outPath of the flake, + # as the flake may be in a subdirectory of a source. + # This is shadowed in the next // + // sourceInfo + // { + # This shadows the sourceInfo.outPath + inherit outPath; + + inherit inputs; + inherit outputs; + inherit sourceInfo; + _type = "flake"; + }; + + in + if node.flake or true then + assert builtins.isFunction flake.outputs; + result + else + sourceInfo + ) lockFile.nodes; + + result = + if !(builtins.pathExists lockFilePath) then + callLocklessFlake rootSrc + else if lockFile.version == 4 then + callFlake4 rootSrc (lockFile.inputs) + else if lockFile.version >= 5 && lockFile.version <= 7 then + allNodes.${lockFile.root} + else + throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}"; + +in +rec { + outputs = result; + + defaultNix = + builtins.removeAttrs result [ "__functor" ] + // ( + if result ? defaultPackage.${system} then { default = result.defaultPackage.${system}; } else { } + ) + // ( + if result ? packages.${system}.default then + { default = result.packages.${system}.default; } + else + { } + ); + + shellNix = + defaultNix + // (if result ? devShell.${system} then { default = result.devShell.${system}; } else { }) + // ( + if result ? devShells.${system}.default then + { default = result.devShells.${system}.default; } + else + { } + ); +} diff --git a/tests/flake.lock b/tests/flake.lock index 4363c6d14..da5ee4173 100644 --- a/tests/flake.lock +++ b/tests/flake.lock @@ -1,78 +1,39 @@ { "nodes": { - "flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "nixos-unstable-small" - ] - }, - "locked": { - "lastModified": 1743550720, - "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "c621e8422220273271f52058f618c94e405bb0f5", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "nixos-hardware": { + "nixos-stable": { "locked": { - "lastModified": 1747083103, - "narHash": "sha256-dMx20S2molwqJxbmMB4pGjNfgp5H1IOHNa1Eby6xL+0=", + "lastModified": 1755274400, + "narHash": "sha256-rTInmnp/xYrfcMZyFMH3kc8oko5zYfxsowaLv1LVobY=", "owner": "NixOS", - "repo": "nixos-hardware", - "rev": "d1d68fe8b00248caaa5b3bbe4984c12b47e0867d", + "repo": "nixpkgs", + "rev": "ad7196ae55c295f53a7d1ec39e4a06d922f3b899", "type": "github" }, "original": { "owner": "NixOS", - "repo": "nixos-hardware", - "type": "github" - } - }, - "nixos-stable": { - "locked": { - "lastModified": 1751211869, - "narHash": "sha256-1Cu92i1KSPbhPCKxoiVG5qnoRiKTgR5CcGSRyLpOd7Y=", - "ref": "nixos-25.05", - "rev": "b43c397f6c213918d6cfe6e3550abfe79b5d1c51", - "shallow": true, - "type": "git", - "url": "https://github.com/NixOS/nixpkgs" - }, - "original": { "ref": "nixos-25.05", - "shallow": true, - "type": "git", - "url": "https://github.com/NixOS/nixpkgs" + "repo": "nixpkgs", + "type": "github" } }, "nixos-unstable-small": { "locked": { - "lastModified": 1747040834, - "narHash": "sha256-iKQKoNlZmxQq+O2WfImm/jn97g5GZBVW5EZEoCTXZ3I=", - "ref": "nixos-unstable-small", - "rev": "e4f52f3ea82ddd3754b467e3fdc0d709685c9a05", - "shallow": true, - "type": "git", - "url": "https://github.com/NixOS/nixpkgs" + "lastModified": 1755375481, + "narHash": "sha256-43PgCQFgFD1nM/7dncytV0c5heNHe/gXrEud18ZWcZU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "35f1742e4f1470817ff8203185e2ce0359947f12", + "type": "github" }, "original": { + "owner": "NixOS", "ref": "nixos-unstable-small", - "shallow": true, - "type": "git", - "url": "https://github.com/NixOS/nixpkgs" + "repo": "nixpkgs", + "type": "github" } }, "root": { "inputs": { - "flake-parts": "flake-parts", - "nixos-hardware": "nixos-hardware", "nixos-stable": "nixos-stable", "nixos-unstable-small": "nixos-unstable-small", "treefmt-nix": "treefmt-nix" diff --git a/tests/flake.nix b/tests/flake.nix index 443bfbd7a..242c07bec 100644 --- a/tests/flake.nix +++ b/tests/flake.nix @@ -1,127 +1,12 @@ { - description = "Test flake for nixos-hardware"; + description = "Private dev inputs for nixos-hardware"; inputs = { - nixos-unstable-small.url = "git+https://github.com/NixOS/nixpkgs?shallow=1&ref=nixos-unstable-small"; - nixos-stable.url = "git+https://github.com/NixOS/nixpkgs?shallow=1&ref=nixos-25.05"; - # override in the test - nixos-hardware.url = "github:NixOS/nixos-hardware"; - flake-parts.url = "github:hercules-ci/flake-parts"; - flake-parts.inputs.nixpkgs-lib.follows = "nixos-unstable-small"; + nixos-unstable-small.url = "github:NixOS/nixpkgs/nixos-unstable-small"; + nixos-stable.url = "github:NixOS/nixpkgs/nixos-25.05"; treefmt-nix.url = "github:numtide/treefmt-nix"; treefmt-nix.inputs.nixpkgs.follows = "nixos-unstable-small"; }; - outputs = - inputs@{ flake-parts, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { - imports = [ - inputs.treefmt-nix.flakeModule - ]; - systems = [ - "aarch64-linux" - "x86_64-linux" - "riscv64-linux" - ]; - perSystem = - { - system, - lib, - pkgs, - ... - }: - let - blackList = [ - # does import-from-derivation - "toshiba-swanky" - # uses custom nixpkgs config - "raspberry-pi-2" - - # deprecated profiles - "framework" - "asus-zephyrus-ga402x" - "lenovo-yoga-7-14ARH7" - ]; - - # There are more, but for those we need to force it. - # In future we should probably already define it in our module. - aarch64Systems = [ - "raspberry-pi-3" - "raspberry-pi-4" - "raspberry-pi-5" - ]; - - matchArch = - moduleName: - if builtins.elem moduleName aarch64Systems then - pkgs.hostPlatform.system == "aarch64-linux" - else - # TODO also add riscv64 - pkgs.hostPlatform.system == "x86_64-linux"; - - modules = lib.filterAttrs ( - name: _: !(builtins.elem name blackList || lib.hasPrefix "common-" name) && matchArch name - ) inputs.nixos-hardware.nixosModules; - buildProfile = import ./build-profile.nix; - - unfreeNixpkgs = - importPath: - import importPath { - config = { - allowBroken = true; - allowUnfree = true; - nvidia.acceptLicense = true; - }; - overlays = [ ]; - inherit system; - }; - nixpkgsUnstable = unfreeNixpkgs inputs.nixos-unstable-small; - nixpkgsStable = unfreeNixpkgs inputs.nixos-stable; - - checksForNixpkgs = - channel: nixpkgs: - lib.mapAttrs' ( - name: module: - lib.nameValuePair "${channel}-${name}" (buildProfile { - pkgs = nixpkgs; - profile = module; - }) - ) modules; - in - { - _module.args.pkgs = nixpkgsUnstable; - - treefmt = { - flakeCheck = pkgs.hostPlatform.system != "riscv64-linux"; - projectRootFile = "COPYING"; - programs = { - deadnix = { - enable = true; - no-lambda-pattern-names = true; - }; - nixfmt = { - enable = true; - package = pkgs.nixfmt-rfc-style; - }; - }; - settings = { - on-unmatched = "info"; - }; - }; - - checks = - checksForNixpkgs "nixos-unstable" nixpkgsUnstable - // checksForNixpkgs "nixos-stable" nixpkgsStable; - packages.run = pkgs.writeShellScriptBin "run.py" '' - #!${pkgs.bash}/bin/bash - export PATH=${ - lib.makeBinPath [ - pkgs.nix-eval-jobs - pkgs.nix-eval-jobs.nix - ] - } - exec ${pkgs.python3.interpreter} ${./.}/run.py --nixos-hardware "$@" - ''; - }; - }; + outputs = inputs: inputs; } diff --git a/tests/nixos-tests.nix b/tests/nixos-tests.nix new file mode 100644 index 000000000..0fcf6ed92 --- /dev/null +++ b/tests/nixos-tests.nix @@ -0,0 +1,70 @@ +{ + self, + privateInputs, + system, + pkgs, +}: +let + # Hardware profile checks + blackList = [ + # does import-from-derivation + "toshiba-swanky" + # uses custom nixpkgs config + "raspberry-pi-2" + # deprecated profiles + "framework" + "asus-zephyrus-ga402x" + "lenovo-yoga-7-14ARH7" + ]; + + aarch64Systems = [ + "raspberry-pi-3" + "raspberry-pi-4" + "raspberry-pi-5" + ]; + + matchArch = + moduleName: + if builtins.elem moduleName aarch64Systems then + system == "aarch64-linux" + else + # TODO also add riscv64 + system == "x86_64-linux"; + + modules = pkgs.lib.filterAttrs ( + name: _: !(builtins.elem name blackList || pkgs.lib.hasPrefix "common-" name) && matchArch name + ) self.nixosModules; + + buildProfile = import ./build-profile.nix; + + unfreeNixpkgs = { + config = { + allowBroken = true; + allowUnfree = true; + nvidia.acceptLicense = true; + }; + overlays = [ ]; + inherit system; + }; + + nixpkgsUnstable = import privateInputs.nixos-unstable-small unfreeNixpkgs; + nixpkgsStable = import privateInputs.nixos-stable unfreeNixpkgs; + + # Build checks for both unstable and stable + unstableChecks = pkgs.lib.mapAttrs' ( + name: module: + pkgs.lib.nameValuePair "unstable-${name}" (buildProfile { + pkgs = nixpkgsUnstable; + profile = module; + }) + ) modules; + + stableChecks = pkgs.lib.mapAttrs' ( + name: module: + pkgs.lib.nameValuePair "stable-${name}" (buildProfile { + pkgs = nixpkgsStable; + profile = module; + }) + ) modules; +in +unstableChecks // stableChecks diff --git a/tests/run-tests.nix b/tests/run-tests.nix new file mode 100644 index 000000000..a2606bbc8 --- /dev/null +++ b/tests/run-tests.nix @@ -0,0 +1,19 @@ +{ + lib, + writeShellScriptBin, + bash, + python3, + nix-eval-jobs, + self, +}: + +writeShellScriptBin "run-tests" '' + #!${bash}/bin/bash + export PATH=${ + lib.makeBinPath [ + nix-eval-jobs + nix-eval-jobs.nix + ] + } + exec ${python3.interpreter} ${self}/tests/run.py +'' diff --git a/tests/run.py b/tests/run.py index efe76b525..862147567 100755 --- a/tests/run.py +++ b/tests/run.py @@ -3,6 +3,7 @@ import argparse import json import multiprocessing +import os import shlex import subprocess import sys @@ -16,6 +17,37 @@ RED = "\033[91m" RESET = "\033[0m" +def is_github_actions() -> bool: + """Check if running in GitHub Actions environment.""" + return os.getenv("GITHUB_ACTIONS") == "true" + +def github_error(message: str, title: str = "") -> None: + """Output GitHub Actions error annotation.""" + if title: + print(f"::error title={title}::{message}") + else: + print(f"::error::{message}") + +def github_warning(message: str, title: str = "") -> None: + """Output GitHub Actions warning annotation.""" + if title: + print(f"::warning title={title}::{message}") + else: + print(f"::warning::{message}") + +def format_nix_error(error_text: str) -> str: + """Format nix evaluation error for better readability.""" + lines = error_text.strip().split('\n') + # Try to extract the most relevant error line + for line in lines: + if 'error:' in line.lower(): + return line.strip() + # If no specific error line found, return first non-empty line + for line in lines: + if line.strip(): + return line.strip() + return error_text.strip() + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Run hardware tests") parser.add_argument( @@ -30,22 +62,15 @@ def parse_args() -> argparse.Namespace: action="store_true", help="Print evaluation commands executed", ) - parser.add_argument( - "--nixos-hardware", - help="Print evaluation commands executed", - ) return parser.parse_args() -def run_eval_test(nixos_hardware: str, gcroot_dir: Path, jobs: int) -> list[str]: +def run_eval_test(gcroot_dir: Path, jobs: int) -> list[str]: failed_profiles = [] cmd = [ "nix-eval-jobs", "--extra-experimental-features", "flakes", - "--override-input", - "nixos-hardware", - nixos_hardware, "--gc-roots-dir", str(gcroot_dir), "--max-memory-size", @@ -53,7 +78,7 @@ def run_eval_test(nixos_hardware: str, gcroot_dir: Path, jobs: int) -> list[str] "--workers", str(jobs), "--flake", - str(TEST_ROOT) + "#checks", + str(ROOT) + "#checks", "--force-recurse", ] print(" ".join(map(shlex.quote, cmd))) @@ -70,8 +95,19 @@ def run_eval_test(nixos_hardware: str, gcroot_dir: Path, jobs: int) -> list[str] attr = data.get("attr") if "error" in data: failed_profiles.append(attr) + error_msg = data['error'] + formatted_error = format_nix_error(error_msg) + + # Output for terminal print(f"{RED}FAIL {attr}:{RESET}", file=sys.stderr) - print(f"{RED}{data['error']}{RESET}", file=sys.stderr) + print(f"{RED}{error_msg}{RESET}", file=sys.stderr) + + # Output for GitHub Actions + if is_github_actions(): + github_error( + formatted_error, + title=f"Hardware profile evaluation failed: {attr}" + ) else: print(f"{GREEN}OK {attr}{RESET}") return failed_profiles @@ -84,12 +120,21 @@ def main() -> None: with TemporaryDirectory() as tmpdir: gcroot_dir = Path(tmpdir) / "gcroot" - failed_profiles = run_eval_test(args.nixos_hardware, gcroot_dir, args.jobs) + failed_profiles = run_eval_test(gcroot_dir, args.jobs) if len(failed_profiles) > 0: - print(f"\n{RED}The following {len(failed_profiles)} test(s) failed:{RESET}") + failure_msg = f"The following {len(failed_profiles)} test(s) failed:" + print(f"\n{RED}{failure_msg}{RESET}") for profile in failed_profiles: print(f" '{profile}'") + + # GitHub Actions summary + if is_github_actions(): + github_error( + f"{failure_msg} {', '.join(failed_profiles)}", + title="Hardware Profile Tests Failed" + ) + sys.exit(1) diff --git a/tests/treefmt.nix b/tests/treefmt.nix new file mode 100644 index 000000000..46df7f2b0 --- /dev/null +++ b/tests/treefmt.nix @@ -0,0 +1,11 @@ +{ + projectRootFile = "flake.nix"; + programs = { + deadnix = { + enable = true; + no-lambda-pattern-names = true; + }; + nixfmt.enable = true; + }; + settings.on-unmatched = "info"; +}