Support flake attr nixosModule (#401)

* flake_info.nix: remove helper fn `default`

Nix has builtin syntax for fetching attrs with a default value.

* flake_info.nix: minor refactor

- Remove commented out code
- Simplify pkg filtering
- Break overlong line

* flake_info.nix: support flake attr `nixosModule`

* Support default modules in rust backend and elm

Co-authored-by: Yannik Sander <me@ysndr.de>
This commit is contained in:
erikarvstedt 2022-01-06 15:42:31 +01:00 committed by GitHub
parent 32700e3eee
commit 096ea9e49c
Failed to generate hash of commit
4 changed files with 99 additions and 54 deletions

View file

@ -5,43 +5,32 @@ let
nixpkgs = (import <nixpkgs> {}); nixpkgs = (import <nixpkgs> {});
lib = nixpkgs.lib; lib = nixpkgs.lib;
default = drv: attr: default: if drv ? ${attr} then drv.${attr} else default;
# filter = lib.filterAttrs (key: _ : key == "apps" || key == "packages"); # filter = lib.filterAttrs (key: _ : key == "apps" || key == "packages");
withSystem = fn: lib.mapAttrs (system: drvs: (fn system drvs)); withSystem = fn: lib.mapAttrs (system: drvs: (fn system drvs));
isValid = d: isValid = d:
let let
r = builtins.tryEval (lib.isDerivation d && ! (lib.attrByPath [ "meta" "broken" ] false d) && builtins.seq d.name true && d ? outputs); r = builtins.tryEval (lib.isDerivation d && ! (lib.attrByPath [ "meta" "broken" ] false d) &&
builtins.seq d.name true && d ? outputs);
in in
r.success && r.value; r.success && r.value;
all = pkgs: validPkgs = lib.filterAttrs (k: v: isValid v);
let
validPkgs = lib.filterAttrs (k: v: isValid v) pkgs;
in
validPkgs;
readPackages = system: drvs: lib.mapAttrsToList ( readPackages = system: drvs: lib.mapAttrsToList (
attribute_name: drv: ( attribute_name: drv: (
# if isValid drv then
{ {
attribute_name = attribute_name; attribute_name = attribute_name;
system = system; system = system;
name = drv.name; name = drv.name;
# TODO consider using `builtins.parseDrvName` # TODO consider using `builtins.parseDrvName`
version = default drv "version" ""; version = drv.version or "";
outputs = drv.outputs; outputs = drv.outputs;
# paths = builtins.listToAttrs ( map (output: {name = output; value = drv.${output};}) drv.outputs ); # paths = builtins.listToAttrs ( map (output: {name = output; value = drv.${output};}) drv.outputs );
} }
// lib.optionalAttrs (drv ? meta && drv.meta ? description) { inherit (drv.meta) description; } // lib.optionalAttrs (drv ? meta && drv.meta ? description) { inherit (drv.meta) description; }
// lib.optionalAttrs (drv ? meta && drv.meta ? license) { inherit (drv.meta) license; } // lib.optionalAttrs (drv ? meta && drv.meta ? license) { inherit (drv.meta) license; }
# else {}
) )
) (all drvs); ) (validPkgs drvs);
readApps = system: apps: lib.mapAttrsToList ( readApps = system: apps: lib.mapAttrsToList (
attribute_name: app: ( attribute_name: app: (
{ {
@ -54,8 +43,7 @@ let
) )
) apps; ) apps;
readOptions = modules: isNixOS: let readOptions = let
declarations = module: ( declarations = module: (
lib.evalModules { lib.evalModules {
modules = (if lib.isList module then module else [ module ]) ++ [ modules = (if lib.isList module then module else [ module ]) ++ [
@ -70,7 +58,7 @@ let
} }
).options; ).options;
cleanUpOption = module: opt: cleanUpOption = extraAttrs: opt:
let let
applyOnAttr = n: f: lib.optionalAttrs (builtins.hasAttr n opt) { ${n} = f opt.${n}; }; applyOnAttr = n: f: lib.optionalAttrs (builtins.hasAttr n opt) { ${n} = f opt.${n}; };
mkDeclaration = decl: mkDeclaration = decl:
@ -96,31 +84,47 @@ let
// applyOnAttr "example" substFunction # (_: { __type = "function"; }) // applyOnAttr "example" substFunction # (_: { __type = "function"; })
// applyOnAttr "type" substFunction // applyOnAttr "type" substFunction
// applyOnAttr "declarations" (map mkDeclaration) // applyOnAttr "declarations" (map mkDeclaration)
// lib.optionalAttrs (!isNixOS) { flake = [ flake module ]; }; // extraAttrs;
options = lib.mapAttrs (
attr: module: let
list = lib.optionAttrSetToDocList (declarations module);
in
map (cleanUpOption attr) (lib.filter (x: !x.internal) list)
) modules;
in in
lib.flatten (builtins.attrValues options); { module, modulePath ? null }: let
opts = lib.optionAttrSetToDocList (declarations module);
extraAttrs = lib.optionalAttrs (modulePath != null) {
flake = modulePath;
};
in
map (cleanUpOption extraAttrs) (lib.filter (x: !x.internal) opts);
readFlakeOptions = let
nixosModulesOpts = builtins.concatLists (lib.mapAttrsToList (moduleName: module:
readOptions {
inherit module;
modulePath = [ flake moduleName ];
}
) (resolved.nixosModules or {}));
nixosModuleOpts = lib.optionals (resolved ? nixosModule) (
readOptions {
module = resolved.nixosModule;
modulePath = [ flake ];
}
);
in
# We assume that `nixosModules` includes `nixosModule` when there
# are multiple modules
if nixosModulesOpts != [] then nixosModulesOpts else nixosModuleOpts;
read = reader: set: lib.flatten (lib.attrValues (withSystem reader set)); read = reader: set: lib.flatten (lib.attrValues (withSystem reader set));
legacyPackages' = read readPackages (default resolved "legacyPackages" {}); legacyPackages' = read readPackages (resolved.legacyPackages or {});
packages' = read readPackages (default resolved "packages" {}); packages' = read readPackages (resolved.packages or {});
apps' = read readApps (default resolved "apps" {}); apps' = read readApps (resolved.apps or {});
collectSystems = lib.lists.foldr ( collectSystems = lib.lists.foldr (
drv@{ attribute_name, system, ... }: set: drv@{ attribute_name, system, ... }: set:
let let
present = default set "${attribute_name}" ({ platforms = []; } // drv); present = set."${attribute_name}" or ({ platforms = []; } // drv);
drv' = present // { drv' = present // {
platforms = present.platforms ++ [ system ]; platforms = present.platforms ++ [ system ];
@ -138,11 +142,9 @@ rec {
legacyPackages = lib.attrValues (collectSystems legacyPackages'); legacyPackages = lib.attrValues (collectSystems legacyPackages');
packages = lib.attrValues (collectSystems packages'); packages = lib.attrValues (collectSystems packages');
apps = lib.attrValues (collectSystems apps'); apps = lib.attrValues (collectSystems apps');
options = readOptions (default resolved "nixosModules" {}) false; options = readFlakeOptions;
nixos-options = readOptions ( nixos-options = readOptions {
{ module = import "${builtins.fetchTarball { url = flake; }}/nixos/modules/module-list.nix";
"nixos" = import "${builtins.fetchTarball { url = flake; }}/nixos/modules/module-list.nix"; };
}
) true;
all = packages ++ apps ++ options; all = packages ++ apps ++ options;
} }

View file

@ -3,7 +3,7 @@
/// Flakes, or Nixpkgs. /// Flakes, or Nixpkgs.
use std::{convert::TryInto, path::PathBuf}; use std::{convert::TryInto, path::PathBuf};
use super::{import::DocValue, pandoc::PandocExt}; use super::{import::{DocValue, ModulePath}, pandoc::PandocExt};
use crate::data::import::NixOption; use crate::data::import::NixOption;
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -110,7 +110,7 @@ pub enum Derivation {
option_example: Option<DocValue>, option_example: Option<DocValue>,
option_flake: Option<(String, String)>, option_flake: Option<ModulePath>,
}, },
} }

View file

@ -63,8 +63,18 @@ pub struct NixOption {
pub default: Option<DocValue>, pub default: Option<DocValue>,
pub example: Option<DocValue>, pub example: Option<DocValue>,
/// If defined in a flake, contains defining flake and module /// If defined in a flake, contains defining flake and optionally a module
pub flake: Option<(String, String)>, pub flake: Option<ModulePath>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ModulePath {
/// A module taken from <flake>.nixosModule
/// JSON representation is a list, therefore use a 1-Tuple as representation
DefaultModule((String,)),
/// A module taken from <flake>.nixosModules.<name>
NamedModule((String, String)),
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Deserialize)]
@ -345,6 +355,32 @@ mod tests {
.collect(); .collect();
} }
#[test]
fn test_flake_option() {
let json = r#"
{
"declarations": [],
"name": "test-option",
"flake": ["flake", "module"]
}
"#;
serde_json::from_str::<NixOption>(json).unwrap();
}
#[test]
fn test_flake_option_default_module() {
let json = r#"
{
"declarations": [],
"name": "test-option",
"flake": ["flake"]
}
"#;
serde_json::from_str::<NixOption>(json).unwrap();
}
#[test] #[test]
fn test_option_parsing() {} fn test_option_parsing() {}
} }

View file

@ -68,7 +68,7 @@ type alias ResultItemSource =
, source : Maybe String , source : Maybe String
-- flake -- flake
, flake : Maybe ( String, String ) , flake : Maybe (List String)
, flakeName : Maybe String , flakeName : Maybe String
, flakeDescription : Maybe String , flakeDescription : Maybe String
, flakeUrl : Maybe String , flakeUrl : Maybe String
@ -274,17 +274,20 @@ viewResultItem channel _ show item =
Maybe.map asGithubLink item.source.source Maybe.map asGithubLink item.source.source
flakeOrNixpkgs = flakeOrNixpkgs =
case ( item.source.flakeName, item.source.flake, item.source.flakeUrl ) of let
mkLink flake url =
a [ href url ] [ text flake ]
in
case ( item.source.flake, item.source.flakeUrl ) of
-- its a flake -- its a flake
( Just name, Just ( flake, attr ), Just flakeUrl_ ) -> ( Just (flake :: []), Just url ) ->
Just Just
[ li [] [ li [] [ mkLink flake url ]
[ a [ href flakeUrl_ ] [ text flake ]
, text "#"
, text attr
]
] ]
( Just (flake :: moduleName :: []), Just url ) ->
Just [ li [] [ mkLink flake url, text "#", text moduleName ] ]
_ -> _ ->
Nothing Nothing
in in
@ -344,12 +347,16 @@ findSource channel source =
flakeOrNixpkgs = flakeOrNixpkgs =
case ( source.flake, source.flakeUrl ) of case ( source.flake, source.flakeUrl ) of
-- its a flake -- its a flake
( Just ( name, module_ ), Just flakeUrl_ ) -> ( Just (name :: attrs), Just flakeUrl_ ) ->
let
module_ =
Maybe.withDefault "(default)" <| Maybe.map (\m -> "(Module: " ++ m ++ ")") <| List.head attrs
in
Just <| Just <|
List.append List.append
(Maybe.withDefault [] <| Maybe.map (\sourceFile_ -> [ sourceFile_, span [] [ text " in " ] ]) sourceFile) (Maybe.withDefault [] <| Maybe.map (\sourceFile_ -> [ sourceFile_, span [] [ text " in " ] ]) sourceFile)
[ span [] [ text "Flake: " ] [ span [] [ text "Flake: " ]
, a [ href flakeUrl_ ] [ text <| name ++ "(Module: " ++ module_ ++ ")" ] , a [ href flakeUrl_ ] [ text <| name ++ module_ ]
] ]
( Nothing, _ ) -> ( Nothing, _ ) ->
@ -420,7 +427,7 @@ decodeResultItemSource =
|> Json.Decode.Pipeline.optional "option_example" (Json.Decode.map Just Json.Decode.string) Nothing |> Json.Decode.Pipeline.optional "option_example" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_source" (Json.Decode.map Just Json.Decode.string) Nothing |> Json.Decode.Pipeline.optional "option_source" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "option_flake" |> Json.Decode.Pipeline.optional "option_flake"
(Json.Decode.map Just <| Json.Decode.map2 Tuple.pair (Json.Decode.index 0 Json.Decode.string) (Json.Decode.index 1 Json.Decode.string)) (Json.Decode.map Just <| Json.Decode.list Json.Decode.string)
Nothing Nothing
|> Json.Decode.Pipeline.optional "flake_name" (Json.Decode.map Just Json.Decode.string) Nothing |> Json.Decode.Pipeline.optional "flake_name" (Json.Decode.map Just Json.Decode.string) Nothing
|> Json.Decode.Pipeline.optional "flake_description" (Json.Decode.map Just Json.Decode.string) Nothing |> Json.Decode.Pipeline.optional "flake_description" (Json.Decode.map Just Json.Decode.string) Nothing