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

Use nixpkgs stdenv no with packages #96

Merged
merged 7 commits into from
Jul 27, 2023
Merged
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
107 changes: 56 additions & 51 deletions builders.ncl
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in
let { NickelDerivation, Derivation, NixString, .. } = import "contracts.ncl" in

let concat_strings_sep = fun sep values =>
if std.array.length values == 0 then "" else
std.array.reduce_left (fun acc value => nix-s%"%{acc}%{sep}%{value}"%) values
in
{
NickelPkg
# we should only need two '%%', but a current Nickel bug (#XXX) bug makes the example being
Expand Down Expand Up @@ -34,61 +38,62 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in
"%%%
= NickelDerivation,

BashShell = {
inputs_spec = {
bash = {},
# Setting the following as a default value conflicts with InputsSpec's
# contract default value (`"nixpkgs"`). Maybe InputsSpec shouldn't set a
# default value at all?
# That being said, `naked_std_env` is an internal compatibility value:
# there doesn't seem to be a good reason to make it a default value. It
# can still be overridden using `force` if really needed
naked_std_env.input = "nickel-nix-internals",
},
inputs,

output =
{
version | default = "0.1",

# This is required otherwise nix develop will fail with a message that it
# only supports bash.
NixpkgsPkg
| doc m%"
Makes a derivation that runs all the build phases from nixpkgs' stdenv as the `build_command`.
Can be controlled with environment variables in the same way as `stdenv.mkDerivation`.
"%
= {
inputs_spec = {
bash.input | default = "nixpkgs",
stdenv.input | default = "nixpkgs",
},
inputs,
output = {
name,
version,
build_command = {
cmd = nix-s%"%{inputs.bash}/bin/bash"%,
args = [],
args = ["-c", "set -euo pipefail; source .attrs.sh; source $stdenv/setup; genericBuild"],
},

structured_env = {
# TODO: handle naked derivations without having to interpolate
stdenv.naked_std_env = nix-s%"%{inputs.naked_std_env}"%,
structured_env = {},
env = {
stdenv = inputs.stdenv,
},
attrs = env & structured_env,
} | NickelPkg,
},

Shell
| doc m%"
A derivation that is to be used as a shell, e.g. with `nix develop`.
Analogous to `mkShell`.
"%
= NixpkgsPkg & {
packages | doc "Packages to be added to the shell, setting PATH, LD_LIBRARY_PATH and other variables as needed" = {},
hooks | doc "Bash scripts to run when entering the shell" = {},

output = {
name | default = "shell",
version | default = "dev",

env.buildCommand = nix-s%"
echo "This derivation is not supposed to be built" 1>&2 1>/dev/null
exit 1
"%,
env.shellHook = concat_strings_sep "\n" (std.record.values hooks),
structured_env.buildInputs = packages,
} | NickelPkg,
},

packages = { bash = inputs.bash },

structured_env.PATH =
packages
|> std.record.map
(
fun _n p =>
nix-s%"%{p}/bin"%
),

# Compute `env` from `structured_env`. Note that thanks to recursive
# overriding, if stuff is added to `structured_env` through merging,
# `env` will be automatically up-to-date
env =
structured_env
|> std.record.map
(
fun _n xs =>
# Intersperse ':' between environment variable fragments
let values = std.record.values xs in
let first = std.array.first values in
values
|> std.array.drop_first
|> std.array.fold_left (fun acc value => nix-s%"%{acc}:%{value}"%) first
),
} | (NickelPkg & { packages | { _ : Derivation } }),
BashShell = Shell & {
inputs_spec = {
bash.input | default = "nixpkgs",
},
inputs,
output.packages = {
bash = inputs.bash,
},
},

RustShell =
Expand Down
58 changes: 34 additions & 24 deletions contracts.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -203,21 +203,21 @@ from the Nix world) or a derivation defined in Nickel.
# Format

`structured_env` is a record whose fields are environment variables
(`PATH`, `CLASSPATH`, `LD_PRELOAD`, etc.). The values are records
(`buildInputs`, etc.). The values are records
themselves, which represents _named pieces_ that are joined together
to form the final value of the variable.

For example:

```nickel
structured_env.PATH = {
bash = nix-s%"%{inputs.bash}/bin"%,
curl = nix-s%"%{inputs.curl}/bin"%,
structured_env.buildInputs = {
bash = inputs.bash,
curl = inputs.curl,
}
```

This structured environment corresponds to a variable `PATH` with
value `"%{inputs.bash}/bin:%{inputs.curl}/bin"`. Note that the order
This structured environment corresponds to a variable `buildInputs` with
value `"%{inputs.bash} %{inputs.curl}"`. Note that the order
isn't preserved. The `bash` and `curl` names don't appear in the
final value, but they are used for composability and overriding.

Expand All @@ -230,7 +230,7 @@ from the Nix world) or a derivation defined in Nickel.
`builder2.ncl`:

```nickel
structured_env.PATH.other-package = nix-s"%{inputs.other-package}/bin"%,
structured_env.buildInputs.other-package = inputs.other-package,
```

The final result will be a path with all three subpaths separated by
Expand All @@ -242,15 +242,14 @@ from the Nix world) or a derivation defined in Nickel.
override them specifically using merging:

```nickel
structured_env.PATH.bash | force = nix-s"%{inputs.special-bash}/bin"%,
structured_env.buildInputs.bash | force = inputs.special-bash,
```

# Interaction with `env`
# Interaction with `env` and `attrs`

Usually, you should only work with `structured_env`. The default
value of `env` is built from `structured_env` automatically. If you
override `env` directly, be aware that **`structured_env` will then
be potentially completly ignored**.
Usually, you should only work with `structured_env`. If you override
`attrs` directly, be aware that **`structured_env` will then be
potentially completly ignored**.
"%%%
| { _ | { _ | NixString } }
| default
Expand All @@ -259,19 +258,30 @@ from the Nix world) or a derivation defined in Nickel.
| doc m%"
Set additional environment variables for the builder.

By default, `env` should computed from `structured_env`, and
`structured_env` should be used preferably. Ultimately, `env` is the actual
source of truth being passed to Nix when building the derivation. See the
documentation of `structured_env` for more details.
You should use `structured_env` where possible.

If you override `attrs` directly, the value of `env` might be ignored.
"%
| { _ | NixString }
# TODO: should we compute `env` from `structured_env` directly here? Or
# let each builder do that by itself?
#
# Some parameters, like the separator, may vary. For now we have no
# choice anyway, because of merging not being idempotent on complex
# values, and because the `NickelDerivation` contract is applied several
# times, we have to move the computing logic to builders.
| default
= {},
attrs
| doc m%%"
*Directly* pass arbitrary attributes to the Nix derivation.

These attributes will appear as environment variables inside the
build, and will be available in `.attrs.json` file.

Unlike `env`, the fields here can be of any type, including
arbitrarily deep records. As a consequence, you should NOT pass
symbolic strings (including `nix-s%"..."%`) here.

By default, `attrs` is computed from values of `env` and
`structured_env`. It is almost always better to use `structured_env`
or `env` instead of `attrs`, unless you wish to pass deeply nested
attribute sets or non-string values.
"%%
| { _ | Dyn }
| default
= {},
"%{type_field}" | force = "nickelDerivation",
Expand Down
2 changes: 1 addition & 1 deletion examples/c-hello-world/nickel.lock.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
nickel-nix = {
builders = import "/nix/store/qb3qvz3xxvnwy5ma1b60ba2gw8pw9vas-5626jyxg49xsax707lciippzazpldgjn-source/builders.ncl",
contracts = import "/nix/store/qb3qvz3xxvnwy5ma1b60ba2gw8pw9vas-5626jyxg49xsax707lciippzazpldgjn-source/contracts.ncl",
naked-stdenv = import "/nix/store/qb3qvz3xxvnwy5ma1b60ba2gw8pw9vas-5626jyxg49xsax707lciippzazpldgjn-source/naked-stdenv.ncl",

nix = import "/nix/store/qb3qvz3xxvnwy5ma1b60ba2gw8pw9vas-5626jyxg49xsax707lciippzazpldgjn-source/nix.ncl",
},
}
33 changes: 14 additions & 19 deletions examples/c-hello-world/package.ncl
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
let builders = (import "./nickel.lock.ncl").nickel-nix.builders in
let nickel-nix = (import "./nickel.lock.ncl").nickel-nix in

{
nickel-nix.builders.NixpkgsPkg & {
inputs_spec = {
bash.input = "nixpkgs",
coreutils.input = "nixpkgs",
gcc.input = "nixpkgs",
hello = {
input = "sources",
path = "hello.c",
},
},

inputs,
Expand All @@ -17,18 +13,17 @@ let builders = (import "./nickel.lock.ncl").nickel-nix.builders in
{
name = "hello",
version = "0.1",
build_command = {
cmd = nix-s%"%{inputs.bash.outputPath}/bin/bash"%,
args = [
"-c",
nix-s%"
%{inputs.gcc.outputPath}/bin/gcc %{inputs.hello.outputPath} -o hello
%{inputs.coreutils.outputPath}/bin/mkdir -p $out/bin
%{inputs.coreutils.outputPath}/bin/cp hello $out/bin/hello
"%,
]
structured_env = {
buildInputs.gcc = inputs.gcc,
buildInputs.coreutils = inputs.coreutils,
buildInputs.bash = inputs.bash,
},
env = {},
dependencies = [inputs.bash, inputs.coreutils, inputs.gcc, inputs.hello],
} | builders.NickelPkg
env = {
buildCommand = nix-s%"
gcc %{nickel-nix.nix.lib.import_file "hello.c"} -o hello
mkdir -p $out/bin
cp hello $out/bin/hello
"%,
},
}
}
8 changes: 0 additions & 8 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,11 @@
lib = pkgs.callPackage ./lib.nix {
inherit system;
nickel = inputs.nickel.packages."${system}".nickel-lang-cli;
inherit (self.lib.${system}) nickel-nix-internals;
};
pkgs = nixpkgs.legacyPackages.${system};
in {
lib.importNcl = lib.importNcl;

# Internal nickel-nix library. Each value is a function that accepts inputs from user flake.
lib.nickel-nix-internals = {
naked_std_env = self.lib.${system}.importNcl ./. "naked-stdenv.ncl";
};

# Helper function that generates ugly contents for "nickel.lock.ncl", see buildLockFile.
lib.buildLockFileContents = contents: let
lib = pkgs.lib;
Expand All @@ -131,7 +125,6 @@
# nickel-nix = {
# builders = "/nix/store/...-source/builders.ncl";
# contracts = "/nix/store/...-source/contracts.ncl";
# naked-stdenv = "/nix/store/...-source/naked-stdenv.ncl";
# nix = "/nix/store/...-source/nix.ncl";
# };
# }
Expand All @@ -140,7 +133,6 @@
# nickel-nix = {
# builders = import "/nix/store/...-source/builders.ncl",
# contracts = import "/nix/store/...-source/contracts.ncl",
# naked-stdenv = import "/nix/store/...-source/naked-stdenv.ncl",
# nix = import "/nix/store/...-source/nix.ncl",
# },
# }
Expand Down
21 changes: 6 additions & 15 deletions lib.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
nickel,
system,
lib,
nickel-nix-internals,
}: let
# Export a Nix value to be consumed by Nickel
typeField = "$__nixel_type";
Expand Down Expand Up @@ -97,16 +96,17 @@
# produced by Nickel, and transform it into valid arguments to
# `derivation`
prepareDerivation = system: value:
(builtins.removeAttrs value ["build_command" "env" "structured_env" "packages"])
(builtins.removeAttrs value ["build_command" "env" "structured_env" "attrs" "packages"])
// {
system =
if value ? system
then "${value.system.arch}-${value.system.os}"
else system;
builder = value.build_command.cmd;
args = value.build_command.args;
__structuredAttrs = true;
}
// value.env;
// value.attrs;

# Import a Nickel value produced by the Nixel DSL
importFromNickel = context: system: baseDir: value: let
Expand Down Expand Up @@ -235,11 +235,8 @@
else if builtins.hasAttr inputTakeFrom flakeInputs
then let
input =
if inputTakeFrom == "nickel-nix-internals"
then flakeInputs.${inputTakeFrom}
else
flakeInputs.${inputTakeFrom}.legacyPackages.${system}
or flakeInputs.${inputTakeFrom}.packages.${system};
flakeInputs.${inputTakeFrom}.legacyPackages.${system}
or flakeInputs.${inputTakeFrom}.packages.${system};

pkgPath = declaredInputs.${inputId}.path or [inputId];
in
Expand Down Expand Up @@ -292,14 +289,8 @@
# declares an input hello from input "nixpkgs", then flakeInputs must have an
# attribute "nixpkgs" with a package "hello".
importNcl = baseDir: nickelFile: flakeInputs: let
flakeInputsWithInternals =
flakeInputs
// {
nickel-nix-internals = builtins.mapAttrs (_: f: f flakeInputs) nickel-nix-internals;
};
nickelResult = callNickel {
inherit nickelFile baseDir;
flakeInputs = flakeInputsWithInternals;
inherit nickelFile baseDir flakeInputs;
};
in
{rawNickel = nickelResult.value;}
Expand Down
Loading