chore: manage all-cabal-json via flake input

This commit is contained in:
DavHau 2022-10-14 18:10:35 +02:00
parent 0cf5483645
commit 48f490a2ed
9 changed files with 346 additions and 29 deletions

298
flake-compat.nix Normal file
View File

@ -0,0 +1,298 @@
# 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
lockFilePath = src + "/flake.lock";
lockFile = builtins.fromJSON (builtins.readFile lockFilePath);
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 = 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;
}
// (
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;};
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
# FIXME: add Mercurial, tarball inputs.
throw "flake input has unsupported input type '${info.type}'";
callFlake4 = flakeSrc: locks: let
flake = import (flakeSrc + "/flake.nix");
inputs = builtins.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 = 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 =
builtins.mapAttrs
(
key: node: let
sourceInfo =
if key == lockFile.root
then rootSrc
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
subdir =
if key == lockFile.root
then ""
else node.locked.dir or "";
flake = import (sourceInfo
+ (
if subdir != ""
then "/"
else ""
)
+ subdir
+ "/flake.nix");
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);
outputs = flake.outputs (inputs // {self = result;});
result =
outputs
// sourceInfo
// {
inherit inputs;
inherit outputs;
inherit sourceInfo;
};
in
if node.flake or true
then assert builtins.isFunction flake.outputs; result
else 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}
else throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}";
in rec {
# expose flake inputs
inherit (result) inputs;
defaultNix =
result
// (
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 {}
);
}

View File

@ -22,6 +22,23 @@
"type": "github"
}
},
"all-cabal-json": {
"flake": false,
"locked": {
"lastModified": 1665552503,
"narHash": "sha256-r14RmRSwzv5c+bWKUDaze6pXM7nOsiz1H8nvFHJvufc=",
"owner": "nix-community",
"repo": "all-cabal-json",
"rev": "d7c0434eebffb305071404edcf9d5cd99703878e",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "hackage",
"repo": "all-cabal-json",
"type": "github"
}
},
"crane": {
"flake": false,
"locked": {
@ -196,6 +213,7 @@
"root": {
"inputs": {
"alejandra": "alejandra",
"all-cabal-json": "all-cabal-json",
"crane": "crane",
"devshell": "devshell",
"flake-utils-pre-commit": "flake-utils-pre-commit",

View File

@ -15,7 +15,6 @@
pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs";
# upstream flake-utils dep not supporting `aarch64-darwin` yet
flake-utils-pre-commit.url = "github:numtide/flake-utils";
pre-commit-hooks.inputs.flake-utils.follows = "flake-utils-pre-commit";
@ -48,6 +47,12 @@
url = "github:ipetkov/crane";
flake = false;
};
# required for haskell translators
all-cabal-json = {
url = "github:nix-community/all-cabal-json/hackage";
flake = false;
};
};
outputs = {
@ -60,6 +65,7 @@
poetry2nix,
pre-commit-hooks,
crane,
all-cabal-json,
...
} @ inp: let
b = builtins;
@ -150,7 +156,7 @@
dream2nixFor = forAllSystems (system: pkgs:
import ./src rec {
externalDir = externalDirFor."${system}";
inherit externalPaths externalSources lib pkgs;
inherit all-cabal-json externalPaths externalSources lib pkgs;
config = {
inherit overridesDirs;
};
@ -168,7 +174,7 @@
# Produces flake-like output schema.
d2n-lib =
(import ./src/lib.nix {
inherit externalPaths externalSources overridesDirs lib;
inherit all-cabal-json externalPaths externalSources overridesDirs lib;
nixpkgsSrc = "${nixpkgs}";
})
# system specific dream2nix library

View File

@ -6,6 +6,13 @@
pkgs ? import <nixpkgs> {},
lib ? pkgs.lib,
nix ? pkgs.nix,
all-cabal-json ?
(import ../flake-compat.nix {
src = ../.;
inherit (pkgs) system;
})
.inputs
.all-cabal-json,
# default to empty dream2nix config
config ?
# if called via CLI, load config via env
@ -52,6 +59,7 @@ in let
framework = import ./modules/framework.nix {
inherit
all-cabal-json
apps
lib
dlib
@ -74,6 +82,7 @@ in let
callPackageDreamArgs =
pkgs
// {
inherit all-cabal-json;
inherit apps;
inherit callPackageDream;
inherit config;

View File

@ -7,12 +7,13 @@
overridesDirs,
externalSources,
externalPaths,
all-cabal-json,
} @ args: let
l = lib // builtins;
initDream2nix = config: pkgs:
import ./default.nix
{inherit config pkgs externalPaths externalSources;};
{inherit all-cabal-json config pkgs externalPaths externalSources;};
loadConfig = config'': let
config' = (import ./utils/config.nix).loadConfig config'';
@ -93,6 +94,7 @@
inherit lib dlib externalSources;
dream2nixConfig = config;
dream2nixConfigFile = l.toFile "dream2nix-config.json" (l.toJSON config);
all-cabal-json = throw "all-cabal-json is not available before nixpkgs is imported";
apps = throw "apps is not available before nixpkgs is imported";
pkgs = throw "pkgs is not available before nixpkgs is imported";
utils = throw "utils is not available before nixpkgs is imported";

View File

@ -1,4 +1,5 @@
{
all-cabal-json,
apps,
dlib,
externals,
@ -25,6 +26,9 @@ in {
./translators
];
options = {
all-cabal-json = lib.mkOption {
type = t.raw;
};
apps = lib.mkOption {
type = t.raw;
};

View File

@ -4,6 +4,7 @@
pkgs,
utils,
name,
all-cabal-json,
...
}: let
l = lib // builtins;
@ -77,11 +78,7 @@ in {
translate = let
stackLockUtils = import ./utils.nix {inherit dlib lib pkgs;};
all-cabal-hashes = let
all-cabal-hashes' = pkgs.runCommandLocal "all-cabal-hashes" {} ''
mkdir $out
cd $out
tar --strip-components 1 -xf ${pkgs.all-cabal-hashes}
'';
all-cabal-hashes' = all-cabal-json;
names = dlib.listDirs all-cabal-hashes';
getVersions = name: dlib.listDirs "${all-cabal-hashes'}/${name}";
in
@ -91,7 +88,7 @@ in {
(getVersions name)
(
version:
(l.fromJSON (l.readFile "${all-cabal-hashes'}/${name}/${version}/${name}.json"))
(l.fromJSON (l.readFile "${all-cabal-hashes'}/${name}/${version}/${name}.hashes.json"))
.package-hashes
));
in
@ -156,7 +153,7 @@ in {
(rawObj: dlib.nameVersionPair rawObj.name rawObj.version)
serializedRawObjects;
haskellUtils = import ../utils.nix {inherit lib pkgs;};
haskellUtils = import ../utils.nix {inherit all-cabal-json lib pkgs;};
cabalData =
haskellUtils.batchFindJsonFromCabalCandidates

View File

@ -10,12 +10,6 @@
sha256 = "1qc703yg0babixi6wshn5wm2kgl5y1drcswgszh4xxzbrwkk9sv7";
});
in rec {
all-cabal-hashes = pkgs.runCommandLocal "all-cabal-hashes" {} ''
mkdir $out
cd $out
tar --strip-components 1 -xf ${pkgs.all-cabal-hashes}
'';
# The cabal2json program
cabal2json = let
haskellLib = pkgs.haskell.lib.compose;

View File

@ -1,21 +1,10 @@
{
lib,
pkgs,
all-cabal-json,
}: let
l = lib // builtins;
all-cabal-json = let
src = pkgs.fetchurl {
url = "https://github.com/nix-community/all-cabal-json/tarball/bdb5c96a57926392fbfd567867fada983f480195";
sha256 = "sha256-C6/4T4yJ8uT0qmRezY5YT+dl0v6/FTpMEahuBMnPhiU=";
};
in
pkgs.runCommandLocal "all-cabal-json" {} ''
mkdir $out
cd $out
tar --strip-components 1 -xf ${src}
'';
findJsonFromCabalCandidate = name: version: let
jsonCabalFile = "${all-cabal-json}/${name}/${version}/${name}.json";
in