如何修补Emacs软件包?


16

我希望更改一个程序包,对其进行测试,并希望以后再提交请求。如何安全有效地做到这一点?这个问题可能太笼统了,我将接受涵盖以下问题的答案:

  1. 我希望安装一个单独的程序包分支,并且能够随心所欲地在程序包和稳定分支之间进行切换,并在必要时自动执行重新编译,但package.el似乎并没有提供一种简单的方法。这个关于emacs-SE的答案告诉我们:“如果安装了一个软件包的多个副本,则将加载第一个副本”,所以我猜可能会手动将其弄乱,load-path但这并不稳健。在已安装的软件包中选择特定版本的软件包的标准方法是什么?

  2. 即使我设法向Emacs公开了多个分支,也需要进行重大调整,以确保未修补的分支已“卸载”并且其副作用得以隔离。是否unload-feature正确处理此问题,或者它具有多版本软件包的每个测试人员都应该了解的特质?

  3. 如何安装和测试本地版本?答案似乎取决于软件包是简单文件(=一个文件)还是多文件包。EmacsWiki关于多文件软件包说:“ MELPA为您构建软件包 ”。我怀疑每次更改defun多文件包中的表单时都必须(或应该)与MELPA进行交谈,但问题仍然存在。至少我需要告诉软件包管理器本地版本,如果是,我该怎么做?

  4. 我应该为本地版本的软件包分配什么名称?假设我想同时处理多个功能或错误,这意味着要有多个分支。Emacs不允许以描述性的方式命名版本(沿着20170117.666-somebugorfeature)。我想我可以重命名程序包本身,每个分支都带有一个后缀,但是再说一次,就像load-path在Q1中手动弄乱一样,这是一个丑陋的破解,所以除非打算被广泛接受,否则我不会尝试将其发送给上游。

这些问题可能很幼稚,因为我从未写过既未在git中使用过补丁,也未在类似vcs中使用过的补丁。但是,对于许多Emacs用户而言,修补Emacs程序包可能是他们有史以来的第一个(或者可能是唯一的)社交编程,这就是为什么我认为对此问题的答案仍然有价值的原因。

Answers:


7

为了配合加载不同版本的软件包的工作流程略有不同,这是我所做的一些变体,两者都使用load-path来控制我使用的版本(更改软件包名称是一个坏主意)如果有依赖性)。我有最新版本安装在“好包”的~/.emacs.d/elpa使用M-x package-install,并在包装回购克隆~/src/nice-packagegit clone ...

带有使用包

在init.el中,我有

(use-package nice-package
  :load-path "~/src/nice-package"
  ...)

没有:load-path注释的行,将使用包的git版本。注释掉这一行,然后使用elpa版本重新加载emacs。

没有使用包时类似

在init.el中

(add-to-list 'load-path "~/src/nice-package")
(require 'nice-package)
...

现在,对第一行执行相同的注释技巧。

使用 emacs -L

这是相同的想法,但是load-path从命令行进行操作。您可以使用以下软件包的git版本加载emacs实例:

emacs -L ~/src/nice-package

这只是将该路径添加到加载路径的前面。这样,您可以emacs从其他终端启动并同时运行旧版本和新版本的软件包。

其他评论

  1. M-x eval-buffer编辑包文件后使用,以加载您创建的新定义。
  2. 检查编译器所说的内容M-x emacs-lisp-byte-compile也很方便

我正在使用这种emacs -L方法来加载我也已使用Cask全局安装的软件包的本地版本。让我失望的一件事是<package>-version,即使我实际上正在运行本地修改版本,运行也总是返回全局安装的版本。原来,这是因为<package>-versionfor软件包从中获取版本packages.el
ntc2

3

好问题!答案是,到目前为止,还没有一个好的答案,因为没有一个现有的程序包管理器是为此用例设计的Borg除外,但Borg并未尝试处理其他常见的程序包管理操作,如依赖项处理)

但是,现在有了straight.elEmacs的下一代软件包管理器,它可以尽可能全面地解决此问题。免责声明:我写了straight.el

插入引导程序片段后,安装软件包非常简单

(straight-use-package 'magit)

这将克隆Magit的Git存储库,通过将其文件符号链接到一个单独的目录中来构建软件包,进行字节编译,生成和评估自动加载并load-path正确配置。当然,如果已经克隆并构建了该程序包,则什么也不会发生,并且您的初始化时间也不会受到影响。

您如何更改Magit?这很琐碎!只需使用M-x find-functionM-x find-library跳至源代码,然后破解即可!您可以评估更改以进行实时测试,这是Emacs Lisp开发的常规做法,并且在重新启动Emacs时,将自动重建,重新编译该程序包等等。这是完全自动化且万无一失的。

当您对更改感到满意时,只需提交,推送并发出拉取请求。您可以完全控制本地软件包。但是您的配置仍然可以100%重现,因为您可以要求straight.el创建一个锁定文件来保存所有软件包的Git修订版,包括straight.el其自身,MELPA等。

straight.el可以从MELPA,GNU ELPA或EmacsMirror安装任何软件包。但它也具有高度灵活的配方DSL,可让您从任何地方安装以及自定义软件包的构建方式。这是显示一些选项的示例:

(straight-use-package
 '(magit :type git 
         :files ("lisp/magit*.el" "lisp/git-rebase.el"
                 "Documentation/magit.texi" "Documentation/AUTHORS.md"
                 "COPYING" (:exclude "lisp/magit-popup.el"))
         :host github :repo "raxod502/magit"
         :upstream (:host github :repo "magit/magit")))

straight.el有荒谬的全面文档。在GitHub上阅读所有相关内容。


2

这些都是好问题!

Emacs在内存映像模型上工作,在其中加载新代码会更改正在运行的实例的内存映像。如果保留新功能和变量的列表,则很容易撤消定义它们的操作,但是,您可能想撤消该模块可能会带来很多副作用。看起来unload-feature确实做了很多。

我认为您想要做的是结合实时编码和偶尔重新启动Emacs,从分支而不是从安装位置加载正在处理的模块。如果确实有很多这样的分支,那么您可能需要一个shell脚本来启动emacs,并使用与load-path您当前正在使用的脚本正确的脚本。无论如何,我都不会重命名程序包。我认为这将更加令人困惑,因为emacs可以同时加载它们两者。

在开发补丁程序时,您可以从简单地重新定义正在实时Emacs会话中更改的功能开始。这使您可以立即测试新定义,而无需离开Emacs。具体来说,在编辑elisp文件时,您可以使用C-M-xeval-defun)在当前Emacs会话中评估当前功能。然后可以调用它以确保它可以工作。如果您要更改Emacs启动时发生的事情,则可能必须启动和停止Emacs进行测试;您可以通过启动和停止一个单独的Emacs进程来做到这一点,以使您的编辑会话不会中断。


2

我认为目前还没有很好的答案(我希望您可以使用Cask来获得部分解决方案,因为我对它还不足够熟悉,无法为您使用它提供一个很好的答案;希望其他人也可以),但是这里是我要做的事情(我很少使用Elisp程序包而不对其进行本地更改,因此,这实际上是我的“正常”方式):

  • cd ~/src; git clone ..../elpa.git
  • 每个包装 cd ~/src/elisp; git clone ....thepackage.git
  • cd ~/src/elpa/packages; ln -s ~/src/elisp/* .
  • cd ~/src/elpa; make
  • 在您的~/.emacs添加中

    (eval-after-load 'package
     '(add-to-list 'package-directory-list
                   "~/src/elpa/packages"))
    

这样,所有软件包都是直接从Git安装的,简单的软件包cd ~/src/elpa; make将重新编译需要的软件包,并C-h o thepackage-function跳转到Git下的源文件。

要“一时之间在它和稳定分支之间切换”,您需要git checkout <branch>; cd ~/src/elpa; make;如果您希望它影响正在运行的Emacs会话,则需要进行更多工作。我通常建议不要使用,unload-feature除非在特殊情况下使用(这是个好功能,但是目前还不够可靠)。

它还不能满足您的许多要求。它还有一些额外的缺点,主要是许多软件包的Git克隆与elpa.git的makefile所期望的布局和内容不完全匹配,因此您需要从调整这些软件包开始(通常与之相关的事情)。<pkg>-pkg.el,因为elpa.git的makefile期望从而<pkg>.el不是提供该文件中生成该文件,但是更成问题的是,编译的执行方式不同,因此有时您需要使用requires)。

哦,当然,这基本上意味着您是手动安装这些软件包,因此您必须注意依赖项。此设置可以与package-installtho所安装的其他软件包正确交互,因此并不可怕。


2

该问题的其他答案,包括我的其他答案,都涉及通过更改其代码来修补Emacs软件包。但是当人们说“修补Emacs程序包”时,通过Google找到这个问题的人可能会想到其他的东西,即覆盖其行为而不必修改其源代码。

执行此操作的机制包括按侵略性递增的顺序:

  • 钩子添加函数或使用以下方法绑定动态变量let
  • 强大的建议系统
  • 只是简单地覆盖您要修改的功能

尽管前两个选项功能强大,但我发现自己还是经常走第三条路线,因为有时别无选择。但是,问题是,如果原始函数定义发生变化该怎么办?您将无法知道是否需要更新已复制并粘贴到init文件中的该定义的版本!

因为我迷恋修补程序el-patch,所以我写了package ,它尽可能全面地解决了这个问题。想法是,您在init文件中定义基于s表达式的差异,这些差异描述了原始函数定义及其更改。这使您的补丁程序更具可读性,并且还允许el-patch以后验证自您制作补丁程序以来是否已更新原始功能定义。(如果是这样,它将通过Ediff向您显示更改!)从文档中引用:

考虑company-statistics软件包中定义的以下功能 :

(defun company-statistics--load ()
  "Restore statistics."
  (load company-statistics-file 'noerror nil 'nosuffix))

假设我们要将第三个参数从更改nil'nomessage,以禁止company-statistics加载其统计信息文件时记录的消息 。我们可以通过将以下代码放入我们的代码中来实现init.el

(el-patch-defun company-statistics--load ()
  "Restore statistics."
  (load company-statistics-file 'noerror
        (el-patch-swap nil 'nomessage)
        'nosuffix))

简单地调用el-patch-defun而不是defun定义一个无操作补丁即可:也就是说,它没有任何作用(嗯,不是很有效,请参阅 后面的内容)。但是,通过包含patch指令,可以使功能的修改版本不同于原始版本。

在这种情况下,我们使用el-patch-swap指令。该 el-patch-swap表单将替换nil为原始定义(即与中的“正式”定义进行比较的版本company-statistics.el)以及'nomessage修改后的定义(即init文件中实际评估的版本)。


0

当您进行很多更改时,我认为您应该使用straight.el,请参阅Radon Rosborough的答案。

如果您只想进行一次更改,那么假设有一个名为的项目fork-mode,请执行以下步骤:

  • 创建一个目录来存储git mkdir ~/.emacs.d/lisp-gits
  • 在您要更改的项目上创建一个分支,例如 https://github.com/user/fork-mode
  • 克隆你的叉子 cd ~/.emacs.d/lisp-gits && git clone git@github.com:user/fork-mode.git

在您的代码中编写以下代码 .emacs

(if (file-exists-p "~/.emacs.d/lisp-gits/fork-mode")
    (use-package fork-mode :load-path "~/.emacs.d/lisp-gits/fork-mode")
  (use-package fork-mode :ensure t))

(use-package fork-mode
  :config
  (setq fork-mode-setting t)
  :hook
  ((fork-mode . (lambda () (message "Inside hook"))))

现在,您可以使用emacs模式,C-h f用于查找要更改的功能。您会注意到,将软件包安装在lisp-gits中后,您将跳到那里。使用magit或其他git命令来提交/推送更改,然后使用github发送您的拉取请求。

接受拉取请求后,您可以直接删除项目,~/.emacs.d/lisp-gits然后让包管理器执行其工作。

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.