Wie kann ich ein Emacs-Paket patchen?


16

Ich möchte ein Paket ändern, testen und anschließend hoffentlich eine Pull-Anfrage stellen. Wie mache ich das sicher und effizient? Die Frage könnte sich zu weit gefasst anfühlen, ich werde die Antwort akzeptieren, die die folgenden Themen abdeckt:

  1. Ich würde erwarten, einen separaten Zweig eines Pakets zu installieren und in der Lage zu sein, nach Belieben zwischen diesem und dem stabilen Zweig zu wechseln, wobei die Neukompilierung automatisch durchgeführt wird, wenn dies erforderlich ist, aber package.eldies scheint keine einfache Möglichkeit zu bieten. Diese Antwort auf emacs-SE informiert uns darüber, dass "Wenn mehrere Kopien eines Pakets installiert sind, wird die erste geladen", sodass man sich zwar manuell damit herumschlagen kann load-path, sich aber nicht robust anfühlt. Was ist die Standardmethode zum Auswählen einer bestimmten Paketversion unter den installierten?

  2. Selbst wenn ich es schaffe, Emacs mehrere Zweige auszusetzen, muss ich bei signifikanten Änderungen sicherstellen, dass der nicht gepatchte Zweig "entladen" und seine Nebenwirkungen isoliert sind. Wird unload-featuredies richtig gehandhabt oder weist es möglicherweise Besonderheiten auf, die jeder Tester von Paketen mit mehreren Versionen kennen sollte?

  3. Wie installiere und teste ich die lokale Version? Die Antwort scheint davon abhängig zu sein, ob das Paket einfach (= eine Datei) oder vielseitig ist. EmacsWiki sagt über Multifile-Pakete: " MELPA erstellt Pakete für Sie ". Ich bezweifle, dass ich jedes Mal mit MELPA sprechen muss (oder sollte), wenn ich ein defunFormular in einem Paket mit mehreren Dateien ändere , aber die Frage bleibt. Zumindest muss ich Package Manager über die lokale Version informieren, und wenn ja, wie mache ich das?

  4. Welche Namen sollte ich lokalen Versionen von Paketen zuweisen? Angenommen, ich möchte gleichzeitig an mehreren Features oder Bugs arbeiten, was bedeutet, dass mehrere Zweige vorhanden sind. Emacs erlaubt es nicht, Versionen in einer beschreibenden Weise zu benennen (in Anlehnung an 20170117.666-somebugorfeature). Ich schätze, ich könnte das Paket selbst umbenennen, ein Suffix pro Zweig, aber wie beim manuellen Herumspielen load-pathin Q1 ist dies ein hässlicher Hack. Ich werde es also nicht mit etwas versuchen, das ich vorab senden möchte, es sei denn, es ist eine allgemein akzeptierte Praxis .

Die Fragen sind wahrscheinlich naiv, da ich nie einen Patch geschrieben habe, noch einen mit git oder einem ähnlichen vcs angewendet habe. Für viele Emacs-Benutzer ist das Patchen eines Emacs-Pakets jedoch möglicherweise das erste (oder vielleicht das einzige) Unternehmen, das sich mit sozialer Programmierung befasst. Aus diesem Grund sind Antworten auf diese Frage meines Erachtens immer noch wertvoll.

Antworten:


7

Um einen etwas anderen Arbeitsablauf für das Laden verschiedener Versionen von Paketen zu verwenden, folgen einige Variationen meiner Aktionen, mit denen beide load-pathsteuern, welche Version ich verwende (es ist keine gute Idee, den Namen des Pakets zu ändern wenn es Abhängigkeiten gibt). Ich habe die aktuelle Version von "nice-package" in ~/.emacs.d/elpausing installiert M-x package-installund das Paket repo in ~/src/nice-packagewith geklont git clone ....

Mit Anwendungspaket

In init.el habe ich

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

Wenn die :load-pathZeile nicht kommentiert ist, wird die Git-Version des Pakets verwendet. Wenn Sie diese Zeile auskommentieren und den Emacs neu laden, wird die elpa-Version verwendet.

Ähnliches ohne Use-Package

In init.el,

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

Machen Sie jetzt den gleichen Kommentar-Trick mit der ersten Zeile.

Verwenden emacs -L

Dies ist die gleiche Idee, aber die load-pathvon der Kommandozeile aus zu manipulieren . Sie können eine Instanz von Emacs mit der Git-Version des Pakets mit laden

emacs -L ~/src/nice-package

Das stellt diesen Pfad einfach vor die Vorderseite des Ladepfads. Auf diese Weise können Sie emacsvon einem anderen Terminal aus starten und die alte und die neue Version des Pakets nebeneinander ausführen.

Verschiedene Kommentare

  1. Verwenden Sie diese Option M-x eval-buffernach dem Bearbeiten einer Paketdatei, um die neu erstellten Definitionen zu laden.
  2. Es M-x emacs-lisp-byte-compileist auch praktisch zu überprüfen, was der Compiler sagt

Ich verwende den emacs -LAnsatz, um eine lokale Version eines Pakets zu laden, das ich auch mit Cask global installiert habe. Eine Sache, die mich abschreckte, war, dass beim Ausführen <package>-versionimmer die global installierte Version zurückgegeben wird, auch wenn ich tatsächlich die lokal geänderte Version ausgeführt habe. Es stellte sich heraus, dass dies daran lag, dass das <package>-versionfür dieses Paket die Version von erhält packages.el.
ntc2

3

Gute Frage! Die Antwort ist, dass es bis jetzt keine gute Antwort gab, da keiner der vorhandenen Paketmanager für diesen Anwendungsfall entwickelt wurde (mit Ausnahme von Borg , aber Borg versucht nicht, andere gängige Paketverwaltungsvorgänge wie die Behandlung von Abhängigkeiten zu handhaben) .

Jetzt gibt es jedoch straight.eleinen Paketmanager der nächsten Generation für Emacs, der dieses Problem so umfassend wie möglich behandelt. Haftungsausschluss: Ich habe geschrieben straight.el!

Nach dem Einfügen des Bootstrap-Snippets ist die Installation eines Pakets so einfach wie

(straight-use-package 'magit)

Dadurch wird das Git-Repository für Magit geklont, das Paket durch Verknüpfen der Dateien in einem separaten Verzeichnis erstellt, Byte-kompiliert, Autoloads generiert und ausgewertet und das Paket load-pathkorrekt konfiguriert . Wenn das Paket bereits geklont und erstellt ist, passiert natürlich nichts und Ihre Init-Zeit leidet nicht.

Wie können Sie Änderungen an Magit vornehmen? Es ist trivial! Verwenden Sie M-x find-functionoder M-x find-library, um zum Quellcode zu springen, und hacken Sie los! Sie können Ihre Änderungen live testen, wie es für die Emacs Lisp-Entwicklung allgemein üblich ist. Wenn Sie Emacs neu starten, wird das Paket automatisch neu erstellt, neu kompiliert und so weiter. Es ist vollautomatisch und kinderleicht.

Wenn Sie mit Ihren Änderungen zufrieden sind, schreiben Sie einfach eine Commit-, Push- und Pull-Anforderung. Sie haben die vollständige Kontrolle über Ihre lokalen Pakete. Ihre Konfiguration kann jedoch immer noch zu 100% reproduzierbar sein, da Sie straight.elnach einer Sperrdatei fragen können, in der die Git-Revisionen aller Ihrer Pakete, einschließlich sich straight.elselbst, MELPA usw., gespeichert werden.

straight.elkann jedes Paket von MELPA, GNU ELPA oder EmacsMirror installieren. Es verfügt jedoch auch über ein hochflexibles DSL-Rezept, mit dem Sie von jedem Ort aus installieren und den Aufbau des Pakets anpassen können. Hier ist ein Beispiel, das einige der Optionen zeigt:

(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.elhat lächerlich umfassende Dokumentation. Lesen Sie alles darüber auf GitHub .


2

Das sind alles gute Fragen!

Emacs arbeitet mit einem Memory-Image-Modell, bei dem das Laden von neuem Code das Memory-Image der ausgeführten Instanz ändert. Das Definieren neuer Funktionen und Variablen ist einfach rückgängig zu machen, wenn Sie eine Liste von ihnen führen. Es gibt jedoch eine Reihe von Nebenwirkungen, die ein Modul möglicherweise rückgängig machen möchte. Es sieht unload-featureaber so aus, als würde es ziemlich gut ausgehen.

Ich denke, was Sie tun wollen, ist eine Kombination aus Live-Codierung und gelegentlichem Neustart von Emacs, wobei Sie das Modul, an dem Sie arbeiten, aus Ihrem Zweig laden und nicht von dem Ort, an dem es installiert ist. Wenn Sie am Ende viele dieser Zweige haben, möchten Sie vielleicht ein Shell-Skript, das Emacs mit dem richtigen load-pathfür den startet, an dem Sie gerade arbeiten. In jedem Fall würde ich das Paket nicht umbenennen; Ich denke, das wäre noch verwirrender, da Emacs dann beide laden könnten.

Während Sie Ihre Patches entwickeln, können Sie zunächst einfach die Funktionen neu definieren, die Sie direkt in Ihrer Live-Emacs-Sitzung ändern. Auf diese Weise können Sie die neuen Definitionen sofort testen, ohne Emacs zu verlassen. Insbesondere können Sie beim Bearbeiten einer elisp-Datei C-M-x( eval-defun) verwenden, um die aktuelle Funktion in Ihrer aktuellen Emacs-Sitzung auszuwerten. Sie können es dann aufrufen, um sicherzustellen, dass es funktioniert. Wenn Sie etwas ändern, das beim Starten von Emacs passiert, müssen Sie Emacs wahrscheinlich starten und stoppen, um es zu testen. Sie können dies tun, indem Sie einen separaten Emacs-Prozess starten und stoppen, damit Ihre Editiersitzung nicht unterbrochen wird.


2

Ich denke, es gibt noch keine gute Antwort darauf (ich gehe davon aus, dass Sie mit Cask eine Teillösung erhalten können, die ich jedoch nicht ausreichend kenne, um Ihnen eine gute Antwort zu geben; hoffentlich wird es jemand anderes tun), aber hier ist die Antwort Was ich mache (ich verwende selten ein Elisp-Paket, ohne lokale Änderungen daran vorzunehmen, also ist es wirklich mein "normaler" Weg):

  • cd ~/src; git clone ..../elpa.git
  • für jedes Paket cd ~/src/elisp; git clone ....thepackage.git
  • cd ~/src/elpa/packages; ln -s ~/src/elisp/* .
  • cd ~/src/elpa; make
  • in deinem ~/.emacsadd

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

Auf diese Weise werden alle Pakete "direkt von Git" installiert. Ein einfacher cd ~/src/elpa; makeBefehl kompiliert die benötigten Pakete neu und C-h o thepackage-functionspringt zu einer Quelldatei, die sich unter Git befindet.

Um "aus einer Laune heraus zwischen dem Zweig und dem stabilen Zweig zu wechseln", müssen Sie Folgendes tun git checkout <branch>; cd ~/src/elpa; make: und wenn Sie möchten, dass sich dies auf die Ausführung von Emacs-Sitzungen auswirkt, ist mehr Arbeit erforderlich. Ich empfehle generell, es unload-featurenur in Ausnahmesituationen zu verwenden (es ist eine gute Funktion, aber momentan nicht zuverlässig genug).

Es erfüllt auch nicht viele Ihrer Anforderungen. Und es hat einige zusätzliche Nachteile, vor allem die Tatsache, dass der Git-Klon vieler Pakete nicht ganz mit dem von elpa.git erwarteten Layout und Inhalt übereinstimmt <pkg>-pkg.elDa das Makefile von elpa.git erwartet, dass diese Datei erstellt wird, <pkg>.elanstatt bereitgestellt zu werden, wird die Kompilierung jedoch problematischer anders ausgeführt, sodass Sie manchmal mit requires) spielen müssen.

Das bedeutet natürlich, dass Sie diese Pakete von Hand installieren, also müssen Sie auf Abhängigkeiten achten. Dieses Setup interagiert korrekt mit anderen Paketen, die von package-install, tho installiert wurden , so dass es nicht so schlimm ist.


2

Die anderen Antworten auf diese Frage, einschließlich meiner anderen Antwort , beziehen sich auf das Patchen eines Emacs-Pakets, indem Änderungen am Code vorgenommen werden. Die Leute, die diese Frage über Google finden, denken möglicherweise an etwas anderes, wenn sie "Emacs-Paket patchen" sagen - nämlich das Überschreiben des Verhaltens, ohne den Quellcode ändern zu müssen.

Mechanismen dafür sind in zunehmender Reihenfolge der Aggressivität:

  • Hinzufügen von Funktionen zu Hooks oder Binden dynamischer Variablen mitlet
  • das leistungsstarke Beratung System
  • Überschreiben Sie einfach die Funktion, die Sie ändern möchten

Trotz der Kraft der ersten beiden Optionen bin ich ziemlich oft auf die dritte Route gegangen, da es manchmal keinen anderen Weg gibt. Aber dann ist die Frage, was passiert, wenn sich die ursprüngliche Funktionsdefinition ändert? Sie könnten nicht wissen, dass Sie die Version dieser Definition aktualisieren müssen, die Sie kopiert und in Ihre Init-Datei eingefügt haben!

Da ich davon besessen bin, Dinge zu patchen, habe ich das Paket geschrieben el-patch, das dieses Problem so umfassend wie möglich löst. Die Idee ist, dass Sie s-expression-basierte Unterschiede in Ihrer init-Datei definieren, die sowohl die ursprüngliche Funktionsdefinition als auch Ihre Änderungen daran beschreiben. Dadurch werden Ihre Patches viel lesbarer und Sie können el-patchspäter überprüfen, ob die ursprüngliche Funktionsdefinition aktualisiert wurde, seit Sie Ihren Patch erstellt haben. (In diesem Fall werden Ihnen die Änderungen über Ediff angezeigt!) Zitat aus der Dokumentation:

Betrachten Sie die folgende im company-statisticsPaket definierte Funktion :

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

Angenommen , wir das dritte Argument von ändern möchten , nilum 'nomessagedie Meldung zu unterdrücken , die protokolliert wird , wenn company-statisticsLasten ihrer Statistikdatei. Wir können das tun, indem wir den folgenden Code in unsere einfügen init.el:

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

Durch einfaches Aufrufen el-patch-defunanstelle von wird defunein No-Op-Patch definiert: Das heißt, es hat keine Auswirkung (na ja, nicht ganz - siehe später ). Durch Einfügen von Patch-Anweisungen können Sie jedoch die geänderte Version der Funktion vom Original unterscheiden.

In diesem Fall verwenden wir die el-patch-swapDirektive. Das el-patch-swapFormular wird ersetzt durch nilin der ursprünglichen Definition (dh der Version, die mit der "offiziellen" Definition in verglichen wird company-statistics.el) und durch 'nomessagein der geänderten Definition (dh der Version, die tatsächlich in Ihrer Init-Datei ausgewertet wird).


0

Wenn Sie viele Änderungen vornehmen, sollten Sie, wie ich finde straight.el, die Antwort von Radon Rosborough verwenden .

Wenn Sie nur eine einmalige Änderung vornehmen möchten, gehen wir von einem Projekt mit dem Namen aus fork-modeund führen die folgenden Schritte aus:

  • Erstellen Sie ein Verzeichnis zum Speichern des Git mkdir ~/.emacs.d/lisp-gits
  • Machen Sie einen Fork des Projekts, das Sie ändern möchten, sagen Sie an https://github.com/user/fork-mode
  • Klonen Sie Ihre Gabel cd ~/.emacs.d/lisp-gits && git clone git@github.com:user/fork-mode.git

Schreiben Sie den folgenden Code in Ihr .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"))))

Jetzt können Sie den Emacs-Modus verwenden C-h f, um die Funktionen zu finden, die Sie ändern möchten. Sie werden feststellen, dass Sie, wenn das Paket in lisp-gits installiert ist, dorthin springen. Verwenden Sie magit oder andere git-Befehle, um Änderungen festzuschreiben / zu pushen, und verwenden Sie dann github, um Ihre Pull-Anforderungen zu senden.

Sobald Ihre Pull-Anforderungen akzeptiert wurden, können Sie das Projekt einfach entfernen ~/.emacs.d/lisp-gitsund den Paketmanager seine Arbeit machen lassen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.