Merge pull request #268619 from tweag/lib-descriptions
[NixPkgs.git] / pkgs / development / lisp-modules-obsolete / quicklisp-to-nix / system-info.lisp
blobaf8d450272c054156371694b7eb7a798de3cab98
1 (unless (find-package :ql-to-nix-util)
2 (load "util.lisp"))
3 (unless (find-package :ql-to-nix-quicklisp-bootstrap)
4 (load "quicklisp-bootstrap.lisp"))
5 (defpackage :ql-to-nix-system-info
6 (:use :common-lisp :ql-to-nix-quicklisp-bootstrap :ql-to-nix-util)
7 (:export #:dump-image))
8 (in-package :ql-to-nix-system-info)
10 (eval-when (:compile-toplevel :load-toplevel :execute)
11 (defparameter *implementation-systems*
12 (append
13 #+sbcl(list :sb-posix :sb-bsd-sockets :sb-rotate-byte :sb-cltl2
14 :sb-introspect :sb-rt :sb-concurrency)))
15 (mapcar (function require) *implementation-systems*))
17 (declaim (optimize (debug 3) (speed 0) (space 0) (compilation-speed 0) (safety 3)))
19 ;; This file cannot have any dependencies beyond quicklisp and asdf.
20 ;; Otherwise, we'll miss some dependencies!
22 ;; (Implementation-provided dependencies are special, though)
24 ;; We can't load quicklisp until runtime (at which point we'll create
25 ;; an isolated quicklisp installation). These wrapper functions are
26 ;; nicer than funcalling intern'd symbols every time we want to talk
27 ;; to quicklisp.
28 (wrap :ql apply-load-strategy)
29 (wrap :ql compute-load-strategy)
30 (wrap :ql show-load-strategy)
31 (wrap :ql quicklisp-systems)
32 (wrap :ql ensure-installed)
33 (wrap :ql quicklisp-releases)
34 (wrap :ql-dist archive-md5)
35 (wrap :ql-dist archive-url)
36 (wrap :ql-dist ensure-local-archive-file)
37 (wrap :ql-dist find-system)
38 (wrap :ql-dist local-archive-file)
39 (wrap :ql-dist name)
40 (wrap :ql-dist provided-systems)
41 (wrap :ql-dist release)
42 (wrap :ql-dist short-description)
43 (wrap :ql-dist system-file-name)
44 (wrap :ql-impl-util call-with-quiet-compilation)
46 (defvar *version* (uiop:getenv "version")
47 "The version number of this program")
49 (defvar *main-system* nil
50 "The name of the system we're trying to extract info from.")
52 (defvar *found-parasites* (make-hash-table :test #'equalp)
53 "Names of systems which have been identified as parasites.
55 A system is parasitic if its name doesn't match the name of the file
56 it is defined in. So, for example, if foo and foo-bar are both
57 defined in a file named foo.asd, foo would be the host system and
58 foo-bar would be a parasitic system.
60 Parasitic systems are not generally loaded without loading the host
61 system first.
63 Keys are system names. Values are unspecified.")
65 (defvar *found-dependencies* (make-hash-table :test #'equalp)
66 "Hash table containing the set of dependencies discovered while installing a system.
68 Keys are system names. Values are unspecified.")
70 (defun decode-asdf-dependency (name)
71 "Translates an asdf system dependency description into a system name.
73 For example, translates (:version :foo \"1.0\") into \"foo\"."
74 (etypecase name
75 (symbol
76 (setf name (symbol-name name)))
77 (string)
78 (cons
79 (ecase (first name)
80 (:version
81 (warn "Discarding version information ~A" name)
82 ;; There's nothing we can do about this. If the version we
83 ;; have around is good enough, then we're golden. If it isn't
84 ;; good enough, then we'll error out and let a human figure it
85 ;; out.
86 (setf name (second name))
87 (return-from decode-asdf-dependency
88 (decode-asdf-dependency name)))
90 (:feature
91 (if (find (second name) *features*)
92 (return-from decode-asdf-dependency
93 (decode-asdf-dependency (third name)))
94 (progn
95 (warn "Dropping dependency due to missing feature: ~A" name)
96 (return-from decode-asdf-dependency nil))))
98 (:require
99 ;; This probably isn't a dependency we can satisfy using
100 ;; quicklisp, but we might as well try anyway.
101 (return-from decode-asdf-dependency
102 (decode-asdf-dependency (second name)))))))
103 (string-downcase name))
105 (defun found-new-parasite (system-name)
106 "Record that the given system has been identified as a parasite."
107 (setf system-name (decode-asdf-dependency system-name))
108 (setf (gethash system-name *found-parasites*) t)
109 (when (nth-value 1 (gethash system-name *found-dependencies*))
110 (error "Found dependency on parasite")))
112 (defun known-parasite-p (system-name)
113 "Have we previously identified this system as a parasite?"
114 (nth-value 1 (gethash system-name *found-parasites*)))
116 (defun found-parasites ()
117 "Return a vector containing all identified parasites."
118 (let ((systems (make-array (hash-table-size *found-parasites*) :fill-pointer 0)))
119 (loop :for system :being :the :hash-keys :of *found-parasites* :do
120 (vector-push system systems))
121 systems))
123 (defvar *track-dependencies* nil
124 "When this variable is nil, found-new-dependency will not record
125 depdendencies.")
127 (defun parasitic-relationship-p (potential-host potential-parasite)
128 "Returns t if potential-host and potential-parasite have a parasitic relationship.
130 See `*found-parasites*'."
131 (let ((host-ql-system (find-system potential-host))
132 (parasite-ql-system (find-system potential-parasite)))
133 (and host-ql-system parasite-ql-system
134 (not (equal (name host-ql-system)
135 (name parasite-ql-system)))
136 (equal (system-file-name host-ql-system)
137 (system-file-name parasite-ql-system)))))
139 (defun found-new-dependency (name)
140 "Record that the given system has been identified as a dependency.
142 The named system may not be recorded as a dependency. It may be left
143 out for any number of reasons. For example, if `*track-dependencies*'
144 is nil then this function does nothing. If the named system isn't a
145 quicklisp system, this function does nothing."
146 (setf name (decode-asdf-dependency name))
147 (unless name
148 (return-from found-new-dependency))
149 (unless *track-dependencies*
150 (return-from found-new-dependency))
151 (when (known-parasite-p name)
152 (return-from found-new-dependency))
153 (when (parasitic-relationship-p *main-system* name)
154 (found-new-parasite name)
155 (return-from found-new-dependency))
156 (unless (find-system name)
157 (return-from found-new-dependency))
158 (setf (gethash name *found-dependencies*) t))
160 (defun forget-dependency (name)
161 "Whoops. Did I say that was a dependency? My bad.
163 Be very careful using this function! You can remove a system from the
164 dependency list, but you can't remove other effects associated with
165 this system. For example, transitive dependencies might still be in
166 the dependency list."
167 (setf name (decode-asdf-dependency name))
168 (remhash name *found-dependencies*))
170 (defun found-dependencies ()
171 "Return a vector containing all identified dependencies."
172 (let ((systems (make-array (hash-table-size *found-dependencies*) :fill-pointer 0)))
173 (loop :for system :being :the :hash-keys :of *found-dependencies* :do
174 (vector-push system systems))
175 systems))
177 (defun host-system (system-name)
178 "If the given system is a parasite, return the name of the system that is its host.
180 See `*found-parasites*'."
181 (let* ((system (find-system system-name))
182 (host-file (system-file-name system)))
183 (unless (equalp host-file system-name)
184 host-file)))
186 (defun get-loaded (system)
187 "Try to load the named system using quicklisp and record any
188 dependencies quicklisp is aware of.
190 Unlike `our-quickload', this function doesn't attempt to install
191 missing dependencies."
192 ;; Let's get this party started!
193 (let* ((strategy (compute-load-strategy system))
194 (ql-systems (quicklisp-systems strategy)))
195 (dolist (dep ql-systems)
196 (found-new-dependency (name dep)))
197 (show-load-strategy strategy)
198 (labels
199 ((make-go ()
200 (apply-load-strategy strategy)))
201 (call-with-quiet-compilation #'make-go)
202 (let ((asdf-system (asdf:find-system system)))
203 ;; If ASDF says that it needed a system, then we should
204 ;; probably track that.
205 (dolist (asdf-dep (asdf:component-sideway-dependencies asdf-system))
206 (found-new-dependency asdf-dep))
207 (dolist (asdf-dep (asdf:system-defsystem-depends-on asdf-system))
208 (found-new-dependency asdf-dep))))))
210 (defun our-quickload (system)
211 "Attempt to install a package like quicklisp would, but record any
212 dependencies that are detected during the install."
213 (setf system (string-downcase system))
214 ;; Load it quickly, but do it OUR way. Turns out our way is very
215 ;; similar to the quicklisp way...
216 (let ((already-tried (make-hash-table :test #'equalp))) ;; Case insensitive
217 (tagbody
218 retry
219 (handler-case
220 (get-loaded system)
221 (asdf/find-component:missing-dependency (e)
222 (let ((required-by (asdf/find-component:missing-required-by e))
223 (missing (asdf/find-component:missing-requires e)))
224 (unless (typep required-by 'asdf:system)
225 (error e))
226 (when (gethash missing already-tried)
227 (error "Dependency loop? ~A" missing))
228 (setf (gethash missing already-tried) t)
229 (let ((parasitic-p (parasitic-relationship-p *main-system* missing)))
230 (if parasitic-p
231 (found-new-parasite missing)
232 (found-new-dependency missing))
233 ;; We always want to track the dependencies of systems
234 ;; that share an asd file with the main system. The
235 ;; whole asd file should be loadable. Otherwise, we
236 ;; don't want to include transitive dependencies.
237 (let ((*track-dependencies* parasitic-p))
238 (our-quickload missing)))
239 (format t "Attempting to load ~A again~%" system)
240 (go retry)))))))
242 (defvar *blacklisted-parasites*
243 #("hu.dwim.stefil/documentation" ;; This system depends on :hu.dwim.stefil.test, but it should depend on hu.dwim.stefil/test
244 "named-readtables/doc" ;; Dependency cycle between named-readtabes and mgl-pax
245 "symbol-munger-test" ;; Dependency cycle between lisp-unit2 and symbol-munger
246 "cl-postgres-simple-date-tests" ;; Dependency cycle between cl-postgres and simple-date
247 "cl-containers/with-variates" ;; Symbol conflict between cl-variates:next-element, metabang.utilities:next-element
248 "serapeum/docs" ;; Weird issue with FUN-INFO redefinition
249 "spinneret/cl-markdown" ;; Weird issue with FUN-INFO redefinition
250 "spinneret/ps" ;; Weird issue with FUN-INFO redefinition
251 "spinneret/tests") ;; Weird issue with FUN-INFO redefinition
252 "A vector of systems that shouldn't be loaded by `quickload-parasitic-systems'.
254 These systems are known to be troublemakers. In some sense, all
255 parasites are troublemakers (you shouldn't define parasitic systems!).
256 However, these systems prevent us from generating nix packages and are
257 thus doubly evil.")
259 (defvar *blacklisted-parasites-table*
260 (let ((ht (make-hash-table :test #'equalp)))
261 (loop :for system :across *blacklisted-parasites* :do
262 (setf (gethash system ht) t))
264 "A hash table where each entry in `*blacklisted-parasites*' is an
265 entry in the table.")
267 (defun blacklisted-parasite-p (system-name)
268 "Returns non-nil if the named system is blacklisted"
269 (nth-value 1 (gethash system-name *blacklisted-parasites-table*)))
271 (defun quickload-parasitic-systems (system)
272 "Attempt to load all the systems defined in the same asd as the named system.
274 Blacklisted systems are skipped. Dependencies of the identified
275 parasitic systems will be tracked."
276 (let* ((asdf-system (asdf:find-system system))
277 (source-file (asdf:system-source-file asdf-system)))
278 (cond
279 (source-file
280 (loop :for system-name :being :the :hash-keys :of asdf/find-system::*registered-systems* :do
281 ; for an unclear reason, a literal 0 which is not a key in the hash table gets observed
282 (when (and (gethash system-name asdf/find-system::*registered-systems*)
283 (parasitic-relationship-p system system-name)
284 (not (blacklisted-parasite-p system-name)))
285 (found-new-parasite system-name)
286 (let ((*track-dependencies* t))
287 (our-quickload system-name)))))
289 (unless (or (equal "uiop" system)
290 (equal "asdf" system))
291 (warn "No source file for system ~A. Can't identify parasites." system))))))
293 (defun determine-dependencies (system)
294 "Load the named system and return a sorted vector containing all the
295 quicklisp systems that were loaded to satisfy dependencies.
297 This function should probably only be called once per process!
298 Subsequent calls will miss dependencies identified by earlier calls."
299 (tagbody
300 retry
301 (restart-case
302 (let ((*standard-output* (make-broadcast-stream))
303 (*trace-output* (make-broadcast-stream))
304 (*main-system* system)
305 (*track-dependencies* t))
306 (our-quickload system)
307 (quickload-parasitic-systems system))
308 (try-again ()
309 :report "Start the quickload over again"
310 (go retry))
311 (die ()
312 :report "Just give up and die"
313 (uiop:quit 1))))
315 ;; Systems can't depend on themselves!
316 (forget-dependency system)
317 (values))
319 (defun parasitic-system-data (parasite-system)
320 "Return a plist of information about the given known-parastic system.
322 Sometimes we are asked to provide information about a system that is
323 actually a parasite. The only correct response is to point them
324 toward the host system. The nix package for the host system should
325 have all the dependencies for this parasite already recorded.
327 The plist is only meant to be consumed by other parts of
328 quicklisp-to-nix."
329 (let ((host-system (host-system parasite-system)))
330 (list
331 :system parasite-system
332 :host host-system
333 :name (string-downcase (format nil "~a" parasite-system))
334 :host-name (string-downcase (format nil "~a" host-system)))))
336 (defun system-data (system)
337 "Produce a plist describing a system.
339 The plist is only meant to be consumed by other parts of
340 quicklisp-to-nix."
341 (when (host-system system)
342 (return-from system-data
343 (parasitic-system-data system)))
345 (determine-dependencies system)
346 (let*
347 ((dependencies (sort (found-dependencies) #'string<))
348 (parasites (coerce (sort (found-parasites) #'string<) 'list))
349 (ql-system (find-system system))
350 (ql-release (release ql-system))
351 (ql-sibling-systems (provided-systems ql-release))
352 (url (archive-url ql-release))
353 (local-archive (local-archive-file ql-release))
354 (local-url (format nil "file://~a" (pathname local-archive)))
355 (archive-data
356 (progn
357 (ensure-local-archive-file ql-release)
358 ;; Stuff this archive into the nix store. It was almost
359 ;; certainly going to end up there anyway (since it will
360 ;; probably be fetchurl'd for a nix package). Also, putting
361 ;; it into the store also gives us the SHA we need.
362 (nix-prefetch-url local-url)))
363 (ideal-md5 (archive-md5 ql-release))
364 (raw-dependencies (coerce dependencies 'list))
365 (name (string-downcase (format nil "~a" system)))
366 (ql-sibling-names
367 (remove name (mapcar 'name ql-sibling-systems)
368 :test 'equal))
369 (dependencies raw-dependencies)
370 (description
372 (ignore-errors (asdf:system-description (asdf:find-system system)))
373 "System lacks description"))
374 (release-name (short-description ql-release)))
375 (list
376 :system system
377 :description description
378 :sha256 (getf archive-data :sha256)
379 :url url
380 :md5 ideal-md5
381 :name name
382 :dependencies dependencies
383 :siblings ql-sibling-names
384 :release-name release-name
385 :parasites parasites)))
387 (defvar *error-escape-valve* *error-output*
388 "When `*error-output*' is rebound to inhibit spew, this stream will
389 still produce output.")
391 (defun print-usage-and-quit ()
392 "Describe how to use this program... and then exit."
393 (format *error-output* "Usage:
394 ~A [--cacheDir <dir>] [--silent] [--debug] [--help|-h] <system-name>
395 Arguments:
396 --cacheDir Store (and look for) compiled lisp files in the given directory
397 --verbose Show compilation output
398 --debug Enter the debugger when a fatal error is encountered
399 --help Print usage and exit
400 <system-name> The quicklisp system to examine
401 " (or (uiop:argv0) "quicklisp-to-nix-system-info"))
402 (uiop:quit 2))
404 (defun main ()
405 "Make it go."
406 (let ((argv (uiop:command-line-arguments))
407 cache-dir
408 target-system
409 verbose-p
410 debug-p)
411 (handler-bind
412 ((warning
413 (lambda (w)
414 (format *error-escape-valve* "~A~%" w)))
415 (error
416 (lambda (e)
417 (if debug-p
418 (invoke-debugger e)
419 (progn
420 (format *error-escape-valve* "~
421 Failed to extract system info. Details are below. ~
422 Run with --debug and/or --verbose for more info.
423 ~A~%" e)
424 (uiop:quit 1))))))
425 (loop :while argv :do
426 (cond
427 ((equal "--cacheDir" (first argv))
428 (pop argv)
429 (unless argv
430 (error "--cacheDir expects an argument"))
431 (setf cache-dir (first argv))
432 (pop argv))
434 ((equal "--verbose" (first argv))
435 (setf verbose-p t)
436 (pop argv))
438 ((equal "--debug" (first argv))
439 (setf debug-p t)
440 (pop argv))
442 ((or (equal "--help" (first argv))
443 (equal "-h" (first argv)))
444 (print-usage-and-quit))
447 (setf target-system (pop argv))
448 (when argv
449 (error "Can only operate on one system")))))
451 (unless target-system
452 (print-usage-and-quit))
454 (when cache-dir
455 (setf cache-dir (pathname-as-directory (parse-namestring cache-dir))))
457 (mapcar (function require) *implementation-systems*)
459 (with-quicklisp (dir) (:cache-dir (or cache-dir :temp))
460 (declare (ignore dir))
462 (let (system-data)
463 (let ((*error-output* (if verbose-p
464 *error-output*
465 (make-broadcast-stream)))
466 (*standard-output* (if verbose-p
467 *standard-output*
468 (make-broadcast-stream)))
469 (*trace-output* (if verbose-p
470 *trace-output*
471 (make-broadcast-stream))))
472 (format *error-output*
473 "quicklisp-to-nix-system-info ~A~%ASDF ~A~%Quicklisp ~A~%Compiler ~A ~A~%"
474 *version*
475 (asdf:asdf-version)
476 (funcall (intern "CLIENT-VERSION" :ql))
477 (lisp-implementation-type)
478 (lisp-implementation-version))
479 (setf system-data (system-data target-system)))
481 (cond
482 (system-data
483 (format t "~W~%" system-data)
484 (uiop:quit 0))
486 (format *error-output* "Failed to determine system data~%")
487 (uiop:quit 1))))))))
489 (defun dump-image ()
490 "Make an executable"
491 (setf uiop:*image-entry-point* #'main)
492 (setf uiop:*lisp-interaction* nil)
493 (uiop:dump-image "quicklisp-to-nix-system-info" :executable t))