Go: Vorschlag: cmd/go: Unterstützung der Einbettung statischer Assets (Dateien) in Binärdateien

Erstellt am 4. Dez. 2019  ·  176Kommentare  ·  Quelle: golang/go

Es gibt viele Tools zum Einbetten statischer Asset-Dateien in Binärdateien:

Tatsächlich listet https://tech.townsourced.com/post/embedding-static-files-in-go/ mehr auf:

Vorschlag

Ich denke, es ist an der Zeit, dies einmal gut zu machen und die Duplizierung zu reduzieren, indem die offizielle Unterstützung für das Einbetten von Dateiressourcen in das cmd/go-Tool hinzugefügt wird.

Probleme mit der aktuellen Situation:

  • Es gibt zu viele Werkzeuge
  • Die Verwendung einer go:generate-basierten Lösung bläst den Git-Verlauf mit einer zweiten (und etwas größeren) Kopie jeder Datei auf.
  • go:generate nicht zu verwenden bedeutet, nicht go install -fähig zu sein oder die Leute dazu zu bringen, ihre eigenen Makefiles zu schreiben usw.

Ziele:

  • Generierte Dateien nicht einchecken
  • keine *.go-Dateien generieren (zumindest nicht im Arbeitsbereich des Benutzers)
  • Lass go install / go build die Einbettung automatisch durchführen
  • Lassen Sie den Benutzer pro Datei/Glob wählen, welche Zugriffsart benötigt wird (z. B. []Byte, func() io.Reader , io.ReaderAt usw.)
  • Eventuell Assets in der Binärdatei komprimiert speichern (zB wenn der Benutzer nur ein io.Reader )? ( edit : aber wahrscheinlich nicht; siehe Kommentare unten)
  • Keine Codeausführung zur Kompilierzeit ; das ist eine langjährige Go-Politik. go build oder go install können keinen beliebigen Code ausführen, genauso wie go:generate nicht automatisch zur Installationszeit ausgeführt wird.

Die beiden wichtigsten Implementierungsansätze sind //go:embed Logo logo.jpg oder ein bekanntes Paket ( var Logo = embed.File("logo.jpg") ).

go:embed-Ansatz

Für einen go:embed Ansatz könnte man sagen, dass jede go/build -ausgewählte *.go Datei Folgendes enthalten kann:

//go:embed Logo logo.jpg

Was, sagen wir, kompiliert zu:

func Logo() *io.SectionReader

(Hinzufügen einer Abhängigkeit zum Paket io )

Oder:

//go:embedglob Assets assets/*.css assets/*.js

kompilieren, sagen Sie:

var Assets interface{
     Files() []string
     Open func(name string) *io.SectionReader
} = runtime.EmbedAsset(123)

Das ist natürlich nicht ganz ausgearbeitet. Es müsste auch etwas für komprimierte Dateien geben, das nur ein io.Reader ergibt.

Einbettungspaket-Ansatz

Der andere Ansatz auf hoher Ebene besteht darin, keine magische //go:embed Syntax zu verwenden und stattdessen den Benutzern einfach den Go-Code in einem neuen "embed" oder "golang.org/x/foo/embed" Paket schreiben zu lassen:

var Static = embed.Dir("static")
var Logo = embed.File("images/logo.jpg")
var Words = embed.CompressedReader("dict/words")

Lassen Sie dann cmd/go die Aufrufe von embed.Foo("foo/*.js") usw. erkennen und glob die Arbeit in cmd/go und nicht zur Laufzeit erledigen. Oder vielleicht könnten bestimmte Build-Tags oder -Flags dazu führen, dass stattdessen Dinge zur Laufzeit ausgeführt werden. Perkeep (oben verlinkt) hat einen solchen Modus, der schön ist, um die inkrementelle Entwicklung zu beschleunigen, wenn Sie sich nicht darum kümmern, eine große Binärdatei zu verknüpfen.

Anliegen

  • Wählen Sie einen Stil (//go:embed* im Vergleich zu einem magischen Paket).
  • Bestimmte Dateien blockieren?

    • Blockieren wahrscheinlich ../../../../../../../../../../etc/shadow

    • Vielleicht blockieren Sie auch das Erreichen von .git

Proposal Proposal-Hold

Hilfreichster Kommentar

@robpike und ich haben vor Jahren einen Vorschlag dafür

Ich bin nicht überzeugt von der Komplexität eines "komprimierten vs. nicht"-Reglers. Wenn wir das tun, werden die Leute wollen, dass wir die Kontrolle über die Komprimierung, Komprimierungsstufe usw. Alles, was wir hinzufügen müssen, ist die Möglichkeit, eine Datei mit einfachen Bytes einzubetten. Wenn Benutzer komprimierte Daten in dieser Datei speichern möchten, ist dies großartig, die Details liegen bei ihnen und es wird auf Go-Seite überhaupt keine API benötigt.

Alle 176 Kommentare

Es lohnt sich zu überlegen, ob embedglob einen vollständigen Dateibaum unterstützen sollte, vielleicht unter Verwendung der ** Syntax, die von einigen Unix-Shells unterstützt wird.

Einige Leute benötigen die Möglichkeit, die eingebetteten Assets mit HTTP mit dem http.FileServer .

Ich persönlich verwende entweder mjibson/esc (was das tut) oder in einigen Fällen meine eigene Dateieinbettungsimplementierung, die Dateien umbenennt, um eindeutige Pfade zu erstellen und eine Zuordnung von den ursprünglichen Pfaden zu den neuen hinzufügt, zB "/js/bootstrap.min.js": "/js/bootstrap.min.827ccb0eea8a706c4c34a16891f84e7b.js" . Dann können Sie diese Karte in den Vorlagen wie folgt verwenden: href="{{ static_path "/css/bootstrap.min.css" }}" .

Ich denke, eine Folge davon wäre, dass es nicht trivial wäre, herauszufinden, welche Dateien zum Erstellen eines Programms erforderlich sind.

Der //go:embed Ansatz führt auch eine weitere Komplexitätsebene ein. Sie müssten die magischen Kommentare analysieren, um den Code überhaupt zu überprüfen. Der Ansatz "Paket einbetten" scheint für die statische Analyse freundlicher zu sein.

(Hier nur laut grübeln.)

@opennota ,

würde die Fähigkeit benötigen, die eingebetteten Assets mit HTTP unter Verwendung von http.FileServer .

Ja, der erste Link oben ist ein Paket, das ich geschrieben habe ( 2011, vor Go 1 ) und immer noch verwende, und es unterstützt die Verwendung von http.FileServer: https://godoc.org/perkeep.org/pkg/fileembed#Files.Open

@cespare ,

Der //go:embed-Ansatz führt auch eine weitere Komplexitätsebene ein. Sie müssten die magischen Kommentare analysieren, um den Code überhaupt zu überprüfen. Der Ansatz "Paket einbetten" scheint für die statische Analyse freundlicher zu sein.

Ja, guter Punkt. Das ist ein sehr starkes Argument für die Verwendung eines Pakets. Es macht es auch lesbarer und dokumentierbarer, da wir alles mit regulärem godoc dokumentieren können, anstatt tief in den Dokumenten von cmd/go.

@bradfitz - Möchten Sie dies https://github.com/golang/go/issues/3035 schließen ?

@agnivade , danke, dass du das gefunden

Wenn wir das magische Paket verwenden, könnten wir den Trick des nicht exportierten Typs verwenden, um sicherzustellen, dass Aufrufer Kompilierzeitkonstanten als Argumente übergeben: https://play.golang.org/p/RtHlKjhXcda.

(Dies ist die Strategie, auf die hier verwiesen wird: https://groups.google.com/forum/#!topic/golang-nuts/RDA9Hag8RZw/discussion)

Eine Sorge, die ich habe, ist, wie es mit einzelnen oder allen Assets umgehen würde, die zu groß sind, um in den Speicher zu passen, und ob es vielleicht ein Build-Tag oder eine Zugriffsoption pro Datei geben würde, um zwischen der Priorisierung der Zugriffszeit gegenüber dem Speicherbedarf oder einer Mittelwegimplementierung zu wählen.

Die Art und Weise, wie ich dieses Problem gelöst habe (weil ich natürlich auch meine eigene Implementierung habe :) ) besteht darin, eine http.FileSystem-Implementierung bereitzustellen, die alle eingebetteten Assets bedient. Auf diese Weise sind Sie nicht auf magische Kommentare angewiesen, um den Typechecker zu besänftigen, die Assets können problemlos über http bereitgestellt werden, eine Fallback-Implementierung kann für Entwicklungszwecke (http.Dir) bereitgestellt werden, ohne den Code zu ändern, und das Finale Die Implementierung ist sehr vielseitig, da http.FileSystem einiges abdeckt, nicht nur beim Lesen von Dateien, sondern auch beim Auflisten von Verzeichnissen.

Man kann immer noch magische Kommentare oder was auch immer verwenden, um anzugeben, was eingebettet werden muss, obwohl es wahrscheinlich einfacher ist, alle Globs über eine reine Textdatei anzugeben.

@AlexRouSg Dieser Vorschlag gilt nur für Dateien, die direkt in die endgültige ausführbare Datei aufgenommen werden können. Es wäre nicht angemessen, dies für Dateien zu verwenden, die zu groß sind, um in den Speicher zu passen. Es gibt keinen Grund, dieses Tool zu komplizieren, um diesen Fall zu handhaben. Verwenden Sie in diesem Fall dieses Tool einfach nicht.

@ianlancetaylor , ich denke, die Unterscheidung, die @AlexRouSg machte, war die Bereitstellung der Dateien als globale []byte s (nicht auslagerbarer, potenziell beschreibbarer Speicher) und die Bereitstellung einer schreibgeschützten On-Demand-Ansicht eines ELF-Abschnitts, der dies kann normalerweise live auf der Festplatte (in der ausführbaren Datei), wie über einen Open Aufruf, der ein *io.SectionReader zurückgibt. (Ich möchte nicht http.File oder http.FileSystem in cmd/go oder Runtime einbacken... net/http kann einen Adapter bereitstellen.)

@bradfitz sowohl http.File selbst ist eine Schnittstelle ohne technische Abhängigkeiten zum http Paket. Es kann eine gute Idee für jede Open Methode sein, eine Implementierung bereitzustellen, die dieser Schnittstelle entspricht, da sowohl die Stat als auch die Readdir Methode für solche Assets sehr nützlich sind

@urandom , es konnte

@robpike und ich haben vor Jahren einen Vorschlag dafür

Ich bin nicht überzeugt von der Komplexität eines "komprimierten vs. nicht"-Reglers. Wenn wir das tun, werden die Leute wollen, dass wir die Kontrolle über die Komprimierung, Komprimierungsstufe usw. Alles, was wir hinzufügen müssen, ist die Möglichkeit, eine Datei mit einfachen Bytes einzubetten. Wenn Benutzer komprimierte Daten in dieser Datei speichern möchten, ist dies großartig, die Details liegen bei ihnen und es wird auf Go-Seite überhaupt keine API benötigt.

Ein paar Gedanken:

  • Es sollte nicht möglich sein, eine Datei außerhalb des Moduls einzubetten, das die Einbettung durchführt. Wir müssen sicherstellen, dass die Dateien bei der Erstellung Teil von Modul-ZIP-Dateien sind, was auch bedeutet, dass keine symbolischen Links, Groß-/Kleinschreibungskonflikte usw. enthalten sind. Wir können den Algorithmus, der ZIP-Dateien erstellt, nicht ändern, ohne die Summen zu brechen.
  • Ich denke, es ist einfacher, die Einbettung auf dasselbe Verzeichnis (wenn //go:embed Kommentare verwendet werden) oder ein bestimmtes Unterverzeichnis (wenn static verwendet wird) zu beschränken. Dies macht es viel einfacher, die Beziehung zwischen Paketen und eingebetteten Dateien zu verstehen.

In jedem Fall blockiert dies das Einbetten von /etc/shadow oder .git . Beides kann nicht in eine Modul-Zip eingebunden werden.

Im Allgemeinen mache ich mir Sorgen, den Umfang des Befehls go zu sehr zu erweitern. Die Tatsache, dass es so viele Lösungen für dieses Problem gibt, bedeutet jedoch, dass es wahrscheinlich eine offizielle Lösung geben sollte.

Ich kenne go_embed_data und go-bindata (von denen es mehrere Forks gibt), und dies scheint diese Anwendungsfälle abzudecken. Gibt es wichtige Probleme, die die anderen lösen und die hier nicht abgedeckt sind?

Das Blockieren bestimmter Dateien sollte nicht allzu schwer sein, insbesondere wenn Sie ein static oder embed Verzeichnis verwenden. Symlinks könnten dies etwas verkomplizieren, aber Sie können einfach verhindern, dass etwas außerhalb des aktuellen Moduls oder, wenn Sie sich auf GOPATH befinden, außerhalb des Pakets eingebettet wird, das das Verzeichnis enthält.

Ich bin nicht besonders ein Fan von Kommentaren, die in Code kompiliert werden, aber ich finde auch das Pseudo-Paket, das die Kompilierung beeinflusst, etwas seltsam. Wenn der Verzeichnisansatz nicht verwendet wird, kann es vielleicht etwas sinnvoller sein, eine Art embed -Deklaration der obersten Ebene tatsächlich in die Sprache integriert zu haben. Es würde ähnlich wie import funktionieren, würde aber nur lokale Pfade unterstützen und würde einen Namen erfordern, dem es zugewiesen werden kann. Zum Beispiel,

embed ui "./ui/build"

func main() {
  file, err := ui.Open("version.txt")
  if err != nil {
    panic(err)
  }
  version, err = ioutil.ReadAll(file)
  if err != nil {
    panic(err)
  }
  file.Close()

  log.Printf("UI Version: %s\n", bytes.TrimSpace(version))
  http.ListenAndServe(":8080", http.EmbeddedDir(ui))
}

Edit: Du hast mich geschlagen, @jayconrod.

Um https://github.com/golang/go/issues/35950#issuecomment -561703346 zu erweitern, gibt es ein Rätsel über die exponierte API. Die offensichtlichen Möglichkeiten, die Daten verfügbar zu machen, sind []byte , string und Read -ish-Schnittstellen.

Der typische Fall ist, dass die eingebetteten Daten unveränderlich sein sollen. Alle Schnittstellen, die []byte (einschließlich io.Reader , io.SectionReader usw.), müssen jedoch entweder (1) eine Kopie erstellen, (2) Veränderlichkeit zulassen oder (3) unveränderlich sein, obwohl es ein []byte . Das Offenlegen der Daten als string s löst dies, jedoch auf Kosten einer API, die am Ende sowieso oft kopiert werden muss, da viel Code, der eingebettete Dateien verbraucht, schließlich auf die eine oder andere Weise Byte-Slices erfordert.

Ich würde Route (3) vorschlagen: unveränderlich sein, obwohl es ein []byte . Sie können dies kostengünstig erzwingen, indem Sie ein schreibgeschütztes Symbol für das Backing-Array verwenden. Auf diese Weise können Sie auch die gleichen Daten wie ein []byte und ein string ; Versuche, die Daten zu mutieren, schlagen fehl. Der Compiler kann die Unveränderlichkeit nicht nutzen, aber das ist kein allzu großer Verlust. Dies ist etwas, das die Toolchain-Unterstützung auf den Tisch bringen kann, was (soweit ich weiß) keines der vorhandenen Codegen-Pakete tut.

(Ein Codegen-Paket eines Drittanbieters könnte dies tun, indem es eine generische Assembly-Datei mit DATA Symbolen generiert, die als schreibgeschützt markiert sind, und dann kurze bogenspezifische Assembly-Dateien, die diese Symbole in Form von string verfügbar machen s und []byte s. Ich habe CL 163747 speziell für diesen Anwendungsfall geschrieben,

Ich bin mir nicht sicher, wovon Sie in Bezug auf die Unveränderlichkeit sprechen. io.Reader erzwingt bereits Unveränderlichkeit. Das ist der springende Punkt. Wenn Sie Read(buf) aufrufen, kopiert es Daten in den Puffer, den _Sie_ bereitgestellt haben. Eine Änderung von buf danach hat keine Auswirkung auf die Interna von io.Reader .

Ich stimme @DeedleFake zu. Ich möchte keine Spiele mit magischen []byte Array-Backings spielen. Es ist in Ordnung, aus der Binärdatei in vom Benutzer bereitgestellte Puffer zu kopieren.

Nur eine weitere Falte hier - ich habe ein anderes Projekt, das DTrace-Quellcode (eingebettet) verwendet. Dies ist empfindlich auf Unterschiede zwischen \n und \r\n. (Wir können uns streiten, ob dies in DTrace eine dumme Sache ist oder nicht – das ist nebensächlich und so ist die Situation heute.)

Es ist sehr nützlich, dass mit Backtick versehene Strings beide als \n behandeln, unabhängig davon, wie sie im Quellcode erscheinen, und ich verlasse mich darauf mit einem go-generate, um den DTrace einzubetten.

Wenn dem go-Befehl also eine Einbettungsdatei hinzugefügt wird, würde ich sanft vorschlagen, dass Optionen zum Ändern der Behandlung von CR/CRLF sehr praktisch sein könnten, insbesondere für Leute, die möglicherweise auf verschiedenen Systemen entwickeln, auf denen die standardmäßigen Zeilenenden sein können ein Hingucker.

Wie bei der Komprimierung würde ich wirklich gerne bei "die Dateibytes in die Binärdatei kopieren" aufhören. CR/CRLF-Normalisierung, Unicode-Normalisierung, gofmt'ing, alles was woanders hingehört. Checken Sie die Dateien ein, die genau die gewünschten Bytes enthalten. (Wenn Ihre Versionskontrolle sie nicht in Ruhe lassen kann, checken Sie vielleicht gzipped Inhalt ein und zippen Sie sie zur Laufzeit.) Es gibt _viele_ Datei-Munging-Knöpfe, die wir uns vorstellen könnten, hinzuzufügen. Lass uns bei 0 aufhören.

Es kann zu spät sein, einen neuen reservierten Verzeichnisnamen einzuführen, so sehr ich es möchte.
(2014 war es noch nicht zu spät, aber jetzt ist es wahrscheinlich zu spät.)
Daher kann eine Art Opt-In-Kommentar erforderlich sein.

Angenommen, wir definieren einen Typ runtime.Files. Dann könntest du dir vorstellen zu schreiben:

//go:embed *.html (or static/* etc)
var files runtime.Files

Und dann rufen Sie zur Laufzeit einfach files.Open auf, um ein interface { io.ReadSeeker; io.ReaderAt } mit den Daten zurückzubekommen. Beachten Sie, dass die var nicht exportiert wird, sodass ein Paket nicht in den eingebetteten Dateien eines anderen Pakets herumstöbern kann.

Benennt TBD, aber was den Mechanismus angeht, scheint das genug zu sein, und ich sehe nicht, wie man es einfacher machen kann. (Vereinfachungen natürlich willkommen!)

Was auch immer wir tun, es muss auch möglich sein, mit Bazel und Gazelle zu unterstützen. Das würde bedeuten, dass Gazelle den Kommentar erkennt und eine Bazel-Regel schreibt, die die Globs sagt, und dann müssten wir ein Tool (go tool embedgen oder was auch immer) bereitstellen, um die zusätzliche Datei zu generieren, die in den Build aufgenommen werden soll (der go-Befehl würde tun Sie dies automatisch und zeigen Sie die zusätzliche Datei nie an). Das scheint einfach genug.

Wenn verschiedenes Munging nicht ausreicht, spricht das gegen die Nutzung dieser neuen Einrichtung. Es ist kein Hindernis für mich – ich kann go generieren wie bisher verwenden, aber es bedeutet, dass ich nicht von der neuen Funktion profitieren kann.

In Bezug auf Munging im Allgemeinen -- ich kann mir eine Lösung vorstellen, bei der jemand eine Implementierung einer Schnittstelle bereitstellt (so etwas wie ein Reader() auf der einen Seite und etwas zum Empfangen der Datei auf der anderen Seite -- vielleicht instanziiert mit einem io.Reader aus der Datei selbst) -- die der go cmd erstellen und ausführen würde, um die Datei vor dem Einbetten vorzufiltern. Dann können die Leute jeden gewünschten Filter bereitstellen. Ich kann mir vorstellen, dass einige Leute Quasi-Standardfilter wie eine dos2unix-Implementierung, Komprimierung usw. bereitstellen würden (Vielleicht sollten sie sogar verkettbar sein.)

Ich denke, es muss davon ausgegangen werden, dass, was auch immer der eingebettete Prozessor ist, er auf jedem Build-System kompilierbar sein muss, da go für diesen Zweck ein temporäres natives Tool erstellen würde.

Es kann zu spät sein, einen neuen reservierten Verzeichnisnamen einzuführen, so sehr ich es möchte. [...] eine Art Opt-in-Kommentar erforderlich sein.

Wenn die Dateien nur über ein spezielles Paket zugänglich sind, sagen wir runtime/embed , dann könnte das Importieren dieses Pakets das Opt-in-Signal sein.

Der Ansatz von io.Read könnte für konzeptionell einfache lineare Operationen wie strings.Contains (wie in cmd/go/internal/cfg ) oder , kritisch, template.Parse .

Für diese Anwendungsfälle scheint es ideal zu sein, dem Aufrufer die Wahl zu lassen, ob er das gesamte Blob als (vermutlich speicherabgebildetes) string oder als io.ReaderAt .

Das scheint jedoch mit dem allgemeinen runtime.Files Ansatz kompatibel zu sein: Das von runtime.Files.Open zurückgegebene Ding könnte eine ReadString() string Methode haben, die die speicherabgebildete Darstellung zurückgibt.

eine Art von Opt-in-Kommentar kann erforderlich sein.

Das könnten wir mit der go Version in der go.mod Datei tun. Vor 1.15 (oder was auch immer) würde das Unterverzeichnis static ein Paket enthalten, und bei 1.15 oder höher würde es eingebettete Assets enthalten.

(Das hilft aber im GOPATH Modus nicht wirklich.)

Ich bin nicht überzeugt von der Komplexität eines "komprimierten vs. nicht"-Reglers. Wenn wir das tun, werden die Leute wollen, dass wir die Kontrolle über die Komprimierung, Komprimierungsstufe usw. Alles, was wir hinzufügen müssen, ist die Möglichkeit, eine Datei mit einfachen Bytes einzubetten.

Obwohl ich das Streben nach Einfachheit schätze, sollten wir auch sicherstellen, dass wir die Bedürfnisse der Benutzer erfüllen.

12 von 14 der unter https://tech.townsourced.com/post/embedding-static-files-in-go/#comparison aufgeführten Tools unterstützen die Komprimierung, was darauf hindeutet, dass dies eine ziemlich häufige Anforderung ist.

Es ist wahr, dass man die Komprimierung als Pre-Build-Schritt außerhalb von Go durchführen könnte, aber das würde immer noch 1) ein Tool erfordern, um die Komprimierung durchzuführen 2) eine Art assets.zip Blob in vcs zu überprüfen 3) wahrscheinlich ein Dienstprogramm Bibliothek um die Einbettungs-API, um die Komprimierung rückgängig zu machen. An welcher Stelle ist unklar, was der Nutzen überhaupt ist.

Drei der im ursprünglichen Vorschlag aufgeführten Ziele waren:

  • Generierte Dateien nicht einchecken
  • make go install / go build führt die Einbettung automatisch durch
  • Speichern Sie Assets, die gegebenenfalls in der Binärdatei komprimiert sind

Wenn wir die zweite davon als "erfordert kein separates Tool zum Einbetten" lesen, dann werden komprimierte Dateien nicht direkt oder indirekt unterstützt, um alle drei dieser Ziele nicht zu erfüllen.

Muss das auf Paketebene sein? Modulebene scheint eine bessere Granularität zu sein, da höchstwahrscheinlich ein Modul = ein Projekt ist.

Da dieses Verzeichnis keinen Go-Code enthalten würde, könnte es etwa _static ?

† oder, wenn ja, würde es als beliebige Bytes behandelt, deren Name zufällig auf ".go" endet, anstatt als zu kompilierender Go-Code

Wenn es sich um ein spezielles Verzeichnis handelt, könnte die Logik einfach darin bestehen, alles und jedes in diesem Verzeichnisbaum aufzuschlürfen. Mit dem magischen Einbettungspaket können Sie beispielsweise embed.Open("img/logo.svg") tun, um eine Datei in einem Unterverzeichnis des Asset-Baums zu öffnen.

Saiten scheinen gut genug zu sein. Sie können leicht in []byte kopiert oder in ein Reader . Codegenerierung oder Bibliotheken könnten verwendet werden, um ausgefallenere APIs bereitzustellen und Dinge während init handhaben. Das könnte eine Dekompression oder das Erstellen eines http.FileSystem .

Verfügt Windows nicht über ein spezielles Format zum Einbetten von Assets. Sollte das beim Erstellen einer ausführbaren Windows-Datei verwendet werden? Wenn ja, hat dies irgendwelche Auswirkungen auf die Arten von Operationen, die bereitgestellt werden können?

Gitfs nicht vergessen 😂

Gibt es einen Grund, warum es nicht Teil von go build / link sein könnte ... zB go build -embed example=./path/example.txt und ein Paket, das den Zugriff darauf freigibt (zB embed.File("example") , anstatt go:embed ?

Dafür brauchst du aber einen Stub in deinem Code

@egonelbre Das Problem mit go build -embed ist, dass alle Benutzer es richtig verwenden müssen. Dies muss vollständig transparent und automatisch erfolgen; vorhandene go install oder go get Befehle können nicht aufhören, das Richtige zu tun.

@bradfitz Ich würde https://github.com/markbates/pkger über Packr empfehlen. Es verwendet die Standardbibliotheks-API zum Arbeiten mit Dateien.

func run() error {
    f, err := pkger.Open("/public/index.html")
    if err != nil {
        return err
    }
    defer f.Close()

    info, err := f.Stat()
    if err != nil {
        return err
    }

    fmt.Println("Name: ", info.Name())
    fmt.Println("Size: ", info.Size())
    fmt.Println("Mode: ", info.Mode())
    fmt.Println("ModTime: ", info.ModTime())

    if _, err := io.Copy(os.Stdout, f); err != nil {
        return err
    }
    return nil
}

Oder vielleicht könnten bestimmte Build-Tags oder -Flags dazu führen, dass stattdessen Dinge zur Laufzeit ausgeführt werden. Perkeep (oben verlinkt) hat einen solchen Modus, der schön ist, um die inkrementelle Entwicklung zu beschleunigen, wenn Sie sich nicht darum kümmern, eine große Binärdatei zu verknüpfen.

mjibson/esc tut dies auch, und es ist eine große Verbesserung der Lebensqualität bei der Entwicklung einer Webapp; Sie sparen nicht nur Zeit beim Verlinken, sondern vermeiden auch, dass die Anwendung neu gestartet werden muss, was je nach Implementierung der Webapp viel Zeit in Anspruch nehmen und/oder zusätzliche Schritte zum Testen Ihrer Änderungen erforderlich machen kann.

Probleme mit der aktuellen Situation:

  • Die Verwendung einer go:generate-basierten Lösung bläst den Git-Verlauf mit einer zweiten (und etwas größeren) Kopie jeder Datei auf.

Ziele:

  • Generierte Dateien nicht einchecken

Nun, dieser Teil lässt sich leicht lösen, indem Sie einfach die generierten Dateien zur .gitignore Datei oder einem Äquivalent hinzufügen. das habe ich immer gemacht...

Alternativ könnte Go einfach sein eigenes "offizielles" Einbettungstool haben, das standardmäßig auf go build läuft und die Leute auffordert, diese Dateien als Konvention zu ignorieren. Das wäre die weniger magische verfügbare Lösung (und abwärtskompatibel mit bestehenden Go-Versionen).

Ich bin hier nur am Brainstorming / denke laut ... aber ich mag die vorgeschlagene Idee insgesamt. 🙂

Da //go:generate Direktiven nicht automatisch auf go build das Verhalten von go build etwas inkonsistent erscheinen: //go:embed funktioniert automatisch, aber für //go:generate Sie müssen go generate manuell ausführen. ( //go:generate kann den go get Flow bereits unterbrechen, wenn es .go Dateien generiert, die für den Build benötigt werden).

//go:generate kann den Go-Get-Flow bereits unterbrechen, wenn es .go Dateien generiert, die für den Build benötigt werden

Ich denke, der übliche Ablauf dafür, und der, den ich im Allgemeinen verwendet habe, obwohl es etwas gewöhnungsbedürftig war, besteht darin, go generate vollständig als entwicklungsendes Tool zu verwenden und einfach die Dateien zu übertragen, die es erzeugt.

@bradfitz muss http.FileSystem selbst implementieren. Wenn die Implementierung einen Typ bereitstellt, der http.File implementiert, wäre es für jeden, einschließlich des http-Pakets stdlib, trivial, einen Wrapper um die Funktion Open bereitzustellen, der den Typ in http.File umwandelt http.FileSystem

@andreynering //go:generate und //go:embed sind jedoch sehr unterschiedlich. Dieser Mechanismus kann zur Build-Zeit nahtlos erfolgen, da er keinen beliebigen Code ausführt. Ich glaube, das macht es ähnlich wie cgo Code als Teil von go build generieren kann.

Ich bin nicht überzeugt von der Komplexität eines "komprimierten vs. nicht"-Reglers. Wenn wir das tun, werden die Leute wollen, dass wir die Kontrolle über die Komprimierung, Komprimierungsstufe usw. Alles, was wir hinzufügen müssen, ist die Möglichkeit, eine Datei mit einfachen Bytes einzubetten.

Obwohl ich das Streben nach Einfachheit schätze, sollten wir auch sicherstellen, dass wir die Bedürfnisse der Benutzer erfüllen.

12 von 14 der unter https://tech.townsourced.com/post/embedding-static-files-in-go/#comparison aufgeführten Tools unterstützen die Komprimierung, was darauf hindeutet, dass dies eine ziemlich häufige Anforderung ist.

Ich bin mir nicht sicher, ob ich dieser Argumentation zustimme.

Die von den anderen Bibliotheken durchgeführte Komprimierung unterscheidet sich vom Hinzufügen zu diesem Vorschlag darin, dass sie die Leistung bei nachfolgenden Builds nicht reduzieren, da die Alternativen im Allgemeinen vor dem Build und nicht während der Buildzeit generiert werden.

Niedrige Build-Zeiten sind ein klarer Mehrwert, da Go gegenüber anderen Sprachen und Komprimierung CPU-Zeit für einen geringeren Speicher-/Übertragungsbedarf eintauscht. Wenn viele Go-Pakete Komprimierungen auf go build ausführen, werden wir noch mehr Build-Zeit hinzufügen als die Zeit, die durch einfaches Kopieren von Assets während des Builds hinzugefügt wird. Ich bin skeptisch, die Komprimierung hinzuzufügen, weil andere dies tun. Solange das ursprüngliche Design eine zukünftige Erweiterung, die Unterstützung für zB Komprimierung hinzufügt, nicht absichtlich verhindert, erscheint es als unnötiges Hedging, es dort einzufügen, weil es etwas sein könnte, von dem einige profitieren könnten.

Es ist nicht so, dass das Einbetten von Dateien ohne Komprimierung nutzlos wäre, Komprimierung ist eine nette Sache, um die Binärgröße von vielleicht 100 MB auf 50 MB zu reduzieren – was großartig ist, aber auch kein klarer Dealbreaker für die Funktionalität für die meisten Anwendungen, die ich mir vorstellen kann . Vor allem nicht, wenn die meisten "schwereren" Assets Dateien wie JPEGs oder PNGs sind, die bereits ziemlich gut komprimiert sind.

Wie wäre es, die Komprimierung vorerst draußen zu lassen und sie hinzuzufügen, wenn sie tatsächlich von vielen Leuten vermisst wird? (und kann ohne unangemessene Kosten durchgeführt werden)

Um dem obigen Kommentar von

@mvdan Ich denke, eines meiner Bedenken ist, dass, wenn ich gesehen habe, das Einbetten zusammen mit einer anderen Vorverarbeitung erfolgt: Verkleinerung, Typoskript-Kompilierung, Datenkomprimierung, Bildzerkleinerung, Bildgröße ändern, Sprite-Blätter. Die einzige Ausnahme sind Websites, die nur html/template . Am Ende könnten Sie also sowieso eine Art "Makefile" verwenden oder den vorverarbeiteten Inhalt hochladen. In diesem Sinne würde ich denken, dass ein Befehlszeilen-Flag mit anderen Tools als Kommentaren besser funktioniert.

Ich denke, eines meiner Bedenken ist, dass, wenn ich gesehen habe, das Einbetten oft mit einer anderen Vorverarbeitung zusammenhängt: Verkleinerung, Typoskript-Kompilierung, Datenkomprimierung, Bildzerkleinerung, Bildgröße ändern, Sprite-Sheets. Die einzige Ausnahme sind Websites, die nur html/template verwenden.

Danke, das ist ein nützlicher Datenpunkt. Vielleicht ist die Notwendigkeit einer Komprimierung nicht so häufig, wie es aussah. Wenn das der Fall ist, stimme ich zu, dass es sinnvoll ist, es wegzulassen.

Es ist nicht so, dass das Einbetten von Dateien ohne Komprimierung nutzlos wäre, Komprimierung ist eine nette Sache, um die Binärgröße von vielleicht 100 MB auf 50 MB zu reduzieren – was großartig ist, aber auch kein klarer Dealbreaker für die Funktionalität für die meisten Anwendungen, die ich mir vorstellen kann .

Die Binärgröße ist für viele Go-Entwickler eine große Sache (https://github.com/golang/go/issues/6853). Go komprimiert DWARF-Debug-Informationen speziell, um die Binärgröße zu reduzieren, obwohl dies mit Kosten für die Linkzeit verbunden ist (https://github.com/golang/go/issues/11799, https://github.com/golang/go/ Ausgaben/26074). Wenn es eine einfache Möglichkeit gäbe, die Binärgröße zu halbieren, würden die Entwickler meiner Meinung nach diese Gelegenheit nutzen (obwohl ich bezweifle, dass die Gewinne hier annähernd so signifikant wären).

Das hilft im GOPATH-Modus aber nicht wirklich weiter

Wenn Sie sich im GOPATH-Modus befinden, gilt diese Funktion vielleicht einfach nicht, da ich mir vorstellen kann, dass das Go-Team nicht vorhat, für immer Feature-Parität für GOPATH anzubieten? Es gibt bereits Funktionen, die in GOPATH nicht unterstützt werden (z. B. Sicherheit mit Prüfsummen-DB, Herunterladen von Abhängigkeiten über einen Proxy-Server und semantische Importversionsverwaltung).

Wie @bcmills erwähnte, ist der statische Verzeichnisname in einer go.mod-Datei eine großartige Möglichkeit, diese Funktion in Go 1.15 einzuführen, da die Funktion in go.mod-Dateien mit einer <=go1.14-Klausel automatisch deaktiviert werden kann.

Dies bedeutet jedoch auch, dass Benutzer den statischen Verzeichnispfad manuell schreiben müssen.

Ich denke, das Herstellerverzeichnis und die _test.go-Konventionen sind großartige Beispiele dafür, wie sie die Arbeit mit Go und diesen beiden Funktionen viel einfacher gemacht haben.

Ich erinnere mich nicht, dass viele Leute nach der Option gefragt haben, den Namen des Herstellerverzeichnisses anzupassen oder die Möglichkeit zu haben, die Konvention _test.go in etwas anderes zu ändern. Aber wenn Go die _test.go-Funktion nie einführen würde, würde das Testen in Go heute ganz anders aussehen.

Daher bietet ein weniger allgemeiner Name als static möglicherweise bessere Chancen auf Nicht-Kollision und daher könnte ein konventionelles Verzeichnis (ähnlich wie Vendor und _test.go) eine bessere Benutzererfahrung im Vergleich zu magischen Kommentaren sein.

Beispiele für potenziell kollisionsarme Namen:

  • _embed - folgt der _test.go Konvention
  • go_binary_assets
  • .gobin folgt der .git-Konvention
  • runtime_files - damit es der runtime.Files Struktur entspricht

Zuletzt wurde das Verzeichnis vendor in Go 1.5 hinzugefügt. Sooo, vielleicht ist es nicht so schlimm, jetzt eine neue Convention hinzuzufügen? 😅

Ich denke, es sollte ein mmap-readonly []byte verfügbar machen. Einfacher Rohzugriff auf Seiten aus der ausführbaren Datei, die nach Bedarf vom Betriebssystem eingelagert werden. Alles andere kann darüber hinaus mit nur bytes.NewReader bereitgestellt werden.

Wenn dies aus irgendeinem Grund nicht akzeptabel ist, geben Sie bitte ReaderAt nicht nur ReadSeeker ; Letzteres ist trivial aus ersterem zu konstruieren, aber der andere Weg ist nicht so gut: Es würde einen Mutex benötigen, um den einzelnen Offset zu schützen und die Leistung zu ruinieren.

Es ist nicht so, dass das Einbetten von Dateien ohne Komprimierung nutzlos wäre, Komprimierung ist eine nette Sache, um die Binärgröße von vielleicht 100 MB auf 50 MB zu reduzieren – was großartig ist, aber auch kein klarer Dealbreaker für die Funktionalität für die meisten Anwendungen, die ich mir vorstellen kann .

Die Binärgröße ist für viele Go-Entwickler eine große Sache (#6853). Go komprimiert DWARF-Debug-Informationen speziell, um die Binärgröße zu reduzieren, auch wenn dies die Linkzeit kostet (#11799, #26074). Wenn es eine einfache Möglichkeit gäbe, die Binärgröße zu halbieren, würden die Entwickler meiner Meinung nach diese Gelegenheit nutzen (obwohl ich bezweifle, dass die Gewinne hier annähernd so signifikant wären).

Das ist auf jeden Fall ein berechtigter Punkt und ich kann sehen, wie mein Argument als Argument für die Nachlässigkeit in Bezug auf die Dateigrößen angesehen werden kann. Das war nicht meine Absicht. Mein Punkt ist eher die Bereitstellung dieser Funktion ohne Komprimierung, was für einige immer noch nützlich wäre, und sie könnten nützliches Feedback und Einblicke geben, wie die Komprimierung richtig hinzugefügt werden kann, damit sich dies langfristig richtig anfühlt. Die Assets können so anschwellen, dass die Debug-Informationen wahrscheinlich nicht ausreichen, und es ist für Entwickler von Paketen, die von anderen installiert/importiert werden, einfacher, die Build-Leistung unnötig zu reduzieren, wenn die Implementierung dies erleichtert.

Eine andere Möglichkeit wäre, die Komprimierung von Assets zu einem Build-Flag zu machen und den Kompromiss zwischen Build-Größe und Zeit eher dem Builder als dem Entwickler zu überlassen. Das würde die Entscheidung dem Endbenutzer der Binärdatei näher bringen, der entscheiden könnte, ob sich die Komprimierung lohnt. Oh, dies würde riskieren, eine größere Oberfläche für Unterschiede zwischen Entwicklung und Produktion zu schaffen, also ist es keine klare bessere Methode als alles andere und es ist nicht etwas, für das ich mich einsetzen möchte.

Mein aktuelles Tool zum Einbetten von Assets lädt Inhalte aus den Asset-Dateien, wenn es mit -tags dev . Eine solche Konvention wäre wahrscheinlich auch hier nützlich; es verkürzt den Entwicklungszyklus erheblich, wenn man zB mit HTML oder einem Template herumfummelt.

Wenn nicht, muss der Aufrufer diesen untergeordneten Mechanismus mit einigen *_dev.go und *_nodev.go Wrappern umschließen und nicht eingebettetes Laden für das dev Szenario implementieren. Nicht einmal schwer, aber dieser Weg wird nur zu einer ähnlichen Explosion von Tools führen, die der erste Kommentar zu diesem Thema beschreibt. Diese Tools werden weniger leisten müssen als heute, aber sie werden sich immer noch vermehren.

Ich denke, dass -tags dev nicht funktioniert, wenn es außerhalb des Go-Moduls ausgeführt wird (kann nicht herausfinden, woher die Assets geladen werden sollen).

Was ist mit nur einem go tool embed , das Eingaben entgegennimmt und Go-Ausgabedateien in einem speziellen Format erzeugt, das vom Computer als eingebettete Dateien erkannt wird, auf die dann über runtime/emved oder so zugegriffen werden kann. Dann könnten Sie einfach ein einfaches //go:generate gzip -o - static.txt | go tool embed -o static.go .

Ein großer Nachteil ist natürlich, dass Sie dann die generierten Dateien festschreiben müssen.

@DeedleFake dieses Problem begann mit

Die Verwendung einer go:generate-basierten Lösung bläst den Git-Verlauf mit einer zweiten (und etwas größeren) Kopie jeder Datei auf.

Hoppla. Macht nichts. Es tut uns leid.

Es ist nicht so, dass das Einbetten von Dateien ohne Komprimierung nutzlos wäre, Komprimierung ist eine nette Sache, um die Binärgröße von vielleicht 100 MB auf 50 MB zu reduzieren – was großartig ist, aber auch kein klarer Dealbreaker für die Funktionalität für die meisten Anwendungen, die ich mir vorstellen kann .

Die Binärgröße ist für viele Go-Entwickler eine große Sache (#6853). Go komprimiert DWARF-Debug-Informationen speziell, um die Binärgröße zu reduzieren, auch wenn dies die Linkzeit kostet (#11799, #26074). Wenn es eine einfache Möglichkeit gäbe, die Binärgröße zu halbieren, würden die Entwickler meiner Meinung nach diese Gelegenheit nutzen (obwohl ich bezweifle, dass die Gewinne hier annähernd so signifikant wären).

Wenn dies erforderlich ist, werden die komprimierten Daten festgeschrieben und eingebettet, und es werden Pakete bereitgestellt, die eine Schicht zwischen runtime.Embed und dem Endverbraucher bereitstellen, die die Dekomprimierung inline durchführen.

Und dann wird es in ein oder zwei Jahren eine neue Ausgabe über das Hinzufügen von Komprimierung geben, die dann sortiert werden kann.

Ich sage dies als einen der 15 konkurrierenden Standards, als ich goembed schrieb :)

@tv42 schrieb:

Ich denke, es sollte ein mmap-readonly []byte verfügbar machen. Einfacher Rohzugriff auf Seiten aus der ausführbaren Datei, die nach Bedarf vom Betriebssystem eingelagert werden.

Dieser Kommentar wird leicht übersehen und ist unglaublich wertvoll.

@tv42 ,

Ich denke, es sollte ein mmap-readonly []Byte verfügbar machen. Einfacher Rohzugriff auf Seiten aus der ausführbaren Datei, die nach Bedarf vom Betriebssystem eingelagert werden. Alles andere kann darüber hinaus mit nur bytes.NewReader bereitgestellt werden.

Der bereits schreibgeschützte Typ ist string . Außerdem: Es stellt eine Größe bereit, im Gegensatz zu io.ReaderAt und hängt nicht von der Standardbibliothek ab. Das ist wahrscheinlich das, was wir aufdecken wollen.

Der bereits schreibgeschützte Typ ist string .

Aber das gesamte Ökosystem von Write usw. funktioniert auf []byte . Es ist einfacher Pragmatismus. Ich sehe nicht, dass diese schreibgeschützte Eigenschaft ein größeres Problem darstellt als die io.Writer.Write Dokumente, die sagen

Write darf die Slice-Daten nicht ändern, auch nicht vorübergehend.

Ein weiterer potenzieller Nachteil ist, dass ich beim Einbetten eines Verzeichnisses mit go:generate die Ausgabe von git diff überprüfen und sehen kann, ob versehentlich Dateien dort sind. Mit diesem Vorschlag - ? Vielleicht würde der Befehl go die Liste der Dateien drucken, die er einbettet?

@tv42

Aber das gesamte Ökosystem von Write usw. funktioniert auf []byte.

html/template funktioniert jedoch mit Strings.

Lassen Sie Sie bereits -ldflags -X verwenden, um einige Zeichenfolgen zu setzen (nützlich, um die Git-Version, die Kompilierzeit, den Server, den Benutzer usw. einzustellen), könnte dieser Mechanismus erweitert werden, um io.Readers anstelle von Zeichenfolgen zu setzen?

@bradfitz Schlagen Sie vor, hier Zeichenfolgen zu verwenden, auch für Daten, die kein Text sind? Es ist üblich, kleine Binärdateien wie Symbole und kleine Bilder usw.

@tv42 Du hast Write gesagt, aber ich nehme an, du meinst Read . Sie können ein string mit strings.NewReader string in ein io.ReaderAt umwandeln.

@andreynering Ein string kann eine beliebige Folge von Bytes enthalten.

Ein string kann eine beliebige Folge von Bytes enthalten.

Ja, aber seine Hauptabsicht besteht darin, Text und nicht willkürliche Daten zu speichern. Ich vermute, dass dies insbesondere für unerfahrene Go-Entwickler ein wenig Verwirrung stiften kann.

Die Idee habe ich aber total. Danke fürs klarstellen.

@ianlancetaylor

Read soll das übergebene Slice mutieren. Write ist es nicht. Daher sagt die Write Dokumentation, dass dies nicht zulässig ist. Ich sehe nicht, dass mehr benötigt wird, als zu dokumentieren, dass Benutzer nicht in die zurückgegebenen []byte schreiben dürfen.

Nur weil strings.Reader existiert, bedeutet das nicht, dass io.WriteString eine effiziente Implementierung zum Schreiben von Strings findet. TCPConn hat beispielsweise kein WriteString .

Ich würde es hassen, wenn Go eine neue Funktion wie diese enthält, nur um zu erzwingen, dass alle Daten kopiert werden, nur um sie in einen Socket zu schreiben.

Außerdem wird allgemein davon ausgegangen, dass Zeichenfolgen von Menschen gedruckt werden können und []byte oft nicht. Das Einfügen von JPEGs in Strings führt zu vielen durcheinandergebrachten Terminals.

@opennota

html/template funktioniert jedoch mit Strings.

Ja, das ist seltsam, es werden nur Dateien nach Pfadnamen akzeptiert, nicht als Leser. Zwei Antworten:

  1. Es gibt keinen Grund, warum die eingebetteten Daten nicht beide Methoden Bytes() []byte und String() string haben könnten.

  2. Hoffentlich analysieren Sie nicht bei jeder einzelnen Anfrage eine Vorlage; wohingegen Sie wirklich für jede Anfrage, die danach fragt, die Daten eines JPEGs in einen TCP-Socket senden müssen.

@tv42 Wir können WriteString Bedarf

Ich glaube nicht, dass die häufigste Verwendung dieser Funktionalität darin besteht, die unveränderten Daten zu schreiben, daher denke ich nicht, dass wir für diesen Fall optimieren sollten.

Ich glaube nicht, dass die häufigste Verwendung dieser Funktionalität darin besteht, die unveränderten Daten zu schreiben.

Ich denke, die absolut häufigste Verwendung dieser Funktionalität wird darin bestehen, Web-Assets, Bilder/js/css, unverändert bereitzustellen.

Aber nehmen Sie nicht mein Wort dafür, schauen wir uns einige der Importeure von Brads Dateieinbettung an:

#fileembed pattern .+\.(js|css|html|png|svg|js.map)$
#fileembed pattern .*\.png



md5-f8b48fccd03599094034bf2b507e9e67



#fileembed pattern .*\.js$

Und so weiter..

Für anekdotische Daten: Ich weiß, wenn dies implementiert würde, würde ich es sofort an zwei Stellen bei der Arbeit verwenden, und beides wäre, um unveränderten Zugriff auf statische Textdateien bereitzustellen. Im Moment verwenden wir einen //go:generate Schritt, um die Dateien in konstante (hexadezimale) Zeichenfolgen zu konvertieren.

Ich würde für das neue Paket im Gegensatz zur Richtlinie stimmen. Viel einfacher in den Griff zu bekommen, einfacher zu handhaben/zu verwalten und viel einfacher zu dokumentieren und zu erweitern. zB Können Sie die Dokumentation für eine Go-Direktive wie „go:generate“ leicht finden? Wie sieht es mit der Dokumentation zum Paket „fmt“ aus? Siehst du, wohin ich damit gehe?

Alternativ könnte Go einfach ein eigenes "offizielles" Einbettungstool haben, das standardmäßig auf go build läuft

@andreynering Ich weiß, dass andere

Zwei weitere Dinge fallen mir ein, wenn ich über diese Funktion nachdenke:

  • Wie würde das automatische Einbetten von Dateien mit dem Build-Cache funktionieren?
  • Verhindert es reproduzierbare Builds? Werden die Daten in irgendeiner Weise verändert (zB komprimieren), sollte die Reproduzierbarkeit berücksichtigt werden.

stuffbin , verlinkt im ersten Kommentar, wurde entwickelt, um in erster Linie selbst gehostete Webanwendungen statische (HTML, JS ...) Assets einbetten zu lassen. Dies scheint ein häufiger Anwendungsfall zu sein.

Abgesehen von der Diskussion über die Kompilierung / Komprimierung ist ein weiterer Schwachpunkt das Fehlen einer Dateisystemabstraktion in der stdlib, weil:

  • Auf dem Computer eines Entwicklers müssen die zahlreichen go run s und Builds nicht durch den Aufwand für das Einbetten (bei optionaler Komprimierung) von Assets belastet werden. Eine Dateisystem-Abstraktion würde während der Entwicklung ein einfaches _Failover_ auf das lokale Dateisystem ermöglichen.

  • Assets können sich während der Entwicklung aktiv ändern, beispielsweise ein vollständiges Javascript-Frontend in einer Webanwendung. Die Möglichkeit, nahtlos zwischen eingebettetem und lokalem Dateisystem anstelle der eingebetteten Assets zu wechseln, würde es ermöglichen, das Kompilieren und erneute Ausführen der Go-Binärdatei zu vermeiden, nur weil sich die Assets geändert haben.

Bearbeiten: Zum Schluss, wenn das Einbettungspaket eine dateisystemähnliche Schnittstelle bereitstellen könnte, etwas Besseres als http.FileSystem, würde es diese Bedenken lösen.

Die Möglichkeit, nahtlos zwischen eingebettetem und lokalem Dateisystem zu wechseln

Dies kann sicherlich auf Anwendungsebene umgesetzt werden und würde den Rahmen dieses Vorschlags sprengen, nicht wahr?

Dies kann sicherlich auf Anwendungsebene umgesetzt werden und würde den Rahmen dieses Vorschlags sprengen, nicht wahr?

Entschuldigung, habe gerade gemerkt, dass die Art und Weise, wie ich es formuliert habe, mehrdeutig ist. Ich habe keine Dateisystemimplementierung innerhalb des Embed-Pakets vorgeschlagen, sondern nur eine Schnittstelle, etwas besser als http.FileSystem . Das würde es Anwendungen ermöglichen, jede Art von Abstraktion zu implementieren.

Bearbeiten: Tippfehler.

@knadh Stimme voll und ganz zu, dass es funktionieren sollte, wenn Sie auch nur go run , die Art und Weise, wie Packr dies handhabt, ist wirklich nett. Es weiß, wo sich Ihre Dateien befinden, wenn sie nicht in die App eingebettet sind, lädt es sie von der Festplatte, da es erwartet, dass dies im Grunde "Entwicklungsmodus" ist.

Der Autor von Packr hat auch ein neues Tool Pkger veröffentlicht, das mehr auf Go-Module ausgerichtet ist. Die Dateien dort sind alle relativ zum Stamm des Go-Moduls. Ich mag diese Idee wirklich, aber Pkger scheint das Laden der lokalen Entwicklung von der Festplatte nicht implementiert zu haben. Eine Kombination aus beidem wäre IMO erstaunlich.

Ich weiß nicht, ob es schon nicht mehr läuft, aber während der "Einbettungspaket-Ansatz" ziemlich magisch ist, bietet er auch eine gewisse Großartigkeit, da das Tool anhand des Aufrufs ableiten kann, was mit der Datei zu tun ist. zB könnte die API so aussehen

package embed
func FileReader(name string) io.Reader {…}
func FileReaderAt(name string) io.ReaderAt {…}
func FileBytes(name string) []byte {…}
func FileString(name string) string {…}

Wenn das go-Tool einen Aufruf von FileReaderAt , weiß es, dass die Daten dekomprimiert werden müssen. Wenn es nur FileReader Aufrufe findet, weiß es, dass es komprimierte Daten speichern kann. Wenn es einen Aufruf FileBytes , weiß es, dass es eine Kopie machen muss, wenn es nur FileString , weiß es, dass es aus dem Nur-Lese-Speicher bedienen kann. Und so weiter.

Ich bin nicht überzeugt, dass dies eine vernünftige Möglichkeit ist, dies für das eigentliche Go-Tool zu implementieren. Aber ich wollte dies erwähnen, da es die Vorteile der Komprimierung und der Zero-Copy-Einbettung ermöglicht, ohne wirkliche Regler zu haben.

[Bearbeiten] natürlich auch, lassen Sie uns diese zusätzlichen mangelhaften Dinge nachträglich hinzufügen und uns zuerst auf einen minimaleren Funktionsumfang konzentrieren [/edit]

Wenn es nur FileReader-Aufrufe findet...

Dies würde die Verwendung der anderen Methoden durch Reflexion ausschließen.

[Bearbeiten] Eigentlich denke ich, dass die Auswirkungen weitreichender sind. Wenn die Verwendung von FileReaderAt ein Hinweis darauf ist, dass die Daten dekomprimiert werden müssen, dann bedeutet die Verwendung von FileReaderAt() mit jeder Nicht- const Eingabe, dass alle Dateien unkomprimiert gespeichert werden müssen.

Ich weiß nicht, ob das gut oder schlecht ist. Ich denke nur, dass die magische Heuristik nicht annähernd so nützlich sein wird, wie es auf den ersten Blick erscheinen mag.

Ein Argument für ein Kommentar-Pragma ( //go:embed ) anstelle eines speziellen Verzeichnisnamens ( static/ ): Ein Kommentar lässt uns eine Datei in das Testarchiv für ein Paket (oder das xtest-Archiv) einbetten ) aber nicht die zu testende Bibliothek. Der Kommentar muss nur in einer _test.go Datei erscheinen.

Ich gehe davon aus, dass dies ein häufiges Problem bei Modulen beheben würde: Es ist schwierig, auf Testdaten für ein anderes Paket zuzugreifen, wenn sich dieses Paket in einem anderen Modul befindet. Ein Paket könnte Daten für andere Tests mit einem Kommentar wie //go:embedglob testdata/* in einer _test.go Datei bereitstellen. Das Paket könnte in eine normale Nicht-Test-Binärdatei importiert werden, ohne diese Dateien einzulesen.

@fd0 ,

Wie würde das automatische Einbetten von Dateien mit dem Build-Cache funktionieren?

Es würde noch funktionieren. Die eingebetteten Dateiinhalts-Hashes würden in den Cache-Schlüssel gemischt.

Wäre es möglich (oder sogar eine gute Idee), ein Modul/ein Paket/einen Mechanismus zu haben, der praktisch transparent wäre, da Sie in Ihrer Anwendung einfach versuchen, einen Pfad wie zu öffnen?

internal://static/default.css

und die Dateifunktionen lesen Daten aus der Binärdatei oder von einem alternativen Speicherort
ex Package.Mount("internal[/<folder>.]", binary_path + "/resources/")

um "internal://" mit allen Dateien in der Binärdatei zu erstellen, greifen Sie auf den ausführbaren Pfad / Ressourcen / zurück, wenn im Dev-Modus oder wenn die Datei nicht in der Binärdatei gefunden wird (und möglicherweise eine Warnung oder etwas zu Protokollierungszwecken ausgeben)

Dies würde es zum Beispiel ermöglichen,

Package.Mount("internal", binary_path  + "/resources/private/")
Package.Mount("anotherkeyword", binary_path  + "/resources/content/")

Wahrscheinlich am besten, den alternativen Speicherort im 'Release'-Modus auf den Pfad der ausführbaren Datei zu sperren, aber lockern Sie dies im Dev-Modus (erlauben Sie nur Ordner in go_path oder ähnlichem).

Standardmäßig "mountet" das Paket internal:// oder ein anderes Schlüsselwort, aber der Benutzer kann es umbenennen, wenn er / sie möchte. ex .ReMount("internal","myCustomName") oder so ähnlich.

Eine andere Sache ... wäre es sinnvoll, die letzte Änderung / Änderungszeit an einem anderen Ort zu überprüfen und die interne Datei automatisch zu überschreiben, wenn eine solche Datei außerhalb der Anwendung vorhanden ist (möglicherweise haben Sie ein Flag, das dies ermöglicht, vom Programmierer vor dem Build konfigurierbar)
Dies kann für superschnelle Patches von Anwendungen erwünscht sein, bei denen Sie nicht warten möchten, bis ein neuer Build erstellt und verteilt wird. Sie können einfach den Ordner erstellen und die Datei dorthin kopieren, und die Binärdatei wechselt zum neuen Datei.

Wäre es unter Windows möglich oder sinnvoll, Ressourcen zu verwenden (wie in binären Datenblobs in einer Ressource)
Und ein bisschen unabhängig, aber vielleicht könnte dieses Paket auch mit dem Bündeln von Symbolen in der ausführbaren Datei oder Manifestdaten oder vielleicht sogar anderen Ressourcen umgehen? Mir ist klar, dass es nur Windows ist...
Ich könnte mir vorstellen, dass der Builder die letzten Änderungs-/Änderungsdaten der Dateien in den alternativen Ordnern protokollieren und nur ein "Datenblob erstellen" auslösen könnte, wenn sich eine Datei ändert und das Blob irgendwo zwischenspeichert.
Möglicherweise erstellt der Benutzer nur eine "Cache" -Datei, um die Komprimierung dieser gebündelten Dateien zu aktivieren (wenn sie sich schließlich dazu entschließen, sie zu komprimieren) ... , andere Dateien würden einfach aus dem Cache in die Binärdatei kopiert.

Ein Problem, das ich sehe, ist, wenn das Paket benutzerdefinierte Namen zulässt, müsste es eine Blacklist haben, da es "udp, file, ftp, http, https und verschiedene andere beliebte Schlüsselwörter" nicht erlaubt.

Was das Speichern als Byte-Array / String oder Komprimierung angeht ... egal, welche Entscheidung getroffen wird, es sollte in Zukunft Platz für einfache Aktualisierungen lassen ... z. B. könnten Sie ohne Komprimierung beginnen und nur eine Liste mit Offsets und Dateigrößen haben und Dateinamen, sondern ermöglichen es, in Zukunft einfach eine Komprimierung hinzuzufügen (z.

Ich persönlich würde mich freuen, wenn die ausführbare Datei mit UPX oder gleichwertig gepackt werden kann, ich gehe davon aus, dass die Binärdatei im Speicher entpackt wird und alles funktioniert.

Ein paar Gedanken, die sich indirekt beziehen:

  • Ich mag den package embed Ansatz für die Verwendung der Go-Syntax
  • Ich denke, bei der Notwendigkeit von Komprimierung und anderen Manipulationen geht es nicht um die Binärgröße, sondern darum, nur die diff-freundlichste Form von Inhalten in einem Repository speichern zu wollen, damit es keinen "nicht synchronen" Zustand gibt, in dem jemand vergisst, sich zu regenerieren und übergeben Sie ein komprimiertes Formular, wenn Sie die "Quelle" ändern, und damit das Paket "auf Abruf" bleibt. Ohne auf diese Punkte einzugehen, lösen wir nur das Standardisierungsproblem, das zwar akzeptabel ist, aber nicht ideal erscheint.
  • Ich denke, wir könnten die Notwendigkeit umgehen, dass die Toolchain bestimmte Komprimierungen/Transformationen aktiv unterstützen muss, wenn die embed Interaktion optional einen "Codec" bereitstellen kann. Wie genau der Codec definiert wird, hängt von der Integrationssyntax ab, aber ich stelle mir so etwas vor wie
package embed

type Codec interface {
    // Encode transforms a source representation to an in-binary encoded asset.
    Encode(io.Writer, io.Reader) error

    // Decode transforms an in-binary asset to its active representation that the embedded application wants to use.
    Decode(io.Writer, io.Reader) error
}

Dies kann sehr spezifische Anwendungsfälle abdecken, wie dieser erfundene:

package main

func NewJSONShrinker() embed.Codec {
   return jsonShrinker{}
}

type jsonShrinker struct{}
func (_ jsonShrinker)  Encode(io.Writer, io.Reader) error {
    // use json.Compact + gzip.Encode...
}
func (_ jsonShrinker)  Decode(io.Writer, io.Reader) error {
    // use gzip.Decode + json.Indent
}

Die Verwendung könnte so aussehen

// go:embed file.name NewJSONShrinker

func main() {
    embed.NewFileReader("file.name") // codec is implied by the comment above
}

oder möglicherweise

func main() {
    f, err := embed.NewFileReaderCodec("file.name", NewJSONShrinker())
    ...
}

In der zweiten Form gibt es die Komplikation, dass die Toolchain statisch verstehen muss, welcher Codec verwendet werden soll, da sie zur Kompilierzeit den Schritt Encode ausführen muss. Daher müssten wir jeden Codec-Wert verbieten, der zur Kompilierzeit nicht einfach bestimmt werden kann.

Angesichts dieser beiden Optionen würde ich mich für den magischen Kommentar plus Codecs entscheiden. Daraus ergibt sich eine leistungsfähigere Funktion, die alle hier genannten Ziele anspricht. Außerdem glaube ich nicht, dass magische Kommentare hier inakzeptabel sind. Wir tolerieren sie bereits jetzt über go:generate zu diesem Zweck. Wenn überhaupt, könnte man das magische Paket allein eher als Abkehr von aktuellen Idiomen betrachten. Das Go-Ökosystem hat derzeit nicht viele Funktionen, die es einer Quelldatei ermöglichen, die Toolchain anzuweisen, zusätzliche Quelldateien zu verwenden, und ich denke, der einzige, der derzeit kein magischer Kommentar ist, ist das Schlüsselwort import .

Wenn wir eine Komprimierung durchführen, gibt es überhaupt keine Codec-Typ- oder Komprimierungspegel-Knöpfe. Das heißt, dass überhaupt keine Regler vorhanden sind, ist das größte Argument dafür, die Kompression überhaupt nicht zu unterstützen.

Die einzige Wahl, die ich offenlegen möchte, ist: Direktzugriff oder nicht. Wenn Sie keinen Direktzugriff benötigen, können die Tools und die Laufzeit die geeignete Komprimierung auswählen und sie den Benutzern nicht zur Verfügung stellen. Und es würde sich wahrscheinlich im Laufe der Zeit ändern / verbessern.

Aber ich bin auf der Seite von @rsc dazu gekommen , keine Komprimierung zu haben, weil ich eine Erkenntnis hatte: Der Inhalt, der am komprimierbarsten ist (HTML, JS, CSS usw.) über http.FileServer , was Bereichsanfragen unterstützt)

Und wenn man sich die kombinierte Größe von Perkeeps HTML/CSS/JS ansieht, die wir einbetten, sind es 48 KB unkomprimiert. Die Binärdatei des Perkeep-Servers ist 49 MB groß. (Ich ignoriere die Größe eingebetteter Bilder, da diese bereits komprimiert sind.) Es scheint sich also nicht zu lohnen, aber es könnte später hinzugefügt werden.

Aus einer Diskussion mit @rsc geht hervor , dass wir eine Mischung der oben genannten Ansätze machen könnten:

In der Paketlaufzeit

package runtime

type Files struct {
     // unexported field(s), at least 1 byte long so Files has a unique address
}

func (f *Files) Open(...) (...) { ...}
func (f *Files) Stat(...) (...) { ...}
func (f *Files) EnumerateSomehow(...) { ...}

Dann in deinem Code:

package yourcode

//go:embed static/*
//go:embed logo.jpg
var website runtime.Files

func F() {
     ... = website.Open("logo.jpg")
}

Dann würde das cmd/go-Tool die go:embed Kommentare parsen und diese Muster globen + diese Dateien hashen und sie mit &website bei der Laufzeit registrieren.

Die Laufzeit hätte effektiv eine Zuordnung jeder Dateiadresse zu ihrem Inhalt und wo sich diese in der ausführbaren Datei befinden (oder wie ihre ELF/etc-Abschnittsnamen lauten). Und vielleicht, ob sie wahlfreien Zugriff unterstützen oder nicht, wenn wir am Ende eine Komprimierung durchführen.

@gdamore ,

Nur eine weitere Falte hier - ich habe ein anderes Projekt, das DTrace-Quellcode (eingebettet) verwendet. Dies ist empfindlich gegenüber Unterschieden zwischen n und rn.
...
Wenn verschiedenes Munging nicht ausreicht, spricht das gegen die Nutzung dieser neuen Einrichtung.

Sie können auch zur Laufzeit munge, um alle Zeilenumbrüche zu entfernen, die von Windows-Benutzern eingebettet werden, die go install ausführen. Ich habe diesen io.Reader-Filter ein paar Mal geschrieben.

Aber ich bin auf der Seite von @rsc dazu gekommen , keine Komprimierung zu haben, weil ich eine Erkenntnis hatte: Der Inhalt, der am komprimierbarsten ist (HTML, JS, CSS usw.) über beispielsweise http.FileServer bedient werden, der Bereichsanfragen unterstützt)

Komprimierung und wahlfreier Zugriff schließen sich gegenseitig nicht vollständig aus. Siehe zB einige Diskussionen hier: https://stackoverflow.com/questions/429987/compression-formats-with-good-support-for-random-access-within-archives

Komprimierung und wahlfreier Zugriff schließen sich nicht vollständig aus

Ja, wenn wir grobkörniges Suchen mit etwas Overhead wollten, um die richtige Position zu erreichen. Ich habe in diesem Bereich mit dem stargz-Format von CRFS gearbeitet . Aber ich fürchte, der Overhead wäre so groß, dass wir das nicht automatisch für die Leute machen wollen. Ich nehme an, Sie könnten es auch träge in den Speicher aufblasen (und es auf GCs ablegen können, wie einen sync.Pool), aber es scheint es einfach nicht wert zu sein.

Ich fürchte, der Overhead wäre so groß, dass wir das nicht automatisch für die Leute machen wollen.

Meinetwegen. Die wichtige Frage ist, ob wir eine API bevorzugen, die es uns ermöglicht, unsere Meinung später kostengünstig zu ändern, wenn sich die Bedürfnisse ändern oder ob Experimente zeigen, dass der Aufwand akzeptabel ist.

@bradfitz guter Punkt. Und das kann ich auf jeden Fall. FWIW, in meinem Repo habe ich git auch so konfiguriert, dass es weniger toxisch ist, wenn .d-Dateien angezeigt werden. Dennoch finde ich die Eigenschaft eingebetteter Strings mit Backquotes nützlich, da sie vorhersehbar ist und nicht den Launen von Git oder dem System unterliegt.

Was ich mit der Codec-Idee erreichen wollte, ist, dass die Komprimierung nicht die einzige Transformation ist, die man sich wünschen könnte, und dass ein vom Benutzer bereitgestellter Codec-Typ es der Toolchain ermöglicht, andere Flags als "welcher Codec" zu ignorieren. Alle Komprimierungsstufen oder der Algorithmus überhaupt, Komprimierung oder anderweitig, müssten für den verwendeten Codec spezifisch sein. Ich stimme voll und ganz zu, dass der Versuch, die Komprimierung zu unterstützen, im Sinne der Bereitstellung bestimmter Formate und Regler eine wilde Gänsejagd mit all den Variationen wäre, die sich die Leute wünschen können. Tatsächlich würde ich mich am meisten über die ungewöhnlichen Anwendungen freuen, wie etwa die Vorverarbeitung von i18n-Daten oder die Verarbeitung von Datensätzen wie in latlong .

Ich habe mir eine andere Möglichkeit ausgedacht, die gleiche Flexibilität zu bieten, die angenehmer sein könnte. Die Direktive // go:embed könnte ein Befehlsaufruf sein, genau wie // go:generate . Für den einfachsten Fall so etwas wie

// go:embed "file.name" go run example.com/embedders/cat file.name

Der Hauptunterschied besteht natürlich darin, dass die stdout des Befehlsaufrufs unter dem angegebenen Namen eingebettet ist. Das Beispiel verwendet auch ein vorgetäuschtes Paket mit go run zu zeigen, wie es wahrscheinlich gemacht wird, den Befehl vom Betriebssystem unabhängig zu machen, da cat möglicherweise nicht überall verfügbar ist, wo Go kompiliert.

Das erledigt den "Encode"-Schritt der Transformation, und vielleicht kann die Aufgabe des "Decode"-Schritts dem Benutzer überlassen werden. Das Laufzeit-/Einbettungspaket kann nur die Bytes bereitstellen, die der Benutzer unabhängig von der Codierung von der Toolchain angefordert hat. Dies ist in Ordnung, da der Benutzer weiß, wie der Decodierungsprozess aussehen soll.

Ein großer Nachteil davon ist, dass ich keine gute Möglichkeit sehe, einen Glob aus mehreren Dateien auf diese Weise einzubetten, außer dass die eingebetteten Bytes eine Zip-Datei oder so sind. Das könnte eigentlich gut genug sein, da ein Glob immer noch von einem zip-Befehl verwendet werden könnte, und es ist auf der definierenden Seite, wo Sie sich wirklich um das Glob kümmern. Aber wir könnten auch zwei Funktionen aus diesem Vorschlag herausholen, eine für einfaches Embedded und eine andere für die Ausführung eines Generator-Embeds.

Ein möglicher Nachteil, der mir aufgefallen ist, ist, dass es einen offenen Schritt in den Build einfügt, vorausgesetzt, Einbettungen sollten von go build und erfordern keinen zusätzlichen Toolchain-Aufruf wie go generate . Ich denke, das ist aber in Ordnung. Vielleicht kann vom Tool erwartet werden, dass es seinen eigenen Cache verwaltet, um die Wiederholung teurer Operationen zu vermeiden, oder vielleicht kann es mit der Toolchain kommunizieren, um den Cache von Go zu verwenden. Das klingt nach einem lösbaren Problem und passt zum Gesamtthema, dass go build mehr für uns tun (wie das Abrufen von Modulen).

Ist eines der Ziele dieses Projekts, sicherzustellen, dass für Go-Builds keine externen Tools und keine go:generate-Linien erforderlich sind?

Wenn nicht, scheint es sinnvoll zu sein, die Dinge einfach zu halten und nur einen Byte-Slice oder eine Zeichenfolge zu unterstützen, denn wenn der Benutzer eine Komprimierung mit vielen Knöpfen möchte, kann er dies in seiner Make-Datei (oder ähnlichem) tun, eine Zeile generieren usw., bevor er sowieso erstellt wird. es lohnt sich also nicht, sie zu dem Ergebnis dieses Vorschlags hinzuzufügen.

Wenn kein Make oder ähnliches erforderlich ist, kann es sinnvoll sein, die Komprimierung zu verwenden, aber ich persönlich würde genauso schnell Make, Go generieren usw. verwenden, um die Komprimierung durchzuführen, dann einfach einbetten und nur einige Bytes einbetten .

@SamWhited ,

Ist eines der Ziele dieses Projekts, sicherzustellen, dass für Go-Builds keine externen Tools und keine go:generate-Linien erforderlich sind?

Jawohl.

Wer go:generate oder Makefiles oder andere Tools nutzen möchte, hat heute Dutzende von Möglichkeiten.

Wir wollen etwas, das tragbar, sicher und korrekt ist und standardmäßig funktioniert. (und um es klar zu sagen: sicher bedeutet, dass wir zum Zeitpunkt "go install" keinen beliebigen Code ausführen können, aus dem gleichen Grund, aus dem go:generate standardmäßig nicht ausgeführt wird)

@stephens2424

Ich denke, wir könnten die Notwendigkeit umgehen, dass die Toolchain bestimmte Komprimierungen/Transformationen aktiv unterstützen muss, wenn die Einbettungsinteraktion optional einen "Codec" bereitstellen kann.

Keine Ausführung willkürlichen Codes während go build .

Keine Ausführung willkürlichen Codes während des Go-Builds.

Ja, das sehe ich jetzt. Ich nehme an, es gibt keine Möglichkeit, nur "Quelldateien" in ein Repository zu übertragen, "verarbeitete" Dateien eingebettet zu haben, das Paket "zu bekommen" zu machen, _und_ go build einfach und sicher zu halten. Ich bin immer noch für den Drang zur Standardisierung hier, aber ich hoffte, meinen Kuchen zu haben und ihn auch zu essen, denke ich. Einen Versuch wert! Danke, dass du das Problem erkannt hast!

@flimzy

Dies würde die Verwendung der anderen Methoden durch Reflexion ausschließen.

Es gibt keine Methoden in dem, was ich erwähnt habe, nur Funktionen. Sie sind zur Laufzeit nicht erkennbar und es gibt keine Möglichkeit, auf sie zu verweisen, ohne sie namentlich in der Quelle zu erwähnen. Beachten Sie, dass die von den verschiedenen Funktionen zurückgegebenen Schnittstellenwerte nicht vom gleichen Typ sein müssen - tatsächlich würde ich erwarten, dass es sich entweder um nicht exportierte Typen mit genau der Methode handelt, die zum Implementieren dieser Schnittstelle erforderlich ist, oder um eine Instanz von *strings.Reader usw., was auch immer im Kontext Sinn machen würde.

Allerdings leidet die Idee darunter, dass die exportierten Funktionen des Einbettungspakets als Werte weitergegeben werden. Obwohl selbst das wahrscheinlich kein Problem wäre - die Signatur enthält einen nicht exportierten Typ (siehe unten), sodass Sie keine Variablen, Argumente oder Rückgaben ihres Typs deklarieren können. Theoretisch können Sie sie an reflect.ValueOf selbst weitergeben. Ich weiß nicht einmal, ob Sie sie damit tatsächlich aufrufen können (Sie müssten immer noch einen Wert ihres Parametertyps konstruieren, der nicht exportiert wird. Keine Ahnung, ob Reflect dies zulässt).

Aber wie dem auch sei: Es wäre immer noch möglich (und am einfachsten), einfach pessimistisch zu sein, falls eine Top-Level-Funktion von embed als Wert verwendet wird, und die Einschränkungen anzunehmen, die sie für alle eingebetteten Dateien schafft. Das würde bedeuten, dass Sie einige Optimierungen verlieren,

Tatsächlich denke ich, dass die Auswirkungen weitreichender sind. Wenn die Verwendung von FileReaderAt ein Hinweis darauf ist, dass die Daten unkomprimiert sein müssen, dann impliziert die Verwendung von FileReaderAt() mit jeder nicht konstanten Eingabe, dass alle Dateien unkomprimiert gespeichert werden müssen.

Es macht keinen Sinn, nicht-konstante Eingaben zuzulassen, da der Dateiname für die Einbettung statisch bekannt sein muss. Es war jedoch ungenau von mir, string als Typ von Dateinamensparametern zu verwenden: Sie sollten eigentlich ein nicht exportiertes type filename string und nicht als etwas anderes als Funktionsargumente verwendet werden. Auf diese Weise ist es unmöglich, etwas zu übergeben, das keine nicht typisierte Zeichenfolgenkonstante ist.

@Merovius

Es macht keinen Sinn, nicht-konstante Eingaben zuzulassen

Ich glaube, wir reden über verschiedene Dinge. Ich meine Eingaben in die Accessor-Funktionen (zB FileReaderAt() ).

Und mein Punkt ist: Angenommen, wir haben 100 Dateien eingebettet, aber wir haben einen FileReaderAt(filename) Aufruf, wobei filename nicht konstant ist; Es gibt keine Möglichkeit zu wissen, auf welche (wenn überhaupt) der eingebetteten Dateien auf diese Weise zugegriffen wird, daher müssen alle unkomprimiert gespeichert werden.

@flimzy wir sprachen über das gleiche, ich dachte nur ernsthaft, dass nicht-konstante Dateinamen Sinn machen würden :) Was, wenn ich daran denke, falsch und ein Versehen war. Das tut mir leid. Möglichkeiten, ganze Verzeichnisse zu globen oder einzuschließen und dann über sie zu iterieren, sind tatsächlich ziemlich wichtig, ja. Dennoch denkt , das gelöst werden könnte - zum Beispiel durch die Entscheidung pro Sammlung machen (dir / glob) und nur so dass diejenigen , die durch konstante Namen wählen - aber wie gesagt: Es ist nicht wirklich ein API ich super geeignet für das Go - Tool in Betracht ziehen würde wegen wie magisch es ist. So ins Unkraut zu gehen, gibt dem Konzept wahrscheinlich mehr Platz in der Diskussion, als es verdient :)

Ein weiterer Fall, den ich in den vorherigen Nachrichten nicht gesehen habe und der mich in Erwägung zog, eine Datei in eine Go-Binärdatei einzubetten, war die Unmöglichkeit, ein Wrapper-Paket einer gemeinsam genutzten C-Bibliothek mit regulärem go build/install ordnungsgemäß zu verteilen (die gemeinsam genutzte Bibliothek bleibt im Quellen).

Ich habe es am Ende nicht getan, aber das würde mich definitiv dazu bringen, es für diesen Fall zu überdenken. Die C-Bibliothek hat in der Tat viele Abhängigkeiten, die als gemeinsam genutzte Bibliothek einfacher zu verteilen wären. Diese gemeinsam genutzte Bibliothek könnte durch die Go-Bindungen eingebettet werden.

Beeindruckend!!!

@Julio-Guerra
Ich bin mir ziemlich sicher, dass Sie sie immer noch auf die Festplatte extrahieren und dann dlopen und dlsym verwenden müssen, um die C-Funktionen aufzurufen.

Bearbeiten: Habe deinen Beitrag ein wenig falsch verstanden, habe gerade festgestellt, dass du davon redest, eine Binärdatei für die Verteilung zu erstellen

Außerhalb der statischen HTTP-Assets wäre es für eingebettete Blobs, die Sie in einem Zeiger auf den Speicher benötigen, schön, eine Funktion zu haben, die den Zeiger auf den bereits in Bearbeitung befindlichen eingebetteten Speicher zurückgibt. Andernfalls müsste man neuen Speicher zuweisen und eine Kopie vom io.Reader erstellen. Das würde den doppelten Speicher verbrauchen.

@glycerin , das ist wieder ein string . Ein string ist ein Zeiger und eine Länge.

Wäre es nicht großartig, Code zu markieren, der zur Kompilierzeit ausgeführt werden soll, und das Ergebnis zur Laufzeit bereitzustellen. Auf diese Weise können Sie jede Datei lesen, sie zur Kompilierzeit komprimieren, wenn Sie möchten, und zur Laufzeit darauf zugreifen. Das würde für einige Berechnungen funktionieren, da es für das Vorladen von Dateiinhalten funktionieren würde.

@burka wie bereits im Thread erwähnt, wird go build keinen beliebigen Code ausführen.

@burka , das ist explizit außerhalb des Geltungsbereichs. Diese Entscheidung (keine Codeausführung zum Zeitpunkt der Kompilierung) wurde vor langer Zeit getroffen und dies ist nicht der Fehler, diese Richtlinie zu ändern.

Ein Nebeneffekt dieses Vorschlags ist, dass Go-Proxys die von ihnen gespeicherten Dateien niemals so optimieren können, dass sie nur Go-Dateien sind. Ein Proxy muss ein gesamtes Repository speichern, da er nicht weiß, ob der Go-Code eine der Nicht-Go-Dateien einbettet.

Ich weiß nicht, ob Proxys bereits dafür optimieren, aber es ist irgendwann machbar, wenn sie dies möchten.

@leighmcculloch Ich glaube jedoch, dass dies heute auch nicht der Fall ist. Alle Nicht-Go-Dateien in einem Go-Paket sollten in Modularchiven enthalten sein, da sie möglicherweise für go test erforderlich sind. Als weiteres Beispiel könnten Sie auch C-Dateien für cgo haben.

Das ist eine spannende Richtung, die wir für unsere Anwendungsfälle definitiv brauchen.

Trotzdem habe ich das Gefühl, dass es verschiedene Anwendungsfälle mit unterschiedlichen Anforderungen gibt, aber die meisten, die sich dazu äußern, _wie_ ihrer Meinung nach gemacht werden sollte, stellen sich implizit ihre eigenen Anwendungsfälle vor, definieren sie jedoch nicht explizit.

Es könnte hilfreich sein – zumindest für mich wirklich hilfreich – wenn wir die verschiedenen Anwendungsfälle für eine Dateieinbettungslösung und die Herausforderungen , die jeder Anwendungsfall mit sich bringt, skizzieren könnten .

Unser primärer Anwendungsfall besteht beispielsweise darin, HTML+CSS+JS+JPG+etc einzubetten, sodass diese Dateien beim Ausführen in ein Verzeichnis geschrieben werden können, sodass sie von einem http.FileServer . Angesichts dieses Anwendungsfalls waren mir die meisten Kommentare, die ich über Leser und Autoren gelesen habe, fremd, weil wir nicht von Go aus auf die Dateien zugreifen müssen, wir lassen sie einfach von go-bindata auf die Festplatte kopieren _(obwohl vielleicht gibt es eine Möglichkeit, bessere Techniken zu nutzen, von denen wir einfach noch nicht wussten, dass wir sie in Betracht ziehen sollten.)_

Unsere Herausforderungen sind jedoch folgende: Wir verwenden GoLand typischerweise mit seinem Debugger und werden an der Web-App arbeiten, die kontinuierliche Änderungen vornehmen. Während der Entwicklung benötigen wir also http.FileServer , um die Dateien direkt aus unserem Quellverzeichnis zu laden. Wenn die App jedoch ausgeführt wird, muss http.FileServer diese Dateien aus dem Verzeichnis lesen, in das die Dateien von der Einbettungslösung geschrieben wurden. Das bedeutet, dass wir beim Kompilieren go-bindata ausführen müssen, um die Dateien zu aktualisieren, und sie dann in Git einchecken. Und das ist im Allgemeinen mit go-bindata praktikabel, wenn auch sicherlich keine Idee.

Zu anderen Zeiten müssen wir jedoch tatsächlich eine kompilierte ausführbare Datei ausführen, damit wir einen Debugger an das laufende Programm anhängen können und das Programm trotzdem Dateien aus dem Quellverzeichnis lädt und nicht aus dem Verzeichnis, in das eingebettete Dateien von go-bindata . Dafür haben wir derzeit keine gute Lösung.

Das sind also unsere Anwendungsfälle und Herausforderungen. Vielleicht könnten andere die anderen Anwendungsfälle und die damit verbundenen Herausforderungen explizit definieren, sodass diese Diskussionen die verschiedenen Problemräume explizit ansprechen und/oder explizit angeben können, dass diese Bemühungen nicht auf die spezifischen Bedürfnisse eines bestimmten Problemraums eingehen?

Vielen Dank im Voraus für die Überlegung.

Da ich dies nicht als Anwendungsfall sehe, würden wir davon auch für unser Vorlagenverzeichnis profitieren, auf das wir über template.ParseFiles zugreifen.

Ich würde die sauberste Methode finden, sich über go.mod . Dies würde sicherstellen, dass es abwärtskompatibel ist (da vorhandene Projekte sich für die Verwendung anmelden müssen) und Tools (wie Go-Proxys) ermöglichen, zu bestimmen, welche Dateien benötigt werden. Der Befehl go mod init könnte aktualisiert werden, um eine Standardversion für neue Projekte aufzunehmen, um die Verwendung in Zukunft zu erleichtern.

Ich sehe Argumente dafür, dass das Verzeichnis ein Standardname ist (wenn wir eine Opt-In-Funktion benötigen, kann es ein saubererer/einfacherer Name sein) oder dass der Name des Verzeichnisses in go.mod selbst definiert wird und Benutzern die Möglichkeit gegeben wird, Wählen Sie den Namen (aber mit einem Standardwert von go mod init .

Meiner Meinung nach erreicht eine solche Lösung eine Balance zwischen Benutzerfreundlichkeit und weniger "Magie".

@jayconrod schrieb:

Ein Argument für ein Kommentar-Pragma (//go:embed) anstelle eines speziellen Verzeichnisnamens (static/): Ein Kommentar lässt uns eine Datei in das Testarchiv für ein Paket (oder das xtest-Archiv) einbetten, aber nicht die Bibliothek im Test.

Das ist eine wirklich schöne Beobachtung. Wenn wir jedoch den speziellen Verzeichnisnamen verwenden wollten, könnten wir einen bekannten Mechanismus verwenden: static für alle Builds, static_test für Testbuilds, static_amd64 für AMD64-Builds und demnächst. Ich sehe jedoch keine offensichtliche Möglichkeit, Unterstützung für beliebige Build-Tags bereitzustellen.

Es könnte eine Manifest-Datei im statischen Verzeichnis geben (Standard, wenn ein leeres Manifest angegeben ist, ist alles außer dem Manifest einschließen), die Globs enthält und die Angabe von Build-Tags und möglicherweise späterer Komprimierung usw. ermöglicht.

Ein Vorteil ist, dass, wenn die Go-Liste ein Verzeichnis trifft, das ein Manifest enthält, diesen Baum à la #30058 übersprungen werden kann

Ein Nachteil ist, dass es sehr hässlich werden könnte und nein danke

Ein einfacher Zero-Knob-Mechanismus zum Bündeln von Dateien in einem Paket könnte ein spezielles Verzeichnis go.files in einem Paketverzeichnis sein (ähnlich wie go.mod in einem Modul). Der Zugriff wäre auf dieses Paket beschränkt, es sei denn, es wird ein Symbol exportiert.

Bearbeiten: Einzelfunktion runtime/files Vorschlag:

package files

func Open(name string) (io.ReadCloser, error) {
    // runtime opens embedded file based on caller package
    return rc, nil
}
package foo

import "runtime/files"

func ReadPackageFile(name string) ([]byte, error) {
    rc, err := files.Open(name)
    if err != nil {
        return nil, err
    }
    defer rc.Close()
    return ioutil.ReadAll(rc)
}

Der import "C" Ansatz hat bereits einen Präzedenzfall für "magische" Importpfade geschaffen. IMO hat es ganz gut geklappt.

Da ich dies nicht als Anwendungsfall sehe, würden wir davon auch für unser Vorlagenverzeichnis profitieren, auf das wir über template.ParseFiles zugreifen.

Es gibt noch eine weitere Herausforderung: Während die Binärdatei alle benötigten Dateien enthalten könnte, wären genau diese Dateien die Standardeinstellungen, die ich als Entwickler bereitstelle. Vorlagen wie zB Impressum oder Datenschutzerklärungen müssen jedoch vom Endanwender anpassbar sein. Soweit ich das sehe, bedeutet dies, dass es eine Möglichkeit geben muss, meine Standarddateien zu exportieren und dann entweder die Binärdatei die angepassten Dateien zur Laufzeit verwenden zu lassen oder die eingebetteten Versionen durch die angepassten zu ersetzen.

Ich denke, dies könnte durch die Bereitstellung einer API mit Funktionen zum "Exportieren" und "Ersetzen" einer eingebetteten Ressource erfolgen. Der Entwickler könnte dem Endbenutzer dann einige Befehlszeilenoptionen zur Verfügung stellen (unter Verwendung der erwähnten API-Aufrufe intern).

All dies natürlich unter der Annahme, dass es tatsächlich eine Art von Einbettung geben wird, die das Deployment definitiv erleichtern würde.

Danke für das Öffnen des Themas. Bei der Arbeit haben wir über die gleiche Feature-Idee nachgedacht, da wir in so ziemlich jedem Golang-Projekt die Einbettung von Dateien benötigen. Die vorhandenen Bibliotheken funktionieren gut, aber ich denke, dies ist eine Funktion, nach der Golang schreit. Es ist eine Sprache, die gemacht wurde, um sich in eine einzelne statische Binärdatei zu verwandeln. Es sollte dies berücksichtigen, indem es uns ermöglicht, erforderliche Asset-Dateien mit einer entwicklerfreundlichen und universellen API in die Binärdatei zu laden.

Ich möchte nur schnell meine bevorzugten Implementierungsdetails bereitstellen. Mehrere Leute haben darüber gesprochen, automatisch eine API zum Lesen der eingebetteten Dateien bereitzustellen, anstatt ein weiteres Signal wie einen magischen Kommentar zu benötigen. Ich denke, das sollte der richtige Weg sein, da es dem Ansatz eine vertraute, programmatische Syntax bietet. Wenn Sie sich für ein spezielles Paket entscheiden, möglicherweise runtime/embed wie bereits erwähnt, würde dies erfüllt und es würde eine einfache Erweiterbarkeit in der Zukunft ermöglichen. Am sinnvollsten wäre für mich eine Implementierung wie die folgende:

type EmbedPackage interface {
    Bytes(filename string) []bytes
    BytesCompressed(filename string, config interface{}) []bytes // compressed in-binary as configured by some kind of config struct, memoizes decompression during runtime on first access
    Reader(filename string) io.Reader
    File(filename string) os.File // readonly and contains all metadata
    Dir(filepath string) []os.File 
    Glob(pattern string) []os.File // like filepath.Glob()

    // maybe? this could allow to load JSON, YAML, INI, TOML, etc files more easily
    // but would probably be too much for the std lib implementation
    Unmarshal(filename string, config interface{}, ptr interface{}) 
}

Die Verwendung dieses Pakets irgendwo in Ihrem Code sollte den Compiler veranlassen, diese Datei der Laufzeit bereitzustellen, indem er sie automatisch einbettet.

// embed a file that is compressed in-binary and automatically decompressed on first access
var LongText = embed.BytesCompressed("legal.html", embed.Config{ Compression: "gzip", CompressionLevel: "9" })

// loads a single file as reader for easy access
var FewLinesOfText = bufio.NewReader(embed.Reader("lines.txt"))
for _, line := range FewLinesOfText.ReadLines() { ... }

// embeds all files in the directory
var PdfFontFiles = embed.Dir("/fonts")

// unmarshals file into custom config
var PdfProcessingConfig MyPdfProcessingConfig
embed.Unmarshal("/pdf_conversion.json", embed.Config{ Encoding: "text/json" }, &PdfProcessingConfig)

Außerdem denke ich, dass Sicherheitsprobleme und Reproduzierbarkeit kein Problem darstellen sollten, wenn wir den Import auf Dateien auf oder möglicherweise 1 Verzeichnisebene unterhalb des go.mod-Verzeichnisses beschränken, was ebenfalls bereits im Thread erwähnt wurde. Absolute Einbettungspfade würden relativ zu dieser Verzeichnisebene aufgelöst.

Wenn während des Kompilierungsvorgangs nicht auf die Dateien zugegriffen werden kann, wird ein Compilerfehler generiert.

Es ist auch möglich, ein ZIP-Archiv hinter einer Binärdatei zu erstellen, damit es effektiv zu einer selbstextrahierenden Binärdatei werden kann. Vielleicht ist das in einigen Anwendungsfällen nützlich? Habe das hier als Experiment gemacht: https://github.com/sanderhahn/gozip

Go hat bereits „Testdaten“. Unit-Tests verwenden reguläre E/A, um zu tun, was sie wollen. Testumfang bedeutet, dass der Inhalt nicht ausgeliefert wird. Das ist alles, was Sie wissen müssen, kein Schnickschnack, keine Magie, keine komprimierte Containerlogik, keine konfigurierbaren Indirektionen, kein META-INF. Schön, schlicht, elegant. Warum nicht einen Ordner „data“ für gebündelte Laufzeitbereichsabhängigkeiten haben?

Wir können bestehende Go-Projekte in Github ea einfach scannen und kommen auf eine Reihe von Projekten, die bereits einen „data“-Ordner verwenden und daher angepasst werden müssen.

Eine andere Sache, die mir nicht klar ist. Bei der Diskussion eines static Verzeichnisses ist mir nicht 100% klar, ob es sich um ein static _source_- Verzeichnis oder ein static Verzeichnis handelt, in dem Dateien verfügbar gemacht werden _at Laufzeit_ ?

Und diese Unterscheidung ist besonders wichtig, da sie sich auf den Entwicklungsprozess und den Debugging-Code bezieht, der sich in der Entwicklung befindet.

@mikeschinkel Aus dem ursprünglichen Beitrag geht ziemlich klar hervor, dass die Einbettung zur Build-Zeit erfolgen würde:

Lass go install / go build die Einbettung automatisch durchführen

Der ursprüngliche Beitrag und einige der obigen Kommentare besprechen auch einen "Dev"-Modus zum Laden von Dateien zur Laufzeit.

@mvdan Danke für die Antwort. Sie denken also, dass das vorgeschlagene /static/ Verzeichnis relativ zum Stamm des App-Repositorys, des Paket-Repositorys und/oder möglicherweise beiden wäre?

Und dieser Speicherort von Laufzeitdateien wäre völlig davon abhängig, wo der Entwickler sie ablegen wollte?

Wenn das alles wahr ist – und es logisch erscheint – wäre es hilfreich, wenn Programme, die mit Debugging-Informationen kompiliert wurden, optional Dateien von ihrem Quellspeicherort laden könnten, um das Debuggen ohne viel zusätzliche – und nicht standardisierte – Logik und Code zu erleichtern.

Ein paar Leute oben erwähnten Modul-Proxys. Ich denke, es ist ein großartiger Lackmustest für ein gutes Design dieser Funktion.

Es scheint heute möglich zu sein, ohne Ausführung von Benutzercode einen funktionsfähigen Modul-Proxy zu implementieren, der Dateien entfernt, die im Build nicht verwendet werden. Einige der oben genannten Designs würden bedeuten, dass Modul-Proxys Benutzercode ausführen müssen, um herauszufinden, welche statischen Dateien ebenfalls eingeschlossen werden sollen.

Die Leute erwähnten go.mod auch als Opt-In.

Idee: Spezifikation in go.mod-Datei? Erleichtert das Parsen für andere Tools.

module github.com/foo/bar

data internal/static ./static/*.tmpl.html

Dies würde zur Kompilierzeit ein Paket mit den Dateidaten erstellen. Die Glob-Syntax mag hier nett sein, aber vielleicht ist es gut genug, Verzeichnisse zu vereinfachen und nur einzubetten. (Außerdem: +1 für ** Glob-Syntax.)

import "github.com/foo/bar/internal/static"

f, err := static.Open("static/templates/foo.tmpl")

So etwas wie StripPrefix ist hier vielleicht nett, aber nicht notwendig. Einfaches Erstellen eines Wrapper-Pakets, das beliebige Dateipfade verwendet.

Es könnte noch weiter vereinfacht werden:

module github.com/foo/bar

data ./static/*.tmpl.html
import "runtime/moddata"

moddata.Open("static/foo.tmpl")

Aber es ist ein bisschen unintuitiv, dass Moddata je nach aufrufendem Paket/Modul ein unterschiedliches Verhalten haben würde. Es würde das Schreiben von Helfern erschweren (z. B. http.Dateisystemkonverter)

Es scheint heute möglich zu sein, ohne Ausführung von Benutzercode einen funktionsfähigen Modul-Proxy zu implementieren, der Dateien entfernt, die im Build nicht verwendet werden. Einige der oben genannten Designs würden bedeuten, dass Modul-Proxys Benutzercode ausführen müssen, um herauszufinden, welche statischen Dateien ebenfalls eingeschlossen werden sollen.

Ich glaube nicht, dass sich hier eine wesentliche Änderung ergeben würde. Insbesondere könnte C-Code bereits jede Datei im Baum enthalten, sodass ein Modul-Proxy, der dies tun möchte, C parsen müsste. An diesem Punkt scheint es, dass alle magischen Kommentare oder APIs, die wir einführen, ein kleiner Schritt sein werden.

Einige der oben genannten Designs würden bedeuten, dass Modul-Proxys Benutzercode ausführen müssen, um herauszufinden, welche statischen Dateien ebenfalls eingeschlossen werden sollen.

Ich denke, es ist ziemlich klar, dass "das Go-Tool während des Builds keinen Benutzercode ausführen darf" eine in den Sand gezogene Linie ist, die hier nicht überschritten wird. Und wenn das go-Tool keinen Benutzercode ausführen kann, muss es möglich sein, anzugeben, welche Dateien ohne es einzuschließen sind.

Ich habe versucht, meine verschiedenen Gedanken zu diesem Anwendungsfall in etwas überzeugendes zu verdichten, und daher ein großes +1 zu dem, was @broady vorgeschlagen hat. Ich denke, das fasst zum größten Teil das zusammen, was ich gedacht habe. Ich denke jedoch, dass das Schlüsselwort das Verb embed anstelle des Substantivs data .

  1. Eingebettete Dateien fühlen sich wie etwas an, das importiert werden sollte, anstatt nur einen speziellen Kommentar oder ein magisches Paket zu haben. Und in einem Go-Projekt kann ein Entwickler in der Datei go.mod die benötigten Module/Dateien angeben. Daher ist es sinnvoll, sie um die Einbettung zu erweitern.

  2. Außerdem scheint mir eine Sammlung eingebetteter Dateien wertvoller und wiederverwendbarer zu sein, wenn ein Paket eingefügt werden könnte, anstatt etwas Ad-hoc zu einem Go-Projekt mit einmaliger Syntax hinzuzufügen. Die Idee dabei ist, dass, wenn Embeds als Pakete implementiert würden, die Leute sie über Github entwickeln und teilen könnten und andere sie in ihren Projekten verwenden könnten. Stellen Sie sich von der Community verwaltete und kostenlos nutzbare Pakete auf GitHub vor, die Folgendes enthalten:

    A. Dateien für Länder, in denen jede Datei alle Postleitzahlen dieses Landes enthält,
    B. Eine Datei mit allen bekannten User-Agent-Strings zur Identifizierung von Browsern,
    C. Bilder der Flagge für jedes Land der Welt,
    D. Ausführliche Hilfeinformationen, die häufig auftretende Fehler in einem Go-Programm beschreiben,
    e. und so weiter...

  3. Ein neues URL-Schema wie goembed:// – oder vielleicht ein vorhandenes –, das zum Öffnen und Lesen von Dateien aus dem Paket verwendet werden kann, wodurch _(alle?)_ vorhandene Dateimanipulations-APIs genutzt werden können, anstatt neue zu erstellen diejenigen, die sich auf die im aktuellen Paket enthaltene Einbettung beziehen würden:

    data, err := ioutil.ReadFile("goembed://postal-codes.txt")    
    if (err != nil) {
      fmt.Println(err)
    }
    

Mit den obigen Konzepten fühlt sich nichts wie _"Magie"_ an; alles würde elegant von einem Mechanismus gehandhabt, der sich anfühlt, als wäre er für einen bestimmten Zweck bestimmt. Es wäre nur sehr wenig Erweiterung erforderlich; ein neues Verb in go.mod und ein neues URL-Schema, das von Go intern erkannt wird. Alles andere würde so wie es ist von Go bereitgestellt werden.

Was mache ich nun

Ich verwende dafür gerade code.soquee.net/pkgzip (es ist eine Abzweigung von statik , die die API ändert, um den globalen Status zu vermeiden und Nebenwirkungen zu importieren). Mein normaler Workflow (zumindest in einer Web-App) besteht darin, Assets, die in einer ZIP-Datei gebündelt sind, einzubetten und sie dann mit golang.org/x/tools/godoc/vfs/zipfs und golang.org/x/tools/godoc/vfs/httpfs .

go:embed-Ansatz

Es gibt zwei Dinge, die mich nachweislich davon abhalten würden, den go:embed Ansatz zu übernehmen:

  1. Generierter Code wird nicht in der Dokumentation angezeigt
  2. Assets können über die gesamte Codebasis verstreut sein (dies gilt auch für die Verwendung externer Tools und go:generate , weshalb ich es im Allgemeinen vorziehe, ein Makefile zu verwenden, um verschiedene Assets vor dem Erstellen zu generieren kann sie alle im Makefile sehen)

Es gibt auch ein Problem, das ich oben nicht einbeziehe, da es für einige eine Funktion sein könnte, dass Assets Teil eines Pakets sind (im Gegensatz zum gesamten Modul), was bedeutet, dass die ganze Komplexität von Build-Tags, Testpaketen usw .auf sie anwenden, brauchen wir eine Möglichkeit, anzugeben, ob sie für dieses Paket öffentlich oder privat sind usw. Dies scheint eine Menge zusätzlicher Build-Komplexität zu sein.

Was mir daran gefällt, ist, dass Bibliotheken geschrieben werden können, die das Importieren von Assets vereinfachen. Z.B. eine Bibliothek mit einer einzelnen Go-Datei, die nur eine Schriftart einbettet, oder einige Symbole könnten veröffentlicht werden und ich könnte sie wie jedes andere Go-Paket importieren. In Zukunft könnte ich eine Icon-Schriftart einfach durch Importieren erhalten:

import "forkaweso.me/forkawesome/v2"

Einbettungspaket-Ansatz

Während ich die Idee mag, dass dies alles expliziter, normaler Go-Code ist, hasse ich die Idee, dass dies ein weiteres magisches Paket wäre, das nicht außerhalb der Standardbibliothek implementiert werden kann.

Wäre ein solches Paket als Teil der Sprachspezifikation definiert? Wenn nicht, würde Go-Code zwischen verschiedenen Implementierungen brechen, was sich ebenfalls schlecht anfühlt. Ich würde wahrscheinlich weiterhin ein externes Werkzeug verwenden, um diesen Bruch zu verhindern.

Wie andere bereits erwähnt haben, bedeutet die Tatsache, dass dies zur Build-Zeit geschieht, dass dieses Paket nur String-Literale oder Konstanten als Argumente annehmen kann. Es gibt derzeit keine Möglichkeit, dies im Typsystem darzustellen, und ich vermute, dass dies zu Verwirrung führen wird. Dies könnte gelöst werden, indem man so etwas wie konstante Funktionen einführt, aber jetzt sprechen wir von großen Sprachänderungen, die es zu einem Nichtstarter machen. Ich sehe keine gute Möglichkeit, das anders zu beheben.

Hybrid

Ich mag die Idee eines hybriden Ansatzes. Anstatt Kommentare wiederzuverwenden (die am Ende überall verstreut sind und sich persönlich einfach nur eklig anfühlen), möchte ich alle Assets an einem Ort sehen, wahrscheinlich die Datei go.mod als andere habe gesagt:

module forkaweso.me/forkawesome/v2

go 1.15

embed (
    fonts/forkawesome-webfont.ttf
    fonts/forkawesome-webfont.woff2
)

Das bedeutet, dass Assets nicht von beliebigen Build-Tags oder in beliebigen Paketen (zB dem _testing-Paket) eingeschlossen oder ausgeschlossen werden können, ohne ein separates Modul zu erstellen. Ich denke, diese Reduzierung der Komplexität kann wünschenswert sein (in einer Bibliothek, die Sie importieren möchten, ist kein Build-Tag versteckt, und Sie können nicht herausfinden, warum Sie nicht über das richtige Asset verfügen, da das Importieren der Bibliothek diese eingebettet haben sollte). , aber YMMV. Wenn dies erwünscht ist, können pragmaähnliche Kommentare weiterhin verwendet werden, außer dass sie keinen Code generieren und stattdessen denselben Ansatz verwenden, den ich gleich für die go.mod Version beschreiben werde.

Im Gegensatz zum ursprünglichen Vorschlag würde dies keinen Code generieren. Stattdessen Funktionen für z. das Lesen des Datenabschnitts der ELF-Datei (oder wie auch immer dies auf dem von Ihnen verwendeten Betriebssystem gespeichert wird) würde gegebenenfalls hinzugefügt (z. B. os oder debug/elf usw.) und dann würde optional ein neues Paket erstellt, das sich genau wie das im OP beschriebene Paket verhält, außer dass es nicht magisch ist und die Einbettung selbst durchführt, sondern lediglich die eingebetteten Dateien liest (was bedeutet, dass es außerhalb der Standardbibliothek implementiert werden könnte) wenn gewünscht).

Dies umgeht Probleme wie das Einschränken des magischen Pakets, um nur String-Literale als Argumente zuzulassen, bedeutet jedoch, dass es schwieriger ist zu überprüfen, ob die eingebetteten Assets tatsächlich irgendwo verwendet werden oder am Ende ein Eigengewicht sind. Es vermeidet auch alle neuen Abhängigkeiten zwischen Standardbibliothekspaketen, da das einzige Paket, das noch etwas zusätzlich importieren muss, ein neues Paket selbst ist.

var IconFont = embed.Dir("forkaweso.me/forkawesome/v2/fonts/")
var Logo = embed.File("images/logo.jpg")

Wie oben gesehen, könnte das Einfügen der Ressourcen in das Modul sie auf Wunsch immer noch auf dieses bestimmte Modul beschränken. Die eigentliche API und die Auswahl eines Assets erfordern möglicherweise einige Arbeit.

Noch eine andere Idee: Anstatt eine neue Art von Verb embed in go.mod hinzuzufügen, könnten wir eine neue Art von Paket einführen, ein Datenpaket, das in go.mod importiert und im . verwendet wird normaler Weg. Hier ist eine Strohmann-Skizze.

Wenn ein Paket genau eine .go Datei enthält, static.go und diese Datei nur Kommentare und eine Paketklausel enthält, dann ist ein Paket ein Datenpaket. Beim Importieren füllt cmd/go das Paket mit exportierten Funktionen, die den Zugriff auf die darin enthaltenen Dateien ermöglichen, die in die resultierende Binärdatei eingebettet sind.

Wenn es sich um ein tatsächliches Paket handelt, würde dies bedeuten, dass internal Regeln gelten und wir Zugriffskontrollen haben können, ohne der API etwas hinzuzufügen.

Wie wäre es dann, alle Nicht- .go Dateien und Unterordner (nach den Regeln für keinen tatsächlichen Code) automatisch in das Verzeichnis aufzunehmen?

Wenn ein Paket genau eine .go Datei enthält, static.go und diese Datei nur Kommentare und eine Paketklausel enthält, dann ist ein Paket ein Datenpaket.

Würde diese Prüfung vor der Anwendung von Build-Tags durchgeführt werden? Wenn ja, scheint dies ein weiterer Sonderfall zu sein, der vermieden werden sollte. Wenn nicht, ist es durchaus möglich, dass ein Paket für einige Build-Tags als Standard-Go-Paket und für andere als Datenpaket angesehen wird. Das scheint seltsam, aber vielleicht ist es wünschenswert?

@flimzy
Das würde es einem ermöglichen, eingebettete Dateien mit einem Tag zu verwenden und die gleichen fns/vars wie das generierte Paket zu definieren und die Dateien auf andere Weise (vielleicht entfernt?) mit einem anderen Tag bereitzustellen.

Wäre schön, wenn es ein Build-Flag gäbe, um die Wrapper-Funktionen zu generieren, damit man nur die Leerzeichen ausfüllen muss.

@josharian

Wenn ein Paket genau eine .go Datei enthält, static.go und diese Datei nur Kommentare und eine Paketklausel enthält, dann ist ein Paket ein Datenpaket.

Ich kann mir "Datenpakete" so vorstellen, dass sie ihre eigene domänenspezifische Funktionalität haben, wie z. B. eine Postleitzahl-Suche. Der von Ihnen gerade vorgeschlagene Ansatz würde alles andere als die Rohdaten verbieten und somit die Vorteile der Möglichkeit, Logik mit Daten zu verpacken, zunichte machen.

Ich kann mir "Datenpakete" so vorstellen, dass sie ihre eigene domänenspezifische Funktionalität haben, wie z. B. eine Postleitzahl-Suche.

Sie könnten Funktionen in my.pkg/postalcode bereitstellen und Daten in my.pkg/postalcode/data (oder my.pkg/postalcode/internal/data) einfügen.

Ich sehe den Reiz darin, es so zu machen, wie Sie es vorschlagen, aber es wirft eine Reihe von Fragen auf: Wie funktioniert die Abwärtskompatibilität? Wie kennzeichnet man ein Datenpaket als solches? Was tun Sie, wenn das Paket Funktionen enthält, die mit denen von cmd/go in Konflikt geraten würden? (Ich sage nicht, dass diese keine Antworten haben, nur dass es einfacher ist, sie nicht beantworten zu müssen.)

@josharian , beachten Sie bitte den Kommentar zur Typprüfung oben (https://github.com/golang/go/issues/35950#issuecomment-561443566).

@bradfitz ja, dies wäre eine

Tatsächlich gibt es eine Möglichkeit, dies ohne eine Sprachänderung zu tun – erfordern Sie, dass static.go körperlose Funktionen enthält, die genau dem entsprechen, was cmd/go ausfüllen würde.

erfordern, dass static.go körperlose Funktionen enthält, die genau dem entsprechen, was cmd/go ausfüllen würde.

Wenn es Pro-Datei-Funktionen anstelle eines Catch-All- embed.File() generiert, würde dies einfache Exportkontrollen pro Asset ermöglichen.

So wie das generierte Zeug aussehen würde:

EmbededFoo() embed.Asset {...}
embededBar() embed.Asset {...}

Ein Blogbeitrag, den ich vor 4 Monaten über statische Dateien geschrieben habe. Siehe den letzten Satz in den Schlussfolgerungen :-)

@josharian

Sie könnten Funktionen in my.pkg/postalcode bereitstellen und Daten in my.pkg/postalcode/data (oder my.pkg/postalcode/internal/data) einfügen.

Das – obwohl unelegant – könnte meine Bedenken ausräumen.

Wie funktioniert die Abwärtskompatibilität?

Ich sehe nicht, wie BC-Bedenken hier gelten. Können Sie näher erläutern?

Wie kennzeichnet man ein Datenpaket als solches?

Mit der embed Anweisung im go.mod ?

Vielleicht befolge ich nicht, was Sie fragen.

Aber ich werde es umdrehen; Wie markiert man ein Paket mit nur Daten als solches?

Was tun Sie, wenn das Paket Funktionen enthält, die mit denen von cmd/go in Konflikt geraten würden?

  1. Mit dem vorgeschlagenen Ansatz glaube ich nicht, dass cmd/go zum Hinzufügen von Funktionen erforderlich wäre.

  2. Auch wenn cmd/go Funktionen hinzufügen muss, stelle ich mir vor, dass das Verhalten von Konflikten in einem _vorhandenen_ Paket _undefiniert_ wäre.

    Der Vorschlag geht davon aus, dass der Entwickler dem Einzelverantwortungsprinzip folgt und daher nur ein Paket mit Daten als datenzentriertes Paket erstellen und keine Daten zu einem bestehenden logikzentrierten Paket hinzufügen sollte.

    Natürlich _könnte_ ein Entwickler zu einem bestehenden Paket hinzufügen, in diesem Fall wäre das Verhalten undefiniert. IOW, wenn ein Entwickler Idiom ignoriert, würde er sich auf Neuland begeben.

Ich sage nicht, dass diese keine Antworten haben, nur dass es einfacher ist, sie nicht beantworten zu müssen.

Abgesehen davon, dass ich denke, dass die Antworten einfach sind. Zumindest für die, die Sie bisher posiert haben.

Ich denke, dass jede Lösung, die Symbole oder Werte für Symbole hinzufügt, paketbezogen sein muss, nicht modulbezogen. Weil die Kompilierungseinheit für Go ein Paket ist, nicht ein Modul.

Dies lässt also jede Verwendung von go.mod , um die Liste der zu importierenden Dateien anzugeben.

@dolmen Wenn das Endergebnis in einem eigenen Paket ist, wäre der Geltungsbereich selbst ein Modul.

@urandom Nein, der Geltungsbereich sind die Pakete, die das generierte Paket importieren. Aber wie auch immer, ich glaube nicht, dass ein vollständig generiertes Paket im Rahmen dieses Vorschlags liegt.

@urandom Nein, der Geltungsbereich sind die Pakete, die das generierte Paket importieren. Aber wie auch immer, ich glaube nicht, dass ein vollständig generiertes Paket im Rahmen dieses Vorschlags liegt.

Unabhängig davon, wie dieser Vorschlag implementiert würde, ist es angesichts der Tatsache, dass verschiedene Modulpakete das Endergebnis verwenden, sinnvoll, dass die Definition dessen, was eingebettet wird, auf Modulebene festgelegt wird. ein Präzedenzfall dafür existiert bereits im Java-Ökosystem, wo eingebettete Dateien modulbezogen sind und aus einem magischen Verzeichnis hinzugefügt werden.

Darüber hinaus bietet go.mod den saubersten Weg, ein solches Feature hinzuzufügen (keine magischen Kommentare oder magischen Verzeichnisse), ohne bestehende Programme zu beschädigen.

Hier ist etwas, das noch nicht erwähnt wurde: Die API für Go-Quellverarbeitungstools (Compiler, statische Analysatoren) ist genauso wichtig wie die Laufzeit-API. Diese Art von API ist ein Kernwert von Go, der zum Wachstum des Ökosystems beiträgt (wie go/ast / go/format und go mod edit ).

Diese API könnte von Präprozessor-Tools (insbesondere in go:generate Schritten) verwendet werden, um die Liste der einzubettenden Dateien abzurufen, oder sie könnte verwendet werden, um die Referenz zu generieren.

Im Fall eines speziellen Pakets sehe ich nichts zu ändern in go.mod Parsing ( go mod Tools) oder go/ast Parser.

@dolmen

_"Ich denke, dass jede Lösung, die Symbole oder Werte für Symbole hinzufügt, paketbezogen sein muss, nicht modulbezogen zu importierende Dateien."_

Was ist Modul? Module sind _" eine Sammlung zusammengehöriger Go-Pakete, die zusammen als eine Einheit versioniert sind ."_ Somit kann ein Modul aus einem einzelnen Paket bestehen, und ein einzelnes Paket kann die Gesamtheit eines Moduls sein.

Daher ist go.mod der richtige Ort, um die Liste der zu importierenden Dateien anzugeben, vorausgesetzt, das Go-Team übernimmt go.mod gegenüber speziellen Kommentaren und magischen Paketen. Es sei denn, das Go-Team beschließt, eine go.pkg Datei hinzuzufügen.

Wenn das Go-Team außerdem go.mod als Ort zum Festlegen von Einbettungsdateien akzeptieren würde, sollte jeder, der Dateien einbetten möchte, ein go.mod mit den embed Anweisungen und dem Paket bereitstellen was durch das Verzeichnis repräsentiert wird, in dem sich die go.mod Datei befindet, wäre das Paket, das die eingebetteten Dateien enthält.

Aber wenn der Entwickler das nicht will, sollte er eine weitere go.mod Datei erstellen und sie in das Verzeichnis für das Paket legen, das seine eingebetteten Dateien enthalten soll.

Gibt es ein legitimes Szenario, das Sie sich vorstellen, in dem diese Einschränkungen nicht durchführbar wären?

@mikeschinkel , ein Modul ist eine Sammlung von _bezogenen_ Paketen. Es ist jedoch möglich (und sinnvoll!), ein Paket aus einem Modul zu verwenden, ohne die transitiven Abhängigkeiten (und Daten!) anderer Pakete innerhalb dieses Moduls mit einzubeziehen.

Datendateien sind im Allgemeinen paketweise Abhängigkeiten, nicht modulbezogen, daher sollten die Informationen zum Auffinden dieser Abhängigkeiten zusammen mit dem Paket platziert werden – nicht als separate Metadaten auf Modulebene gespeichert.

@bcmills

Es scheint, dass man 'Datendateien' in Ihrer Nachricht durch 'Module' ersetzen kann und es gilt immer noch.
Es ist durchaus üblich, bestimmte Module als Abhängigkeiten für bestimmte eigene Pakete zu haben.
Aber wir haben sie alle in go.mod gepackt.

@urandom , nicht alle Pakete in den Modulen, die in der Datei go.mod sind, sind mit der endgültigen Binärdatei verknüpft. (Das Einfügen einer Abhängigkeit in die Datei go.mod ist _nicht_ gleichbedeutend mit dem Verknüpfen dieser Abhängigkeit mit dem Programm.)

Meta-Punkt

Es ist klar, dass dies etwas ist, das viele Leute interessiert, und Brads ursprünglicher Kommentar oben war weniger ein fertiger Vorschlag als eine erste Skizze / ein Ausgangspunkt / eine Aufforderung zum Handeln. Ich denke, es wäre an dieser Stelle sinnvoll, diese spezielle Diskussion zu beenden, Brad und vielleicht ein paar andere Leute an einem detaillierten Designdokument zusammenzuarbeiten und dann eine neue Ausgabe für eine Diskussion dieses spezifischen (noch zu schreibenden) Dokuments zu starten . Es scheint, als würde das helfen, sich auf das zu konzentrieren, was zu einem weitläufigen Gespräch geworden ist.

Die Gedanken?

Ich bin mir nicht sicher, ob ich damit einverstanden bin, dieses zu schließen, aber wir können es vorschlagen, bis es ein Designdokument gibt, und Kommentare für eine Weile sperren. (Fast alle Kommentare sind an dieser Stelle überflüssig, da es zu viele Kommentare gibt, als dass die Leute lesen könnten, ob ihr Kommentar überflüssig ist...)

Wenn es ein Design-Dokument gibt, kann dieses vielleicht geschlossen werden.

Oder schließen Sie dieses und öffnen Sie #3035 (mit eingefrorenen Kommentaren), damit mindestens ein offenes Problem es verfolgt.

Es tut mir leid, dies gleich nach dem Gespräch über das Schließen zu tun, aber die enge Diskussion sprang direkt nach dem Kommentar von

"_Allerdings ist es möglich (und sinnvoll!), ein Paket aus einem Modul zu verwenden, ohne die transitiven Abhängigkeiten (und Daten!) anderer Pakete innerhalb dieses Moduls mit einzubeziehen."_

Ja, das ist eindeutig _möglich._ Aber wie bei jeder bewährten Methode könnte eine bewährte Methode für Datenpakete darin bestehen, ein einzelnes Paket für ein Modul zu erstellen, was Ihr Anliegen ausräumt. Wenn das ein go.mod im Stammverzeichnis und ein weiteres go.mod im Unterverzeichnis mit dem Datenpaket bedeutet, dann sei es so.

Ich denke, ich plädiere dafür, dass Sie hier nicht den Perfekten zum Feind des Guten machen, wo das Gute hier als go.mod identifiziert wird und ein perfekter Ort ist, um Einbettungsdateien anzugeben, da sie von Natur aus eine Auflistung der Komponenten eines Moduls.

Entschuldigung, aber Pakete sind das grundlegende Konzept in Go, nicht Module.
Module sind einfach Gruppen von Paketen, die als Einheit versioniert werden.
Module liefern keine zusätzliche Semantik, die über die der einzelnen Pakete hinausgeht.
Das ist ein Teil ihrer Einfachheit.
Alles, was wir hier tun, sollte an Pakete gebunden sein, nicht an Module.

Wo immer dies hinführt und wie auch immer, es sollte eine Möglichkeit geben, eine Liste aller Assets zu erhalten, die mithilfe der Go-Liste eingebettet werden (nicht nur die verwendeten Muster).

In der Warteschleife, bis Brad und andere ein formales Designdokument ausarbeiten.

Das Blockieren bestimmter Dateien sollte nicht allzu schwer sein, insbesondere wenn Sie ein static oder embed Verzeichnis verwenden. Symlinks könnten dies etwas verkomplizieren, aber Sie können einfach verhindern, dass etwas außerhalb des aktuellen Moduls oder, wenn Sie sich auf GOPATH befinden, außerhalb des Pakets eingebettet wird, das das Verzeichnis enthält.

Ich bin nicht besonders ein Fan von Kommentaren, die in Code kompiliert werden, aber ich finde auch das Pseudo-Paket, das die Kompilierung beeinflusst, etwas seltsam. Wenn der Verzeichnisansatz nicht verwendet wird, kann es vielleicht etwas sinnvoller sein, eine Art embed -Deklaration der obersten Ebene tatsächlich in die Sprache integriert zu haben. Es würde ähnlich wie import funktionieren, würde aber nur lokale Pfade unterstützen und würde einen Namen erfordern, dem es zugewiesen werden kann. Zum Beispiel,

embed ui "./ui/build"

func main() {
  file, err := ui.Open("version.txt")
  if err != nil {
    panic(err)
  }
  version, err = ioutil.ReadAll(file)
  if err != nil {
    panic(err)
  }
  file.Close()

  log.Printf("UI Version: %s\n", bytes.TrimSpace(version))
  http.ListenAndServe(":8080", http.EmbeddedDir(ui))
}

Edit: Du hast mich geschlagen, @jayconrod.

Dies ist sauber und lesbar, aber ich bin mir nicht sicher, ob das Go-Team ein neues Schlüsselwort einführen möchte

Die Idee, wie ich mich erinnere, war, nur einen speziellen Verzeichnisnamen wie "static" zu haben, der die statischen Daten enthält, und sie automatisch über eine API verfügbar zu machen, ohne dass Anmerkungen erforderlich sind.

Die Verwendung von static als spezieller Verzeichnisname ist etwas verwirrend und ich würde eher assets .
Eine andere Idee, die ich im Thread nicht gesehen habe, ist das Importieren von assets als Paket, zB import "example.com/internal/assets" . Die exponierte API braucht noch ein Design, aber sie sieht zumindest sauberer aus als spezielle Kommentare oder neue runtime/files -Pakete.

Eine andere Idee, die ich im Thread nicht gesehen habe, ist das Importieren von Assets als Paket

Dies wurde hier vorgeschlagen: https://github.com/golang/go/issues/35950#issuecomment -562966654

Eine Komplikation besteht darin, dass Sie, um die Typprüfung zu ermöglichen, entweder eine Sprachänderung vornehmen oder körperlose Funktionen bereitstellen müssen, die von cmd/go ausgefüllt werden .

Das ist eine ähnliche Idee, aber das Design von static.go ermöglicht es, einen beliebigen Importpfad in ein Datenpaket umzuwandeln, während das Verzeichnis assets eher wie testdata , internal oder vendor funktioniert. assets könnte, ist, keine Go-Pakete zu enthalten (oder nur Dokumente zuzulassen), dh für implizite Abwärtskompatibilität.

Dies könnte auch mit der runtime/files -thingy API zum Abrufen der Dateien kombiniert werden. Das heißt, Sie verwenden rohe import s zum Einbetten von Verzeichnisbäumen in Dateien und verwenden dann ein Laufzeitpaket für den Zugriff darauf. Könnte sogar os.Open , aber das wird wahrscheinlich nicht akzeptiert.

Das shurcooL/vfsgen zusammen shurcooL/httpgzip hat eine nette Funktion, mit der der Inhalt ohne Dekompression bereitgestellt werden kann.

z.B

    rsp.Header().Set("Content-Type", "image/png")
    httpgzip.ServeContent(rsp, req, "", time.Time{}, file)

Eine ähnliche Funktion wird für C++ vorgeschlagen: std::embed :

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1040r0.html
https://mobile.twitter.com/Cor3ntin/status/1208389050698215427

Es kann als Inspiration für ein Design nützlich sein und um mögliche Anwendungsfälle zu sammeln.

Ich bin etwas spät zur Party, aber ich hatte eine Idee. Statt spezieller Kommentare, ein festes spezielles Verzeichnis (statisch) oder der scheinbar no-no-Ansatz, go.mod zu erweitern: Wie wäre es mit einer neuen Manifest-Datei, die pro Paket ist: go.res

  • Enthält eine Liste von Dateien. Keine Pfade oder Globs, nur Namen im aktuellen Paketverzeichnis. Generieren Sie es aus einem Glob, bevor Sie ein Commit durchführen, wenn Sie es benötigen.

    • __Edit__ es könnte eine einzelne package mypackagename Zeile am Anfang benötigen, wie es eine go-Datei tun würde. Alternativ können Sie den Paketnamen in den Dateinamen aufnehmen (zB mypackagename.go.res). Mir persönlich gefällt die Kopfzeile package besser.

  • Ein neues Kernpaket namens „resource“ oder vielleicht „io/resource“. Hat mindestens eine Funktion: func Read(name string) (io.Reader, bool) zum Lesen von Ressourcen, die im aktuellen Paket eingebettet sind.

    • __Bearbeiten__ Ich bin mir nicht sicher, ob Kernpakete auf diese Weise funktionieren. Möglicherweise muss es sich um eine generierte private Paketfunktion handeln (z. B. func readresource(name string) (io.Reader, bool) )

  • Wenn Sie Ressourcen in einem Unterverzeichnis haben möchten, machen Sie das Unterverzeichnis zu einem Paket, indem Sie eine go.res-Datei und mindestens eine .go Datei hinzufügen. Die go-Datei exportiert Ihre eigene öffentliche API für den Zugriff auf Ressourcen im Unterverzeichnispaket. Die go-Datei und die exportierte API sind notwendig, da Ressourcen aus anderen Paketen nicht automatisch exportiert werden (von Design). Sie können auch anpassen, wie sie auf diese Weise exportiert werden.

    • __Edit__ Alternativ, wenn Sie eine Verzeichnisstruktur und/oder Komprimierung benötigen, verwenden Sie eine tar-Ressource. Dies ermöglicht Dinge wie Webpack-Bundles, die bereits eine Kompilierung erfordern (und von einer Vorkomprimierung profitieren könnten). Es ist einfach, sie einen Schritt weiter zu einem Teer zu bringen.

  • __Bearbeiten__ Benötigen Sie ein Manifest? Fügen Sie einfach die Datei go.res selbst als Ressource ein. Wir müssen nicht einmal eine listresources-Funktion erstellen.

Ganz einfach. Eine neue Funktion. Eine neue Datei. Keine Pfade. Keine Kompression. Keine neue Syntax. Keine Magie. Erweiterbar. Readonly-Zugriff über Leser (aber offen für alternative Zugriffsmuster in der Zukunft). Nahezu keine Möglichkeit, vorhandene Pakete zu brechen. Bleibt dabei, dass das Paket das Kernkonstrukt in go ist.

__Edit__ Nach einer Github-Suche language:go filename:go.res extension:res scheint go.res ein ziemlich sicherer Dateiname zu sein. Es gibt keine Übereinstimmungen in Go-Repos und nur wenige in Non-Go-Repos.

Ich mag die Idee von @chris.ackermanm. Aber ich würde eine Kombination bevorzugen:

Eine go.res-Datei, die den Namespace innerhalb eines Verzeichnisses angibt.

Dies ermöglicht

  • multiple include solange sich der Namespace unterscheidet
  • die Dateien vorher nicht kennen und eine Liste erstellen müssen

Letzteres sollte die Ausgabe von Webpack und dergleichen in Angriff nehmen, die das Layout aufgrund von Updates, verschiedenen Optionen, was auch immer Sie sich vorstellen können, ändern können.

In Bezug auf die Komprimierung: Dies ist meiner Meinung nach eher eine Funktion, um die binären Größen nicht explodieren zu lassen und sollte für den verwendenden Code transparent sein.

Später könnten Sie Umschreibungen wie z

filename => stored-as.png

Nur meine 2¢

@sascha-andres Es scheint, als ob Ultra-Einfachheit und Null-Magie der Ton dieses Threads sind. Sehen Sie sich die Änderungen an, die ich an meinem Kommentar zu Ihren Vorschlägen vorgenommen habe.

Die Zuordnung gefällt mir nicht. Das ist nicht nötig. Das ist möglich, indem Sie Ihre eigene Lesefunktion ohnehin aus einem separaten Paket bereitstellen, und jetzt brauchen wir eine neue Dateisyntax oder etwas Komplexeres als Datei-pro-Zeile.

Hi

Dieser Vorschlag ist genial!

Und ich habe meinen Ansatz, Assets einzubetten. keine Notwendigkeit, andere Tools als GNU-Bintools einzuführen. Es ist irgendwie schmutzig, aber funktioniert für mich jetzt gut. Ich möchte es nur teilen und sehen, ob es hilft.

Mein Ansatz besteht darin, einfach meine Assets (komprimiert mit tar&gz) in einen elf/pe32-Abschnitt mit objcopy einzubetten und sie bei Bedarf über die Pakete debug/elf und debug/pe32 zusammen mit zip zu lesen. Ich muss mich nur daran erinnern, keinen vorhandenen Abschnitt zu berühren. alle Assets sind unveränderlich, und dann liest der Code den Inhalt und verarbeitet ihn im Speicher.

Ich bin ziemlich unerfahren mit Sprachdesign oder Compilerdesign. Also würde ich einfach den oben beschriebenen Ansatz verwenden und .goassets oder so ähnlich als Abschnittsnamen verwenden. und machen Sie die Komprimierung optional.

Mein Ansatz besteht darin, einfach meine Assets (komprimiert mit tar&gz) in einen elf/pe32-Abschnitt mit objcopy einzubetten und sie bei Bedarf über die Pakete debug/elf und debug/pe32 zusammen mit zip zu lesen. Ich muss mich nur daran erinnern, keinen vorhandenen Abschnitt zu berühren. alle Assets sind unveränderlich, und dann liest der Code den Inhalt und verarbeitet ihn im Speicher.

Das hört sich so an, als würde es auf elf / pe32 funktionieren, aber was ist mit mach-o / plan9 ?

Ein weiteres Problem ist, dass es auf das Öffnen eines Dateihandles für die ausführbare Datei angewiesen ist. Wenn die ausführbare Datei überschrieben / aktualisiert / gelöscht wurde, werden andere Daten zurückgegeben, nicht sicher, ob dies ein legitimes Problem oder eine unerwartete Funktion ist.

Ich hatte selbst ein bisschen versucht (mit Debug/Macho ), aber ich sehe keinen Weg, um diese plattformübergreifende Funktion zu erhalten, ich baue auf macOS und die installierten GNU-Binutils scheinen nur die mach-o-x86-64 zu beschädigen mach-o Struktur sein und zu lange, seit ich mir objcopy ).

Ein weiteres Problem ist, dass es auf das Öffnen eines Datei-Handles für die ausführbare Datei angewiesen ist

Ich bin mir ziemlich sicher, dass der Programmlader den Ressourcenabschnitt in den Speicher laden wird (oder könnte), sodass keine Debug-Pakete verwendet werden müssen. Der Zugriff auf die Daten würde jedoch viel mehr Basteln mit Objektdateien erfordern, als es wert ist.

Warum nicht folgen, was funktioniert - zB wie Java es macht. Ich würde verlangen, dass die Dinge ein großer Go-ish sind, aber etwas in den Zeilen:

  • Erstellen Sie eine go.res Datei oder ändern Sie go.mod , um auf das Verzeichnis zu verweisen, in dem sich die Ressourcen befinden
  • alle Dateien aus diesem Verzeichnis werden automatisch eingeschlossen, keine Ausnahmen vom Compiler in der endgültigen ausführbaren Datei
  • language bietet eine pfadähnliche API für den Zugriff auf diese Ressourcen

Komprimierung usw. sollten außerhalb des Umfangs dieser Ressourcenbündelung liegen und können // go:generate Bedarf bis zu

Hat sich jemand Markbates/Pkger angesehen ? Es ist eine ziemlich einfache Lösung, go.mod als aktuelles Arbeitsverzeichnis zu verwenden. Angenommen, ein index.html soll eingebettet werden, würde es pkger.Open("/index.html") öffnen. Ich denke, dies ist eine bessere Idee, als ein static/ Verzeichnis im Projekt fest zu kodieren.

Erwähnenswert ist auch, dass Go meines Erachtens keine nennenswerten Strukturanforderungen an ein Projekt stellt. go.mod ist nur eine Datei und nicht viele Leute verwenden jemals vendor/ . Ich persönlich glaube nicht, dass ein static/ Verzeichnis gut wäre.

Da wir bereits eine Möglichkeit haben (wenn auch begrenzte) Daten in einen Build über das vorhandene ldflags Link-Flag -X importpath.name=value , könnte dieser Codepfad angepasst werden, um -X importpath.name=@filename zum Einfügen zu akzeptieren externe beliebige Daten?

Mir ist klar, dass dies nicht alle angegebenen Ziele des ursprünglichen Problems abdeckt, aber als Erweiterung der bestehenden -X Funktionalität scheint es ein vernünftiger Schritt nach vorne zu sein?

(Und wenn das funktioniert, dann ist die Erweiterung der go.mod Syntax als eine sauberere Möglichkeit, ldflags -X Werte anzugeben, der nächste vernünftige Schritt?)

Das ist eine sehr interessante Idee, aber ich mache mir Sorgen über die Auswirkungen auf die Sicherheit.

Es ist ziemlich üblich, -X 'pkg.BuildVersion=$(git rev-parse HEAD)' zu tun, aber wir möchten go.mod nicht willkürliche Befehle ausführen lassen, oder? (Ich schätze, gogenere tut das, aber das ist nichts, was Sie normalerweise für heruntergeladene OSS-Pakete ausführen.) Wenn go.mod damit nicht umgehen kann, fehlt ein wichtiger Anwendungsfall, sodass ldflags immer noch sehr verbreitet sind.

Dann gibt es noch das andere Problem, um sicherzustellen, dass @filename kein symbolischer Link zu /etc/passwd oder was auch immer ist.

Die Verwendung des Linkers schließt die Unterstützung für WASM und möglicherweise andere Ziele aus, die keinen Linker verwenden.

Basierend auf der Diskussion hier haben @bradfitz und ich ein Design ausgearbeitet, das sich irgendwo in der Mitte der beiden oben betrachteten Ansätze befindet und das scheinbar Beste von jedem verwendet. Ich habe einen Entwurf für ein Designdokument, ein Video und einen Code veröffentlicht (Links unten). Anstelle von Kommentaren zu diesem Thema verwenden Sie bitte die Reddit-Fragen und Antworten für Kommentare zu diesem spezifischen Entwurfsentwurf – Reddit führt Diskussionen besser und skaliert Diskussionen besser als GitHub. Vielen Dank!

Video: https://golang.org/s/draft-embed-video
Gestaltung: https://golang.org/s/draft-embed-design
Fragen und Antworten: https://golang.org/s/draft-embed-reddit
Code: https://golang.org/s/draft-embed-code

@rsc Meiner Meinung nach ist der go:embed-Vorschlag minderwertiger als die Bereitstellung von _universal_ sandboxed Go-Codeausführung zur Kompilierzeit, die das Lesen von Dateien und das Umwandeln von gelesenen Daten in ein _optimales Format_ umfasst, das sich am besten für den Verbrauch zur Laufzeit eignet.

@atomsymbol Das klingt nach etwas außerhalb des Rahmens dieses Themas.

@atomsymbol Das klingt nach etwas außerhalb des Rahmens dieses Themas.

Das ist mir bewusst.

Ich habe den Vorschlag durchgelesen und den Code gescannt, konnte aber keine Antwort darauf finden: Enthält dieses Einbettungsschema Informationen über die Datei auf der Festplatte (~os.Stat)? Oder werden diese Zeitstempel auf die Build-Zeit zurückgesetzt? In jedem Fall sind dies nützliche Informationen, die an verschiedenen Stellen verwendet werden, z. B. können wir auf dieser Grundlage eine 304 für unveränderte Assets senden.

Vielen Dank!

Edit: Habe es im Reddit-Thread gefunden.

Die Änderungszeit für alle eingebetteten Dateien ist die Nullzeit, für genau die von Ihnen aufgeführten Reproduzierbarkeitsbedenken. (Module zeichnen aus dem gleichen Grund nicht einmal Änderungszeiten auf.)

https://old.reddit.com/r/golang/comments/hv96ny/qa_goembed_draft_design/fytj7my/

In jedem Fall sind dies nützliche Informationen, die an verschiedenen Stellen verwendet werden, z. B. können wir auf dieser Grundlage eine 304 für unveränderte Assets senden.

Ein ETag-Header basierend auf dem Dateidaten-Hash würde dieses Problem lösen, ohne dass Sie etwas über Datumsangaben wissen müssen. Aber das müsste von http.HandlerFS oder so bekannt sein, um arbeiten zu können und keine Ressourcen zu verschwenden, es müsste nur einmal pro Datei durchgeführt werden.

Aber das müsste von http.HandlerFS oder so bekannt sein, um arbeiten zu können und keine Ressourcen zu verschwenden, es müsste nur einmal pro Datei durchgeführt werden.

Wie würde http.HandlerFS wissen, dass das fs.FS unveränderlich war? Sollte es eine optionale IsImmutable() bool Schnittstelle geben?

Wie würde http.HandlerFS wissen, dass das fs.FS unveränderlich war? Sollte es eine optionale IsImmutable() bool Schnittstelle geben?

Ich möchte nicht auf Implementierungsdetails eingehen, da ich nicht der Designer dieser Dinge bin, aber http.HandlerFS könnte überprüfen, ob es sich um einen embed.FS-Typ handelt, und darauf als Sonderfall reagieren, ich glaube nicht, dass das jemand möchte Erweitern Sie jetzt die FS-API. Es könnte auch ein Optionsargument für HandlerFS geben, das speziell anweist, ein Dateisystem als unveränderlich zu behandeln. Auch wenn dies beim Start der Anwendung erfolgt und alle ctime / mtime einen Nullwert haben, könnte handlerFS diese Informationen verwenden, um zu "wissen", dass sich die Datei nicht geändert hat, aber es gibt auch Dateisysteme, die mtime möglicherweise nicht haben oder deaktiviert haben da könnte es auch probleme geben.

Ich habe mir die Kommentare zu diesem Thema nicht angesehen.

@atomsymbol willkommen zurück! Schön, dass Sie hier wieder kommentieren.
Ich stimme grundsätzlich zu, dass viele Dinge einfacher wären, wenn wir Sandboxing hätten.
Auf der anderen Seite könnten viele Dinge schwieriger sein – Builds werden möglicherweise nie abgeschlossen.
Jedenfalls haben wir heute diese Art von Sandboxing definitiv nicht. :-)

@kokes Ich bin mir bei den Details nicht sicher,
aber wir werden sicherstellen, dass die Bereitstellung einer eingebetteten Datei über HTTP standardmäßig die richtigen ETags erhält.

Ich habe Nr. 41191 für die Annahme des im Juli veröffentlichten Designentwurfs eingereicht.
Ich werde dieses Thema schließen, da es durch dieses ersetzt wurde.
Danke für das tolle Vorgespräch hier.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen