diff options
-rw-r--r-- | nix/buildLisp/default.nix | 293 |
1 files changed, 188 insertions, 105 deletions
diff --git a/nix/buildLisp/default.nix b/nix/buildLisp/default.nix index 3d0c36958f02..b1d852fbb51d 100644 --- a/nix/buildLisp/default.nix +++ b/nix/buildLisp/default.nix @@ -14,57 +14,20 @@ let # Internal helper definitions # - # 'genLoadLisp' generates Lisp code that instructs SBCL to load all - # the provided Lisp libraries. - genLoadLisp = deps: lib.concatStringsSep "\n" - (map (lib: "(load \"${lib}/${lib.lispName}.fasl\")") (allDeps deps)); - - # 'genCompileLisp' generates a Lisp file that instructs SBCL to - # compile the provided list of Lisp source files to $out. - genCompileLisp = srcs: deps: writeText "compile.lisp" '' - ;; This file compiles the specified sources into the Nix build - ;; directory, creating one FASL file for each source. - (require 'sb-posix) - - ${genLoadLisp deps} - - (defun nix-compile-lisp (file srcfile) - (let ((outfile (make-pathname :type "fasl" - :directory (or (sb-posix:getenv "NIX_BUILD_TOP") - (error "not running in a Nix build")) - :name (substitute #\- #\/ srcfile)))) - (multiple-value-bind (_outfile _warnings-p failure-p) - (compile-file srcfile :output-file outfile) - (if failure-p (sb-posix:exit 1) - (progn - ;; For the case of multiple files belonging to the same - ;; library being compiled, load them in order: - (load outfile) - - ;; Write them to the FASL list in the same order: - (format file "cat ~a~%" (namestring outfile))))))) - - (let ((*compile-verbose* t) - ;; FASL files are compiled into the working directory of the - ;; build and *then* moved to the correct out location. - (pwd (sb-posix:getcwd))) - - (with-open-file (file "cat_fasls" - :direction :output - :if-does-not-exist :create) - - ;; These forms were inserted by the Nix build: - ${ - lib.concatStringsSep "\n" (map (src: "(nix-compile-lisp file \"${src}\")") srcs) - } - )) - ''; - - # 'genTestLisp' generates a Lisp file that loads all sources and deps and - # executes expression - genTestLisp = name: srcs: deps: expression: writeText "${name}.lisp" '' + defaultImplementation = "sbcl"; + + # Generates lisp code which instructs the given lisp implementation to load + # all the given dependencies. + genLoadLispGeneric = impl: deps: + lib.concatStringsSep "\n" + (map (lib: "(load \"${lib}/${lib.lispName}.${impl.faslExt}\")") + (allDeps impl deps)); + + # 'genTestLispGeneric' generates a Lisp file that loads all sources and deps + # and executes expression for a given implementation description. + genTestLispGeneric = impl: { name, srcs, deps, expression }: writeText "${name}.lisp" '' ;; Dependencies - ${genLoadLisp deps} + ${impl.genLoadLisp deps} ;; Sources ${lib.concatStringsSep "\n" (map (src: "(load \"${src}\")") srcs)} @@ -78,9 +41,16 @@ let dependsOn = a: b: builtins.elem a b.lispDeps; # 'allDeps' flattens the list of dependencies (and their - # dependencies) into one ordered list of unique deps. - allDeps = deps: (lib.toposort dependsOn (lib.unique ( - lib.flatten (deps ++ (map (d: d.lispDeps) deps)) + # dependencies) into one ordered list of unique deps which + # all use the given implementation. + allDeps = impl: deps: let + # The override _should_ propagate itself recursively, as every derivation + # would only expose its actually used dependencies + deps' = builtins.map (dep: dep.overrideLisp or (lib.const dep) (_: { + implementation = impl.name; + })) deps; + in (lib.toposort dependsOn (lib.unique ( + lib.flatten (deps' ++ (map (d: d.lispDeps) deps')) ))).result; # 'allNative' extracts all native dependencies of a dependency list @@ -90,26 +60,6 @@ let lib.flatten (native ++ (map (d: d.lispNativeDeps) deps)) ); - # 'genDumpLisp' generates a Lisp file that instructs SBCL to dump - # the currently loaded image as an executable to $out/bin/$name. - # - # TODO(tazjin): Compression is currently unsupported because the - # SBCL in nixpkgs is, by default, not compiled with zlib support. - genDumpLisp = name: main: deps: writeText "dump.lisp" '' - (require 'sb-posix) - - ${genLoadLisp deps} - - (let* ((bindir (concatenate 'string (sb-posix:getenv "out") "/bin")) - (outpath (make-pathname :name "${name}" - :directory bindir))) - (save-lisp-and-die outpath - :executable t - :toplevel (function ${main}) - :purify t)) - ;; - ''; - # Add an `overrideLisp` attribute to a function result that works # similar to `overrideAttrs`, but is used specifically for the # arguments passed to Lisp builders. @@ -119,44 +69,176 @@ let # 'testSuite' builds a Common Lisp test suite that loads all of srcs and deps, # and then executes expression to check its result - testSuite = { name, expression, srcs, deps ? [], native ? [] }: + testSuite = { name, expression, srcs, deps ? [], native ? [], impl }: let lispNativeDeps = allNative native deps; - lispDeps = allDeps deps; + lispDeps = allDeps impl deps; in runCommandNoCC name { LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; LANG = "C.UTF-8"; } '' echo "Running test suite ${name}" - ${sbcl}/bin/sbcl --script ${genTestLisp name srcs deps expression} \ - | tee $out + ${impl.runScript} ${ + impl.genTestLisp { + inherit name srcs deps expression; + } + } | tee $out echo "Test suite ${name} succeeded" ''; + # 'impls' is an attribute set of attribute sets which describe how to do common + # tasks when building for different Common Lisp implementations. Each + # implementation set has the following members: + # + # Required members: + # + # - runScript :: string + # Describes how to invoke the implementation from the shell, so it runs a + # lisp file as a script and exits. + # - faslExt :: string + # File extension of the implementations loadable (FASL) files. + # Implementations are free to generate native object files, but with the way + # buildLisp works it is required that we can also 'load' libraries, so + # (additionally) building a FASL or equivalent is required. + # - genLoadLisp :: [ dependency ] -> string + # Returns lisp code to 'load' the given dependencies. 'genLoadLispGeneric' + # should work for most dependencies. + # - genCompileLisp :: { name, srcs, deps } -> file + # Builds a lisp file which instructs the implementation to build a library + # from the given source files when executed. After running at least + # the file "$out/${name}.${impls.${implementation}.faslExt}" should have + # been created. + # - genDumpLisp :: { name, main, deps } -> file + # Builds a lisp file which instructs the implementation to build an + # 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. + # - 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 + # true or false, the script must exit with a zero or non-zero exit code. + # 'genTestLispGeneric' will work for most implementations. + # - lispWith :: [ dependency ] -> drv + # Builds a script (or dumped image) which when executed loads (or has + # loaded) all given dependencies. When built this should create an executable + # at "$out/bin/${implementation}". + impls = lib.mapAttrs (name: v: { inherit name; } // v) { + sbcl = { + runScript = "${sbcl}/bin/sbcl --script"; + faslExt = "fasl"; + + # 'genLoadLisp' generates Lisp code that instructs SBCL to load all + # the provided Lisp libraries. + genLoadLisp = genLoadLispGeneric impls.sbcl; + + # 'genCompileLisp' generates a Lisp file that instructs SBCL to + # compile the provided list of Lisp source files to "$out/${name}.fasl". + genCompileLisp = { name, srcs, deps }: writeText "sbcl-compile.lisp" '' + ;; This file compiles the specified sources into the Nix build + ;; directory, creating one FASL file for each source. + (require 'sb-posix) + + ${impls.sbcl.genLoadLisp deps} + + (defun nix-compile-lisp (srcfile) + (let ((outfile (make-pathname :type "fasl" + :directory (or (sb-posix:getenv "NIX_BUILD_TOP") + (error "not running in a Nix build")) + :name (substitute #\- #\/ srcfile)))) + (multiple-value-bind (out-truename _warnings-p failure-p) + (compile-file srcfile :output-file outfile) + (if failure-p (sb-posix:exit 1) + (progn + ;; For the case of multiple files belonging to the same + ;; library being compiled, load them in order: + (load out-truename) + + ;; Return pathname as a string for cat-ting it later + (namestring out-truename)))))) + + (let ((*compile-verbose* t) + (catted-fasl (make-pathname :type "fasl" + :directory (or (sb-posix:getenv "out") + (error "not running in a Nix build")) + :name "${name}"))) + + (with-open-file (file catted-fasl + :direction :output + :if-does-not-exist :create) + + ;; SBCL's FASL files can just be bundled together using cat + (sb-ext:run-program "cat" + (mapcar #'nix-compile-lisp + ;; These forms were inserted by the Nix build: + '(${ + lib.concatMapStringsSep "\n" (src: "\"${src}\"") srcs + })) + :output file :search t))) + ''; + + # 'genDumpLisp' generates a Lisp file that instructs SBCL to dump + # the currently loaded image as an executable to $out/bin/$name. + # + # TODO(tazjin): Compression is currently unsupported because the + # SBCL in nixpkgs is, by default, not compiled with zlib support. + genDumpLisp = { name, main, deps }: writeText "sbcl-dump.lisp" '' + (require 'sb-posix) + + ${impls.sbcl.genLoadLisp deps} + + (let* ((bindir (concatenate 'string (sb-posix:getenv "out") "/bin")) + (outpath (make-pathname :name "${name}" + :directory bindir))) + (save-lisp-and-die outpath + :executable t + :toplevel (function ${main}) + :purify t)) + ''; + + genTestLisp = genTestLispGeneric impls.sbcl; + + lispWith = deps: + let lispDeps = filter (d: !d.lispBinary) (allDeps impls.sbcl deps); + in writeShellScriptBin "sbcl" '' + export LD_LIBRARY_PATH="${lib.makeLibraryPath (allNative [] lispDeps)}" + export LANG="C.UTF-8" + exec ${sbcl}/bin/sbcl ${ + lib.optionalString (deps != []) + "--load ${writeText "load.lisp" (impls.sbcl.genLoadLisp lispDeps)}" + } $@ + ''; + }; + }; + # # Public API functions # - # 'library' builds a list of Common Lisp files into a single FASL - # which can then be loaded into SBCL. + # 'library' builds a list of Common Lisp files into an implementation + # specific library format, usually a single FASL file, which can then be + # loaded and built into an executable via 'program'. library = { name + , implementation ? defaultImplementation , srcs , deps ? [] , native ? [] , tests ? null }: let + impl = impls."${implementation}" or + (builtins.throw "Unkown Common Lisp Implementation ${implementation}"); lispNativeDeps = (allNative native deps); - lispDeps = allDeps deps; + lispDeps = allDeps impl deps; testDrv = if ! isNull tests then testSuite { name = tests.name or "${name}-test"; srcs = srcs ++ (tests.srcs or []); deps = deps ++ (tests.deps or []); expression = tests.expression; + inherit impl; } else null; in lib.fix (self: runCommandNoCC "${name}-cllib" { @@ -167,28 +249,28 @@ let lispName = name; lispBinary = false; tests = testDrv; - sbcl = sbclWith [ self ]; + ${implementation} = impl.lispWith [ self ]; }; } '' ${if ! isNull testDrv then "echo 'Test ${testDrv} succeeded'" else "echo 'No tests run'"} - ${sbcl}/bin/sbcl --script ${genCompileLisp srcs lispDeps} - - echo "Compilation finished, assembling FASL files" - # FASL files can be combined by simply concatenating them - # together, but it needs to be in the compilation order. mkdir $out - chmod +x cat_fasls - ./cat_fasls > $out/${name}.fasl + ${impl.runScript} ${ + impl.genCompileLisp { + inherit name srcs; + deps = lispDeps; + } + } ''); - # 'program' creates an executable containing a dumped image of the + # 'program' creates an executable, usually containing a dumped image of the # specified sources and dependencies. program = { name + , implementation ? defaultImplementation , main ? "${name}:main" , srcs , deps ? [] @@ -196,9 +278,12 @@ let , tests ? null }: let - lispDeps = allDeps deps; + impl = impls."${implementation}" or + (builtins.throw "Unkown Common Lisp Implementation ${implementation}"); + lispDeps = allDeps impl deps; libPath = lib.makeLibraryPath (allNative native lispDeps); - selfLib = library { + # overriding is used internally to propagate the implementation to use + selfLib = (makeOverridable library) { inherit name srcs native; deps = lispDeps; }; @@ -210,6 +295,7 @@ let srcs ++ (tests.srcs or [])); deps = deps ++ (tests.deps or []); expression = tests.expression; + inherit impl; } else null; in lib.fix (self: runCommandNoCC "${name}" { @@ -222,7 +308,7 @@ let lispNativeDeps = native; lispBinary = true; tests = testDrv; - sbcl = sbclWith [ self ]; + ${implementation} = impl.lispWith [ self ]; }; } '' ${if ! isNull testDrv @@ -230,8 +316,11 @@ let else ""} mkdir -p $out/bin - ${sbcl}/bin/sbcl --script ${ - genDumpLisp name main ([ selfLib ] ++ lispDeps) + ${impl.runScript} ${ + impl.genDumpLisp { + inherit name main; + deps = ([ selfLib ] ++ lispDeps); + } } wrapProgram $out/bin/${name} --prefix LD_LIBRARY_PATH : "${libPath}" @@ -243,18 +332,12 @@ let inherit name; srcs = lib.singleton (builtins.toFile "${name}.lisp" "(require '${name})"); }; - - # 'sbclWith' creates an image with the specified libraries / - # programs loaded. - sbclWith = deps: - let lispDeps = filter (d: !d.lispBinary) (allDeps deps); - in writeShellScriptBin "sbcl" '' - export LD_LIBRARY_PATH="${lib.makeLibraryPath (allNative [] lispDeps)}" - export LANG="C.UTF-8" - exec ${sbcl}/bin/sbcl ${lib.optionalString (deps != []) "--load ${writeText "load.lisp" (genLoadLisp lispDeps)}"} $@ - ''; in { library = makeOverridable library; program = makeOverridable program; - inherit sbclWith bundled; + inherit bundled; + + # 'sbclWith' creates an image with the specified libraries / + # programs loaded in SBCL. + sbclWith = impls.sbcl.lispWith; } |