Jak automatycznie zainstalować pakiety Emacsa, określając listę nazw pakietów?

123

Używam packagedo zarządzania rozszerzeniami Emacsa. Aby zsynchronizować moje ustawienia Emacsa na różnych komputerach, chciałbym określić listę nazw pakietów w .emacspliku, a następnie packagemóc automatycznie wyszukiwać i instalować pakiety, aby nie musieć instalować ich ręcznie, dzwoniąc M-x package-list-packages. Jak to zrobić?

RNA
źródło
6
Jeśli polegasz na menedżerze pakietów w celu zainstalowania konfiguracji, prawdopodobnie będziesz chciał określić dokładne wersje (a jeśli to nie jest możliwe, rozważ samodzielne przechowywanie wszystkiego w kontroli wersji), ponieważ w przeciwnym razie nie jesteś chroniony, gdy biblioteki są aktualizowane i uruchamiane do konfliktu.
phils

Odpowiedzi:

107
; list the packages you want
(setq package-list '(package1 package2))

; list the repositories containing them
(setq package-archives '(("elpa" . "http://tromey.com/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")
                         ("marmalade" . "http://marmalade-repo.org/packages/")))

; activate all the packages (in particular autoloads)
(package-initialize)

; fetch the list of packages available 
(unless package-archive-contents
  (package-refresh-contents))

; install the missing packages
(dolist (package package-list)
  (unless (package-installed-p package)
    (package-install package)))
Nicolas Dudebout
źródło
7
Wolę: (lub (plik-istnieje-p-katalog-użytkownika-pakietu) (zawartość-odświeżenia-pakietu)) z zaakceptowanej odpowiedzi. Odświeżanie pakietu w tym miejscu wydłuża czas uruchamiania w systemach, w których pakiety są już zainstalowane. Reszta odpowiedzi jest jednak doskonała.
rfinz
Wartość symbolu jako zmiennej to void: zawartość-archiwum-pakietu. Czy jest jakiś sposób, żebym mógł zrobić listę w .emacs i użyć zdefiniowanej w niej funkcji, aby zainstalować wszystkie pakiety z listy (pomiń, jeśli jest zainstalowany, zaktualizuj, jeśli jest stary), jak Vundle dla Vima. Ponieważ nie chcę wypychać wszystkich pakietów w elpa / do github, muszę to robić za każdym razem, gdy pakiet jest aktualizowany w package.
CodyChan
Co masz na myśli @rfinz? Wygląda na to, package-refresh-contentsże zostanie uruchomiony tylko wtedy, gdy pakiet nie jest zainstalowany? Co jest (or (file-exists-p package-user-dir))lepsze / jak to w ogóle sprawdza, czy pakiety są zainstalowane?
Startec
@Startec tak, masz rację! Sprawdza, czy istnieje katalog pakietu użytkownika, a jeśli nie, to działa package-refresh-contents. Prawdopodobnie zostanie to uruchomione tylko przy pierwszym otwarciu emacsa na nowym komputerze i nie przeszkadza mi to. Jeśli pakiet wymaga aktualizacji, można to zrobić ręcznie.
rfinz
2
Jeśli już używasz use-package, możesz użyć :ensuresłowa kluczowego, aby automatycznie zainstalować pakiety. Jest to również konfigurowane, package-selected-packagesjeśli chcesz uzyskać dostęp do listy pakietów poprzez dostosowywanie lub programowo.
Nick McCurdy,
45

Na podstawie komentarzy Profpatscha i odpowiedzi poniżej:

(defun ensure-package-installed (&rest packages)
  "Assure every package is installed, ask for installation if it’s not.

Return a list of installed packages or nil for every skipped package."
  (mapcar
   (lambda (package)
     ;; (package-installed-p 'evil)
     (if (package-installed-p package)
         nil
       (if (y-or-n-p (format "Package %s is missing. Install it? " package))
           (package-install package)
         package)))
   packages))

;; make sure to have downloaded archive description.
;; Or use package-archive-contents as suggested by Nicolas Dudebout
(or (file-exists-p package-user-dir)
    (package-refresh-contents))

(ensure-package-installed 'iedit 'magit) ;  --> (nil nil) if iedit and magit are already installed

;; activate installed packages
(package-initialize)
RNA
źródło
2
Czy to… mapa z efektami ubocznymi? I nadużywanie lenistwa or? Och, wow.
Profpatsch,
1
Cóż, mapcdotyczy skutków ubocznych. Ale dlaczego nie użyć unless?
Profpatsch,
Wcześniej użyłem tego kodu i czasami z jakiegoś nieznanego powodu nie działał, mówiąc „Pakiet bla-bla nie jest dostępny do instalacji” (tutaj bla-bla jest zawsze pierwszym elementem listy). Jeśli zainstaluję pierwszy pakiet ręcznie, wszystko działa dobrze, ale to nie jest rozwiązanie. W każdym razie odpowiedź Nicolasa Dudebois działa dobrze.
avp
Potrzebowałem (package-initialize)przed odniesieniem dopackage-user-dir
Frank Henard
3
Więc gdzie właściwie mamy listę pakietów, które chcemy zainstalować?
Andriy Drozdyuk
41

Emacs 25.1+ automatycznie śledzi pakiety zainstalowane przez użytkownika w dostosowywalnej package-selected-packageszmiennej. package-installzaktualizuje zmienną dostosowywania i będzie można zainstalować wszystkie wybrane pakiety za pomocą tej package-install-selected-packagesfunkcji.

Inną wygodną zaletą tego podejścia jest to, że można go użyć package-autoremovedo automatycznego usuwania pakietów, które nie są uwzględnione package-selected-packages(choć zachowa zależności).

(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))
(package-install-selected-packages)

Źródło: http://endlessparentheses.com/new-in-package-el-in-emacs-25-1-user-selected-packages.html

Nick McCurdy
źródło
17

Oto kod, którego używam do Emacs Prelude :

(require 'package)
(require 'melpa)
(add-to-list 'package-archives
             '("melpa" . "http://melpa.milkbox.net/packages/") t)
(package-initialize)

(setq url-http-attempt-keepalives nil)

(defvar prelude-packages
  '(ack-and-a-half auctex clojure-mode coffee-mode deft expand-region
                   gist haml-mode haskell-mode helm helm-projectile inf-ruby
                   magit magithub markdown-mode paredit projectile
                   python sass-mode rainbow-mode scss-mode solarized-theme
                   volatile-highlights yaml-mode yari yasnippet zenburn-theme)
  "A list of packages to ensure are installed at launch.")

(defun prelude-packages-installed-p ()
  (loop for p in prelude-packages
        when (not (package-installed-p p)) do (return nil)
        finally (return t)))

(unless (prelude-packages-installed-p)
  ;; check for new packages (package versions)
  (message "%s" "Emacs Prelude is now refreshing its package database...")
  (package-refresh-contents)
  (message "%s" " done.")
  ;; install the missing packages
  (dolist (p prelude-packages)
    (when (not (package-installed-p p))
      (package-install p))))

(provide 'prelude-packages)

Jeśli nie używasz MELPY, nie musisz tego wymagać (a jeśli tak, to melpa.elmusi być na twoim load-path(lub zainstalowanym przez MELPA). Pakiet db nie jest odświeżany za każdym razem (ponieważ to znacznie spowolniłoby uruchamianie ) - tylko w przypadku obecności odinstalowanych pakietów.

Bozhidar Batsov
źródło
W oparciu o twoją odpowiedź nieco ją zmodyfikowałem i usunąłem użycie „pętli” github.com/slipset/emacs/blob/master/ensure-packages.el
podsumowanie
Tak, ten przykład jest naprawdę bardziej złożony, niż powinien. Kod, którego obecnie używam w Prelude jest znacznie prostszy.
Bozhidar Batsov
7

Nikt jeszcze nie wspomniał o Cask , ale nadaje się do tego zadania.

Zasadniczo tworzysz ~/.emacs.d/Casklistę pakietów, które chcesz zainstalować. Na przykład:

(source melpa)
(depends-on "expand-region")
(depends-on "goto-last-change")
; ... etc

Uruchomienie caskz wiersza poleceń spowoduje zainstalowanie tych pakietów i wszelkich potrzebnych im zależności.

Możesz także automatycznie aktualizować zainstalowane pakiety za pomocą cask update.

Alastair
źródło
Od jakiegoś czasu używam cask w moich dotfiles , działa świetnie.
Alastair
Pity Cask wydaje się wymagać Pythona. Zastanawiam się, czy istnieje alternatywa tylko dla elisp? (To jest w pakiecie; oczywiście odpowiedzi na tej stronie spełniają wymagania elisp).
Peter Jaric
1
Skrypt w Pythonie to cienkie opakowanie wokół cask-cli.el, które możesz wywołać bezpośrednio, jeśli chcesz:/path/to/emacs -Q --script /path/to/cask/cask-cli.el -- [args]
Alastair
Ciekawy! Czy nie można go używać z wnętrza Emacsa? Wydaje mi się, że to dlatego, że jest to również narzędzie deweloperskie, ale dość niezwykłe jest wyjście poza Emacsa do CLI, aby zarządzać Emacsem.
Peter Jaric
4

Zadzwoń package-installpodając nazwę pakietu jako symbol. Możesz znaleźć nazwy pakietów dla swoich pakietów, wywołując package-installinteraktywnie i uzupełniając nazwę. Funkcja package-installed-ppoinformuje Cię, czy została już zainstalowana.

Na przykład:

(mapc
 (lambda (package)
   (or (package-installed-p package)
       (package-install package)))
 '(package1 package2 package3))
ataylor
źródło
1
Dzięki, ale wyskoczył mi błąd error: Package dired + „nie można zainstalować”. dired + to pakiet, który wypróbowałem z twoim kodem.
RNA
Pojawia dired+się podczas biegania package-list-packages? Myślę, że będziesz musiał dodać do swojego marmoladę lub melpę package-archives. Jeśli tak, czy możesz biec (package-install 'dired+)?
ataylor
W takim przypadku (package-installed-p 'dired+)powinien powrócić ti zostanie pominięty w powyższym kodzie.
ataylor
Samo package-installed-pdziała dobrze, ale cały blok kodu nie. Wypróbowałem kilka pakietów.
RNA
2
Wygląda na to, że preludium w odpowiedzi Nicolasa Dudebouta rozwiąże ten problem.
ataylor
4
(require 'cl)
(require 'package)

(setq cfg-var:packages '(
       emmet-mode
       ergoemacs-mode
       flycheck
       flycheck-pyflakes
       monokai-theme
       py-autopep8
       py-isort
       rainbow-mode
       yafolding
       yasnippet))

(defun cfg:install-packages ()
    (let ((pkgs (remove-if #'package-installed-p cfg-var:packages)))
        (when pkgs
            (message "%s" "Emacs refresh packages database...")
            (package-refresh-contents)
            (message "%s" " done.")
            (dolist (p cfg-var:packages)
                (package-install p)))))

(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(package-initialize)

(cfg:install-packages)
Dunaevsky Maxim
źródło
3

Lubię sprawdzać, czy użytkownik chce najpierw zainstalować pakiety, jak to zrobiono w tej odpowiedzi . Odświeżam również zawartość pakietu przed zainstalowaniem czegokolwiek. Nie jestem pewien, czy to najlepszy sposób, ale nie sądzę, że najlepsze odpowiedzi robiły to za mnie.

(setq required-pkgs '(jedi flycheck cider clojure-mode paredit markdown-mode jsx-mode company))

(require 'cl)

(setq pkgs-to-install
      (let ((uninstalled-pkgs (remove-if 'package-installed-p required-pkgs)))
        (remove-if-not '(lambda (pkg) (y-or-n-p (format "Package %s is missing. Install it? " pkg))) uninstalled-pkgs)))

(when (> (length pkgs-to-install) 0)
  (package-refresh-contents)
  (dolist (pkg pkgs-to-install)
    (package-install pkg)))
Frank Henard
źródło
1

Pobiegłem na problem, że nic się nie stało, po dodaniu (package-install 'org)do .emacs. Chciałem zainstalować aktualną wersję programu, org-modea wbudowany org-modejest dość stary.

package-installWygrzebałem kod źródłowy z Emacsa 25.3.1. Funkcja self już sprawdza, czy pakiet jest zainstalowany, czy nie, i odmawia instalacji, jeśli pakiet jest już zainstalowany. Tak więc czek (unless (package-installed-p package) ...)z odpowiedzi 10093312 jest w rzeczywistości niewskazany .

(defun package-install (pkg &optional dont-select)
  "Install the package PKG.
PKG can be a package-desc or a symbol naming one of the available packages
in an archive in `package-archives'.  Interactively, prompt for its name.

If called interactively or if DONT-SELECT nil, add PKG to
`package-selected-packages'.

If PKG is a package-desc and it is already installed, don't try
to install it but still mark it as selected."
  (interactive
   (progn
     ;; Initialize the package system to get the list of package
     ;; symbols for completion.
     (unless package--initialized
       (package-initialize t))
     (unless package-archive-contents
       (package-refresh-contents))
     (list (intern (completing-read
                    "Install package: "
                    (delq nil
                          (mapcar (lambda (elt)
                                    (unless (package-installed-p (car elt))
                                      (symbol-name (car elt))))
                                  package-archive-contents))
                    nil t))
           nil)))
  (add-hook 'post-command-hook #'package-menu--post-refresh)
  (let ((name (if (package-desc-p pkg)
                  (package-desc-name pkg)
                pkg)))
    (unless (or dont-select (package--user-selected-p name))
      (package--save-selected-packages
       (cons name package-selected-packages)))
    (if-let ((transaction
              (if (package-desc-p pkg)
                  (unless (package-installed-p pkg)
                    (package-compute-transaction (list pkg)
                                                 (package-desc-reqs pkg)))
                (package-compute-transaction () (list (list pkg))))))
        (package-download-transaction transaction)
      (message "`%s' is already installed" name))))

Wbudowany org-moderównież liczy się jako zainstalowany i package-installodmawia zainstalowania nowszej wersji firmy ELPA. Po spędzeniu trochę czasu na czytaniu package.el wpadłem na następujące rozwiązanie.

(dolist (package (package-compute-transaction
                  () (list (list 'python '(0 25 1))
                           (list 'org '(20171211)))))
  ;; package-download-transaction may be more suitable here and
  ;; I don't have time to check it
  (package-install package))

Powodem, dla którego to działa, jest to, że package-*funkcje rodzinne różnie obsługują argumenty w zależności od tego, czy jest to symbol, czy package-descobiekt. Informacje o wersji można określić tylko za package-installpośrednictwem package-descobiektu.

Lei Zhao
źródło
0

Oto moje, jest krótsze :)

(mapc
 (lambda (package)
   (unless (package-installed-p package)
     (progn (message "installing %s" package)
            (package-refresh-contents)
            (package-install package))))
 '(browse-kill-ring flycheck less-css-mode tabbar org auto-complete undo-tree clojure-mode markdown-mode yasnippet paredit paredit-menu php-mode haml-mode rainbow-mode fontawesome))
yPhil
źródło
0

Oto inny sposób.

;; assure every package is installed
(defun ensure-package-installed (&rest packages)
  (let ((user-required-packages
         (seq-remove
          (lambda (package) (package-installed-p package))
          packages)))
    (when user-required-packages
      (package-refresh-contents)
      (dolist (package user-required-packages)
        (package-install package)))))

;; list of packages to install
(ensure-package-installed
 'try
 'which-key)
Yogesh Kamat
źródło