在不同机器之间同步包


57

我在不同的地方使用emacs,并且希望在各处安装类似的设置和软件包。我想可以将版本控制存储库用于安装文件。由于我使用的是Prelude,那就是~/.emacs.d/personal/

我不知道如何处理包裹。在.emacs.d/安装的软件包列表中是否有文件可用于在其他计算机上制作emacs并安装其中列出的软件包?


2
对我来说,已安装的软件包只是我的Emacs版本控制存储库的一部分。我使用我想/需要使用的最旧版本的Emacs来对软件包进行字节编译,然后将.elc文件放入存储库中。这提供了最大的控制力和一致性。权衡是存储库的(相对)较大。我6岁的git存储库大小为120MB。如果我不包括软件包,我可能可以摆脱其中的1/10,但那些“浪费”的兆字节确实让我不担心。
辣椒粉

1
我应该补充一点,尽管ELPA / MELPA / ...在当今非常流行,但并非所有软件包都可以通过它们获得。因此,如果您使用需要手动安装的软件包,则可能不想在使用的每台新计算机上重复进行工作(每次升级软件包都需要花费额外的精力)。同样,一个简单的解决方案是将该软件包添加到您的Emacs版本控制存储库中。
辣椒粉

您不能只是在现场进行字节编译,而忽略git中的.elc文件吗?
Vamsi

@Vamsi:我将字节编译的文件放到存储库中,因为某些(非ELPA)程序包由于其依赖性而难以编译。对于这些,如果不需要,我不想重复该过程。
辣椒粉2014年

@paprika您能否提供一些详细信息以获取设置?您是否将.emacs.d和.emacs中的所有内容都添加到存储库中,仅此而已?
初学者

Answers:


49

没有自动生成的清单文件可以同步以达到所需的效果。

也就是说,您可以做的就是package-install在emacs配置本身中添加对的调用。

(package-install 'auctex)

这个想法是package-install幂等的,因此,如果该软件包已经存在,则实际上什么也不会发生。假设您对使用的每个程序包(或至少是依赖关系图中的叶子)都进行了调用,那么它将有效地跨计算机同步程序包。


对于多个软件包,可以使用以下命令:

(setq my-package-list '(package1 package2 packageN))
(mapc #'package-install my-package-list)

2
使我惊讶的是,这种方法与这里建议的解决方案相比要简单得多。有什么区别吗?该代码段也使用package-install
Stenskjaer 2014年

1
package.el自从链接答案以来,已经发生了变化。当时有可能package-install对现有软件包进行操作,而不仅仅是已卸载的软件包。
乔纳森·里希·佩平2014年

3
不幸的是,此技术存在问题-即使程序包归档库在计算机之间是统一的,并且在SCM中指定。它不能确保机器之间的软件包版本相同。问题是未指定软件包版本。这些单独的程序包可能会随时间变化,并且它们的依赖性可能会变得不兼容。这在活动包档案(例如melpa)上很容易发生。
ctpenrose '16

@ctpenrose:您有建议避免这个问题吗?
学生

@student我通过使用melpa-stable减少了更新问题的频率,从而最小化了问题。
ctpenrose '17

34

我将.emacs.d目录保留在版本控制中。然后,在我的init.el及后续文件中,我使用use-package定义软件包设置。use-package不仅会延迟加载您的软件包,而且如果您设置的任何软件包存储库中不存在它们,它也会按需下载它们。

例如,我使用go-mode,但不是在每台机器上都使用。在我的init.el中,我有以下内容:

(use-package go-mode
  :ensure t
  :config
  (progn
    (defun my-go-mode-hook ()
      (linum-mode t)
      (setq tab-width 4)
      (add-hook 'before-save-hook 'gofmt-before-save))
    (add-hook 'go-mode-hook 'my-go-mode-hook)))

这会添加一个模式挂钩,但更重要的是,通过指定:ensure t它可以按需下载软件包。

要使机器保持同步,您只需检出或从存储库中取出并启动Emacs。任何新软件包都将下载并安装。


这也是我现在使用的解决方案,而不是Cask,主要是因为(如T. Verron所指出的)Cask在Windows上无法正常工作,并且是另一个依赖项。
安迪2015年

1
这也是我使用的方式,但是您不必:ensure go-mode指定重复的软件包名称,而是只需指定:ensure t
Pedro Luz

好点子!这是一个老答案。我会更新。
elarson '16

您也可以使用:hook关键字来简化代码。
GuilhermeSalomé18年

17

在Emacs-25中,存在变量package-selected-packages,因此您可以自定义此变量并使用package-install-selected-packages以确保已安装它们。


请注意,我看到了,该命令的名称有些混乱。我可以将其更改为package-install-selected-packages吗?
马拉巴巴

假设您的意思是“现在”而不是“注释”,是的。
斯特凡

9

您想要使用的是Cask,它使您可以创建一个Cask文件,指定要安装在哪些软件包上cask install。它可以用来轻松管理软件包的依赖关系以及Emacs配置的“依赖关系”。将您的Cask文件置于版本控制下,并按计算机安装/更新软件包。


4
值得注意的是(到目前为止)该解决方案不适用于Windows计算机。
T. Verron 2014年

1
由于您的原因,我不再使用此解决方案了(而且Cask是另一个依赖项)。非常适合制作包装;可怕的配置管理。
安迪

6

一种替代方法如下:由于我不仅要同步我的Emacs软件包,还要同步服务器和笔记本电脑之间的其他文件(例如.emacs.bashrc以及其他目录),因此我开始使用unison来同步文件和目录。因此,在笔记本电脑上工作时,我可以unison laptop先运行其他设备。我的~/.unison/laptop.prf文件包含有关Emacs相关文件的以下部分:

path = .emacs
path = .emacs.d
ignore = Path {.emacs.d/semanticdb}

因为我的Emacs软件包(以及我的Emacs备份和书签)都存储在其中,~/.emacs.d所以可以确保我在所有计算机上都拥有了所有东西。

一种替代方法是将.emacs.d目录放置在与OwnCloud,DropBox或任何其他文件同步服务同步的目录中,然后从~/.emacs.d该目录创建指向该共享目录的符号链接。


5

虽然这package.el是安装软件包的标准方法,但您可能还想尝试一下el-get这对于安装不在elpa上(或不能在elpa上)的软件包非常有用。该答案涉及同步此类软件包。

确保使用el-get时已安装给定软件包的方法是在init文件中添加以下内容

(el-get 'sync '(packages))

其中,packages是要安装的软件包的列表。此功能类似于package-install仅在尚未安装软件包的情况下才安装软件包,否则仅初始化软件包。


5

我使用emacs-starter-kit中的一个小技巧“偷了”(我认为):

(defun maybe-install-and-require (p)
  (when (not (package-installed-p p))
   (package-install p))
  (require p))

因此,当我需要包装时,我只需使用:

(maybe-install-and-require 'magit)

在emacs初创公司上,评估我的配置package.el将提供安装magit(如果未安装的话)。

您可以在这里找到我的配置:

https://github.com/mdallastella/emacs-config/


1
继同样的理念,你可以使用“悖论,需要”从悖论
csantosb

3

我有〜/ emacs目录,该目录是由软件版本控制的,包含我的emacs设置包括的所有内容(〜/ emacs / site-lisp用于手动下载的库,〜/ emacs / elpa用于elpa安装的库,〜/ emacs / etc /对于分割的.emacs,〜/ emacs / dot-emacs.el,我将其链接为〜/ .emacs)。它需要对某些软件包进行一些调整,以使所有重要文件都包含在该树中,但是效果很好。我通过系统名称的条件实现了那些机器专用的几位。

因此,在我安装/重新配置/更改任何内容之后,我只需提交/拉动我使用的所有计算机之间的所有更改。

额外的好处是,我具有完整的配置历史记录,可以在发生任何错误的情况下返回/平分/还原。

PS mercurial似乎特别适合,因为它具有自然的两侧拉/推功能,但是使用git或任何其他dvc很难实现类似的设置。


3

setup-packages.el我的emacs设置中包含此代码,它是PreludeTomorokoshi的Package Management博客中代码的混合体。

setup-packages.el 执行以下操作:

  • elpa如果不存在,请为软件包创建目录,并将其及其子目录添加到中load-path
  • package-archives用Melpa 更新清单。
  • 检查是否已my-packages安装列表中列出的所有软件包。如果未安装软件包,请安装它。

如何实施

  • setup-packages.el以下内容保存到您的~/.emacs.d/目录中。
  • 设置user-emacs-directorysetup-packages-file以及my-packages变量在你init.el(load setup-packages-file)

当您第一次在没有安装这些软件包的机器上启动emacs时,其中列出的所有功能my-packages都会自动安装。

setup-packages.el

;; setup-packages.el - Package management

(require 'cl)
(require 'package)

;; Set the directory where you want to install the packages
(setq package-user-dir (concat user-emacs-directory "elpa/"))

;; Add melpa package source when using package list
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)

;; Load emacs packages and activate them
;; This must come before configurations of installed packages.
;; Don't delete this line.
(package-initialize)
;; `package-initialize' call is required before any of the below
;; can happen

;; Auto install the required packages
;; Method to check if all packages are installed
(defun packages-installed-p ()
  (loop for p in my-packages
        when (not (package-installed-p p)) do (return nil)
        finally (return t)))

;; if not all packages are installed, check one by one and install the missing ones.
(unless (packages-installed-p)
  ;; check for new packages (package versions)
  (message "%s" "Emacs is now refreshing its package database...")
  (package-refresh-contents)
  (message "%s" " done.")
  ;; install the missing packages
  (dolist (p my-packages)
    (when (not (package-installed-p p))
      (package-install p))))

(provide 'setup-packages)

初始化

您将需要以下内容init.el

(setq user-home-directory  (getenv "HOME"))
(setq user-emacs-directory (concat user-home-directory ".emacs.d/"))
(setq setup-packages-file  (expand-file-name "setup-packages.el" user-emacs-directory))

;; A list of packages to ensure are installed at launch
(setq my-packages
      '(
        ;; package1
        ;; package2
       ))

(load setup-packages-file nil :nomessage) ; Load the packages

2

为了反映我的配置,我决定采用Syncthing;采用另一种方法;我的任何配置文件中的每个更改都会传播到我的任何其他PC上,而无需关心,因此,当我升级软件包时,我只需要在其中一台PC中进行操作。


2

RSYNC:使用rsync,通过家庭网络或通过来同步选择的文件夹/文件ssh远程服务器。

rsync是一种单向同步实用程序,能够删除目标上的文件,因此执行真实操作之前,请确保在源位置和目标位置上都备份数据,使用该--dry-run选项进行彻底测试。

要了解有关如何正确配置.authinfo文件的信息,请参阅 https://www.gnu.org/software/emacs/manual/auth.html 示例.authinfo文件内容(可能包含多个不同的条目)如下:

machine mymachine login myloginname password mypassword port myport

配置并使用该功能rsync-remote同步ssh到远程服务器。或者,使用该功能rsync-local在同一台计算机上或通过受信任的家庭网络上进行同步。

(require 'auth-source)

;;; EXAMPLE:
;;;   (get-auth-info "12.34.567.89" "username")
;;;   (get-auth-info "localhost" "root")
(defun get-auth-info (host user &optional port)
  (let ((info (nth 0 (auth-source-search
                      :host host
                      :user user
                      :port port
                      :require '(:user :secret)
                      :create t))))
    (if info
      (let* ((port (plist-get info :port))
             (secret-maybe (plist-get info :secret))
             (secret
               (if (functionp secret-maybe)
                 (funcall secret-maybe)
                 secret-maybe)))
          (list port secret))
    nil)))

(defun rsync-filter (proc string)
  (cond
    ((string-match
       "^\\([a-zA-Z0-9_\\-\\.]+\\)@\\([a-zA-Z0-9_\\-\\.]+\\)'s password: "
       string)
      (let* ((user (substring string (match-beginning 1) (match-end 1)))
             (host (substring string (match-beginning 2) (match-end 2)))
             (password (car (cdr (get-auth-info host user)))))
        (process-send-string proc (concat password "\n"))))
    ((not (or (string-match "files\\.\\.\\.\r" string)
              (string-match "files to consider\n" string)))
      (with-current-buffer (messages-buffer)
        (let ((inhibit-read-only t))
          (goto-char (point-max))
          (when (not (bolp))
            (insert "\n"))
          (insert string)
          (when (not (bolp))
            (insert "\n")))))))

(defun rsync-remote ()
"Use rsync to a remote server via ssh.  Back-up your data first!!!"
(interactive)
  (let* (
      (host "localhost")
      (username "root")
      (port (or (car (get-auth-info host username))
                (number-to-string (read-number "Port:  "))))
      (source
        (let ((dir (expand-file-name (locate-user-emacs-file "elpa/"))))
          (if (file-directory-p dir)
            dir
            (let ((debug-on-quit nil)
                  (msg (format "`%s` is not a valid directory." dir)))
              (signal 'quit `(,msg))))))
      (target "/private/var/mobile/elpa/")
      (ssh "/usr/bin/ssh")
      (rsync "/usr/bin/rsync")
      (rsync-include-file "/path/to/include-file.txt")
      (rsync-exclude-file "/path/to/exclude-file.txt")
      (rsh (concat "--rsh=ssh -p " port " -l " username))
      (host+target (concat host ":" target)))
    (start-process
        "rsync-process"
        nil
        rsync
        "-avr" ;; must specify the `-r` argument when using `--files-from`
        "--delete"
        ;; The paths inside the exclusion file must be relative, NOT absolute.
        ;;; (concat "--files-from=" rsync-include-file)
        ;;; (concat "--exclude-from=" rsync-exclude-file)
        rsh
        source
        host+target)
    (set-process-filter (get-process "rsync-process") 'rsync-filter)
    (set-process-sentinel
      (get-process "rsync-process")
      (lambda (p e) (when (= 0 (process-exit-status p))
        (message "rsync-remote:  synchronizing ... done."))))))

(defun rsync-local ()
"Use rsync locally -- e.g., over a trusted home network.
 Back-up your data first!!!"
  (interactive)
  (let (
      (rsync-program "/usr/bin/rsync")
      (source
        (let ((dir (expand-file-name
                     (file-name-as-directory
                       (read-directory-name "Source Directory: " nil nil nil nil)))))
          (if (file-directory-p dir)
            dir
            (let ((debug-on-quit nil)
                  (msg (format "`%s` is not a valid directory." dir)))
              (signal 'quit `(,msg))))))
      (target (expand-file-name
                (file-name-as-directory
                  (read-directory-name "Target Directory: " nil nil nil nil)))))
    (unless (y-or-n-p (format "SOURCE:  %s | TARGET:  %s" source target))
      (let ((debug-on-quit nil))
        (signal 'quit `("You have exited the function."))))
    (start-process "rsync-process"
      nil
      rsync-program
      "--delete"
      "-arzhv"
      source
      target)
    (set-process-filter (get-process "rsync-process") #'rsync-process-filter)
    (set-process-sentinel
      (get-process "rsync-process")
      (lambda (p e)
        (when (= 0 (process-exit-status p))
        (message "Done!"))))))

0

https://github.com/redguardtoo/elpa-mirror创建所有已安装软件包的本地存储库。

用法很简单,只需运行即可M-x elpamr-create-mirror-for-installed

在其他机器上,插入(setq package-archives '(("myelpa" . "~/myelpa/")))您的.emacsEmacs并重新启动。

现在,在所有计算机上,您将获得完全相同版本的软件包。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.