Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split up the Nix interop #209

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/bootstrap-no-flake.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ let
organistSrc = builtins.fetchTarball "https://github.com/nickel-lang/organist/archive/main.tar.gz";
organist = pkgs.callPackage "${organistSrc}/lib/lib.nix" {inherit organistSrc;};
in
(organist.importNcl {baseDir = ./.;}).shells.default
(organist.importOrganist {baseDir = ./.;}).shells.default
```

And then use `nix develop -f shell.nix` as usual. Note that this will not use nixpkgs and Nickel from Organist's flake.lock.
Expand All @@ -22,5 +22,5 @@ let
organistSrc = builtins.fetchTarball "https://github.com/nickel-lang/organist/archive/main.tar.gz";
organist = ((import flake-compat) {src = organistSrc;}).defaultNix.lib.${builtins.currentSystem};
in
(organist.importNcl {baseDir = ./.;}).shells.default
(organist.importOrganist {baseDir = ./.;}).shells.default
```
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
188 changes: 120 additions & 68 deletions lib/lib.nix
Original file line number Diff line number Diff line change
Expand Up @@ -133,92 +133,144 @@
else value;

# Call Nickel on a given Nickel expression with the inputs declared in it.
# See importNcl for details about the flakeInputs parameter.
# See importOrganist for details about the flakeInputs parameter.
callNickel = {
nickelFile,
flakeInputs,
/*
: [String]

The command-line arguments to pass to `nickel export`
*/
nickelArgs,
/*
: String

The base path for resolving `organist.nix.import_file`
*/
baseDir,
lockFileContents,
}: let
sources = builtins.path {
path = baseDir;
# TODO: filter .ncl files
# filter =
};
}:
runCommand "nickel-res.json" {
__structuredAttrs = true;
inherit nickelArgs;
} ''
${nickel}/bin/nickel export --format json ''${nickelArgs[@]} > $out
'';

lockfilePath = "${sources}/nickel.lock.ncl";
expectedLockfileContents = buildLockFileContents lockFileContents;
needNewLockfile = !builtins.pathExists lockfilePath || (builtins.readFile lockfilePath) != expectedLockfileContents;
/*
Import a Nickel expression as a Nix value.

nickelWithImports = src: ''
let params = {
system = "${system}",
}
in
let organist = (import "${src}/nickel.lock.ncl").organist in
The result of the Nickel evaluation will be directly mapped to Nix values
(through their JSON interface), with the exception of record containing the
special `__organist_type` field which have a special treatment as per the
`importFromNickel` function above.
*/
importNickel = {
/*
: [String]

let nickel_expr =
import "${src}/${nickelFile}" in
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.

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
lib.warn ''
Lockfile contents are outdated. Please run "nix run .#regenerate-lockfile" to update them.
''
''
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
''
);
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.

# 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_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 = "${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
importNickel preprocessedExpression;
in {
inherit
importNcl
importNickel
importOrganist
buildLockFile
buildLockFileContents
regenerateLockFileApp
Expand Down
2 changes: 1 addition & 1 deletion lib/nix-interop/derivation.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ in
| optional
| Version,
system
| doc "The system to build the package on. Defaults to the system used by importNcl."
| doc "The system to build the package on. Defaults to the system used by importOrganist."
| NullOr System
| default
= null,
Expand Down
Loading