Magically provide pkgs.* from checks.* in devShell?

Hi, I would like to provide the pkgs.* used in checks.* in a devShell. I can add them to the buildInputs list of devShell as shown in the example flake below. But, the duplication doesn’t look nice. Is there a more elegant solution?

{
  inputs.flake-utils.url = "github:numtide/flake-utils";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in rec {
        checks = with pkgs.python3Packages; {
          hello = pkgs.runCommand "hello" { } ''
            ${pkgs.hello}/bin/hello
            mkdir $out
          '';
        };

        devShell = pkgs.mkShell {
          # XXX: I don't want to duplicate all pkgs.* used in checks.* here:
          buildInputs = [ pkgs.hello ];
        };
      });
}

Thanks,
Daniel

This is what the self argument of the flake is for: referencing other outputs in the same flake.

I pretty always define an overlay and use it to get the pkgs. I.e. Personally I’ve found that things evolve into a pattern roughly like this on most non-trivial things I’ve tried:

# flake.nix
{
  inputs.flake-utils.url = "github:numtide/flake-utils";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { self, nixpkgs, flake-utils }:

    let
      inherit (nixpkgs) lib;

      # Add the flake's overlay to each of the pkgs
      eachDefaultEnvironment = f: flake-utils.lib.eachDefaultSystem
        (
          system:
          f {
            inherit system;
            pkgs = (import nixpkgs { inherit system; config.allowUnfree = true; }).extend self.overlay;
          }
        );

    in
    eachDefaultEnvironment
      ({ pkgs, system }: {

        devShell = import ./shell.nix ({ inherit pkgs; } // self.packages."${system}");

        packages = rec {
          py-hello = pkgs.python3Packages.py-hello;
        };

        defaultPackage = (self.packages."${system}").py-hello;

      }) // {

        overlay = import ./overlay.nix self.inputs;
        checks = self.packages;

      };
}
# overlay.nix
inputs: final: prev:

{
  python3 = prev.python3.override {
    packageOverrides = pythonFinal: pythonSelf: {
      py-hello = final.runCommand "py-hello" { } ''
        ${final.hello}/bin/hello
        # presumably you need python for something here...
        mkdir $out
      '';
    };
  };
}
# shell.nix
{ pkgs
, py-hello
, ...
}:

pkgs.mkShell {
  buildInputs = with pkgs; [ py-hello ];
}

IMHO, overlay should almost be mandatory and then package, devShell, check etc as optional extras. (It’s quite easy to tie yourself into knots with self if you’re not careful)

flake-utils.lib.simpleFlake is another way to go about this (See https://github.com/numtide/flake-utils#example-1)