about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsterni <sternenseemann@systemli.org>2021-08-09T00·47+0200
committersterni <sternenseemann@systemli.org>2021-08-24T22·00+0000
commit02566cdcfb15043070c990ec17c0405313a13874 (patch)
tree12bb84edcd1e01714f50a4a69662dbefc3c7444c
parent0285ea7eac8e214f6159c37ca1eff6ca61fc883b (diff)
feat(nix/buildLisp): add ecl r/2771
Adds ECL as a second supported implementation, specifically a statically
linked ECL. This is interesting because we can create statically linked
binaries, but has a few drawbacks which doesn't make it generally
useful:

* Loading things is very slow: The statically linked ECL only has byte
  compilation available, so when we do load things or use the REPL it is
  significantly worse than with e. g. SBCL.

* We can't load shared objects via the FFI since ECL's dffi is not
  available when linked statically. This means that as it stands, we
  can't build a statically linked //web/panettone for example.

Since ECL is quite slow anyways, I think these drawbacks are worth it
since the biggest reason for using ECL would be to get a statically
linked binary. If we change our minds, it shouldn't be too hard to
provide ecl-static and ecl-dynamic as separate implementations.

ECL is LGPL and some libraries it uses as part of its runtime are as
well. I've outlined in the ecl-static overlay why this should be of no
concern in the context of depot even though we are statically linking.

Currently everything is building except projects that are using cffi to
load shared libaries which have gotten an appropriate
`badImplementations` entry. To get the rest building the following
changes were made:

* Anywhere a dependency on UIOP is expressed as `bundled "uiop"` we now
  use `bundled "asdf"` for all implementations except SBCL. From my
  testing, SBCL seems to be the only implementation to support using
  `(require 'uiop)` to only load the UIOP package. Where both a
  dependency on ASDF and UIOP exists, we just delete the UIOP one.
  `(require 'asdf)` always causes UIOP to be available.

* Where appropriate only conditionally compile SBCL-specific code and
  if any build the corresponding files for ECL.

* //lisp/klatre: Use the standard condition parse-error for all
  implementations except SBCL in try-parse-integer.

* //3p/lisp/ironclad: disable SBCL assembly optimization hack for all
  other platforms as it may interfere with compilation.

* //3p/lisp/trivial-mimes: prevent call to asdf function by substituting
  it out of the source since it always errors out in ECL and we hardcode
  the correct path elsewhere anyways.

As it stands ECL still suffers from a very weird problem which happens
when compiling postmodern and moptilities:
https://gitlab.com/embeddable-common-lisp/ecl/-/issues/651

Change-Id: I0285924f92ac154126b4c42145073c3fb33702ed
Reviewed-on: https://cl.tvl.fyi/c/depot/+/3297
Tested-by: BuildkiteCI
Reviewed-by: tazjin <mail@tazj.in>
Reviewed-by: eta <tvl@eta.st>
-rw-r--r--fun/gemma/default.nix5
-rw-r--r--fun/🕰️/default.nix9
-rw-r--r--lisp/dns/default.nix4
-rw-r--r--lisp/klatre/klatre.lisp3
-rw-r--r--nix/buildLisp/default.nix161
-rw-r--r--third_party/lisp/bordeaux-threads.nix19
-rw-r--r--third_party/lisp/cffi.nix9
-rw-r--r--third_party/lisp/cl-fad.nix4
-rw-r--r--third_party/lisp/cl-plus-ssl.nix4
-rw-r--r--third_party/lisp/cl-smtp.nix4
-rw-r--r--third_party/lisp/cl-unicode.nix5
-rw-r--r--third_party/lisp/closer-mop.nix5
-rw-r--r--third_party/lisp/drakma.nix4
-rw-r--r--third_party/lisp/easy-routes.nix3
-rw-r--r--third_party/lisp/hunchentoot.nix4
-rw-r--r--third_party/lisp/ironclad.nix37
-rw-r--r--third_party/lisp/lisp-binary.nix4
-rw-r--r--third_party/lisp/md5.nix7
-rw-r--r--third_party/lisp/moptilities.nix4
-rw-r--r--third_party/lisp/nibbles.nix9
-rw-r--r--third_party/lisp/postmodern.nix4
-rw-r--r--third_party/lisp/restas.nix3
-rw-r--r--third_party/lisp/trivial-features.nix5
-rw-r--r--third_party/lisp/trivial-ldap.nix4
-rw-r--r--third_party/lisp/trivial-mimes.nix10
-rw-r--r--third_party/lisp/uax-15.nix1
-rw-r--r--third_party/lisp/usocket.nix8
-rw-r--r--third_party/nixpkgs/default.nix1
-rw-r--r--third_party/overlays/ecl-static.nix41
-rw-r--r--users/sterni/clhs-lookup/default.nix5
-rw-r--r--web/panettone/default.nix4
31 files changed, 346 insertions, 44 deletions
diff --git a/fun/gemma/default.nix b/fun/gemma/default.nix
index 902f5857db..20acace2d0 100644
--- a/fun/gemma/default.nix
+++ b/fun/gemma/default.nix
@@ -47,4 +47,9 @@ in depot.nix.buildLisp.program {
     ./src/gemma.lisp
     injectFrontend
   ];
+
+  # depends on SBCL
+  brokenOn = [
+    "ecl"
+  ];
 }
diff --git a/fun/🕰️/default.nix b/fun/🕰️/default.nix
index d6fd5fc35e..230d9f02f1 100644
--- a/fun/🕰️/default.nix
+++ b/fun/🕰️/default.nix
@@ -21,7 +21,10 @@ let
     deps = [
       depot.third_party.lisp.unix-opts
       depot.lisp.klatre
-      (buildLisp.bundled "uiop")
+      {
+        default = buildLisp.bundled "asdf";
+        sbcl = buildLisp.bundled "uiop";
+      }
       lib
     ];
 
@@ -30,6 +33,10 @@ let
     ];
 
     main = "🕰️.bin:🚂";
+
+    brokenOn = [
+      "ecl" # refuses to create non-ASCII paths even on POSIX…
+    ];
   };
 in bin // {
   inherit lib;
diff --git a/lisp/dns/default.nix b/lisp/dns/default.nix
index 43e7ea5030..cb2445b460 100644
--- a/lisp/dns/default.nix
+++ b/lisp/dns/default.nix
@@ -14,4 +14,8 @@ depot.nix.buildLisp.library {
     ./message.lisp
     ./client.lisp
   ];
+
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/lisp/klatre/klatre.lisp b/lisp/klatre/klatre.lisp
index 0b986ac251..79c7259752 100644
--- a/lisp/klatre/klatre.lisp
+++ b/lisp/klatre/klatre.lisp
@@ -105,7 +105,8 @@ separated by SEP."
   "Attempt to parse STR as an integer, returning nil if it is invalid."
   (check-type str string)
   (handler-case (parse-integer str)
-    (sb-int:simple-parse-error (_) (declare (ignore _)) nil)))
+    (#+sbcl sb-int:simple-parse-error
+     #-sbcl parse-error (_) (declare (ignore _)) nil)))
 
 ;;;
 ;;; Function utilities
diff --git a/nix/buildLisp/default.nix b/nix/buildLisp/default.nix
index c214a542de..ec42cc66f3 100644
--- a/nix/buildLisp/default.nix
+++ b/nix/buildLisp/default.nix
@@ -8,7 +8,7 @@
 
 let
   inherit (builtins) map elemAt match filter;
-  inherit (pkgs) lib runCommandNoCC makeWrapper writeText writeShellScriptBin sbcl;
+  inherit (pkgs) lib runCommandNoCC makeWrapper writeText writeShellScriptBin sbcl ecl-static;
 
   #
   # Internal helper definitions
@@ -16,6 +16,19 @@ let
 
   defaultImplementation = "sbcl";
 
+  # Many Common Lisp implementations (like ECL and CCL) will occasionally drop
+  # you into an interactive debugger even when executing something as a script.
+  # In nix builds we don't want such a situation: Any error should make the
+  # script exit non-zero. Luckily the ANSI standard specifies *debugger-hook*
+  # which is invoked before the debugger letting us just do that.
+  disableDebugger = writeText "disable-debugger.lisp" ''
+    (setf *debugger-hook*
+          (lambda (error hook)
+            (declare (ignore hook))
+            (format *error-output* "~%Unhandled error: ~a~%" error)
+            #+ecl (ext:quit 1)))
+  '';
+
   # Process a list of arbitrary values which also contains “implementation
   # filter sets” which describe conditonal inclusion of elements depending
   # on the CL implementation used. Elements are processed in the following
@@ -199,6 +212,9 @@ let
   #   executable which runs 'main' (and exits) where 'main' is available from
   #   'deps'. The executable should be created as "$out/bin/${name}", usually
   #   by dumping the lisp image with the replaced toplevel function replaced.
+  # - wrapProgram :: boolean
+  #   Whether to wrap the resulting binary / image with a wrapper script setting
+  #   `LD_LIBRARY_PATH`.
   # - genTestLisp :: { name, srcs, deps, expression } -> file
   #   Builds a lisp file which loads the given 'deps' and 'srcs' files and
   #   then evaluates 'expression'. Depending on whether 'expression' returns
@@ -291,6 +307,8 @@ let
                              :purify t))
       '';
 
+      wrapProgram = true;
+
       genTestLisp = genTestLispGeneric impls.sbcl;
 
       lispWith = deps:
@@ -304,6 +322,141 @@ let
           } $@
         '';
     };
+    ecl = {
+      runScript = "${ecl-static}/bin/ecl --load ${disableDebugger} --shell";
+      faslExt = "fasc";
+      genLoadLisp = genLoadLispGeneric impls.ecl;
+      genCompileLisp = { name, srcs, deps }: writeText "ecl-compile.lisp" ''
+        ;; This seems to be required to bring make the 'c' package available
+        ;; early, otherwise ECL tends to fail with a read failure…
+        (ext:install-c-compiler)
+
+        ;; Load dependencies
+        ${impls.ecl.genLoadLisp deps}
+
+        (defun getenv-or-fail (var)
+          (or (ext:getenv var)
+              (error (format nil "Missing expected environment variable ~A" var))))
+
+        (defun nix-compile-file (srcfile &key native)
+          "Compile the given srcfile into a compilation unit in :out-dir using
+          a unique name based on srcfile as the filename which is returned after
+          compilation. If :native is true, create an native object file,
+          otherwise a byte-compile fasc file is built and immediately loaded."
+
+          (let* ((unique-name (substitute #\_ #\/ srcfile))
+                 (out-file (make-pathname :type (if native "o" "fasc")
+                                          :directory (getenv-or-fail "NIX_BUILD_TOP")
+                                          :name unique-name)))
+            (multiple-value-bind (out-truename _warnings-p failure-p)
+                (compile-file srcfile :system-p native
+                                      :load (not native)
+                                      :output-file out-file
+                                      :verbose t :print t)
+              (if failure-p (ext:quit 1) out-truename))))
+
+        (let* ((out-dir (getenv-or-fail "out"))
+               (nix-build-dir (getenv-or-fail "NIX_BUILD_TOP"))
+               (srcs
+                ;; These forms are inserted by the Nix build
+                '(${lib.concatMapStringsSep "\n" (src: "\"${src}\"") srcs})))
+
+          ;; First, we'll byte compile loadable FASL files and load them
+          ;; immediately. Since we are using a statically linked ECL, there's
+          ;; no way to load native objects, so we rely on byte compilation
+          ;; for all our loading — which is crucial in compilation of course.
+          (ext:install-bytecodes-compiler)
+
+          ;; ECL's bytecode FASLs can just be concatenated to create a bundle
+          ;; at least since a recent bugfix which we apply as a patch.
+          ;; See also: https://gitlab.com/embeddable-common-lisp/ecl/-/issues/649
+          (let ((bundle-out (make-pathname :type "fasc" :name "${name}"
+                                           :directory out-dir)))
+
+            (with-open-file (fasc-stream bundle-out :direction :output)
+              (ext:run-program "cat"
+                               (mapcar (lambda (f)
+                                         (namestring
+                                          (nix-compile-file f :native nil)))
+                                       srcs)
+                               :output fasc-stream)))
+
+          (ext:install-c-compiler)
+
+          ;; Build a (natively compiled) static archive (.a) file. We want to
+          ;; use this for (statically) linking an executable later. The bytecode
+          ;; dance is only required because we can't load such archives.
+          (c:build-static-library
+           (make-pathname :type "a" :name "${name}" :directory out-dir)
+           :lisp-files (mapcar (lambda (x)
+                                 (nix-compile-file x :native t))
+                               srcs)))
+      '';
+      genDumpLisp = { name, main, deps }: writeText "ecl-dump.lisp" ''
+        (defun getenv-or-fail (var)
+          (or (ext:getenv var)
+              (error (format nil "Missing expected environment variable ~A" var))))
+
+        ${impls.ecl.genLoadLisp deps}
+
+        ;; makes a 'c' package available that can link executables
+        (ext:install-c-compiler)
+
+        (c:build-program
+         (make-pathname :name "${name}"
+                        :directory (concatenate 'string
+                                                (getenv-or-fail "out")
+                                                "/bin"))
+         :epilogue-code `(progn
+                          ;; UIOP doesn't understand ECL, so we need to make it
+                          ;; aware that we are a proper executable, causing it
+                          ;; to handle argument parsing and such properly. Since
+                          ;; this needs to work even when we're not using UIOP,
+                          ;; we need to do some compile-time acrobatics.
+                          ,(when (find-package 'uiop)
+                            `(setf ,(find-symbol "*IMAGE-DUMPED-P*" :uiop) :executable))
+                          ;; Run the actual application…
+                          (${main})
+                          ;; … and exit.
+                          (ext:quit))
+         ;; ECL can't remember these from its own build…
+         :ld-flags '("-static")
+         :lisp-files
+         ;; The following forms are inserted by the Nix build
+         '(${
+             lib.concatMapStrings (dep: ''
+               "${dep}/${dep.lispName}.a"
+             '') (allDeps impls.ecl deps)
+           }))
+      '';
+
+      wrapProgram = false;
+
+      genTestLisp = genTestLispGeneric impls.ecl;
+
+      lispWith = deps:
+        let lispDeps = filter (d: !d.lispBinary) (allDeps impls.ecl deps);
+        in writeShellScriptBin "ecl" ''
+          exec ${ecl-static}/bin/ecl ${
+            lib.optionalString (deps != [])
+              "--load ${writeText "load.lisp" (impls.ecl.genLoadLisp lispDeps)}"
+          } $@
+        '';
+
+      bundled = name: runCommandNoCC "${name}-cllib" {
+        passthru = {
+          lispName = name;
+          lispNativeDeps = [];
+          lispDeps = [];
+          lispBinary = false;
+          repl = impls.ecl.lispWith [ (impls.ecl.bundled name) ];
+        };
+      } ''
+        mkdir -p "$out"
+        ln -s "${ecl-static}/lib/ecl-${ecl-static.version}/${name}.${impls.ecl.faslExt}" -t "$out"
+        ln -s "${ecl-static}/lib/ecl-${ecl-static.version}/lib${name}.a" "$out/${name}.a"
+      '';
+    };
   };
 
   #
@@ -412,7 +565,7 @@ let
         lispBinary = true;
         tests = testDrv;
       };
-    } ''
+    } (''
       ${if ! isNull testDrv
         then "echo 'Test ${testDrv} succeeded'"
         else ""}
@@ -424,9 +577,9 @@ let
           deps = ([ selfLib ] ++ lispDeps);
         }
       }
-
+    '' + lib.optionalString impl.wrapProgram ''
       wrapProgram $out/bin/${name} --prefix LD_LIBRARY_PATH : "${libPath}"
-    '');
+    ''));
 
   # 'bundled' creates a "library" which makes a built-in package available,
   # such as any of SBCL's sb-* packages or ASDF. By default this is done
diff --git a/third_party/lisp/bordeaux-threads.nix b/third_party/lisp/bordeaux-threads.nix
index b2596672ba..92bc1f2629 100644
--- a/third_party/lisp/bordeaux-threads.nix
+++ b/third_party/lisp/bordeaux-threads.nix
@@ -2,18 +2,25 @@
 # in Common Lisp simple.
 { depot, ... }:
 
-let src = builtins.fetchGit {
-  url = "https://github.com/sionescu/bordeaux-threads.git";
-  rev = "499b6d3f0ce635417d6096acf0a671d8bf3f6e5f";
-};
+let
+  src = builtins.fetchGit {
+    url = "https://github.com/sionescu/bordeaux-threads.git";
+    rev = "499b6d3f0ce635417d6096acf0a671d8bf3f6e5f";
+  };
+  getSrc = f: "${src}/src/${f}";
 in depot.nix.buildLisp.library {
   name = "bordeaux-threads";
   deps = [ depot.third_party.lisp.alexandria ];
 
-  srcs = map (f: src + ("/src/" + f)) [
+  srcs = map getSrc [
     "pkgdcl.lisp"
     "bordeaux-threads.lisp"
-    "impl-sbcl.lisp"
+  ] ++ [
+    {
+      sbcl = getSrc "impl-sbcl.lisp";
+      ecl = getSrc "impl-ecl.lisp";
+    }
+  ] ++ map getSrc [
     "default-implementations.lisp"
   ];
 }
diff --git a/third_party/lisp/cffi.nix b/third_party/lisp/cffi.nix
index 9a50e57e05..89fe9fcad4 100644
--- a/third_party/lisp/cffi.nix
+++ b/third_party/lisp/cffi.nix
@@ -13,11 +13,14 @@ in buildLisp.library {
     babel
     trivial-features
     (buildLisp.bundled "asdf")
-    (buildLisp.bundled "uiop")
   ];
 
-  srcs = map (f: src + ("/src/" + f)) [
-    "cffi-sbcl.lisp"
+  srcs = [
+    {
+      ecl = src + "/src/cffi-ecl.lisp";
+      sbcl = src + "/src/cffi-sbcl.lisp";
+    }
+  ] ++ map (f: src + ("/src/" + f)) [
     "package.lisp"
     "utils.lisp"
     "libraries.lisp"
diff --git a/third_party/lisp/cl-fad.nix b/third_party/lisp/cl-fad.nix
index 54f6328d53..2249db66ac 100644
--- a/third_party/lisp/cl-fad.nix
+++ b/third_party/lisp/cl-fad.nix
@@ -15,7 +15,9 @@ in buildLisp.library {
   deps = with depot.third_party.lisp; [
     alexandria
     bordeaux-threads
-    (buildLisp.bundled "sb-posix")
+    {
+      sbcl = buildLisp.bundled "sb-posix";
+    }
   ];
 
   srcs = map (f: src + ("/" + f)) [
diff --git a/third_party/lisp/cl-plus-ssl.nix b/third_party/lisp/cl-plus-ssl.nix
index e4f3fd95e4..1dab7c3abb 100644
--- a/third_party/lisp/cl-plus-ssl.nix
+++ b/third_party/lisp/cl-plus-ssl.nix
@@ -37,4 +37,8 @@ in buildLisp.library {
     "context.lisp"
     "verify-hostname.lisp"
   ];
+
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/cl-smtp.nix b/third_party/lisp/cl-smtp.nix
index 6b6b415a03..a9905b5ef6 100644
--- a/third_party/lisp/cl-smtp.nix
+++ b/third_party/lisp/cl-smtp.nix
@@ -25,4 +25,8 @@ in depot.nix.buildLisp.library {
     "cl-smtp.lisp"
     "mime-types.lisp"
   ];
+
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/cl-unicode.nix b/third_party/lisp/cl-unicode.nix
index 8b42e2eaec..5fff1fbe6b 100644
--- a/third_party/lisp/cl-unicode.nix
+++ b/third_party/lisp/cl-unicode.nix
@@ -29,7 +29,10 @@ let
     deps = with depot.third_party.lisp; [
       cl-unicode-base
       flexi-streams
-      (bundled "uiop")
+      {
+        ecl = bundled "asdf";
+        default = bundled "uiop";
+      }
     ];
 
     srcs = (map (f: src + ("/build/" + f)) [
diff --git a/third_party/lisp/closer-mop.nix b/third_party/lisp/closer-mop.nix
index 9e6aac36c1..d6a677625e 100644
--- a/third_party/lisp/closer-mop.nix
+++ b/third_party/lisp/closer-mop.nix
@@ -15,6 +15,9 @@ in depot.nix.buildLisp.library {
   srcs = [
     "${src}/closer-mop-packages.lisp"
     "${src}/closer-mop-shared.lisp"
-    "${src}/closer-sbcl.lisp"
+    {
+      sbcl = "${src}/closer-sbcl.lisp";
+      ecl = "${src}/closer-ecl.lisp";
+    }
   ];
 }
diff --git a/third_party/lisp/drakma.nix b/third_party/lisp/drakma.nix
index 80c82aee1f..3757aad7b1 100644
--- a/third_party/lisp/drakma.nix
+++ b/third_party/lisp/drakma.nix
@@ -32,4 +32,8 @@ in depot.nix.buildLisp.library {
     "encoding.lisp"
     "request.lisp"
   ];
+
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/easy-routes.nix b/third_party/lisp/easy-routes.nix
index 63eb8b5e38..93aed8a667 100644
--- a/third_party/lisp/easy-routes.nix
+++ b/third_party/lisp/easy-routes.nix
@@ -23,4 +23,7 @@ in depot.nix.buildLisp.library {
     "routes-map-printer.lisp"
   ];
 
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/hunchentoot.nix b/third_party/lisp/hunchentoot.nix
index 3006f5fd72..24eae6a348 100644
--- a/third_party/lisp/hunchentoot.nix
+++ b/third_party/lisp/hunchentoot.nix
@@ -58,4 +58,8 @@ in depot.nix.buildLisp.library {
     "acceptor.lisp"
     "easy-handlers.lisp"
   ];
+
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/ironclad.nix b/third_party/lisp/ironclad.nix
index fa860d4d0a..fe0e052c32 100644
--- a/third_party/lisp/ironclad.nix
+++ b/third_party/lisp/ironclad.nix
@@ -10,33 +10,40 @@ let
     sha256 = "0k4bib9mbrzalbl9ivkw4a7g4c7bbad1l5jw4pzkifqszy2swkr5";
   };
 
+  getSrc = f: "${src}/src/${f}";
+
 in depot.nix.buildLisp.library {
   name = "ironclad";
 
   deps = with depot.third_party.lisp; [
     (bundled "asdf")
-    (bundled "sb-rotate-byte")
-    (bundled "sb-posix")
+    { sbcl = bundled "sb-rotate-byte"; }
+    { sbcl = bundled "sb-posix"; }
     alexandria
     bordeaux-threads
     nibbles
   ];
 
   srcs = [
-    "${src}/ironclad.asd"
-    # TODO(grfn): Figure out how to get this compiling with the assembly
-    # optimization eventually - see https://cl.tvl.fyi/c/depot/+/1333
-    (runCommand "package.lisp" {} ''
-      substitute ${src}/src/package.lisp $out \
-        --replace \#-ecl-bytecmp "" \
-        --replace '(pushnew :ironclad-assembly *features*)' ""
-    '')
-  ] ++ (map (f: src + ("/src/" + f)) [
+    {
+      # TODO(grfn): Figure out how to get this compiling with the assembly
+      # optimization eventually - see https://cl.tvl.fyi/c/depot/+/1333
+      sbcl = runCommand "package.lisp" {} ''
+        substitute ${src}/src/package.lisp $out \
+          --replace \#-ecl-bytecmp "" \
+          --replace '(pushnew :ironclad-assembly *features*)' ""
+      '';
+      default = getSrc "package.lisp";
+    }
+  ] ++ map getSrc [
     "macro-utils.lisp"
+  ] ++ [
+    { sbcl = getSrc "opt/sbcl/fndb.lisp"; }
+    { sbcl = getSrc "opt/sbcl/cpu-features.lisp"; }
+    { sbcl = getSrc "opt/sbcl/x86oid-vm.lisp"; }
 
-    "opt/sbcl/fndb.lisp"
-    "opt/sbcl/cpu-features.lisp"
-    "opt/sbcl/x86oid-vm.lisp"
+    { ecl = getSrc "opt/ecl/c-functions.lisp"; }
+  ] ++ map getSrc [
 
     "common.lisp"
     "conditions.lisp"
@@ -142,5 +149,5 @@ in depot.nix.buildLisp.library {
     "public-key/elgamal.lisp"
     "public-key/pkcs1.lisp"
     "public-key/rsa.lisp"
-  ]);
+  ];
 }
diff --git a/third_party/lisp/lisp-binary.nix b/third_party/lisp/lisp-binary.nix
index e6111c20a7..3e7a43b8ac 100644
--- a/third_party/lisp/lisp-binary.nix
+++ b/third_party/lisp/lisp-binary.nix
@@ -28,4 +28,8 @@ in depot.nix.buildLisp.library {
     "binary-2.lisp"
     "types.lisp"
   ];
+
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/md5.nix b/third_party/lisp/md5.nix
index a789f7bc2a..ef265d5b6e 100644
--- a/third_party/lisp/md5.nix
+++ b/third_party/lisp/md5.nix
@@ -11,6 +11,11 @@ let src = pkgs.fetchFromGitHub {
 };
 in buildLisp.library {
   name = "md5";
-  deps = [ (buildLisp.bundled "sb-rotate-byte") ];
+  deps = [
+    {
+      sbcl = buildLisp.bundled "sb-rotate-byte";
+      default = depot.third_party.lisp.flexi-streams;
+    }
+  ];
   srcs = [ (src + "/md5.lisp") ];
 }
diff --git a/third_party/lisp/moptilities.nix b/third_party/lisp/moptilities.nix
index 89cfb9a938..a8a387ab91 100644
--- a/third_party/lisp/moptilities.nix
+++ b/third_party/lisp/moptilities.nix
@@ -11,4 +11,8 @@ in depot.nix.buildLisp.library {
   name = "moptilities";
   deps = [ depot.third_party.lisp.closer-mop ];
   srcs = [ "${src}/dev/moptilities.lisp" ];
+
+  brokenOn = [
+    "ecl" # TODO(sterni): https://gitlab.com/embeddable-common-lisp/ecl/-/issues/651
+  ];
 }
diff --git a/third_party/lisp/nibbles.nix b/third_party/lisp/nibbles.nix
index ec4e6e6b10..b797c83a5f 100644
--- a/third_party/lisp/nibbles.nix
+++ b/third_party/lisp/nibbles.nix
@@ -24,9 +24,10 @@ in depot.nix.buildLisp.library {
     "types.lisp"
     "vectors.lisp"
     "streams.lisp"
-    "sbcl-opt/fndb.lisp"
-    "sbcl-opt/nib-tran.lisp"
-    "sbcl-opt/x86-vm.lisp"
-    "sbcl-opt/x86-64-vm.lisp"
+  ] ++ [
+    { sbcl = "${src}/sbcl-opt/fndb.lisp"; }
+    { sbcl = "${src}/sbcl-opt/nib-tran.lisp"; }
+    { sbcl = "${src}/sbcl-opt/x86-vm.lisp"; }
+    { sbcl = "${src}/sbcl-opt/x86-64-vm.lisp"; }
   ];
 }
diff --git a/third_party/lisp/postmodern.nix b/third_party/lisp/postmodern.nix
index 10a0da3892..333a9d9b77 100644
--- a/third_party/lisp/postmodern.nix
+++ b/third_party/lisp/postmodern.nix
@@ -83,6 +83,10 @@ let
       "table.lisp"
       "deftable.lisp"
     ]);
+
+    brokenOn = [
+      "ecl" # TODO(sterni): https://gitlab.com/embeddable-common-lisp/ecl/-/issues/651
+    ];
   };
 
 in postmodern // {
diff --git a/third_party/lisp/restas.nix b/third_party/lisp/restas.nix
index 8a0b5f907f..cf231286e7 100644
--- a/third_party/lisp/restas.nix
+++ b/third_party/lisp/restas.nix
@@ -35,4 +35,7 @@ in depot.nix.buildLisp.library {
     "policy.lisp"
   ];
 
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/trivial-features.nix b/third_party/lisp/trivial-features.nix
index 647ae9a3b0..3ad424b8ab 100644
--- a/third_party/lisp/trivial-features.nix
+++ b/third_party/lisp/trivial-features.nix
@@ -7,6 +7,9 @@ let src = builtins.fetchGit {
 in depot.nix.buildLisp.library {
   name = "trivial-features";
   srcs = [
-    (src + "/src/tf-sbcl.lisp")
+    {
+      sbcl = src + "/src/tf-sbcl.lisp";
+      ecl = src + "/src/tf-ecl.lisp";
+    }
   ];
 }
diff --git a/third_party/lisp/trivial-ldap.nix b/third_party/lisp/trivial-ldap.nix
index ec111bc682..c8a27431c6 100644
--- a/third_party/lisp/trivial-ldap.nix
+++ b/third_party/lisp/trivial-ldap.nix
@@ -19,4 +19,8 @@ in depot.nix.buildLisp.library {
     "package.lisp"
     "trivial-ldap.lisp"
   ];
+
+  brokenOn = [
+    "ecl" # dynamic cffi
+  ];
 }
diff --git a/third_party/lisp/trivial-mimes.nix b/third_party/lisp/trivial-mimes.nix
index c4b21045c5..ce45993d05 100644
--- a/third_party/lisp/trivial-mimes.nix
+++ b/third_party/lisp/trivial-mimes.nix
@@ -10,14 +10,20 @@ let
 
   mime-types = pkgs.runCommand "mime-types.lisp" {} ''
     substitute ${src}/mime-types.lisp $out \
-      --replace /etc/mime.types ${src}/mime.types
+      --replace /etc/mime.types ${src}/mime.types \
+      --replace "(asdf:system-source-directory :trivial-mimes)" '"/bogus-dir"'
+      # We want to prevent an ASDF lookup at build time since this will
+      # generally fail — we are not using ASDF after all.
   '';
 
 in depot.nix.buildLisp.library {
   name = "trivial-mimes";
 
   deps = [
-    (depot.nix.buildLisp.bundled "uiop")
+    {
+      sbcl = depot.nix.buildLisp.bundled "uiop";
+      default = depot.nix.buildLisp.bundled "asdf";
+    }
   ];
 
   srcs = [ mime-types ];
diff --git a/third_party/lisp/uax-15.nix b/third_party/lisp/uax-15.nix
index 8d420d26f6..a13e5c1690 100644
--- a/third_party/lisp/uax-15.nix
+++ b/third_party/lisp/uax-15.nix
@@ -19,7 +19,6 @@ in depot.nix.buildLisp.library {
   deps = with depot.third_party.lisp; [
     split-sequence
     cl-ppcre
-    (bundled "uiop")
     (bundled "asdf")
   ];
 
diff --git a/third_party/lisp/usocket.nix b/third_party/lisp/usocket.nix
index 888d5e01a0..dbbfd2fbf1 100644
--- a/third_party/lisp/usocket.nix
+++ b/third_party/lisp/usocket.nix
@@ -33,6 +33,12 @@ in buildLisp.library {
     "package.lisp"
     "usocket.lisp"
     "condition.lisp"
-    "backend/sbcl.lisp"
+  ] ++ [
+    { sbcl = "${src}/backend/sbcl.lisp"; }
+
+    # ECL actually has two files, it supports the SBCL backend,
+    # but usocket also has some ECL specific code
+    { ecl = "${src}/backend/sbcl.lisp"; }
+    { ecl = "${src}/backend/ecl.lisp"; }
   ]);
 }
diff --git a/third_party/nixpkgs/default.nix b/third_party/nixpkgs/default.nix
index d04bfeea15..c19809fc5a 100644
--- a/third_party/nixpkgs/default.nix
+++ b/third_party/nixpkgs/default.nix
@@ -66,5 +66,6 @@ in import nixpkgsSrc {
     depot.third_party.overlays.tvl
     depot.third_party.overlays.haskell
     depot.third_party.overlays.emacs
+    depot.third_party.overlays.ecl-static
   ];
 }
diff --git a/third_party/overlays/ecl-static.nix b/third_party/overlays/ecl-static.nix
new file mode 100644
index 0000000000..beda6641a0
--- /dev/null
+++ b/third_party/overlays/ecl-static.nix
@@ -0,0 +1,41 @@
+{ ... }:
+
+self: super:
+
+{
+  # Statically linked ECL with statically linked dependencies.
+  # Works quite well, but solving this properly in a nixpkgs
+  # context will require figuring out cross compilation (for
+  # pkgsStatic), so we're gonna use this override for now.
+  #
+  # Note that ecl-static does mean that we have things
+  # statically linked against GMP and ECL which are LGPL.
+  # I believe this should be alright: The way ppl are gonna
+  # interact with the distributed binaries (i. e. the binary
+  # cache) is Nix in the depot monorepo, so the separability
+  # requirement should be satisfied: Source code or overriding
+  # would be available as ways to swap out the used GMP in the
+  # program.
+  # See https://www.gnu.org/licenses/gpl-faq.en.html#LGPLStaticVsDynamic
+  ecl-static = (super.pkgsMusl.ecl.override {
+    inherit (self.pkgsStatic) gmp libffi boehmgc;
+  }).overrideAttrs (drv: {
+    # Patches that make .fasc files concatenable again
+    patches = drv.patches ++ [
+      (self.fetchpatch {
+        name = "make-bytecode-fasl-concatenatable-1.patch";
+        url = "https://gitlab.com/embeddable-common-lisp/ecl/-/commit/fbb75a0fc524e3280d89d8abf3be2ee9924955c8.patch";
+        sha256 = "0k6cx1bh835rl0j0wbbi5nj0aa2rwbyfyz5q2jw643iqc62l16kv";
+      })
+      (self.fetchpatch {
+        name = "make-bytecode-fasl-concatenatable-2.patch";
+        url = "https://gitlab.com/embeddable-common-lisp/ecl/-/commit/a8b1c0da43f89800d09c23a27832d0b4c9dcc1e8.patch";
+        sha256 = "18hl79lss0dxglpa34hszqb6ajvs8rs4b4g1qlrqrvgh1gs667n0";
+      })
+    ];
+    configureFlags = drv.configureFlags ++ [
+      "--disable-shared"
+      "--with-dffi=no" # will fail at runtime anyways if statically linked
+    ];
+  });
+}
diff --git a/users/sterni/clhs-lookup/default.nix b/users/sterni/clhs-lookup/default.nix
index 951b94d72f..b6a0bd0679 100644
--- a/users/sterni/clhs-lookup/default.nix
+++ b/users/sterni/clhs-lookup/default.nix
@@ -23,7 +23,10 @@ let
     name = "clhs-lookup";
 
     deps = [
-      (buildLisp.bundled "uiop")
+      {
+        default = buildLisp.bundled "asdf";
+        sbcl = buildLisp.bundled "uiop";
+      }
     ];
 
     srcs = [
diff --git a/web/panettone/default.nix b/web/panettone/default.nix
index 095ccaac6e..8dd4543af0 100644
--- a/web/panettone/default.nix
+++ b/web/panettone/default.nix
@@ -47,4 +47,8 @@ depot.nix.buildLisp.program {
 
     expression = "(fiveam:run!)";
   };
+
+  brokenOn = [
+    "ecl" # dependencies use dynamic cffi
+  ];
 }