Last active
January 1, 2022 09:38
-
-
Save tttuuu888/33813aa720df759c1563d1d7c7a8cd68 to your computer and use it in GitHub Desktop.
A script that installs all Emacs external packages maintained by `use-package ' in parallel.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; pinstall.el --- Parallel package install script | |
;; Copyright (C) 2019 SeungKi Kim | |
;; Author: SeungKi Kim <[email protected]> | |
;; URL: https://github.com/tttuuu888/.emacs.d | |
;; Version: 0.7.0 | |
;;; Commentary | |
;; This script installs all external packages managed by `use-package' as | |
;; parallel processes. | |
;; Usage: | |
;; To start `pinstall' from shell, use below command: | |
;; | |
;; $ emacs -l ~/.emacs.d/pinstall.el -batch -init | |
;; | |
;; To start `pinstall' directly in emacs, use as below: | |
;; | |
;; (require 'pinstall) | |
;; (pinstall-init) | |
;;; Code: | |
(defvar min-number-of-process 4) | |
(defconst pinstall-file load-file-name) | |
(defun get-proper-process-number () | |
(let* ((ret (shell-command-to-string "grep processor /proc/cpuinfo")) | |
(num (if (equal ret "") | |
min-number-of-process | |
(* 2 (1+ (string-to-number (car (last (split-string ret))))))))) | |
(max num min-number-of-process))) | |
(defun package-archives-init () | |
(setq package-archives | |
'(("gnu" . "https://elpa.gnu.org/packages/") | |
("melpa" . "https://melpa.org/packages/"))) | |
(package-initialize)) | |
(defun get-package-list () | |
(let ((package-list (list 'use-package))) | |
(defmacro use-package (pkg &rest args) | |
(let ((ensure (memq :ensure args)) | |
(disabled (memq :disabled args)) | |
(always-ensure (bound-and-true-p use-package-always-ensure))) | |
(unless (or disabled | |
(if always-ensure | |
(and ensure (eq nil (cadr ensure))) | |
(or (not ensure) (eq nil (cadr ensure))))) | |
`(add-to-list 'package-list ',pkg)))) | |
(provide 'use-package) | |
(cl-letf (((symbol-function 'package-install) | |
(lambda (&rest _) nil)) | |
((symbol-function 'package-refresh-contents) | |
(lambda (&rest _)) nil)) | |
(load "~/.emacs.d/init.el")) | |
(package-archives-init) | |
package-list)) | |
(defun remove-duplicate-packages-in-depth (packages &optional depth) | |
(let* ((result packages) | |
(base-depth (if depth depth 0)) | |
(base-packages (nth base-depth packages))) | |
(if (< base-depth (length packages)) | |
(progn | |
(dotimes (dep (length packages)) | |
(when (> dep base-depth) | |
(let ((target-packages (nth dep result))) | |
(mapcar (lambda (x) | |
(setq target-packages (delq x target-packages))) | |
base-packages) | |
(setcar (nthcdr dep result) target-packages)))) | |
(remove-duplicate-packages-in-depth result (1+ base-depth))) | |
packages))) | |
(defun get-dependency-package-list () | |
(let* ((all-package-list (get-package-list)) | |
(dep-packages all-package-list) | |
(no-dep-packages (list)) | |
(result (list (seq-filter (lambda (x) (not (package-installed-p x))) | |
all-package-list)))) | |
(while dep-packages | |
(let ((deps | |
(cl-delete-duplicates | |
(mapcan | |
(lambda (pkg) | |
(let* ((desc (car (cdr (assq pkg package-archive-contents)))) | |
(reqs (when desc | |
(seq-filter | |
(lambda (x) (not (package-installed-p x))) | |
(mapcar #'car (package-desc-reqs desc)))))) | |
(when (and (not reqs) (not (package-installed-p pkg))) | |
(add-to-list 'no-dep-packages pkg)) | |
reqs)) | |
dep-packages)))) | |
(setq dep-packages deps) | |
(when deps (add-to-list 'result deps)))) | |
(when (or result no-dep-packages) | |
(setcar (nthcdr 0 result) (cl-delete-duplicates | |
(append no-dep-packages (car result))))) | |
result)) | |
(defun print-package-installed (package) | |
(let* ((pkg-len (length package)) | |
(padding (make-string (max 1 (- 25 pkg-len)) ?.))) | |
(message (format "%s %s..done." package padding)))) | |
(defun packages-installed-p (remained-packages) | |
(let ((pkg-list remained-packages) | |
(pkg-dirs (directory-files package-user-dir))) | |
(dolist (pkg pkg-list) | |
(let ((directory | |
(car (seq-filter | |
(lambda (x) (string= pkg (car (split-string x "-[0-9]")))) | |
pkg-dirs)))) | |
(when (and directory | |
(seq-filter | |
(lambda (f) (string-match "\.elc$" f)) | |
(directory-files | |
(concat (file-name-as-directory package-user-dir) | |
directory)))) | |
(print-package-installed pkg) | |
(setq remained-packages (remove pkg remained-packages)))))) | |
remained-packages) | |
(defun init-process-check (package-list proc-list) | |
(let ((remained-packages (mapcar #'symbol-name package-list))) | |
(while (seq-filter (lambda (proc) (process-live-p proc)) proc-list) | |
(setq remained-packages (packages-installed-p remained-packages)) | |
(sleep-for 0.5)) | |
(packages-installed-p remained-packages))) | |
(defun async-install-packages (package-list) | |
(let* ((process-number (get-proper-process-number)) | |
(default-args-len (1+ (/ (length package-list) process-number))) | |
(remained-packages package-list) | |
(output-buffer (generate-new-buffer "*install-packages*")) | |
(proc-list nil)) | |
(while remained-packages | |
(let* ((args-len (min default-args-len (length remained-packages))) | |
(packages (mapcar #'symbol-name | |
(cl-subseq remained-packages 0 args-len)))) | |
(setq remained-packages (nthcdr args-len remained-packages)) | |
(apply | |
#'start-process | |
"Install" | |
output-buffer | |
"emacs" "-l" | |
pinstall-file | |
"-batch" "-install" | |
packages) | |
(add-to-list 'proc-list (get-buffer-process output-buffer)))) | |
(init-process-check package-list proc-list) | |
;; (with-current-buffer output-buffer | |
;; (message (buffer-substring 1 (point-max)))) | |
)) | |
(defun init-function (&rest _) | |
(delete "-init" command-line-args) | |
(package-archives-init) | |
(package-refresh-contents) | |
(let* ((deps-list (get-dependency-package-list)) | |
(packages-list (remove-duplicate-packages-in-depth deps-list)) | |
(all-packages-count (length (apply #'append packages-list)))) | |
;; (print packages-list) | |
(if (zerop all-packages-count) | |
(message (format "\nAll packages are already installed.\n")) | |
(message (format "\n%s packages will be installed.\n" all-packages-count)) | |
(dolist (packages packages-list) | |
(async-install-packages packages))) | |
(message "Init done."))) | |
(defun install-function (&rest r) | |
(delete "-install" command-line-args) | |
(package-archives-init) | |
(dolist (pkg command-line-args-left) | |
(dotimes (try-count 2) | |
(print (format "try %s for %s" try-count pkg)) | |
(package-install (intern pkg) t)))) | |
(add-to-list 'command-switch-alist '("-install" . install-function)) | |
(add-to-list 'command-switch-alist '("-init" . init-function)) | |
;;;###autoload | |
(defun pinstall-init () | |
(let ((output-buffer (generate-new-buffer "*Init*"))) | |
(switch-to-buffer output-buffer) | |
(call-process "emacs" nil output-buffer t | |
"-l" pinstall-file "-batch" "-init"))) | |
(provide 'pinstall) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment