diff --git a/16-formatter-and-checks/README.md b/16-formatter-and-checks/README.md new file mode 100644 index 0000000..9f4e21d --- /dev/null +++ b/16-formatter-and-checks/README.md @@ -0,0 +1,16 @@ +# 16-formatter-and-checks + +This example shows `formatter.` and `checks..*`. + +It includes: + +- a `formatter` output pointing at `nixfmt`, and +- a check that uses that formatter on a file in the example. + +Useful commands: + +```bash +nix fmt sample.nix +nix flake show +nix flake check +``` diff --git a/16-formatter-and-checks/flake.lock b/16-formatter-and-checks/flake.lock new file mode 100644 index 0000000..dfdfdf9 --- /dev/null +++ b/16-formatter-and-checks/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/16-formatter-and-checks/flake.nix b/16-formatter-and-checks/flake.nix new file mode 100644 index 0000000..7fee7b7 --- /dev/null +++ b/16-formatter-and-checks/flake.nix @@ -0,0 +1,26 @@ +{ + # Exposes a formatter output and a check that uses the same formatter on + # a file in this example directory. + description = "A minimal formatter and checks example"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { nixpkgs, ... }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + in + { + formatter.${system} = pkgs.nixfmt; + + checks.${system}.formats = pkgs.runCommand "formatter-check" { } '' + cp ${./sample.nix} ./sample.nix + ${pkgs.nixfmt}/bin/nixfmt ./sample.nix >/dev/null + grep -q 'greeting = "hello";' ./sample.nix + echo ok > "$out" + ''; + }; +} diff --git a/16-formatter-and-checks/sample.nix b/16-formatter-and-checks/sample.nix new file mode 100644 index 0000000..2d5e513 --- /dev/null +++ b/16-formatter-and-checks/sample.nix @@ -0,0 +1,3 @@ +{ + greeting = "hello"; +} diff --git a/17-package-from-source/README.md b/17-package-from-source/README.md new file mode 100644 index 0000000..f43b03c --- /dev/null +++ b/17-package-from-source/README.md @@ -0,0 +1,18 @@ +# 17-package-from-source + +This example shows packaging a selected local source tree. + +It includes: + +- a derivation built from a local source tree, +- `lib.fileset.toSource` to include only selected files, and +- a check that proves excluded files are not in the packaged source. + +Useful commands: + +```bash +nix build +./result/bin/source-greet +nix run +nix flake check +``` diff --git a/17-package-from-source/flake.lock b/17-package-from-source/flake.lock new file mode 100644 index 0000000..dfdfdf9 --- /dev/null +++ b/17-package-from-source/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/17-package-from-source/flake.nix b/17-package-from-source/flake.nix new file mode 100644 index 0000000..833e1b8 --- /dev/null +++ b/17-package-from-source/flake.nix @@ -0,0 +1,62 @@ +{ + # Packages a selected local source tree so the derivation only sees the + # files that the example deliberately includes. + description = "Package a local source tree with fileset"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { self, nixpkgs, ... }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + sourceFiles = pkgs.lib.fileset.unions [ + ./src/message.txt + ./src/run.sh + ]; + selectedSource = pkgs.lib.fileset.toSource { + root = ./.; + fileset = sourceFiles; + }; + in + { + packages.${system}.default = pkgs.stdenv.mkDerivation { + pname = "source-greet"; + version = "0.1.0"; + src = selectedSource; + dontBuild = true; + dontUnpack = true; + + installPhase = '' + mkdir -p $out/bin $out/share/source-greet + cp $src/src/message.txt $out/share/source-greet/message.txt + cp $src/src/run.sh $out/bin/source-greet + chmod +x $out/bin/source-greet + + substituteInPlace $out/bin/source-greet \ + --replace-fail "@bash@" "${pkgs.bash}/bin/bash" \ + --replace-fail "@message@" "$out/share/source-greet/message.txt" + ''; + }; + + apps.${system}.default = { + type = "app"; + program = "${self.packages.${system}.default}/bin/source-greet"; + meta.description = "Run the package built from the selected local source tree."; + }; + + checks.${system}.selected-source = pkgs.runCommand "selected-source-check" { } '' + [ ! -e ${selectedSource}/ignored.txt ] + output="$(${self.packages.${system}.default}/bin/source-greet)" + + if [ "$output" = "hello from the selected source tree" ]; then + echo ok > "$out" + else + echo "unexpected output: $output" >&2 + exit 1 + fi + ''; + }; +} diff --git a/17-package-from-source/ignored.txt b/17-package-from-source/ignored.txt new file mode 100644 index 0000000..38ab5fb --- /dev/null +++ b/17-package-from-source/ignored.txt @@ -0,0 +1 @@ +this file is intentionally excluded from the package source diff --git a/17-package-from-source/src/message.txt b/17-package-from-source/src/message.txt new file mode 100644 index 0000000..2ea19ee --- /dev/null +++ b/17-package-from-source/src/message.txt @@ -0,0 +1 @@ +hello from the selected source tree diff --git a/17-package-from-source/src/run.sh b/17-package-from-source/src/run.sh new file mode 100644 index 0000000..a2d0cd8 --- /dev/null +++ b/17-package-from-source/src/run.sh @@ -0,0 +1,2 @@ +#!@bash@ +cat @message@ diff --git a/18-home-manager-module/README.md b/18-home-manager-module/README.md new file mode 100644 index 0000000..8e57823 --- /dev/null +++ b/18-home-manager-module/README.md @@ -0,0 +1,17 @@ +# 18-home-manager-module + +This example shows a small reusable Home Manager module. + +It includes: + +- a module stored in `module.nix`, +- one option namespace, +- one config effect, and +- a check that evaluates a throwaway Home Manager configuration. + +Useful commands: + +```bash +nix flake show +nix flake check +``` diff --git a/18-home-manager-module/flake.lock b/18-home-manager-module/flake.lock new file mode 100644 index 0000000..9570385 --- /dev/null +++ b/18-home-manager-module/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1776950293, + "narHash": "sha256-t6KMARLILjPuTBSRoYanUxV+FU50IFZ7L5XVdOcdtaY=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "6837e0d6c5eda81fd26308489799fbf83a160465", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "home-manager": "home-manager", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/18-home-manager-module/flake.nix b/18-home-manager-module/flake.nix new file mode 100644 index 0000000..a586766 --- /dev/null +++ b/18-home-manager-module/flake.nix @@ -0,0 +1,59 @@ +{ + # Evaluates a minimal Home Manager module from `module.nix` and verifies + # its effect through a throwaway Home Manager configuration. + description = "A minimal Home Manager module"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + nixpkgs, + home-manager, + ... + }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + + testConfig = home-manager.lib.homeManagerConfiguration { + inherit pkgs; + modules = [ + ./module.nix + { + home = { + username = "learner"; + homeDirectory = "/home/learner"; + stateVersion = "24.11"; + }; + playground.welcome = { + enable = true; + name = "flakes"; + }; + } + ]; + }; + in + { + checks.${system}.welcome = + pkgs.runCommand "home-manager-welcome-check" + { + got = testConfig.config.home.file."welcome.txt".text; + expected = "hello, flakes"; + } + '' + if [ "$got" = "$expected" ]; then + echo ok > "$out" + else + echo "unexpected welcome text: $got" >&2 + exit 1 + fi + ''; + }; +} diff --git a/18-home-manager-module/module.nix b/18-home-manager-module/module.nix new file mode 100644 index 0000000..9910785 --- /dev/null +++ b/18-home-manager-module/module.nix @@ -0,0 +1,20 @@ +{ lib, config, ... }: + +let + cfg = config.playground.welcome; +in +{ + options.playground.welcome = { + enable = lib.mkEnableOption "the playground Home Manager welcome file"; + + name = lib.mkOption { + type = lib.types.str; + default = "world"; + description = "Name placed in the generated welcome file."; + }; + }; + + config = lib.mkIf cfg.enable { + home.file."welcome.txt".text = "hello, ${cfg.name}"; + }; +} diff --git a/19-devshell-vs-shellfor/README.md b/19-devshell-vs-shellfor/README.md new file mode 100644 index 0000000..dcf7322 --- /dev/null +++ b/19-devshell-vs-shellfor/README.md @@ -0,0 +1,20 @@ +# 19-devshell-vs-shellfor + +This example compares two dev shell styles for the same local package. + +It includes: + +- a generic `mkShell` shell, +- a Haskell-specific `shellFor` shell, +- one local package that both shells support, and +- a test suite run by `nix flake check`. + +Useful commands: + +```bash +nix develop .#generic +nix develop .#shellFor +nix build +./result/bin/mini-shell-choice +nix flake check +``` diff --git a/19-devshell-vs-shellfor/app/Main.hs b/19-devshell-vs-shellfor/app/Main.hs new file mode 100644 index 0000000..5fef45c --- /dev/null +++ b/19-devshell-vs-shellfor/app/Main.hs @@ -0,0 +1,6 @@ +module Main where + +import MiniShellChoice.Message (message) + +main :: IO () +main = putStrLn message diff --git a/19-devshell-vs-shellfor/flake.lock b/19-devshell-vs-shellfor/flake.lock new file mode 100644 index 0000000..dfdfdf9 --- /dev/null +++ b/19-devshell-vs-shellfor/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/19-devshell-vs-shellfor/flake.nix b/19-devshell-vs-shellfor/flake.nix new file mode 100644 index 0000000..30bdffa --- /dev/null +++ b/19-devshell-vs-shellfor/flake.nix @@ -0,0 +1,54 @@ +{ + # Compares a generic dev shell with a Haskell-specific `shellFor` shell + # while building the same local package in both cases. + description = "Compare mkShell and shellFor"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { self, nixpkgs, ... }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + haskellPackages = pkgs.haskellPackages.override { + overrides = final: _: { + mini-shell-choice = final.callCabal2nix "mini-shell-choice" ./. { }; + }; + }; + project = haskellPackages.mini-shell-choice; + checkedProject = pkgs.haskell.lib.doCheck project; + genericShell = pkgs.mkShell { + packages = [ + haskellPackages.ghc + pkgs.cabal-install + pkgs.haskell-language-server + ]; + }; + shellForShell = haskellPackages.shellFor { + packages = hp: [ hp.mini-shell-choice ]; + nativeBuildInputs = [ + pkgs.cabal-install + pkgs.haskell-language-server + ]; + }; + in + { + packages.${system}.default = project; + + apps.${system}.default = { + type = "app"; + program = "${self.packages.${system}.default}/bin/mini-shell-choice"; + meta.description = "Run the package used by both shell styles."; + }; + + devShells.${system} = { + default = genericShell; + generic = genericShell; + shellFor = shellForShell; + }; + + checks.${system}.test-suite = checkedProject; + }; +} diff --git a/19-devshell-vs-shellfor/mini-shell-choice.cabal b/19-devshell-vs-shellfor/mini-shell-choice.cabal new file mode 100644 index 0000000..13ed1a4 --- /dev/null +++ b/19-devshell-vs-shellfor/mini-shell-choice.cabal @@ -0,0 +1,27 @@ +cabal-version: 2.4 +name: mini-shell-choice +version: 0.1.0.0 +build-type: Simple + +library + exposed-modules: MiniShellChoice.Message + hs-source-dirs: src + build-depends: base >=4.14 && <5 + default-language: Haskell2010 + +executable mini-shell-choice + main-is: Main.hs + hs-source-dirs: app + build-depends: + base >=4.14 && <5, + mini-shell-choice + default-language: Haskell2010 + +test-suite mini-shell-choice-test + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: test + build-depends: + base >=4.14 && <5, + mini-shell-choice + default-language: Haskell2010 diff --git a/19-devshell-vs-shellfor/src/MiniShellChoice/Message.hs b/19-devshell-vs-shellfor/src/MiniShellChoice/Message.hs new file mode 100644 index 0000000..2c04e4b --- /dev/null +++ b/19-devshell-vs-shellfor/src/MiniShellChoice/Message.hs @@ -0,0 +1,4 @@ +module MiniShellChoice.Message where + +message :: String +message = "compare the generic shell and the shellFor shell" diff --git a/19-devshell-vs-shellfor/test/Main.hs b/19-devshell-vs-shellfor/test/Main.hs new file mode 100644 index 0000000..0348839 --- /dev/null +++ b/19-devshell-vs-shellfor/test/Main.hs @@ -0,0 +1,11 @@ +module Main where + +import MiniShellChoice.Message (message) +import System.Exit (die) + +main :: IO () +main = + if message == "compare the generic shell and the shellFor shell" then + putStrLn "test passed" + else + die "unexpected message" diff --git a/notes/019-formatter-and-checks.md b/notes/019-formatter-and-checks.md new file mode 100644 index 0000000..01508e4 --- /dev/null +++ b/notes/019-formatter-and-checks.md @@ -0,0 +1,38 @@ +# Formatter and Checks + +This note covers `16-formatter-and-checks/`, which exposes `formatter.` and a small `checks..*` derivation that uses the same formatter. + +--- + +## 1. Why These Outputs Matter + +Two flake outputs are especially practical in day-to-day use: + +- `formatter.`, which powers `nix fmt`, and +- `checks..*`, which powers `nix flake check`. + +This example keeps both outputs small so the wiring is obvious. + +--- + +## 2. The Important Connection + +The flake points `formatter.` at `pkgs.nixfmt`, then reuses that same formatter in a check: + +```nix +formatter.${system} = pkgs.nixfmt; +``` + +That shows the main idea: a formatter output is just another derivation, so checks can use it too. + +--- + +## 3. Commands to Try + +```bash +cd 16-formatter-and-checks + +nix fmt sample.nix +nix flake show +nix flake check +``` diff --git a/notes/020-package-from-source.md b/notes/020-package-from-source.md new file mode 100644 index 0000000..c5251bc --- /dev/null +++ b/notes/020-package-from-source.md @@ -0,0 +1,41 @@ +# Package from Source + +This note covers `17-package-from-source/`, which packages a selected local source tree and excludes unrelated files from the build input. + +--- + +## 1. Why Source Selection Matters + +`src = ./.;` copies the whole example directory into the store. That is fine for small demos, but real packages often need a narrower source tree. + +This example uses `lib.fileset.toSource` so the derivation only sees: + +- `src/run.sh`, and +- `src/message.txt`. + +That leaves `ignored.txt` out of the packaged source on purpose. + +--- + +## 2. Why the Check Looks at the Source Path + +The check does two things: + +- it runs the packaged binary, and +- it asserts that the generated source path does not contain `ignored.txt`. + +That keeps the example focused on the real behavior being taught: selecting build inputs, not just producing a runnable script. + +--- + +## 3. Commands to Try + +```bash +cd 17-package-from-source + +nix build +./result/bin/source-greet + +nix run +nix flake check +``` diff --git a/notes/021-home-manager-modules.md b/notes/021-home-manager-modules.md new file mode 100644 index 0000000..ce6dc97 --- /dev/null +++ b/notes/021-home-manager-modules.md @@ -0,0 +1,40 @@ +# Home Manager Modules + +This note covers `18-home-manager-module/`, which defines a small reusable Home Manager module in `module.nix` and verifies it by evaluating a throwaway Home Manager configuration. + +--- + +## 1. What This Example Mirrors + +This example is the Home Manager counterpart to `04-nixos-module/`. + +It has the same shape: + +- one option namespace, +- one configuration effect, and +- one check that evaluates a temporary configuration. + +The important difference is the target system. Home Manager modules work on `home.*` options rather than `services.*`, `environment.*`, or other NixOS module options. + +--- + +## 2. Why the Check Reads a Config Value + +The flake does not need to switch a real Home Manager configuration. It only needs to prove that the module evaluates and contributes the expected value. + +That is why the check reads: + +- `testConfig.config.home.file."welcome.txt".text` + +instead of trying to activate a real profile. + +--- + +## 3. Commands to Try + +```bash +cd 18-home-manager-module + +nix flake show +nix flake check +``` diff --git a/notes/022-devshell-vs-shellfor.md b/notes/022-devshell-vs-shellfor.md new file mode 100644 index 0000000..07c612c --- /dev/null +++ b/notes/022-devshell-vs-shellfor.md @@ -0,0 +1,40 @@ +# Dev Shell versus shellFor + +This note covers `19-devshell-vs-shellfor/`, which builds one local package and exposes two dev shells around it. + +--- + +## 1. What Is Being Compared + +The example defines: + +- `devShells..generic`, built with `pkgs.mkShell`, and +- `devShells..shellFor`, built with `haskellPackages.shellFor`. + +Both shells support the same local package, but they are assembled differently. + +--- + +## 2. Why the Difference Matters + +`mkShell` is generic. You list tools directly. + +`shellFor` is package-set-aware. It starts from Haskell packages and builds a shell around their dependencies. + +That makes `shellFor` more tightly coupled to the package graph, while `mkShell` stays more explicit and general-purpose. + +--- + +## 3. Commands to Try + +```bash +cd 19-devshell-vs-shellfor + +nix develop .#generic +nix develop .#shellFor + +nix build +./result/bin/mini-shell-choice + +nix flake check +```