Comment patcher un paquet Emacs?

16

Je souhaite modifier un package, le tester et, espérons-le, soumettre une demande d'extraction par la suite. Comment le faire de manière sûre et efficace? La question peut sembler trop large, j'accepterai la réponse qui couvre les questions suivantes:

  1. Je m'attends à installer une branche distincte d'un package et à pouvoir basculer entre celle-ci et une branche stable sur un coup de tête, avec une recompilation effectuée automatiquement lorsque cela est nécessaire, mais package.elne semble pas offrir un moyen simple de le faire. Cette réponse sur emacs-SE nous informe que "Si plusieurs copies d'un paquet sont installées, alors la première sera chargée", donc je suppose que l'on pourrait jouer manuellement load-pathmais cela ne semble pas robuste. Quelle est la méthode standard pour sélectionner une version spécifique du package parmi celles installées?

  2. Même si je parviens à exposer plusieurs branches à Emacs, pour des ajustements importants, je dois m'assurer que la branche non corrigée est «déchargée» et ses effets secondaires isolés. Est- unload-featurece que cela gère correctement ou peut-être qu'il a des particularités que tout testeur de packages multi-versions devrait connaître?

  3. Comment installer et tester la version locale? La réponse semble dépendre du fait que le package est simple (= un fichier) ou multifichier. EmacsWiki dit à propos des packages multifichiers : « MELPA crée des packages pour vous ». Je doute que je doive (ou devrais) parler à MELPA chaque fois que je change un defunformulaire dans un package multifichier, mais la question demeure. Au moins, je dois informer le gestionnaire de paquets de la version locale, et si oui, comment dois-je procéder?

  4. Quels noms dois-je attribuer aux versions locales des packages? Supposons que je veuille travailler sur plusieurs fonctionnalités ou bugs simultanément, ce qui signifie avoir plusieurs branches. Emacs ne permettra pas de nommer les versions de manière descriptive (dans le sens de 20170117.666-somebugorfeature). Je suppose que je pourrais renommer le package lui-même, un suffixe par branche, mais encore une fois, comme jouer manuellement avec load-pathQ1, c'est un vilain hack, donc je ne vais pas l'essayer avec quelque chose que j'ai l'intention d'envoyer en amont, sauf si c'est une pratique largement acceptée .

Les questions sont probablement naïves, car je n'ai jamais écrit de correctif ni appliqué de correctif avec git ou un vcs similaire. Cependant, pour de nombreux utilisateurs d'Emacs, patcher un paquet Emacs pourrait être leur tout premier (ou peut-être le seul) effort de programmation sociale, c'est pourquoi, je pense, les réponses à cette question seraient toujours utiles.

akater
la source

Réponses:

7

Pour synchroniser avec un flux de travail légèrement différent pour charger différentes versions de packages, voici quelques variantes de ce que je fais, qui utilisent toutes deux load-pathpour contrôler la version que j'utilise (changer le nom du package est une mauvaise idée s'il y a des dépendances). J'ai la version actuelle de "nice-package" installée dans ~/.emacs.d/elpausing M-x package-install, et le package repo clone ~/src/nice-packageavec git clone ....

Avec package d'utilisation

Dans init.el, j'ai

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

Avec la :load-pathligne non commentée, cela utilisera la version git du paquet. Commenter cette ligne et recharger emacs utilise la version elpa.

Similaire sans package d'utilisation

Dans init.el,

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

Faites maintenant le même tour de commentaire avec la première ligne.

En utilisant emacs -L

C'est la même idée, mais en manipulant le load-pathdepuis la ligne de commande. Vous pouvez charger une instance d'emacs avec la version git du package avec

emacs -L ~/src/nice-package

qui ajoute juste ce chemin à l'avant du chemin de chargement. De cette façon, vous pouvez lancer à emacspartir d'un autre terminal et faire fonctionner côte à côte l'ancienne et la nouvelle version du package.

Commentaires divers

  1. À utiliser M-x eval-bufferaprès avoir modifié un fichier de package pour charger les nouvelles définitions que vous avez créées.
  2. Vérifier ce que dit le compilateur M-x emacs-lisp-byte-compileest également pratique
Justbur
la source
J'utilise l' emacs -Lapproche pour charger une version locale d'un package que j'ai également installé globalement à l'aide de Cask. Une chose qui m'a dérouté était que l'exécution <package>-versionrenvoie toujours la version installée globalement, même lorsque j'exécutais réellement la version modifiée locale. Il s'avère que c'est parce que <package>-versionpour ce package obtient la version packages.el.
ntc2
3

Bonne question! La réponse est que jusqu'à présent, il n'y avait pas de bonne réponse, car aucun des gestionnaires de packages existants n'a été conçu pour ce cas d'utilisation (à l'exception de Borg , mais Borg n'essaie pas de gérer d'autres opérations courantes de gestion de packages comme la gestion des dépendances) .

Mais maintenant, il existe straight.elun gestionnaire de packages de nouvelle génération pour Emacs qui résout ce problème de manière aussi complète que possible. Avertissement: j'ai écrit straight.el!

Après avoir inséré l' extrait de bootstrap , l'installation d'un package est aussi simple que

(straight-use-package 'magit)

Cela clonera le référentiel Git pour Magit, construira le paquet en associant ses fichiers à un répertoire séparé, compilera des octets, générera et évaluera les chargements automatiques et configurera load-pathcorrectement. Bien sûr, si le package est déjà cloné et construit, rien ne se passe et votre temps d'initialisation ne souffre pas.

Comment apportez-vous des modifications à Magit? C'est trivial! Utilisez simplement M-x find-functionou M-x find-librarypour accéder au code source et pirater! Vous pouvez évaluer vos modifications pour les tester en direct, comme c'est la pratique générale pour le développement Emacs Lisp, et lorsque vous redémarrez Emacs, le package sera automatiquement reconstruit, recompilé, etc. C'est complètement automatique et infaillible.

Lorsque vous êtes satisfait de vos modifications, il vous suffit de valider, de pousser et de faire une demande d'extraction. Vous avez un contrôle total sur vos packages locaux. Mais votre configuration peut toujours être reproductible à 100%, car vous pouvez demander straight.elde créer un fichier de verrouillage qui enregistre les révisions Git de tous vos packages, y compris straight.ellui-même, MELPA, etc.

straight.elpeut installer n'importe quel package à partir de MELPA, GNU ELPA ou EmacsMirror. Mais il a également une recette DSL très flexible qui vous permet d'installer de n'importe où, ainsi que de personnaliser la façon dont le package est construit. Voici un exemple qui montre certaines des options:

(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.ela une documentation ridiculement complète. Lisez tout sur GitHub .

Radon Rosborough
la source
2

Ce sont toutes de bonnes questions!

Emacs fonctionne sur un modèle d'image mémoire, où le chargement de nouveau code modifie l'image mémoire de l'instance en cours d'exécution. La définition de nouvelles fonctions et variables est facile à annuler, si vous en gardez une liste, mais il y a beaucoup d'effets secondaires qu'un module pourrait avoir que vous voudriez annuler. Il semble cependant que unload-featurecela soit plutôt bon.

Je pense que ce que vous allez vouloir faire est une combinaison de codage en direct et de relance occasionnelle d'Emacs, en chargeant le module sur lequel vous travaillez à partir de votre branche plutôt qu'à partir de l'endroit où il est installé. Si vous vous retrouvez avec beaucoup de ces branches, vous voudrez peut-être un script shell qui lance emacs avec le bon load-pathpour celui sur lequel vous travaillez en ce moment. En tout cas, je ne renommerais pas le package; Je pense que ce serait encore plus déroutant car emacs pourrait alors les charger tous les deux.

Lorsque vous développez vos patchs, vous pouvez commencer par redéfinir simplement les fonctions que vous modifiez directement dans votre session Emacs en direct. Cela vous permet de tester les nouvelles définitions immédiatement, sans quitter Emacs. Plus précisément, lorsque vous modifiez un fichier elisp, vous pouvez utiliser C-M-x( eval-defun) pour évaluer la fonction actuelle dans votre session Emacs actuelle. Vous pouvez ensuite l'appeler pour vous assurer que cela fonctionne. Si vous changez quelque chose qui se produit au démarrage d'Emacs, vous devrez probablement démarrer et arrêter Emacs pour le tester; vous pouvez le faire en démarrant et en arrêtant un processus Emacs distinct afin que votre session d'édition ne soit pas interrompue.

db48x
la source
2

Je ne pense pas qu'il y ait encore une bonne réponse à cela (je m'attends à ce que vous puissiez obtenir une solution partielle avec Cask, mais je ne la connais pas suffisamment pour vous donner une bonne réponse en l'utilisant; j'espère que quelqu'un d'autre le fera), mais voici ce que je fais (j'utilise rarement un paquet Elisp sans y apporter de modifications locales, c'est donc vraiment ma façon "normale"):

  • cd ~/src; git clone ..../elpa.git
  • pour chaque colis cd ~/src/elisp; git clone ....thepackage.git
  • cd ~/src/elpa/packages; ln -s ~/src/elisp/* .
  • cd ~/src/elpa; make
  • dans votre ~/.emacsannonce

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

De cette façon, tous les packages sont installés "directement depuis Git", un simple cd ~/src/elpa; makerecompilera ceux qui en ont besoin, et C-h o thepackage-functionpassera à un fichier source qui est sous Git.

Pour "basculer entre elle et une branche stable sur un coup de tête", vous devrez le faire git checkout <branch>; cd ~/src/elpa; make; et si vous voulez que cela affecte l'exécution des sessions Emacs, cela demandera plus de travail. Je recommande généralement de ne pas l'utiliser unload-featuresauf dans des situations exceptionnelles (c'est une bonne fonctionnalité, mais ce n'est pas assez fiable actuellement).

Il ne satisfait pas non plus à bon nombre de vos exigences. Et il a quelques inconvénients supplémentaires, principalement le fait que le clone Git de nombreux packages ne correspond pas tout à fait à la présentation et au contenu attendus par le makefile d'elpa.git, vous devrez donc commencer par peaufiner ces packages (généralement des choses qui ont à voir avec <pkg>-pkg.el, puisque le makefile d'elpa.git s'attend à construire ce fichier <pkg>.elplutôt que de le fournir, mais plus problématique, la compilation est effectuée différemment, donc parfois vous devez jouer avec le requires).

Oh et bien sûr, cela signifie essentiellement que vous installez ces packages à la main, vous devez donc faire attention aux dépendances. Cette configuration interagit correctement avec les autres packages installés par package-installTho, donc ce n'est pas si terrible.

Stefan
la source
2

Les autres réponses à cette question, y compris mon autre réponse , parlent de patcher un paquet Emacs en apportant des modifications à son code. Mais les gens qui trouvent cette question via Google pourraient penser à autre chose lorsqu'ils disent "patcher un paquet Emacs" - à savoir, remplacer son comportement sans avoir à modifier son code source.

Les mécanismes pour ce faire comprennent, par ordre croissant d'agressivité:

Malgré la puissance des deux premières options, je me suis retrouvé à prendre la troisième route assez souvent, car il n'y a parfois pas d'autre moyen. Mais alors la question est, et si la définition de fonction d'origine change? Vous n'auriez aucun moyen de savoir que vous deviez mettre à jour la version de cette définition que vous aviez copiée et collée dans votre fichier init!

Parce que je suis obsédé par les correctifs, j'ai écrit le package el-patch, qui résout ce problème de manière aussi complète que possible. L'idée est que vous définissez des différences basées sur l'expression s dans votre fichier init, qui décrivent à la fois la définition de la fonction d'origine et vos modifications. Cela rend vos correctifs beaucoup plus lisibles et permet également el-patchde valider ultérieurement si la définition de fonction d'origine a été mise à jour depuis que vous avez créé votre correctif. (Si oui, il vous montrera les changements via Ediff!) Citant de la documentation:

Considérez la fonction suivante définie dans le company-statisticspackage:

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

Supposons que nous voulons changer le troisième argument de nilen 'nomessage, pour supprimer le message qui est enregistré lors du company-statisticschargement de son fichier de statistiques. Nous pouvons le faire en plaçant le code suivant dans notre init.el:

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

Un simple appel el-patch-defunau lieu de defundéfinit un patch sans opération: c'est-à-dire qu'il n'a aucun effet (enfin, pas tout à fait - voir plus loin ). Cependant, en incluant des directives de correctif , vous pouvez rendre la version modifiée de la fonction différente de l'original.

Dans ce cas, nous utilisons la el-patch-swapdirective. Le el-patch-swapformulaire est remplacé par nildans la définition d'origine (c'est-à-dire la version qui est comparée à la définition "officielle" dans company-statistics.el), et par 'nomessagedans la définition modifiée (c'est-à-dire la version qui est réellement évaluée dans votre fichier init).

Radon Rosborough
la source
0

Lorsque vous apportez beaucoup de changements, je pense que vous devriez utiliser straight.el, voir la réponse de Radon Rosborough .

Si vous souhaitez simplement apporter une modification unique, supposons que pour un projet appelé fork-mode, procédez comme suit:

  • Créez un répertoire pour stocker le git mkdir ~/.emacs.d/lisp-gits
  • Faites une fourchette du projet que vous souhaitez changer, dites à https://github.com/user/fork-mode
  • Clonez votre fourche cd ~/.emacs.d/lisp-gits && git clone [email protected]:user/fork-mode.git

Écrivez le code suivant dans votre .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"))))

Vous pouvez maintenant utiliser le mode emacs, en utilisant C-h fpour trouver les fonctions que vous souhaitez modifier. Vous remarquerez que lorsque le package est installé dans lisp-gits, vous allez y accéder. Utilisez magit ou d'autres commandes git pour valider / pousser les modifications, puis utilisez github pour envoyer vos demandes d'extraction.

Une fois vos demandes de tirage acceptées, vous pouvez simplement supprimer le projet ~/.emacs.d/lisp-gitset laisser le gestionnaire de packages faire son travail.

préparer
la source