Commit c7b0b625 authored by Francois-Rene Rideau's avatar Francois-Rene Rideau
Browse files

Update to best_practices document

parent b3622011
...@@ -4,10 +4,10 @@ This document presents the current best practices and conventions ...@@ -4,10 +4,10 @@ This document presents the current best practices and conventions
for using ASDF 3, as of 2017. for using ASDF 3, as of 2017.
It is not a tutorial, though it starts like one, It is not a tutorial, though it starts like one,
because it assumes for each category of ASDF user because it assumes for each category of ASDF user
(beginner, simple user, more elaborate user, advanced user) (beginner, simple user, more elaborate user)
that he already knows what seems to be common knowledge among such users, that he already knows what seems to be common knowledge among such users,
and tries to complete this knowledge with less obvious points and tries to complete this knowledge with less obvious points
that are often wrong in systems in the wild. that are often wrong in systems seen in the wild.
[TOC] [TOC]
...@@ -42,13 +42,27 @@ The convention is that an error SHOULD be signalled if tests are unsuccessful. ...@@ -42,13 +42,27 @@ The convention is that an error SHOULD be signalled if tests are unsuccessful.
Using keywords to name systems is good and well at the REPL. Using keywords to name systems is good and well at the REPL.
However, when writing a program, a bootstrap script, or a system definition, However, when writing a program, a bootstrap script, or a system definition,
you SHOULD follow the good style of writing names in the canonical way; you SHOULD follow the good style of writing names in the canonical way;
and the canonical name of a system is a string, which by convention is in lower-case. and the canonical name of a system is a string, which by convention is in lower-case
(indeed any symbol used as system designator is downcased by `asdf:coerce-name`).
Thus, the proper way to designate a system in a program is with lower-case strings, as in: Thus, the proper way to designate a system in a program is with lower-case strings, as in:
(asdf:load-system "foobar") (asdf:load-system "foobar")
(asdf:test-system "foobar") (asdf:test-system "foobar")
(Historical note: MK-DEFSYSTEM and before it Genera's SCT
used string-upcase to normalize system names,
and use that as filenames, which made sense on Genera's filesystem
or using logical pathnames as common with MK-DEFSYSTEM.
Dan Barlow's ASDF, to "Play Nice With Unix", preferred lower-case file names,
and used string-downcase to normalize symbols into strings;
at the same time, he allowed you to specify mixed case or upper-case names by using a string.
The latter was probably a mistake, and I strongly recommend against using anything but lower-case.
If that weren't allowed, there would be a case for making lower-case symbols the default syntax,
and let the various case conversions do their job without quotes.
But that is not where we are.
In any case, system names designate `.asd` files, not Lisp bindings,
and this determines their syntax.)
### Trivial System Definition ### Trivial System Definition
...@@ -137,12 +151,14 @@ and would be colloquial for neither. ...@@ -137,12 +151,14 @@ and would be colloquial for neither.
But *friends don't let friends use ASDF 2*. But *friends don't let friends use ASDF 2*.
ASDF 3 has been available since 2013, and ASDF 3 has been available since 2013, and
all implementations have now provided it for a long time. all implementations have now been providing it for a long time.
Many systems, and growing, assume ASDF 3, possibly some you depend on, Many systems, and growing, assume ASDF 3, possibly some you depend on,
and it's pointless to maintain compatibility with a moribund system. and it's pointless to maintain compatibility with a moribund system
(especially so if you don't also check that all your dependencies do, too).
Upgrade your implementations and/or at least your ASDF, Upgrade your implementations and/or at least your ASDF,
and tell your friends to do as much. and tell your friends to do as much.
The very founding principle of ASDF 2 (and 3) was to provide users with The [very founding principle of ASDF 2](http://fare.livejournal.com/149264.html) (and 3)
was to provide users with
the ability to install a newer ASDF on top of an old one to fix its bugs the ability to install a newer ASDF on top of an old one to fix its bugs
(an ability that did not exist with ASDF 1); (an ability that did not exist with ASDF 1);
thus, users do not have to maintain compatibility with antique bugs. thus, users do not have to maintain compatibility with antique bugs.
...@@ -150,27 +166,28 @@ thus, users do not have to maintain compatibility with antique bugs. ...@@ -150,27 +166,28 @@ thus, users do not have to maintain compatibility with antique bugs.
These days, I even recommended that you should freely rely These days, I even recommended that you should freely rely
on features of ASDF 3.1 (2014) not present in ASDF 3.0 (2013), such as: on features of ASDF 3.1 (2014) not present in ASDF 3.0 (2013), such as:
`~/common-lisp/` being included in the source-registry by default; `~/common-lisp/` being included in the source-registry by default;
a full-fledged `run-program` with input and error-output support; `run-program` being full-fledged with input and error-output support;
or the `package-inferred-system` feature. or the `package-inferred-system` feature.
Indeed, if you want indefinite backward compatibility, why stop at ASDF 2? Conversely, if you want indefinite backward compatibility, why stop at ASDF 2?
Why not also support ASDF 1, and MK-DEFSYSTEM, and Why not also support ASDF 1, and MK-DEFSYSTEM, and
the original Lisp Machine DEFSYSTEM? the original Lisp Machine DEFSYSTEM?
A good rule of thumb for me is that it is not worth writing workarounds A good rule of thumb for me is that it is not worth writing workarounds
for bugs that were fixed or features that were changed two years ago or more. for bugs that were fixed or features that were changed two years ago or more.
Two years seems to be the time it takes for a release of ASDF Two years seems to be the time it takes for a release of ASDF
to be ubiquitously available by default on all implementations; to become ubiquitously available by default on all implementations;
and, remember, if a user must use an older implementation, and, remember, if a user must use an older implementation,
he can always trivially install a newer ASDF on top of it. he can always trivially install a newer ASDF on top of it.
Thus, by 2019, people should not be shy about Thus, by 2019, people should not be shy about
dropping support for versions older than 3.2 or 3.3. dropping support for ASDF versions older than 3.2 or 3.3.
And even before then, if you need a recent ASDF, just document it, And even before then, if you need a recent ASDF, just document it,
and tell your users to upgrade their implementation and/or their ASDF on top of it. and tell your users to upgrade their implementation and/or
install a recent ASDF on top of their implementation's.
### Trivial Packaging ### Trivial Packaging
In the previous testing code, `symbol-call` is a function defined in package `uiop`. In the previous testing code, `symbol-call` is a function defined in package `uiop`.
It that allows to deal with the fact that the package `:fiveam` isn't defined yet It helps deal with the fact that the package `:fiveam` isn't defined yet
at the time the `defsystem` form is read. at the time the `defsystem` form is read.
Thus you can't simply write `(fiveam:run! :foobar)`, as Thus you can't simply write `(fiveam:run! :foobar)`, as
this might cause a failure at read-time when file `foobar.asd` is first read this might cause a failure at read-time when file `foobar.asd` is first read
...@@ -180,15 +197,19 @@ This of course also holds for packages defined by system `foobar` itself. ...@@ -180,15 +197,19 @@ This of course also holds for packages defined by system `foobar` itself.
To get a symbol in it that is not a function being called, To get a symbol in it that is not a function being called,
you can also use `(find-symbol* :some-symbol :foobar)`, you can also use `(find-symbol* :some-symbol :foobar)`,
and you can use `symbol-value` to get the value bound to such a symbol as denoting a variable. and you can use `symbol-value` to get the value bound to such a symbol as denoting a variable.
For complex expressions, you can use `eval` and `read-from-string`, as in: For complex expressions, you can use `eval` and `read-from-string`,
`(eval (read-from-string "(foobar:some-fun foobar:*some-var*)"))`. as in `(eval (read-from-string "(foobar:some-fun foobar:*some-var*)"))`,
or equivalently, also from `uiop`: `(eval-input "(foobar:some-fun foobar:*some-var*)")`.
System definition files are loaded with the current `*package*` bound to the package `:asdf-user`, System definition files are loaded with the current `*package*` bound to the package `:asdf-user`,
that uses the packages `:cl`, `:asdf` and `:uiop`. that uses the packages `:cl`, `:asdf` and `:uiop`.
Therefore, you don't need to specify `cl:`, `asdf:` or `uiop:` prefixes Therefore, you don't need to and you SHOULD NOT specify `cl:`, `asdf:` or `uiop:` prefixes
when referring to symbols in these respective packages. when referring to symbols in these respective packages.
It is considered bad form to do so, unless you change the package. It is considered bad form to do so, unless you changed the package,
You SHOULD NOT use `(in-package ...)` or `(defpackage ...)` and even then, your package should probably use these packages, too.
In particular you SHOULD NOT to write `(asdf:defsystem "foobar" ...)`
instead of `(defsystem "foobar" ...)`.
Also, you SHOULD NOT use `(in-package ...)` or `(defpackage ...)`
if all you do can legitimately be done in package `ASDF-USER`. if all you do can legitimately be done in package `ASDF-USER`.
System definition files are loaded by a special function `asdf::load-asd`; System definition files are loaded by a special function `asdf::load-asd`;
...@@ -202,7 +223,8 @@ You SHOULD NOT encourage the illusion that a `.asd` file can be loaded with `cl: ...@@ -202,7 +223,8 @@ You SHOULD NOT encourage the illusion that a `.asd` file can be loaded with `cl:
by using `(in-package :asdf-user)` or anything, by using `(in-package :asdf-user)` or anything,
or by using the package prefix `asdf:` for `defsystem` and other such symbols. or by using the package prefix `asdf:` for `defsystem` and other such symbols.
You SHOULD be using the `slime-asdf` extension to SLIME You SHOULD be using the `slime-asdf` extension to SLIME
if you are going to edit `.asd` file and then load them from SLIME. if you are going to edit `.asd` file and then load them from SLIME,
as it will automatically use `load-asd` to load the file contents.
#### Digression about symbols and packages #### Digression about symbols and packages
...@@ -289,8 +311,8 @@ will ensure this operation is performed on that system ...@@ -289,8 +311,8 @@ will ensure this operation is performed on that system
(after all the necessary dependencies of such an action). (after all the necessary dependencies of such an action).
A short-hand `asdf:oos` is available for `asdf:operate` A short-hand `asdf:oos` is available for `asdf:operate`
(the name `oos` is an old acronym for `operate-on-system`, (the name `oos` is an old acronym for `operate-on-system`,
a function that existed in the old MK-DEFSYSTEM, but that doesn't exist in ASDF, and both functions existed as synonyms in the old MK-DEFSYSTEM;
that just has `operate`). the latter function doesn't exist in ASDF, which instead has just `operate`).
The operation is typically specified as a symbol that names the operation class. The operation is typically specified as a symbol that names the operation class.
Since ASDF 3, you can also use a keyword to specify an action in the ASDF package. Since ASDF 3, you can also use a keyword to specify an action in the ASDF package.
...@@ -328,7 +350,12 @@ in the trivial testing definition above. ...@@ -328,7 +350,12 @@ in the trivial testing definition above.
The convention above allows ASDF 3 and later to find a secondary system by name, The convention above allows ASDF 3 and later to find a secondary system by name,
by first looking for the associated primary system. by first looking for the associated primary system.
ASDF 3.2 or later will issue a `style-warning` when you violate this naming convention. ASDF 3.2 or later will issue a `warning` when you violate this naming convention.
(Historically, ASDF 1 (and 2) allowed arbitrary names for secondary systems,
and in practice many people used `foo-test` or such for secondary system names in `foo.asd`;
however, ASDF 1 (and 2) couldn't find those systems by name, and horrific bugs could happen
if a system was simultaneously defined in multiple files.)
### Simple System Definition ### Simple System Definition
...@@ -402,7 +429,8 @@ with an `in-package` form, optionally preceded by a `defpackage` form ...@@ -402,7 +429,8 @@ with an `in-package` form, optionally preceded by a `defpackage` form
You SHOULD NOT write `cl:in-package` or precede your `defpackage` with an `(in-package :cl-user)` You SHOULD NOT write `cl:in-package` or precede your `defpackage` with an `(in-package :cl-user)`
(which is stupid, because to be pedantic you'd have to `(cl:in-package :cl-user)`, (which is stupid, because to be pedantic you'd have to `(cl:in-package :cl-user)`,
at which point you may as well `(cl:defpackage ...)` and `(cl:in-package ...)`). at which point you may as well `(cl:defpackage ...)` and `(cl:in-package ...)`).
If it's a regular `cl-source-file`, it can assume the language is CL indeed. If it's a regular `cl-source-file`, it can assume the language is CL indeed,
and that the readtable something reasonable, etc.
#### Using symbols from ASDF and UIOP #### Using symbols from ASDF and UIOP
...@@ -415,6 +443,7 @@ or other deprecated functions that were once recommended in the time of ASDF 1. ...@@ -415,6 +443,7 @@ or other deprecated functions that were once recommended in the time of ASDF 1.
They will be removed in the near future (one to two year horizon). They will be removed in the near future (one to two year horizon).
ASDF 3.2 will issue a `style-warning` when you do, and ASDF 3.3 will issue a full `warning`, ASDF 3.2 will issue a `style-warning` when you do, and ASDF 3.3 will issue a full `warning`,
which will break the SBCL build. which will break the SBCL build.
See `backward-interface.lisp` for a list of deprecated function — or just heed the damn warnings.
### Simple Testing ### Simple Testing
...@@ -464,17 +493,30 @@ by evaluating `(asdf:make :foobar/executable)`: ...@@ -464,17 +493,30 @@ by evaluating `(asdf:make :foobar/executable)`:
:depends-on ("foobar") :depends-on ("foobar")
:components ((:file "main"))) :components ((:file "main")))
The `build-pathname` gives the name of the executable;
a `.exe` type will be automatically added on Windows.
As a horrible kluge that may go away in the future,
the output will be created in the system's directory (where the `foobar.asd` file resides)
when the `build-operation` matches the requested operation.
A future version of ASDF will probably instead have some `:output` argument to `operate`,
or some such thing, and drop this ugly special case.
You have been warned. Contact me if you want that sooner, or not at all.
There, file `main.lisp` defines a function `start-foobar` in package `foobar`, There, file `main.lisp` defines a function `start-foobar` in package `foobar`,
that takes no argument, and initializes and starts the executable; that takes no argument, and initializes and starts the executable ---
typically, it will be defined as something like: after `uiop` calls its own `*image-restore-hook*` and evaluates any provided `*image-prelude*`.
Importantly, libraries may register functions to call in the `*image-restore-hook*`
using `register-image-restore-hook` (see also `register-image-dump-hook`);
UIOP and ASDF themselves make use of this facility.
Typically, `start-foobar` will be defined as something like:
(defun start-foobar () (main (uiop:command-line-arguments))) (defun start-foobar () (main (uiop:command-line-arguments)))
Where function `main` parses the arguments Where function `main` parses the arguments
(a list of strings, excluding the magic C `argv[0]`, which can be computed as `(uiop:argv0)`) (a list of strings, excluding the magic C `argv[0]`, which can be computed as `(uiop:argv0)`)
and does whatever its magic. and does whatever its magic.
You may want to use `net.didierverna.clon` or `command-line-arguments` or a slew of others You may want to use the full-featured `net.didierverna.clon` or my small `command-line-arguments`
to parse the command-line arguments. or one of a slew of other libraries to parse the command-line arguments.
You may want to use `cl-scripting` to nicely wrap Lisp code into execution contexts You may want to use `cl-scripting` to nicely wrap Lisp code into execution contexts
that handle errors in a nice(r) user-visible way for the shell user. that handle errors in a nice(r) user-visible way for the shell user.
You may want to use `inferior-shell` if your program in turn invokes other shell programs. You may want to use `inferior-shell` if your program in turn invokes other shell programs.
...@@ -507,7 +549,7 @@ to define such system connections that are *automatically* loaded ...@@ -507,7 +549,7 @@ to define such system connections that are *automatically* loaded
when both the connected systems are loaded. when both the connected systems are loaded.
While I debugged that mechanism and made sure it works with ASDF 3, While I debugged that mechanism and made sure it works with ASDF 3,
I recommend against using it, because it introduces side-effects within the build. I recommend against using it, because it introduces side-effects within the build.
I recommend explicitly loading the system connections Instead I recommend explicitly loading the system connections
as part of the larger system that will use them. as part of the larger system that will use them.
## More Elaborate Examples ## More Elaborate Examples
...@@ -669,7 +711,11 @@ ASDF currently allows arbitrary Lisp code in a `.asd` file. ...@@ -669,7 +711,11 @@ ASDF currently allows arbitrary Lisp code in a `.asd` file.
I would like to deprecate that in the future to make ASDF more declarative. I would like to deprecate that in the future to make ASDF more declarative.
In the meantime, here are some guidelines for how to write such code properly. In the meantime, here are some guidelines for how to write such code properly.
First, `.asd` files are read with the current package being `asdf-user`. First, you SHOULD minimize the amount of such code and
you SHOULD strongly consider defining an extension
in a separate `.asd` file that you `:defsystem-depends-on`.
Then, `.asd` files are read with the current package being `asdf-user`.
You MAY use any symbols defined in packages `cl`, `asdf` and `uiop`, You MAY use any symbols defined in packages `cl`, `asdf` and `uiop`,
and you SHOULD stay in package `asdf-user` if it suffices; and you SHOULD stay in package `asdf-user` if it suffices;
you SHOULD NOT needlessly create a package for you system definition file you SHOULD NOT needlessly create a package for you system definition file
...@@ -750,11 +796,15 @@ Instead you SHOULD define methods on `asdf:perform`, `asdf:component-depends-on` ...@@ -750,11 +796,15 @@ Instead you SHOULD define methods on `asdf:perform`, `asdf:component-depends-on`
### Defining ASDF Extensions ### Defining ASDF Extensions
#### Source File Types #### Source File Types
You MUST NOT define methods on `source-file-type`. This bad ASDF 1 interface must die. You MUST NOT define methods on `source-file-type`. This bad ASDF 1 interface must die.
Instead, override the slot `type` for your file class and provide a proper `:initform`, as in: Instead, override the slot `type` for your file class and provide a proper `:initform`, as in:
(defclass cl-source-file.l (cl-source-file) ((type :initform "l"))) (defclass cl-source-file.l (cl-source-file) ((type :initform "l")))
You can then provide the name of that class as class name for individual components,
or as `:default-component-class` to relevant systems or modules.
#### Conditional Outputs and Conditional Perform #### Conditional Outputs and Conditional Perform
An operation MUST actually create all the outputs declared by the `output-files` method. An operation MUST actually create all the outputs declared by the `output-files` method.
...@@ -769,7 +819,7 @@ where the action is not performed. ...@@ -769,7 +819,7 @@ where the action is not performed.
In that case, both the `output-files` and `input-files` methods In that case, both the `output-files` and `input-files` methods
should probably return `nil` when that condition is met. should probably return `nil` when that condition is met.
Alternatively, if avoiding some actions is uniform for all Alternatively, if avoiding some actions is uniform for all
operations on a given component, you may want to override or wrap around operations on a given component, you MAY want to override or wrap around
the `component-if-feature` method for that component instead of defining the `component-if-feature` method for that component instead of defining
all those `perform`, `output-files` and `input-files` methods. all those `perform`, `output-files` and `input-files` methods.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment