├── .travis.yml ├── COPYING ├── README.org ├── default.nix ├── main.lisp ├── mkNixlispBundle.nix ├── nixlispBundle.nix ├── nixlispDist.nix ├── ql2nix.asd └── ql2nix.nix /.travis.yml: -------------------------------------------------------------------------------- 1 | language: nix 2 | os: 3 | - linux 4 | 5 | script: 6 | - nix-build default.nix && ./result/bin/ql2nix --help 7 | 8 | install: 9 | - true 10 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2019 Bradley Jensen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Quicklisp To Nix 2 | #+AUTHOR: Brad Jensen 3 | 4 | So you've got a Common Lisp project that you want to build in Nix? 5 | ~ql2nix~ can help! ~ql2nix~ will help you produce a nix expression 6 | that builds a Quicklisp bundle that contains the Common Lisp 7 | dependencies of your project. 8 | 9 | * Building 10 | The preferred way to build ~ql2nix~ is with Nix, of course! ~ql2nix~ 11 | includes a ~default.nix~ that builds ~ql2nix~. Just run ~nix-build~! 12 | #+BEGIN_EXAMPLE 13 | nix-build 14 | #+END_EXAMPLE 15 | 16 | ~ql2nix~ is also a valid ASDF system and it is set up to output an 17 | executable. So, all you need to do is get ASDF to perform the 18 | ~program-op~ on ~"ql2nix"~. Something like this! 19 | 20 | #+BEGIN_EXAMPLE 21 | sbcl --eval '(require :asdf)' \ 22 | --eval '(let ((asdf:*central-registry* (cons (truename ".") asdf:*central-registry*))) 23 | (asdf:oos (quote asdf:program-op) "ql2nix"))' 24 | #+END_EXAMPLE 25 | 26 | ASDF will save the executable in the usual output location -- usually 27 | somewhere in ~$HOME/.cache/common-lisp/~. 28 | 29 | * Usage 30 | #+BEGIN_EXAMPLE 31 | ql2nix [--quicklisp-setup path/to/quicklisp/setup.lisp] [--project-dir PATH] [--] system... 32 | #+END_EXAMPLE 33 | 34 | ~ql2nix~ works by loading the named systems with ~ql:quickload~. Any 35 | system that ASDF touches gets marked. If that system is provided by 36 | your Quicklisp installation then ~ql2nix~ will include it in the 37 | closure. 38 | 39 | If ~ql2nix~ wasn't built with Quicklisp already loaded then you must 40 | provide the path to Quicklisp's ~setup.lisp~ via the 41 | ~--quicklisp-setup~ command line argument. 42 | 43 | Any paths you specify with ~--project-dir~ will be included in ASDF's 44 | source registry. Systems contained within those paths are not 45 | included in the closure that ~ql2nix~ produces. 46 | 47 | There are two ways you can use ~ql2nix~. You can either have ~ql2nix~ 48 | load your ASDF system and discover required dependencies automatically 49 | or you can pass in dependencies explicitly. Either way, you must run 50 | ~ql2nix~ in an environment where it can ~ql:quickload~ the systems 51 | that are named. So, if a package depends on a native library then 52 | that native library must be available in the environment when you run 53 | ~ql2nix~. For example... 54 | 55 | #+BEGIN_EXAMPLE 56 | nix-shell -p someNativeDependency # if you have any! 57 | # Automatic dependency discovery 58 | ql2nix --quicklisp-setup ~/quicklisp/setup.lisp --project-dir path/to/your/source/ your-system-name 59 | # Explicit dependencies 60 | ql2nix --quicklisp-setup ~/quicklisp/setup.lisp systems-you depend-on-go here 61 | #+END_EXAMPLE 62 | 63 | Either way, ~ql2nix~ will output a file named ~qlDist.nix~. This file 64 | describes the transitive closure of Quicklisp systems that were 65 | touched while loading the systems specified on the command line. 66 | ~qlDist.nix~ can be combined with the provided ~mkNixlispBundle.nix~ 67 | to produce a Nix derivation that contains the transitive closure of 68 | systems described by ~qlDist.nix~. 69 | 70 | If you're already using Nixpkgs's ~clwrapper~, then using 71 | ~mkNixlispBundle~ is trivial. Simply include the derivation it 72 | returns in your ~buildInputs~. ~clwrapper~ will ensure that all of 73 | the closure's systems are included in ASDF's source registry. For example, 74 | 75 | #+BEGIN_EXAMPLE 76 | { pkgs ? import {} }: 77 | 78 | with (import (fetchFromGitHub { 79 | owner = "SquircleSpace" 80 | repo = "ql2nix"; 81 | rev = "..."; 82 | sha256 = "..."; 83 | }) { inherit pkgs; }); 84 | 85 | let 86 | nixlispBundle = mkNixlispBundle ./qlDist.nix; 87 | in 88 | pkgs.stdenv.mkDerivation rec { 89 | name = "example"; 90 | buildInputs = [ ql2nix nixlispBundle ]; 91 | # ... 92 | } 93 | #+END_EXAMPLE 94 | 95 | If you're not using ~clwrapper~, then you'll need to ~LOAD~ the file 96 | at ~lib/common-lisp/bundle/bundle.lisp~. This file will configure 97 | ASDF so that the closure's systems are included in ASDF's source 98 | registry. 99 | 100 | As mentioned above, you need to use ~mkNixlispBundle~ to leverage the 101 | ~qlDist.nix~ file produced by ~ql2nix~. Until Nixpkgs includes 102 | ~mkNixlispBundle~, you'll need to embed it within your project. 103 | ~ql2nix~ will helpfully write out the supporting Nix expressions 104 | alongside ~qlDist.nix~ if you pass in the ~--nixlisp-lib~ flag. 105 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Bradley Jensen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | { pkgs ? import {} }: 23 | 24 | { 25 | ql2nix = import ./ql2nix.nix { inherit pkgs; }; 26 | mkNixlispBundle = import ./mkNixlispBundle.nix pkgs; 27 | } 28 | -------------------------------------------------------------------------------- /main.lisp: -------------------------------------------------------------------------------- 1 | ;; Copyright 2019 Bradley Jensen 2 | ;; 3 | ;; Permission is hereby granted, free of charge, to any person obtaining 4 | ;; a copy of this software and associated documentation files (the 5 | ;; "Software"), to deal in the Software without restriction, including 6 | ;; without limitation the rights to use, copy, modify, merge, publish, 7 | ;; distribute, sublicense, and/or sell copies of the Software, and to 8 | ;; permit persons to whom the Software is furnished to do so, subject to 9 | ;; the following conditions: 10 | ;; 11 | ;; The above copyright notice and this permission notice shall be 12 | ;; included in all copies or substantial portions of the Software. 13 | ;; 14 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | ;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | ;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | ;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | (defpackage :ql2nix 23 | (:use :common-lisp) 24 | (:import-from :uiop) 25 | (:export #:main)) 26 | (in-package :ql2nix) 27 | 28 | (declaim (optimize (speed 0) (space 0) (safety 3) (debug 3) (compilation-speed 0))) 29 | 30 | (defvar *nixlisp-bundle-contents* 31 | (load-time-value 32 | (with-open-file (stream (uiop:merge-pathnames* #P"nixlispBundle.nix" *load-truename*)) 33 | (uiop:slurp-input-stream :string stream)))) 34 | 35 | (defvar *nixlisp-dist-contents* 36 | (load-time-value 37 | (with-open-file (stream (uiop:merge-pathnames* #P"nixlispDist.nix" *load-truename*)) 38 | (uiop:slurp-input-stream :string stream)))) 39 | 40 | (defvar *mk-nixlisp-bundle-contents* 41 | (load-time-value 42 | (with-open-file (stream (uiop:merge-pathnames* #P"mkNixlispBundle.nix" *load-truename*)) 43 | (uiop:slurp-input-stream :string stream)))) 44 | 45 | (defun produce-nixlisp-lib-files () 46 | (with-open-file (stream #P"nixlispDist.nix" :direction :output :if-exists :supersede) 47 | (write-string *nixlisp-dist-contents* stream)) 48 | (with-open-file (stream #P"nixlispBundle.nix" :direction :output :if-exists :supersede) 49 | (write-string *nixlisp-bundle-contents* stream)) 50 | (with-open-file (stream #P"mkNixlispBundle.nix" :direction :output :if-exists :supersede) 51 | (write-string *mk-nixlisp-bundle-contents* stream))) 52 | 53 | (defmacro define-wrapper (function-name package-name) 54 | `(defun ,function-name (object) 55 | (funcall (find-symbol ,(symbol-name function-name) ,package-name) object))) 56 | 57 | (define-wrapper ensure-local-archive-file :ql-dist) 58 | (define-wrapper name :ql-dist) 59 | (define-wrapper archive-url :ql-dist) 60 | (define-wrapper archive-size :ql-dist) 61 | (define-wrapper archive-md5 :ql-dist) 62 | (define-wrapper archive-content-sha1 :ql-dist) 63 | (define-wrapper prefix :ql-dist) 64 | (define-wrapper system-files :ql-dist) 65 | (define-wrapper release :ql-dist) 66 | (define-wrapper system-file-name :ql-dist) 67 | (define-wrapper required-systems :ql-dist) 68 | (define-wrapper provided-releases :ql-dist) 69 | (define-wrapper provided-systems :ql-dist) 70 | (define-wrapper find-system :ql-dist) 71 | (define-wrapper quickload :ql) 72 | 73 | (defun ensure-quicklisp () 74 | (loop 75 | (if (find-package :quicklisp) 76 | (return) 77 | (restart-case 78 | (error "You must load quicklisp before continuing") 79 | (check-again ()))))) 80 | 81 | (defmacro equal-case (key &body clauses) 82 | (let ((value (gensym "VALUE"))) 83 | `(let ((,value ,key)) 84 | (cond 85 | ,@(loop :for clause :in clauses :collect 86 | `(,(if (eq 'otherwise (car clause)) 87 | `t 88 | `(equal ,value ,(car clause))) 89 | ,@(cdr clause))))))) 90 | 91 | (defun nix-prefetch (path) 92 | (destructuring-bind (hash store-path) 93 | (uiop:run-program `("nix-prefetch-url" "--print-path" ,(concatenate 'string "file://" (uiop:unix-namestring path))) 94 | :output :lines) 95 | (values hash store-path))) 96 | 97 | (defun path-name (pathname) 98 | (make-pathname :name (pathname-name pathname) :type (pathname-type pathname))) 99 | 100 | (defvar *indent-level* 0) 101 | 102 | (defmacro with-indent (&body body) 103 | `(let ((*indent-level* (1+ *indent-level*))) 104 | ,@body)) 105 | 106 | (defun indent-format (output format-specifier &rest args) 107 | (dotimes (count *indent-level*) 108 | (format output " ")) 109 | (apply 'format output format-specifier args)) 110 | 111 | (defun produce-ql-releases (releases ql-systems &key (output-stream *standard-output*)) 112 | (setf releases (sort (copy-list releases) 'string< :key 'name)) 113 | (indent-format output-stream "{~%") 114 | (with-indent 115 | (dolist (release releases) 116 | (let* ((path (ensure-local-archive-file release)) 117 | (nix-hash (nix-prefetch path)) 118 | (allowable-system-files (make-hash-table :test 'equal))) 119 | ;; We only want the ultimate nixlisp dist closure to include 120 | ;; systems that we... well... captured in the closure. asd 121 | ;; files that weren't implicated as part of building the 122 | ;; closure shouldn't be included in the list. Otherwise, 123 | ;; quicklisp will try to find a system corresponding to the 124 | ;; asd and get confused. If we don't mention the asd and we 125 | ;; don't mention any systems contained within the asd then 126 | ;; quicklisp (hopefully) won't even notice it. 127 | (dolist (system (provided-systems release)) 128 | (when (gethash (name system) ql-systems) 129 | (setf (gethash (concatenate 'string (system-file-name system) ".asd") allowable-system-files) t))) 130 | (indent-format output-stream "\"~A\" = {~%" (name release)) 131 | (with-indent 132 | (indent-format output-stream "archive = fetchurl {~%") 133 | (with-indent 134 | (indent-format output-stream "url = \"~A\";~%" (archive-url release)) 135 | (indent-format output-stream "sha256 = \"~A\";~%" nix-hash)) 136 | (indent-format output-stream "};~%") 137 | (indent-format output-stream "name = \"~A\";~%" (name release)) 138 | (indent-format output-stream "archiveName = \"~A\";~%" (path-name path)) 139 | (indent-format output-stream "archiveSize = ~A;~%" (archive-size release)) 140 | (indent-format output-stream "archiveMD5 = \"~A\";~%" (archive-md5 release)) 141 | (indent-format output-stream "archiveContentSHA1 = \"~A\";~%" (archive-content-sha1 release)) 142 | (indent-format output-stream "prefix = \"~A\";~%" (prefix release)) 143 | (indent-format output-stream "systemFiles = [~%") 144 | (with-indent 145 | (dolist (file-name (system-files release)) 146 | (when (gethash file-name allowable-system-files) 147 | (indent-format output-stream "\"~A\"~%" file-name)))) 148 | (indent-format output-stream "];~%")) 149 | (indent-format output-stream "};~%")))) 150 | (indent-format output-stream "};~%")) 151 | 152 | (defun produce-ql-systems (systems &key (output-stream *standard-output*)) 153 | (setf systems (sort (copy-list systems) 'string< :key 'name)) 154 | (indent-format output-stream "{~%") 155 | (with-indent 156 | (dolist (system systems) 157 | (indent-format output-stream "\"~A\" = {~%" (name system)) 158 | (with-indent 159 | (indent-format output-stream "release = qlReleases.\"~A\";~%" (name (release system))) 160 | (indent-format output-stream "name = \"~A\";~%" (name system)) 161 | (indent-format output-stream "systemFileName = \"~A\";~%" (system-file-name system)) 162 | (indent-format output-stream "requiredSystems = [~%") 163 | (with-indent 164 | (dolist (required-system (required-systems system)) 165 | (unless (or (equal "asdf" required-system) 166 | (equal "uiop" required-system)) 167 | ;; ASDF and UIOP will be handled separately 168 | (indent-format output-stream "qlSystems.\"~A\"~%" required-system)))) 169 | (indent-format output-stream "];~%")) 170 | (indent-format output-stream "};~%"))) 171 | (indent-format output-stream "};~%")) 172 | 173 | (defun produce-ql-dist (ql-releases ql-systems) 174 | (with-open-file (stream #P"qlDist.nix" :direction :output :if-exists :supersede) 175 | (indent-format stream "# This file is machine generated. Do not edit it!~%") 176 | (indent-format stream "{ fetchurl }:~%") 177 | (indent-format stream "let~%") 178 | (with-indent 179 | (indent-format stream "qlReleases =~%") 180 | (with-indent 181 | (produce-ql-releases (hash-table-values ql-releases) ql-systems :output-stream stream)) 182 | (indent-format stream "qlSystems =~%") 183 | (with-indent 184 | (produce-ql-systems (hash-table-values ql-systems) :output-stream stream))) 185 | (indent-format stream "in { inherit qlSystems qlReleases; }~%"))) 186 | 187 | (defun hash-table-values (hash-table) 188 | (loop :for value :being :the :hash-values :of hash-table :collect value)) 189 | 190 | (defvar *touched-systems* (make-hash-table :test 'eq)) 191 | 192 | (defmethod asdf:perform :before (op (system asdf:system)) 193 | (setf (gethash system *touched-systems*) t)) 194 | 195 | (defvar *help* 196 | "Usage: ql2nix --quicklisp-setup [options] [--] [systems...] 197 | 198 | ql2nix uses QL:QUICKLOAD to load the named systems. Any Quicklisp 199 | system that gets touched by ASDF will be captured by ql2nix. After 200 | loading all the systems, ql2nix will output a Nix file describing the 201 | captured Quicklisp systems. Using the mkNixlispBundle Nix function, 202 | you can easily obtain a derivation which includes all of the Quicklisp 203 | packages captured in the closure. The derivation also includes a 204 | Common Lisp source file that, when loaded, makes the systems in the 205 | closure available to ASDF. 206 | 207 | Options: 208 | 209 | --quicklisp-setup 210 | Must be specified exactly once. Pass in the path to Quicklisp's 211 | setup.lisp file. ql2nix will use the given Quicklisp installation 212 | when quickloading dependencies. 213 | 214 | --project-dir 215 | Add PATH to ASDF's source registry. In addition, ql2nix will ignore 216 | any systems contained within PATH. If you pass in the path to your 217 | project and then have ql2nix quickload your system then ql2nix will 218 | automatically discover all of your project's dependencies! Nifty! 219 | 220 | --load 221 | Load the file at the given path. If this file uses Quicklisp (or 222 | ASDF) to load a system that Quicklisp provides then the loaded 223 | system will be included in the closure. You can also use this 224 | option to trigger side effects. For example, you can add something 225 | to *FEATURES* or modify state that will impact how the systems are 226 | loaded. 227 | 228 | --eval 229 | Evaluate the given Common Lisp form. Like the --load option, this 230 | option allows you to modify state or load systems in specialized 231 | ways. 232 | 233 | --system-file 234 | Open the given text file and treat every line of the file as a 235 | system to quickload. This option gives you a way to avoid naming a 236 | lot of systems on the command line. 237 | 238 | --nixlisp-lib 239 | Write out the supporting nixlisp Nix files required to make use of 240 | ql2nix's system closures. Until ql2nix's mkNixlispBundle function 241 | gets integrated into nixpkgs, you will probably want to use this 242 | option! 243 | 244 | --help 245 | Show this text and exit. 246 | ") 247 | 248 | (defun help () 249 | (write-string *help* *error-output*)) 250 | 251 | (defun main (&optional (argv (uiop:raw-command-line-arguments))) 252 | (pop argv) 253 | (let (project-paths 254 | quicklisp-setup-path 255 | output-nixlisp-lib-files 256 | eval-forms 257 | system-file-paths) 258 | (loop :while argv :for arg = (pop argv) :do 259 | (equal-case arg 260 | ("--quicklisp-setup" 261 | (setf quicklisp-setup-path (pop argv)) 262 | (unless quicklisp-setup-path 263 | (error "--quicklisp-setup requires a path to Quicklisp's setup.lisp file"))) 264 | 265 | ("--project-dir" 266 | (let ((project-path (pop argv))) 267 | (unless project-path 268 | (error "Missing required argument: path to project source")) 269 | (setf project-path (truename (uiop:ensure-directory-pathname (uiop:merge-pathnames* (uiop:parse-native-namestring project-path))))) 270 | (assert (uiop:absolute-pathname-p project-path)) 271 | (push project-path asdf:*central-registry*))) 272 | 273 | ("--nixlisp-lib" 274 | (setf output-nixlisp-lib-files t)) 275 | 276 | ("--load" 277 | (let ((path (pop argv))) 278 | (unless path 279 | (error "Missing required argument: path to load")) 280 | (push `(load ,path) eval-forms))) 281 | 282 | ("--eval" 283 | (let ((form-string (pop argv))) 284 | (unless form-string 285 | (error "Missing required argument: form to eval")) 286 | (push (read (make-string-input-stream form-string)) eval-forms))) 287 | 288 | ("--system-file" 289 | (let ((path (pop argv))) 290 | (unless path 291 | (error "Missing required argument: path to system file")) 292 | (push path system-file-paths))) 293 | 294 | ("--help" 295 | (help) 296 | (return-from main)) 297 | 298 | ("--" 299 | (return)) 300 | 301 | (otherwise 302 | (push arg argv) 303 | (return)))) 304 | 305 | (when quicklisp-setup-path 306 | (if (find-package :quicklisp) 307 | (error "Quicklisp is already loaded") 308 | (progn 309 | (load quicklisp-setup-path) 310 | (ensure-quicklisp)))) 311 | 312 | (when output-nixlisp-lib-files 313 | (produce-nixlisp-lib-files)) 314 | 315 | (dolist (form (nreverse eval-forms)) 316 | (eval form)) 317 | 318 | (let ((system-names argv)) 319 | (dolist (system-file-path system-file-paths) 320 | (with-open-file (stream system-file-path) 321 | (loop :for line = (readline stream nil :eof) :while line :do 322 | (push line system-names)))) 323 | 324 | (unless system-names 325 | (return-from main)) 326 | 327 | (ensure-quicklisp) 328 | 329 | (let ((ql-systems (make-hash-table :test 'equal)) 330 | (ql-releases (make-hash-table :test 'equal))) 331 | (dolist (system-name system-names) 332 | (quickload system-name)) 333 | 334 | (loop :for system :being :the :hash-keys :of *touched-systems* :do 335 | (block continue 336 | (dolist (project-path project-paths) 337 | (when (uiop:subpathp (asdf:component-pathname system) project-path) 338 | (return-from continue))) 339 | (let* ((system-name (asdf:component-name system)) 340 | (ql-system (or (find-system system-name) 341 | (progn 342 | (warn "Couldn't find quicklisp system for ASDF system: ~A" system-name) 343 | (return-from continue))))) 344 | (setf (gethash (name ql-system) ql-systems) ql-system) 345 | (setf (gethash (name (release ql-system)) ql-releases) (release ql-system))))) 346 | 347 | (produce-ql-dist ql-releases ql-systems))))) 348 | -------------------------------------------------------------------------------- /mkNixlispBundle.nix: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Bradley Jensen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | { writeTextFile, fetchurl, lib, stdenv, lispPackages, ... }: 23 | qlDist: 24 | let 25 | concatMapStrings = lib.concatMapStrings; 26 | mkDerivation = stdenv.mkDerivation; 27 | quicklisp = lispPackages.quicklisp; 28 | clwrapper = lispPackages.clwrapper; 29 | nixlispDist = import ./nixlispDist.nix { 30 | inherit writeTextFile concatMapStrings mkDerivation; 31 | qlDist = if builtins.pathExists qlDist 32 | then (import qlDist) { inherit fetchurl; } 33 | else { qlReleases = {}; qlSystems = {}; }; 34 | }; 35 | nixlispBundle = import ./nixlispBundle.nix { 36 | inherit nixlispDist quicklisp clwrapper writeTextFile mkDerivation; 37 | }; 38 | in nixlispBundle 39 | -------------------------------------------------------------------------------- /nixlispBundle.nix: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Bradley Jensen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | { nixlispDist, quicklisp, clwrapper, writeTextFile, mkDerivation }: 23 | let 24 | bundler = writeTextFile { 25 | name = "bundler.lisp"; 26 | text = '' 27 | (setf *debugger-hook* 28 | (lambda (&rest args) 29 | (declare (ignore args)) 30 | ;; UIOP may not be loaded yet. Let's play it safe 31 | (let* ((uiop-package (find-package :uiop)) 32 | (print-backtrace (when uiop-package (find-symbol "PRINT-BACKTRACE" uiop-package)))) 33 | (if print-backtrace 34 | (funcall print-backtrace) 35 | (format t "Early fatal error. No backtrace. Good luck!~%"))) 36 | (uiop:quit 1))) 37 | 38 | (eval-when (:compile-toplevel :load-toplevel :execute) 39 | (load "quicklisp/setup.lisp")) 40 | 41 | (ql:bundle-systems (mapcar 'ql-dist:name (ql-dist:provided-systems (ql-dist:find-dist "nixlisp"))) 42 | :to #P"bundle/") 43 | 44 | (uiop:quit 0) 45 | ''; 46 | }; 47 | in mkDerivation rec { 48 | name = "nixlisp-bundle-${version}"; 49 | version = "1.0.0"; 50 | propagatedBuildInputs = [ clwrapper ]; 51 | unpackPhase = "true"; 52 | buildPhase = '' 53 | mkdir -p quicklisp/tmp 54 | mkdir -p quicklisp/local-projects 55 | mkdir -p quicklisp/dists/nixlisp/archives 56 | mkdir -p quicklisp/quicklisp 57 | 58 | cp -r "${quicklisp}/lib/common-lisp/quicklisp/"* quicklisp/ 59 | 60 | # We have our own dists, thank you very much 61 | if [ -d quicklisp/dists/quicklisp ]; then 62 | rm -rdf quicklisp/dists/quicklisp 63 | fi 64 | 65 | ${nixlispDist}/bin/nixlisp-installer quicklisp/dists/nixlisp/ 66 | 67 | # Sourcing this file with NIX_LISP_SKIP_CODE=1 always returns 68 | # non-zero. Since we're building in a set -e environment we need to 69 | # cause that non-zero return value to become zero. 70 | NIX_LISP_SKIP_CODE=1 source "${clwrapper}/bin/common-lisp.sh" || true 71 | mkdir -p bundle 72 | "${clwrapper}/bin/common-lisp.sh" "$NIX_LISP_LOAD_FILE" "${bundler}" 73 | 74 | # This file contains nondeterministic output (e.g. date and info 75 | # about host). Until we have some reason to include it (or a 76 | # replacement), its better to leave it out. 77 | rm "bundle/bundle-info.sexp" 78 | ''; 79 | installPhase = '' 80 | mkdir -p "$out/lib/common-lisp" 81 | mv bundle "$out/lib/common-lisp/" 82 | 83 | mkdir -p "$out/lib/common-lisp-settings" 84 | outhash="$out" 85 | outhash="''${outhash##*/}" 86 | outhash="''${outhash%%-*}" 87 | cat > "$out/lib/common-lisp-settings/bundle-path-config.sh" < "$out/etc/nixlisp/enabled.txt" 66 | 67 | mkdir -p "$out/etc/nixlisp/archives" 68 | ${concatMapStrings (release: "ln -s \"${release.archive}\" \"$out/etc/nixlisp/archives/${release.archiveName}\"\n") (builtins.attrValues qlDist.qlReleases)} 69 | 70 | mkdir -p "$out/bin" 71 | cat > "$out/bin/nixlisp-installer" <&2 76 | exit 1 77 | fi 78 | 79 | mkdir -p "\$1"/archives 80 | cp "$out/etc/nixlisp/"*.txt "\$1/" 81 | if [ -z "$(find "$out/etc/nixlisp/archives/" -maxdepth 0 -empty)" ]; then 82 | for archive in "$out/etc/nixlisp/archives/"*; do 83 | cp "\$archive" "\$1"/archives/ 84 | done 85 | fi 86 | EOF 87 | chmod +x "$out/bin/nixlisp-installer" 88 | ''; 89 | } 90 | -------------------------------------------------------------------------------- /ql2nix.asd: -------------------------------------------------------------------------------- 1 | ;; Copyright 2019 Bradley Jensen 2 | ;; 3 | ;; Permission is hereby granted, free of charge, to any person obtaining 4 | ;; a copy of this software and associated documentation files (the 5 | ;; "Software"), to deal in the Software without restriction, including 6 | ;; without limitation the rights to use, copy, modify, merge, publish, 7 | ;; distribute, sublicense, and/or sell copies of the Software, and to 8 | ;; permit persons to whom the Software is furnished to do so, subject to 9 | ;; the following conditions: 10 | ;; 11 | ;; The above copyright notice and this permission notice shall be 12 | ;; included in all copies or substantial portions of the Software. 13 | ;; 14 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | ;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | ;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | ;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | (defsystem "ql2nix" 23 | :description "Convert quicklisp packages into nix packages" 24 | :version "1.0.0" 25 | :author "Brad Jensen " 26 | :licence "MIT" 27 | :components ((:file "main")) 28 | :entry-point "ql2nix::main") 29 | -------------------------------------------------------------------------------- /ql2nix.nix: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Bradley Jensen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | { pkgs ? import {} }: 23 | let 24 | build = pkgs.writeTextFile { 25 | name = "ql2nix-build.lisp"; 26 | text = '' 27 | (require '#:asdf) 28 | (let* ((asdf:*central-registry* (cons (truename ".") asdf:*central-registry*)) 29 | (system (asdf:find-system '#:ql2nix)) 30 | (uiop:*image-dump-hook* (cons (lambda () (asdf:clear-system system)) uiop:*image-dump-hook*))) 31 | (asdf:oos 'asdf:program-op system)) 32 | ''; 33 | }; 34 | clwrapper = pkgs.lispPackages.clwrapper; 35 | in 36 | pkgs.stdenv.mkDerivation rec { 37 | name = "ql2nix-${version}"; 38 | version = "1.0.0"; 39 | src = ./.; 40 | buildInputs = [ 41 | clwrapper 42 | ]; 43 | buildPhase = '' 44 | ASDF_OUTPUT_TRANSLATIONS="(:output-translations :ignore-inherited-configuration (t \"$(pwd)\"))" 45 | export ASDF_OUTPUT_TRANSLATIONS 46 | NIX_LISP_SKIP_CODE=1 source "${clwrapper}/bin/common-lisp.sh" || true 47 | "${clwrapper}/bin/common-lisp.sh" "$NIX_LISP_LOAD_FILE" "${build}" 48 | ''; 49 | installPhase = '' 50 | mkdir -p "$out/bin" 51 | cp ql2nix "$out/bin" 52 | ''; 53 | dontStrip = true; 54 | } 55 | --------------------------------------------------------------------------------