emacs/var/elfeed/db/data/a1/a173856e9bbacaf5a1e330d301eb4fe510d6f5f9
2022-01-03 12:49:32 -06:00

138 lines
11 KiB
Plaintext

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="author" content="By Pierre Neidhardt" />
<title>Universal package management</title>
<style type="text/css">
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
</style>
<style type="text/css">
a.sourceLine { display: inline-block; line-height: 1.25; }
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
a.sourceLine:empty { height: 1.2em; position: absolute; }
.sourceCode { overflow: visible; }
code.sourceCode { white-space: pre; position: relative; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
code.sourceCode { white-space: pre-wrap; }
a.sourceLine { text-indent: -1em; padding-left: 1em; }
}
pre.numberSource a.sourceLine
{ position: relative; }
pre.numberSource a.sourceLine:empty
{ position: absolute; }
pre.numberSource a.sourceLine::before
{ content: attr(data-line-number);
position: absolute; left: -5em; text-align: right; vertical-align: baseline;
border: none; pointer-events: all;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
a.sourceLine::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<header>
<h1 class="title">Universal package management</h1>
<p class="author">By Pierre Neidhardt</p>
</header>
<p>With <a href="https://nyxt.atlas.engineer/article/release-2-pre-release-4.org">pre-release 4</a> we included a universal package manager interface in Nyxt. Thus far, it only supports <a href="https://guix.gnu.org/">Guix</a>. More package manager support will come in the future, contributions are welcome!</p>
<p>The package manager interface profits from the Nyxt minibuffer to allow for quick fuzzy-search and multi-selection actions.</p>
<p><img src="../static/image/article/describe-os-package.png" /></p>
<h1 id="why-in-nyxt">Why in Nyxt?</h1>
<p>One might wonder: why include a package manager interface in a web browser?</p>
<p>In short, a package manager allows us to integrate more easily with external programs and services. For instance, if we want to add an interface to IPFS, we can automatically install the required dependencies (after prompting the user of course).</p>
<p>With a functional package manager like Nix or Guix, it's even better, we can leverage the <code>nix-shell</code> and <code>guix environment</code> commands to run commands without installing them (i.e. without polluting the user environment).</p>
<h1 id="guix-interface-implementation-details">Guix interface implementation details</h1>
<p>Guix was the first interface we developed. This choice was made for two primary reasons:</p>
<ul>
<li><p>Being functional (in the mathematical sense), it is possibly the package manager with the broadest feature set. If our interface is complete enough to support Guix, then it's probably complete enough to support any other package manager.</p></li>
<li><p>Being written in Guile Scheme (which is a Lisp), we can naturally &quot;talk&quot; to Guix with no necessity for cumbersome language bindings or brittle shell output parsing.</p></li>
</ul>
<p>This last point is key:</p>
<p>Guix has a <code>guix repl</code> command, which is an interpreter (just like the one you'd get when running <code>sbcl</code> or <code>python</code>). Our approach is to start the interpreter, send it some Guile code and read the result.</p>
<p>To start the process, we use</p>
<div class="sourceCode" id="cb1" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb1-1" data-line-number="1">(uiop:launch-program `(<span class="st">&quot;guix&quot;</span> <span class="st">&quot;repl&quot;</span> <span class="st">&quot;--type=machine&quot;</span>)</a>
<a class="sourceLine" id="cb1-2" data-line-number="2"> <span class="bu">:input</span> <span class="bu">:stream</span> <span class="bu">:output</span> <span class="bu">:stream</span>)</a></code></pre></div>
<p>Note that the <code>guix</code> process will automatically terminate when its input stream gets closed (which happens when Nyxt terminates).</p>
<p>To simplify, we can send instructions to the Guix REPL by writing Scheme code to the input stream:</p>
<div class="sourceCode" id="cb2" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb2-1" data-line-number="1">(<span class="kw">format</span> (uiop:process-info-input guix-process)</a>
<a class="sourceLine" id="cb2-2" data-line-number="2"> <span class="st">&quot;~a~%~a&quot;</span></a>
<a class="sourceLine" id="cb2-3" data-line-number="3"> &#39;(use-modules (guix config))</a>
<a class="sourceLine" id="cb2-4" data-line-number="4"> &#39;%guix-version)</a></code></pre></div>
<p>Then we read the result with:</p>
<div class="sourceCode" id="cb3" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb3-1" data-line-number="1">(<span class="kw">read-line</span> (uiop:process-info-output guix-process) <span class="kw">nil</span> :eof)</a></code></pre></div>
<p>This should get us the current Guix version. It is really that easy!</p>
<p>Now this is where it gets mind blowing: we can write Guile Scheme code directly in our Common Lisp code base! While the syntax is similar, it's not completely interchangeable and the Common Lisp compiler will choke on some Guile Scheme symbols like <code>#t</code> and <code>#f</code> (true and false, respectively). The solution is to write reader macros. This is made easy thanks to the <a href="https://github.com/melisgl/named-readtables/">named-readtables</a> library which lets us effortlessly switch between Scheme and Common Lisp syntax whenever, and wherever we want.</p>
<p>This is what our function to list installed packages looks like:</p>
<div class="sourceCode" id="cb4" data-org-language="lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><a class="sourceLine" id="cb4-1" data-line-number="1">(<span class="kw">defun</span><span class="fu"> list-installed </span>(&amp;optional (profile &#39;%current-profile))</a>
<a class="sourceLine" id="cb4-2" data-line-number="2"> <span class="st">&quot;Return the installed package outputs in PROFILE as a list of (NAME (:VERSION :OUTPUT)).</span></a>
<a class="sourceLine" id="cb4-3" data-line-number="3"><span class="st">PROFILE is a full path to a profile.&quot;</span></a>
<a class="sourceLine" id="cb4-4" data-line-number="4"> (guix-eval</a>
<a class="sourceLine" id="cb4-5" data-line-number="5"> &#39;(use-modules</a>
<a class="sourceLine" id="cb4-6" data-line-number="6"> (guix profiles))</a>
<a class="sourceLine" id="cb4-7" data-line-number="7"></a>
<a class="sourceLine" id="cb4-8" data-line-number="8"> `(define make-output ,%make-output)</a>
<a class="sourceLine" id="cb4-9" data-line-number="9"></a>
<a class="sourceLine" id="cb4-10" data-line-number="10"> `(<span class="kw">map</span> make-output</a>
<a class="sourceLine" id="cb4-11" data-line-number="11"> (manifest-entries</a>
<a class="sourceLine" id="cb4-12" data-line-number="12"> (profile-manifest ,(<span class="kw">namestring</span> profile))))))</a></code></pre></div>
<p>Notice that it allows us to intertwine Common Lisp and Guile Scheme. We define a Common Lisp function which evaluates a bunch of Scheme expressions which themselves expand other Common Lisp expressions!</p>
<h1 id="future-work">Future work</h1>
<p>In addition to more package manager support (Nix, Dpkg, etc.), we would like to extend our Common Lisp &quot;system&quot; management to bring it under the same interface, thereby providing a uniform experience for the user (whether installing Lisp systems or system packages).</p>
<p><a href="https://www.quicklisp.org/beta/">Quicklisp</a> is a obvious target, and already partly implemented, but we are also considering <a href="https://www.clpm.dev/">CLPM</a> integration for the added benefit of full system version control.</p>
<p>Addendum: our experience with the Guix REPL has paved the way for more REPL interactions. This means that we are very close to implementing a universal REPL interface (à la Emacs' <code>comint-mode</code>). This means supporting interpreters from programming languages such as Guile, or Python, and giving the user the possibility to fiddle with foreign code from within the browser itself!</p>
<p>Thanks for reading!</p>
</body>
</html>