Merge remote-tracking branch 'upstream/main' into patch-1

This commit is contained in:
Robert Hensing 2025-11-10 22:26:48 +01:00
commit 11647f17dc
18 changed files with 604 additions and 63 deletions

View file

@ -10,6 +10,10 @@ Opinionated features are provided by an ecosystem of modules that you can import
`flake-parts` _itself_ has the goal to be a minimal mirror of the Nix flake schema.
Used by itself, it is very lightweight.
> It is definitely the best Flake framework and it is simply out of this world!
—Pol Dellaiera ([source](https://not-a-number.io/2025/refactoring-my-infrastructure-as-code-configurations/#trade-offs))
---
**Documentation**: [flake.parts](https://flake.parts)

View file

@ -15,32 +15,6 @@
"." = { };
"dev" = { };
};
effect.settings = {
# Only fetch the `lib` subtree.
# NOTE: Users don't have to do this. They are recommended to use follows
# and just use the `nixpkgs` they're already fetching anyway.
# It doesn't have to be `lib/` only!
git.update.script = lib.mkBefore ''
echo 'Fetching nixpkgs-lib tree'
branch="nixos-unstable"
mkdir ~/nixpkgs
git -C ~/nixpkgs init
git -C ~/nixpkgs remote add origin https://github.com/NixOS/nixpkgs.git
git -C ~/nixpkgs fetch origin --filter=blob:none --depth=1 "$branch"
commit="$(git -C ~/nixpkgs rev-parse FETCH_HEAD)"
tree="$(git -C ~/nixpkgs rev-parse FETCH_HEAD:lib)"
echo 'Adjusting nixpkgs-lib.url'
sed -i flake.nix -e \
's^ nixpkgs-lib\.url = ".*^ nixpkgs-lib\.url = "https://github.com/NixOS/nixpkgs/archive/'$tree'.tar.gz"; # '$commit' /lib from '$branch'^'
git diff
grep -F "$tree" flake.nix >/dev/null || {
echo 'failed to write new tree to flake.nix'
exit 1
}
git commit flake.nix -m 'flake.nix: Update nixpkgs-lib tree'
'';
};
};
perSystem = { config, pkgs, ... }: {

36
dev/flake.lock generated
View file

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
@ -24,11 +24,11 @@
]
},
"locked": {
"lastModified": 1736143030,
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
"lastModified": 1759362264,
"narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
"rev": "758cf7296bee11f1706a574c77d072b8a7baa881",
"type": "github"
},
"original": {
@ -63,11 +63,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1738237977,
"narHash": "sha256-oJN/yvRL7G0WlR/hTkQIjFbPkzCV+sFnNB/38Tb9RL4=",
"lastModified": 1761230615,
"narHash": "sha256-pLE7U5gOtlA/2wbKCsVRYf5DqMQ5TWBCrCfZGytDDeo=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "6d1b6d5d59758b4f5f05745f774fc13cdc59da43",
"rev": "7db2b867219a26781437d840ce457b75b7645154",
"type": "github"
},
"original": {
@ -78,11 +78,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1736798957,
"narHash": "sha256-qwpCtZhSsSNQtK4xYGzMiyEDhkNzOCz/Vfu4oL2ETsQ=",
"lastModified": 1760284886,
"narHash": "sha256-TK9Kr0BYBQ/1P5kAsnNQhmWWKgmZXwUQr4ZMjCzWf2c=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9abb87b552b7f55ac8916b6fc9e5cb486656a2f3",
"rev": "cf3f5c4def3c7b5f1fc012b3d839575dbe552d43",
"type": "github"
},
"original": {
@ -94,11 +94,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1738452225,
"narHash": "sha256-Qmwx3FXM0x0pdjibwTk/uRbayqDrs3EwmRJe7tQWu48=",
"lastModified": 1762038587,
"narHash": "sha256-jGt23EE/KEkLpTtFJfh9LgH2Kga13VIQUCB4ElpTFKQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6c4e0724e0a785a20679b1bca3a46bfce60f05b6",
"rev": "ac6d58bb8b60e0835eb24af588e36bedb0f492ea",
"type": "github"
},
"original": {
@ -116,11 +116,11 @@
]
},
"locked": {
"lastModified": 1737465171,
"narHash": "sha256-R10v2hoJRLq8jcL4syVFag7nIGE7m13qO48wRIukWNg=",
"lastModified": 1760663237,
"narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17",
"rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"type": "github"
},
"original": {

View file

@ -83,6 +83,18 @@ rec {
};
};
bundlersExample = mkFlake
{ inputs.self = { }; }
{
imports = [ flake-parts.flakeModules.bundlers ];
systems = [ "a" "b" ];
perSystem = { system, ... }: {
packages.hello = pkg system "hello";
bundlers.toTarball = drv: pkg system "tarball-${drv.name}";
bundlers.toAppImage = drv: pkg system "appimage-${drv.name}";
};
};
modulesFlake = mkFlake
{
inputs.self = { };
@ -180,6 +192,44 @@ rec {
};
};
/**
This one is for manual testing. Should look like:
```
nix-repl> checks.x86_64-linux.eval-tests.internals.printSystem.withSystem "foo" ({ config, ... }: null)
trace: Evaluating perSystem for foo
null
nix-repl> checks.x86_64-linux.eval-tests.internals.printSystem.withSystem "foo" ({ config, ... }: null)
null
```
*/
printSystem = mkFlake
{ inputs.self = { }; }
({ withSystem, ... }: {
systems = [ ];
perSystem = { config, system, ... }:
builtins.trace "Evaluating perSystem for ${system}" { };
flake.withSystem = withSystem;
});
dogfoodProvider = mkFlake
{ inputs.self = { }; }
({ flake-parts-lib, ... }: {
imports = [
(flake-parts-lib.importAndPublish "dogfood" { flake.marker = "dogfood"; })
];
});
dogfoodConsumer = mkFlake
{ inputs.self = { }; }
({ flake-parts-lib, ... }: {
imports = [
dogfoodProvider.modules.flake.dogfood
];
});
runTests = ok:
assert empty == {
@ -224,6 +274,9 @@ rec {
};
};
assert bundlersExample.bundlers.a.toTarball (pkg "a" "hello") == pkg "a" "tarball-hello";
assert bundlersExample.bundlers.b.toAppImage (pkg "b" "hello") == pkg "b" "appimage-hello";
# - exported package becomes part of overlay.
# - perSystem is invoked for the right system, when system is non-memoized
assert nixpkgsWithEasyOverlay.hello == pkg "x86_64-linux" "hello";
@ -277,6 +330,9 @@ rec {
];
}).config.test.option == "nixos-test";
assert dogfoodProvider.marker == "dogfood";
assert dogfoodConsumer.marker == "dogfood";
ok;
result = runTests "ok";

28
extras/bundlers.nix Normal file
View file

@ -0,0 +1,28 @@
{ lib
, flake-parts-lib
, ...
}:
let
inherit
(lib)
mkOption
types
;
inherit
(flake-parts-lib)
mkTransposedPerSystemModule
;
in
mkTransposedPerSystemModule {
name = "bundlers";
option = mkOption {
type = types.lazyAttrsOf (types.functionTo types.package);
default = { };
description = ''
An attribute set of bundlers to be used by [`nix bundle`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-bundle.html).
`nix bundle --bundler .#<name>` <derivation> will bundle <derivation> using the bundler `bundlers.<name>`.
'';
};
file = ./bundlers.nix;
}

View file

@ -85,11 +85,7 @@ let
# addressed store path is a pure input, so we have to fetch and wire it
# manually with flake-compat.
get-flake = src: (flake-compat { inherit src; system = throw "operating flake-compat in pure mode; system not allowed to be used"; }).outputs;
# TODO: update
flake-compat = import (builtins.fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/9ed2ac151eada2306ca8c418ebd97807bb08f6ac.tar.gz";
sha256 = "sha256:063slk1np1g1dkh21a82x655kpja7p4pc74rb3lqankyrbbpy4hx";
});
flake-compat = import ../vendor/flake-compat;
in
{
@ -105,6 +101,10 @@ in
The flake attributes are overridden with `lib.mkForce` priority.
See the `partitions` options to understand the purpose.
Example: `partitionedAttrs.devShells = "dev";`
Equivalent: `flake.devShells = lib.mkForce config.partitions.dev.module.flake.devShells;`
'';
example = {
"devShells" = "dev";

15
flake.lock generated
View file

@ -2,14 +2,17 @@
"nodes": {
"nixpkgs-lib": {
"locked": {
"lastModified": 1738452942,
"narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
"lastModified": 1761765539,
"narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "719359f4562934ae99f5443f20aa06c2ffff91fc",
"type": "github"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {

View file

@ -2,7 +2,7 @@
description = "Flake basics described using the module system";
inputs = {
nixpkgs-lib.url = "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"; # 3a228057f5b619feb3186e986dbe76278d707b6e /lib from nixos-unstable
nixpkgs-lib.url = "github:nix-community/nixpkgs.lib";
};
outputs = inputs@{ nixpkgs-lib, ... }:
@ -50,6 +50,7 @@
flakeModules = ./extras/flakeModules.nix;
modules = ./extras/modules.nix;
partitions = ./extras/partitions.nix;
bundlers = ./extras/bundlers.nix;
};
in
lib.mkFlake { inherit inputs; } {

24
lib.nix
View file

@ -174,7 +174,11 @@ let
options = {
flake = flake-parts-lib.mkSubmoduleOptions {
${name} = mkOption {
type = types.lazyAttrsOf option.type;
type = types.attrsWith {
elemType = option.type;
lazy = true;
placeholder = "system";
};
default = { };
description = ''
See {option}`perSystem.${name}` for description and examples.
@ -221,6 +225,24 @@ let
importApply =
modulePath: staticArgs:
lib.setDefaultModuleLocation modulePath (import modulePath staticArgs);
inherit (import ./lib/memoize/memoize.nix {
inherit lib;
}) memoizeStr;
/**
`importAndPublish name module` returns a module that both imports the `module`, and exposes it as flake attribute `modules.flake.${name}`.
This also imports the optional [`modules`](https://flake.parts/options/flake-parts-modules.html) module to support that.
*/
importAndPublish = name: module: { lib, ... }: {
_class = "flake";
imports = [
module
./extras/modules.nix
];
flake.modules.flake.${name} = module;
};
};
# A best effort, lenient estimate. Please use a recent nixpkgs lib if you

2
lib/memoize/bytes.dat Normal file
View file

@ -0,0 +1,2 @@

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€亗儎厗噲墛媽崕彁憭摂晼棙櫄洔潪煚、¥ウЖ┆<D096><E29486><EFBFBD>辈炒刀犯购患骄坷谅媚牌侨墒颂臀闲岩釉罩棕仝圮蒉哙徕沅彐玷殛腱眍镳耱篝貊鼬<E8B28A><E9BCAC><EFBFBD><EFBFBD>

View file

@ -0,0 +1,23 @@
# Run with:
# NIX_SHOW_STATS=1 nix eval --expr 'import ./measure-bytes-per-char.nix { control = false; size = 10; }' --impure
# NIX_SHOW_STATS=1 nix eval --expr 'import ./measure-bytes-per-char.nix { control = true; size = 10; }' --impure
{ control ? false, size ? 10 }:
let
lib = import <nixpkgs/lib>;
inherit (import ./memoize.nix { inherit lib; }) memoizeStr;
# Create a string of the specified size
key = lib.concatStrings (lib.genList (i: "a") size);
# Memoized identity function
memoId = memoizeStr (x: x);
# Prime the trie with a minimal query to force its construction
prime = memoId "";
in
if control
then builtins.seq prime key # Return key after priming
else builtins.seq prime (memoId key) # Pass through memoization after priming

50
lib/memoize/memoize.nix Normal file
View file

@ -0,0 +1,50 @@
{ lib, ... }:
let
keys =
let
nonNullBytesStr =
builtins.readFile ./bytes.dat;
nonNullItems =
lib.stringToCharacters nonNullBytesStr;
keysList = [ "" ] ++ nonNullItems;
byteNames = lib.genAttrs keysList (k: null);
in
byteNames;
/**
Produce an infinite trie for memoizing a function with a string input.
This uses memory in terms of a large factor of the number of unique string suffixes passed to the memoizeStr / queryTrie functions.
*/
makeTrie = prefix: f:
lib.mapAttrs
(k: v: if k == "" then f prefix else makeTrie (prefix + k) f)
keys;
queryTrie =
trie: needle:
let
needleList = lib.stringToCharacters needle;
destination = lib.foldl'
(subtrie: c: subtrie.${c})
trie
needleList;
in
destination."";
in
{
/**
Turn a function that accepts a string input into one that memoizes the results.
Make sure to partially apply it and use it over and over in e.g. the same let binding.
Otherwise, you're wasting kilobytes of memory allocations *for each letter in each call*.
That's 12+ KB per input byte on Nix 2.31, and more on older versions.
Yes, this function is surprisingly EXPENSIVE, but cheaper than e.g. reinvoking Nixpkgs.
Its memory cost is comparable to that of loading a small Nix file.
*/
memoizeStr = f:
let trie = makeTrie "" f;
in queryTrie trie;
}

10
lib/memoize/test.nix Normal file
View file

@ -0,0 +1,10 @@
# Ad hoc manual test dependent on observing side effects
let
lib = import ~/src/nixpkgs-master/lib;
inherit (import ./memoize.nix { inherit lib; }) memoizeStr;
# Don't use this in the wild, it's too expensive!
printOnce = memoizeStr (x: builtins.trace "computing f ${lib.strings.escapeNixString x}" x);
in
{
inherit printOnce memoizeStr lib;
}

View file

@ -59,6 +59,14 @@ let
}
'';
/**
We primarily use `systems` to help memoize the per system context, but that
doesn't extend to arbitrary `system`s.
For that, we use the slightly less efficient, but perfectly acceptable
`memoizeStr` function.
*/
otherMemoizedSystems = flake-parts-lib.memoizeStr config.perSystem;
in
{
options = {
@ -139,8 +147,7 @@ in
config = {
allSystems = genAttrs config.systems config.perSystem;
# TODO: Sub-optimal error message. Get Nix to support a memoization primop, or get Nix Flakes to support systems properly or get Nix Flakes to add a name to flakes.
_module.args.getSystem = system: config.allSystems.${system} or (builtins.trace "using non-memoized system ${system}" config.perSystem system);
_module.args.getSystem = system: config.allSystems.${system} or (otherMemoizedSystems system);
# The warning is there for a reason. Only use this in situations where the
# performance cost has already been incurred, such as in `flakeModules.easyOverlay`,

View file

@ -9,10 +9,11 @@
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
# To import a flake module
# 1. Add foo to inputs
# 2. Add foo as a parameter to the outputs function
# 3. Add here: foo.flakeModule
# To import an internal flake module: ./other.nix
# To import an external flake module:
# 1. Add foo to inputs
# 2. Add foo as a parameter to the outputs function
# 3. Add here: foo.flakeModule
];
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];

20
vendor/flake-compat/COPYING vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2020-2021 Eelco Dolstra and the flake-compat contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

21
vendor/flake-compat/README.md vendored Normal file
View file

@ -0,0 +1,21 @@
# `vendor/flake-compat`
Flake-parts uses [flake-compat] in the partitions module.
Revision: f387cd2afec9419c8ee37694406ca490c3f34ee5
Non-essential files were omitted: `flake.nix`, CI configuration.
## Why vendor?
Vendoring is generally not recommended, but for flake-parts we make a different trade-off.
- Dependency is tiny
- Fetching latency is significant compared to size
- Cost gets multiplied by the high number of users/callers and flake-parts occurrences (when follows isn't used)
- Some users care about the size of their lock file
- Nix has some overhead for each lock node when updating a lock file
- Hard to provide input in the Nix sandbox (for those who have evaluation tests in derivations)
- Most users are unaffected (only impacts users of `partitions`)
[flake-compat]: https://github.com/NixOS/flake-compat

319
vendor/flake-compat/default.nix vendored Normal file
View file

@ -0,0 +1,319 @@
# Compatibility function to allow flakes to be used by
# non-flake-enabled Nix versions. Given a source tree containing a
# 'flake.nix' and 'flake.lock' file, it fetches the flake inputs and
# calls the flake's 'outputs' function. It then returns an attrset
# containing 'defaultNix' (to be used in 'default.nix'), 'shellNix'
# (to be used in 'shell.nix').
{ src
, system ? builtins.currentSystem or "unknown-system"
,
}:
let
inherit (builtins) mapAttrs;
lockFilePath = src + "/flake.lock";
lockFile = builtins.fromJSON (builtins.readFile lockFilePath);
fetchTree =
builtins.fetchTree or (
info:
if info.type == "github" then
{
outPath = fetchTarball (
{
url = "https://api.${info.host or "github.com"}/repos/${info.owner}/${info.repo}/tarball/${info.rev}";
}
// (if info ? narHash then { sha256 = info.narHash; } else { })
);
rev = info.rev;
shortRev = builtins.substring 0 7 info.rev;
lastModified = info.lastModified;
lastModifiedDate = formatSecondsSinceEpoch info.lastModified;
narHash = info.narHash;
}
else if info.type == "git" then
{
outPath = builtins.fetchGit (
{
url = info.url;
}
// (if info ? rev then { inherit (info) rev; } else { })
// (if info ? ref then { inherit (info) ref; } else { })
// (if info ? submodules then { inherit (info) submodules; } else { })
);
lastModified = info.lastModified;
lastModifiedDate = formatSecondsSinceEpoch info.lastModified;
narHash = info.narHash;
revCount = info.revCount or 0;
}
// (
if info ? rev then
{
rev = info.rev;
shortRev = builtins.substring 0 7 info.rev;
}
else
{ }
)
else if info.type == "path" then
{
outPath = builtins.path {
path = info.path;
sha256 = info.narHash;
};
narHash = info.narHash;
}
else if info.type == "tarball" then
{
outPath = fetchTarball (
{ inherit (info) url; } // (if info ? narHash then { sha256 = info.narHash; } else { })
);
}
else if info.type == "gitlab" then
{
inherit (info) rev narHash lastModified;
outPath = fetchTarball (
{
url = "https://${info.host or "gitlab.com"}/api/v4/projects/${info.owner}%2F${info.repo}/repository/archive.tar.gz?sha=${info.rev}";
}
// (if info ? narHash then { sha256 = info.narHash; } else { })
);
shortRev = builtins.substring 0 7 info.rev;
}
else if info.type == "sourcehut" then
{
inherit (info) rev narHash lastModified;
outPath = fetchTarball (
{
url = "https://${info.host or "git.sr.ht"}/${info.owner}/${info.repo}/archive/${info.rev}.tar.gz";
}
// (if info ? narHash then { sha256 = info.narHash; } else { })
);
shortRev = builtins.substring 0 7 info.rev;
}
else
# FIXME: add Mercurial, tarball inputs.
throw "flake input has unsupported input type '${info.type}'"
);
callFlake4 =
flakeSrc: locks:
let
flake = import (flakeSrc + "/flake.nix");
inputs = mapAttrs
(
n: v:
if v.flake or true then
callFlake4 (fetchTree (v.locked // v.info)) v.inputs
else
fetchTree (v.locked // v.info)
)
locks;
outputs = flakeSrc // (flake.outputs (inputs // { self = outputs; }));
in
assert flake.edition == 201909;
outputs;
callLocklessFlake =
flakeSrc:
let
flake = import (flakeSrc + "/flake.nix");
outputs = flakeSrc // (flake.outputs ({ self = outputs; }));
in
outputs;
rootSrc =
let
# Try to clean the source tree by using fetchGit, if this source
# tree is a valid git repository.
tryFetchGit =
src:
if isGit && !isShallow then
let
res = builtins.fetchGit src;
in
if res.rev == "0000000000000000000000000000000000000000" then
removeAttrs res [
"rev"
"shortRev"
]
else
res
else
{
outPath =
# Massage `src` into a store path.
if builtins.isPath src then
if
dirOf (toString src) == builtins.storeDir
# `builtins.storePath` is not available in pure-eval mode.
&& builtins ? currentSystem
then
# If it's already a store path, don't copy it again.
builtins.storePath src
else
"${src}"
else
src;
};
# NB git worktrees have a file for .git, so we don't check the type of .git
isGit = builtins.pathExists (src + "/.git");
isShallow = builtins.pathExists (src + "/.git/shallow");
in
{
lastModified = 0;
lastModifiedDate = formatSecondsSinceEpoch 0;
}
// (if src ? outPath then src else tryFetchGit src);
# Format number of seconds in the Unix epoch as %Y%m%d%H%M%S.
formatSecondsSinceEpoch =
t:
let
rem = x: y: x - x / y * y;
days = t / 86400;
secondsInDay = rem t 86400;
hours = secondsInDay / 3600;
minutes = (rem secondsInDay 3600) / 60;
seconds = rem t 60;
# Courtesy of https://stackoverflow.com/a/32158604.
z = days + 719468;
era = (if z >= 0 then z else z - 146096) / 146097;
doe = z - era * 146097;
yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
y = yoe + era * 400;
doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
mp = (5 * doy + 2) / 153;
d = doy - (153 * mp + 2) / 5 + 1;
m = mp + (if mp < 10 then 3 else -9);
y' = y + (if m <= 2 then 1 else 0);
pad = s: if builtins.stringLength s < 2 then "0" + s else s;
in
"${toString y'}${pad (toString m)}${pad (toString d)}${pad (toString hours)}${pad (toString minutes)}${pad (toString seconds)}";
allNodes = mapAttrs
(
key: node:
let
isRelative = node.locked.type or null == "path" && builtins.substring 0 1 node.locked.path != "/";
parentNode = allNodes.${getInputByPath lockFile.root node.parent};
sourceInfo =
if key == lockFile.root then
rootSrc
else if isRelative then
parentNode.sourceInfo
else
fetchTree (node.info or { } // removeAttrs node.locked [ "dir" ]);
subdir = if key == lockFile.root then "" else node.locked.dir or "";
outPath =
if isRelative then
parentNode.outPath + (if node.locked.path == "" then "" else "/" + node.locked.path)
else
sourceInfo.outPath + (if subdir == "" then "" else "/" + subdir);
flake = import (outPath + "/flake.nix");
inputs = mapAttrs (inputName: inputSpec: allNodes.${resolveInput inputSpec}.result) (
node.inputs or { }
);
# Resolve a input spec into a node name. An input spec is
# either a node name, or a 'follows' path from the root
# node.
resolveInput =
inputSpec: if builtins.isList inputSpec then getInputByPath lockFile.root inputSpec else inputSpec;
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
# root node, returning the final node.
getInputByPath =
nodeName: path:
if path == [ ] then
nodeName
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);
outputs = flake.outputs (inputs // { self = result; });
result =
outputs
# We add the sourceInfo attribute for its metadata, as they are
# relevant metadata for the flake. However, the outPath of the
# sourceInfo does not necessarily match the outPath of the flake,
# as the flake may be in a subdirectory of a source.
# This is shadowed in the next //
// sourceInfo
// {
# This shadows the sourceInfo.outPath
inherit outPath;
inherit inputs;
inherit outputs;
inherit sourceInfo;
_type = "flake";
};
in
{
result =
if node.flake or true then
assert builtins.isFunction flake.outputs;
result
else
sourceInfo // { inherit sourceInfo outPath; };
inherit outPath sourceInfo;
}
)
lockFile.nodes;
result =
if !(builtins.pathExists lockFilePath) then
callLocklessFlake rootSrc
else if lockFile.version == 4 then
callFlake4 rootSrc (lockFile.inputs)
else if lockFile.version >= 5 && lockFile.version <= 7 then
allNodes.${lockFile.root}.result
else
throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}";
in
rec {
outputs = result;
defaultNix =
builtins.removeAttrs result [ "__functor" ]
// (
if result ? defaultPackage.${system} then { default = result.defaultPackage.${system}; } else { }
)
// (
if result ? packages.${system}.default then
{ default = result.packages.${system}.default; }
else
{ }
);
shellNix =
defaultNix
// (if result ? devShell.${system} then { default = result.devShell.${system}; } else { })
// (
if result ? devShells.${system}.default then
{ default = result.devShells.${system}.default; }
else
{ }
);
}