Skip to content

Commit

Permalink
port flake-compat to make flake inputs accessible (#386)
Browse files Browse the repository at this point in the history
this makes a bunch of changes with regard to readability and reusability:
- use more descriptive names in the datetime conversion
- extract interesting parts of the business logic into a library

the formatting made by Alejandra is terrible...
most of these assignments can be one-liners, and it breaks doc comments.
  • Loading branch information
fricklerhandwerk authored Oct 31, 2024
1 parent 0576026 commit aae8678
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 1 deletion.
14 changes: 14 additions & 0 deletions default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
sources ? (import ./flake-compat.nix {root = ./.;}).inputs,
system ? builtins.currentSystem,
pkgs ?
import sources.nixpkgs {
config = {};
overlays = [];
inherit system;
},
}: {
shell = pkgs.mkShellNoCC {
packages = [];
};
}
4 changes: 4 additions & 0 deletions flake-compat.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let
lib = import ./lib.nix {lib = {};};
in
lib.flake-inputs
275 changes: 274 additions & 1 deletion lib.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{lib}: {
{lib}: rec {
# Take an attrset of arbitrary nesting and make it flat
# by concatenating the nested names with the given separator.
flattenAttrs = separator: let
Expand All @@ -9,4 +9,277 @@
else {${path + name} = value;};
in
f "";

/*
*
Convert number of seconds in the Unix epoch to a Gregorian calendar date and time
This does not take into account leap seconds, which would require a table lookup.
*/
datetime-from-timestamp = timestamp: let
remainder = x: y: x - x / y * y;
seconds-per-day = 86400;
day-of-epoch = timestamp / seconds-per-day;
seconds-of-day = remainder timestamp seconds-per-day;
hours = seconds-of-day / 3600;
minutes = (remainder seconds-of-day 3600) / 60;
seconds = remainder timestamp 60;

# Courtesy of http://howardhinnant.github.io/date_algorithms.html via https://stackoverflow.com/a/32158604
day' = day-of-epoch + 719468; # internal representation of days, based on number of days between 0000-03-01 and 1970-01-01
days-per-era = 146097;
era =
# 400-year interval of the Gregorian calendar
(
if day' >= 0
then day'
else day' - (days-per-era - 1)
)
/ days-per-era;
day-of-era = day' - era * days-per-era;
year-of-era = (day-of-era - day-of-era / 1460 + day-of-era / 36524 - day-of-era / (days-per-era - 1)) / 365;
year' = year-of-era + era * 400; # internal representation of years
day-of-year = day-of-era - (365 * year-of-era + year-of-era / 4 - year-of-era / 100);
month' = (5 * day-of-year + 2) / 153; # internal representation of months
day = day-of-year - (153 * month' + 2) / 5 + 1;
month =
month'
+ (
if month' < 10
then 3
else -9
);
year =
year'
+ (
if month <= 2
then 1
else 0
);
in {
inherit year month day hours minutes seconds;
};

# Format number of seconds in the Unix epoch as %Y%m%d%H%M%S.
format-timestamp = timestamp: let
pad = n: s: let
str = toString s;
in
with builtins; concatStringsSep "" (genList (_: "0") (n - stringLength str)) + str;
in
with builtins.mapAttrs
(name: s:
if name == "year"
then pad 4 s
else pad 2 s)
(datetime-from-timestamp timestamp); "${year}${month}${day}${hours}${minutes}${seconds}";

/*
*
Polyfill for the experimental `builtins.fetchTree`
https://nix.dev/manual/nix/latest/language/builtins#builtins-fetchTree
*/
fetchTree = 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 = format-timestamp info.lastModified;
narHash = info.narHash;
}
else if info.type == "git"
then
{
outPath = builtins.fetchGit (
{
url = info.url;
shallow = true;
allRefs = true;
}
// (
if info ? rev
then {inherit (info) rev;}
else {}
)
// (
if info ? submodules
then {inherit (info) submodules;}
else {}
)
);
lastModified = info.lastModified;
lastModifiedDate = format-timestamp info.lastModified;
narHash = info.narHash;
}
// (
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 =
if builtins.substring 0 1 info.path != "/"
# XXX: relative paths require an additional `root` attribute!
# this is supplied by our own flake-inputs, but may not work elsewhere
then "${info.root}/${info.path}"
else info.path;
};
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;
}
# TODO: Mercurial, tarball inputs, ...
else throw "flake input has unsupported input type '${info.type}'";

/*
*
Compatibility layer to allow `flake.lock` files to be used with stable Nix.
Modified from https://github.com/nix-community/dream2nix/blob/main/dev-flake/flake-compat.nix
https://github.com/nix-community/flake-compat is not actively maintained
*/
flake-inputs = {
root,
overrides ? {},
}: let
lockFilePath = root + "/flake.lock";
lockFile = builtins.fromJSON (builtins.readFile lockFilePath);

tree = 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 = src;};
# NB git worktrees have a file for .git, so we don't check the type of .git
isGit = builtins.pathExists (root + "/.git");
isShallow = builtins.pathExists (root + "/.git/shallow");
in
{
lastModified = 0;
lastModifiedDate = format-timestamp 0;
}
// (
if root ? outPath
then root
else tryFetchGit root
);

# we can't import those from the Nixpkgs `lib`,
# since flake inputs are used to fetch Nixpkgs to begin with
nameValuePair = name: value: {inherit name value;};
mapAttrs' = f: set:
builtins.listToAttrs (map (attr: f attr set.${attr}) (builtins.attrNames set));

rootOverrides =
mapAttrs'
(input: lockKey': let
lockKey =
if builtins.isList lockKey'
then builtins.concatStringsSep "/" lockKey'
else lockKey';
in
nameValuePair lockKey (overrides.${input} or null))
lockFile.nodes.${lockFile.root}.inputs;

allNodes =
builtins.mapAttrs
(
key: node: let
sourceInfo =
if key == lockFile.root
then tree
else if rootOverrides.${key} or null != null
then {
type = "path";
outPath = rootOverrides.${key};
narHash = throw "flake-inputs: overriding narHash not implemented";
}
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"] // {inherit root;});

inputs =
builtins.mapAttrs
(_inputName: inputSpec: allNodes.${resolveInput inputSpec})
(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);
in
sourceInfo
// {
inherit inputs;
inherit sourceInfo;
_type = "flake";
}
)
lockFile.nodes;
in
if lockFile.version >= 5 && lockFile.version <= 7
then
allNodes.${lockFile.root}
// {
overrideInputs = ov:
flake-inputs {
inherit root;
overrides = ov;
};
}
else throw "flake-inputs: lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}";
}

0 comments on commit aae8678

Please sign in to comment.