aboutsummaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2023-02-26 12:08:27 -0700
committerSean Whitton <spwhitton@spwhitton.name>2023-02-26 12:29:43 -0700
commitba083296c0ad1007fe0ebc13c28f5cb74095d8de (patch)
tree4fed2ae1ae0890e5684b73725f9ea5b37ef1c479 /doc
parent49b9ea1d0988282fc8807f6b7b6e7ff18b0f775b (diff)
downloadconsfigurator-ba083296c0ad1007fe0ebc13c28f5cb74095d8de.tar.gz
properties.rst: add a section "Defining new properties"
Signed-off-by: Sean Whitton <spwhitton@spwhitton.name>
Diffstat (limited to 'doc')
-rw-r--r--doc/properties.rst208
1 files changed, 208 insertions, 0 deletions
diff --git a/doc/properties.rst b/doc/properties.rst
index ccd40dd..265e8f8 100644
--- a/doc/properties.rst
+++ b/doc/properties.rst
@@ -1,6 +1,212 @@
Properties
==========
+Defining new properties
+-----------------------
+
+Defining new properties is like defining new functions: ``DEFPROP``,
+``DEFPROPLIST`` and ``DEFPROPSPEC`` are more like ``DEFUN`` than anything
+else. Here is a guide to these three macros; in particular, why you might
+need to move from the basic ``DEFPROPLIST`` to either ``DEFPROPSPEC`` or
+``DEFPROP``.
+
+``DEFPROPLIST`` and ``DEFPROPSPEC``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These macros allow you to define properties by combining existing properties.
+Most user properties in consfigs should be defineable using one of these: you
+should not need to resort to ``DEFPROP``. And indeed, most new properties to
+be added to Consfigurator itself should not need to use ``DEFPROP`` either.
+
+``DEFPROPLIST``
+^^^^^^^^^^^^^^^
+
+``DEFPROPLIST`` allows you to define properties very similarly to how you
+define hosts with ``DEFHOST``. You simply supply an unevaluated property
+application specification. Compare:
+
+::
+
+ (defproplist setup-test-pure-wayland ()
+ "Set up system for testing programs' support for running Wayland-native."
+ (apt:installed "sway")
+ (apt:removed "xwayland")
+ (systemd:disabled "lightdm"))
+
+ (defhost laptop.example.com ()
+ (os:debian-stable "bullseye" :amd64)
+ #| ... |#
+ (setup-test-pure-wayland)
+ ;; Previously we had these; now factored out:
+ ;; (apt:installed "sway")
+ ;; (apt:removed "xwayland")
+ ;; (systemd:disabled "lightdm")
+ #| ... |#)
+
+You can use parameters just like with plain functions:
+
+::
+
+ (defproplist setup-test-pure-wayland (compositor)
+ "Set up system for testing programs' support for running Wayland-native."
+ (apt:installed compositor)
+ (apt:removed "xwayland")
+ (systemd:disabled "lightdm"))
+
+ (defhost laptop.example.com ()
+ (os:debian-stable "bullseye" :amd64)
+ #| ... |#
+ (setup-test-pure-wayland "sway")
+ #| ... #|)
+
+To compute intermediate values, you can use ``&aux`` parameters. Be aware
+that code for ``&optional`` and ``&key`` default values, and for ``&aux``
+parameters, may be executed more than once per application of the property, so
+it should usually be side effect-free.
+
+``DEFPROPSPEC``
+^^^^^^^^^^^^^^^
+
+Unevaluated property application specifications are not always as expressive
+as is required. For example, what if, in our example, we want to allow the
+user to supply a list of packages to install, of arbitrary length? For this
+we need ``DEFPROPSPEC``. The body of this macro is ordinary Lisp code which
+should return a property application specification. In most cases all you
+need is a single backquote expression:
+
+::
+
+ (defpropspec setup-test-pure-wayland :posix (&rest compositor-packages)
+ "Set up system for testing programs' support for running Wayland-native."
+ `(eseqprops
+ (apt:installed ,@compositor-packages)
+ (apt:removed "xwayland")
+ (systemd:disabled "lightdm")))
+
+ (defhost laptop.example.com ()
+ (os:debian-stable "bullseye" :amd64)
+ #| ... |#
+ (setup-test-pure-wayland "sway" "swayidle" "swaylock")
+ #| ... #|)
+
+Use this basic shape, with ``ESEQPROPS``, if you want ``DEFPROPLIST`` with
+just a little more expressive power -- ``DEFPROPLIST`` has an implicit
+``ESEQPROPS``.
+
+If you want to include elements of the property application specification
+conditionally, you will need ``DEFPROPSPEC``. For example, perhaps disabling
+lightdm is not appropriate on all hosts to which we want to apply this
+property, because not all of them have it installed. So we might use::
+
+ (defpropspec setup-test-pure-wayland :posix (lightdmp &rest compositor-packages)
+ "Set up system for testing programs' support for running Wayland-native."
+ `(eseqprops
+ (apt:installed ,@compositor-packages)
+ (apt:removed "xwayland")
+ ,@(and lightdmp '((systemd:disabled "lightdm")))))
+
+ (defhost laptop.example.com ()
+ (os:debian-stable "bullseye" :amd64)
+ #| ... |#
+ (setup-test-pure-wayland t "sway" "swayidle" "swaylock")
+ #| ... #|)
+
+The expression ``,@(and x '((y)))`` (or ``,@(and x `((,y)))``) is a Lisp
+idiom for conditional inclusion of sublists of backquoted lists.
+
+One disadvantage of moving to ``DEFPROPSPEC`` (aside from just being less
+declarative) is that you can't use unevaluated property application
+specification-specific features, such as dotted propapp notation, directly
+within backquote expressions. This won't work:
+
+::
+
+ (defpropspec setup-... :lisp (...)
+ `(eseqprops
+ #| ... |#
+ ;; won't work
+ (chroot:os-bootstrapped. nil "/srv/chroot/unstable-amd64"
+ (os:debian-unstable :amd64)
+ (apt:installed "build-essential"))
+ #| ... |#))
+
+However, use of the ``PROPAPP`` macro makes it possible to temporarily switch
+back to something more like ``DEFPROPLIST``:
+
+::
+
+ (defpropspec setup-... :lisp (...)
+ `(eseqprops
+ #| ... |#
+ ,(propapp (chroot:os-bootstrapped. nil "/srv/chroot/unstable-amd64"
+ (os:debian-unstable :amd64)
+ (apt:installed "build-essential")))
+ #| ... |#))
+
+In all these examples, the body of the ``DEFPROPSPEC`` has been a single form.
+Sometimes you will need to wrap binding forms around this, or precede it with
+other forms, to compute the propspec expression. In some cases you might not
+use backquote at all; see ``INSTALLER:BOOTLOADER-BINARIES-INSTALLED`` for an
+example.
+
+Note that arguments to property applications within backquotes are not
+evaluated. Whereas you might use::
+
+ (file:contains-lines "/etc/foo" '("bar" "baz"))
+
+within ``DEFPROPLIST``, within backquotes within ``DEFPROPSPEC`` you would
+need::
+
+ (file:contains-lines "/etc/foo" ("bar" "baz"))
+
+DEFPROP
+~~~~~~~
+
+Returning to our ``SETUP-TEST-PURE-WAYLAND`` example, it's not great that the
+user has to supply a parameter specifying whether or not lightdm needs to be
+disabled. We should just check whether lightdm is installed, and disable it
+only if it's there (some sort of check is necessary because
+``SYSTEMD:DISABLED`` will fail if there is no service to disable).
+
+``DEFPROP`` is more fundamental than both ``DEFPROPLIST`` and ``DEFPROPSPEC``:
+it allows you to supply arbitrary code for each of the property's subroutines
+(see :ref:`property-subroutines`, below). In our example, the crucial
+difference is that ``DEFPROPLIST`` and ``DEFPROPSPEC`` permit you to run code
+only before any connection to the host is established, which, of course, is
+too early to check whether lightdm is installed. We need to run the check at
+``:APPLY`` time:
+
+::
+
+ (defprop %no-lightdm :posix ()
+ (:hostattrs (os:required 'os:debianlike))
+ (:apply (if (apt:all-installed-p "lightdm")
+ (systemd:disabled "lightdm"))
+ :no-change))
+
+ (defpropspec setup-test-pure-wayland :posix (&rest compositor-packages)
+ "Set up system for testing programs' support for running Wayland-native."
+ `(eseqprops
+ (apt:installed ,@compositor-packages)
+ (apt:removed "xwayland")
+ (%no-lightdm)))
+
+Here, we can call the ordinary function ``APT:ALL-INSTALLED-P`` to examine the
+actual state of the host. We have had to introduce two complexities to
+account for several implicit features of ``DEFPROPLIST`` and ``DEFPROPSPEC``.
+Firstly, we have to specify that the property is only applicable to
+Debian-like hosts (in this case we could get away with not doing that because
+we apply ``%NO-LIGHTDM`` only within a ``DEFPROPSPEC`` that has other
+properties which are applicable only to Debian-like hosts, but that's highly
+contingent). And secondly, we have to take care to return ``:NO-CHANGE`` in
+the case that lightdm is not installed. Both of these things are taken care
+of for us with ``DEFPROPLIST`` and ``DEFPROPSPEC``. Nevertheless, if you need
+to examine the actual state of the host, only ``DEFPROP`` will do.
+
+Finally, note how we keep the rest of ``SETUP-TEST-PURE-WAYLAND`` in a
+``DEFPROPSPEC``, only dropping down to ``DEFPROP`` for the part that requires
+it. This is good practice.
+
Names
-----
@@ -23,6 +229,8 @@ relative to the remote home directory. Use ``WITH-REMOTE-CURRENT-DIRECTORY``
to change the remote working directory in a way which ensures it will get
changed back.
+.. _property-subroutines:
+
Property subroutines
--------------------