From 7e8aabfaff17cb7152020f89ea068f5f8a6c94cb Mon Sep 17 00:00:00 2001 From: Jake Hamilton Date: Wed, 7 Sep 2022 17:06:33 -0700 Subject: [PATCH] chore: initial commit --- .gitignore | 1 + README.md | 1248 ++++++++++++++++++++++++++++++++ default.nix | 10 + flake.lock | 137 ++++ flake.nix | 42 ++ lib/attrs/default.nix | 36 + lib/default.nix | 109 +++ lib/flake/default.nix | 130 ++++ lib/fp/default.nix | 36 + lib/fs/default.nix | 153 ++++ lib/internal/default.nix | 48 ++ lib/module/default.nix | 48 ++ lib/overlay/default.nix | 43 ++ lib/package/default.nix | 51 ++ lib/path/default.nix | 77 ++ lib/shell/default.nix | 51 ++ lib/system/default.nix | 201 +++++ lib/system/virtual-systems.nix | 34 + lib/template/default.nix | 50 ++ 19 files changed, 2505 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 default.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 lib/attrs/default.nix create mode 100644 lib/default.nix create mode 100644 lib/flake/default.nix create mode 100644 lib/fp/default.nix create mode 100644 lib/fs/default.nix create mode 100644 lib/internal/default.nix create mode 100644 lib/module/default.nix create mode 100644 lib/overlay/default.nix create mode 100644 lib/package/default.nix create mode 100644 lib/path/default.nix create mode 100644 lib/shell/default.nix create mode 100644 lib/system/default.nix create mode 100644 lib/system/virtual-systems.nix create mode 100644 lib/template/default.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9a5aec --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp diff --git a/README.md b/README.md new file mode 100644 index 0000000..378b542 --- /dev/null +++ b/README.md @@ -0,0 +1,1248 @@ +# Snowfall Lib + + + Nix Flakes Ready + + + Linux Ready + + + macOS Ready + + + Generators Ready + + +

+ +   +

+ +> Unified configuration for systems, packages, modules, shells, templates, and more with Nix Flakes. +> +> _Snowfall Lib is built on top of [flake-utils-plus](https://github.com/gytis-ivaskevicius/flake-utils-plus)._ + +## Get Started + +1. Create a new directory to work in. + +```bash +mkdir config + +cd config +``` + +2. Create a new flake with one of the templates from [@snowfallorg/templates](https://github.com/snowfallorg/templates). + +| Name | Description | +|-----------|---------------------------------------------------| +| `system` | A NixOS system and modules ready to modify. | +| `package` | A Nix Flake that exports packages and an overlay. | +| `module` | A Nix Flake that exports NixOS modules. | +| `lib` | A Nix Flake that exports a custom `lib` | + +```bash +# For example, to use the system template. +nix flake init -t github:snowfallorg/templates#system +``` + +3. See [Usage](#usage) for information on how to use and customize your flake. + +## Usage + +`snowfall-lib` provides two utilities directly on the flake itself. + +### `mkLib` + +The library generator function. This is the entrypoint for `snowfall-lib` and is how +you access all of its features. See the following Nix Flake example for how to create a +library instance with `mkLib`. + +```nix +{ + description = "My Flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + + snowfall-lib = { + url = "github:snowfallorg/lib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs: + let + lib = inputs.snowfall-lib.mkLib { + # You must pass in both your flake's inputs and the root directory of + # your flake. + inherit inputs; + src = ./.; + }; + in + # We'll cover what to do here next. + { }; +} +``` + +For information on how to use `lib`, see the [`lib`](#lib) section. Or skip directly +to [`lib.mkFlake`](#lib.mkflake) to see how to configure your flake's outputs. + +### `mkFlake` + +A convenience wrapper for writing the following. + +```nix +let + lib = inputs.snowfall-lib.mkLib { + inherit inputs; + src = ./.; + }; +in lib.mkFlake { +} +``` + +Instead, with `mkFlake` you can combine these calls into one like the following. + +```nix +inputs.snowfall-lib.mkFlake { + inherit inputs; + src = ./.; +}; +``` + +See [`lib.mkFlake`](#lib.mkflake) for information on how to configure your flake's outputs. + +## `lib` + +Snowfall Lib provides utilities for creating flake outputs as well as some +necessary helpers. In addition, `lib` is an extension of `nixpkgs.lib` and every +flake input that contains a `lib` attribute. This means that you can use `lib` +directly for all of your needs, whether they're Snowfall-related, NixPkgs-related, +or for one of the other flake inputs. + +The way that `mkLib` merges libraries is by starting with the base `nixpkgs.lib` and +then merge each flake input's `lib` attribute, namespaced by the name of the input. +For example, if you have the input `flake-utils-plus` then you will be able to use +`lib.flake-utils-plus` instead of having to keep a reference to the input's lib at +`inputs.flake-utils-plus.lib`. + +If you have your own library in a `lib/` directory at your flake's root, definitions +in there will automatically be imported and merged as well. + +When producing flake outputs with `mkFlake` or another Snowfall `lib` utility, `lib` will +be passed in as an input. All of this together gives you easy access to a common +library of utilities and easy access to the libraries of flake inputs or your own +custom library. + +### `lib.mkFlake` + +The `lib.mkFlake` function creates full flake outputs. For most cases you will only +need to use this helper and the Snowfall `lib` will take care of everything else. + +#### Flake Structure + +Snowfall Lib has opinions about how a flake's files are laid out. This lets +`lib` do all of the busy work for you and allows you to focus on creating. Here is +the structure that `lib` expects to find at the root of your flake. + +``` +flake-root/ +│ +│ Your Nix flake. +├─ flake.nix +│ +│ An optional custom library. +├─ lib/ +│ │ +│ │ A Nix function called with `inputs`, `snowfall-inputs`, and `lib`. +│ │ The function should return an attribute set to merge with `lib`. +│ ├─ default.nix +│ │ +│ │ Any (nestable) directory name. +│ └─ **/ +│ │ +│ │ A Nix function called with `inputs`, `snowfall-inputs`, and `lib`. +│ │ The function should return an attribute set to merge with `lib`. +│ └─ default.nix +│ +│ An optional set of packages to export. +├─ packages/ +│ │ +│ │ Any (nestable) directory name. The name of the directory will be the +│ │ name of the package. +│ └─ **/ +│ │ +│ │ A Nix package to be instantiated with `callPackage`. This file +│ │ should contain a function that takes an attribute set of packages +│ │ and *required* `lib` and returns a derivation. +│ └─ default.nix +│ +│ +├─ modules/ (optional modules) +│ │ +│ │ Any (nestable) directory name. The name of the directory will be the +│ │ name of the module. +│ └─ **/ +│ │ +│ │ A NixOS module. +│ └─ default.nix +│ +├─ overlays/ (optional overlays) +│ │ +│ │ Any (nestable) directory name. +│ └─ **/ +│ │ +│ │ A custom overlay. This file should contain a function that takes three arguments: +│ │ - An attribute set of your flake's inputs and a `channels` attribute containing +│ │ all of your available channels (eg. nixpkgs, unstable). +│ │ - The final set of `pkgs`. +│ │ - The previous set of `pkgs`. +│ │ +│ │ This function should return an attribute set to merge onto `pkgs`. +│ └─ default.nix +│ +├─ systems/ (optional system configurations) +│ │ +│ │ A directory named after the `system` type that will be used for all machines within. +│ │ +│ │ The architecture is any supported architecture of NixPkgs, for example: +│ │ - x86_64 +│ │ - aarch64 +│ │ - i686 +│ │ +│ │ The format is any supported NixPkgs format *or* a format provided by either nix-darwin +│ │ or nixos-generators. However, in order to build systems with nix-darwin or nixos-generators, +│ │ you must add `darwin` and `nixos-generators` inputs to your flake respectively. Here +│ │ are some example formats: +│ │ - linux +│ │ - darwin +│ │ - iso +│ │ - install-iso +│ │ - do +│ │ - vmware +│ │ +│ │ With the architecture and format together (joined by a hyphen), you get the name of the +│ │ directory for the system type. +│ └─ -/ +│ │ +│ │ A directory that contains a single system's configuration. The directory name +│ │ will be the name of the system. +│ └─ / +│ │ +│ │ A NixOS module for your system's configuration. +│ └─ default.nix +``` + +#### Default Flake + +Without any extra input, `lib.mkFlake` will generate outputs for all systems, modules, +packages, overlays, and shells specified by the [Flake Structure](#flake-structure) section. + +```nix +{ + description = "My Flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + + snowfall-lib = { + url = "github:snowfallorg/lib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs: + # This is an example and in your actual flake you can use `snowfall-lib.mkFlake` + # directly unless you explicitly need a feature of `lib`. + let + lib = inputs.snowfall-lib.mkLib { + # You must pass in both your flake's inputs and the root directory of + # your flake. + inherit inputs; + src = ./.; + }; + in + lib.mkFlake { }; +} +``` + +#### External Overlays And Modules + +You can apply overlays and modules from your flake's inputs with the following options. + +```nix +{ + description = "My Flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + + snowfall-lib = { + url = "github:snowfallorg/lib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + nix-ld = { + url = "github:Mic92/nix-ld"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs: + # This is an example and in your actual flake you can use `snowfall-lib.mkFlake` + # directly unless you explicitly need a feature of `lib`. + let + lib = inputs.snowfall-lib.mkLib { + # You must pass in both your flake's inputs and the root directory of + # your flake. + inherit inputs; + src = ./.; + }; + in + lib.mkFlake { + # Add overlays for the `nixpkgs` channel. + overlays = with inputs; [ + home-manager.overlay + ]; + + # Add modules to all systems. + systems.modules = with inputs; [ + nix-ld.nixosModules.nix-ld + ]; + + # Add modules to a specific system. + systems.hosts.my-host = with inputs; [ + nix-ld.nixosModules.nix-ld + ]; + }; +} +``` + +#### Internal Packages And Outputs + +Packages created from your `packages/` directory are automatically made available via an +overlay for your `nixpkgs` channel. System configurations can access these packages directly +on `pkgs` and consumers of your flake can use the generated `.overlays` attributes. + +```nix +{ + description = "My Flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + + snowfall-lib = { + url = "github:snowfallorg/lib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + comma = { + url = "github:nix-community/comma"; + inputs.nixpkgs.follows = "unstable"; + }; + }; + + outputs = inputs: + # This is an example and in your actual flake you can use `snowfall-lib.mkFlake` + # directly unless you explicitly need a feature of `lib`. + let + lib = inputs.snowfall-lib.mkLib { + # You must pass in both your flake's inputs and the root directory of + # your flake. + inherit inputs; + src = ./.; + }; + in + lib.mkFlake { + # Optionally place all packages under a namespace when used in an overlay. + # Instead of accessing packages with `pkgs.`, your internal packages + # will be available at `pkgs..`. + overlay-package-namespace = "my-namespace"; + + # You can also pass through external packages or dynamically create new ones + # in addition to the ones that `lib` will create from your `packages/` directory. + outputs-builder = channels: { + packages = { + comma = inputs.comma.packages.${channels.nixpkgs.system}.comma; + }; + }; + }; +} +``` + +#### Default Packages And Shells + +Snowfall Lib will create packages and shells based on your `packages/` and `shells` +directories. However, it is common to additionally map one of those packages or shells +to be their respective default. This can be achieved by using `outputs-builder` and +mapping the `default` package or shell to the name of the one you want. + +```nix +{ + description = "My Flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + + snowfall-lib = { + url = "github:snowfallorg/lib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs: + # This is an example and in your actual flake you can use `snowfall-lib.mkFlake` + # directly unless you explicitly need a feature of `lib`. + let + lib = inputs.snowfall-lib.mkLib { + # You must pass in both your flake's inputs and the root directory of + # your flake. + inherit inputs; + src = ./.; + }; + in + lib.mkFlake { + # You can also pass through external packages or dynamically create new ones + # in addition to the ones that `lib` will create from your `packages/` directory. + outputs-builder = channels: { + packages = { + default = "my-package"; + }; + + devShells = { + default = "my-shell"; + }; + }; + }; +} +``` + +#### Darwin And NixOS Generators + +Snowfall Lib has support for configuring macOS systems and building any output +supported by NixOS Generators. In order to use these features, your flake must +include `darwin` and/or `nixos-generators` as inputs. + +```nix +{ + description = "My Flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + + snowfall-lib = { + url = "github:snowfallorg/lib"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + # In order to configure macOS systems. + darwin = { + url = "github:lnl7/nix-darwin"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + # In order to build system images and artifacts supported by nixos-generators. + nixos-generators = { + url = "github:nix-community/nixos-generators"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs: + # This is an example and in your actual flake you can use `snowfall-lib.mkFlake` + # directly unless you explicitly need a feature of `lib`. + let + lib = inputs.snowfall-lib.mkLib { + # You must pass in both your flake's inputs and the root directory of + # your flake. + inherit inputs; + src = ./.; + }; + in + # No additional configuration is required to use this feature, you only + # have to add darwin or nixos-generators to your flake inputs. + lib.mkFlake { }; +} +``` + +Any macOS systems will be available on your flake at `darwinConfigurations` for use +with `darwin-rebuild`. Any system type supported by NixOS Generators will be available +on your flake at `Configurations` where `` is the name of the generator +type. See the following table for a list of supported formats from NixOS Generators. + +| format | description | +| -------------------- | ---------------------------------------------------------------------------------------- | +| amazon | Amazon EC2 image | +| azure | Microsoft azure image (Generation 1 / VHD) | +| cloudstack | qcow2 image for cloudstack | +| do | Digital Ocean image | +| gce | Google Compute image | +| hyperv | Hyper-V Image (Generation 2 / VHDX) | +| install-iso | Installer ISO | +| install-iso-hyperv | Installer ISO with enabled hyper-v support | +| iso | ISO | +| kexec | kexec tarball (extract to / and run /kexec_nixos) | +| kexec-bundle | Same as before, but it's just an executable | +| kubevirt | KubeVirt image | +| lxc | Create a tarball which is importable as an lxc container, use together with lxc-metadata | +| lxc-metadata | The necessary metadata for the lxc image to start | +| openstack | qcow2 image for openstack | +| proxmox | [VMA](https://pve.proxmox.com/wiki/VMA) file for proxmox | +| qcow | qcow2 image | +| raw | Raw image with bios/mbr | +| raw-efi | Raw image with efi support | +| sd-aarch64 | Like sd-aarch64-installer, but does not use default installer image config. | +| sd-aarch64-installer | create an installer sd card for aarch64 | +| vagrant-virtualbox | VirtualBox image for [Vagrant](https://www.vagrantup.com/) | +| virtualbox | virtualbox VM | +| vm | Only used as a qemu-kvm runner | +| vm-bootloader | Same as vm, but uses a real bootloader instead of netbooting | +| vm-nogui | Same as vm, but without a GUI | +| vmware | VMWare image (VMDK) | + +### `lib.snowfall.flake` + +Helpers related to Nix flakes. + +#### `lib.snowfall.flake.without-self` + +Remove the `self` attribute from an attribute set. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +without-self { self = {}; x = true; } +``` + +Result: + +```nix +{ x = true; } +``` + +#### `lib.snowfall.flake.without-src` + +Remove the `src` attribute from an attribute set. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +without-src { src = {}; x = true; } +``` + +Result: + +```nix +{ x = true; } +``` + +#### `lib.snowfall.flake.without-snowfall-inputs` + +Remove the `src` and `self` attributes from an attribute set. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +without-snowfall-inputs { self = {}; src = {}; x = true; } +``` + +Result: + +```nix +{ x = true; } +``` + +#### `lib.snowfall.flake.get-libs` + +Transform an attribute set of inputs into an attribute set where +the values are the inputs' `lib` attribute. Entries without a `lib` +attribute are removed. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +get-lib { x = nixpkgs; y = {}; } +``` + +Result: + +```nix +{ x = nixpkgs.lib; } +``` + +### `lib.snowfall.path` + +#### `lib.snowfall.path.split-file-extension` + +Split a file name and its extension. + +Type: `String -> [String]` + +Usage: + +```nix +split-file-extension "my-file.md" +``` + +Result: + +```nix +[ "my-file" "md" ] +``` + +#### `lib.snowfall.path.has-any-file-extension` + +Check if a file name has a file extension. + +Type: `String -> Bool` + +Usage: + +```nix +has-any-file-extension "my-file.txt" +``` + +Result: + +```nix +true +``` + +#### `lib.snowfall.path.get-file-extension` + +Get the file extension of a file name. + +Type: `String -> String` +Usage: + +```nix +get-file-extension "my-file.final.txt" +``` + +Result: + +```nix +"txt" +``` + +#### `lib.snowfall.path.has-file-extension` + +Check if a file name has a specific file extension. + +Type: `String -> String -> Bool` + +Usage: + +```nix +has-file-extension "txt" "my-file.txt" +``` + +Result: + +```nix +true +``` + +#### `lib.snowfall.path.get-parent-directory` + +Get the parent directory for a given path. + +Type: `Path -> Path` + +Usage: + +```nix +get-parent-directory "/a/b/c" +``` + +Result: + +```nix +"/a/b" +``` + +#### `lib.snowfall.path.get-file-name-without-extension` + +Get the file name of a path without its extension. + +Type: `Path -> String` + +Usage: + +```nix +get-file-name-without-extension ./some-directory/my-file.pdf +``` + +Result: + +```nix +"my-file" +``` + +### `lib.snowfall.fs` + +File system utilities. + +#### `lib.snowfall.fs.is-file-kind` +#### `lib.snowfall.fs.is-symlink-kind` +#### `lib.snowfall.fs.is-directory-kind` +#### `lib.snowfall.fs.is-unknown-kind` + +Matchers for file kinds. These are often used with `readDir`. + +Type: `String -> Bool` + +Usage: + +```nix +is-file-kind "directory" +``` + +Result: + +```nix +false +``` + +#### `lib.snowfall.fs.get-file` + +Get a file path relative to the user's flake. + +Type: `Path -> Path` + +Usage: + +```nix +get-file "systems" +``` + +Result: + +```nix +"/user-source/systems" +``` + +#### `lib.snowfall.fs.internal-get-file` + +Get a file relative to the Snowfall Lib flake. You probably shouldn't use this! + +Type: `Path -> Path` + +Usage: + +```nix +get-file "systems" +``` + +Result: + +```nix +"/snowfall-lib-source/systems" +``` + +#### `lib.snowfall.fs.safe-read-directory` + +Safely read from a directory if it exists. + +Type: `Path -> Attrs` + +Usage: + +```nix +safe-read-directory ./some/path +``` + +Result: + +```nix +{ "my-file.txt" = "regular"; } +``` + +#### `lib.snowfall.fs.get-directories` + +Get directories at a given path. + +Type: `Path -> [Path]` + +Usage: + +```nix +get-directories ./something +``` + +Result: + +```nix +[ "./something/a-directory" ] +``` + +#### `lib.snowfall.fs.get-files` + +Get files at a given path. + +Type: `Path -> [Path]` + +Usage: + +```nix +get-files ./something +``` + +Result: + +```nix +[ "./something/a-file" ] +``` + +#### `lib.snowfall.fs.get-files-recursive` + +Get files at a given path, traversing any directories within. + +Type: `Path -> [Path]` + +Usage: + +```nix +get-files-recursive ./something +``` + +Result: + +```nix +[ "./something/some-directory/a-file" ] +``` + +#### `lib.snowfall.fs.get-nix-files` + +Get nix files at a given path. + +Type: `Path -> [Path]` + +Usage: + +```nix +get-nix-files "./something" +``` + +Result: + +```nix +[ "./something/a.nix" ] +``` + +#### `lib.snowfall.fs.get-nix-files-recursive` + +Get nix files at a given path, traversing any directories within. + +Type: `Path -> [Path]` + +Usage: + +```nix +get-nix-files "./something" +``` + +Result: + +```nix +[ "./something/a.nix" ] +``` + +#### `lib.snowfall.fs.get-default-nix-files` + +Get nix files at a given path named "default.nix". + +Type: `Path -> [Path]` + +Usage: + +```nix +get-default-nix-files "./something" +``` + +Result: + +```nix +[ "./something/default.nix" ] +``` + +#### `lib.snowfall.fs.get-default-nix-files-recursive` + +Get nix files at a given path named "default.nix", traversing any directories within. + +Type: `Path -> [Path]` + +Usage: + +```nix +get-default-nix-files-recursive "./something" +``` + +Result: + +```nix +[ "./something/some-directory/default.nix" ] +``` + +#### `lib.snowfall.fs.get-non-default-nix-files` + +Get nix files at a given path not named "default.nix". + +Type: `Path -> [Path]` + +Usage: + +```nix +get-non-default-nix-files "./something" +``` + +Result: + +```nix +[ "./something/a.nix" ] +``` + +#### `lib.snowfall.fs.get-non-default-nix-files-recursive` + +Get nix files at a given path not named "default.nix", traversing any directories within. + +Type: `Path -> [Path]` + +Usage: + +```nix +get-non-default-nix-files-recursive "./something" +``` + +Result: + +```nix +[ "./something/some-directory/a.nix" ] +``` + +### `lib.snowfall.attrs` + +Utilities for working with attribute sets. + +#### `lib.snowfall.attrs.map-concat-attrs-to-list` + +Map and flatten an attribute set into a list. + +Type: `(a -> b -> [c]) -> Attrs -> [c]` + +Usage: + +```nix +map-concat-attrs-to-list (name: value: [name value]) { x = 1; y = 2; } +``` + +Result: + +```nix +[ "x" 1 "y" 2 ] +``` + +#### `lib.snowfall.attrs.merge-deep` + +Recursively merge a list of attribute sets. + +Type: `[Attrs] -> Attrs` + +Usage: + +```nix +merge-deep [{ x = 1; } { x = 2; }] +``` + +Result: + +```nix +{ x = 2; } +``` + +#### `lib.snowfall.attrs.merge-shallow` + +Merge the root of a list of attribute sets. + +Type: `[Attrs] -> Attrs` + +Usage: + +```nix +merge-shallow [{ x = 1; } { x = 2; }] +``` + +Result: + +```nix +{ x = 2; } +``` + +### `lib.snowfall.system` + +#### `lib.snowfall.system.is-darwin` + +Check whether a named system is macOS. + +Type: `String -> Bool` + +Usage: + +```nix +is-darwin "x86_64-linux" +``` + +Result: + +```nix +false +``` + +#### `lib.snowfall.system.is-linux` + +Check whether a named system is Linux. + +Type: `String -> Bool` + +Usage: + +```nix +is-linux "x86_64-linux" +``` + +Result: + +```nix +false +``` + +#### `lib.snowfall.system.get-virtual-system-type` + +Get the virtual system type of a system target. + +Type: `String -> String` + +Usage: + +```nix +get-virtual-system-type "x86_64-iso" +``` + +Result: + +```nix +"iso" +``` + +#### `lib.snowfall.system.get-target-systems-metadata` + +Get structured data about all systems for a given target. + +Type: `String -> [Attrs]` + +Usage: + +```nix +get-target-systems-metadata "x86_64-linux" +``` + +Result: + +```nix +[ { target = "x86_64-linux"; name = "my-machine"; path = "/systems/x86_64-linux/my-machine"; } ] +``` + +#### `lib.snowfall.system.get-system-builder` + +Get the system builder for a given target. + +Type: `String -> Function` + +Usage: + +```nix +get-system-builder "x86_64-iso" +``` + +Result: + +```nix +(args: ) +``` + +#### `lib.snowfall.system.get-system-output` + +Get the flake output attribute for a system target. + +Type: `String -> String` + +Usage: + +```nix +get-system-output "aarch64-darwin" +``` + +Result: + +```nix +"darwinConfigurations" +``` + +#### `lib.snowfall.system.get-resolved-system-target` + +Get the resolved (non-virtual) system target. + +Type: `String -> String` + +Usage: + +```nix +get-resolved-system-target "x86_64-iso" +``` + +Result: + +```nix +"x86_64-linux" +``` + +#### `lib.snowfall.system.create-system` + +Create a system. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-system { path = ./systems/my-system; } +``` + +Result: + +```nix + +``` + +#### `lib.snowfall.system.create-systems` + +Create all available systems. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-systems { hosts.my-host.specialArgs.x = true; modules = [ my-shared-module ]; } +``` + +Result: + +```nix +{ my-host = ; } +``` + +### `lib.snowfall.package` + +Utilities for working with flake packages. + +#### `lib.snowfall.package.create-packages` + +Create flake output packages. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-packages { inherit channels; src = ./my-packages; overrides = { inherit another-package; default = "my-package"; }; } +``` + +Result: + +```nix +{ another-package = ...; my-package = ...; default = ...; } +``` + +### `lib.snowfall.shell` + +Utilities for working with flake dev shells. + +#### `lib.snowfall.shell.create-shell` + +Create flake output packages. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-shells { inherit channels; src = ./my-shells; overrides = { inherit another-shell; default = "my-shell"; }; } +``` + +Result: + +```nix +{ another-shell = ...; my-shell = ...; default = ...; } +``` + +### `lib.snowfall.overlay` + +Utilities for working with channel overlays. + +#### `lib.snowfall.overlay.create-overlays` + +Create a flake-utils-plus overlays builder. + +Type: `Attrs -> Attrs -> [(a -> b -> c)]` + +Usage: + +```nix +create-overlays { src = ./my-overlays; overlay-package-namespace = "my-packages"; } +``` + +Result: + +```nix +(channels: [ ... ]) +``` + +### `lib.snowfall.template` + +Utilities for working with flake templates. + +#### `lib.snowfall.template.create-templates` + +Create flake templates. + +Type: `Attrs -> Attrs` + +Usage: + +```nix +create-templates { src = ./my-templates; overrides = { inherit another-template; default = "my-template"; }; } +``` + +Result: + +```nix +{ another-template = ...; my-template = ...; default = ...; } +``` diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..2cccff2 --- /dev/null +++ b/default.nix @@ -0,0 +1,10 @@ +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).defaultNix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..5154ca6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,137 @@ +{ + "nodes": { + "darwin": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1661882940, + "narHash": "sha256-4LaVFnV22WrOA0aolqqk9dXrM8crikcrLQt29G18F7M=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "80cec5115aae74accc4ccfb9f84306d7863f0632", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1644229661, + "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils-plus": { + "inputs": { + "flake-utils": "flake-utils" + }, + "locked": { + "lastModified": 1657226504, + "narHash": "sha256-GIYNjuq4mJlFgqKsZ+YrgzWm0IpA4axA3MCrdKYj7gs=", + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "rev": "2bf0f91643c2e5ae38c1b26893ac2927ac9bd82a", + "type": "github" + }, + "original": { + "owner": "gytis-ivaskevicius", + "repo": "flake-utils-plus", + "type": "github" + } + }, + "nixlib": { + "locked": { + "lastModified": 1636849918, + "narHash": "sha256-nzUK6dPcTmNVrgTAC1EOybSMsrcx+QrVPyqRdyKLkjA=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "28a5b0557f14124608db68d3ee1f77e9329e9dd5", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixos-generators": { + "inputs": { + "nixlib": "nixlib", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660727616, + "narHash": "sha256-zYTIvdPMYMx/EYqXODAwIIU30RiEHqNHdgarIHuEYZc=", + "owner": "nix-community", + "repo": "nixos-generators", + "rev": "adccd191a0e83039d537e021f19495b7bad546a1", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-generators", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1662316000, + "narHash": "sha256-NkJHCVi6sj1+QqqXaozSJ0K23ANmvsnDmeC/6WXo+wI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "50c62eeda9df340ff6b83a0e2343a447af04237c", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "release-22.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "darwin": "darwin", + "flake-compat": "flake-compat", + "flake-utils-plus": "flake-utils-plus", + "nixos-generators": "nixos-generators", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..87dac1b --- /dev/null +++ b/flake.nix @@ -0,0 +1,42 @@ +{ + description = "A very basic flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/release-22.05"; + flake-utils-plus.url = "github:gytis-ivaskevicius/flake-utils-plus"; + + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + }; + + outputs = inputs: + let + core-inputs = inputs // { + src = ./.; + }; + + # Create the library, extending the nixpkgs library and merging + # libraries from other inputs to make them available like + # `lib.flake-utils-plus.mkApp`. + # Usage: mkLib { inherit inputs; src = ./.; } + # result: lib + mkLib = import ./lib core-inputs; + + # A convenience wrapper to create the library and then call `lib.mkFlake`. + # Usage: mkFlake { inherit inputs; src = ./.; ... } + # result: + mkFlake = flake-and-lib-options@{ inputs, src, ... }: + let + lib = mkLib { + inherit inputs src; + }; + flake-options = builtins.removeAttrs flake-and-lib-options [ "inputs" "src" ]; + in + lib.mkFlake flake-options; + in + { + inherit mkLib mkFlake; + }; +} diff --git a/lib/attrs/default.nix b/lib/attrs/default.nix new file mode 100644 index 0000000..44c66ee --- /dev/null +++ b/lib/attrs/default.nix @@ -0,0 +1,36 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (core-inputs.nixpkgs.lib) + assertMsg + mapAttrsToList + flatten + fold + recursiveUpdate + mergeAttrs; +in +{ + attrs = { + # Map and flatten an attribute set into a list. + # Type: (a -> b -> [c]) -> Attrs -> [c] + # Usage: map-concat-attrs-to-list (name: value: [name value]) { x = 1; y = 2; } + # result: [ "x" 1 "y" 2 ] + map-concat-attrs-to-list = f: attrs: + flatten (mapAttrsToList f attrs); + + # Recursively merge a list of attribute sets. + # Type: [Attrs] -> Attrs + # Usage: merge-deep [{ x = 1; } { x = 2; }] + # result: { x = 2; } + merge-deep = fold recursiveUpdate { }; + + # Merge the root of a list of attribute sets. + # Type: [Attrs] -> Attrs + # Usage: merge-shallow [{ x = 1; } { x = 2; }] + # result: { x = 2; } + merge-shallow = fold mergeAttrs { }; + }; +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..fd22b10 --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,109 @@ +# @NOTE(jakehamilton): The role of this file is to bootstrap the +# Snowfall library. There is some duplication shared between this +# file and the library itself due to the library needing to pass through +# another extended library for its own applications. +core-inputs: +user-options: + +let + user-inputs = user-options.inputs // { src = user-options.src; }; + + inherit (core-inputs.nixpkgs.lib) assertMsg fix filterAttrs mergeAttrs fold recursiveUpdate; + + # Recursively merge a list of attribute sets. + # Type: [Attrs] -> Attrs + # Usage: merge-deep [{ x = 1; } { x = 2; }] + # result: { x = 2; } + merge-deep = fold recursiveUpdate { }; + + # Merge the root of a list of attribute sets. + # Type: [Attrs] -> Attrs + # Usage: merge-shallow [{ x = 1; } { x = 2; }] + # result: { x = 2; } + merge-shallow = fold mergeAttrs { }; + + # Transform an attribute set of inputs into an attribute set where + # the values are the inputs' `lib` attribute. Entries without a `lib` + # attribute are removed. + # Type: Attrs -> Attrs + # Usage: get-lib { x = nixpkgs; y = {}; } + # result: { x = nixpkgs.lib; } + get-libs = attrs: + let + # @PERF(jakehamilton): Replace filter+map with a fold. + attrs-with-libs = filterAttrs + (name: value: builtins.isAttrs (value.lib or null)) + attrs; + libs = + builtins.mapAttrs (name: input: input.lib) attrs-with-libs; + in + libs; + + # Remove the `self` attribute from an attribute set. + # Type: Attrs -> Attrs + # Usage: without-self { self = {}; x = true; } + # result: { x = true; } + without-self = attrs: builtins.removeAttrs attrs [ "self" ]; + + core-inputs-libs = get-libs (without-self core-inputs); + user-inputs-libs = get-libs (without-self user-inputs); + + snowfall-lib-root = "${core-inputs.src}/lib"; + snowfall-lib-dirs = + let + files = builtins.readDir snowfall-lib-root; + dirs = filterAttrs (name: kind: kind == "directory") files; + names = builtins.attrNames dirs; + in + names; + + snowfall-lib = fix (snowfall-lib: + let + attrs = { + inherit snowfall-lib core-inputs user-inputs; + }; + libs = builtins.map + (dir: import "${snowfall-lib-root}/${dir}" attrs) + snowfall-lib-dirs; + in + merge-deep libs + ); + + snowfall-top-level-lib = filterAttrs (name: value: !builtins.isAttrs value) snowfall-lib; + + base-lib = merge-shallow [ + core-inputs.nixpkgs.lib + core-inputs-libs + user-inputs-libs + snowfall-top-level-lib + { snowfall = snowfall-lib; } + ]; + + user-lib-root = "${user-inputs.src}/lib"; + user-lib-modules = snowfall-lib.fs.get-default-nix-files-recursive user-lib-root; + + user-lib = fix (user-lib: + let + attrs = { + inherit (user-options) inputs; + snowfall-inputs = core-inputs; + lib = merge-shallow [ base-lib user-lib ]; + }; + libs = builtins.map + (path: import path attrs) + user-lib-modules; + in + merge-deep libs + ); + + lib = merge-deep [ + base-lib + user-lib + ]; + + user-inputs-has-self = builtins.elem "self" (builtins.attrNames user-inputs); + user-inputs-has-src = builtins.elem "src" (builtins.attrNames user-inputs); +in + assert (assertMsg (user-inputs-has-self) "Missing attribute `self` for mkLib."); + assert (assertMsg (user-inputs-has-src) "Missing attribute `src` for mkLib."); + lib diff --git a/lib/flake/default.nix b/lib/flake/default.nix new file mode 100644 index 0000000..eb38938 --- /dev/null +++ b/lib/flake/default.nix @@ -0,0 +1,130 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (core-inputs.nixpkgs.lib) assertMsg fold filterAttrs const; +in +rec { + flake = rec { + # Remove the `self` attribute from an attribute set. + # Type: Attrs -> Attrs + # Usage: without-self { self = {}; x = true; } + # result: { x = true; } + without-self = flake-inputs: builtins.removeAttrs flake-inputs [ "self" ]; + + # Remove the `src` attribute from an attribute set. + # Type: Attrs -> Attrs + # Usage: without-src { src = ./.; x = true; } + # result: { x = true; } + without-src = flake-inputs: builtins.removeAttrs flake-inputs [ "src" ]; + + # Remove the `src` and `self` attributes from an attribute set. + # Type: Attrs -> Attrs + # Usage: without-snowfall-inputs { self = {}; src = ./.; x = true; } + # result: { x = true; } + without-snowfall-inputs = snowfall-lib.fp.compose without-self without-src; + + # Remove Snowfall-specific attributes so the rest can be safely + # passed to flake-utils-plus. + # Type: Attrs -> Attrs + # Usage: without-snowfall-options { src = ./.; x = true; } + # result: { x = true; } + without-snowfall-options = flake-options: + builtins.removeAttrs + flake-options + [ + "systems" + "modules" + "overlays" + "packages" + "outputs-builder" + "outputsBuilder" + "packagesPrefix" + "hosts" + "channels-config" + "templates" + ]; + + # Transform an attribute set of inputs into an attribute set where + # the values are the inputs' `lib` attribute. Entries without a `lib` + # attribute are removed. + # Type: Attrs -> Attrs + # Usage: get-lib { x = nixpkgs; y = {}; } + # result: { x = nixpkgs.lib; } + get-libs = attrs: + let + # @PERF(jakehamilton): Replace filter+map with a fold. + attrs-with-libs = filterAttrs + (name: value: builtins.isAttrs (value.lib or null)) + attrs; + libs = + builtins.mapAttrs (name: input: input.lib) attrs-with-libs; + in + libs; + + }; + + mkFlake = full-flake-options: + let + custom-flake-options = flake.without-snowfall-options full-flake-options; + systems = snowfall-lib.system.create-systems (full-flake-options.systems or {}); + hosts = snowfall-lib.attrs.merge-shallow [ (full-flake-options.systems.hosts or {}) systems ]; + templates = snowfall-lib.template.create-templates { + overrides = (full-flake-options.templates or {}); + }; + modules = snowfall-lib.module.create-modules { + overrides = (full-flake-options.modules or {}); + }; + + outputs-builder = channels: + let + user-outputs-builder = + full-flake-options.outputs-builder + or full-flake-options.outputsBuilder + or (const {}); + user-outputs = user-outputs-builder channels; + packages = snowfall-lib.package.create-packages { + inherit channels; + overrides = (full-flake-options.packages or {}) // (user-outputs.packages or {}); + }; + shells = snowfall-lib.shell.create-shells { + inherit channels; + overrides = (full-flake-options.shells or {}) // (user-outputs.devShells or {}); + }; + + outputs = { + inherit packages; + + devShells = shells; + }; + in + snowfall-lib.attrs.merge-deep [ user-outputs outputs ]; + + flake-options = custom-flake-options // { + inherit hosts templates; + inherit (user-inputs) self; + + lib = snowfall-lib.internal.system-lib; + inputs = snowfall-lib.flake.without-src user-inputs; + + nixosModules = modules; + + channelsConfig = full-flake-options.channels-config or {}; + + overlays = core-inputs.flake-utils-plus.lib.exportOverlays ({ + inherit (user-inputs.self) pkgs; + inputs = user-inputs; + }); + + channels.nixpkgs.overlaysBuilder = snowfall-lib.overlay.create-overlays { + overlay-package-namespace = full-flake-options.overlay-package-namespace or null; + extra-overlays = full-flake-options.overlays or []; + }; + + outputsBuilder = outputs-builder; + }; + in + core-inputs.flake-utils-plus.lib.mkFlake flake-options; +} diff --git a/lib/fp/default.nix b/lib/fp/default.nix new file mode 100644 index 0000000..78da0ee --- /dev/null +++ b/lib/fp/default.nix @@ -0,0 +1,36 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (builtins) baseNameOf dirOf; + inherit (core-inputs.nixpkgs.lib) id foldr flip; +in +{ + fp = rec { + # Compose two functions. + # Type: (b -> c) -> (a -> b) -> a -> c + # Usage: compose add-two add-one + # result: (x: add-two (add-one x)) + compose = f: g: x: f (g x); + + # Compose many functions. + # Type: [(x -> y)] -> a -> b + # Usage: compose-all [ add-two add-one ] + # result: (x: add-two (add-one x)) + compose-all = foldr compose id; + + # Call a function with an argument. + # Type: (a -> b) -> a -> b + # Usage: call (x: x + 1) 0 + # result: 1 + call = f: x: f x; + + # Apply an argument to a function. + # Type: a -> (a -> b) -> b + # Usage: call (x: x + 1) 0 + # result: 1 + apply = flip call; + }; +} diff --git a/lib/fs/default.nix b/lib/fs/default.nix new file mode 100644 index 0000000..48910ad --- /dev/null +++ b/lib/fs/default.nix @@ -0,0 +1,153 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (builtins) readDir pathExists; + inherit (core-inputs) flake-utils-plus; + inherit (core-inputs.nixpkgs.lib) assertMsg filterAttrs mapAttrsToList flatten; + + file-name-regex = "(.*)\\.(.*)$"; +in +{ + fs = rec { + # Matchers for file kinds. These are often used with `readDir`. + # Type: String -> Bool + # Usage: is-file-kind "directory" + # result: false + is-file-kind = kind: kind == "regular"; + is-symlink-kind = kind: kind == "symlink"; + is-directory-kind = kind: kind == "directory"; + is-unknown-kind = kind: kind == "unknown"; + + # Get a file path relative to the user's flake. + # Type: Path -> Path + # Usage: get-file "systems" + # result: "/user-source/systems" + get-file = path: "${user-inputs.src}/${path}"; + + # Get a file path relative to the this flake. + # Type: Path -> Path + # Usage: get-file "systems" + # result: "/user-source/systems" + internal-get-file = path: "${core-inputs.src}/${path}"; + + # Safely read from a directory if it exists. + # Type: Path -> Attrs + # Usage: safe-read-directory ./some/path + # result: { "my-file.txt" = "regular"; } + safe-read-directory = path: + if pathExists path then + readDir path + else + {}; + + # Get directories at a given path. + # Type: Path -> [Path] + # Usage: get-directories ./something + # result: [ "./something/a-directory" ] + get-directories = path: + let + entries = safe-read-directory path; + filtered-entries = filterAttrs (name: kind: is-directory-kind kind) entries; + in + mapAttrsToList (name: kind: "${path}/${name}") filtered-entries; + + # Get files at a given path. + # Type: Path -> [Path] + # Usage: get-files ./something + # result: [ "./something/a-file" ] + get-files = path: + let + entries = safe-read-directory path; + filtered-entries = filterAttrs (name: kind: is-file-kind kind) entries; + in + mapAttrsToList (name: kind: "${path}/${name}") filtered-entries; + + # Get files at a given path, traversing any directories within. + # Type: Path -> [Path] + # Usage: get-files-recursive ./something + # result: [ "./something/some-directory/a-file" ] + get-files-recursive = path: + let + entries = safe-read-directory path; + filtered-entries = + filterAttrs + (name: kind: (is-file-kind kind) || (is-directory-kind kind)) + entries; + map-file = name: kind: + let + path' = "${path}/${name}"; + in if is-directory-kind kind then + get-files-recursive path' + else + path'; + files = snowfall-lib.attrs.map-concat-attrs-to-list + map-file + filtered-entries; + in + files; + + # Get nix files at a given path. + # Type: Path -> [Path] + # Usage: get-nix-files "./something" + # result: [ "./something/a.nix" ] + get-nix-files = path: + builtins.filter + (snowfall-lib.path.has-file-extension "nix") + (get-files path); + + # Get nix files at a given path, traversing any directories within. + # Type: Path -> [Path] + # Usage: get-nix-files "./something" + # result: [ "./something/a.nix" ] + get-nix-files-recursive = path: + builtins.filter + (snowfall-lib.path.has-file-extension "nix") + (get-files-recursive path); + + # Get nix files at a given path named "default.nix". + # Type: Path -> [Path] + # Usage: get-default-nix-files "./something" + # result: [ "./something/default.nix" ] + get-default-nix-files = path: + builtins.filter + (name: builtins.baseNameOf name == "default.nix") + (get-files path); + + # Get nix files at a given path named "default.nix", traversing any directories within. + # Type: Path -> [Path] + # Usage: get-default-nix-files-recursive "./something" + # result: [ "./something/some-directory/default.nix" ] + get-default-nix-files-recursive = path: + builtins.filter + (name: builtins.baseNameOf name == "default.nix") + (get-files-recursive path); + + # Get nix files at a given path not named "default.nix". + # Type: Path -> [Path] + # Usage: get-non-default-nix-files "./something" + # result: [ "./something/a.nix" ] + get-non-default-nix-files = path: + builtins.filter + (name: + (snowfall-lib.path.has-file-extension "nix" name) + && (builtins.baseNameOf name != "default.nix") + ) + (get-files path); + + # Get nix files at a given path not named "default.nix", + # traversing any directories within. + # Type: Path -> [Path] + # Usage: get-non-default-nix-files-recursive "./something" + # result: [ "./something/some-directory/a.nix" ] + get-non-default-nix-files-recursive = path: + builtins.filter + (name: + (snowfall-lib.path.has-file-extension "nix" name) + && (builtins.baseNameOf name != "default.nix") + ) + (get-files-recursive path); + }; +} diff --git a/lib/internal/default.nix b/lib/internal/default.nix new file mode 100644 index 0000000..94d7cd6 --- /dev/null +++ b/lib/internal/default.nix @@ -0,0 +1,48 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (core-inputs.nixpkgs.lib) assertMsg fix fold filterAttrs; + + core-inputs-libs = snowfall-lib.flake.get-libs (snowfall-lib.flake.without-self core-inputs); + user-inputs-libs = snowfall-lib.flake.get-libs (snowfall-lib.flake.without-self user-inputs); + + snowfall-top-level-lib = filterAttrs (name: value: !builtins.isAttrs value) snowfall-lib; + + base-lib = snowfall-lib.attrs.merge-shallow [ + core-inputs.nixpkgs.lib + core-inputs-libs + user-inputs-libs + snowfall-top-level-lib + { snowfall = snowfall-lib; } + ]; + + user-lib-root = snowfall-lib.fs.get-file "lib"; + user-lib-modules = snowfall-lib.fs.get-default-nix-files-recursive user-lib-root; + + user-lib = fix (user-lib: + let + attrs = { + inputs = snowfall-lib.flake.without-snowfall-inputs user-inputs; + snowfall-inputs = core-inputs; + lib = snowfall-lib.attrs.merge-shallow [ base-lib user-lib ]; + }; + libs = builtins.map + (path: import path attrs) + user-lib-modules; + in + snowfall-lib.attrs.merge-deep libs + ); + + system-lib = snowfall-lib.attrs.merge-shallow [ + base-lib + user-lib + ]; +in +{ + internal = { + inherit system-lib; + }; +} diff --git a/lib/module/default.nix b/lib/module/default.nix new file mode 100644 index 0000000..5b7d49c --- /dev/null +++ b/lib/module/default.nix @@ -0,0 +1,48 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (builtins) baseNameOf; + inherit (core-inputs.nixpkgs.lib) assertMsg foldl; + + user-modules-root = snowfall-lib.fs.get-file "modules"; +in +{ + module = { + # Create flake output modules. + # Type: Attrs -> Attrs + # Usage: create-modules { src = ./my-modules; overrides = { inherit another-module; default = "my-module"; }; } + # result: { another-module = ...; my-module = ...; default = ...; } + create-modules = + { src ? user-modules-root + , overrides ? { } + }: + let + user-modules = snowfall-lib.fs.get-default-nix-files-recursive src; + create-module-metadata = module: { + name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory module); + path = module; + }; + modules-metadata = builtins.map create-module-metadata user-modules; + merge-modules = modules: metadata: + modules // { + ${metadata.name} = metadata.path; + }; + modules-without-default = foldl merge-modules { } modules-metadata; + default-module = + if overrides.default or null == null then + { } + else if builtins.isAttrs overrides.default then + { default = overrides.default; } + else if modules-without-default.${overrides.default} or null != null then + { default = modules-without-default.${overrides.default}; } + else + { }; + overrides-without-default = builtins.removeAttrs overrides [ "default" ]; + modules = modules-without-default // default-module // overrides-without-default; + in + modules; + }; +} diff --git a/lib/overlay/default.nix b/lib/overlay/default.nix new file mode 100644 index 0000000..384769b --- /dev/null +++ b/lib/overlay/default.nix @@ -0,0 +1,43 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (core-inputs.nixpkgs.lib) assertMsg; + + user-overlays-root = snowfall-lib.fs.get-file "overlays"; +in +{ + overlay = { + # Create a flake-utils-plus overlays builder. + # Type: Attrs -> Attrs -> [(a -> b -> c)] + # Usage: create-overlays { src = ./my-overlays; overlay-package-namespace = "my-packages"; } + # result: (channels: [ ... ]) + create-overlays = + { src ? user-overlays-root + , overlay-package-namespace ? null + , extra-overlays ? [ ] + }: channels: + let + user-overlays = snowfall-lib.fs.get-default-nix-files-recursive src; + create-overlay = overlay: import overlay (user-inputs // { inherit channels; }); + user-packages-overlay = final: prev: + let + user-packages = snowfall-lib.package.create-packages { + channels = channels; + }; + user-packages-without-default = builtins.removeAttrs + (user-packages) [ "default" ]; + in + if overlay-package-namespace == null then + user-packages-without-default + else + { + ${overlay-package-namespace} = user-packages-without-default; + }; + overlays = [ user-packages-overlay ] ++ extra-overlays ++ (builtins.map create-overlay user-overlays); + in + overlays; + }; +} diff --git a/lib/package/default.nix b/lib/package/default.nix new file mode 100644 index 0000000..4dc7f11 --- /dev/null +++ b/lib/package/default.nix @@ -0,0 +1,51 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (core-inputs.nixpkgs.lib) assertMsg foldl; + + user-packages-root = snowfall-lib.fs.get-file "packages"; +in +{ + package = { + # Create flake output packages. + # Type: Attrs -> Attrs + # Usage: create-packages { inherit channels; src = ./my-packages; overrides = { inherit another-package; default = "my-package"; }; } + # result: { another-package = ...; my-package = ...; default = ...; } + create-packages = + { channels + , src ? user-packages-root + , overrides ? { } + }: + let + user-packages = snowfall-lib.fs.get-default-nix-files-recursive src; + create-package-metadata = package: { + name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory package); + drv = channels.nixpkgs.callPackage package { + inherit channels; + lib = snowfall-lib.internal.system-lib; + }; + }; + packages-metadata = builtins.map create-package-metadata user-packages; + merge-packages = packages: metadata: + packages // { + ${metadata.name} = metadata.drv; + }; + packages-without-default = foldl merge-packages { } packages-metadata; + default-package = + if overrides.default or null == null then + { } + else if builtins.isAttrs overrides.default then + { default = overrides.default; } + else if packages-without-default.${overrides.default} or null != null then + { default = packages-without-default.${overrides.default}; } + else + { }; + overrides-without-default = builtins.removeAttrs overrides [ "default" ]; + packages = packages-without-default // default-package // overrides-without-default; + in + packages; + }; +} diff --git a/lib/path/default.nix b/lib/path/default.nix new file mode 100644 index 0000000..216f194 --- /dev/null +++ b/lib/path/default.nix @@ -0,0 +1,77 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (builtins) toString baseNameOf dirOf concatStringsSep; + inherit (core-inputs.nixpkgs.lib) assertMsg last init; + + file-name-regex = "(.*)\\.(.*)$"; +in +{ + path = rec { + # Split a file name and its extension. + # Type: String -> [String] + # Usage: split-file-extension "my-file.md" + # result: [ "my-file" "md" ] + split-file-extension = file: + let + match = builtins.match file-name-regex file; + in + assert assertMsg (match != null) "lib.snowfall.split-file-extension: File must have an extension to split."; + match; + + # Check if a file name has a file extension. + # Type: String -> Bool + # Usage: has-any-file-extension "my-file.txt" + # result: true + has-any-file-extension = file: + let + match = builtins.match file-name-regex (toString file); + in + match != null; + + # Get the file extension of a file name. + # Type: String -> String + # Usage: get-file-extension "my-file.final.txt" + # result: "txt" + get-file-extension = file: + if has-any-file-extension file then + let + match = builtins.match file-name-regex (toString file); + in + last match + else + ""; + + # Check if a file name has a specific file extension. + # Type: String -> String -> Bool + # Usage: has-file-extension "txt" "my-file.txt" + # result: true + has-file-extension = extension: file: + if has-any-file-extension file then + extension == get-file-extension file + else + false; + + # Get the parent directory for a given path. + # Type: Path -> Path + # Usage: get-parent-directory "/a/b/c" + # result: "/a/b" + get-parent-directory = snowfall-lib.fp.compose baseNameOf dirOf; + + # Get the file name of a path without its extension. + # Type: Path -> String + # Usage: get-file-name-without-extension ./some-directory/my-file.pdf + # result: "my-file" + get-file-name-without-extension = path: + let + file-name = baseNameOf path; + in + if has-any-file-extension file-name then + concatStringsSep "" (init (split-file-extension file-name)) + else + file-name; + }; +} diff --git a/lib/shell/default.nix b/lib/shell/default.nix new file mode 100644 index 0000000..bbf37b1 --- /dev/null +++ b/lib/shell/default.nix @@ -0,0 +1,51 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (core-inputs.nixpkgs.lib) assertMsg foldl; + + user-shells-root = snowfall-lib.fs.get-file "shells"; +in +{ + shell = { + # Create flake output packages. + # Type: Attrs -> Attrs + # Usage: create-shells { inherit channels; src = ./my-shells; overrides = { inherit another-shell; default = "my-shell"; }; } + # result: { another-shell = ...; my-shell = ...; default = ...; } + create-shells = + { channels + , src ? user-shells-root + , overrides ? { } + }: + let + user-shells = snowfall-lib.fs.get-default-nix-files-recursive src; + create-shell-metadata = shell: + { + name = builtins.unsafeDiscardStringContext (snowfall-lib.path.get-parent-directory shell); + drv = channels.nixpkgs.callPackage shell { + lib = snowfall-lib.internal.system-lib; + }; + }; + shells-metadata = builtins.map create-shell-metadata user-shells; + merge-shells = shells: metadata: + shells // { + ${metadata.name} = metadata.drv; + }; + shells-without-default = foldl merge-shells { } shells-metadata; + default-shell = + if overrides.default or null == null then + { } + else if builtins.isAttrs overrides.default then + { default = overrides.default; } + else if shells-without-default.${overrides.default} or null != null then + { default = shells-without-default.${overrides.default}; } + else + { }; + overrides-without-default = builtins.removeAttrs overrides [ "default" ]; + shells = shells-without-default // default-shell // overrides-without-default; + in + shells; + }; +} diff --git a/lib/system/default.nix b/lib/system/default.nix new file mode 100644 index 0000000..7959178 --- /dev/null +++ b/lib/system/default.nix @@ -0,0 +1,201 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (builtins) dirOf baseNameOf; + inherit (core-inputs.nixpkgs.lib) assertMsg fix hasInfix concatMap foldl; + + virtual-systems = import ./virtual-systems.nix; + + user-systems-root = snowfall-lib.fs.get-file "systems"; + user-modules-root = snowfall-lib.fs.get-file "modules"; + + get-inferred-system-name = path: + if snowfall-lib.path.has-file-extension "nix" path then + snowfall-lib.path.get-parent-directory path + else + baseNameOf path; +in +{ + system = rec { + # Check whether a named system is macOS. + # Type: String -> Bool + # Usage: is-darwin "x86_64-linux" + # result: false + is-darwin = hasInfix "darwin"; + + # Check whether a named system is Linux. + # Type: String -> Bool + # Usage: is-linux "x86_64-linux" + # result: false + is-linux = hasInfix "linux"; + + # Get the virtual system type of a system target. + # Type: String -> String + # Usage: get-virtual-system-type "x86_64-iso" + # result: "iso" + get-virtual-system-type = target: + foldl + (result: virtual-system: + if result == "" && hasInfix virtual-system target then + virtual-system + else + result + ) + "" + virtual-systems; + + # Get structured data about all systems for a given target. + # Type: String -> [Attrs] + # Usage: get-target-systems-metadata "x86_64-linux" + # result: [ { target = "x86_64-linux"; name = "my-machine"; path = "/systems/x86_64-linux/my-machine"; } ] + get-target-systems-metadata = target: + let + systems = snowfall-lib.fs.get-directories target; + existing-systems = builtins.filter (system: builtins.pathExists "${system}/default.nix") systems; + create-system-metadata = path: { + path = "${path}/default.nix"; + # We are building flake outputs based on file contents. Nix doesn't like this + # so we have to explicitly discard the string's path context to allow us to + # use the name as a variable. + name = builtins.unsafeDiscardStringContext (builtins.baseNameOf path); + # We are building flake outputs based on file contents. Nix doesn't like this + # so we have to explicitly discard the string's path context to allow us to + # use the name as a variable. + target = builtins.unsafeDiscardStringContext (builtins.baseNameOf target); + }; + system-configurations = builtins.map create-system-metadata existing-systems; + in + system-configurations; + + # Get the system builder for a given target. + # Type: String -> Function + # Usage: get-system-builder "x86_64-iso" + # result: (args: ) + get-system-builder = target: + let + virtual-system-type = get-virtual-system-type target; + virtual-system-builder = args: + assert (assertMsg (user-inputs ? nixos-generators) "In order to create virtual systems, you must include `nixos-generators` as a flake input."); + user-inputs.nixos-generators.nixosGenerate + (args // { + format = virtual-system-type; + specialArgs = args.specialArgs // { + format = virtual-system-type; + }; + }); + darwin-system-builder = args: + assert (assertMsg (user-inputs ? darwin) "In order to create virtual systems, you must include `darwin` as a flake input."); + user-inputs.darwin.lib.darwinSystem ((builtins.removeAttrs args [ "system" ]) // { + specialArgs = args.specialArgs // { + format = "darwin"; + }; + }); + linux-system-builder = args: + core-inputs.nixpkgs.lib.nixosSystem + (args // { + specialArgs = args.specialArgs // { + format = "linux"; + }; + }); + in + if virtual-system-type != "" then + virtual-system-builder + else if is-darwin target then + darwin-system-builder + else + linux-system-builder; + + # Get the flake output attribute for a system target. + # Type: String -> String + # Usage: get-system-output "aarch64-darwin" + # result: "darwinConfigurations" + get-system-output = target: + let + virtual-system-type = get-virtual-system-type target; + in + if virtual-system-type != "" then + "${virtual-system-type}Configurations" + else if is-darwin target then + "darwinConfigurations" + else + "nixosConfigurations"; + + # Get the resolved (non-virtual) system target. + # Type: String -> String + # Usage: get-resolved-system-target "x86_64-iso" + # result: "x86_64-linux" + get-resolved-system-target = target: + let + virtual-system-type = get-virtual-system-type target; + in + if virtual-system-type != "" then + builtins.replaceStrings [ virtual-system-type ] [ "linux" ] target + else + target; + + # Create a system. + # Type: Attrs -> Attrs + # Usage: create-system { path = ./systems/my-system; } + # result: + create-system = + { target ? "x86_64-linux" + , system ? get-resolved-system-target target + , path + , name ? builtins.unsafeDiscardStringContext (get-inferred-system-name path) + , modules ? [ ] + , specialArgs ? { } + , channelName ? "nixpkgs" + , builder ? get-system-builder target + , output ? get-system-output target + , systems ? { } + }: + let + lib = snowfall-lib.internal.system-lib; + in + # (lib.traceSeqN 1 path) + { + inherit channelName system builder output; + + modules = [ path ] ++ modules; + + specialArgs = specialArgs // { + inherit system name systems lib; + + inputs = snowfall-lib.flake.without-src user-inputs; + }; + }; + + # Create all available systems. + # Type: Attrs -> Attrs + # Usage: create-systems { hosts.my-host.specialArgs.x = true; modules = [ my-shared-module ]; } + # result: { my-host = ; } + create-systems = systems: + let + targets = snowfall-lib.fs.get-directories user-systems-root; + target-systems-metadata = concatMap get-target-systems-metadata targets; + user-modules = snowfall-lib.fs.get-default-nix-files-recursive user-modules-root; + create-system' = created-systems: system-metadata: + let + overrides = systems.hosts.${system-metadata.name} or { }; + in + { + ${system-metadata.name} = create-system (overrides // system-metadata // { + systems = created-systems; + modules = user-modules ++ (overrides.modules or [ ]) ++ (systems.modules or [ ]); + }); + }; + created-systems = fix (created-systems: + foldl + (systems: system-metadata: + systems // (create-system' created-systems system-metadata) + ) + { } + target-systems-metadata + ); + in + created-systems; + }; +} diff --git a/lib/system/virtual-systems.nix b/lib/system/virtual-systems.nix new file mode 100644 index 0000000..dac5d25 --- /dev/null +++ b/lib/system/virtual-systems.nix @@ -0,0 +1,34 @@ +# @NOTE(jakehamilton): The order of these entries matters. We search them +# from start to finish and only match based on whether they appear in a +# system target. This means that entries like "vm" would match all cases +# of "vm-bootloader", "vm-no-gui", and "vmware". To avoid this mismatch, +# entries should be ordered from most-specific to least-specific. +[ + "amazon" + "azure" + "cloudstack" + "do" + "gce" + "hyperv" + "install-iso-hyperv" + "install-iso" + "iso" + "kexec" + "kexec-bundle" + "kubevirt" + "lxc-metadata" + "lxc" + "openstack" + "proxmox" + "qcow" + "raw" + "raw-efi" + "sd-aarch64-installer" + "sd-aarch64" + "vagrant-virtualbox" + "virtualbox" + "vm-bootloader" + "vm-nogui" + "vmware" + "vm" +] diff --git a/lib/template/default.nix b/lib/template/default.nix new file mode 100644 index 0000000..6f2a47b --- /dev/null +++ b/lib/template/default.nix @@ -0,0 +1,50 @@ +{ core-inputs +, user-inputs +, snowfall-lib +}: + +let + inherit (builtins) baseNameOf; + inherit (core-inputs.nixpkgs.lib) assertMsg foldl; + + user-templates-root = snowfall-lib.fs.get-file "templates"; +in +{ + template = { + # Create flake templates. + # Type: Attrs -> Attrs + # Usage: create-templates { src = ./my-templates; overrides = { inherit another-template; default = "my-template"; }; } + # result: { another-template = ...; my-template = ...; default = ...; } + create-templates = + { src ? user-templates-root + , overrides ? { } + }: + let + user-templates = snowfall-lib.fs.get-directories src; + create-template-metadata = template: { + name = builtins.unsafeDiscardStringContext (baseNameOf template); + path = template; + }; + templates-metadata = builtins.map create-template-metadata user-templates; + merge-templates = templates: metadata: + templates // { + ${metadata.name} = (overrides.${metadata.name} or { }) // { + inherit (metadata) path; + }; + }; + templates-without-default = foldl merge-templates { } templates-metadata; + default-template = + if overrides.default or null == null then + { } + else if builtins.isAttrs overrides.default then + { default = overrides.default; } + else if templates-without-default.${overrides.default} or null != null then + { default = templates-without-default.${overrides.default}; } + else + { }; + overrides-without-default = builtins.removeAttrs overrides [ "default" ]; + templates = templates-without-default // default-template // overrides-without-default; + in + templates; + }; +}