Skip to content

Commit

Permalink
Split up the Nix interop
Browse files Browse the repository at this point in the history
Separate the bit about calling Nickel (`importNickel`), and the one really about
Organist (`importOrganist`).
  • Loading branch information
Théophane Hufschmitt committed Jun 17, 2024
1 parent e444d37 commit c4b6262
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 71 deletions.
8 changes: 4 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@
# devShells.${system} and packages.${system} generated from project.ncl
#
# (to be extended with more features later)
outputsFromNickel = baseDir: flakeInputs: {
outputsFromNickel = projectRoot: flakeInputs: {
systems ? flake-utils.lib.defaultSystems,
lockFileContents ? {
organist = "${self}/lib/organist.ncl";
},
}:
flake-utils.lib.eachSystem systems (system: let
lib = self.lib.${system};
nickelOutputs = lib.importNcl {
inherit baseDir flakeInputs lockFileContents;
nickelOutputs = lib.importOrganist {
inherit projectRoot flakeInputs lockFileContents;
};
in
# Can't do just `{inherit nickelOutputs;} // nickelOutputs.flake` because of infinite recursion over self
if (! builtins.readDir baseDir ? "project.ncl")
if (! builtins.readDir projectRoot ? "project.ncl")
then {}
else {
inherit nickelOutputs;
Expand Down
172 changes: 105 additions & 67 deletions lib/lib.nix
Original file line number Diff line number Diff line change
Expand Up @@ -135,91 +135,129 @@
# Call Nickel on a given Nickel expression with the inputs declared in it.
# See importNcl for details about the flakeInputs parameter.
callNickel = {
nickelFile,
flakeInputs,
baseDir,
lockFileContents,
}: let
sources = builtins.path {
path = baseDir;
# TODO: filter .ncl files
# filter =
};
/*
: [String]
lockfilePath = "${sources}/nickel.lock.ncl";
expectedLockfileContents = buildLockFileContents lockFileContents;
needNewLockfile = !builtins.pathExists lockfilePath || (builtins.readFile lockfilePath) != expectedLockfileContents;
The command-line arguments to pass to `nickel export`
*/
nickelArgs,
/*
: String
nickelWithImports = src: ''
let params = {
system = "${system}",
}
in
let organist = (import "${src}/nickel.lock.ncl").organist in
The base path for resolving `organist.nix.import_file`
*/
baseDir,
}:
runCommand "nickel-res.json" {
__structuredAttrs = true;
inherit nickelArgs;
} ''
${nickel}/bin/nickel export --format json ''${nickelArgs[@]} > $out
'';

let nickel_expr =
import "${src}/${nickelFile}" in
/*
Import a Nickel expression as a Nix value.
*/
importNickel = {
/*
: [String]
nickel_expr & params
'';
in
runCommand "nickel-res.json" {
# If expectedLockfileContents references current flake in context, propagate it even if we don't need it.
inherit expectedLockfileContents;
passAsFile = ["expectedLockfileContents"];
} (
if needNewLockfile
then ''
cp -r "${sources}" sources
if [ -f sources/nickel.lock.ncl ]; then
chmod +w sources sources/nickel.lock.ncl
else
chmod +w sources
fi
cp $expectedLockfileContentsPath sources/nickel.lock.ncl
cat > eval.ncl <<EOF
${nickelWithImports "sources"}
EOF
${nickel}/bin/nickel export eval.ncl --field config.flake > $out
''
else ''
cat > eval.ncl <<EOF
${nickelWithImports sources}
EOF
${nickel}/bin/nickel export eval.ncl --field config.flake > $out
''
);
Raw arguments passed to the `nickel export` command.
This must at least contain the path to one Nickel file
*/
nickelArgs,
/*
The path relative to which to resolve the `nix.import_file` calls.
# Import a Nickel expression as a Nix value. flakeInputs are where the packages
# passed to the Nickel expression are taken from. If the Nickel expression
# declares an input hello from input "nixpkgs", then flakeInputs must have an
# attribute "nixpkgs" with a package "hello".
importNcl = {
baseDir,
nickelFile ? "project.ncl",
Can be left to its default value if `import_file` is not used.
*/
baseDir ? "/non-existent-path-please-set-baseDir",
/*
The inputs to which `nix.import_nix` calls will be resolved.
Can be left to its default value if `import_nix` is not used.
*/
flakeInputs ? {
nixpkgs = pkgs;
organist = import organistSrc;
},
lockFileContents ? {
organist = "${organistSrc}/lib/organist.ncl";
},
}: let
nickelResult = callNickel {
inherit nickelFile baseDir flakeInputs lockFileContents;
nickelResult = callNickel {inherit nickelArgs baseDir;};
in
{rawNickel = nickelResult;}
// (importFromNickel flakeInputs system baseDir (builtins.fromJSON
(builtins.unsafeDiscardStringContext (builtins.readFile nickelResult))));

/*
Prepare an Organist project for consumption in Nix.
*/
preprocessOrganist = {
projectRoot,
lockFileContents,
nickelFile,
flakeInputs,
}
: let
sources = builtins.path {
path = projectRoot;
# TODO: filter .ncl files?
# filter =
};
expectedLockfileContents = buildLockFileContents lockFileContents;

paramsFile = pkgs.writers.writeJSON "params.json" {inherit system;};

# Regenerate the lockfile to force it to match what we pass in through Nix
nickel_source_root =
runCommand "nickel-res.json" {
inherit expectedLockfileContents;
passAsFile = ["expectedLockfileContents"];
}
''
cp -r "${sources}" $out
if [ -f $out/nickel.lock.ncl ]; then
chmod +w $out $out/nickel.lock.ncl
else
chmod +w $out
fi
cp $expectedLockfileContentsPath $out/nickel.lock.ncl
'';

# The original flake inputs, but with an extra internal one containing the path to the lockfile
# so that we can add it to the repository afterwards
enrichedFlakeInputs =
flakeInputs
// {
"%%organist_internal".nickelLock = builtins.toFile "nickel.lock.ncl" (buildLockFileContents lockFileContents);
"%%organist_internal".nickelLock = "${nickel_source_root}/nickel.lock.ncl";
};
in {
nickelArgs = [paramsFile "${nickel_source_root}/${nickelFile}" "--field" "config.flake"];
baseDir = nickel_source_root;
flakeInputs = enrichedFlakeInputs;
};

/*
Import an Organist project into Nix.
*/
importOrganist = {
/* The desired contents of the `nickel.lock.ncl` file. */
lockFileContents,
/* The inputs of the flake, for resolving the `nix.import_nix` function. */
flakeInputs,
/* The root of the project */
projectRoot,
/* Path to the entrypoint, relative to the project root */
nickelFile ? "project.ncl",
}: let
preprocessedExpression = preprocessOrganist {
inherit nickelFile lockFileContents projectRoot flakeInputs;
};
in
{rawNickel = nickelResult;}
// (importFromNickel enrichedFlakeInputs system baseDir (builtins.fromJSON
(builtins.unsafeDiscardStringContext (builtins.readFile nickelResult))));
importNickel preprocessedExpression;
in {
inherit
importNcl
importNickel
importOrganist
buildLockFile
buildLockFileContents
regenerateLockFileApp
Expand Down

0 comments on commit c4b6262

Please sign in to comment.