README.org
September 2, 2025 · View on GitHub
#+title: Vend
=vend= is a dependency management tool for Common Lisp. The concept is simple:
#+begin_quote Just vendor your dependencies! #+end_quote
=vend='s focus is first and foremost on simplicity.
- =vend get= to fetch dependencies directly into your project.
- =vend repl= to open an isolated Lisp session.
- =vend test= to run all your test suites.
- =vend= only has 3 dependencies itself and compiles to a 400kb binary.
- Neither Quicklisp (the tool) nor Quicklisp (the repository) are used.
- Trivial integration with Sly / Slime / Lem / Slimv.
It's time for some peace of mind.
- Table of Contents :TOC_5_gh:noexport:
- [[#why-vendor-dependencies][Why vendor dependencies?]]
- [[#why-vend][Why vend?]]
- [[#best-practices][Best Practices]]
- [[#installation][Installation]]
- [[#from-source][From Source]]
- [[#linux][Linux]]
- [[#arch][Arch]]
- [[#editor-integration][Editor Integration]]
- [[#emacs][Emacs]]
- [[#doom-emacs][Doom Emacs]]
- [[#lem][Lem]]
- [[#vim][Vim]]
- [[#emacs][Emacs]]
- [[#usage][Usage]]
- [[#vend-get][vend get]]
- [[#vend-repl][vend repl]]
- [[#vend-check][vend check]]
- [[#vend-eval][vend eval]]
- [[#vend-graph][vend graph]]
- [[#vend-init][vend init]]
- [[#vend-search][vend search]]
- [[#vend-test][vend test]]
- [[#setup][Setup]]
- [[#integrations][Integrations]]
- [[#ci-integration][CI Integration]]
- [[#coverage][Coverage]]
- [[#compiler-compatibility][Compiler Compatibility]]
- [[#faq][FAQ]]
- [[#how-do-i-update-dependencies][How do I update dependencies?]]
- [[#how-can-i-build-executables-of-my-application][How can I build executables of my application?]]
- [[#how-do-i-refer-to-local-dependencies][How do I refer to local dependencies?]]
- [[#can-i-install-new-dependencies-while-vend-repl-is-running][Can I install new dependencies while =vend repl= is running?]]
- [[#does-this-use-git-submodules][Does this use git submodules?]]
- [[#why-ecl][Why ECL?]]
- Why vendor dependencies?
Fast internet connections and centralised repositories have gotten us used to the idea that dependencies are free; things we can pluck off a shelf and employ with no risk. In languages like Javascript and Rust, it's not uncommon to have projects with several hundred dependencies.
But are these really free? Have you inspected each one? Do you know and trust the authors, as well as the pipeline that feeds you updates? Is your project still guaranteed to compile in 5-10 years with no extra intervention? Can you write code on a plane? Can users reliably build your project after little more than a =git clone=?
The truth is that your dependencies are your code. And quite literally so - they might make up the majority of your final compiled artifact. By vendoring your dependencies directly into your project, you're taking responsibility for its true form.
** Why vend?
Dependency management in Common Lisp has traditionally centred around [[https://www.quicklisp.org/beta/][Quicklisp]]. A desire for access to more rapid package updates spawned [[https://ultralisp.org/][Ultralisp]]. The need for version pinning and isolation birthed [[https://github.com/fukamachi/qlot][Qlot]]. The want for a better distribution system brought us [[https://github.com/ocicl/ocicl][ocicl]].
But, could there be a simpler paradigm than just /downloading the code and putting it right there/?
With =vend=:
- We need not perform bespoke installation scripts to get started.
- We need not wait for Quicklisp to update.
- We need not relegate all our systems to =~/common-lisp/=.
- We need not worry about where ASDF is looking for systems.
- We need not fret over tools performing strange internal overrides.
- We need not manage extra config files or lockfiles.
Plus, =vend= is actually an external tool with extra commands to help you inspect and manage your dependencies.
** Best Practices
For library development, you are encouraged to:
- =vend get= to fetch dependencies.
- Add =vendored/*= to your =.gitignore=.
As this allows downstream users the most freedom when consuming your library.
For application development, you are encouraged to:
- =vend get= to fetch dependencies.
- =rm -rf vendored/**/.git/=
- Actively commit =vendored/= to git.
By committing these dependencies directly, it is never a mystery to your users how your software should be provisioned.
- Installation
In all cases, =vend= requires [[https://ecl.common-lisp.dev/][ECL]] to build and run. However, it can be used to manage projects of any underlying compiler (see =vend repl=).
** From Source
=vend='s dependencies are all vendored, so it is enough to run:
#+begin_example make make install #+end_example
or manually:
#+begin_example ecl --load build.lisp mv vend ~/.local/bin/ #+end_example
** Linux
*** Arch
=vend= is [[https://aur.archlinux.org/packages/vend][available on the AUR]] and can be installed with tools like [[https://github.com/fosskers/aura][Aura]]:
#+begin_example aura -A vend #+end_example
- Editor Integration
If rough order of support / integration quality.
#+begin_quote ⚠ In all cases below, when starting a Lisp session, you must do so from the top-level of the repository. Doing this from the =.asd= file makes it easy. #+end_quote
** Emacs
[[https://github.com/joaotavora/sly][Sly]] and [[https://github.com/slime/slime][Slime]] have variables for setting how Lisp REPLs should be launched:
#+begin_src emacs-lisp (setq sly-default-lisp 'sbcl sly-lisp-implementations '((sbcl ("vend" "repl" "sbcl") :coding-system utf-8-unix) (ecl ("vend" "repl" "ecl") :coding-system utf-8-unix) (abcl ("vend" "repl" "abcl") :coding-system utf-8-unix) (clasp ("vend" "repl" "clasp") :coding-system utf-8-unix))) #+end_src
Adjust as necessary for Slime.
Note that adding ="--dynamic-space-size" "4GB"= to the =sbcl= list is useful for hungry projects like [[https://github.com/Shirakumo/trial][Trial]].
*** Doom Emacs
As of 2025 January, you also need to manually disable =sly-stepper= and =sly-quicklisp= or they will interfere with the REPL starting:
#+begin_src emacs-lisp (package! sly-stepper :disable t) (package! sly-quicklisp :disable t) #+end_src
** Lem
[[https://lem-project.github.io/][Lem]] is built and configured in Common Lisp and so offers excellent support for it. To start a REPL with =vend=:
#+begin_example
C-u M-x slime
And all your local systems will be available for loading.
** Vim
[[https://github.com/kovisoft/slimv][Slimv]] is a port of Slime from Emacs that utilises Slime's Swank backend server for a very similar experience to Emacs. However, unlike Emacs which supports multiple running Lisps, Slimv requires one standalone server that persists through Vim restarts.
If we want our dependencies in =vendored/= to be visible to Slimv, we must start its server manually from our project directory:
#+begin_example
cd project/ vend repl ecl --load /home/YOU/.vim/pack/common-lisp/start/slimv/slime/start-swank.lisp #+end_example
Now, =,c= (REPL Connect) within Vim will automatically find the running server, and you can load any system available in your project and in =vendored/=.
If you want to switch projects, you would need to quit the REPL server manually and restart it as above. You may also wish to set a shell alias or create a wrapper script for the long invocation shown above.
- Usage ** vend get
From the top-level directory of your project, simply =vend get= to fetch all dependencies. They will be stored in =vendored/=. From here, they are yours. You are free to inspect, edit, and remove them as you please.
#+begin_example
vend get [vend] Downloading dependencies. [vend] Fetching FN-MACRO [vend] Fetching ARROW-MACROS [vend] Fetching TRANSDUCERS ... [vend] Done. #+end_example
If during your usage of =vend= you discover a project that fails to resolve, please [[https://github.com/fosskers/vend/issues][open an Issue]].
** vend repl
From the top-level directory of your project, =vend repl= opens a Lisp REPL while instructing ASDF to only look within this directory tree for =.asd= files.
#+begin_example
vend repl This is SBCL 2.4.9, an implementation of ANSI Common Lisp. (asdf:load-system :transducers) ; Lots of compilation here. T
#+end_example
By default, =vend repl= starts SBCL. You can easily override this:
#+begin_example
vend repl ecl ECL (Embeddable Common-Lisp) 24.5.10 (+ 1 1) #+end_example
=vend repl= actually accepts any number of arguments, which is useful for adding additional settings for hungry projects like [[https://github.com/Shirakumo/trial][Trial]]:
#+begin_example
vend repl sbcl --dynamic-space-size 4GB #+end_example
** vend check
Since your dependencies are your code, you should care about what's in there.
#+begin_example
vend check DYNAMIC-CLASSES is deprecated. PGLOADER -> CL-MARKDOWN -> DYNAMIC-CLASSES TRIVIAL-BACKTRACE is deprecated. PGLOADER -> TRIVIAL-BACKTRACE #+end_example
Woops! And while Common Lisp has a culture of "done means done, not dead", it's still important to know what you're getting yourself into.
** vend eval
Run arbitrary Lisp code with all your dependencies available.
#+begin_example
vend eval "(asdf:load-system :transducers) (in-package :transducers) (princ (transduce #'pass #'first '(1)))" #+end_example
You can pass as many s-expressions as you want within the argument string. Useful also for scripting; you can load a known system and run any function within it.
** vend graph
After running =vend get=, you can inspect your full dependency graph via =vend graph=:
#+begin_example
vend graph #+end_example
This produces a =deps.dot= file, which can be viewed directly with =xdot=:
#+begin_example
xdot deps.dot #+end_example
Or you can render it into a static PNG to send around to your friends to brag about how few dependencies you're using:
#+begin_example
cat deps.dot | dot -Tpng -o deps.png #+end_example
In the case of =vend=, this produces an image like:
[[file:deps.png]]
If the graph is too messy, you can "focus" it with an extra argument to =vend graph=:
#+begin_example vend graph lem #+end_example
In the case of the large [[https://github.com/lem-project/lem][Lem]] project, this would display a graph of only the core application and not its test suites, etc.
** vend init
If you don't even have a project yet, =vend init= will create a simple skeleton for you.
#+begin_example
vend init foo #+end_example
This generates:
#+begin_example foo ├── foo.asd └── src └── package.lisp #+end_example
** vend search
Search the known systems via some term.
#+begin_example
vend search woo woo https://github.com/fukamachi/woo.git wookie https://github.com/orthecreedence/wookie.git #+end_example
** vend test
Detect and run testable systems. Yields a proper error code to the terminal if failures are detected (good for CI!).
#+begin_example
vend test [vend] Running tests. ... ;; Summary: Passed: 68 Failed: 0 Skipped: 0 #+end_example
Pass an additional arg to switch compilers:
#+begin_example
vend test ecl #+end_example
*** Setup
In order for the test suite to be detected properly, your systems must look something like this:
#+begin_src lisp (defsystem "foo" :components ((:module "src" :components ((:file "package")))) :in-order-to ((test-op (test-op :foo/tests))))
(defsystem "foo/tests" :depends-on (:foo :parachute) :components ((:module "tests" :components ((:file "tests")))) :perform (test-op (op c) (symbol-call :parachute :test :foo/tests))) #+end_src
*** Integrations
| Library | Compatibility | Notes | |-----------+---------------+--------------------------------------------| | [[https://github.com/Shinmera/parachute][Parachute]] | ✅ | | | [[https://codeberg.org/cage/clunit2][Clunit2]] | ✅ | | | [[https://github.com/lispci/fiveam][FiveAM]] | ✅* | Test system must export =all-tests= function | | [[https://github.com/fukamachi/rove][Rove]] | ❌ | Usage of =package-inferred-system= |
If no specific (or an unknown) testing library is used, =vend= will fall back to a naive =(asdf:test-system :foo)= call. However, this will not yield the correct error code to the terminal in the event of test failures.
If you desire integration with libraries not listed here, please [[https://github.com/fosskers/vend/issues][open an Issue]].
- CI Integration
A [[https://github.com/marketplace/actions/common-lisp-testing][Github Action]] is available that utilises =vend=. In the simplest case:
#+begin_src yaml on: push: branches: [master] pull_request:
jobs: test: runs-on: ubuntu-latest name: Unit Tests steps: - name: Clone the Project uses: actions/checkout@v4
- name: Set up Common Lisp
uses: fosskers/common-lisp@v1
- name: Test
run: |
vend test
#+end_src
See its README for more information.
- Coverage
=vend= does not cover all of what's available on Quicklisp, but it does have significant enough coverage to resolve and compile a number of large, modern projects:
- Resolves: Does =vend get= complete?
- Compiles: Does =(asdf:load-system :foo)= within =vend repl= complete?
- Tests: Does =vend test= or =(asdf:test-system :foo)= within =vend repl= pass?
| Project | Resolves? | Compiles? | Tests? | Category | Notes | |------------+-----------+-----------+--------+----------------+-------------------------------------| | [[https://github.com/Shirakumo/alloy][Alloy]] | ✅ | ✅ | ✅ | UI | | | [[https://github.com/phantomics/april][April]] | ✅ | ✅ | - | Language | | | [[https://chiselapp.com/user/MistressRemilia/repository/benben/home][Benben]] | ❌ | - | - | Music | =vend= can't pull Fossil repos | | [[https://github.com/ciel-lang/CIEL][CIEL]] | ✅ | ✅* | - | Dev tool | CCL only | | [[https://github.com/rabbibotton/clog][Clog]] | ✅ | ✅ | - | GUI | | | [[https://github.com/coalton-lang/coalton][Coalton]] | ✅ | ✅ | ✅ | Language | | | [[https://github.com/bohonghuang/cl-gtk4][GTK4]] | ✅ | ✅ | - | GUI | | | [[https://github.com/Shirakumo/kandria][Kandria]] | ✅ | ✅ | - | Game | | | [[https://github.com/lem-project/lem][Lem]] | ✅ | ❌ | - | Editor | Usage of =package-inferred-system= | | [[https://github.com/Lisp-Stat/lisp-stat][Lisp-stat]] | ✅ | ✅ | - | Math | | | [[https://codeberg.org/McCLIM/McCLIM][McCLIM]] | ✅ | ✅ | ✅ | GUI | | | [[https://github.com/fukamachi/mito][Mito]] | ✅ | ✅ | ✅ | Database | | | [[https://codeberg.org/cage/nodgui][Nodgui]] | ✅ | ✅ | ✅ | GUI | | | [[https://github.com/atlas-engineer/nyxt][Nyxt]] | ✅ | ✅ | ✅ | Browser | | | [[https://github.com/ocicl/ocicl][OCICL]] | ✅ | ✅ | - | Dev tool | | | [[https://github.com/marijnh/Postmodern][Postmodern]] | ✅ | ✅ | ✅ | Database | | | [[https://github.com/fukamachi/qlot][Qlot]] | ❌ | - | - | Dev tool | Usage of =package-inferred-system= | | [[https://github.com/quicklisp/quicklisp-client][Quicklisp]] | ✅ | ❌ | - | Dev tool | System is unloadable: [[https://github.com/quicklisp/quicklisp-client/issues/125][(1)]] [[https://github.com/quicklisp/quicklisp-client/issues/140][(2)]] | | [[https://github.com/Shirakumo/radiance][Radiance]] | ✅ | ✅ | ✅ | Web | | | [[https://github.com/roswell/roswell][Roswell]] | ✅ | ❌ | - | Dev tool | Requires =quicklisp= internally | | [[https://github.com/stumpwm/stumpwm][StumpWM]] | ✅ | ✅ | ✅ | Window Manager | SBCL-only | | [[https://codeberg.org/cage/tinmop][Tinmop]] | ✅ | ✅ | - | Chat | | | [[https://github.com/Shirakumo/trial][Trial]] | ✅ | ✅ | ✅ | Gamedev | [[https://github.com/Shirakumo/trial-assets/][trial-assets]] manual setup for demos | | [[https://github.com/fukamachi/woo][Woo]] | ✅ | ✅ | ❌ | Web | Usage of =package-inferred-system= |
If during your usage of =vend= you discover a project that fails to resolve, please [[https://github.com/fosskers/vend/issues][open an Issue]].
- Compiler Compatibility
=vend repl= and =eval= work with the following compilers:
| Compiler | Status | Notes | |-----------+--------+-----------------------| | SBCL | ✅ | | | ECL | ✅ | | | ABCL | ✅ | | | CMUCL | ✅ | From 2025-07 Snapshot | | Clasp | ✅ | | | CCL | ✅ | | | [[https://gitlab.com/gnu-clisp/clisp][Clisp]] | ✅ | Doesn't support [[https://gitlab.com/gnu-clisp/clisp/-/merge_requests/3][PLN]] | |-----------+--------+-----------------------| | Allegro | ✅ | | | LispWorks | ❓ | |
[[https://wiki.archlinux.org/title/Common_Lisp#Historical][Historical implementations]] are not considered.
- FAQ
** How do I update dependencies?
The intent is that by vendoring, you're taking responsibility for the "true shape" of your program. So, upgrading dependencies should always be a conscious choice, done for a specific reason. Therefore there is no "bulk update" button.
To update a single dependency, you can =git pull= it specifically. If you've already committed that dependency to your repo (as in the application case), you're still able to:
#+begin_example rm -rf vendored/old-dep vend get rm -rf vendored/old-dep/.git/ #+end_example
But you're discouraged from doing this habitually.
** How can I build executables of my application?
See [[file:build.lisp][build.lisp]] for how =vend= is built, which uses ECL. For SBCL, consider adding:
#+begin_src lisp (sb-ext:save-lisp-and-die #p"foobar" :toplevel #'foobar:launch :executable t :compression t) #+end_src
Consider also =vend eval=. For projects that have sufficient build commands already set in their =.asd= files, this may be enough:
#+begin_example vend eval "(asdf:load-system :waytemp) (asdf:make :waytemp)" #+end_example
** How do I refer to local dependencies?
=vend get= fetches dependencies it knows about via =git=, but sometimes you want to refer to a dependency that already exists somewhere else on your local machine. To trick =vend=, you can either:
- Make a copy of the local project within =vendored/=.
- Create a symlink inside =vendored/= that refers to the local project.
Then, when running =vend get= it will see the folder you added and assume it had already fetched it via a previous call to =vend get=. Likewise, =vend repl= should "just work".
** Can I install new dependencies while =vend repl= is running?
Probably not. At least, =vend= assumes that Quicklisp doesn't exist, and it tells ASDF to only look for systems in the current directory tree. It's not clear what a call to =(ql:quickload ...)= would do in that case.
If you want new packages available to =vend repl=, you can:
- Manually clone them into =vendored/= (discouraged).
- Add them to your =.asd= explicitly and call =vend get=.
Either way, it's expected that you ensure that when a user freshly clones your repository, runs =vend get=, and then loads your system, everything should work.
** Does this use git submodules?
No. Submodules need to be recloned by your users, which is a weaker long-term guarantee than true vendoring. Submodules are also a pain in the neck to manage. With =vend=, if you want to change and make new commits to vendored dependencies, you're still free to do so.
** Why ECL?
To avoid dependencies, =vend= uses a number of [[https://gitlab.com/embeddable-common-lisp/ecl][ECL]]-specific extensions. However, you're free to use it to manage Lisp projects of any (modern) compiler. ECL typically produces very small binaries; in the case of =vend= it's only a few hundred kilobytes, which eases distribution.