Skip to content

Latest commit

 

History

History
163 lines (122 loc) · 10.8 KB

architecture.org

File metadata and controls

163 lines (122 loc) · 10.8 KB

Nix-Gui has two components: the backend and the frontend.

The backend (nixui/options) reads information from two sources:

  • NixOS (<nixpkgs> and <nixos>)
  • and the configuration path being edited.

It then converts the read information to data-structures useful for navigation, search, and editing. The backend maintains in-memory state for all changes and commits those changes to disk by writing to the configuration path.

The frontend (nixui/graphics) constructs various widgets comprising Nix-Gui and loads them with data from the backend. The frontend doesn’t manage any edit state. All “edit” actions performed in the GUI send a request-for-update to the backend.

Backend

The backend handles three major tasks: configuration retrieval to initialize data-structures, in-memory configuration mutation, and configuration committing. It provides APIs for frontend interaction, but doesn’t concern itself with how the frontend renders any of its data, similar to most websites wherein the webpage defines the Javascript determining how the backend will be queried, and the HTML / CSS determining how it will be rendered to the user.

Loading the Option Tree Data-Structure

Nix-Gui reads information from the configuration path and <nixos> in order to construct a data-structure (nixui.options.option_tree.OptionTree) mapping options and attributes to metadata (nixui.options.option_tree.OptionData) including the attributes type and definition:

  • _type: A static python-object representation of the option type (NixType). Informs the frontend of how FieldWidget should be generated and input checking should be performed.
  • system_default_definition: Provided by <nixpkgs>, the default value if nothing is set in the configuration path.
  • configured_definition: Provided by the configuration path, overrides system_default_definition.
  • in_memory_definition: Undefined when the data-structure is loaded, can be mutated by the user and later committed to disk.

nixui/nix/lib.nix (invoked by nixui.options.nix_eval) contains the function get_all_nixos_options() which is used to retrieve OptionData including system_default_definition and _type for all options from <nixos>.

nixui/options/parser.py contains the function get_all_option_values(module_path) which

  • retrieves all attributePath = string representation of nix expression; pairs in the file by parsing the nixui.options.syntax_tree.SyntaxTree constructed using nix_dump_syntax_tree_json (a wrapper for rnix-parser)
  • resolves the path of all imports and recurses

The result of get_all_option_values(module_path) is used to set OptionData.configured_definition where applicable.

Caveat

Attributes in the OptionTree can either be schema-defined or user-defined.

Schema-Defined Attribute

These attributes are defined in the NixOS config schema, so we call them “schema-defined” attributes. In the <nixos> source code, schema-defined attributes are specified by mkOption, for example:

hardware.bluetooth.powerOnBoot = mkOption {
  type = types.bool;
  default = true;
  description = "Whether to power up the default Bluetooth controller on boot.";
};

User-Defined Attribute

For types.attrsOf options, users can specify the name of the attribute being defined, such as the names of users, the paths to files, and systemd service names. For example in <nixos> source code, the schema-defined attribute environment.etc provides the ability to set the User-Defined Attribute environment.etc."resolv.conf"= in =configuration.nix.

nixos/modules/system/etc/etc.nix:

environment.etc = mkOption {
  default = {};
  type = with types; attrsOf (submodule (
    { name, config, options, ... }:
    { options = {
        text = mkOption {
          default = null;
          type = types.nullOr types.lines;
        };
        ...

configuration.nix:

environment.etc."resolv.conf".text = "text here";
User-Defined List Elements

types.listOf options are also part of the OptionTree despite not having a proper attribute path, e.g. a list of allowed TCP ports in the firewall will have the element

networking.firewall.allowedTCPPorts[0]

This is not the same as

networking.firewall.allowedTCPPorts[0] = <expression>;  # illegal syntax in nix

rather, it is equivalent to

networking.firewall.allowedTCPPorts = [<expression> ...];

OptionDefinition

Nix expression strings are immediately converted to nixui.options.option_definition.OptionDefinition Python objects. The OptionDefinition class provides methods to get the python representation of an expression string. E.g. an attribute set is converted to a dict, =”true”= is converted to True, =”5.424”= is converted to float(5.424)).

OptionDefinition’s also have the _type property, which returns the NixType the current definition is compatible with.

Additionally OptionDefinition’s can be used to convert Python objects to nix expressions, which is useful for the “Commit Changes to Disk” section below.

State Management

Once constructed, the backend can perform four mutation operations:

  • replacing an old in_memory_definition (OptionDefinition) with a new one
  • adding a new attribute with a new OptionDefinition
  • renaming an attribute
  • removing an attribute

OptionTree.in_memory_diff contains a cache of all state changes represented simply as a mapping between attributes and their new OptionDefinition (or None if deleted).

All updates to the OptionTree coming from the frontend pass through nixui.state_model.StateModel, which is a layer on top of the OptionTree with Update’s integrated. Each mutating method results in an Update (an object containing information necessary to revert a change) being appended to StateModel.update_history.

The StateModel’s mutating methods include

  • record_update: Update the in_memory_definition of an attribute in the OptionTree
  • rename_option: Generally used to rename a submodule, e.g. filesystems."/".foo -> =”filesystems.”/boot”.foo=
  • add_new_option: Generally used to add an attribute or element to a submodule or list of.
  • undo: Revert the latest Update in update_history

Commit Changes to Disk

The StateModel also provides the method persist_changes, a pass-through function which

  • Calls OptionTree.get_changes(), which retrieves a mapping of attributes to new definitions for which the in_memory_diff and configured_definition differ.
  • calls parser.calculate_changed_module, which extracts the expression_string from each changed OptionDefinition and calls one of three SyntaxTree-modifying functions (parser.add_definition(), parser.apply_replace_definition(), or parser.apply_remove_definition() if None). Each of these three functions call add_comment with distinct parameters to note changes by Nix-Gui. Each of these three functions apply modifications to the syntax tree using methods provided by SyntaxTree.

We are left with a new module string which will be written to module_path.

Frontend

The frontend renders a graphical tool for changing configurations. A primer on frontend functionality can be found in Usage#Interface.

The Nav Interface (nixui.graphics.nav_interface.OptionNavigationInterface) is the main widget. It contains a layout with place-holders for three widgets:

  • Navbar: View and update the URI.
  • Navlist: A list of attribute paths which, if clicked, updates the URI.
  • Options Editor: A container for a list of FieldWidgets which contains option/attribute metadata and editing widgets.

URI Resolution

Each time the URI changes, the Nav Interface creates a new instance of each widget, replacing the old instance.

When loading a new URI,

  • A Navbar is instantiated which displays the new URIs
  • A Navlist is instantiated which displays the children of the attribute path, or search results. Selects the navlist item if the URI instructs to.
  • An Options Editor is instantiated which either is blank or shows a list of Option Displays an item in the navlist is selected.

URI Format

There are currently two types of URIs, config:option.path.here and search:search text here.

Navbar

The Navbar displays the URI and has four widgets, each of which results in a callback telling the Nav Interface to change the URI:

  • Up Arrow: Change the URI from config:foo.bar.baz to config:foo.bar. (disabled for search:anything and top level config:)
  • Back Arrow: Change the URI to the previous URI.
  • URI Box: Shows a pretty format of the URI, allow for direct editing of the URI when clicked.
  • Search Box: Change URI to search:<entered text>.

Navlist

The Navlist displays navigable options based on the URI. If the URI is config:parent.option.path, the navlist will display each option which is a member of the set parent.option.path. If the URI is search:<search string>, the navlist will display each option matching the search.

If a Navlist item is clicked, the Nav Interface will load the clicked items URI.

There are a variety of Navlist types defined in nixui.graphics.navlist:

  • StaticAttrsOf: immutable listing of attributes of the URIs config path.
  • DynamicAttrsOf: mutable list of attributes. Useful for attribute set of <t> type attributes.
  • DynamicListOf: mutable list elements, shown for a list of <t> type attributes.
  • SearchResultListDisplay: immutable list of search results including details about why it matched the search. Searches are matched based on Attribute Path, Type, and Description.

Options Editor

The Options Editor is comprised of an Option Display Group (nixui.graphics.option_display_group.OptionDisplayGroupBox), a QGroupBox containing one or many Option Displays (nixui.graphics.option_display.GenericOptionDisplay).

An Option Display is a tool for editing the value of a single option or attribute. The current value and option/attribute type impact how it is rendered.

A Field Widget (nixui.graphics.field_widgets) is the component of an Option Display which allows the user to edit the value of an option/attribute.

There are a variety of Field Widgets, and types of functionality for Field Widgets:

  • Standard Field Widget: allows changes to OptionDefinition.obj which will be converted to a nix expression
  • Expression Field Widget: allows changes to the nix expression itself (OptionDefinition.expression_string)
  • Reference Field Widget: (NOT IMPLEMENTED) allows users to refer to a package, option, or other variable in scope. This is a more constrained form of the Expression Field Widget and allows users to reference variables more easily.
  • Redirect Field Widget: For ListOf and AttrsOf, changes the URI so the navlist is the editor for the elements / set members for the list / set.