Hi,
I've been thinking a lot about the structure of Common Lisp projects lately and would like to turn those thoughts into a public discussion.
I believe that some written guidelines for ASDF system authors could really improve how the Common Lisp ecosystem functions as a whole.
The documentation on the ASDF website is comprehensive with respect to the capabilities of its implementation, but there are few guidelines on how developers are supposed to structure their systems. I perceive a need for a manual chapter or document that consolidates best practices advice and somehow documents the `intended' use of ASDFs countless features. This information is currently scattered all over the ASDF manual. Getting the big picture is possible but kind of difficult.
The new section `Extensions' on the ASDF project website as well as the FAQ section in the manual are very much a step in the right direction.
Some of the questions that I think need a documented solution which could also act as a community convention:
- How to properly conditionalize on *FEATURES*.
This is touched briefly in manual section 6.2.1.3 `Required features'. There's a note explaining that excluding components through the #+ syntax is a bad idea, and that the correct way of doing it i.e. (:file "foo" :depends-on (:feature :sbcl)) does not work at this time. In the meantime, Nikodemus has written MADEIRA-PORT for this exact purpose.
- Implementing TEST-OP
The manual tells me that `it has proven difficult to define how the test operation should signal its results' and that TEST-OP does nothing by default. Current practice seems to be defining TEST-OP as a method that loads another system and then somehow executes all tests, failing the operation when some test did not pass. This is noted in the FAQ section and should really be the official way.
- How to specify optional dependencies in the `correct' way?
Suppose I'm working on a library that can use either IRONCLAD or CL+SSL to generate random bytes using a strong crypto algorithm. I don't want to clutter the final application image with both crypto libraries but rather use whatever happens to be the application developer's choice.
I have several options to do this.
1) Use :WEAKLY-DEPENDS-ON to rely on the application's transient dependencies to load either one. This is obviously a nice solution, but I have to document the requirement of loading at least one of them in the README and figure out which one to use at runtime. 2) Define two systems, MY-LIBRARY-IRONCLAD and MY-LIBRARY-CL+SSL. This would force the user to require one of them in order to use the library. I also need to document this, but do not have to switch at runtime. 3) Solve the problem outside of ASDF
- How much `formalization' of the build process pays off in the long run?
ASDF could obviously have more uses if people followed a common guideline. What's the advantage of custom component classes vs. a custom operation that is hooked into COMPILE-OP via :IN-ORDER-TO? What's used by existing systems?
- Whether or not to define a package for the system definition
The documentation uses (IN-PACKAGE :ASDF) in section 5.1. Common practice seems to be defining a package inside the system definition file if the system uses custom component classes or features that require toplevel forms other than ASDF:DEFSYSTEM. :DEFSYSTEM-DEPENDS-ON is also an option.
- What are some good practices when building applications with ASDF?
As the manual points out, `ASDF presents three faces'. People who want to use the ASDF for applications have very different needs from library developers and will often want tight control over their dependency graph.
Are versioned dependencies usable and practical? How can I override decisions made by library authors in my application's build process? What's a good way to prevent package name conflicts?
Some `war stories' from commercial application development could really help others arrive at a good solution quickly.
I see that `style guide for .asd files' is a TODO item in the manual, so it seems some of those question are already being addressed. I am willing to do documentation work as soon as I understand people's intentions.
Looking forward to your point of view,
Felix
Dear Felix,
sorry for having taken so long getting back to you.
On Mon, Dec 10, 2012 at 8:08 PM, Felix Lange fjl@twurst.com wrote:
Hi,
I've been thinking a lot about the structure of Common Lisp projects lately and would like to turn those thoughts into a public discussion.
I believe that some written guidelines for ASDF system authors could really improve how the Common Lisp ecosystem functions as a whole.
That's very possible. I'm looking forward to reviewing any document you write, and I'm accepting patches to the ASDF documentation.
The documentation on the ASDF website is comprehensive with respect to the capabilities of its implementation, but there are few guidelines on how developers are supposed to structure their systems.
Indeed. Even links to existing systems would be nice.
I perceive a need for a manual chapter or document that consolidates best practices advice and somehow documents the `intended' use of ASDFs countless features. This information is currently scattered all over the ASDF manual. Getting the big picture is possible but kind of difficult.
The new section `Extensions' on the ASDF project website as well as the FAQ section in the manual are very much a step in the right direction.
Some of the questions that I think need a documented solution which could also act as a community convention:
How to properly conditionalize on *FEATURES*.
This is touched briefly in manual section 6.2.1.3 `Required features'. There's a note explaining that excluding components through the #+ syntax is a bad idea, and that the correct way of doing it i.e. (:file "foo" :depends-on (:feature :sbcl)) does not work at this time. In the meantime, Nikodemus has written MADEIRA-PORT for this exact purpose.
A new, sensible, way of doing things has just been introduced in 2.26.22: :if-feature. The only downside is that it's not backwards compatible, so you have to make sure you and your user upgrade ASDF before you use it; or you should wait a few months for ASDF 2.27 to have been released and propagated. In the meantime I recommend using #+.
:if-feature :foo is just like #+foo except that with #+foo you also have to annotate the dependency references. Not so with if-feature.
Implementing TEST-OP
The manual tells me that `it has proven difficult to define how the test operation should signal its results' and that TEST-OP does nothing by default. Current practice seems to be defining TEST-OP as a method that loads another system and then somehow executes all tests, failing the operation when some test did not pass. This is noted in the FAQ section and should really be the official way.
Yes, probably.
How to specify optional dependencies in the `correct' way?
Suppose I'm working on a library that can use either IRONCLAD or CL+SSL to generate random bytes using a strong crypto algorithm. I don't want to clutter the final application image with both crypto libraries but rather use whatever happens to be the application developer's choice.
I have several options to do this.
- Use :WEAKLY-DEPENDS-ON to rely on the application's transient dependencies to load either one. This is obviously a nice solution, but I have to document the requirement of loading at least one of them in the README and figure out which one to use at runtime.
- Define two systems, MY-LIBRARY-IRONCLAD and MY-LIBRARY-CL+SSL. This would force the user to require one of them in order to use the library. I also need to document this, but do not have to switch at runtime.
- Solve the problem outside of ASDF
2 and/or 3 are the obvious only sensible choices.
I am personally *STRONGLY AGAINST* :WEAKLY-DEPENDS-ON, and would NEVER have allowed this feature inside ASDF under my watch. It's just a booby trap waiting to explode in the face of users: it makes the meaning of your software depend on what was in environment at some earlier time on which you don't even always have control. It's the promise of endless hours of frustration due to build bugs. The only reason it still exists is that I lacked the care and energy to track down those who use it and LART them before I remove the feature.
With or without ASDF, implementing your library in a parametric way and providing some default behavior configurable through hooks is the one and only proper way of writing software.
If you want something to assemble programs out of other programs in a way that varies depending on various parameters, what you need is a layer on top of ASDF. And then, you might as well handle versions and compatibility constraints, in a way that ASDF never can and never will.
How much `formalization' of the build process pays off in the long run?
ASDF could obviously have more uses if people followed a common guideline. What's the advantage of custom component classes vs. a custom operation that is hooked into COMPILE-OP via :IN-ORDER-TO? What's used by existing systems?
I'm not sure what you mean, but there has been a massive cleanup of the dependency mechanism in ASDF 2.26.x, and :IN-ORDER-TO is now much more powerful than it used to be, since (1) it can now return operation and component objects, not just names relative to current operation and current component's parent. (2) TRAVERSE will no longer automatically propagate all operations downward, so you won't get screwed anymore when that's not what you wanted.
All in all, you can now express a wider variety of dependency patterns using COMPONENT-DEPENDS-ON and :IN-ORDER-TO than was possible before. Whether people will actually take advantage of this feature, I don't know.
Whether or not to define a package for the system definition
The documentation uses (IN-PACKAGE :ASDF) in section 5.1. Common practice seems to be defining a package inside the system definition file if the system uses custom component classes or features that require toplevel forms other than ASDF:DEFSYSTEM. :DEFSYSTEM-DEPENDS-ON is also an option.
My personal recommendation is that a .asd file should have ONE AND ONLY ONE FORM, the defsystem form. If you need to define classes and methods, this should happen in another system that you DEFSYSTEM-DEPENDS-ON, and said system also has ONE AND ONLY ONE FORM in its asd, its own defsystem form.
What are some good practices when building applications with ASDF?
As the manual points out, `ASDF presents three faces'. People who want to use the ASDF for applications have very different needs from library developers and will often want tight control over their dependency graph.
Are versioned dependencies usable and practical? How can I override decisions made by library authors in my application's build process? What's a good way to prevent package name conflicts?
Some `war stories' from commercial application development could really help others arrive at a good solution quickly.
Personally, I believe ASDF versioning is only a stop gap to detect obvious incompatibilities early. It is NOT meant as a solution for managing versions. Real version management would require significant work, and either adding a lot of code to ASDF or doing things outside of it.
My war story is that at ITA, we have a much saner build process now that we abandoned any pretense of having optional components. It just doesn't work, and creates a nightmare for quality control and deployment to production.
For production code, we import all dependencies in our source control, and make sure ASDF is configured to see these and only these while building. Our source control system *is* the version management system. No one in his right mind should ever do otherwise for anything serious. And if you're not serious, it doesn't matter.
I see that `style guide for .asd files' is a TODO item in the manual, so it seems some of those question are already being addressed. I am willing to do documentation work as soon as I understand people's intentions.
That would be great! Thanks a lot for the undertaking.
Looking forward to your point of view,
Felix
Best regards,
—♯ƒ • François-René ÐVB Rideau •Reflection&Cybernethics• http://fare.tunes.org Superstition brings bad luck. — Paul Carvel