330 lines
12 KiB
HTML
330 lines
12 KiB
HTML
![]() |
<!DOCTYPE html>
|
|||
|
<html lang="en">
|
|||
|
<head>
|
|||
|
<meta name="generator" content=
|
|||
|
"HTML Tidy for HTML5 for Linux version 5.2.0">
|
|||
|
<title>Defining Systems</title>
|
|||
|
<meta charset="utf-8">
|
|||
|
<meta name="description" content="A collection of examples of using Common Lisp">
|
|||
|
<meta name="viewport" content=
|
|||
|
"width=device-width, initial-scale=1">
|
|||
|
<link rel="icon" href=
|
|||
|
"assets/cl-logo-blue.png"/>
|
|||
|
<link rel="stylesheet" href=
|
|||
|
"assets/style.css">
|
|||
|
<script type="text/javascript" src=
|
|||
|
"assets/highlight-lisp.js">
|
|||
|
</script>
|
|||
|
<script type="text/javascript" src=
|
|||
|
"assets/jquery-3.2.1.min.js">
|
|||
|
</script>
|
|||
|
<script type="text/javascript" src=
|
|||
|
"assets/jquery.toc/jquery.toc.min.js">
|
|||
|
</script>
|
|||
|
<script type="text/javascript" src=
|
|||
|
"assets/toggle-toc.js">
|
|||
|
</script>
|
|||
|
|
|||
|
<link rel="stylesheet" href=
|
|||
|
"assets/github.css">
|
|||
|
|
|||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> – Defining Systems</h1>
|
|||
|
<div id="logo-container">
|
|||
|
<a href="index.html">
|
|||
|
<img id="logo" src="assets/cl-logo-blue.png"/>
|
|||
|
</a>
|
|||
|
|
|||
|
<div id="searchform-container">
|
|||
|
<form onsubmit="duckSearch()" action="javascript:void(0)">
|
|||
|
<input id="searchField" type="text" value="" placeholder="Search...">
|
|||
|
</form>
|
|||
|
</div>
|
|||
|
|
|||
|
<div id="toc-container" class="toc-close">
|
|||
|
<div id="toc-title">Table of Contents</div>
|
|||
|
<ul id="toc" class="list-unstyled"></ul>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div id="content-container">
|
|||
|
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> – Defining Systems</h1>
|
|||
|
|
|||
|
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
|
|||
|
<p class="announce">
|
|||
|
📹 💻
|
|||
|
<a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPMACROSPOWER" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover vindarel's Lisp course in videos with this September coupon.</a>
|
|||
|
<strong>
|
|||
|
Recently added: 18 videos on MACROS.
|
|||
|
</strong>
|
|||
|
<a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</a>.
|
|||
|
</p>
|
|||
|
<p class="announce-neutral">
|
|||
|
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
|
|||
|
</p>
|
|||
|
|
|||
|
|
|||
|
<div id="content"
|
|||
|
<p>A <strong>system</strong> is a collection of Lisp files that together constitute an application or a library, and that should therefore be managed as a whole. A <strong>system definition</strong> describes which source files make up the system, what the dependencies among them are, and the order they should be compiled and loaded in.</p>
|
|||
|
|
|||
|
<h2 id="asdf">ASDF</h2>
|
|||
|
|
|||
|
<p><a href="https://gitlab.common-lisp.net/asdf/asdf">ASDF</a> is the standard build
|
|||
|
system for Common Lisp. It is shipped in most Common Lisp
|
|||
|
implementations. It includes
|
|||
|
<a href="https://gitlab.common-lisp.net/asdf/asdf/blob/master/uiop/README.md">UIOP</a>,
|
|||
|
<em>“the Utilities for Implementation- and OS- Portability”</em>. You can read
|
|||
|
<a href="https://common-lisp.net/project/asdf/asdf.html">its manual</a> and the
|
|||
|
<a href="https://gitlab.common-lisp.net/asdf/asdf/blob/master/doc/best_practices.md">tutorial and best practices</a>.</p>
|
|||
|
|
|||
|
<p><a name="example"></a></p>
|
|||
|
|
|||
|
<h2 id="simple-examples">Simple examples</h2>
|
|||
|
|
|||
|
<h3 id="loading-a-system-definition">Loading a system definition</h3>
|
|||
|
|
|||
|
<p>When you start your Lisp, it knows about its internal modules and, by
|
|||
|
default, it has no way to know that your shiny new project is located
|
|||
|
under your <code>~/code/foo/bar/new-ideas/</code> directory. So, in order to load
|
|||
|
your project in your image, you have one of three ways:</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>use ASDF or Quicklisp defaults</li>
|
|||
|
<li>configure where ASDF or Quicklisp look for project definitions</li>
|
|||
|
<li>load your project definition explicitely.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Please read our section on the <a href="getting-started.html#how-to-load-an-existing-project">getting started#how-to-load-an-existing-project</a> page.</p>
|
|||
|
|
|||
|
<h3 id="loading-a-system">Loading a system</h3>
|
|||
|
|
|||
|
<p>Once your Lisp knows what your system is and where it lives, you can load it.</p>
|
|||
|
|
|||
|
<p>The most trivial use of ASDF is by calling <code>asdf:load-system</code> to load your library.
|
|||
|
Then you can use it.
|
|||
|
For instance, if it exports a function <code>some-fun</code> in its package <code>foobar</code>,
|
|||
|
then you will be able to call it with <code>(foobar:some-fun ...)</code> or with:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(in-package :foobar)
|
|||
|
(some-fun ...)
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>You can also use Quicklisp.</p>
|
|||
|
|
|||
|
<p>Quicklisp calls ASDF under the hood, with the advantage that it will download and install any dependency if they are not already installed.</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(ql:quickload "foobar")
|
|||
|
;; =>
|
|||
|
;; installs all dependencies
|
|||
|
;; and loads the system.
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>Also, you can use SLIME to load a system, using the <code>M-x slime-load-system</code> Emacs command or the <code>, load-system</code> comma command in the prompt.
|
|||
|
The interesting thing about this way of doing it is that SLIME collects all the system warnings and errors in the process,
|
|||
|
and puts them in the <code>*slime-compilation*</code> buffer, from which you can interactively inspect them after the loading finishes.</p>
|
|||
|
|
|||
|
<h3 id="testing-a-system">Testing a system</h3>
|
|||
|
|
|||
|
<p>To run the tests for a system, you may use:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(asdf:test-system :foobar)
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>The convention is that an error SHOULD be signalled if tests are unsuccessful.</p>
|
|||
|
|
|||
|
<h3 id="designating-a-system">Designating a system</h3>
|
|||
|
|
|||
|
<p>The proper way to designate a system in a program is with lower-case
|
|||
|
strings, not symbols, as in:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(asdf:load-system "foobar")
|
|||
|
(asdf:test-system "foobar")
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<h3 id="how-to-write-a-trivial-system-definition">How to write a trivial system definition</h3>
|
|||
|
|
|||
|
<p>A trivial system would have a single Lisp file called <code>foobar.lisp</code>, located at the project’s root.
|
|||
|
That file would depend on some existing libraries,
|
|||
|
say <code>alexandria</code> for general purpose utilities,
|
|||
|
and <code>trivia</code> for pattern-matching.
|
|||
|
To make this system buildable using ASDF,
|
|||
|
you create a system definition file called <code>foobar.asd</code>,
|
|||
|
with the following contents:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(asdf:defsystem "foobar"
|
|||
|
:depends-on ("alexandria" "trivia")
|
|||
|
:components ((:file "foobar")))
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>Note how the type <code>lisp</code> of <code>foobar.lisp</code>
|
|||
|
is implicit in the name of the file above.
|
|||
|
As for contents of that file, they would look like this:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(defpackage :foobar
|
|||
|
(:use :common-lisp :alexandria :trivia)
|
|||
|
(:export
|
|||
|
#:some-function
|
|||
|
#:another-function
|
|||
|
#:call-with-foobar
|
|||
|
#:with-foobar))
|
|||
|
|
|||
|
(in-package :foobar)
|
|||
|
|
|||
|
(defun some-function (...)
|
|||
|
...)
|
|||
|
...
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>Instead of <code>using</code> multiple complete packages, you might want to just import parts of them:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(defpackage :foobar
|
|||
|
(:use #:common-lisp)
|
|||
|
(:import-from #:alexandria
|
|||
|
#:some-function
|
|||
|
#:another-function))
|
|||
|
(:import-from #:trivia
|
|||
|
#:some-function
|
|||
|
#:another-function))
|
|||
|
...)
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<h4 id="using-the-system-you-defined">Using the system you defined</h4>
|
|||
|
|
|||
|
<p>Assuming your system is installed under <code>~/common-lisp/</code>,
|
|||
|
<code>~/quicklisp/local-projects/</code> or some other filesystem hierarchy
|
|||
|
already configured for ASDF, you can load it with: <code>(asdf:load-system "foobar")</code>.</p>
|
|||
|
|
|||
|
<p>If your Lisp was already started when you created that file,
|
|||
|
you may have to, either:</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>load the new .asd file: <code>(asdf:load-asd "path/to/foobar.asd")</code>, or with <code>C-c C-k</code> in Slime to compile and load the whole file.
|
|||
|
<ul>
|
|||
|
<li>note: avoid using the built-in <code>load</code> for ASDF files, it may work but <code>asdf:load-asd</code> is preferred.</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><code>(asdf:clear-configuration)</code> to re-process the configuration.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h3 id="how-to-write-a-trivial-testing-definition">How to write a trivial testing definition</h3>
|
|||
|
|
|||
|
<p>Even the most trivial of systems needs some tests,
|
|||
|
if only because it will have to be modified eventually,
|
|||
|
and you want to make sure those modifications don’t break client code.
|
|||
|
Tests are also a good way to document expected behavior.</p>
|
|||
|
|
|||
|
<p>The simplest way to write tests is to have a file <code>foobar-tests.lisp</code>
|
|||
|
and modify the above <code>foobar.asd</code> as follows:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(asdf:defsystem "foobar"
|
|||
|
:depends-on ("alexandria" "trivia")
|
|||
|
:components ((:file "foobar"))
|
|||
|
:in-order-to ((test-op (test-op "foobar/tests"))))
|
|||
|
|
|||
|
(asdf:defsystem "foobar/tests"
|
|||
|
:depends-on ("foobar" "fiveam")
|
|||
|
:components ((:file "foobar-tests"))
|
|||
|
:perform (test-op (o c) (symbol-call :fiveam '#:run! :foobar)))
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>The <code>:in-order-to</code> clause in the first system
|
|||
|
allows you to use <code>(asdf:test-system :foobar)</code>
|
|||
|
which will chain into <code>foobar/tests</code>.
|
|||
|
The <code>:perform</code> clause in the second system does the testing itself.</p>
|
|||
|
|
|||
|
<p>In the test system, <code>fiveam</code> is the name of a popular test library,
|
|||
|
and the content of the <code>perform</code> method is how to invoke this library
|
|||
|
to run the test suite <code>:foobar</code>.
|
|||
|
Obvious YMMV if you use a different library.</p>
|
|||
|
|
|||
|
<h2 id="create-a-project-skeleton">Create a project skeleton</h2>
|
|||
|
|
|||
|
<p><a href="https://github.com/fukamachi/cl-project">cl-project</a> can be used to
|
|||
|
generate a project skeleton. It will create a default ASDF definition,
|
|||
|
generate a system for unit testing, etc.</p>
|
|||
|
|
|||
|
<p>Install with</p>
|
|||
|
|
|||
|
<pre><code>(ql:quickload "cl-project")
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>Create a project:</p>
|
|||
|
|
|||
|
<pre><code class="language-lisp">(cl-project:make-project #p"lib/cl-sample/"
|
|||
|
:author "Eitaro Fukamachi"
|
|||
|
:email "e.arrows@gmail.com"
|
|||
|
:license "LLGPL"
|
|||
|
:depends-on '(:clack :cl-annot))
|
|||
|
;-> writing /Users/fukamachi/Programs/lib/cl-sample/.gitignore
|
|||
|
; writing /Users/fukamachi/Programs/lib/cl-sample/README.markdown
|
|||
|
; writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample-test.asd
|
|||
|
; writing /Users/fukamachi/Programs/lib/cl-sample/cl-sample.asd
|
|||
|
; writing /Users/fukamachi/Programs/lib/cl-sample/src/hogehoge.lisp
|
|||
|
; writing /Users/fukamachi/Programs/lib/cl-sample/t/hogehoge.lisp
|
|||
|
;=> T
|
|||
|
</code></pre>
|
|||
|
|
|||
|
<p>And you’re done.</p>
|
|||
|
|
|||
|
|
|||
|
<p class="page-source">
|
|||
|
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/systems.md">systems.md</a>
|
|||
|
</p>
|
|||
|
</div>
|
|||
|
|
|||
|
<script type="text/javascript">
|
|||
|
|
|||
|
// Don't write the TOC on the index.
|
|||
|
if (window.location.pathname != "/cl-cookbook/") {
|
|||
|
$("#toc").toc({
|
|||
|
content: "#content", // will ignore the first h1 with the site+page title.
|
|||
|
headings: "h1,h2,h3,h4"});
|
|||
|
}
|
|||
|
|
|||
|
$("#two-cols + ul").css({
|
|||
|
"column-count": "2",
|
|||
|
});
|
|||
|
$("#contributors + ul").css({
|
|||
|
"column-count": "4",
|
|||
|
});
|
|||
|
</script>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<div>
|
|||
|
<footer class="footer">
|
|||
|
<hr/>
|
|||
|
© 2002–2023 the Common Lisp Cookbook Project
|
|||
|
<div>
|
|||
|
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">vindarel's Lisp course on Udemy</a>
|
|||
|
</div>
|
|||
|
</footer>
|
|||
|
|
|||
|
</div>
|
|||
|
<div id="toc-btn">T<br>O<br>C</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<script text="javascript">
|
|||
|
HighlightLisp.highlight_auto({className: null});
|
|||
|
</script>
|
|||
|
|
|||
|
<script type="text/javascript">
|
|||
|
function duckSearch() {
|
|||
|
var searchField = document.getElementById("searchField");
|
|||
|
if (searchField && searchField.value) {
|
|||
|
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
|
|||
|
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
|
|||
|
// https://duckduckgo.com/params
|
|||
|
// kj=b2: blue header in results page
|
|||
|
// kf=-1: no favicons
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
|
|||
|
|
|||
|
</body>
|
|||
|
</html>
|