diff --git a/builders.ncl b/builders.ncl index 3e2cbbd7..18d3b908 100644 --- a/builders.ncl +++ b/builders.ncl @@ -19,12 +19,12 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in name = "hello", version = "0.1", build_command = { - cmd = nix-s%"%{inputs.bash}/bin/bash"%, + cmd = nix-s%"%{inputs.nixpkgs.bash}/bin/bash"%, args = [ "-c", nix-s%" - %{inputs.gcc}/bin/gcc %{nix.lib.import_file "hello.c"} -o hello - %{inputs.coreutils}/bin/mkdir -p $out/bin - %{inputs.coreutils}/bin/cp hello $out/bin/hello + %{inputs.nixpkgs.gcc}/bin/gcc %{nix.lib.import_file "hello.c"} -o hello + %{inputs.nixpkgs.coreutils}/bin/mkdir -p $out/bin + %{inputs.nixpkgs.coreutils}/bin/cp hello $out/bin/hello "% ] }, @@ -35,16 +35,7 @@ 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_spec = {}, inputs, output = @@ -54,16 +45,16 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in # This is required otherwise nix develop will fail with a message that it # only supports bash. build_command = { - cmd = nix-s%"%{inputs.bash}/bin/bash"%, + cmd = nix-s%"%{inputs.nixpkgs.bash}/bin/bash"%, args = [], }, structured_env = { # TODO: handle naked derivations without having to interpolate - stdenv.naked_std_env = nix-s%"%{inputs.naked_std_env}"%, + stdenv.naked_std_env = nix-s%"%{inputs.nickel-nix-internals.naked_std_env}"%, }, - packages = { bash = inputs.bash }, + packages = { bash = inputs.nixpkgs.bash }, structured_env.PATH = packages @@ -94,64 +85,50 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in RustShell = BashShell & { - inputs_spec = { - cargo = {}, - rustc = {}, - rustfmt = {}, - rust-analyzer = {}, - }, + inputs_spec = {}, inputs, output.packages = { - cargo = inputs.cargo, - rustc = inputs.rustc, - rustfmt = inputs.rustfmt, - rust-analyzer = inputs.rust-analyzer, + cargo = inputs.nixpkgs.cargo, + rustc = inputs.nixpkgs.rustc, + rustfmt = inputs.nixpkgs.rustfmt, + rust-analyzer = inputs.nixpkgs.rust-analyzer, }, }, GoShell = BashShell & { - inputs_spec = { - go = {}, - gopls = {}, - }, + inputs_spec = {}, inputs, output.packages = { - go = inputs.go, - gopls = inputs.gopls, + go = inputs.nixpkgs.go, + gopls = inputs.nixpkgs.gopls, }, }, ClojureShell = BashShell & { - inputs_spec = { - clojure = {}, - clojure-lsp = {}, - }, + inputs_spec = {}, inputs, output.packages = { - clojure = inputs.clojure, - clojure-lsp = inputs.clojure-lsp, + clojure = inputs.nixpkgs.clojure, + clojure-lsp = inputs.nixpkgs.clojure-lsp, }, }, CShell = BashShell & { - inputs_spec = { - clang = {}, - clang-tools = {}, - }, + inputs_spec = {}, inputs, output.packages = { - clang = inputs.clang, - clang-tools = inputs.clang-tools, + clang = inputs.nixpkgs.clang, + clang-tools = inputs.nixpkgs.clang-tools, }, }, @@ -159,32 +136,24 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in PhpShell = BashShell & { - inputs_spec = { - php = {}, - intelephense = { - path = "nodePackages"."intelephense", - }, - }, + inputs_spec = {}, inputs, output.packages = { - php = inputs.php, - intelephense = inputs.intelephense, + php = inputs.nixpkgs.php, + intelephense = inputs.nixpkgs.nodePackages.intelephense, }, }, ZigShell = BashShell & { - inputs_spec = { - zig = {}, - zls = {}, - }, + inputs_spec = {}, inputs, output.packages = { - zig = inputs.zig, - zls = inputs.zls, + zig = inputs.nixpkgs.zig, + zls = inputs.nixpkgs.zls, }, }, @@ -192,45 +161,35 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in JavascriptShell = BashShell & { - inputs_spec = { - nodejs = {}, - typescript-language-server = { - path = "nodePackages_latest.typescript-language-server", - } - }, + inputs_spec = {}, inputs, output.packages = { - nodejs = inputs.nodejs, - ts-lsp = inputs.typescript-language-server, + nodejs = inputs.nixpkgs.nodejs, + ts-lsp = inputs.nixpkgs.nodePackages_latest.typescript-language-server, }, }, RacketShell = BashShell & { - inputs_spec = { - racket = {}, - }, + inputs_spec = {}, inputs, output.packages = { - racket = inputs.racket, + racket = inputs.nixpkgs.racket, }, }, ScalaShell = BashShell & { - inputs_spec = { - scala = {}, - metals = {}, - }, + inputs_spec = {}, inputs, output.packages = { - scala = inputs.scala, - metals = inputs.metals, + scala = inputs.nixpkgs.scala, + metals = inputs.nixpkgs.metals, }, }, @@ -239,32 +198,24 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in Python310Shell = BashShell & { - inputs_spec = { - python310 = {}, - python-lsp-server = { - path = "python310Packages.python-lsp-server", - } - }, + inputs_spec = {}, inputs, output.packages = { - python = inputs.python310, - python-lsp = inputs.python-lsp-server, + python = inputs.nixpkgs.python310, + python-lsp = inputs.nixpkgs.python310Packages.python-lsp-server, }, }, ErlangShell = BashShell & { - inputs_spec = { - erlang = {}, - erlang-ls = {}, - }, + inputs_spec = {}, inputs, output.packages = { - erlang = inputs.erlang, - erlang-lsp = inputs.erlang-ls, + erlang = inputs.nixpkgs.erlang, + erlang-lsp = inputs.nixpkgs.erlang-ls, }, }, @@ -273,14 +224,6 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in & { ghcVersion, # User-defined. To keep in sync with the one used by stack inputs_spec = { - stack = {}, - ormolu = {}, - nix = {}, - git = {}, - coreutils = {}, - haskell-language-server = { - path = "haskell.packages.ghc%{ghcVersion}.haskell-language-server", - }, # This will point to a copy of nixpkgs in nix store path = {}, }, @@ -290,21 +233,21 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in let stack-wrapped = { name = "stack-wrapped", - version = inputs.stack.version, + version = inputs.nixpkgs.stack.version, build_command = { - cmd = nix-s%"%{inputs.bash}/bin/bash"%, + cmd = nix-s%"%{inputs.nixpkgs.bash}/bin/bash"%, args = [ "-c", # Sorry about Topiary formatting of the following lines nix-s%" - export PATH="%{inputs.coreutils}/bin:$PATH" + export PATH="%{inputs.nixpkgs.coreutils}/bin:$PATH" mkdir -p $out/bin echo "$0" > $out/bin/stack chmod a+x $out/bin/* "%, nix-s%" - #!%{inputs.bash}/bin/bash - %{inputs.stack}/bin/stack \ + #!%{inputs.nixpkgs.bash}/bin/bash + %{inputs.nixpkgs.stack}/bin/stack \ --nix \ --no-nix-pure \ --nix-path="nixpkgs=%{inputs.path}" \ @@ -316,11 +259,11 @@ let { NickelDerivation, Derivation, .. } = import "contracts.ncl" in in { stack = stack-wrapped, - stack' = inputs.stack, - ormolu = inputs.ormolu, - nix = inputs.nix, - git = inputs.git, - haskell-language-server = inputs.haskell-language-server, + stack' = inputs.nixpkgs.stack, + ormolu = inputs.nixpkgs.ormolu, + nix = inputs.nixpkgs.nix, + git = inputs.nixpkgs.git, + haskell-language-server = inputs.nixpkgs.haskell.packages."ghc%{ghcVersion}".haskell-language-server, }, }, } diff --git a/flake.nix b/flake.nix index 2cf51d70..496405e3 100644 --- a/flake.nix +++ b/flake.nix @@ -104,7 +104,8 @@ # 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"; + # Use importNclInternal to avoid infinite recursion over inputs + naked_std_env = lib.importNclInternal ./. "naked-stdenv.ncl"; }; # Helper function that generates ugly contents for "nickel.lock.ncl", see buildLockFile. diff --git a/lib.nix b/lib.nix index aa257b40..ec413600 100644 --- a/lib.nix +++ b/lib.nix @@ -109,10 +109,10 @@ // value.env; # Import a Nickel value produced by the Nixel DSL - importFromNickel = context: system: baseDir: value: let + importFromNickel = flakeInputs: system: baseDir: value: let type = builtins.typeOf value; isNickelDerivation = type: type == "nickelDerivation"; - importFromNickel_ = importFromNickel context system baseDir; + importFromNickel_ = importFromNickel flakeInputs system baseDir; in if (type == "set") then @@ -128,7 +128,7 @@ in derivation prepared else if nixelType == "nixDerivation" - then context.${value.drvPath}.${value.outputName or "out"} + then lib.getAttrFromPath value.attrPath flakeInputs else if nixelType == "nixString" then builtins.concatStringsSep "" (builtins.map importFromNickel_ value.fragments) else if nixelType == "nixPath" @@ -145,6 +145,7 @@ baseDir, nickelFile, exportedPkgsNcl, + flakeInputs, }: let sources = builtins.path { path = baseDir; @@ -159,7 +160,15 @@ nickelWithImports = builtins.toFile "eval.ncl" '' let params = { - inputs = import "${exportedJSON}", + inputs = (import "${exportedJSON}") & { + ${lib.concatStrings (lib.mapAttrsToList (name: input: '' + "${name}" = import "${ + # FIXME: Should cache in Nix derivation, but evaluating Nix inside Nix is troublesome + builtins.toFile "${name}.ncl" (builtins.unsafeDiscardStringContext (collectPkgs name input)) + }", + '') + flakeInputs)} + }, system = "${system}", nix = import "${./.}/nix.ncl", } @@ -276,7 +285,7 @@ {inherit declaredInputs flakeInputs baseDir;}; pkgsExportResult = exportForNickel exportedPkgs.value; fileToCall = computeNickelFile { - inherit baseDir nickelFile; + inherit baseDir nickelFile flakeInputs; exportedPkgsNcl = pkgsExportResult.value; }; in @@ -291,18 +300,98 @@ # 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: flakeInputs: let - flakeInputsWithInternals = - flakeInputs + importNcl = baseDir: nickelFile: flakeInputs: + importNclInternal baseDir nickelFile (flakeInputs // { - nickel-nix-internals = builtins.mapAttrs (_: f: f flakeInputs) nickel-nix-internals; - }; + nickel-nix-internals = {packages.${system} = builtins.mapAttrs (_: f: f flakeInputs) nickel-nix-internals;}; + }); + + importNclInternal = baseDir: nickelFile: flakeInputs: let nickelResult = callNickel { - inherit nickelFile baseDir; - flakeInputs = flakeInputsWithInternals; + inherit nickelFile baseDir flakeInputs; }; in {rawNickel = nickelResult.value;} - // lib.traceVal (importFromNickel nickelResult.context system baseDir (builtins.fromJSON + // lib.traceVal (importFromNickel flakeInputs system baseDir (builtins.fromJSON (builtins.unsafeDiscardStringContext (builtins.readFile nickelResult.value)))); -in {inherit importNcl;} + + doCollect = attrPath: drvF: attrs: let + try = builtins.tryEval attrs; + result = try.value; + in + if !try.success || !(lib.isAttrs result) + then [] + else if lib.isDerivation result + then [(drvF attrPath result)] + else if attrPath != [] && (!(result.recurseForDerivations or false)) + then [] + else + [(lib.optionalString (attrPath != []) "\"${lib.last attrPath}\" = ") "{\n"] + ++ lib.concatLists (lib.mapAttrsToList (name: doCollect (attrPath ++ [name]) drvF) result) + ++ ["}" (lib.optionalString (attrPath != []) ",\n")]; + + collectPkgs = name: input': let + input = + if name != "nixpkgs" + then input' + else smallNixpkgs input'; + listToNcl = sep: lst: lib.concatStringsSep sep (map (x: ''"${x}"'') lst); + drvF = attrPrefix: attrPath: drv: '' + "${lib.last attrPath}"={${ + ''"${typeField}"="nixDerivation",'' + + ''outputPath="${drv.outPath}",'' + + (lib.optionalString (drv ? version) ''version="${drv.version}",'') + + ''attrPath=[${listToNcl "," (attrPrefix ++ attrPath)}]'' + }}, + ''; + drvsWithPaths = lib.concatLists ( + (lib.optional (input ? packages && input.packages ? ${system}) (doCollect [] (drvF [name "packages" system]) input.packages.${system})) + ++ (lib.optional (input ? legacyPackages && input.legacyPackages ? ${system}) (doCollect [] (drvF [name "legacyPackages" system]) input.legacyPackages.${system})) + ); + in + if builtins.length drvsWithPaths == 0 + then "{}" + else lib.concatStrings drvsWithPaths; + + # FIXME: This is only a small subset of nixpkgs required by our shells. + # Full nixpkgs cannot be handled by Nickel because of + # https://github.com/tweag/nickel/issues/1427 and https://github.com/tweag/nickel/issues/1428 + smallNixpkgs = nixpkgs: { + legacyPackages.${system} = { + inherit + (nixpkgs.legacyPackages.${system}) + bash + cargo + clang + clang-tools + clojure + clojure-lsp + coreutils + erlang + erlang-ls + git + go + gopls + metals + nix + nodejs + ormolu + path + php + python310 + racket + rust-analyzer + rustc + rustfmt + scala + stack + zig + zls + ; + haskell.packages.ghc927.haskell-language-server = nixpkgs.legacyPackages.${system}.haskell.packages.ghc927.haskell-language-server; + haskell.packages.ghc927.recurseForDerivations = true; + haskell.packages.recurseForDerivations = true; + haskell.recurseForDerivations = true; + }; + }; +in {inherit importNcl importNclInternal;} diff --git a/naked-stdenv.ncl b/naked-stdenv.ncl index 34cf792b..c1363d77 100644 --- a/naked-stdenv.ncl +++ b/naked-stdenv.ncl @@ -1,8 +1,5 @@ { - inputs_spec = { - bash = { input = "nixpkgs" }, - coreutils = { input = "nixpkgs" }, - }, + inputs_spec = {}, # parameters inputs, @@ -13,14 +10,14 @@ name = "naked-stdenv", version = "0.1", build_command = { - cmd = nix-s%"%{inputs.bash}/bin/bash"%, + cmd = nix-s%"%{inputs.nixpkgs.bash}/bin/bash"%, args = [ "-c", nix-s%" - %{inputs.coreutils}/bin/mkdir -p $out + %{inputs.nixpkgs.coreutils}/bin/mkdir -p $out target=$out/setup - %{inputs.coreutils}/bin/touch $target - %{inputs.coreutils}/bin/cp %{nix.lib.import_file "naked-stdenv.sh"} $target + %{inputs.nixpkgs.coreutils}/bin/touch $target + %{inputs.nixpkgs.coreutils}/bin/cp %{nix.lib.import_file "naked-stdenv.sh"} $target "%, ], },