1{ lib, stdenv, callPackage, ecl, coreutils, fetchurl, strace, texinfo, which, writeText, zstd
2, version
3 # Set this to a lisp binary to use a custom bootstrap lisp compiler for SBCL.
4 # Leave as null to use the default. This is useful for local development of
5 # SBCL, because you can use your existing stock SBCL as a boostrap. On Hydra
6 # of course we can’t do that because SBCL hasn’t been built yet, so we use
7 # ECL but that’s much slower.
8, bootstrapLisp ? null
9}:
10
11let
12 versionMap = {
13 "2.4.6" = {
14 sha256 = "sha256-pImQeELa4JoXJtYphb96VmcKrqLz7KH7cCO8pnw/MJE=";
15 };
16 "2.4.7" = {
17 sha256 = "sha256-aFRNJQNjWs0BXVNMzJsq6faJltQptakGP9Iv8JJQEdI=";
18 };
19 };
20 # Collection of pre-built SBCL binaries for platforms that need them for
21 # bootstrapping. Ideally these are to be avoided. If ECL (or any other
22 # non-binary-distributed Lisp) can run on any of these systems, that entry
23 # should be removed from this list.
24 bootstrapBinaries = rec {
25 i686-linux = {
26 version = "1.2.7";
27 system = "x86-linux";
28 sha256 = "07f3bz4br280qvn85i088vpzj9wcz8wmwrf665ypqx181pz2ai3j";
29 };
30 armv7l-linux = {
31 version = "1.2.14";
32 system = "armhf-linux";
33 sha256 = "0sp5445rbvms6qvzhld0kwwvydw51vq5iaf4kdqsf2d9jvaz3yx5";
34 };
35 armv6l-linux = armv7l-linux;
36 x86_64-freebsd = {
37 version = "1.2.7";
38 system = "x86-64-freebsd";
39 sha256 = "14k42xiqd2rrim4pd5k5pjcrpkac09qnpynha8j1v4jngrvmw7y6";
40 };
41 x86_64-solaris = {
42 version = "1.2.7";
43 system = "x86-64-solaris";
44 sha256 = "05c12fmac4ha72k1ckl6i780rckd7jh4g5s5hiic7fjxnf1kx8d0";
45 };
46 };
47 sbclBootstrap = callPackage ./bootstrap.nix {
48 cfg = bootstrapBinaries.${stdenv.hostPlatform.system};
49 };
50 bootstrapLisp' =
51 if bootstrapLisp != null
52 then bootstrapLisp
53 else if (builtins.hasAttr stdenv.hostPlatform.system bootstrapBinaries)
54 then "${sbclBootstrap}/bin/sbcl --disable-debugger --no-userinit --no-sysinit"
55 else "${lib.getExe ecl} --norc";
56
57in
58
59stdenv.mkDerivation (self: {
60 pname = "sbcl";
61 inherit version;
62
63 src = fetchurl {
64 # Changing the version shouldn’t change the source for the
65 # derivation. Override the src entirely if desired.
66 url = "mirror://sourceforge/project/sbcl/sbcl/${version}/sbcl-${version}-source.tar.bz2";
67 inherit (versionMap.${version}) sha256;
68 };
69
70 nativeBuildInputs = [
71 texinfo
72 ] ++ lib.optionals self.doCheck (
73 [
74 which
75 ] ++ lib.optionals (builtins.elem stdenv.system strace.meta.platforms) [
76 strace
77 ]
78 );
79 buildInputs = lib.optionals self.coreCompression (
80 # Declare at the point of actual use in case the caller wants to override
81 # buildInputs to sidestep this.
82 assert lib.assertMsg (!self.purgeNixReferences) ''
83 Cannot enable coreCompression when purging Nix references, because compression requires linking in zstd
84 '';
85 [ zstd ]
86 );
87
88 threadSupport = (stdenv.hostPlatform.isx86 || "aarch64-linux" == stdenv.hostPlatform.system || "aarch64-darwin" == stdenv.hostPlatform.system);
89 # Meant for sbcl used for creating binaries portable to non-NixOS via save-lisp-and-die.
90 # Note that the created binaries still need `patchelf --set-interpreter ...`
91 # to get rid of ${glibc} dependency.
92 purgeNixReferences = false;
93 coreCompression = true;
94 markRegionGC = self.threadSupport;
95 disableImmobileSpace = false;
96 linkableRuntime = stdenv.hostPlatform.isx86;
97
98 # I don’t know why these are failing (on ofBorg), and I’d rather just disable
99 # them and move forward with the succeeding tests than block testing
100 # altogether. One by one hopefully we can fix these (on ofBorg,
101 # upstream--somehow some way) in due time.
102 disabledTestFiles = lib.optionals (builtins.elem stdenv.hostPlatform.system [
103 "x86_64-linux"
104 "aarch64-linux"
105 ]) [
106 "foreign-stack-alignment.impure.lisp"
107 # Floating point tests are fragile
108 # https://sourceforge.net/p/sbcl/mailman/message/58728554/
109 "compiler.pure.lisp"
110 "float.pure.lisp"
111 ] ++ lib.optionals (stdenv.hostPlatform.system == "aarch64-linux") [
112 # This is failing on aarch64-linux on ofBorg. Not on my local machine nor on
113 # a VM on my laptop. Not sure what’s wrong.
114 "traceroot.impure.lisp"
115 # Heisentest, sometimes fails on ofBorg, would rather just disable it than
116 # have it block a release.
117 "futex-wait.test.sh"
118 ];
119 patches = [
120 # Support the NIX_SBCL_DYNAMIC_SPACE_SIZE envvar. Upstream SBCL didn’t want
121 # to include this (see
122 # "https://sourceforge.net/p/sbcl/mailman/sbcl-devel/thread/2cf20df7-01d0-44f2-8551-0df01fe55f1a%400brg.net/"),
123 # but for Nix envvars are sufficiently useful that it’s worth maintaining
124 # this functionality downstream.
125 ./dynamic-space-size-envvar-feature.patch
126 ./dynamic-space-size-envvar-tests.patch
127 ];
128 postPatch = lib.optionalString (self.disabledTestFiles != [ ]) ''
129 (cd tests ; rm -f ${lib.concatStringsSep " " self.disabledTestFiles})
130 ''
131 + lib.optionalString self.purgeNixReferences ''
132 # This is the default location to look for the core; by default in $out/lib/sbcl
133 sed 's@^\(#define SBCL_HOME\) .*$@\1 "/no-such-path"@' \
134 -i src/runtime/runtime.c
135 ''
136 + ''
137 (
138 shopt -s nullglob
139 # Tests need patching regardless of purging of paths from the final
140 # binary. There are some tricky files in nested directories which should
141 # definitely NOT be patched this way, hence just a single * (and no
142 # globstar).
143 substituteInPlace ${if self.purgeNixReferences then "tests" else "{tests,src/code}"}/*.{lisp,sh} \
144 --replace-quiet /usr/bin/env "${coreutils}/bin/env" \
145 --replace-quiet /bin/uname "${coreutils}/bin/uname" \
146 --replace-quiet /bin/sh "${stdenv.shell}"
147 )
148 # Official source release tarballs will have a version.lispexpr, but if you
149 # want to override { src = ... } it might not exist. It’s required for
150 # building, so create a mock version as a backup.
151 if [[ ! -a version.lisp-expr ]]; then
152 echo '"${self.version}.nixos"' > version.lisp-expr
153 fi
154 '';
155
156 preBuild = ''
157 export INSTALL_ROOT=$out
158 mkdir -p test-home
159 export HOME=$PWD/test-home
160 '';
161
162 enableFeatures = with lib;
163 assert assertMsg (self.markRegionGC -> self.threadSupport) "SBCL mark region GC requires thread support";
164 optional self.threadSupport "sb-thread" ++
165 optional self.linkableRuntime "sb-linkable-runtime" ++
166 optional self.coreCompression "sb-core-compression" ++
167 optional stdenv.isAarch32 "arm" ++
168 optional self.markRegionGC "mark-region-gc";
169
170 disableFeatures = with lib;
171 optional (!self.threadSupport) "sb-thread" ++
172 optionals self.disableImmobileSpace [ "immobile-space" "immobile-code" "compact-instance-header" ];
173
174 buildArgs = [
175 "--prefix=$out"
176 "--xc-host=${lib.escapeShellArg bootstrapLisp'}"
177 ] ++ builtins.map (x: "--with-${x}") self.enableFeatures
178 ++ builtins.map (x: "--without-${x}") self.disableFeatures
179 ++ lib.optionals (stdenv.hostPlatform.system == "aarch64-darwin") [
180 "--arch=arm64"
181 ];
182
183 # Fails to find `O_LARGEFILE` otherwise.
184 env.NIX_CFLAGS_COMPILE = "-D_GNU_SOURCE";
185
186 buildPhase = ''
187 runHook preBuild
188
189 sh make.sh ${lib.concatStringsSep " " self.buildArgs}
190 (cd doc/manual ; make info)
191
192 runHook postBuild
193 '';
194
195 # Tests on ofBorg’s x86_64-darwin platforms are so unstable that a random one
196 # will fail every other run. There’s a deeper problem here; we might as well
197 # disable them entirely so at least the other platforms get to benefit from
198 # testing.
199 doCheck = stdenv.hostPlatform.system != "x86_64-darwin";
200
201 # From the INSTALL docs
202 checkPhase = ''
203 runHook preCheck
204
205 (cd tests && sh run-tests.sh)
206
207 runHook postCheck
208 '';
209
210 installPhase = ''
211 runHook preInstall
212
213 INSTALL_ROOT=$out sh install.sh
214
215 ''
216 + lib.optionalString (!self.purgeNixReferences) ''
217 cp -r src $out/lib/sbcl
218 cp -r contrib $out/lib/sbcl
219 cat >$out/lib/sbcl/sbclrc <<EOF
220 (setf (logical-pathname-translations "SYS")
221 '(("SYS:SRC;**;*.*.*" #P"$out/lib/sbcl/src/**/*.*")
222 ("SYS:CONTRIB;**;*.*.*" #P"$out/lib/sbcl/contrib/**/*.*")))
223 EOF
224 '' + ''
225 runHook postInstall
226 '';
227
228 setupHook = lib.optional self.purgeNixReferences (writeText "setupHook.sh" ''
229 addEnvHooks "$targetOffset" _setSbclHome
230 _setSbclHome() {
231 export SBCL_HOME='@out@/lib/sbcl/'
232 }
233 '');
234
235 meta = with lib; {
236 description = "Common Lisp compiler";
237 homepage = "https://sbcl.org";
238 license = licenses.publicDomain; # and FreeBSD
239 mainProgram = "sbcl";
240 maintainers = lib.teams.lisp.members;
241 platforms = attrNames bootstrapBinaries ++ [
242 # These aren’t bootstrapped using the binary distribution but compiled
243 # using a separate (lisp) host
244 "x86_64-darwin"
245 "x86_64-linux"
246 "aarch64-darwin"
247 "aarch64-linux"
248 ];
249 };
250})