summary refs log blame commit diff
path: root/lib/modules.nix
blob: f914947e7849457c719f09ff2a012ccd4029309e (plain) (tree)
1
2
3
4
5
6
7
8
9



                                  
                                  



                           
                          












                                            
 





                       
 





                                 
 



                                                                  
 


                                                                  
       
                                        
 

                                                            



                                                         
                                                              
 
      
                        
                                            
          

                                                          
                             
              
                                       
                                               
                                               



              
 






                                                                          
                      


                                  
        
                                       


    

                                    
                                      

                                                       
                                                                

                              
                                                        
          
 
                                      
 






                                                   
      
                           

                                                            
         
 









                                           
 










                                                                            
 





                                                                           
                       
                                                                                
 
 
                                
                                               

                                                          



                            
















                                                            
 





                                                        
 

                            
                                 

           
 
                               



                                                       
                                                       




                                                                      
                         

                                                    
                                             
                          
                                    


                            
                                                            

                                     
                                                                      





                     






                                                              



                                                     









                                                                





                                                       
                                                           



                           
                                      
                                                             





                                                                        


                                          
                                       








                                               
                                       
                                                   


                                                                             

                                                 
 











                                                                









                                                                     
                                                                  




                                                   
                                                                                                                  
































                                                                                   
 


                                            


                                                                      
                                                           
                      
                                                  


       
 
















                                                                              
                                                               





                                                                    
 
# NixOS module handling.

let lib = import ./default.nix; in

with { inherit (builtins) head; };
with import ./trivial.nix;
with import ./lists.nix;
with import ./misc.nix;
with import ./attrsets.nix;
with import ./options.nix;
with import ./properties.nix;

rec {

  # Unfortunately this can also be a string.
  isPath = x: !(
     builtins.isFunction x
  || builtins.isAttrs x
  || builtins.isInt x
  || builtins.isBool x
  || builtins.isList x
  );


  importIfPath = path:
    if isPath path then
      import path
    else
      path;


  applyIfFunction = f: arg:
    if builtins.isFunction f then
      f arg
    else
      f;


  isModule = m:
       (m ? config && isAttrs m.config && ! isOption m.config)
    || (m ? options && isAttrs m.options && ! isOption m.options);


  # Convert module to a set which has imports / options and config
  # attributes.
  unifyModuleSyntax = m:
    let
      delayedModule = delayProperties m;

      getImports =
        toList (rmProperties (delayedModule.require or []));
      getImportedPaths = filter isPath getImports;
      getImportedSets = filter (x: !isPath x) getImports;

      getConfig =
        removeAttrs delayedModule ["require" "key" "imports"];

    in
      if isModule m then
        { key = "<unknown location>"; } // m
      else
        { key = "<unknown location>";
          imports = (m.imports or []) ++ getImportedPaths;
          config = getConfig;
        } // (
          if getImportedSets != [] then
            assert length getImportedSets == 1;
            { options = head getImportedSets; }
          else
            {}
        );


  unifyOptionModule = {key ? "<unknown location>"}: name: index: m: (args:
    let
      module = lib.applyIfFunction m args;
      key_ = rec {
        file = key;
        option = name;
        number = index;
        outPath = key;
      };
    in if lib.isModule module then
      { key = key_; } // module
    else
      { key = key_; options = module; }
  );


  moduleClosure = initModules: args:
    let
      moduleImport = origin: index: m:
        let m' = applyIfFunction (importIfPath m) args;
        in (unifyModuleSyntax m') // {
          # used by generic closure to avoid duplicated imports.
          key =
            if isPath m then m
            else m'.key or (newModuleName origin index);
        };

      getImports = m: m.imports or [];

      newModuleName = origin: index:
        "${origin.key}:<import-${toString index}>";

      topLevel = {
        key = "<top-level>";
      };

    in
      (lazyGenericClosure {
        startSet = imap (moduleImport topLevel) initModules;
        operator = m: imap (moduleImport m) (getImports m);
      });


  moduleApply = funs: module:
    lib.mapAttrs (name: value:
      if builtins.hasAttr name funs then
        let fun = lib.getAttr name funs; in
        fun value
      else
        value
    ) module;


  # Handle mkMerge function left behind after a delay property.
  moduleFlattenMerge = module:
    if module ? config &&
       isProperty module.config &&
       isMerge module.config.property
    then
      (map (cfg: { key = module.key; config = cfg; }) module.config.content)
      ++ [ (module // { config = {}; }) ]
    else
      [ module ];


  # Handle mkMerge attributes which are left behind by previous delay
  # properties and convert them into a list of modules. Delay properties
  # inside the config attribute of a module and create a second module if a
  # mkMerge attribute was left behind.
  #
  # Module -> [ Module ]
  delayModule = module:
    map (moduleApply { config = delayProperties; }) (moduleFlattenMerge module);


  evalDefinitions = opt: values:
    if opt.type.delayOnGlobalEval or false then
      map (delayPropertiesWithIter opt.type.iter opt.name)
        (evalLocalProperties values)
    else
      evalProperties values;


  selectModule = name: m:
    { inherit (m) key;
    } // (
      if m ? options && builtins.hasAttr name m.options then
        { options = lib.getAttr name m.options; }
      else {}
    ) // (
      if m ? config && builtins.hasAttr name m.config then
        { config = lib.getAttr name m.config; }
      else {}
    );

  filterModules = name: modules:
    filter (m: m ? config || m ? options) (
      map (selectModule name) modules
    );


  modulesNames = modules:
    lib.concatMap (m: []
    ++ optionals (m ? options) (lib.attrNames m.options)
    ++ optionals (m ? config) (lib.attrNames m.config)
    ) modules;


  moduleZip = funs: modules:
    lib.mapAttrs (name: fun:
      fun (catAttrs name modules)
    ) funs;


  moduleMerge = path: modules_:
    let
      addName = name:
        if path == "" then name else path + "." + name;

      modules = concatLists (map delayModule modules_);

      modulesOf = name: filterModules name modules;
      declarationsOf = name: filter (m: m ? options) (modulesOf name);
      definitionsOf  = name: filter (m: m ? config ) (modulesOf name);

      recurseInto = name:
        moduleMerge (addName name) (modulesOf name);

      recurseForOption = name: modules: args:
        moduleMerge name (
          moduleClosure modules args
        );

      errorSource = modules:
        "The error may come from the following files:\n" + (
          lib.concatStringsSep "\n" (
            map (m:
              if m ? key then toString m.key else "<unknown location>"
            ) modules
          )
        );

      eol = "\n";

      allNames = modulesNames modules;

      getResults = m:
        let fetchResult = s: mapAttrs (n: v: v.result) s; in {
          options = fetchResult m.options;
          config = fetchResult m.config;
        };

      endRecursion =  { options = {}; config = {}; };

    in if modules == [] then endRecursion else
      getResults (fix (crossResults: moduleZip {
        options = lib.zipWithNames allNames (name: values: rec {
          config = lib.getAttr name crossResults.config;

          declarations = declarationsOf name;
          declarationSources =
            map (m: {
              source = m.key;
            }) declarations;

          hasOptions = values != [];
          isOption = any lib.isOption values;

          decls = # add location to sub-module options.
            map (m:
              mapSubOptions
                (unifyOptionModule {inherit (m) key;} name)
                m.options
            ) declarations;

          decl =
            lib.addErrorContext "${eol
              }while enhancing option `${addName name}':${eol
              }${errorSource declarations}${eol
            }" (
              addOptionMakeUp
                { name = addName name; recurseInto = recurseForOption; }
                (mergeOptionDecls decls)
            );

          value = decl // (with config; {
            inherit (config) isNotDefined;
            isDefined = ! isNotDefined;
            declarations = declarationSources;
            definitions = definitionSources;
            config = strictResult;
          });

          recurse = (recurseInto name).options;

          result =
            if isOption then value
            else if !hasOptions then {}
            else if all isAttrs values then recurse
            else
              throw "${eol
                }Unexpected type where option declarations are expected.${eol
                }${errorSource declarations}${eol
              }";

        });

        config = lib.zipWithNames allNames (name: values_: rec {
          option = lib.getAttr name crossResults.options;

          definitions = definitionsOf name;
          definitionSources =
            map (m: {
              source = m.key;
              value = m.config;
            }) definitions;

          values = values_ ++
            optionals (option.isOption && option.decl ? extraConfigs)
              option.decl.extraConfigs;

          defs = evalDefinitions option.decl values;

          isNotDefined = defs == [];

          value =
            lib.addErrorContext "${eol
              }while evaluating the option `${addName name}':${eol
              }${errorSource (modulesOf name)}${eol
            }" (
              let opt = option.decl; in
              opt.apply (
                if isNotDefined then
                  opt.default or (throw "Option `${addName name}' not defined and does not have a default value.")
                else opt.merge defs
              )
            );

          strictResult = builtins.tryEval (builtins.toXML value);

          recurse = (recurseInto name).config;

          configIsAnOption = v: isOption (rmProperties v);
          errConfigIsAnOption =
            let badModules = filter (m: configIsAnOption m.config) definitions; in
            "${eol
              }Option ${addName name} is defined in the configuration section.${eol
              }${errorSource badModules}${eol
            }";

          errDefinedWithoutDeclaration =
            let badModules = definitions; in
            "${eol
              }Option '${addName name}' defined without option declaration.${eol
              }${errorSource badModules}${eol
            }";

          result =
            if option.isOption then value
            else if !option.hasOptions then throw errDefinedWithoutDeclaration
            else if any configIsAnOption values then throw errConfigIsAnOption
            else if all isAttrs values then recurse
            # plain value during the traversal
            else throw errDefinedWithoutDeclaration;

        });
      } modules));


  fixMergeModules = initModules: {...}@args:
    lib.fix (result:
      # This trick avoids an infinite loop because names of attribute
      # are know and it is not required to evaluate the result of
      # moduleMerge to know which attributes are present as arguments.
      let module = { inherit (result) options config; }; in
      moduleMerge "" (
        moduleClosure initModules (module // args)
      )
    );


  # Visit all definitions to raise errors related to undeclared options.
  checkModule = path: {config, options, ...}@m:
    let
      eol = "\n";
      addName = name:
        if path == "" then name else path + "." + name;
    in
    if lib.isOption options then
      if options ? options then
        options.type.fold
          (cfg: res: res && checkModule (options.type.docPath path) cfg._args)
          true config
      else
        true
    else if isAttrs options && lib.attrNames m.options != [] then
      all (name:
        lib.addErrorContext "${eol
          }while checking the attribute `${addName name}':${eol
        }" (checkModule (addName name) (selectModule name m))
      ) (lib.attrNames m.config)
    else
      builtins.trace "try to evaluate config ${lib.showVal config}."
      false;

}