nixpkgs/pkgs/development/lisp-modules/import/repository/quicklisp.lisp
2023-07-14 21:37:56 +02:00

200 lines
8.1 KiB
Common Lisp

(defpackage org.lispbuilds.nix/repository/quicklisp
(:use :cl)
(:import-from :dex)
(:import-from :alexandria :read-file-into-string :ensure-list)
(:import-from :arrow-macros :->>)
(:import-from :str)
(:import-from
:org.lispbuilds.nix/database/sqlite
:sqlite-database
:init-db
:database-url
:init-file)
(:import-from
:org.lispbuilds.nix/api
:import-lisp-packages)
(:import-from
:org.lispbuilds.nix/util
:replace-regexes)
(:export :quicklisp-repository)
(:local-nicknames
(:json :com.inuoe.jzon)))
(in-package org.lispbuilds.nix/repository/quicklisp)
(defclass quicklisp-repository ()
((dist-url :initarg :dist-url
:reader dist-url
:initform (error "dist url required"))))
(defun clear-line ()
(write-char #\Return *error-output*)
(write-char #\Escape *error-output*)
(write-char #\[ *error-output*)
(write-char #\K *error-output*))
(defun status (&rest format-args)
(clear-line)
(apply #'format (list* *error-output* format-args))
(force-output *error-output*))
;; TODO: This should not know about the imported.nix file.
(defun init-tarball-hashes (database)
(status "no packages.sqlite - will pre-fill tarball hashes from ~A to save time~%"
(truename "imported.nix"))
(let* ((lines (uiop:read-file-lines "imported.nix"))
(lines (remove-if-not
(lambda (line)
(let ((trimmed (str:trim-left line)))
(or (str:starts-with-p "url = " trimmed)
(str:starts-with-p "sha256 = " trimmed))))
lines))
(lines (mapcar
(lambda (line)
(multiple-value-bind (whole groups)
(ppcre:scan-to-strings "\"\(.*\)\"" line)
(declare (ignore whole))
(svref groups 0)))
lines)))
(sqlite:with-open-database (db (database-url database))
(init-db db (init-file database))
(sqlite:with-transaction db
(loop while lines do
(sqlite:execute-non-query db
"insert or ignore into sha256(url,hash) values (?,?)"
(prog1 (first lines) (setf lines (rest lines)))
(prog1 (first lines) (setf lines (rest lines))))))
(status "OK, imported ~A hashes into DB.~%"
(sqlite:execute-single db
"select count(*) from sha256")))))
(defmethod import-lisp-packages ((repository quicklisp-repository)
(database sqlite-database))
;; If packages.sqlite is missing, we should populate the sha256
;; table to speed things up.
(unless (probe-file (database-url database))
(init-tarball-hashes database))
(let* ((db (sqlite:connect (database-url database)))
(systems-url (str:concat (dist-url repository) "systems.txt"))
(releases-url (str:concat (dist-url repository) "releases.txt"))
(systems-lines (rest (butlast (str:split #\Newline (dex:get systems-url)))))
(releases-lines (rest (butlast (str:split #\Newline (dex:get releases-url))))))
(flet ((sql-query (sql &rest params)
(apply #'sqlite:execute-to-list (list* db sql params))))
;; Ensure database schema
(init-db db (init-file database))
;; Prepare temporary tables for efficient access
(sql-query "create temp table if not exists quicklisp_system
(project, asd, name unique, deps)")
(sql-query "create temp table if not exists quicklisp_release
(project unique, url, size, md5, sha1, prefix not null, asds)")
(sqlite:with-transaction db
(dolist (line systems-lines)
(destructuring-bind (project asd name &rest deps)
(str:words line)
(sql-query
"insert or ignore into quicklisp_system values(?,?,?,?)"
project asd name (json:stringify (coerce deps 'vector))))))
(sqlite:with-transaction db
(dolist (line releases-lines)
(destructuring-bind (project url size md5 sha1 prefix &rest asds)
(str:words line)
(sql-query
"insert or ignore into quicklisp_release values(?,?,?,?,?,?,?)"
project url size md5 sha1 prefix (json:stringify (coerce
asds
'vector))))))
(sqlite:with-transaction db
;; Should these be temp tables, that then get queried by
;; system name? This looks like it uses a lot of memory.
(let ((systems
(sql-query
"with pkgs as (
select
name, asd, url, deps,
ltrim(replace(prefix, r.project, ''), '-_') as version
from quicklisp_system s, quicklisp_release r
where s.project = r.project
)
select
name, version, asd, url,
(select json_group_array(
json_array(value, (select version from pkgs where name=value))
)
from json_each(deps)
where value <> 'asdf') as deps
from pkgs"
)))
;; First pass: insert system and source tarball informaton.
;; Can't insert dependency information, because this works
;; on system ids in the database and they don't exist
;; yet. Could it be better to just base dependencies on
;; names? But then ACID is lost.
(dolist (system systems)
(destructuring-bind (name version asd url deps) system
(declare (ignore deps))
(status "importing system '~a-~a'" name version)
(let ((hash (nix-prefetch-tarball url db)))
(sql-query
"insert or ignore into system(name,version,asd) values (?,?,?)"
name version asd)
(sql-query
"insert or ignore into sha256(url,hash) values (?,?)"
url hash)
(sql-query
"insert or ignore into src values
((select id from sha256 where url=?),
(select id from system where name=? and version=?))"
url name version))))
;; Second pass: connect the in-database systems with
;; dependency information
(dolist (system systems)
(destructuring-bind (name version asd url deps) system
(declare (ignore asd url))
(dolist (dep (coerce (json:parse deps) 'list))
(destructuring-bind (dep-name dep-version) (coerce dep 'list)
(if (eql dep-version 'NULL)
(warn "Bad data in Quicklisp: ~a has no version" dep-name)
(sql-query
"insert or ignore into dep values
((select id from system where name=? and version=?),
(select id from system where name=? and version=?))"
name version
dep-name dep-version))))))))))
(write-char #\Newline *error-output*))
(defun shell-command-to-string (cmd)
;; Clearing the library path is needed to prevent a bug, where the
;; called subprocess uses a different glibc than the SBCL process
;; is. In that case, the call to execve attempts to load the
;; libraries used by SBCL from LD_LIBRARY_PATH using a different
;; glibc than they expect, which errors out.
(let ((ld-library-path (uiop:getenv "LD_LIBRARY_PATH")))
(setf (uiop:getenv "LD_LIBRARY_PATH") "")
(unwind-protect
(uiop:run-program cmd :output '(:string :stripped t))
(setf (uiop:getenv "LD_LIBRARY_PATH") ld-library-path))))
(defun nix-prefetch-tarball (url db)
(restart-case
(compute-sha256 url db)
(try-again ()
:report "Try downloading again"
(nix-prefetch-tarball url db))))
(defun compute-sha256 (url db)
(or (sqlite:execute-single db "select hash from sha256 where url=?" url)
(let ((sha256 (shell-command-to-string (str:concat "nix-prefetch-url --unpack " url))))
sha256)))