Personal Monorepo ❄️
1# rust-nixpkgs/lib.nix
2#
3# Helper functions for assembling a Rust-based stdenv from individual
4# component replacements. Each "component" is a small attribute set
5# describing the original C package it replaces, the Rust replacement
6# package (or null when not yet available), and metadata used for
7# status reporting.
8#
9# Design principles:
10# - Each component is declared in components/*.nix as a function
11# taking `pkgs` and returning the component attrset.
12# - Components whose `replacement` is null are silently skipped
13# when assembling the stdenv — this lets us declare the full
14# target map up front and fill in replacements incrementally.
15# - Replacement packages must be flag-compatible drop-ins for the
16# originals. Wrappers are acceptable when needed for
17# binary name compatibility.
18{lib}: let
19 # Status values used in component declarations.
20 # available — a working Rust replacement exists and is wired in
21 # in-progress — a Rust rewrite is underway (in this repo or upstream)
22 # planned — replacement is on the roadmap but not started
23 status = {
24 available = "available";
25 inProgress = "in-progress";
26 planned = "planned";
27 };
28
29 # Source values describing where the replacement comes from.
30 # nixpkgs — already packaged in nixpkgs (e.g. uutils, ripgrep)
31 # repo — a sibling subproject at the monorepo root (e.g. ../make)
32 # upstream — an upstream Rust project not yet in nixpkgs
33 # internal — will be developed inside rust-nixpkgs/crates
34 source = {
35 nixpkgs = "nixpkgs";
36 repo = "repo";
37 upstream = "upstream";
38 internal = "internal";
39 };
40
41 # mkComponent — canonical constructor for a component declaration.
42 #
43 # mkComponent {
44 # name = "coreutils";
45 # original = pkgs.coreutils;
46 # replacement = pkgs.uutils-coreutils-noprefix;
47 # status = status.available;
48 # source = source.nixpkgs;
49 # phase = 1;
50 # description = "Core file/text/shell utilities";
51 # notes = "Using uutils — drop-in noprefix variant";
52 # }
53 #
54 mkComponent = {
55 name,
56 original,
57 replacement ? null,
58 status ? "planned",
59 source ? "internal",
60 phase ? 0,
61 description ? "",
62 notes ? "",
63 }: {
64 inherit
65 name
66 original
67 replacement
68 status
69 source
70 phase
71 description
72 notes
73 ;
74 isAvailable = replacement != null;
75 };
76
77 # loadComponents — import every components/*.nix file and call each
78 # with `pkgs`, returning an attrset keyed by component name.
79 loadComponents = pkgs: let
80 dir = ./components;
81 entries = lib.readDir dir;
82 nixFiles =
83 lib.filterAttrs
84 (n: t: t == "regular" && lib.hasSuffix ".nix" n)
85 entries;
86 load = filename: _: let
87 component = import (dir + "/${filename}") {inherit pkgs lib mkComponent status source;};
88 in {
89 inherit (component) name;
90 value = component;
91 };
92 in
93 lib.listToAttrs (lib.mapAttrsToList load nixFiles);
94
95 # availableComponents — filter to only components with a non-null
96 # replacement.
97 availableComponents = components:
98 lib.filterAttrs (_: c: c.isAvailable) components;
99
100 # componentsByPhase — group components by their phase number.
101 componentsByPhase = components: let
102 phaseNums = lib.unique (lib.mapAttrsToList (_: c: c.phase) components);
103 grouped =
104 map (
105 p: {
106 name = toString p;
107 value = lib.filterAttrs (_: c: c.phase == p) components;
108 }
109 )
110 phaseNums;
111 in
112 lib.listToAttrs grouped;
113
114 # mkReplacements — produce a list of { original, replacement } attrsets
115 # suitable for `system.replaceDependencies.replacements` or for
116 # iterating when building a custom stdenv. Only includes components
117 # whose replacement is non-null.
118 mkReplacements = components:
119 lib.mapAttrsToList
120 (_: c: {
121 inherit (c) original;
122 inherit (c) replacement;
123 })
124 (availableComponents components);
125
126 # overrideInitialPath — given a stdenv and a components attrset,
127 # produce a new initialPath where every original package that has
128 # a Rust replacement is swapped out.
129 overrideInitialPath = stdenv: components: let
130 available = availableComponents components;
131 replacementMap = lib.listToAttrs (
132 lib.mapAttrsToList
133 (_: c: {
134 name = lib.unsafeDiscardStringContext (toString c.original);
135 value = c.replacement;
136 })
137 available
138 );
139 swapPkg = pkg: let
140 key = lib.unsafeDiscardStringContext (toString pkg);
141 in
142 replacementMap.${key} or pkg;
143 in
144 map swapPkg stdenv.initialPath;
145
146 # mkRustStdenv — create an overridden stdenv with available Rust
147 # replacements swapped into the initial path, and optionally the
148 # shell replaced.
149 #
150 # This is the main entry point for Phase 7 (full oxidized stdenv).
151 # Earlier phases use individual component replacements or targeted
152 # overlays instead.
153 mkRustStdenv = {
154 stdenv,
155 components,
156 replaceShell ? false,
157 }: let
158 available = availableComponents components;
159 newInitialPath = overrideInitialPath stdenv components;
160 shellComponent = available.shell or null;
161 newShell =
162 if replaceShell && shellComponent != null
163 then "${shellComponent.replacement}/bin/bash"
164 else stdenv.shell;
165 in
166 stdenv.override {
167 initialPath = newInitialPath;
168 shell = newShell;
169 };
170
171 # statusReport — produce a human-readable markdown table of all
172 # components and their replacement status.
173 statusReport = components: let
174 header = "| Component | Phase | Status | Source | Notes |\n|-----------|-------|--------|--------|-------|\n";
175 rows = lib.mapAttrsToList (
176 _: c: "| ${c.name} | ${toString c.phase} | ${c.status} | ${c.source} | ${c.notes} |"
177 ) (lib.attrsets.mergeAttrsList (lib.mapAttrsToList (_: phase: phase) (componentsByPhase components)));
178 in
179 header + (lib.concatStringsSep "\n" rows);
180in {
181 inherit
182 status
183 source
184 mkComponent
185 loadComponents
186 availableComponents
187 componentsByPhase
188 mkReplacements
189 overrideInitialPath
190 mkRustStdenv
191 statusReport
192 ;
193}