This is the third entry in my series of blog posts about adding Parameterized Packages to GNU Guix for my Google Summer of Code project. Parameterization here refers to making it possible for packages to be built with compile-time options, and I have gone over the reasons and benefits for doing so in detail in the first post about Parameterized Packages.
In the last update I talked about implementing support for boolean, non-negative parameters. In this update, I've implemented negation, enumeration and a few other features that will make parameters considerably more powerful!
To make global parameters useful, it is necessary to be able to change the contents of a package in ways that Package Transformations might not be able to by themselves. Additionally, users might find themselves in situations where they wish to perform different operations for different values of an enumerated type.
"Parametric Variants" refers to matching against enumerated values and using methods of Defining Package Variants, such as package transforms, modify-inputs
and procedures that return packages. Parameters now have the ability to use any of these methods instead of just using transformations.
For a better understanding of them, please look at any of the examples using the variant-match
macro.
It's possible that enabling a parameter might require enabling another parameter or a package.
For such situations, I've added a new dependencies
field to the parameter record that lets users specify parameters or packages a given parameter depends on. You can also fine-tune values for the parameter and the parameters in dependencies.
In the previous version, parameters could only be in two states: on
and off
.
This version makes it possible for parameters to take multiple states, as long as the user specifies all the possible states.
This has a huge number of uses- for example, here's a parameter type for locales:
(define locale-parameter-type
(parameter-type
(name 'locale-type)
(accepted-values
'(ca_ES cs_CZ da_DK de_DE
el_GR en_AU en_CA en_GB
en_US es_AR es_CL es_ES
es_MX fi_FI fr_BE fr_CA
fr_CH fr_FR ga_IE it_IT
ja_JP ko_KR nb_NO nl_NL
pl_PL pt_PT ro_RO ru_RU
sv_SE tr_TR uk_UA vi_VN
zh_CN))
(negation #f)
(default 'en_US)
(description "Type for Locales")))
name
which must be a symbolaccepted-values
which must be a list of symbols with at least two elements.negation
which returns the 'negative' element.default
is a value taken by the parameter if the keyword #:default
is used on it.description
which provides a description of the parameter type.(define-global-parameter (package-parameter (name 'tests) (variants (parameter-variant-match (#:off #:transform (without-tests #:package-name)))) (description "Toggle for tests") (predicate #t))
name
is a symbol, similar to parameter-type~'s ~name
field.type
is the parameter-type
to use as the basis for the parameter.variants
is an associative list that assigns transforms, procedures, valid build systems etc. to parameter values.predicate
is set to #f
by default.dependencies
is a list of parameters and packages that a given parameter depends on. The list is punctuated by keywords to indicate parameter and package dependencies, with #:parameter
and #:package
respectively. If no keywords are given, the arguments are assumed to be parameters.description
is a simple description of the parameter.guix install emacs-parameterized \ --with-parameters=emacs-parameterized=pgtk=on \ --with-parameters=emacs-parameterized=tree-sitter=on
Under the hood, this is what the implementation looks like.
(package-with-parameters
[parameter-spec
(local
(list
(package-parameter (name 'next))
(package-parameter (name 'tree-sitter)
(dependencies '(tree-sitter)))
(package-parameter
(name 'pgtk)
(variants
(parameter-variant-match
(_ #:transform (with-configure-flag
#:package-name "=--with-pgtk"))))
(dependencies '(tree-sitter x11)))
(package-parameter
(name 'xwidgets)
(variants
(parameter-variant-match
(_ #:transform (with-configure-flag
#:package-name "=--with-xwidgets")))))
(package-parameter
(name 'wide-int)
(variants
(parameter-variant-match
(_ #:transform (with-configure-flag
#:package-name "=--with-wide-int")))))))
(one-of '((_ (x11 #:off) pgtk)
(_ (x11 #:off) xwidgets)))]
(inherit emacs)
(name "emacs-parameterized")
(source
(parameter-if
'next
(origin
(inherit (package-source emacs))
(method git-fetch)
(uri (git-reference
(url "https://git.savannah.gnu.org/git/emacs.git/")
(commit (string-append "emacs-" version))))
(file-name (git-file-name name version))
(patches
(search-patches
"emacs-exec-path.patch"
"emacs-fix-scheme-indent-function.patch"
"emacs-native-comp-driver-options.patch"
(parameter-if 'pgtk
"emacs-pgtk-super-key-fix.patch"
nil)))
(sha256
(base32
"09jm1q5pvd1dc0xq5rhn66v1j235zlr72kwv5i27xigvi9nfqkv1")))
(origin
(inherit (package-source emacs)))))
(arguments
(substitute-keyword-arguments (package-arguments emacs)
(parameter-match
[((x11 #:off))
'(((#:configure-flags flags #~'())
#~(delete "--with-cairo" #$flags))
((#:modules _) (%emacs-modules build-system))
((#:phases phases)
#~(modify-phases #$phases
(delete 'restore-emacs-pdmp)
(delete 'strip-double-wrap))))]
[(#:all xwidgets (pgtk #:off))
'(((#:configure-flags flags #~'())
#~(cons "--with-xwidgets" #$flags))
((#:modules _) (%emacs-modules build-system))
((#:phases phases)
#~(modify-phases #$phases
(delete 'restore-emacs-pdmp)
(delete 'strip-double-wrap))))])))
(inputs
(parameter-modify-inputs
[(next) (prepend sqlite)]
[(tree-sitter) (prepend tree-sitter)]
[(xwidgets) (prepend gsettings-desktop-schemas
webkitgtk-with-libsoup2)]
[((x11 #:off))
(delete "libx11" "gtk+" "libxft" "libtiff" "giflib" "libjpeg"
"imagemagick" "libpng" "librsvg" "libxpm" "libice" "libsm"
"cairo" "pango" "harfbuzz" "libotf" "m17n-lib" "dbus")])))
package-with-parameters
parameter-spec
parameter-match
(package-parameter (name 'gcc-oflag) (type (parameter-type (name '_) (accepted-values '(-O0 -O1 -O2 -O3 -Os -Ofast -Og -Oz)) (negation #f))) (variants (parameter-variant-match (_ #:build-system gnu-build-system #:lambda (package/inherit #:package (arguments (substitute-keyword-arguments (package-arguments #:package) ((#:make-flags flags #~'()) #~(append #$flags (list (string-append "CFLAGS=" #:parameter-value))))))))))
In High-Performance Computing, it's often necessary to produce static builds of packages to share them with others. This parameter is a basic attempt at making it possible to do so with any given library.
(package-parameter
(name 'static-lib)
(variants
(parameter-variant-match
(_ #:transform
(with-configure-flag #:package-name "=--disable-shared")
(with-configure-flag #:package-name "=--enable-static")))))
I recently made a post on Mastodon that claimed that the real advantage of Guix is that it's extensible with Guile Scheme. To back up this claim, once parameters have been merged to trunk I'll be writing a set of tutorials on hacking Guix with Guile Scheme.
One of these planned tutorials is going to be about writing a RESTful API using Guile that'll allow users to request a package with specific parameters.
Here is what the POST
request for this API may look like:
POST /test HTTP/1.1
Host: guix.example
Accept: application/json
Content-Type: application/json
Content-Length: 194
{
"User" : "guix-hacker",
"Package" : "emacs",
"Parameters" : [
{ "Parameter" : "next",
"Value" : "on"},
{ "Parameter" : "tree-sitter",
"Value" : "off"}
]
}
Here I have demonstrated a basic DSL that is more-or-less just S-expressions. There is however scope for making it a lot more convenient to use parameters, and thus there are plans on building a convenience syntax on top of this simple one.
One example is using ~parameter-name
to indicate the negation of a parameter. However, syntax like this may not be obvious to everyone at a glance, which is why we have decided to make a convenience DSL with these features only after heavy deliberation and discussion.
The next few updates will focus on the UI for Parameterization. The primary goals for the UI are to make it easy to discover parameterization options, tell what type a parameter is and to figure out parameter combinations that work for a given package.
As can be seen with the Parameterized Emacs example in this post, parameterization will make it possible to join a large number of variations of packages and reduce the amount of code requiring maintenance. One of the aims of this project is to also create procedures that test parameter combinations and measure the combinatorial complexity brought about by parameterization, which should make testing parameteric variants easy too.
I expect parameterization to be particularly useful for running Guix on exotic hardware (such as static minimalistic targets) or on High-Performance Computing Systems (specific architecture optimizations) and make it generally easy to tailor a lot of packages for a particular system's requirements.
This update marks the completion of this Google Summer of Code project's midterms. I'd like to thank my mentors Pjotr Prins and Gábor Boskovit as well as Ludovic Courtès, Arun Isaac and Efraim Flashner for their guidance and help, without which I don't think I'd have been able to reach this milestone. I'm also very grateful to the many wonderful people in the Guix community that provided me with a lot of useful advice and suggestions.
Stay tuned for updates, and happy hacking!