1 (defpackage org.lispbuilds.nix
/repository
/quicklisp
4 (:import-from
:alexandria
:read-file-into-string
:ensure-list
)
5 (:import-from
:arrow-macros
:-
>>)
8 :org.lispbuilds.nix
/database
/sqlite
14 :org.lispbuilds.nix
/api
15 :import-lisp-packages
)
17 :org.lispbuilds.nix
/util
19 (:export
:quicklisp-repository
)
21 (:json
:com.inuoe.jzon
)))
23 (in-package org.lispbuilds.nix
/repository
/quicklisp
)
25 (defclass quicklisp-repository
()
26 ((dist-url :initarg
:dist-url
28 :initform
(error "dist url required"))))
31 (write-char #\Return
*error-output
*)
32 (write-char #\Escape
*error-output
*)
33 (write-char #\
[ *error-output
*)
34 (write-char #\K
*error-output
*))
36 (defun status (&rest format-args
)
38 (apply #'format
(list* *error-output
* format-args
))
39 (force-output *error-output
*))
41 ;; TODO: This should not know about the imported.nix file.
42 (defun init-tarball-hashes (database)
43 (status "no packages.sqlite - will pre-fill tarball hashes from ~A to save time~%"
44 (truename "imported.nix"))
45 (let* ((lines (uiop:read-file-lines
"imported.nix"))
48 (let ((trimmed (str:trim-left line
)))
49 (or (str:starts-with-p
"url = " trimmed
)
50 (str:starts-with-p
"sha256 = " trimmed
))))
54 (multiple-value-bind (whole groups
)
55 (ppcre:scan-to-strings
"\"\(.*\)\"" line
)
56 (declare (ignore whole
))
59 (sqlite:with-open-database
(db (database-url database
))
60 (init-db db
(init-file database
))
61 (sqlite:with-transaction db
63 (sqlite:execute-non-query db
64 "insert or ignore into sha256(url,hash) values (?,?)"
65 (prog1 (first lines
) (setf lines
(rest lines
)))
66 (prog1 (first lines
) (setf lines
(rest lines
))))))
67 (status "OK, imported ~A hashes into DB.~%"
68 (sqlite:execute-single db
69 "select count(*) from sha256")))))
71 (defmethod import-lisp-packages ((repository quicklisp-repository
)
72 (database sqlite-database
))
74 ;; If packages.sqlite is missing, we should populate the sha256
75 ;; table to speed things up.
76 (unless (probe-file (database-url database
))
77 (init-tarball-hashes database
))
79 (let* ((db (sqlite:connect
(database-url database
)))
80 (systems-url (str:concat
(dist-url repository
) "systems.txt"))
81 (releases-url (str:concat
(dist-url repository
) "releases.txt"))
82 (systems-lines (rest (butlast (str:split
#\Newline
(dex:get systems-url
)))))
83 (releases-lines (rest (butlast (str:split
#\Newline
(dex:get releases-url
))))))
85 (flet ((sql-query (sql &rest params
)
86 (apply #'sqlite
:execute-to-list
(list* db sql params
))))
88 ;; Ensure database schema
89 (init-db db
(init-file database
))
91 ;; Prepare temporary tables for efficient access
92 (sql-query "create temp table if not exists quicklisp_system
93 (project, asd, name unique, deps)")
95 (sql-query "create temp table if not exists quicklisp_release
96 (project unique, url, size, md5, sha1, prefix not null, asds)")
98 (sqlite:with-transaction db
99 (dolist (line systems-lines
)
100 (destructuring-bind (project asd name
&rest deps
)
103 "insert or ignore into quicklisp_system values(?,?,?,?)"
104 project asd name
(json:stringify
(coerce deps
'vector
))))))
106 (sqlite:with-transaction db
107 (dolist (line releases-lines
)
108 (destructuring-bind (project url size md5 sha1 prefix
&rest asds
)
111 "insert or ignore into quicklisp_release values(?,?,?,?,?,?,?)"
112 project url size md5 sha1 prefix
(json:stringify
(coerce
116 (sqlite:with-transaction db
117 ;; Should these be temp tables, that then get queried by
118 ;; system name? This looks like it uses a lot of memory.
123 name, asd, url, deps,
124 ltrim(replace(prefix, r.project, ''), '-_') as version
125 from quicklisp_system s, quicklisp_release r
126 where s.project = r.project
129 name, version, asd, url,
130 (select json_group_array(
131 json_array(value, (select version from pkgs where name=value))
134 where value <> 'asdf') as deps
138 ;; First pass: insert system and source tarball informaton.
139 ;; Can't insert dependency information, because this works
140 ;; on system ids in the database and they don't exist
141 ;; yet. Could it be better to just base dependencies on
142 ;; names? But then ACID is lost.
143 (dolist (system systems
)
144 (destructuring-bind (name version asd url deps
) system
145 (declare (ignore deps
))
146 (status "importing system '~a-~a'" name version
)
147 (let ((hash (nix-prefetch-tarball url db
)))
149 "insert or ignore into system(name,version,asd) values (?,?,?)"
152 "insert or ignore into sha256(url,hash) values (?,?)"
155 "insert or ignore into src values
156 ((select id from sha256 where url=?),
157 (select id from system where name=? and version=?))"
160 ;; Second pass: connect the in-database systems with
161 ;; dependency information
162 (dolist (system systems
)
163 (destructuring-bind (name version asd url deps
) system
164 (declare (ignore asd url
))
165 (dolist (dep (coerce (json:parse deps
) 'list
))
166 (destructuring-bind (dep-name dep-version
) (coerce dep
'list
)
167 (if (eql dep-version
'NULL
)
168 (warn "Bad data in Quicklisp: ~a has no version" dep-name
)
170 "insert or ignore into dep values
171 ((select id from system where name=? and version=?),
172 (select id from system where name=? and version=?))"
174 dep-name dep-version
))))))))))
176 (write-char #\Newline
*error-output
*))
178 (defun shell-command-to-string (cmd)
179 ;; Clearing the library path is needed to prevent a bug, where the
180 ;; called subprocess uses a different glibc than the SBCL process
181 ;; is. In that case, the call to execve attempts to load the
182 ;; libraries used by SBCL from LD_LIBRARY_PATH using a different
183 ;; glibc than they expect, which errors out.
184 (let ((ld-library-path (uiop:getenv
"LD_LIBRARY_PATH")))
185 (setf (uiop:getenv
"LD_LIBRARY_PATH") "")
187 (uiop:run-program cmd
:output
'(:string
:stripped t
))
188 (setf (uiop:getenv
"LD_LIBRARY_PATH") ld-library-path
))))
190 (defun nix-prefetch-tarball (url db
)
192 (compute-sha256 url db
)
194 :report
"Try downloading again"
195 (nix-prefetch-tarball url db
))))
197 (defun compute-sha256 (url db
)
198 (or (sqlite:execute-single db
"select hash from sha256 where url=?" url
)
199 (let ((sha256 (shell-command-to-string (str:concat
"nix-prefetch-url --unpack " url
))))