Byte-Kompilierung eines Pakets mit mehreren Dateien: „Es ist nicht bekannt, dass die Funktion definiert ist.“

7

Stellen Sie sich vor, ich habe die folgenden Dateien in meinem (lächerlichen) Paket:

Datei test1.el:

;;; test1.el ---                                   

;;; Code:

(defvar test-var1)

(defun test-fun1 (test)
  nil)

(require 'test2 "./test2.el)

(provide 'test1)
;;; test1.el ends here

Datei test2.el:

;;; test2.el ---  

;;; Code:

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

Wenn ich dann renne:

emacs -batch -f batch-byte-compile *.el

Ich erhalte folgendes Ergebnis:

Compiling .../test1.el...
Wrote .../test1.elc
Compiling .../test2.el...

In test-fun2:
test2.el:9:15:Warning: reference to free variable `test-var1'

In end of data:
test2.el:14:1:Warning: the function `test-fun1' is not known to be defined.
Wrote .../test2.elc

Ich verstehe, warum diese Warnungen erscheinen, und ich verstehe, dass es sich nur um Warnungen handelt. Es wäre jedoch leicht, einen Tippfehler in einem Funktionsnamen zu übersehen, wenn alle Warnungen dieser Art verworfen würden.

Ich dachte irgendwie, dass das Hinzufügen einer (require 'test2)Zeile das test2.elProblem beheben sollte. In diesem Fall bekomme ich jedoch:

Compiling .../test1.el...

In toplevel form:
test1.el:10:1:Error: Recursive `require' for feature `test2'
Compiling .../test2.el...

In toplevel form:
test2.el:5:1:Error: Recursive `require' for feature `test1'

Das ist kryptisch, weil ich dachte, dass requirees genau darum geht, rekursives Laden zu vermeiden. Ich gehe davon aus, dass requiresich das wie loadwährend der Kompilierungszeit verhält .

Was ist ein guter (und sicherer) Weg, um diese Warnungen loszuwerden?

Das Handbuch gibt einen Workaround (ich poste es unten als eine Antwort, die besser als nichts ist), aber letztendlich möchte ich, dass die Lösung eher automatisch ist (ohne dass ich alle Funktionen und Variablen auflisten muss, die ich benötige jede Datei).

Die ideale Lösung wäre in Emacs eingebaut oder mit Cask versehen. Wenn es nicht existiert, werde ich natürlich nehmen, was verfügbar ist.

T. Verron
quelle

Antworten:

8

Über erfordern

requiresoll nicht das rezessive Laden vermeiden, sondern das wiederholte Laden. Also nein, es löst Ihr Problem hier nicht.

Über das Problem

Der richtige Weg, dies zu erreichen (meiner Meinung nach), wäre, die gegenseitige Abhängigkeit zu vermeiden.

Die test1Datei in Ihrem Beispiel hat keinen Grund zur Anforderung test2. Auch wenn dies für Ihr tatsächliches Paket nicht zutrifft, können Sie möglicherweise neu delegieren, wie Sie Code zwischen den Dateien delegieren. Es ist im Allgemeinen möglich, eine gegenseitige Abhängigkeit zwischen Ihren Dateien zu vermeiden.

Workarounds

  1. Wenn die gegenseitige Abhängigkeit nicht vermieden werden kann, wird im Handbuch eine Lösung erwähnt. Sie müssen für jede Funktion / Variable, die Sie benötigen, Zeilen wie die folgenden hinzufügen.

    (declare-function test-fun1 "./test1.el")
    (defvar test-var1)
    
  2. Eine andere Möglichkeit besteht darin, requiredie Dateien nur bedingt zu verwenden. Fügen Sie der Datei so etwas hinzu 1:

    (defvar test1-is-loading t)
    (unless (and (boundp 'test2-is-loading)
                 test2-is-loading)
      (require 'test2))
    

    Und so etwas zu archivieren 2:

    (defvar test2-is-loading t)
    (unless (and (boundp 'test1-is-loading)
                 test1-is-loading)
      (require 'test1))
    
Malabarba
quelle
Die realistische Situation ist ein Paket, in dem die Hauptdatei einige Dinge deklariert (Variablen wie eine Moduszuordnung, ausführbare Pfade, Gruppe anpassen), andere Dateien benötigt, die eine Reihe von Funktionen für das Paket bereitstellen, und alles zusammenfügt (zumindest zu einem -modeFunktion). Das Beispiel ist ein Spielzeugbeispiel, dessen Verwendung test-fun2das Problem überhaupt nicht ändern würde.
T. Verron
1
@ T.Verron Ja, ich bin davon ausgegangen, dass Ihr Beispiel nicht ganz korrekt ist, und habe Problemumgehungen angeboten. Trotzdem stehe ich zu meiner Aussage, dass das Paket besser gestaltet werden kann. In Ihrem realistischen Beispiel gibt es keinen Grund, warum die Datei, die alles zusammensetzt, auch diejenige sein sollte, die Variablen und Gruppen definiert. Erstellen Sie eine zusätzliche Datei, die diese Definitionen enthält (z. B. test-variables), und für diese Datei müssen keine anderen Dateien erforderlich sein.
Malabarba
Wie ich in meiner ersten Antwort erwähnt habe, funktioniert Ihre erste Problemumgehung gut, ist aber ziemlich langwierig. Das zweite klingt vielversprechend, wird aber etwas komplizierter: Die Anweisungen müssen in eval-when-compile's eingeschlossen werden, und die Pakete müssen ihre Variable auf nilam Ende der Datei setzen (da alle Dateien kompiliert sind eine einzelne Sitzung). Es hat auch den Vorteil, mir zu zeigen, warum genaues rekursives Laden komplizierter zu vermeiden ist als wiederholtes Laden.
T. Verron
Und danke für den Refactoring-Vorschlag, das würde in der Tat funktionieren.
T. Verron
4

Ihr Beispiel ist komisch:

  • Sie requiretesten2 am Ende von test1, während require"immer" am Anfang einer Datei stehen sollte.
  • Ihr test1 ruft keine test2-Funktionen auf, daher muss test2 nicht funktionieren (daher ist requirees das unnötig), und OTOH Ihr test2 ruft test1-Funktionen auf, benötigt also test1, schlägt jedoch fehl require.

IOW, du hast dein requires rückwärts.

Stefan
quelle
In einem realen Beispiel test1würde natürlich die durch definierte Funktion verwendet test2, und das Paket würde nur durch test1(durch Autoload) geladen . Sollte diese Antwort nicht stattdessen ein Kommentar sein? Es wird nur darauf hingewiesen, dass mein Beispiel eher schlecht gewählt ist und ansonsten keine Antwort auf die Frage liefert.
T. Verron
@ T.Verron Diese Antwort beantwortete den von Ihnen angebotenen speziellen Fall. Die Tatsache, dass es ein schlecht gewähltes Beispiel war, ist nicht seine Schuld. ;-)
Malabarba
Da beide Antworten diesen Refactoring-Punkt ansprechen, muss ich davon ausgehen, dass es sich angesichts der Frage um einen gültigen Punkt handelt. Ihre hatten den Vorteil, die allgemeinen Probleme sowie die Hinweise im Kommentar für meinen speziellen Fall anzusprechen. Ist dies wirklich wahrscheinlich für jemanden mit einem echten Anwendungsfall nützlich?
T. Verron
Und mit einer kurzfristigeren Sichtweise hätte ich die Frage bearbeiten können, um sie (etwas) realistischer zu machen, wenn sie als Kommentar veröffentlicht worden wäre. Da es sich jedoch um eine Antwort handelt, kann ich die Frage nicht bearbeiten, ohne sie zu einer anderen Frage zu machen (anders, da eine frühere Antwort nicht mehr anwendbar wäre). Imo, diese Antwort fällt genau in den Bereich der Kommentare: Bitte um Klarstellung (in diesem Fall eine Bestätigung, dass das minimale Beispiel wirklich repräsentativ für das Problem ist) vom Fragesteller.
T. Verron
3

Das Handbuch schlägt das Hinzufügen declare-functionund defvarLinien vor.

Die resultierende test2Datei lautet:

;;; test2.el ---  

;;; Code:

(declare-function test-fun1 "./test1.el")
(defvar test-var1)

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

Dies muss jedoch für alle Funktionen und alle Variablen erfolgen, die in "übergeordneten" Dateien definiert sind.

T. Verron
quelle