idris2: Move to by-name, clean-up, add withPackages helper, make unwrapped compiler (#445120)

This commit is contained in:
Mathew Polzin
2025-10-22 01:05:37 +00:00
committed by GitHub
21 changed files with 529 additions and 136 deletions

View File

@@ -1,5 +1,57 @@
# Idris2 {#sec-idris2}
When developing using Idris2, by default the Idris compiler only has the minimal support libraries in its environment. This means that it will not attempt to read any libraries installed
globally, for example in the `$HOME` directory. The recommended way to use Idris2 is to wrap the compiler in an environment that provides these packages per-project, for example in a
devShell.
```nix
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
packages = [ (idris2.withPackages (p: [ p.idris2Api ])) ];
}
```
or, alternatively if Nix is used to build the Idris2 project:
```nix
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
inputsFrom = [ (pkgs.callPackage ./package.nix { }) ];
}
```
By default, the Idris2 compiler provided by Nixpkgs does not read globally installed packages, nor can install them. Running `idris2 --install` will fail because the Nix store is
a read-only file-system. If globally-installed packages are desired rather than the above strategy, one can set `IDRIS2_PREFIX`, or additional `IDRIS2_PACKAGE_PATH` entries
for the compiler to read from. The following snippet will append `$HOME/.idris2` to `$IDRIS2_PACKAGE_PATH`, and if such a variable does not exist, create it. The Nixpkg's Idris2
compiler append a few required libraries to this path variable, but any paths in the user's environment will be prefixed to those libraries.
```nix
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
packages = [ (idris2.withPackages (p: [ p.idris2Api ])) ];
shellHook = ''
IDRIS2_PACKAGE_PATH="''${IDRIS2_PACKAGE_PATH:+$IDRIS2_PACKAGE_PATH}$HOME/.idris2"
'';
}
```
The following snippet will allow the Idris2 to run `idris2 --install` successfully:
```nix
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
packages = [ (idris2.withPackages (p: [ p.idris2Api ])) ];
shellHook = ''
IDRIS2_PREFIX="$HOME/.idris2"
'';
}
```
In addition to exposing the Idris2 compiler itself, Nixpkgs exposes an `idris2Packages.buildIdris` helper to make it a bit more ergonomic to build Idris2 executables or libraries.
The `buildIdris` function takes an attribute set that defines at a minimum the `src` and `ipkgName` of the package to be built and any `idrisLibraries` required to build it. The `src` is the same source you're familiar with and the `ipkgName` must be the name of the `ipkg` file for the project (omitting the `.ipkg` extension). The `idrisLibraries` is a list of other library derivations created with `buildIdris`. You can optionally specify other derivation properties as needed but sensible defaults for `configurePhase`, `buildPhase`, and `installPhase` are provided.
@@ -56,3 +108,20 @@ lspPkg.executable
```
The above uses the default value of `withSource = false` for the `idris2Api` but could be modified to include that library's source by passing `(idris2Api { withSource = true; })` to `idrisLibraries` instead. `idris2Api` in the above derivation comes built in with `idris2Packages`. This library exposes many of the otherwise internal APIs of the Idris2 compiler.
The compiler package can be instantiated with packages on its `IDRIS2_PACKAGES` path from the `idris2Packages` set.
```nix
{
idris2,
devShell,
}:
let
myIdris = idris2.withPackages (p: [ p.idris2Api ]);
in
devShell {
packages = [ myIdris ];
}
```
This search path is extended from the path already in the user's environment.

View File

@@ -269,6 +269,8 @@
- `installShellCompletion`: now supports Nushell completion files
- `idris2` supports being instantiated with a package environment with `idris.withPackages (p: [ ])`
- New hardening flags, `strictflexarrays1` and `strictflexarrays3` were made available, corresponding to the gcc/clang options `-fstrict-flex-arrays=1` and `-fstrict-flex-arrays=3` respectively.
- `gramps` has been updated to 6.0.0

View File

@@ -0,0 +1,5 @@
{ mkPrelude, prelude }:
mkPrelude {
name = "base";
dependencies = [ prelude ];
}

View File

@@ -0,0 +1,12 @@
{
mkPrelude,
prelude,
base,
}:
mkPrelude {
name = "contrib";
dependencies = [
prelude
base
];
}

View File

@@ -0,0 +1,33 @@
{
stdenv,
lib,
gmp,
idris2-src,
idris2-version,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "libidris2_support";
version = idris2-version;
src = idris2-src;
strictDeps = true;
buildInputs = [ gmp ];
enableParallelBuilding = true;
makeFlags = [
"PREFIX=${placeholder "out"}"
]
++ lib.optional stdenv.isDarwin "OS=";
buildFlags = [ "support" ];
installTargets = "install-support";
postInstall = ''
mv "$out/idris2-${finalAttrs.version}/lib" "$out/lib"
mv "$out/idris2-${finalAttrs.version}/support" "$out/share"
rm -rf $out/idris2-${finalAttrs.version}
'';
meta.description = "Runtime library for Idris2";
})

View File

@@ -0,0 +1,12 @@
{
mkPrelude,
prelude,
base,
}:
mkPrelude {
name = "linear";
dependencies = [
prelude
base
];
}

View File

@@ -0,0 +1,39 @@
{
lib,
stdenvNoCC,
idris2-src,
idris2-version,
idris2-unwrapped,
}:
lib.extendMkDerivation {
constructDrv = stdenvNoCC.mkDerivation;
excludeDrvArgNames = [
"dependencies"
];
extendDrvArgs =
finalAttrs:
{
name,
dependencies ? [ ],
}:
{
pname = name;
version = idris2-version;
src = idris2-src;
strictDeps = true;
makeFlags = "IDRIS2=${lib.getExe idris2-unwrapped}";
enableParallelBuilding = true;
preBuild = ''
cd libs/${name}
'';
env = {
IDRIS2_PREFIX = placeholder "out";
IDRIS2_PACKAGE_PATH = lib.makeSearchPath "idris2-${idris2-version}" dependencies;
};
};
}

View File

@@ -0,0 +1,14 @@
{
mkPrelude,
prelude,
base,
linear,
}:
mkPrelude {
name = "network";
dependencies = [
prelude
base
linear
];
}

View File

@@ -0,0 +1,35 @@
{
lib,
newScope,
fetchFromGitHub,
}:
let
idris2CompilerPackages = lib.makeScope newScope (
self:
let
inherit (self) callPackage;
in
{
# Compiler version & repo
idris2-version = "0.7.0";
idris2-src = fetchFromGitHub {
owner = "idris-lang";
repo = "Idris2";
rev = "v${self.idris2-version}";
hash = "sha256-VwveX3fZfrxEsytpbOc5Tm6rySpLFhTt5132J6rmrmM=";
};
# Prelude libraries
mkPrelude = callPackage ./mkPrelude.nix { }; # Build helper
prelude = callPackage ./prelude.nix { };
base = callPackage ./base.nix { };
linear = callPackage ./linear.nix { };
network = callPackage ./network.nix { };
contrib = callPackage ./contrib.nix { };
test = callPackage ./test.nix { };
libidris2_support = callPackage ./libidris2_support.nix { };
idris2-unwrapped = callPackage ./unwrapped.nix { };
}
);
in
idris2CompilerPackages.idris2-unwrapped.withPackages (_: [ ])

View File

@@ -0,0 +1,6 @@
{
mkPrelude,
}:
mkPrelude {
name = "prelude";
}

View File

@@ -0,0 +1,14 @@
{
mkPrelude,
prelude,
base,
contrib,
}:
mkPrelude {
name = "test";
dependencies = [
prelude
base
contrib
];
}

View File

@@ -2,9 +2,9 @@
stdenv,
runCommand,
lib,
pname,
idris2,
idris2Packages,
chez,
zsh,
tree,
}:
@@ -18,6 +18,7 @@ let
packages ? [ ],
}:
let
inherit (idris2) pname;
packageString = builtins.concatStringsSep " " (map (p: "--package " + p) packages);
in
runCommand "${pname}-${testName}"
@@ -28,7 +29,8 @@ let
# is not the case with pure nix environments. Thus, we need to include zsh
# when we build for darwin in tests. While this is impure, this is also what
# we find in real darwin hosts.
nativeBuildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ zsh ];
strictDeps = true;
nativeBuildInputs = [ chez ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ zsh ];
}
''
set -eo pipefail
@@ -39,6 +41,7 @@ let
${idris2}/bin/idris2 ${packageString} -o packageTest packageTest.idr
patchShebangs --build ./build/exec/packageTest
GOT=$(./build/exec/packageTest)
if [ "$GOT" = "${want}" ]; then
@@ -61,12 +64,14 @@ let
expectedTree,
}:
let
inherit (idris2) pname;
idrisPkg = transformBuildIdrisOutput (idris2Packages.buildIdris buildIdrisArgs);
in
runCommand "${pname}-${testName}"
{
meta.timeout = 60;
strictDeps = true;
nativeBuildInputs = [ tree ];
}
''

View File

@@ -0,0 +1,173 @@
{
lib,
stdenv,
chez,
chez-racket,
clang,
gmp,
installShellFiles,
gambit,
nodejs,
zsh,
callPackage,
idris2Packages,
testers,
libidris2_support,
idris2-version,
idris2-src,
}:
let
inherit (stdenv.hostPlatform) extensions;
# Runtime library
libsupportLib = lib.makeLibraryPath [ libidris2_support ];
libsupportShare = lib.makeSearchPath "share" [ libidris2_support ];
platformChez =
if (stdenv.system == "x86_64-linux") || (lib.versionAtLeast chez.version "10.0.0") then
chez
else
chez-racket;
in
stdenv.mkDerivation (finalAttrs: {
pname = "idris2";
version = idris2-version;
src = idris2-src;
postPatch = ''
shopt -s globstar
# Patch all occurences of the support lib with an absolute path so it
# works without wrapping.
substituteInPlace **/*.idr \
--replace-quiet "libidris2_support" "${libidris2_support}/lib/libidris2_support${extensions.sharedLibrary}"
# The remove changes libidris2_support.a to /nix/store/..../libidris2_support.so.a
# Fix that up so the reference-counted C backend works
substituteInPlace src/Compiler/RefC/CC.idr \
--replace-fail "libidris2_support${extensions.sharedLibrary}.a" "libidris2_support.a"
substituteInPlace bootstrap-stage2.sh \
--replace-fail "MAKE all" "MAKE idris2-exec"
patchShebangs --build tests
'';
strictDeps = true;
nativeBuildInputs = [
clang
platformChez
installShellFiles
]
++ lib.optionals stdenv.hostPlatform.isDarwin [ zsh ];
buildInputs = [
platformChez
gmp
libidris2_support
];
enableParallelBuilding = true;
makeFlags = [
"PREFIX=${placeholder "out"}"
"IDRIS2_SUPPORT_DIR=${libsupportLib}"
]
++ lib.optional stdenv.hostPlatform.isDarwin "OS=";
# The name of the main executable of pkgs.chez is `scheme`
buildFlags = [
"bootstrap"
"SCHEME=scheme"
"IDRIS2_LIBS=${libsupportLib}"
"IDRIS2_DATA=${libsupportShare}"
];
doCheck = false;
checkTarget = "test";
nativeCheckInputs = [
gambit
nodejs
];
checkFlags = [
"INTERACTIVE="
"IDRIS2_DATA=${libsupportShare}"
"IDRIS2_LIBS=${libsupportLib}"
"TEST_IDRIS2_DATA=${libsupportShare}"
"TEST_IDRIS2_LIBS=${libsupportLib}"
"TEST_IDRIS2_SUPPORT_DIR=${libsupportLib}"
];
installTargets = "install-idris2";
postInstall = ''
# Remove existing idris2 wrapper that sets incorrect LD_LIBRARY_PATH
rm $out/bin/idris2
# The only thing we need from idris2_app is the actual binary, which is a Chez
# scheme object and for some reason *.so on darwin too
mv $out/bin/idris2_app/idris2.so $out/bin/idris2
rm -rf $out/bin/idris2_app
''
+ lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
installShellCompletion --cmd idris2 \
--bash <($out/bin/idris2 --bash-completion-script idris2)
'';
# Run package tests
passthru = {
inherit libidris2_support;
tests = {
wrapped = testers.testVersion {
package = finalAttrs.finalPackage.withPackages (p: [ p.idris2Api ]);
};
prelude = testers.runCommand {
name = "idris2-prelude-wrapped";
script = ''
local packages=$(idris2 --list-packages)
if ! [[ $packages =~ "contrib" ]]; then
exit 1
fi
touch "$out"
'';
nativeBuildInputs = [
(finalAttrs.finalPackage.withPackages (_: [ ]))
];
};
}
// (callPackage ./tests.nix {
idris2 = finalAttrs.finalPackage.withPackages (_: [ ]);
idris2Packages = idris2Packages.override { idris2 = finalAttrs.finalPackage; };
});
chez = platformChez;
withPackages =
f:
callPackage ./wrapped.nix {
idris2-unwrapped = finalAttrs.finalPackage;
extraPackages = f idris2Packages;
};
updateScript = ./update.nu;
};
meta = {
description = "Purely functional programming language with first class types";
mainProgram = "idris2";
homepage = "https://github.com/idris-lang/Idris2";
changelog = "https://github.com/idris-lang/Idris2/releases/tag/v${finalAttrs.version}";
license = lib.licenses.bsd3;
maintainers = with lib.maintainers; [
fabianhjr
wchresta
mattpolzin
RossSmyth
];
platforms = lib.platforms.all;
};
})

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env nix-shell
#! nix-shell -I ./.
#! nix-shell -i nu
#! nix-shell -p nushell nix
const PACKAGE = './pkgs/by-name/id/idris2/package.nix'
def main [] {
let tag = http get "https://api.github.com/repos/idris-lang/Idris2/releases"
| sort-by -r created_at
| first
| get tag_name
print $"Newest version: ($tag)"
let hash = run-external "nix" "flake" "prefetch" "--json" $"github:idris-lang/Idris2/($tag)"
| from json
| get hash
let current_hash = nix eval -f ./. idris2.unwrapped.src.outputHash --json | from json
let current_version = nix eval -f ./. idris2.version --json | from json
$PACKAGE
| open
| str replace $current_version ($tag | str trim -c 'v')
| str replace $current_hash $hash
| save -f $PACKAGE
}

View File

@@ -0,0 +1,66 @@
{
lib,
makeBinaryWrapper,
symlinkJoin,
idris2-unwrapped,
prelude,
linear,
base,
network,
contrib,
test,
extraPackages ? [ ],
}:
let
preludeLibs = [
prelude
linear
base
network
contrib
test
];
supportLibrariesPath = lib.makeLibraryPath [ idris2-unwrapped.libidris2_support ];
supportSharePath = lib.makeSearchPath "share" [ idris2-unwrapped.libidris2_support ];
packagePath = lib.makeSearchPath "idris2-${idris2-unwrapped.version}" (
preludeLibs ++ extraPackages
);
in
symlinkJoin {
inherit (idris2-unwrapped) version;
pname = "idris2-wrapped";
paths = [ idris2-unwrapped ];
nativeBuildInputs = [ makeBinaryWrapper ];
postBuild = ''
wrapProgram "$out/bin/idris2" \
--set CHEZ "${lib.getExe idris2-unwrapped.chez}" \
--suffix IDRIS2_LIBS ':' "${supportLibrariesPath}" \
--suffix IDRIS2_DATA ':' "${supportSharePath}" \
--suffix IDRIS2_PACKAGE_PATH ':' ${packagePath} \
--suffix LD_LIBRARY_PATH ':' "${supportLibrariesPath}" \
--suffix DYLD_LIBRARY_PATH ':' "${supportLibrariesPath}"
'';
passthru = {
prelude = preludeLibs;
unwrapped = idris2-unwrapped;
src = idris2-unwrapped.src;
}
// idris2-unwrapped.passthru;
meta = {
# Manually inherit so that pos works
inherit (idris2-unwrapped.meta)
description
mainProgram
homepage
changelog
license
maintainers
platforms
;
};
}

View File

@@ -49,8 +49,8 @@ let
idrName = "idris2-${idris2.version}";
libSuffix = "lib/${idrName}";
libDirs = libs: (lib.makeSearchPath libSuffix libs) + ":${idris2}/${idrName}";
supportDir = "${idris2}/${idrName}/lib";
drvAttrs = removeAttrs attrs [
supportDir = "${idris2.libidris2_support}/lib";
drvAttrs = builtins.removeAttrs attrs [
"ipkgName"
"idrisLibraries"
];

View File

@@ -1,6 +1,6 @@
{ callPackage }:
{ callPackage, idris2 }:
{
idris2 = callPackage ./idris2.nix { };
inherit idris2;
idris2Api = callPackage ./idris2-api.nix { };
idris2Lsp = callPackage ./idris2-lsp.nix { };

View File

@@ -2,7 +2,7 @@
let
inherit (idris2Packages) idris2 buildIdris;
apiPkg = buildIdris {
inherit (idris2) src version;
inherit (idris2.unwrapped) src version;
ipkgName = "idris2api";
idrisLibraries = [ ];
preBuild = ''

View File

@@ -6,17 +6,18 @@
}:
let
globalLibraries =
globalLibrariesPath =
let
idrName = "idris2-${idris2Packages.idris2.version}";
libSuffix = "lib/${idrName}";
in
lib.makeSearchPath idrName (
[
"\\$HOME/.nix-profile/lib/${idrName}"
"/run/current-system/sw/lib/${idrName}"
"${idris2Packages.idris2}/${idrName}"
];
globalLibrariesPath = builtins.concatStringsSep ":" globalLibraries;
"\\$HOME/.nix-profile/lib/"
"/run/current-system/sw/lib/"
"${idris2Packages.idris2}"
]
++ idris2Packages.idris2.prelude
);
inherit (idris2Packages) idris2Api;
lspLib = idris2Packages.buildIdris {

View File

@@ -1,119 +0,0 @@
# Almost 1:1 copy of idris2's nix/package.nix. Some work done in their flake.nix
# we do here instead.
{
stdenv,
lib,
chez,
chez-racket,
clang,
gmp,
fetchFromGitHub,
makeWrapper,
gambit,
nodejs,
zsh,
callPackage,
}:
# NOTICE: An `idris2WithPackages` is available at: https://github.com/claymager/idris2-pkgs
let
platformChez =
if (stdenv.system == "x86_64-linux") || (lib.versionAtLeast chez.version "10.0.0") then
chez
else
chez-racket;
in
stdenv.mkDerivation rec {
pname = "idris2";
version = "0.7.0";
src = fetchFromGitHub {
owner = "idris-lang";
repo = "Idris2";
rev = "v${version}";
sha256 = "sha256-VwveX3fZfrxEsytpbOc5Tm6rySpLFhTt5132J6rmrmM=";
};
strictDeps = true;
nativeBuildInputs = [
makeWrapper
clang
platformChez
]
++ lib.optionals stdenv.hostPlatform.isDarwin [ zsh ];
buildInputs = [
platformChez
gmp
];
prePatch = ''
patchShebangs --build tests
'';
makeFlags = [ "PREFIX=$(out)" ] ++ lib.optional stdenv.hostPlatform.isDarwin "OS=";
# The name of the main executable of pkgs.chez is `scheme`
buildFlags = [
"bootstrap"
"SCHEME=scheme"
];
checkTarget = "test";
nativeCheckInputs = [
gambit
nodejs
]; # racket ];
checkFlags = [ "INTERACTIVE=" ];
# TODO: Move this into its own derivation, such that this can be changed
# without having to recompile idris2 every time.
postInstall =
let
name = "${pname}-${version}";
globalLibraries = [
"\\$HOME/.nix-profile/lib/${name}"
"/run/current-system/sw/lib/${name}"
"$out/${name}"
];
globalLibrariesPath = builtins.concatStringsSep ":" globalLibraries;
in
''
# Remove existing idris2 wrapper that sets incorrect LD_LIBRARY_PATH
rm $out/bin/idris2
# The only thing we need from idris2_app is the actual binary
mv $out/bin/idris2_app/idris2.so $out/bin/idris2
rm $out/bin/idris2_app/*
rmdir $out/bin/idris2_app
# idris2 needs to find scheme at runtime to compile
# idris2 installs packages with --install into the path given by
# IDRIS2_PREFIX. We set that to a default of ~/.idris2, to mirror the
# behaviour of the standard Makefile install.
# TODO: Make support libraries their own derivation such that
# overriding LD_LIBRARY_PATH is unnecessary
wrapProgram "$out/bin/idris2" \
--set-default CHEZ "${platformChez}/bin/scheme" \
--run 'export IDRIS2_PREFIX=''${IDRIS2_PREFIX-"$HOME/.idris2"}' \
--suffix IDRIS2_LIBS ':' "$out/${name}/lib" \
--suffix IDRIS2_DATA ':' "$out/${name}/support" \
--suffix IDRIS2_PACKAGE_PATH ':' "${globalLibrariesPath}" \
--suffix DYLD_LIBRARY_PATH ':' "$out/${name}/lib" \
--suffix LD_LIBRARY_PATH ':' "$out/${name}/lib"
'';
# Run package tests
passthru.tests = callPackage ./tests.nix { inherit pname; };
meta = {
description = "Purely functional programming language with first class types";
mainProgram = "idris2";
homepage = "https://github.com/idris-lang/Idris2";
license = lib.licenses.bsd3;
maintainers = with lib.maintainers; [
fabianhjr
wchresta
mattpolzin
];
inherit (chez.meta) platforms;
};
}

View File

@@ -5048,8 +5048,6 @@ with pkgs;
idris2Packages = recurseIntoAttrs (callPackage ../development/compilers/idris2 { });
inherit (idris2Packages) idris2;
inherit (callPackage ../development/tools/database/indradb { })
indradb-server
indradb-client