[Nix-dev] NixOS: Introduce sub-configurations

Eelco Dolstra e.dolstra at tudelft.nl
Tue May 12 10:50:18 CEST 2009


Hi,

Nicolas Pierron wrote:

> This mail presents a way to improve documentation as well as the
> modularity in "instances".  Section 1 clarifies what I call an
> "instance".  Section 2 highlights issues related to instances.
> Section 3 introduces sub-configurations to tackle modularity and
> documentation issues.
> 
> 
> 1/ Instances
> 
> 
> In this mail, an instance correspond to a part of a configuration
> which can be registered from any places.  This naming implies that the
> configuration which aggregate all definitions (instances) will use
> them as a job where each instance has its "own life".  File systems,
> Apache virtual hosts and upstart jobs configurations are instances.
> Usually, they are sets of options.
> 
> fileSystems = [
>   { device = "/dev/hda1"; mountPoint = "/"; }
>   { device = "/dev/hda2"; fsType = "ext3"; mountPoint = "/data";
> options = "data=journal"; }
>   { label = "bigdisk"; mountPoint = "/bigdisk"; }
> ];

Yes :-)  Improving the handling of structured options like fileSystems is long
overdue.

I agree with Ludovic that the term "instance" isn't very good, and it's not
needed.  Basically, they're just a different kind of option type.

Right now options are not explicitly typed.  (Some options have a merge function
 which is *almost* a type declaration.)  What we need is a way to declare the
type of an option.  "Instances" are then just more complex (structured) option
types.

For instance, a simple option like networking.hostName would have type string:

  hostName = mkOption {
    default = "nixos";
    type = tpString;
    description = "
      The name of the machine.  Leave it empty if you want to obtain
      it from a DHCP server (if using DHCP).
    ";
  };

I.e. we have basic types like tpString, tpBool, ... (which would be special
values defined in lib/options.nix).

Then we could have structured types, i.e. lists and attribute sets:

  kernelModules = mkOption {
    default = ["ide_cd" "ext3" ...];
    type = tpList tpString;
    ...
  };

That is, tpList is a type constructor of kind * -> * in Haskell terms ;-).

And fileSystems becomes:

  fileSystems = mkOption {

    type = tpAttrSet
      { device = mkOption {
          type = tpString;
          description = "Path of the device.";
        };
        mountPoint = mkOption {
          type = tpString;
          description = "Mount point.";
        };
        options = mkOption {
          default = [];
          type = tpList tpString;
          description = "Mount options.";
        };
      };

    description = "File systems bla bla...";
  };

So the type constructor tpAttrSet gets a set of option declarations, which can
be recursively applied to an option value to check attributes and fill in
defaults.  This can also be used to handle Apache virtual hosts cleanly:

  let

    # Options common to the global Apache instance and vhosts.
    perServerOptions =
      { hostName = mkOption {
          type = tpString;
          description = "Canonical hostname for the server.";
        };
        port = mkOption {
          type = tpInt;
          default = 80;
          description = "Port number.";
        };
        serverAliases = mkOption {
          type = tpList tpString;
          default = [];
          example = ["www.example.org" ...];
        };
        ...
      };

  in

  apache = {

    # Global Apache options.
    ...

    # And the vhosts...
    virtualHosts = mkOption {
      type = tpList (tpAttrSet perServerOptions);
      default = [];
    };

  } // perServerOptions;

BTW, if we have explicitly declared types, then probably a lot of "merge"
attribute in option declarations become redundant (e.g. "merge =
pkgs.lib.mergeListOption;"), since the sensible default merge policy follows
from the type.

> Instances are sets composed of multiple attributes.  The attributes
> are documented in the option containing the list of sets.  The
> description attribute is usually getting bigger and bigger.  Apache
> virtual hosts have their own file of options with their own
> descriptions (cf upstart-jobs/apache-httpd/per-server-options.nix).
> Unfortunately, this options does not show-up directly under the
> virtualHosts option which is not good as you may have to look at the
> sources to know what option you can set.  

Yes, the Apache virtual host thing was basically a primitive attempt at
structured options.

I really like the notion of adding structured option types, because it will make
it very easy to add support for component "instances", such as multiple
instances of Apache, the X server and so on.

> 3/ sub-configurations
> 
> 
> Sub-configurations are special options which have the ability to
> contains extra option declarations.  Two options are defined on
> sub-configuration, which target the first declaration and extensions.
> 
> fileSystems = mkSubConfigs {
>   default = null;
>   example = [ { device = "/dev/hda1"; mountPoint = "/"; } ];
>   description = "List of mounted file systems";
> } {
>   devices = mkOption { .. };
>   mountPoint = mkOption { .. };
> };

Yes, this is basically like a type of tpAttrSet above.

> Even if this example is pointless because it would be easier to write
> a small function to do it instead, you have to keep in mind that this
> option will be documented and visible inside a GUI at the opposite of
> a function.  

Andres and I once had a project proposal for adding a type system to Nix, the
main goal being that configuration user interfaces could be derived
automatically from the types of functions.  It got rejected, unfortunately...

-- 
Eelco Dolstra | http://www.st.ewi.tudelft.nl/~dolstra/



More information about the nix-dev mailing list