Go: proposition : cmd/go : prise en charge de l'intégration d'éléments statiques (fichiers) dans des fichiers binaires

CrĂ©Ă© le 4 dĂ©c. 2019  Â·  176Commentaires  Â·  Source: golang/go

Il existe de nombreux outils pour intégrer des fichiers d'actifs statiques dans des binaires :

En fait, https://tech.townsourced.com/post/embedding-static-files-in-go/ répertorie plus :

Proposition

Je pense qu'il est temps de bien le faire une fois et de réduire les doublons, en ajoutant un support officiel pour l'intégration de ressources de fichiers dans l'outil cmd/go.

ProblĂšmes avec la situation actuelle :

  • Il y a trop d'outils
  • L'utilisation d'une solution basĂ©e sur go:generate gonfle l'historique de git avec une deuxiĂšme copie (et lĂ©gĂšrement plus grande) de chaque fichier.
  • Ne pas utiliser go:generate signifie ne pas ĂȘtre go install -capable ou obliger les gens Ă  Ă©crire leurs propres Makefiles, etc.

Buts:

  • ne pas archiver les fichiers gĂ©nĂ©rĂ©s
  • ne gĂ©nĂ©rez pas du tout de fichiers *.go (du moins pas dans l'espace de travail de l'utilisateur)
  • faire go install / go build faire l'intĂ©gration automatiquement
  • laisser l'utilisateur choisir par fichier/glob quel type d'accĂšs est nĂ©cessaire (par exemple []byte, func() io.Reader , io.ReaderAt , etc)
  • Peut-ĂȘtre stocker les ressources compressĂ©es dans le binaire le cas Ă©chĂ©ant (par exemple, si l'utilisateur n'a besoin que d'un io.Reader ) ? ( edit : mais probablement pas ; voir les commentaires ci-dessous)
  • Pas d'exĂ©cution de code Ă  la compilation ; c'est une politique de Go de longue date. go build ou go install ne peuvent pas exĂ©cuter de code arbitraire, tout comme go:generate ne s'exĂ©cute pas automatiquement au moment de l'installation.

Les deux principales approches d'implémentation sont //go:embed Logo logo.jpg ou un package bien connu ( var Logo = embed.File("logo.jpg") ).

go:embed approche

Pour une approche go:embed , on pourrait dire que tout fichier go/build sélectionné *.go peut contenir quelque chose comme :

//go:embed Logo logo.jpg

Qui, disons, compile pour :

func Logo() *io.SectionReader

(ajout d'une dépendance au package io )

Ou:

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

compiler pour, dites :

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

De toute évidence, ce n'est pas entiÚrement étoffé. Il faudrait aussi quelque chose pour les fichiers compressés qui ne donnent qu'un io.Reader .

approche de paquet intégré

L'autre approche de haut niveau consiste à ne pas avoir //go:embed syntaxe "embed" ou "golang.org/x/foo/embed" :

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

Ensuite, demandez Ă  cmd/go de reconnaĂźtre les appels Ă  embed.Foo("foo/*.js"), etc. et glob effectue le travail dans cmd/go, plutĂŽt qu'Ă  l'exĂ©cution. Ou peut-ĂȘtre que certaines balises ou drapeaux de construction pourraient le faire revenir Ă  faire des choses au moment de l'exĂ©cution Ă  la place. Perkeep (liĂ© ci-dessus) a un tel mode, ce qui est bien pour accĂ©lĂ©rer le dĂ©veloppement incrĂ©mentiel oĂč vous ne vous souciez pas de lier un gros binaire.

Préoccupations

  • Choisissez un style (//go:embed* vs un package magique).
  • Bloquer certains fichiers ?

    • Probablement bloquer l'encastrement ../../../../../../../../../../etc/shadow

    • Peut-ĂȘtre bloquer l'accĂšs Ă  .git aussi

Proposal Proposal-Hold

Commentaire le plus utile

@robpike et moi avons discutĂ© d'une proposition pour faire cela il y a des annĂ©es (avant qu'il n'y ait un processus de proposition) et nous n'avons jamais recommencĂ© Ă  faire quoi que ce soit. Ça me dĂ©range depuis des annĂ©es que nous n'ayons jamais fini de faire ça. L'idĂ©e, si je me souviens bien, Ă©tait d'avoir simplement un nom de rĂ©pertoire spĂ©cial comme "static" contenant les donnĂ©es statiques et de les rendre automatiquement disponibles via une API, sans annotations nĂ©cessaires.

Je ne suis pas convaincu de la complexité d'un bouton "compressé vs pas". Si nous faisons cela, les gens voudront que nous ajoutions un contrÎle sur la compression, le niveau de compression, etc. Tout ce que nous devrions ajouter, c'est la possibilité d'intégrer un fichier d'octets bruts. Si les utilisateurs souhaitent stocker des données compressées dans ce fichier, tant mieux, les détails leur appartiennent et aucune API n'est nécessaire du cÎté de Go.

Tous les 176 commentaires

Cela vaut la peine de se demander si embedglob doit prendre en charge une arborescence de fichiers complĂšte, peut-ĂȘtre en utilisant la syntaxe ** prise en charge par certains shells Unix.

Certaines personnes auraient besoin de pouvoir servir les ressources intégrées avec HTTP en utilisant le http.FileServer .

Personnellement, j'utilise soit mjibson/esc (qui fait cela) soit, dans certains cas, ma propre implémentation d'intégration de fichiers qui renomme les fichiers pour créer des chemins uniques et ajoute une carte des chemins d'origine aux nouveaux, par exemple "/js/bootstrap.min.js": "/js/bootstrap.min.827ccb0eea8a706c4c34a16891f84e7b.js" . Ensuite, vous pouvez utiliser cette carte dans les modÚles comme ceci : href="{{ static_path "/css/bootstrap.min.css" }}" .

Je pense qu'une conséquence de cela serait qu'il ne serait pas trivial de déterminer quels fichiers sont nécessaires pour construire un programme.

L'approche //go:embed introduit Ă©galement un autre niveau de complexitĂ©. Vous auriez Ă  analyser les commentaires magiques pour mĂȘme taper le code. L'approche « embed package » semble plus conviviale pour l'analyse statique.

(Je réfléchis juste à voix haute ici.)

@opennota ,

aurait besoin de la capacité de servir les actifs intégrés avec HTTP en utilisant le http.FileServer .

Oui, le premier lien ci-dessus est un package que j'ai écrit ( en 2011, avant Go 1 ) et que j'utilise toujours, et il prend en charge l'utilisation de http.FileServer : https://godoc.org/perkeep.org/pkg/fileembed#Files.Open

@cespare ,

L'approche //go:embed introduit Ă©galement un autre niveau de complexitĂ©. Vous auriez Ă  analyser les commentaires magiques pour mĂȘme taper le code. L'approche « embed package » semble plus conviviale pour l'analyse statique.

Oui, bonne remarque. C'est un argument trĂšs fort pour l'utilisation d'un package. Cela le rend Ă©galement plus lisible et documentable, car nous pouvons tout documenter avec godoc ordinaire, plutĂŽt qu'en profondeur dans les documents de cmd/go.

@bradfitz - Voulez-vous fermer ce https://github.com/golang/go/issues/3035 ?

@agnivade , merci d'avoir trouvé ça ! Je pensais m'en souvenir mais je ne l'ai pas trouvé. Laissons-le ouvert pour le moment et voyons ce que les autres en pensent.

Si nous utilisons le package magique, nous pourrions utiliser l'astuce du type non exporté pour nous assurer que les appelants transmettent les constantes de compilation en tant qu'arguments : https://play.golang.org/p/RtHlKjhXcda.

(Il s'agit de la stratégie référencée ici : https://groups.google.com/forum/#!topic/golang-nuts/RDA9Hag8RZw/discussion)

Une prĂ©occupation que j'ai est de savoir comment gĂ©rer les ressources individuelles ou tous les actifs trop volumineux pour tenir dans la mĂ©moire et s'il y aurait peut-ĂȘtre une Ă©tiquette de construction ou une option d'accĂšs par fichier pour choisir entre la prise en charge du temps d'accĂšs par rapport Ă  l'empreinte mĂ©moire ou une implĂ©mentation intermĂ©diaire.

la façon dont j'ai rĂ©solu ce problĂšme (parce que bien sĂ»r j'ai aussi ma propre implĂ©mentation :) ) est de fournir une implĂ©mentation http.FileSystem qui sert tous les actifs intĂ©grĂ©s. De cette façon, vous ne comptez pas sur des commentaires magiques pour apaiser le vĂ©rificateur de types, les actifs peuvent facilement ĂȘtre servis par http, une implĂ©mentation de secours peut ĂȘtre fournie Ă  des fins de dĂ©veloppement (http.Dir) sans changer le code, et la version finale L'implĂ©mentation est assez polyvalente, car http.FileSystem couvre pas mal de choses, non seulement dans la lecture de fichiers, mais aussi dans la liste des rĂ©pertoires.

On peut toujours utiliser des commentaires magiques ou autre pour spĂ©cifier ce qui doit ĂȘtre intĂ©grĂ©, bien qu'il soit probablement plus facile de spĂ©cifier tous les globs via un fichier texte brut.

@AlexRouSg Cette proposition ne concernerait que les fichiers qu'il convient d'inclure directement dans l'exécutable final. Il ne serait pas approprié de l'utiliser pour des fichiers trop volumineux pour tenir en mémoire. Il n'y a aucune raison de compliquer cet outil pour gérer ce cas ; dans ce cas, n'utilisez pas cet outil.

@ianlancetaylor , je pense que la distinction que @AlexRouSg faisait était entre avoir les fichiers fournis en tant que []byte s globaux (mémoire non paginable, potentiellement inscriptible) et fournir une vue en lecture seule et à la demande d'une section ELF qui peut normalement en direct sur le disque (dans l'exécutable), comme via un appel Open qui renvoie un *io.SectionReader . (Je ne veux pas intégrer http.File ou http.FileSystem dans cmd/go ou runtime... net/http peut fournir un adaptateur.)

@bradfitz Ă  la fois http.File lui-mĂȘme est une interface sans dĂ©pendances techniques avec le package http . Il peut ĂȘtre judicieux pour toute mĂ©thode Open de fournir une implĂ©mentation conforme Ă  cette interface, car les mĂ©thodes Stat et Readdir sont toutes deux trĂšs utiles pour de tels actifs

@urandom , il ne pouvait cependant pas implémenter http.FileSystem sans faire référence au nom "http.File" (https://play.golang.org/p/-r3KjG1Gp-8).

@robpike et moi avons discutĂ© d'une proposition pour faire cela il y a des annĂ©es (avant qu'il n'y ait un processus de proposition) et nous n'avons jamais recommencĂ© Ă  faire quoi que ce soit. Ça me dĂ©range depuis des annĂ©es que nous n'ayons jamais fini de faire ça. L'idĂ©e, si je me souviens bien, Ă©tait d'avoir simplement un nom de rĂ©pertoire spĂ©cial comme "static" contenant les donnĂ©es statiques et de les rendre automatiquement disponibles via une API, sans annotations nĂ©cessaires.

Je ne suis pas convaincu de la complexité d'un bouton "compressé vs pas". Si nous faisons cela, les gens voudront que nous ajoutions un contrÎle sur la compression, le niveau de compression, etc. Tout ce que nous devrions ajouter, c'est la possibilité d'intégrer un fichier d'octets bruts. Si les utilisateurs souhaitent stocker des données compressées dans ce fichier, tant mieux, les détails leur appartiennent et aucune API n'est nécessaire du cÎté de Go.

Quelques réflexions :

  • Il ne devrait pas ĂȘtre possible d'intĂ©grer un fichier en dehors du module effectuant l'intĂ©gration. Nous devons nous assurer que les fichiers font partie des fichiers zip du module lorsque nous les crĂ©ons, ce qui signifie Ă©galement qu'il n'y a pas de liens symboliques, de conflits de casse, etc. Nous ne pouvons pas modifier l'algorithme qui produit les fichiers zip sans casser les sommes.
  • Je pense qu'il est plus simple de restreindre l'intĂ©gration dans le mĂȘme rĂ©pertoire (si des commentaires //go:embed sont utilisĂ©s) ou dans un sous-rĂ©pertoire spĂ©cifique (si static est utilisĂ©). Cela facilite grandement la comprĂ©hension de la relation entre les packages et les fichiers intĂ©grĂ©s.

Dans tous les cas, cela bloque l'intĂ©gration de /etc/shadow ou .git . Ni l'un ni l'autre ne peut ĂȘtre inclus dans un module zip.

En général, je crains d'étendre trop la portée de la commande go. Cependant, le fait qu'il existe tant de solutions à ce problÚme signifie qu'il devrait probablement y avoir une solution officielle.

Je connais go_embed_data et go-bindata (dont il existe plusieurs forks), et cela semble couvrir ces cas d'utilisation. Y a-t-il des problÚmes importants que les autres résolvent que cela ne couvre pas ?

Le blocage de certains fichiers ne devrait pas ĂȘtre trop difficile, surtout si vous utilisez un rĂ©pertoire static ou embed . Les liens symboliques peuvent compliquer un peu cela, mais vous pouvez simplement l'empĂȘcher d'intĂ©grer quoi que ce soit en dehors du module actuel ou, si vous ĂȘtes sur GOPATH, en dehors du package contenant le rĂ©pertoire.

Je ne suis pas particuliĂšrement fan d'un commentaire qui compile en code, mais je trouve Ă©galement un peu Ă©trange le pseudo-paquet qui affecte la compilation. Si l'approche par rĂ©pertoire n'est pas utilisĂ©e, il serait peut-ĂȘtre plus logique d'avoir une sorte embed dĂ©claration de niveau supĂ©rieur import , mais ne prendrait en charge que les chemins locaux et nĂ©cessiterait un nom pour lui ĂȘtre attribuĂ©. Par exemple,

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 : Tu m'as devancé, @jayconrod.

Pour dĂ©velopper sur https://github.com/golang/go/issues/35950#issuecomment -561703346, il y a un casse-tĂȘte sur l'API exposĂ©e. Les moyens Ă©vidents d'exposer les donnĂ©es sont les interfaces []byte , string et Read -ish.

Le cas typique est que vous voulez que les donnĂ©es intĂ©grĂ©es soient immuables. Cependant, toutes les interfaces exposant []byte (ce qui inclut io.Reader , io.SectionReader , etc.) doivent soit (1) faire une copie, (2) autoriser la mutabilitĂ©, ou (3) ĂȘtre immuable en dĂ©pit d'ĂȘtre un []byte . Exposer les donnĂ©es en tant que string s rĂ©sout ce problĂšme, mais au prix d'une API qui finira souvent par nĂ©cessiter une copie de toute façon, car beaucoup de code qui consomme des fichiers intĂ©grĂ©s nĂ©cessite finalement des tranches d'octets d'une maniĂšre ou d'une autre.

Je suggĂ©rerais l'itinĂ©raire (3) : ĂȘtre immuable malgrĂ© le fait qu'il s'agisse d'un []byte . Vous pouvez appliquer cela Ă  moindre coĂ»t en utilisant un symbole en lecture seule pour le tableau de sauvegarde. Cela vous permet Ă©galement d'exposer en toute sĂ©curitĂ© les mĂȘmes donnĂ©es qu'un []byte et un string ; les tentatives de mutation des donnĂ©es Ă©choueront. Le compilateur ne peut pas profiter de l'immuabilitĂ©, mais ce n'est pas une trop grande perte. C'est quelque chose que la prise en charge de la chaĂźne d'outils peut apporter Ă  la table que (pour autant que je sache) aucun des packages codegen existants ne le fait.

(Un package de codegen tiers pourrait le faire en générant un fichier d'assemblage générique contenant des symboles DATA qui sont marqués comme en lecture seule, puis de courts fichiers d'assemblage spécifiques à l'arch exposant ces symboles sous la forme de string s et []byte s. J'ai écrit CL 163747 spécifiquement avec ce cas d'utilisation à l'esprit, mais je n'ai jamais réussi à l'intégrer dans des packages codegen.)

Je ne sais pas de quoi vous parlez en termes d'immuabilitĂ©. io.Reader applique dĂ©jĂ  l'immuabilitĂ©. C'est tout l'intĂ©rĂȘt. Lorsque vous appelez Read(buf) , il copie les donnĂ©es dans le tampon que _vous_ avez fourni. Changer buf aprĂšs cela n'a aucun effet sur les composants internes du io.Reader .

Je suis d'accord avec @DeedleFake. Je ne veux pas jouer Ă  des jeux avec des supports de tableau magiques []byte . Vous pouvez copier du binaire dans les tampons fournis par l'utilisateur.

Juste une autre ride ici - j'ai un projet différent qui utilise le code source DTrace (intégré). Ceci est sensible aux différences entre \n et \r\n. (Nous pouvons discuter si c'est une chose stupide dans DTrace ou non - c'est hors de propos et c'est la situation aujourd'hui.)

Il est trÚs utile que les chaßnes backticked traitent les deux comme \n quelle que soit la façon dont elles apparaissent dans la source, et je m'appuie sur cela avec un go-generate pour intégrer le DTrace.

Donc, s'il y a un fichier d'intĂ©gration ajoutĂ© Ă  la commande go, je suggĂ©rerais doucement que les options pour modifier la gestion de CR/CRLF pourraient ĂȘtre trĂšs utiles, en particulier pour les personnes qui pourraient dĂ©velopper sur diffĂ©rents systĂšmes oĂč les fins de ligne par dĂ©faut peuvent ĂȘtre un piĂšge.

Comme avec la compression, j'aimerais vraiment m'arrĂȘter Ă  "copier les octets du fichier dans le binaire". Normalisation CR/CRLF, normalisation Unicode, gofmt'ing, tout ce qui appartient Ă  l'autre. Archivez les fichiers contenant les octets exacts que vous souhaitez. (Si votre contrĂŽle de version ne peut pas les laisser seuls, vĂ©rifiez peut-ĂȘtre le contenu gzippĂ© et compressez-le au moment de l'exĂ©cution.) Il existe _beaucoup_ de boutons de gestion de fichiers que nous pourrions imaginer ajouter. ArrĂȘtons-nous Ă  0.

Il est peut-ĂȘtre trop tard pour introduire un nouveau nom de rĂ©pertoire rĂ©servĂ©, autant que je le souhaite.
(Il n'Ă©tait pas trop tard en 2014, mais il est probablement trop tard maintenant.)
Ainsi, une sorte de commentaire d'opt-in peut ĂȘtre nĂ©cessaire.

Supposons que nous définissions un type runtime.Files. Alors vous pourriez imaginer écrire :

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

Et puis, au moment de l'exécution, vous appelez simplement files.Open pour récupérer un interface { io.ReadSeeker; io.ReaderAt } avec les données. Notez que la var n'est pas exportée, de sorte qu'un package ne peut pas parcourir les fichiers intégrés d'un autre package.

Noms à déterminer mais en ce qui concerne le mécanisme, il semble que cela devrait suffire, et je ne vois pas comment faire plus simple. (Simplifications bienvenues bien sûr !)

Quoi que nous fassions, il doit ĂȘtre possible de soutenir avec Bazel et Gazelle aussi. Cela signifierait que Gazelle reconnaisse le commentaire et Ă©crive une rĂšgle Bazel indiquant les globs, puis nous aurions besoin d'exposer un outil (go tool embedgen ou autre) pour gĂ©nĂ©rer le fichier supplĂ©mentaire Ă  inclure dans la construction (la commande go le faire automatiquement et ne jamais afficher le fichier supplĂ©mentaire). Cela semble assez simple.

Si divers munging ne font pas l'affaire, alors c'est un argument contre l'utilisation de cette nouvelle fonctionnalité. Ce n'est pas un frein pour moi - je peux utiliser go generate comme je l'ai fait, mais cela signifie que je ne peux pas bénéficier de la nouvelle fonctionnalité.

En ce qui concerne le munging en gĂ©nĂ©ral - je peux imaginer une solution oĂč quelqu'un fournit une implĂ©mentation d'une interface (quelque chose comme un Reader() d'un cĂŽtĂ©, et quelque chose pour recevoir le fichier de l'autre - peut-ĂȘtre instanciĂ© avec un io.Reader Ă  partir du fichier lui-mĂȘme) -- que le cmd go crĂ©erait et exĂ©cuterait pour prĂ©filtrer le fichier avant de l'intĂ©grer. Ensuite, les gens peuvent fournir le filtre qu'ils veulent. J'imagine que certaines personnes fourniraient des filtres quasi standard comme une implĂ©mentation dos2unix, une compression, etc. (Peut-ĂȘtre qu'ils devraient mĂȘme ĂȘtre chaĂźnables.)

Je suppose qu'il faudrait supposer que quel que soit le processeur intĂ©grĂ©, il doit ĂȘtre compilable sur ~ chaque systĂšme de construction, car cela crĂ©erait un outil natif temporaire Ă  cette fin.

Il est peut-ĂȘtre trop tard pour introduire un nouveau nom de rĂ©pertoire rĂ©servĂ©, autant que je le souhaite. [...] sorte de commentaire d'opt-in peut ĂȘtre nĂ©cessaire.

Si les fichiers ne sont accessibles que via un package spĂ©cial, disons runtime/embed , alors l'importation de ce package pourrait ĂȘtre le signal d'activation.

L'approche io.Read semble pouvoir ajouter une surcharge importante (en termes de copie et d'empreinte mémoire) pour des opérations linéaires conceptuellement simples comme strings.Contains (comme dans cmd/go/internal/cfg ) ou , critique, template.Parse .

Pour ces cas d'utilisation, il semble idéal de permettre à l'appelant de choisir de traiter le blob entier comme un string (probablement mappé en mémoire) ou un io.ReaderAt .

Cela semble cependant compatible avec l'approche générale de runtime.Files : la chose renvoyée par runtime.Files.Open pourrait avoir une méthode ReadString() string qui renvoie la représentation mappée en mémoire.

une sorte de commentaire d'opt-in peut ĂȘtre nĂ©cessaire.

Nous pourrions le faire avec la version go dans le fichier go.mod . Avant 1.15 (ou autre) le sous-répertoire static contiendrait un package, et à 1.15 ou plus, il contiendrait des actifs intégrés.

(Cependant, cela n'aide pas vraiment en mode GOPATH .)

Je ne suis pas convaincu de la complexité d'un bouton "compressé vs pas". Si nous faisons cela, les gens voudront que nous ajoutions un contrÎle sur la compression, le niveau de compression, etc. Tout ce que nous devrions ajouter, c'est la possibilité d'intégrer un fichier d'octets bruts.

Bien que j'apprécie la recherche de simplicité, nous devons également nous assurer que nous répondons aux besoins des utilisateurs.

12 des 14 outils répertoriés sur https://tech.townsourced.com/post/embedding-static-files-in-go/#comparison prennent en charge la compression, ce qui suggÚre qu'il s'agit d'une exigence assez courante.

Il est vrai que l'on pourrait faire la compression en tant qu'Ă©tape de prĂ©-construction Ă  l'extĂ©rieur, mais cela nĂ©cessiterait toujours 1) un outil pour faire la compression 2) vĂ©rifier une sorte de blob assets.zip dans vcs 3) probablement un utilitaire bibliothĂšque autour de l'API intĂ©grĂ©e pour annuler la compression. À ce stade, on ne sait pas du tout quel est l'avantage.

Trois des objectifs énumérés dans la proposition initiale étaient les suivants :

  • ne pas archiver les fichiers gĂ©nĂ©rĂ©s
  • make go install / go build fait l'intĂ©gration automatiquement
  • stocker les actifs compressĂ©s dans le binaire, le cas Ă©chĂ©ant

Si nous lisons le second comme « ne nécessite pas d'outil distinct pour l'intégration », alors la non prise en charge directe ou indirecte des fichiers compressés ne permet pas d'atteindre ces trois objectifs.

Cela doit-il ĂȘtre au niveau du package ? Le niveau du module semble une meilleure granularitĂ© puisque trĂšs probablement un module = un projet.

Étant donnĂ© que ce rĂ©pertoire ne contiendrait pas de code Go, cela pourrait-il ĂȘtre quelque chose comme _static ?

† ou, si c'est le cas, il serait traitĂ© comme des octets arbitraires dont le nom se termine par ".go" au lieu de code Go Ă  compiler

S'il s'agit d'un répertoire spécial, la logique pourrait simplement consister à aspirer tout et n'importe quoi dans cette arborescence de répertoires. Le package d'intégration magique pourrait vous permettre de faire quelque chose comme embed.Open("img/logo.svg") pour ouvrir un fichier dans un sous-répertoire de l'arborescence des ressources.

Les cordes semblent assez bonnes. Ils peuvent facilement ĂȘtre copiĂ©s dans []byte ou convertis en Reader . La gĂ©nĂ©ration de code ou les bibliothĂšques pourraient ĂȘtre utilisĂ©es pour fournir des API plus sophistiquĂ©es et gĂ©rer les choses pendant init . Cela pourrait inclure une dĂ©compression ou la crĂ©ation d'un http.FileSystem .

Windows n'a-t-il pas un format spĂ©cial pour l'intĂ©gration des actifs. Cela devrait-il ĂȘtre utilisĂ© lors de la crĂ©ation d'un exĂ©cutable Windows ? Si oui, cela a-t-il des implications sur les types d'opĂ©rations qui peuvent ĂȘtre fournis?

N'oubliez pas les gitfs

Y a-t-il une raison pour laquelle cela ne pourrait pas faire partie de go build / link ... par exemple go build -embed example=./path/example.txt et un package qui expose l'accĂšs (par exemple embed.File("example") , au lieu d'utiliser go:embed ?

vous avez besoin d'un talon pour cela dans votre code cependant

@egonelbre le problĂšme avec go build -embed est que tous les utilisateurs auraient besoin de l'utiliser correctement. Cela doit ĂȘtre entiĂšrement transparent et automatique ; Les commandes go install ou go get existantes ne peuvent pas arrĂȘter de faire la bonne chose.

@bradfitz Je recommanderais https://github.com/markbates/pkger sur Packr. Il utilise l'API de bibliothĂšque standard pour travailler avec des fichiers.

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
}

Ou peut-ĂȘtre que certaines balises ou drapeaux de construction pourraient le faire revenir Ă  faire des choses au moment de l'exĂ©cution Ă  la place. Perkeep (liĂ© ci-dessus) a un tel mode, ce qui est bien pour accĂ©lĂ©rer le dĂ©veloppement incrĂ©mentiel oĂč vous ne vous souciez pas de lier un gros binaire.

mjibson/esc le fait également, et c'est une grande amélioration de la qualité de vie lors du développement d'une application Web ; non seulement vous économisez du temps de liaison mais évitez également d'avoir à redémarrer l'application, ce qui peut prendre un temps considérable et/ou nécessiter la répétition d'étapes supplémentaires pour tester vos modifications, selon l'implémentation de la webapp.

ProblĂšmes avec la situation actuelle :

  • L'utilisation d'une solution basĂ©e sur go:generate gonfle l'historique de git avec une deuxiĂšme copie (et lĂ©gĂšrement plus grande) de chaque fichier.

Buts:

  • ne pas archiver les fichiers gĂ©nĂ©rĂ©s

Eh bien, cette partie est facilement résolvable en ajoutant simplement les fichiers générés au fichier .gitignore ou équivalent. J'ai toujours fait ça...

Ainsi, Go pourrait simplement avoir son propre outil d'intégration "officiel" qui s'exécute par défaut sur go build et demander aux gens d'ignorer ces fichiers par convention. Ce serait la solution la moins magique disponible (et rétrocompatible avec les versions Go existantes).

Je suis juste en train de réfléchir/réfléchir à haute voix ici... mais j'aime en fait l'idée proposée dans l'ensemble. ??

De plus, étant donné que les directives //go:generate ne s'exécutent pas automatiquement sur go build le comportement de go build peut sembler un peu incohérent : //go:embed fonctionnera automatiquement mais pour //go:generate vous devez exécuter go generate manuellement. ( //go:generate peut déjà casser le flux go get s'il génÚre les fichiers .go nécessaires à la construction).

//go:generate peut déjà casser le flux de go get s'il génÚre les fichiers .go nécessaires à la construction

Je pense que le flux habituel pour cela, et celui que j'ai généralement utilisé, bien qu'il ait fallu un peu de temps pour s'y habituer, consiste à utiliser go generate entiÚrement comme un outil de développement et à simplement valider les fichiers qui il génÚre.

@bradfitz, il n'a pas besoin d'implĂ©menter http.FileSystem lui-mĂȘme. Si l'implĂ©mentation fournit un type qui implĂ©mente http.File , alors il serait trivial pour quiconque, y compris le package http stdlib, de fournir un wrapper autour de la fonction Open , convertissant le type en http.File afin de se conformer Ă  http.FileSystem

@andreynering //go:generate et //go:embed sont cependant trÚs différents. Ce mécanisme peut se produire de maniÚre transparente au moment de la génération car il n'exécutera pas de code arbitraire. Je pense que cela ressemble à la façon dont cgo peut générer du code dans le cadre de go build .

Je ne suis pas convaincu de la complexité d'un bouton "compressé vs pas". Si nous faisons cela, les gens voudront que nous ajoutions un contrÎle sur la compression, le niveau de compression, etc. Tout ce que nous devrions ajouter, c'est la possibilité d'intégrer un fichier d'octets bruts.

Bien que j'apprécie la recherche de simplicité, nous devons également nous assurer que nous répondons aux besoins des utilisateurs.

12 des 14 outils répertoriés sur https://tech.townsourced.com/post/embedding-static-files-in-go/#comparison prennent en charge la compression, ce qui suggÚre qu'il s'agit d'une exigence assez courante.

Je ne suis pas sĂ»r d'ĂȘtre d'accord avec ce raisonnement.

La compression effectuée par les autres bibliothÚques est différente de son ajout à cette proposition en ce sens qu'elles ne réduiront pas les performances sur les versions ultérieures, car les alternatives sont généralement générées avant la construction plutÎt que pendant la construction.

Les temps de construction faibles sont une valeur ajoutĂ©e Ă©vidente avec Go sur d'autres langues et la compression Ă©change le temps CPU pour une empreinte de stockage/transfert rĂ©duite. Si de nombreux packages Go commencent Ă  exĂ©cuter des compressions sur go build nous allons ajouter encore plus de temps de construction que le temps ajoutĂ© en copiant simplement les actifs pendant les constructions. Je suis sceptique quant Ă  l'ajout de la compression Ă  cause d'autres qui le font. Tant que la conception initiale n'empĂȘche pas par conception une extension future qui ajoute un support pour, par exemple, la compression, l'insĂ©rer lĂ -dedans parce que cela pourrait ĂȘtre quelque chose qui pourrait profiter Ă  certains semble ĂȘtre une couverture inutile.

Ce n'est pas comme si l'intĂ©gration de fichiers serait inutile sans compression, la compression est une bonne chose Ă  avoir pour rĂ©duire la taille binaire de peut-ĂȘtre 100 Mo Ă  50 Mo - ce qui est gĂ©nial, mais pas non plus une rupture claire pour la fonctionnalitĂ© de la plupart des applications auxquelles je peux penser . Surtout pas si la plupart des actifs "plus lourds" sont des fichiers tels que des JPEG ou des PNG qui sont dĂ©jĂ  assez bien compressĂ©s.

Qu'en est-il de garder la compression pour le moment et de l'ajouter si elle est rĂ©ellement manquĂ©e par beaucoup de gens ? (et peut ĂȘtre fait sans frais excessifs)

Pour compléter le commentaire de @sakjur ci-dessus : la compression me semble orthogonale. Je souhaite généralement compresser une archive binaire ou de version complÚte, et pas seulement les actifs. En particulier lorsque les binaires Go dans Go peuvent facilement atteindre des dizaines de mégaoctets sans aucun actif.

@mvdan Je suppose que l'une de mes préoccupations est que trÚs souvent, lorsque j'ai vu l'intégration se faire avec d'autres prétraitements : minification, compilation dactylographiée, compression de données, traitement d'images, redimensionnement d'images, feuilles de sprite. La seule exception étant les sites Web qui n'utilisent que html/template . Donc, à la fin, vous pourriez finir par utiliser une sorte de "Makefile" de toute façon ou télécharger le contenu pré-traité. En ce sens, je pense qu'un indicateur de ligne de commande fonctionnerait mieux avec d'autres outils que les commentaires.

Je suppose que l'une de mes préoccupations est que trÚs souvent, lorsque j'ai vu l'intégration se faire avec d'autres pré-traitements : minification, compilation dactylographiée, compression de données, traitement d'image, redimensionnement d'image, feuilles de sprite. La seule exception étant les sites Web qui n'utilisent que html/template.

Merci, c'est un point de donnĂ©es utile. Peut-ĂȘtre que le besoin de compression n'est pas aussi courant qu'il y paraĂźt. Si tel est le cas, je suis d'accord qu'il est logique de le laisser de cĂŽtĂ©.

Ce n'est pas comme si l'intĂ©gration de fichiers serait inutile sans compression, la compression est une bonne chose Ă  avoir pour rĂ©duire la taille binaire de peut-ĂȘtre 100 Mo Ă  50 Mo - ce qui est gĂ©nial, mais pas non plus une rupture claire pour la fonctionnalitĂ© de la plupart des applications auxquelles je peux penser .

La taille binaire est un gros problĂšme pour de nombreux dĂ©veloppeurs de go (https://github.com/golang/go/issues/6853). Go compresse les informations de dĂ©bogage DWARF spĂ©cifiquement pour rĂ©duire la taille binaire, mĂȘme si cela a un coĂ»t en temps de liaison (https://github.com/golang/go/issues/11799, https://github.com/golang/go/ numĂ©ros/26074). S'il existait un moyen simple de rĂ©duire de moitiĂ© la taille binaire, je pense que les dĂ©veloppeurs saisiraient cette opportunitĂ© (bien que je doute que les gains ici soient presque aussi importants).

Cela n'aide pas vraiment en mode GOPATH, cependant

Peut-ĂȘtre que si vous ĂȘtes en mode GOPATH, cette fonctionnalitĂ© ne s'applique tout simplement pas car j'imagine que l'Ă©quipe Go ne prĂ©voit pas de faire la paritĂ© des fonctionnalitĂ©s pour GOPATH pour toujours ? Il existe dĂ©jĂ  des fonctionnalitĂ©s qui ne sont pas prises en charge dans GOPATH (telles que la sĂ©curitĂ© avec la base de donnĂ©es de somme de contrĂŽle, le tĂ©lĂ©chargement de dĂ©pendances via un serveur proxy et la gestion des versions d'importation sĂ©mantique)

Comme @bcmills l'a mentionnĂ©, avoir le nom de rĂ©pertoire statique dans un fichier go.mod est un excellent moyen d'introduire cette fonctionnalitĂ© dans Go 1.15 car la fonctionnalitĂ© peut ĂȘtre automatiquement dĂ©sactivĂ©e dans les fichiers go.mod qui ont une clause <=go1.14.

Cela dit, cela signifie également que les utilisateurs doivent écrire manuellement le chemin du répertoire statique.

Je pense que le répertoire des fournisseurs et les conventions _test.go sont d'excellents exemples de la façon dont ils ont rendu le travail avec Go et ces deux fonctionnalités beaucoup plus faciles.

Je ne me souviens pas de nombreuses personnes ayant demandé l'option de personnaliser le nom du répertoire du fournisseur ou d'avoir la possibilité de changer la convention _test.go en autre chose. Mais si Go n'introduisait jamais la fonctionnalité _test.go, alors les tests dans Go seraient trÚs différents aujourd'hui.

Par consĂ©quent, peut-ĂȘtre qu'un nom moins gĂ©nĂ©rique que static donne de meilleures chances de non-collision et donc avoir un rĂ©pertoire conventionnel (similaire Ă  vendor et _test.go) pourrait ĂȘtre une meilleure expĂ©rience utilisateur par rapport aux commentaires magiques.

Exemples de noms potentiellement à faible collision :

  • _embed - suit la convention _test.go
  • go_binary_assets
  • .gobin suit la convention .git
  • runtime_files - pour qu'il corresponde Ă  la structure runtime.Files

Enfin, le rĂ©pertoire vendor Ă©tĂ© ajoutĂ© dans Go 1.5 . Alors, peut-ĂȘtre que ce n'est pas si mal d'ajouter une nouvelle convention maintenant ? ??

Je pense que cela devrait exposer un []byte lecture seule mmap . Juste un accĂšs brut aux pages de l'exĂ©cutable, paginĂ© par le systĂšme d'exploitation selon les besoins. Tout le reste peut ĂȘtre fourni en plus, avec seulement bytes.NewReader .

Si cela est inacceptable pour une raison quelconque, veuillez fournir ReaderAt pas seulement ReadSeeker ; ce dernier est trivial à construire à partir du premier, mais l'inverse n'est pas aussi bon : il faudrait un mutex pour garder le décalage unique et ruiner les performances.

Ce n'est pas comme si l'intĂ©gration de fichiers serait inutile sans compression, la compression est une bonne chose Ă  avoir pour rĂ©duire la taille binaire de peut-ĂȘtre 100 Mo Ă  50 Mo - ce qui est gĂ©nial, mais pas non plus une rupture claire pour la fonctionnalitĂ© de la plupart des applications auxquelles je peux penser .

La taille binaire est un gros problĂšme pour de nombreux dĂ©veloppeurs de go (#6853). Go compresse les informations de dĂ©bogage DWARF spĂ©cifiquement pour rĂ©duire la taille binaire, mĂȘme si cela a un coĂ»t en temps de liaison (#11799, #26074). S'il existait un moyen simple de rĂ©duire de moitiĂ© la taille binaire, je pense que les dĂ©veloppeurs saisiraient cette opportunitĂ© (bien que je doute que les gains ici soient presque aussi importants).

C'est certainement un point juste et je peux voir comment mon argument peut ĂȘtre considĂ©rĂ© comme un argument en faveur de la nĂ©gligence en ce qui concerne la taille des fichiers. Ce n'Ă©tait pas mon intention. Mon point est plus conforme Ă  l'expĂ©dition de cette fonctionnalitĂ© sans compression, ce qui serait toujours utile pour certains, et ils pourraient fournir des commentaires et des informations utiles sur la façon d'ajouter correctement la compression d'une maniĂšre qui semble correcte Ă  long terme. Les ressources peuvent gonfler d'une maniĂšre que les informations de dĂ©bogage sont peu susceptibles de faire et il est plus facile pour les dĂ©veloppeurs de packages installĂ©s/importĂ©s par d'autres de rĂ©duire inutilement les performances de construction si l'implĂ©mentation le rend facile.

Une autre option serait de faire de la compression des actifs un indicateur de construction et de laisser le compromis entre la taille et le temps de construction au constructeur plutÎt qu'au développeur. Cela rapprocherait la décision de l'utilisateur final du binaire qui pourrait décider si la compression en vaut la peine. Otoh, cela risquerait de créer une surface accrue pour les différences entre le développement et la production, donc ce n'est pas une meilleure méthode qu'autre chose et ce n'est pas quelque chose que je voudrais défendre.

Mon outil d'intégration d'actifs actuel charge le contenu des fichiers d'actifs lorsqu'il est construit avec -tags dev . Une convention comme celle-là serait probablement utile ici aussi ; cela raccourcit considérablement le cycle de développement lorsque, par exemple, vous jouez avec du HTML ou un modÚle.

Sinon, l'appelant devra envelopper ce mĂ©canisme de niveau infĂ©rieur avec des enveloppes *_dev.go et *_nodev.go et implĂ©menter un chargement non intĂ©grĂ© pour le scĂ©nario dev . MĂȘme pas difficile, mais cette route ne fera que conduire Ă  une explosion d'outils similaire Ă  celle dĂ©crite dans le premier commentaire sur ce problĂšme. Ces outils devront faire moins qu'aujourd'hui, mais ils continueront Ă  se multiplier.

Je pense que -tags dev ne pas fonctionner lorsqu'il est exĂ©cutĂ© en dehors du module Go serait raisonnable (je ne peux pas savoir d'oĂč charger les actifs).

Qu'en est-il juste d'un go tool embed qui prend des entrĂ©es et produit des fichiers de sortie Go dans un format spĂ©cial reconnu par l'ordinateur en tant que fichiers intĂ©grĂ©s qui pourraient ensuite ĂȘtre accessibles via runtime/emved ou quelque chose du genre. Ensuite, vous pouvez simplement faire un simple //go:generate gzip -o - static.txt | go tool embed -o static.go .

Un gros inconvénient, bien sûr, est que vous devez ensuite valider les fichiers générés.

@DeedleFake ce problÚme a commencé avec

L'utilisation d'une solution basée sur go:generate gonfle l'historique de git avec une deuxiÚme copie (et légÚrement plus grande) de chaque fichier.

Oups. Peu importe. Désolé.

Ce n'est pas comme si l'intĂ©gration de fichiers serait inutile sans compression, la compression est une bonne chose Ă  avoir pour rĂ©duire la taille binaire de peut-ĂȘtre 100 Mo Ă  50 Mo - ce qui est gĂ©nial, mais pas non plus une rupture claire pour la fonctionnalitĂ© de la plupart des applications auxquelles je peux penser .

La taille binaire est un gros problĂšme pour de nombreux dĂ©veloppeurs de go (#6853). Go compresse les informations de dĂ©bogage DWARF spĂ©cifiquement pour rĂ©duire la taille binaire, mĂȘme si cela a un coĂ»t en temps de liaison (#11799, #26074). S'il existait un moyen simple de rĂ©duire de moitiĂ© la taille binaire, je pense que les dĂ©veloppeurs saisiraient cette opportunitĂ© (bien que je doute que les gains ici soient presque aussi importants).

S'il y a un besoin, alors les données compressées seront validées et intégrées, et il y aura des packages pour fournir une couche entre runtime.Embed et le consommateur final qui effectue la décompression en ligne.

Et puis dans un an ou deux, il y aura un nouveau problĂšme concernant l'ajout de la compression et cela pourra ĂȘtre rĂ©glĂ© Ă  ce moment-lĂ .

Je dis cela comme l'un des 15 standards concurrents lorsque j'ai Ă©crit goembed :)

@tv42 a Ă©crit :

Je pense que cela devrait exposer un []byte lecture seule mmap . Juste un accÚs brut aux pages de l'exécutable, paginé par le systÚme d'exploitation selon les besoins.

Ce commentaire est facilement raté et incroyablement précieux.

@tv42 ,

Je pense qu'il devrait exposer un [] octet en lecture seule mmap. Juste un accĂšs brut aux pages de l'exĂ©cutable, paginĂ© par le systĂšme d'exploitation selon les besoins. Tout le reste peut ĂȘtre fourni en plus de cela, avec seulement bytes.NewReader.

Le type qui est déjà en lecture seule est string . Aussi : il fournit une taille, contrairement à io.ReaderAt , et il ne dépend pas de la bibliothÚque standard. C'est probablement ce que nous voulons exposer.

Le type qui est déjà en lecture seule est string .

Mais tout l'Ă©cosystĂšme de Write etc fonctionne sur []byte . C'est du pragmatisme simple. Je ne vois pas cette propriĂ©tĂ© en lecture seule ĂȘtre plus un problĂšme que io.Writer.Write docs disant

Write ne doit pas modifier les donnĂ©es de la tranche, mĂȘme temporairement.

Un autre inconvĂ©nient potentiel est que lors de l'intĂ©gration d'un rĂ©pertoire avec go:generate je peux vĂ©rifier la sortie de git diff et voir si des fichiers s'y trouvent par erreur. Avec cette proposition - ? Peut-ĂȘtre que la commande go imprimerait la liste des fichiers qu'elle intĂšgre ?

@tv42

Mais tout l'Ă©cosystĂšme de Write etc fonctionne sur []byte.

Cependant, html/template fonctionne avec des chaĂźnes.

Go vous permet dĂ©jĂ  d'utiliser -ldflags -X pour dĂ©finir certaines chaĂźnes (utile pour dĂ©finir la version de git, l'heure de compilation, le serveur, l'utilisateur, etc.), ce mĂ©canisme pourrait-il ĂȘtre Ă©tendu pour dĂ©finir io.Readers au lieu de chaĂźnes ?

@bradfitz Proposez -vous d'utiliser des chaĂźnes ici mĂȘme pour des donnĂ©es qui ne sont pas du texte ? Il est assez courant d'intĂ©grer de petits fichiers binaires comme des icĂŽnes et de petites images, etc.

@tv42 Vous avez dit Write mais je suppose que vous vouliez dire Read . Vous pouvez transformer un string en un io.ReaderAt utilisant strings.NewReader , donc l'utilisation d'une chaĂźne ne semble pas ĂȘtre un obstacle.

@andreynering Un string peut contenir n'importe quelle séquence d'octets.

Un string peut contenir n'importe quelle séquence d'octets.

Oui, mais son intention premiÚre est de contenir du texte et non des données arbitraires. Je suppose que cela peut causer un peu de confusion, en particulier pour les développeurs Go inexpérimentés.

J'ai tout à fait compris l'idée, cependant. Merci de clarifier.

@ianlancetaylor

Read est censé muter la tranche transmise. Write ne l'est pas. Par conséquent, la documentation de Write indique que cela n'est pas autorisé. Je ne vois rien de plus nécessaire que de documenter que les utilisateurs ne doivent pas écrire dans le []byte renvoyé.

Ce n'est pas parce que strings.Reader existe que io.WriteString trouvera une implémentation efficace de l'écriture de chaßnes. Par exemple, TCPConn n'a pas WriteString .

Je dĂ©testerais que Go inclue une nouvelle fonctionnalitĂ© comme celle-ci uniquement pour forcer toutes les donnĂ©es Ă  ĂȘtre copiĂ©es juste pour les Ă©crire dans un socket.

De plus, l'hypothÚse générale est que les chaßnes sont imprimables par l'homme et que []byte ne l'est souvent pas. Mettre des JPEG dans des chaßnes causera beaucoup de terminaux en désordre.

@opennota

html/template fonctionne avec des chaĂźnes, cependant.

Ouais, c'est bizarre, il ne prend que les fichiers par chemin d'accÚs pas comme lecteurs. Deux réponses :

  1. Il n'y a aucune raison pour que les données intégrées ne puissent pas avoir les deux méthodes Bytes() []byte et String() string .

  2. J'espĂšre que vous n'analysez pas un modĂšle Ă  chaque requĂȘte ; alors que vous devez vraiment envoyer les donnĂ©es d'un JPEG dans un socket TCP pour chaque requĂȘte qui le demande.

@tv42 Nous pouvons ajouter des méthodes WriteString au besoin.

Je ne pense pas que l'utilisation la plus courante de cette fonctionnalité sera d'écrire les données non modifiées, donc je ne pense pas que nous devrions optimiser pour ce cas.

Je ne pense pas que l'utilisation la plus courante de cette fonctionnalité sera d'écrire les données non modifiées,

Je pense que l'utilisation la plus courante de cette fonctionnalité sera de servir des actifs Web, images/js/css, non modifiés.

Mais ne me croyez pas sur parole, regardons quelques-uns des importateurs du fileembed de Brad :

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



md5-f8b48fccd03599094034bf2b507e9e67



#fileembed pattern .*\.js$

Etc..

Pour les donnĂ©es anecdotiques : je sais que si cela Ă©tait mis en Ɠuvre, je l'utiliserais immĂ©diatement Ă  deux endroits au travail, et les deux fourniraient un accĂšs non modifiĂ© aux fichiers texte statiques. À l'heure actuelle, nous utilisons une Ă©tape //go:generate pour convertir les fichiers en chaĂźnes constantes (format hexadĂ©cimal).

Je voterais pour le nouveau paquet par opposition Ă  la directive. Beaucoup plus facile Ă  saisir, plus facile Ă  manipuler/gĂ©rer et beaucoup plus facile Ă  documenter et Ă  Ă©tendre. Par exemple, pouvez-vous facilement trouver la documentation d'une directive Go telle que « go:generate » ? Qu'en est-il de la documentation du package « fmt » ? Vous voyez oĂč je veux en venir ?

Ainsi, Go pourrait simplement avoir son propre outil d'intégration "officiel" qui s'exécute par défaut sur go build

@andreynering Je sais que d'autres gestionnaires de packages et outils de langage le permettent, mais l'exĂ©cution de code/commandes arbitraires au moment de la construction est une vulnĂ©rabilitĂ© de sĂ©curitĂ© (pour ce que j'espĂšre ĂȘtre des raisons Ă©videntes).

Deux choses supplémentaires me viennent à l'esprit en pensant à cette fonctionnalité :

  • Comment l'intĂ©gration de fichiers fonctionnerait-elle automatiquement avec le cache de construction ?
  • Cela empĂȘche-t-il les builds reproductibles ? Si les donnĂ©es sont modifiĂ©es de quelque maniĂšre que ce soit (par exemple en les compressant), cela doit prendre en compte la reproductibilitĂ©.

stuffbin , liĂ© dans le premier commentaire, a Ă©tĂ© conçu pour permettre principalement aux applications Web auto-hĂ©bergĂ©es d'intĂ©grer des actifs statiques (HTML, JS...). Cela semble ĂȘtre un cas d'utilisation courant.

À l'exception de la discussion sur la compilation / compression, un autre problùme est l'absence d'abstraction du systùme de fichiers dans la stdlib car :

  • Sur la machine d'un dĂ©veloppeur, les nombreux go run s et builds n'ont pas besoin d'ĂȘtre surchargĂ©s par la surcharge d'intĂ©gration (tout en compressant Ă©ventuellement) les actifs. Une abstraction du systĂšme de fichiers permettrait de basculer facilement vers le systĂšme de fichiers local pendant le dĂ©veloppement.

  • Les actifs peuvent changer activement au cours du dĂ©veloppement, par exemple, une interface Javascript complĂšte dans une application Web. La possibilitĂ© de basculer de maniĂšre transparente entre l'intĂ©gration et le systĂšme de fichiers local au lieu des actifs intĂ©grĂ©s permettrait d'Ă©viter la compilation et la rĂ©exĂ©cution du binaire Go simplement parce que les actifs ont changĂ©.

Edit : Pour conclure, si le package d'intégration pouvait exposer une interface de type systÚme de fichiers, quelque chose de mieux que http.FileSystem, cela résoudrait ces problÚmes.

La possibilité de basculer de maniÚre transparente entre l'intégration et le systÚme de fichiers local

Cela peut certainement ĂȘtre mis en Ɠuvre au niveau de l'application et dĂ©passe le cadre de cette proposition, non ?

Cela peut certainement ĂȘtre mis en Ɠuvre au niveau de l'application et dĂ©passe le cadre de cette proposition, non ?

Désolé, je viens de me rendre compte, la façon dont je l'ai formulé est ambiguë. Je ne proposais pas une implémentation de systÚme de fichiers dans le package d'intégration, mais juste une interface, quelque chose de mieux que http.FileSystem . Cela permettrait aux applications d'implémenter n'importe quelle sorte d'abstraction.

Edit : faute de frappe.

@knadh Tout Ă  fait d'accord pour dire que cela devrait fonctionner lorsque vous utilisez Ă©galement go run , la façon dont Packr gĂšre cela est vraiment sympa. Il sait oĂč se trouvent vos fichiers, s'ils ne sont pas intĂ©grĂ©s dans l'application, il les charge alors Ă  partir du disque car il s'attend Ă  ce qu'il s'agisse essentiellement d'un "mode de dĂ©veloppement".

L'auteur de Packr a également publié un nouvel outil Pkger qui est plus axé sur les modules Go. Les fichiers qui s'y trouvent sont tous relatifs à la racine du module Go. J'aime beaucoup cette idée, mais Pkger ne semble pas avoir implémenté le chargement de développement local à partir du disque. Une combinaison des deux serait incroyable OMI.

Je ne sais pas s'il est dĂ©jĂ  hors d'usage, mais bien que "l'approche du package d'intĂ©gration" soit assez magique, elle offre Ă©galement une certaine efficacitĂ© car l'outil peut dĂ©duire ce qu'il faut faire du fichier en fonction de l'appel. par exemple, l'API pourrait ĂȘtre quelque chose comme

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

Si l'outil go trouve un appel Ă  FileReaderAt , il sait que les donnĂ©es doivent ĂȘtre dĂ©compressĂ©es. S'il ne trouve que des appels FileReader , il sait qu'il peut stocker des donnĂ©es compressĂ©es. S'il trouve un appel FileBytes , il sait qu'il doit faire une copie, s'il ne trouve que FileString , il sait qu'il peut servir Ă  partir de la mĂ©moire en lecture seule. Etc.

Je ne suis pas convaincu que ce soit un moyen raisonnable de mettre en Ɠuvre cela pour l'outil de go proprement dit. Mais je voulais mentionner ceci, car cela permet d'obtenir les avantages de la compression et de l'intĂ©gration sans copie sans avoir de boutons rĂ©els.

[edit] aussi, bien sûr, nous ajoutons ces choses supplémentaires aprÚs coup, en nous concentrant d'abord sur un ensemble de fonctionnalités plus minimal [/edit]

S'il ne trouve que les appels FileReader...

Cela exclurait l'utilisation des autres méthodes par la réflexion.

[Modifier] En fait, je pense que les implications sont plus larges que cela. Si l'utilisation de FileReaderAt indique que les donnĂ©es doivent ĂȘtre dĂ©compressĂ©es, alors l'utilisation de FileReaderAt() avec toute entrĂ©e non const implique que tous les fichiers doivent ĂȘtre stockĂ©s non compressĂ©s.

Je ne sais pas si c'est bon ou mauvais. Je pense juste que l'heuristique magique ne sera pas aussi utile que cela puisse paraĂźtre Ă  premiĂšre vue.

Un argument en faveur d'un pragma de commentaire ( //go:embed ) au lieu d'un nom de répertoire spécial ( static/ ) : un commentaire permet d'intégrer un fichier dans l'archive de test d'un package (ou l'archive xtest ) mais pas la bibliothÚque testée. Le commentaire doit juste apparaßtre dans un fichier _test.go .

Je pense que cela rĂ©soudrait un problĂšme courant avec les modules : il est difficile d'accĂ©der aux donnĂ©es de test pour un autre package si ce package se trouve dans un autre module. Un package pourrait fournir des donnĂ©es pour d'autres tests avec un commentaire comme //go:embedglob testdata/* dans un fichier _test.go . Le package peut ĂȘtre importĂ© dans un binaire non test standard sans extraire ces fichiers.

@fd0 ,

Comment l'intégration de fichiers fonctionnerait-elle automatiquement avec le cache de construction ?

Cela fonctionnerait encore. Les hachages du contenu du fichier intégré seraient mélangés à la clé de cache.

Serait-il possible (ou mĂȘme une bonne idĂ©e) d'avoir un module/package/mĂ©canisme qui serait pratiquement transparent, car depuis l'intĂ©rieur de votre application, vous essayez simplement d'ouvrir un chemin comme

internal://static/default.css

et les fonctions de fichier liront les données à l'intérieur du binaire ou à partir d'un autre emplacement
ex Package.Mount("internal[/<folder>.]", binary_path + "/resources/")

pour crĂ©er "internal://" avec tous les fichiers dans le binaire, revenir au chemin exĂ©cutable / ressources / si en mode dev ou si le fichier n'est pas trouvĂ© dans le binaire (et peut-ĂȘtre lancer un avertissement ou quelque chose Ă  des fins de journalisation)

Cela permettrait par exemple d'avoir

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

Il est probablement préférable de verrouiller l'emplacement alternatif sur le chemin de l'exécutable en mode "release", mais détendez-le en mode dev (autorisez uniquement les dossiers dans go_path ou quelque chose comme ça)

Par défaut, le package "monte" internal:// ou un autre mot-clé mais permet à l'utilisateur de le renommer s'il le souhaite... ex .ReMount("internal","myCustomName") ou quelque chose comme ça.

Une autre chose... serait-il judicieux de vĂ©rifier l'heure de la derniĂšre modification/modification sur un autre emplacement et de remplacer automatiquement le fichier interne s'il existe un tel fichier en dehors de l'application (peut-ĂȘtre avoir un indicateur permettant cela, configurable par le programmeur avant la construction)
Cela peut ĂȘtre souhaitĂ© pour les correctifs d'applications super rapides, oĂč vous ne voulez pas attendre qu'une nouvelle version soit crĂ©Ă©e et distribuĂ©e. dĂ©poser.

Sous Windows, serait-il possible ou logique d'utiliser des ressources (comme dans le blob de données binaires dans une ressource)
Et un peu sans rapport, mais peut-ĂȘtre que ce package pourrait Ă©galement traiter des icĂŽnes de regroupement dans l'exĂ©cutable, ou des donnĂ©es manifestes, ou peut-ĂȘtre mĂȘme d'autres ressources ? Je me rends compte que c'est uniquement Windows...
J'imagine que le constructeur pourrait enregistrer les derniÚres dates de modification/modification des fichiers dans les dossiers alternatifs et ne déclencher un "créer un blob de données" que si un fichier change et mettre le blob en cache quelque part.
Peut-ĂȘtre ne crĂ©er qu'un fichier "cache" si l'utilisateur choisit d'activer la compression sur ces fichiers groupĂ©s (s'il est finalement dĂ©cidĂ© de les compresser) ... si la compression est choisie, seul le fichier particulier qui a Ă©tĂ© modifiĂ© devra ĂȘtre recompressĂ© au moment de la construction , les autres fichiers seraient simplement copiĂ©s dans le binaire Ă  partir du cache.

Un problÚme que je vois est que si le package autorise les noms personnalisés, il devrait avoir une sorte de liste noire, car il n'autorise pas "udp, file, ftp, http, https et divers autres mots clés populaires".

En ce qui concerne le stockage sous forme de tableau / chaßne d'octets ou de compression ... à mon humble avis, quelle que soit la décision prise, cela devrait laisser de la place pour une mise à jour facile à l'avenir ... ex. les noms de fichiers mais permettent d'ajouter facilement de la compression à l'avenir (ex méthode zlib, lzma, taille compressée, taille non compressée s'il est nécessaire d'allouer suffisamment de mémoire pour décompresser des morceaux, etc.

Personnellement, je serais heureux si l'exĂ©cutable pouvait ĂȘtre compressĂ© avec UPX ou Ă©quivalent, je suppose que le binaire serait dĂ©compressĂ© en mĂ©moire et que tout fonctionnerait.

Quelques réflexions qui se rapportent indirectement :

  • J'aime l'approche package embed pour son utilisation de la syntaxe Go
  • Je pense que le besoin de compression et d'autres manipulations ne concerne pas la taille binaire, il s'agit de vouloir stocker uniquement la forme de contenu la plus conviviale pour les diff dans un rĂ©fĂ©rentiel, pour qu'il n'y ait pas d'Ă©tat "dĂ©synchronisĂ©" oĂč quelqu'un oublie de se rĂ©gĂ©nĂ©rer et validez une forme compressĂ©e lors de la modification de la "source" et pour que le package reste "go gettable". Sans aborder ces points, nous ne rĂ©solvons que le problĂšme de la standardisation, ce qui peut ĂȘtre acceptable, mais ne semble pas idĂ©al.
  • Je pense que nous pourrions contourner la nĂ©cessitĂ© pour la chaĂźne d'outils de prendre en charge activement des compressions/transformations spĂ©cifiques si l'interaction embed peut Ă©ventuellement fournir un "codec". Comment dĂ©finir exactement le codec dĂ©pend de la syntaxe d'intĂ©gration, mais j'imagine quelque chose comme
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
}

Cela peut couvrir des cas d'utilisation trÚs spécifiques, comme celui-ci :

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
}

L'utiliser pourrait ressembler Ă 

// go:embed file.name NewJSONShrinker

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

ou Ă©ventuellement

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

Dans la seconde forme, il y a la complication dont la chaĂźne d'outils a besoin pour comprendre statiquement quel codec utiliser, car elle doit effectuer l'Ă©tape Encode au moment de la compilation. Nous devrions donc interdire toute valeur de codec qui ne peut pas ĂȘtre facilement dĂ©terminĂ©e au moment de la compilation.

Compte tenu de ces deux options, je pense que je choisirais le commentaire magique plus les codecs. Il en résulte une fonctionnalité plus puissante qui répond à tous les objectifs énoncés ici. De plus, je ne pense pas que les commentaires magiques soient inacceptables ici. Nous les tolérons déjà via go:generate à cette fin en ce moment. Si quoi que ce soit, on pourrait considérer le paquet magique à lui seul comme un écart par rapport aux idiomes actuels. L'écosystÚme Go n'a actuellement pas beaucoup de fonctionnalités qui permettent à un fichier source d'indiquer à la chaßne d'outils d'utiliser des fichiers source supplémentaires, et je pense que le seul qui n'est pas un commentaire magique en ce moment est le mot-clé import .

Si nous faisons de la compression, il n'y aura pas du tout de boutons de type de codec ou de niveau de compression. C'est-Ă -dire que le fait d'avoir n'importe quel bouton est le plus grand argument pour ne pas prendre en charge la compression du tout.

Le seul choix que je voudrais exposer, le cas échéant, est : accÚs aléatoire ou non. Si vous n'avez pas besoin d'un accÚs aléatoire, l'outillage et l'environnement d'exécution peuvent choisir la compression appropriée et ne pas l'exposer aux utilisateurs. Et cela changera probablement / s'améliorera avec le temps.

Mais je suis venu du cĂŽtĂ© de @rsc pour ne pas avoir de compression Ă  cause d'une rĂ©alisation que j'avais : le contenu mĂȘme qui est le plus compressible (HTML, JS, CSS, etc.) est le contenu sur lequel vous voudriez toujours un accĂšs alĂ©atoire (Ă  ĂȘtre servi via, disons, http.FileServer , qui prend en charge les demandes de plage)

Et en regardant la taille combinĂ©e du HTML/CSS/JS de Perkeep que nous intĂ©grons, c'est 48 Ko non compressĂ©. Le binaire du serveur Perkeep fait 49 Mo. (J'ignore la taille des images intĂ©grĂ©es car elles sont dĂ©jĂ  compressĂ©es.) Il semble donc que cela n'en vaut pas la peine, mais cela pourrait ĂȘtre ajoutĂ© plus tard.

D'aprÚs une discussion avec @rsc , il semble que nous pourrions faire un mélange des approches ci-dessus :

Dans l'exécution du package,

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(...) { ...}

Ensuite dans ton code :

package yourcode

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

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

Ensuite, l'outil cmd/go analyserait les commentaires go:embed et globait ces modĂšles + hacherait ces fichiers et les enregistrerait avec le runtime, en utilisant &website .

L'environnement d'exĂ©cution aurait effectivement une carte de chaque adresse de fichier avec son contenu et son emplacement dans l'exĂ©cutable du fichier (ou quels sont leurs noms de section ELF/etc). Et peut-ĂȘtre qu'ils prennent ou non en charge l'accĂšs alĂ©atoire, si nous finissons par effectuer une compression.

@gdamore ,

Juste une autre ride ici - j'ai un projet différent qui utilise le code source DTrace (intégré). Ceci est sensible aux différences entre n et rn.
...
Si divers munging ne font pas l'affaire, alors c'est un argument contre l'utilisation de cette nouvelle fonctionnalité.

Vous pouvez également munge au moment de l'exécution pour supprimer tous les retours chariot qui sont intégrés à partir des utilisateurs Windows exécutant go install. J'ai écrit ce filtre io.Reader plusieurs fois.

Mais je suis venu du cĂŽtĂ© de @rsc pour ne pas avoir de compression Ă  cause d'une rĂ©alisation que j'avais : le contenu mĂȘme qui est le plus compressible (HTML, JS, CSS, etc.) est le contenu sur

La compression et l'accÚs aléatoire ne s'excluent pas totalement. Voir par exemple une discussion ici : https://stackoverflow.com/questions/429987/compression-formats-with-good-support-for-random-access-within-archives

La compression et l'accÚs aléatoire ne s'excluent pas totalement

Oui, si nous voulions une recherche à gros grains avec un peu de frais généraux pour arriver à la bonne position. J'ai travaillé dans cet espace avec le format stargz de CRFS . Mais je crains que les frais généraux ne soient suffisamment importants pour que nous ne voulions pas le faire automatiquement pour les gens. Je suppose que vous pouvez également le gonfler paresseusement en mémoire (et pouvoir le déposer sur les GC, comme un sync.Pool), mais cela ne semble pas en valoir la peine.

Je crains que les frais généraux ne soient suffisamment importants pour que nous ne voulions pas le faire automatiquement pour les gens.

Assez juste. La question importante est de savoir si nous préférerions une API qui nous permette de changer d'avis à moindre coût plus tard, si les besoins changent ou si les expériences montrent que la surcharge est acceptable.

@bradfitz bon point. Et je peux certainement le faire. FWIW, dans mon référentiel, j'ai également configuré git pour qu'il soit moins toxique lors de la visualisation des fichiers .d. Néanmoins, je trouve que la propriété des chaßnes intégrées avec des guillemets arriÚre est utile, en ce sens qu'elle est prévisible et non soumise aux caprices de git ou du systÚme.

Ce Ă  quoi je voulais en venir avec l'idĂ©e du codec, c'est que la compression n'est pas la seule transformation que l'on pourrait souhaiter et qu'un type de codec fourni par l'utilisateur permet Ă  la chaĂźne d'outils d'ignorer les indicateurs autres que "quel codec". Tout niveau de compression, ou l'algorithme du tout, compression ou autre, devrait ĂȘtre spĂ©cifique au codec utilisĂ©. Je suis tout Ă  fait d'accord qu'essayer de "prendre en charge la compression" dans le sens de fournir un ensemble spĂ©cifique de formats et de boutons serait une course folle avec toutes les variations que les gens pourraient demander. En fait, je serais trĂšs enthousiasmĂ© par les utilisations peu courantes, comme le prĂ©traitement des donnĂ©es i18n, peut-ĂȘtre, ou le traitement des ensembles de donnĂ©es comme dans latlong , donc je pense qu'il vaut toujours la peine d'envisager des options autour de cela.

J'ai pensĂ© Ă  une autre façon d'offrir la mĂȘme flexibilitĂ© qui pourrait ĂȘtre plus agrĂ©able. La directive // go:embed pourrait ĂȘtre une invocation de commande, tout comme // go:generate . Pour le cas le plus simple, quelque chose comme

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

La principale diffĂ©rence Ă©tant, bien sĂ»r, que la sortie standard de l'appel de commande est intĂ©grĂ©e sous le nom fourni. L'exemple utilise Ă©galement un package factice avec go run pour montrer comment il serait probable que la commande soit indĂ©pendante du systĂšme d'exploitation, car cat peut ne pas ĂȘtre disponible partout oĂč Go compile.

Cela prend en charge l'Ă©tape « encoder » de la transformation, et peut-ĂȘtre que la tĂąche de l'Ă©tape « dĂ©coder » peut ĂȘtre laissĂ©e Ă  l'utilisateur. Le package runtime/embed peut simplement fournir les octets que l'utilisateur a demandĂ© Ă  la chaĂźne d'outils d'intĂ©grer, quel que soit l'encodage. C'est bien parce que l'utilisateur sait quel devrait ĂȘtre le processus de dĂ©codage.

Un gros inconvĂ©nient est que je ne vois pas de bon moyen d'intĂ©grer un glob de plusieurs fichiers de cette façon, au-delĂ  des octets intĂ©grĂ©s Ă©tant un zip ou quelque chose. Cela pourrait en fait ĂȘtre suffisant, car un glob pourrait toujours ĂȘtre utilisĂ© par une commande zip, et c'est du cĂŽtĂ© de la dĂ©finition que vous vous souciez vraiment du glob. Mais nous pourrions Ă©galement tirer deux fonctionnalitĂ©s de cette proposition, l'une pour faire de l'embarquĂ© simple et l'autre pour exĂ©cuter un gĂ©nĂ©rateur-embarquĂ©.

Un inconvĂ©nient possible qui m'est venu est qu'il ajoute une Ă©tape ouverte dans la construction, en supposant que les intĂ©grations doivent ĂȘtre gĂ©rĂ©es par go build et ne nĂ©cessitent pas une invocation supplĂ©mentaire de la chaĂźne d'outils comme le fait go generate . Je pense que c'est bien, cependant. Peut-ĂȘtre que l'outil peut gĂ©rer son propre cache pour Ă©viter de rĂ©pĂ©ter des opĂ©rations coĂ»teuses, ou peut-ĂȘtre qu'il peut communiquer avec la chaĂźne d'outils pour utiliser le cache de Go. Cela ressemble Ă  un problĂšme qui peut ĂȘtre rĂ©solu et correspond au thĂšme gĂ©nĂ©ral de go build faire plus pour nous (comme rĂ©cupĂ©rer des modules).

L'un des objectifs de ce projet est-il de garantir que les versions Go ne nécessitent aucun outil externe et aucune ligne go:generate ?

Sinon, il semble utile de garder les choses simples et de ne prendre en charge qu'une tranche ou une chaßne d'octets, car si l'utilisateur souhaite une compression avec beaucoup de boutons, il peut le faire dans son fichier make (ou similaire), générer une ligne, etc. avant de construire de toute façon, il ne semble donc pas utile de les ajouter à quel que soit le résultat de cette proposition.

Si ne pas exiger Make ou similaire est un objectif, alors je suppose qu'il peut ĂȘtre judicieux d'utiliser la compression, mais personnellement, je ferais aussi bien d'utiliser Make, go generate, etc. .

@SamWhited ,

L'un des objectifs de ce projet est-il de garantir que les versions Go ne nécessitent aucun outil externe et aucune ligne go:generate ?

Oui.

Si les gens veulent utiliser go:generate ou Makefiles ou d'autres outils, ils ont des dizaines de choix aujourd'hui.

Nous voulons quelque chose de portable, sĂ»r et correct qui fonctionne par dĂ©faut. (et pour ĂȘtre clair : safe signifie que nous ne pouvons pas exĂ©cuter de code arbitraire au moment de l'installation, pour la mĂȘme raison que go:generate ne s'exĂ©cute pas par dĂ©faut)

@stephens2424

Je pense que nous pourrions contourner la nécessité pour la chaßne d'outils de prendre en charge activement des compressions/transformations spécifiques si l'interaction d'intégration peut éventuellement fournir un "codec".

Aucune exécution de code arbitraire pendant go build .

Pas d'exécution de code arbitraire pendant go build.

Oui, je vois ça maintenant. Je suppose qu'il n'y a aucun moyen de rĂ©concilier le fait d'avoir uniquement des fichiers "source" dans un dĂ©pĂŽt, de vouloir des fichiers "traitĂ©s" intĂ©grĂ©s, d'avoir le package "go gettable", _et_ de garder go build simple et sĂ»r. Je suis toujours en faveur de la standardisation ici, mais j'espĂ©rais avoir mon gĂąteau et le manger aussi, je suppose. Ça vaut le coup! Merci d'avoir attrapĂ© le problĂšme !

@flimzy

Cela exclurait l'utilisation des autres méthodes par la réflexion.

Il n'y a pas de mĂ©thodes dans ce que j'ai mentionnĂ©, seulement des fonctions. Ils ne sont pas dĂ©tectables au moment de l'exĂ©cution et il n'y a aucun moyen de les rĂ©fĂ©rencer sans les mentionner par leur nom dans la source. Et notez que les valeurs d'interface renvoyĂ©es par les diffĂ©rentes fonctions ne doivent pas nĂ©cessairement ĂȘtre du mĂȘme type - en effet, je m'attendrais Ă  ce qu'elles soient soit des types non exportĂ©s avec exactement la mĂ©thode requise pour implĂ©menter cette interface, soit une instance de *strings.Reader etc., tout ce qui aurait du sens dans le contexte.

On peut cependant soutenir que l'idĂ©e souffre de la transmission des fonctions exportĂ©es du package d'intĂ©gration en tant que valeurs. Bien que mĂȘme cela ne soit probablement pas un problĂšme - la signature contient un type non exportĂ© (voir ci-dessous), vous ne pouvez donc pas dĂ©clarer une variable, un argument ou un retour de leur type. Vous pouvez les transmettre Ă  reflect.ValueOf eux-mĂȘmes, en thĂ©orie. Je ne sais mĂȘme pas si cela vous permettrait de les appeler (vous auriez toujours Ă  construire une valeur de leur type de paramĂštre, qui n'est pas exportĂ©e. Je ne sais pas si reflect le permet).

Mais quoi qu'il en soit : il serait toujours possible (et le plus simple) d'ĂȘtre simplement pessimiste au cas oĂč une fonction de niveau supĂ©rieur de embed serait utilisĂ©e comme valeur et assumerait les restrictions qu'elle crĂ©e sur tous les fichiers intĂ©grĂ©s. Ce qui voudrait dire que si vous dĂ©cidez de faire des choses extrĂȘmement bizarres et inutiles avec le package embed, vous perdez certaines optimisations (sur lesquelles nous ne faisons pas nĂ©cessairement de promesses de toute façon). Semble juste.

En fait, je pense que les implications sont plus larges que cela. Si l'utilisation de FileReaderAt indique que les donnĂ©es doivent ĂȘtre dĂ©compressĂ©es, alors l'utilisation de FileReaderAt() avec toute entrĂ©e non const implique que tous les fichiers doivent ĂȘtre stockĂ©s non compressĂ©s.

Cela n'a aucun sens d'autoriser des entrĂ©es non constantes, car le nom de fichier doit ĂȘtre connu de maniĂšre statique pour effectuer l'intĂ©gration. Il Ă©tait cependant imprĂ©cis de ma part d'utiliser string comme type de paramĂštres de nom de fichier : ils auraient dĂ» vraiment ĂȘtre un type filename string non exportĂ© et ne pas ĂȘtre utilisĂ©s comme autre chose que des arguments de fonction. De cette façon, il est impossible de passer quoi que ce soit qui ne soit pas une constante de chaĂźne non typĂ©e.

@Merovius

Cela n'a aucun sens d'autoriser des entrées non constantes

Je pense que nous parlons de choses différentes. Je veux dire les entrées des fonctions d'accÚs (c'est- FileReaderAt() dire

Et mon propos est le suivant : supposons que nous ayons incorporĂ© 100 fichiers, mais que nous ayons un appel FileReaderAt(filename) , oĂč filename n'est pas constant ; il n'y a aucun moyen de savoir lesquels (le cas Ă©chĂ©ant) des fichiers intĂ©grĂ©s seront accessibles de cette maniĂšre, donc tous doivent ĂȘtre stockĂ©s non compressĂ©s.

@flimzy, nous parlions de la mĂȘme chose, je ne pensais vraiment pas que les noms de fichiers non const auraient du sens :) Ce qui, Ă  bien y penser, Ă©tait faux et un oubli. DĂ©solĂ© pour ça. Les fonctionnalitĂ©s permettant de regrouper ou d'inclure des rĂ©pertoires entiers, puis de les parcourir, sont en fait assez importantes, oui. Pense encore cela pourrait ĂȘtre rĂ©solu - par exemple , en prenant la dĂ©cision par collection (dir / glob) et ne permettant de sĂ©lectionner ceux des noms constants - mais comme je l' ai dit: Il est pas vraiment une API que je considĂšre super appropriĂ© pour l'outil Go Ă  cause de comme c'est magique. Donc, entrer dans les mauvaises herbes comme ça donne probablement au concept plus d'espace dans la discussion qu'il ne le mĂ©rite :)

Un autre cas que je n'ai pas vu dans les messages précédents et qui m'a fait envisager d'intégrer un fichier dans un binaire Go était l'impossibilité de distribuer correctement un package wrapper d'une bibliothÚque partagée C en utilisant go build/install (la bibliothÚque partagée reste dans le sources).

Je ne l'ai pas fait au final mais cela me ferait certainement reconsidĂ©rer ma dĂ©cision pour cette affaire. La bibliothĂšque C a en effet beaucoup de dĂ©pendances qui seraient plus faciles Ă  distribuer en tant que bibliothĂšque partagĂ©e. Cette bibliothĂšque partagĂ©e pourrait ĂȘtre intĂ©grĂ©e par les liaisons Go.

Wow!!!

@Julio-Guerra
Je suis sûr que vous auriez toujours à les extraire sur le disque, puis à utiliser dlopen et dlsym pour appeler les fonctions C.

Edit: J'ai un peu mal compris votre message, je viens de réaliser que vous parlez de créer un binaire pour la distribution

En dehors des ressources statiques http, pour les blobs intégrés dont vous avez besoin dans un pointeur vers la mémoire, il serait bien d'avoir une fonction qui renvoie le pointeur vers la mémoire intégrée déjà en cours. Sinon, il faudrait allouer une nouvelle mémoire et faire une copie à partir du io.Reader. Cela consommerait deux fois plus de mémoire.

@glycerine , encore une fois, c'est un string . Un string est un pointeur et une longueur.

Ne serait-il pas formidable d'avoir juste un moyen de marquer le code à exécuter au moment de la compilation et de fournir le résultat au moment de l'exécution. De cette façon, vous pouvez lire n'importe quel fichier, le compresser si vous le souhaitez au moment de la compilation et au moment de l'exécution, vous pouvez y accéder. Cela fonctionnerait pour certains calculs comme cela fonctionnerait pour le préchargement du contenu du fichier.

@burka comme dit précédemment dans le fil, go build n'exécutera pas de code arbitraire.

@burka , c'est explicitement hors de portée. Cette décision (pas d'exécution de code au moment de la compilation) a été prise il y a longtemps et ce n'est pas le bogue pour changer cette politique.

Un effet secondaire de cette proposition est que les proxys go ne peuvent jamais optimiser les fichiers qu'ils stockent pour qu'ils ne soient que des fichiers go. Un proxy doit stocker un référentiel entier car il ne saura pas si le code Go intÚgre l'un des fichiers non-Go.

Je ne sais pas si les proxys optimisent dĂ©jĂ  pour cela, mais c'est faisable un jour, ils le voudront peut-ĂȘtre.

@leighmcculloch Je ne pense pas non plus que ce soit le cas aujourd'hui. Tous les fichiers non Go d'un package Go doivent ĂȘtre inclus dans les archives du module, car ils peuvent ĂȘtre requis pour go test . Vous pouvez Ă©galement avoir des fichiers C pour cgo, comme autre exemple.

C'est une direction passionnante, nous en avons certainement besoin pour nos cas d'utilisation.

Cela dit, j'ai l'impression qu'il existe diffĂ©rents cas d'utilisation avec des exigences diffĂ©rentes, mais la plupart de ceux qui commentent _comment_ ils pensent que cela devrait ĂȘtre fait envisagent implicitement leurs propres cas d'utilisation mais ne les dĂ©finissent pas explicitement.

Cela pourrait ĂȘtre utile - du moins vraiment utile pour moi - si nous pouvions dĂ©limiter les diffĂ©rents cas d' les dĂ©fis que chaque cas d'utilisation prĂ©sente.

Par exemple, notre cas d'utilisation principal consiste Ă  intĂ©grer HTML+CSS+JS+JPG+etc afin que lorsque l'application go est exĂ©cutĂ©e, elle puisse Ă©crire ces fichiers dans un rĂ©pertoire de maniĂšre Ă  ce qu'ils puissent ĂȘtre servis par un http.FileServer . Étant donnĂ© ce cas d'utilisation, la plupart des commentaires que j'ai lus sur les lecteurs et les Ă©crivains m'Ă©taient Ă©trangers car nous n'avons pas besoin d'accĂ©der aux fichiers depuis Go, nous laissons simplement go-bindata copier sur le disque _(bien que peut-ĂȘtre existe-t-il un moyen de tirer parti de meilleures techniques que nous n'avons tout simplement pas encore rĂ©alisĂ© que nous devrions envisager.)_

Mais nos défis sont les suivants : nous utilisons généralement GoLand avec son débogueur et travaillerons sur l'application Web en apportant des modifications continues. Ainsi, pendant le développement, nous avons besoin de http.FileServer pour charger les fichiers directement depuis notre répertoire source. Mais lorsque l'application s'exécute, http.FileServer doit lire ces fichiers à partir du répertoire dans lequel les fichiers ont été écrits par la solution d'intégration. Ce qui signifie que lorsque nous compilons, nous devons exécuter go-bindata pour mettre à jour les fichiers, puis les archiver dans Git. Et tout cela est généralement réalisable avec go-bindata , bien que certainement pas une idée.

Cependant, Ă  d'autres moments, nous devons rĂ©ellement exĂ©cuter un exĂ©cutable compilĂ©, afin que nous puissions attacher un dĂ©bogueur au programme en cours d'exĂ©cution tout en laissant ce programme charger les fichiers Ă  partir du rĂ©pertoire source et non du rĂ©pertoire oĂč le fichier intĂ©grĂ© est Ă©crit par go-bindata . Actuellement, nous n'avons pas de bonne solution pour cela.

VoilĂ  donc nos cas d'utilisation et nos dĂ©fis. Peut-ĂȘtre que d'autres pourraient dĂ©finir explicitement les autres cas d'utilisation et l'ensemble de dĂ©fis associĂ©s, afin que ces discussions puissent explicitement aborder les diffĂ©rents espaces problĂ©matiques et/ou indiquer explicitement que cet effort ne rĂ©pondra pas aux besoins spĂ©cifiques d'un espace problĂ©matique donné ?

Merci d'avance de considérer.

Comme je ne le vois pas mentionné comme cas d'utilisation, nous en bénéficierions également pour notre répertoire de modÚles auxquels nous accédons via template.ParseFiles.

Je trouverais que l'approche la plus propre serait une mĂ©thode d'inscription via go.mod . Cela garantirait qu'il Ă©tait rĂ©trocompatible (car les projets existants devraient choisir de l'utiliser) et permettrait aux outils (tels que les proxys go) de dĂ©terminer quels fichiers sont nĂ©cessaires. La commande go mod init pourrait ĂȘtre mise Ă  jour pour inclure une version par dĂ©faut pour les nouveaux projets afin de faciliter son utilisation Ă  l'avenir.

Je peux voir des arguments pour que le rĂ©pertoire soit un nom standard (si nous avons besoin d'un opt-in, il peut s'agir d'un nom plus propre / plus simple) ou que le nom du rĂ©pertoire soit dĂ©fini dans go.mod lui-mĂȘme et permet aux utilisateurs de choisissez le nom (mais ayant une valeur par dĂ©faut fournie par go mod init .

Dans mon esprit, une solution comme celle-ci atteint un équilibre entre facilité d'utilisation et moins "magique".

@jayconrod a Ă©crit :

Un argument en faveur d'un pragma de commentaire (//go:embed) au lieu d'un nom de répertoire spécial (static/) : un commentaire nous permet d'intégrer un fichier dans l'archive de test pour un paquet (ou l'archive xtest) mais pas la bibliothÚque sous test.

C'est une trÚs belle observation. Bien que si nous voulions utiliser le nom de répertoire spécial, nous pourrions utiliser un mécanisme familier : static pour toutes les versions, static_test pour les versions de test, static_amd64 pour les versions amd64, et bientÎt. Cependant, je ne vois pas de moyen évident de fournir une prise en charge arbitraire des balises de construction.

Il pourrait y avoir un fichier manifeste dans le rĂ©pertoire statique (par dĂ©faut lorsqu'un manifeste vide est tout inclus sauf le manifeste) qui inclut des globs et permet de spĂ©cifier des balises de construction et peut-ĂȘtre une compression ultĂ©rieure, etc.

Un avantage est que si go list atteint un répertoire contenant un manifeste, il peut ignorer cet arbre à la #30058

Un inconvénient est que cela pourrait devenir trÚs difficile et non merci

Un mĂ©canisme simple Ă  bouton zĂ©ro pour regrouper des fichiers dans un package pourrait ĂȘtre un rĂ©pertoire spĂ©cial go.files dans un rĂ©pertoire de package (similaire Ă  go.mod dans un module). L'accĂšs serait limitĂ© Ă  ce package, Ă  moins qu'il ne choisisse d'exporter un symbole.

Edit : proposition runtime/files fonction unique :

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)
}

L'approche import "C" a déjà créé un précédent pour les chemins d'importation "magiques". OMI, cela a plutÎt bien fonctionné.

Comme je ne le vois pas mentionné comme cas d'utilisation, nous en bénéficierions également pour notre répertoire de modÚles auxquels nous accédons via template.ParseFiles.

Il y a un autre dĂ©fi : alors que le binaire peut contenir tous les fichiers nĂ©cessaires, ces mĂȘmes fichiers seraient les valeurs par dĂ©faut que je fournis en tant que dĂ©veloppeur. Cependant, les modĂšles comme par exemple les mentions lĂ©gales ou les politiques de confidentialitĂ© doivent ĂȘtre personnalisables par l'utilisateur final. Autant que je sache, cela signifie qu'il doit y avoir un moyen d'exporter mes fichiers par dĂ©faut, puis de laisser le binaire utiliser les fichiers personnalisĂ©s au moment de l'exĂ©cution, ou de remplacer les versions intĂ©grĂ©es par les versions personnalisĂ©es.

Je pense que cela pourrait ĂȘtre fait en fournissant une API avec des fonctions pour « exporter » et « remplacer » une ressource intĂ©grĂ©e. Le dĂ©veloppeur pourrait alors fournir des options de ligne de commande Ă  l'utilisateur final (en utilisant en interne les appels d'API mentionnĂ©s).

Tout cela, bien sûr, basé sur l'hypothÚse qu'il y aura en fait une sorte d'intégration qui faciliterait certainement le déploiement.

Merci d'avoir ouvert le sujet. Au travail, nous avons pensĂ© Ă  la mĂȘme idĂ©e de fonctionnalitĂ©, car nous avons besoin d'intĂ©grer des fichiers dans Ă  peu prĂšs tous les projets Golang. Les bibliothĂšques existantes fonctionnent bien, mais je pense que c'est une fonctionnalitĂ© que Golang rĂ©clame. C'est un langage fait pour se transformer en un seul binaire statique. Il devrait adopter cela en nous permettant de charger les fichiers d'actifs requis dans le binaire, avec une API universelle et conviviale pour les dĂ©veloppeurs.

Je veux juste fournir rapidement mes dĂ©tails de mise en Ɠuvre prĂ©fĂ©rĂ©s. Plusieurs personnes ont parlĂ© de fournir automatiquement une API pour lire les fichiers intĂ©grĂ©s, au lieu d'avoir besoin d'un autre signal comme un commentaire magique. Je pense que cela devrait ĂȘtre la voie Ă  suivre, car cela offre une syntaxe programmatique familiĂšre Ă  l'approche. Opter pour un package spĂ©cial, peut-ĂȘtre runtime/embed comme mentionnĂ© prĂ©cĂ©demment, satisferait cela et permettrait une extensibilitĂ© facile Ă  l'avenir. Une implĂ©mentation comme la suivante serait la plus logique pour moi :

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{}) 
}

L'utilisation de ce package quelque part dans votre code devrait déclencher le compilateur pour fournir ce fichier au runtime en l'incorporant automatiquement.

// 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)

De plus, je pense que les problÚmes de sécurité et de reproductibilité ne devraient pas poser de problÚme si nous limitons l'importation à des fichiers situés ou éventuellement à 1 niveau de répertoire en dessous du répertoire go.mod, qui a également déjà été mentionné précédemment dans le fil de discussion. Les chemins d'intégration absolus seraient résolus par rapport à ce niveau de répertoire.

Ne pas accéder aux fichiers pendant le processus de compilation générera une erreur de compilation.

Il est Ă©galement possible de crĂ©er une archive zip derriĂšre un binaire, afin qu'il puisse effectivement devenir un binaire auto-extractible. Peut-ĂȘtre que cela est utile dans certains cas d'utilisation ? A fait cela comme une expĂ©rience ici: https://github.com/sanderhahn/gozip

Go a déjà des « données de test ». Les tests unitaires utilisent des IO réguliÚres pour faire ce qu'ils veulent faire. La portée du test signifie que le contenu n'est pas expédié. C'est tout ce qu'il y a à savoir, pas de fioritures, pas de magie, pas de logique de conteneur compressé, pas d'indirections configurables, pas de META-INF. Beau, simple, élégant. Pourquoi ne pas avoir un dossier « données » pour les dépendances de portée d'exécution groupées ?

Nous pouvons facilement analyser des projets Go existants dans Github ea et proposer un certain nombre de projets qui utilisent déjà un dossier « données » et nécessitent donc une adaptation.

Autre chose qui n'est pas claire pour moi. Pour la discussion d'un rĂ©pertoire static , il n'est pas clair pour moi Ă  100% si nous parlons d'un rĂ©pertoire static _source_ , ou d'un rĂ©pertoire static oĂč les fichiers seront mis Ă  disposition _at runtime_ ?

Et cette distinction est particuliÚrement importante car elle concerne le processus de développement et le code de débogage en cours de développement.

@mikeschinkel, il ressort assez clairement du message d'origine que l'intégration se produirait au moment de la construction :

faire go install / go build faire l'intégration automatiquement

Le message d'origine et certains des commentaires ci-dessus parlent également d'un mode "dev" pour charger les fichiers au moment de l'exécution.

@mvdan Merci pour la réponse. Vous pensez donc que cela signifie que le répertoire /static/ proposé serait relatif à la racine du référentiel d'applications, du référentiel de packages et/ou éventuellement des deux ?

Et cet emplacement des fichiers d'exĂ©cution dĂ©pendrait totalement de l'endroit oĂč le dĂ©veloppeur voulait les placer ?

Si tout cela est vrai - et cela semble logique - il serait utile que les programmes compilés avec des informations de débogage puissent éventuellement charger des fichiers à partir de leur emplacement source pour faciliter le débogage sans beaucoup de logique et de code supplémentaires - et non standardisés.

Quelques personnes ont mentionné les proxys de module ci-dessus. Je pense que c'est un excellent test décisif pour une bonne conception de cette fonctionnalité.

Il semble possible aujourd'hui, sans exĂ©cuter de code utilisateur, d'implĂ©menter aujourd'hui un proxy de module fonctionnel qui supprime les fichiers qui ne sont pas utilisĂ©s dans la construction. Certaines des conceptions ci-dessus signifient que les proxys de module doivent exĂ©cuter le code utilisateur pour dĂ©terminer quels fichiers statiques doivent Ă©galement ĂȘtre inclus.

Les gens ont également mentionné go.mod comme opt-in.

Idée : spécification dans le fichier go.mod ? Facilite l'analyse des autres outils.

module github.com/foo/bar

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

Cela crĂ©erait un package au moment de la compilation avec les donnĂ©es du fichier. La syntaxe Glob peut ĂȘtre agrĂ©able ici, mais peut-ĂȘtre que simplifier et n'incorporer que des rĂ©pertoires est suffisant. (A part : +1 pour la syntaxe ** glob.)

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

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

Quelque chose comme StripPrefix pourrait ĂȘtre sympa ici mais pas nĂ©cessaire. CrĂ©ez facilement un package wrapper qui utilise les chemins de fichiers que vous souhaitez.

On pourrait encore simplifier :

module github.com/foo/bar

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

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

Mais il est un peu peu intuitif que moddata ait un comportement différent selon le package/module appelant. Cela rendrait plus difficile l'écriture d'aides (par exemple, le convertisseur http.Filesystem)

Il semble possible aujourd'hui, sans exĂ©cuter de code utilisateur, d'implĂ©menter aujourd'hui un proxy de module fonctionnel qui supprime les fichiers qui ne sont pas utilisĂ©s dans la construction. Certaines des conceptions ci-dessus signifient que les proxys de module doivent exĂ©cuter le code utilisateur pour dĂ©terminer quels fichiers statiques doivent Ă©galement ĂȘtre inclus.

Je ne pense pas qu'il y aurait un changement significatif ici. En particulier, le code C pourrait déjà inclure n'importe quel fichier dans l'arborescence, donc un proxy de module qui voudrait faire cela devrait analyser C. Il semble qu'à ce stade, quels que soient les commentaires magiques ou l'API que nous introduirons, ce sera un petit pas.

Certaines des conceptions ci-dessus signifient que les proxys de module doivent exĂ©cuter le code utilisateur pour dĂ©terminer quels fichiers statiques doivent Ă©galement ĂȘtre inclus.

Je pense qu'il est assez clair que "l'outil go ne doit pas exĂ©cuter de code utilisateur pendant la construction" est une ligne tracĂ©e dans le sable qui ne sera pas franchie ici. Et si l'outil go ne peut pas exĂ©cuter le code utilisateur, alors il doit ĂȘtre possible de dire quels fichiers inclure sans lui.

J'ai essayĂ© de condenser mes diverses rĂ©flexions sur ce cas d'utilisation en quelque chose de convaincant, et donc un gros +1 Ă  ce que @broady a suggĂ©rĂ©. Je pense que cela rĂ©sume en grande partie ce que j'ai pensĂ©. Cependant, je pense que le mot-clĂ© devrait ĂȘtre le verbe embed au lieu du nom data .

  1. Les fichiers intĂ©grĂ©s ressemblent Ă  quelque chose qui devrait ĂȘtre importĂ© plutĂŽt que d'avoir simplement un commentaire spĂ©cial ou un package magique. Et dans un projet Go, le fichier go.mod est l'endroit oĂč un dĂ©veloppeur peut spĂ©cifier les modules/fichiers nĂ©cessaires, il est donc logique de l'Ă©tendre pour prendre en charge l'intĂ©gration.

  2. De plus, une collection de fichiers intĂ©grĂ©s me semble plus utile et rĂ©utilisable si un package qui pourrait ĂȘtre inclus plutĂŽt que quelque chose d'ad-hoc ajoutĂ© Ă  un projet Go en utilisant une syntaxe unique. L'idĂ©e ici est que si les intĂ©grations Ă©taient implĂ©mentĂ©es sous forme de packages, les gens pourraient les dĂ©velopper et les partager via Github et d'autres pourraient les utiliser dans leurs projets. Imaginez des packages gĂ©rĂ©s par la communautĂ© et gratuits sur GitHub contenant :

    une. Fichiers pour les pays oĂč chaque fichier contient tous les codes postaux de ce pays,
    b. Un fichier avec toutes les chaĂźnes d'agent utilisateur connues pour identifier les navigateurs,
    c. Images du drapeau de chaque pays du monde,
    ré. Informations d'aide détaillées décrivant les erreurs courantes dans un programme Go,
    e. etc...

  3. Un nouveau schéma d'URL tel que goembed:// - ou peut -

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

Avec les concepts ci-dessus, rien ne ressemble à de la _"magie"_ ; tout serait géré avec élégance par un mécanisme qui semble avoir été conçu à cet effet. Il n'y aurait besoin que de trÚs peu d'extensions ; un nouveau verbe dans go.mod et un nouveau schéma d'URL qui serait reconnu en interne par Go. Tout le reste serait fourni tel quel depuis Go.

Ce que je fais maintenant

J'utilise code.soquee.net/pkgzip pour cela en ce moment (c'est un fork de statik qui modifie l'API pour éviter l'état global et les effets secondaires d'importation). Mon flux de travail normal (au moins dans une application Web) consiste à intégrer des ressources regroupées dans un fichier ZIP, puis à les servir à l'aide de golang.org/x/tools/godoc/vfs/zipfs et golang.org/x/tools/godoc/vfs/httpfs .

go:embed approche

Il y a deux choses qui pourraient m'empĂȘcher d'adopter l'approche go:embed :

  1. Le code généré n'apparaßtra pas dans la documentation
  2. Les actifs peuvent ĂȘtre dispersĂ©s dans toute la base de code (cela est vrai de l'utilisation d'outils externes et de go:generate Ă©galement, c'est pourquoi je prĂ©fĂšre gĂ©nĂ©ralement utiliser un makefile pour gĂ©nĂ©rer divers ensembles d'actifs avant la construction, puis je peut les voir tous dans le makefile)

Il y a aussi un problÚme que je n'inclus pas ci-dessus, car il peut s'agir pour certains d'une fonctionnalité selon laquelle le fait que les actifs fassent partie d'un package (par opposition à l'ensemble du module) signifie que toute la complexité des balises de construction, des packages de test, etc. . s'appliquent à eux, nous avons besoin d'un moyen de spécifier s'ils sont publics ou privés pour ce paquet, etc.

Ce que j'aime Ă  ce sujet, c'est que des bibliothĂšques pourraient ĂȘtre Ă©crites pour faciliter l'importation d'actifs. Par exemple. une bibliothĂšque avec un seul fichier Go qui intĂšgre simplement une police, ou certaines icĂŽnes pourraient ĂȘtre publiĂ©es et je pourrais l'importer comme n'importe quel autre package go. À l'avenir, je pourrais obtenir une police d'icĂŽne simplement en l'important :

import "forkaweso.me/forkawesome/v2"

approche de paquet intégré

Bien que j'aime l'idĂ©e que tout cela soit un code Go explicite et normal, je dĂ©teste l'idĂ©e que ce serait un autre package magique qui ne peut pas ĂȘtre implĂ©mentĂ© en dehors de la bibliothĂšque standard.

Un tel package serait-il dĂ©fini dans le cadre de la spĂ©cification du langage ? Sinon, c'est un autre endroit oĂč le code Go se briserait entre diffĂ©rentes implĂ©mentations, ce qui semble Ă©galement mĂ©diocre. Je continuerais probablement Ă  utiliser un outil externe pour Ă©viter cette casse.

De plus, comme d'autres l'ont mentionnĂ©, le fait que cela soit fait au moment de la construction signifie que ce package ne peut prendre que des littĂ©raux de chaĂźne ou des constantes comme arguments. Il n'y a actuellement aucun moyen de reprĂ©senter cela dans le systĂšme de types, et je soupçonne que ce sera un point de confusion. Cela pourrait ĂȘtre rĂ©solu en introduisant quelque chose comme des fonctions constantes, mais maintenant nous parlons de changements de langage majeurs, ce qui en fait un non-dĂ©marreur. Je ne vois pas un bon moyen de rĂ©soudre ce problĂšme autrement.

Hybride

J'aime l'idĂ©e d'une approche hybride. Au lieu de rĂ©utiliser les commentaires (qui finissent par ĂȘtre Ă©parpillĂ©s un peu partout et, sur une note personnelle, se sentent juste dĂ©goĂ»tants), j'aimerais voir tous les actifs mis au mĂȘme endroit, probablement le fichier go.mod comme les autres a dit:

module forkaweso.me/forkawesome/v2

go 1.15

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

Cela signifie que les actifs ne peuvent pas ĂȘtre inclus ou exclus par des balises de construction arbitraires ou dans des packages arbitraires (par exemple, le package _testing) sans crĂ©er un module sĂ©parĂ©. Je pense que cette rĂ©duction de la complexitĂ© peut ĂȘtre souhaitable (il n'y a pas de balise de construction cachĂ©e dans une bibliothĂšque que vous essayez d'importer, et vous ne pouvez pas comprendre pourquoi vous n'avez pas le bon actif car l'importation de la bibliothĂšque aurait dĂ» l'intĂ©grer) , mais YMMV. Si cela est souhaitable, des commentaires de type pragma peuvent toujours ĂȘtre utilisĂ©s, sauf qu'ils ne gĂ©nĂšrent pas de code et utilisent Ă  la place la mĂȘme approche que celle que je suis sur le point de dĂ©crire pour la version go.mod .

Contrairement Ă  la proposition originale, cela ne gĂ©nĂ©rerait aucun code. Au lieu de cela, la fonctionnalitĂ© pour par exemple. la lecture de la section de donnĂ©es du fichier ELF (ou cependant, cela finit par ĂȘtre stockĂ© sur le systĂšme d'exploitation que vous utilisez) serait ajoutĂ©e le cas Ă©chĂ©ant (par exemple, os ou debug/elf , etc.) et puis, Ă©ventuellement, un nouveau package serait crĂ©Ă© qui se comporte exactement comme le package dĂ©crit dans l'OP, sauf qu'au lieu d'ĂȘtre magique et de faire l'intĂ©gration lui-mĂȘme, il lit simplement les fichiers intĂ©grĂ©s (ce qui signifie qu'il pourrait ĂȘtre implĂ©mentĂ© en dehors de la bibliothĂšque standard si on le dĂ©sire).

Cela permet de contourner des problĂšmes tels que la nĂ©cessitĂ© de restreindre le package magique pour n'autoriser que les littĂ©raux de chaĂźne comme arguments, mais cela signifie qu'il est plus difficile de vĂ©rifier si les actifs intĂ©grĂ©s sont rĂ©ellement utilisĂ©s n'importe oĂč ou finissent par ĂȘtre un poids mort. Cela Ă©vite Ă©galement toute nouvelle dĂ©pendance entre les packages de bibliothĂšque standard, car le seul package qui doit importer quelque chose de supplĂ©mentaire est un nouveau package lui-mĂȘme.

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

Comme vu ci-dessus, mettre les ressources dans le module peut toujours les étendre à ce module particulier si vous le souhaitez. L'API réelle et la façon dont vous sélectionnez un élément peuvent nécessiter un certain travail.

Encore une autre idée : au lieu d'ajouter un nouveau type de verbe embed dans go.mod , nous pourrions introduire un nouveau type de package, un package de données, qui est importé et utilisé dans go.mod dans le ManiÚre habituelle. Voici un croquis d'homme de paille.

Si un package contient exactement un fichier .go , static.go , et que ce fichier ne contient que des commentaires et une clause de package, alors un package est un package de données. Lorsqu'il est importé, cmd/go remplit le package avec des fonctions exportées donnant accÚs aux fichiers qu'il contient, qui sont intégrés dans le binaire résultant.

S'il s'agit d'un package réel, cela signifierait que les rÚgles internal s'appliqueraient et que nous pouvons avoir des contrÎles d'accÚs sans ajouter à l'API.

Qu'en est-il alors de l'inclusion automatique de tous les fichiers et sous-dossiers non .go (en suivant les rÚgles d'absence de code) dans le répertoire ?

Si un package contient exactement un fichier .go , static.go , et que ce fichier ne contient que des commentaires et une clause de package, alors un package est un package de données.

Cette vĂ©rification serait-elle effectuĂ©e avant l'application des balises de construction ? Si c'est le cas, cela semble ĂȘtre un autre cas particulier, qu'il faudrait peut-ĂȘtre Ă©viter. Sinon, il est tout Ă  fait possible qu'un package soit considĂ©rĂ© comme un package Go standard pour certaines balises de construction et comme un package de donnĂ©es pour d'autres. Cela semble Ă©trange, mais peut-ĂȘtre est-ce souhaitable ?

@flimzy
Cela permettrait en quelque sorte d'utiliser des fichiers intĂ©grĂ©s avec une seule balise et de dĂ©finir les mĂȘmes fns/vars que le package gĂ©nĂ©rĂ© et de servir les fichiers d'une autre maniĂšre (peut-ĂȘtre Ă  distance ?) avec une autre balise.

Ce serait bien s'il y avait un indicateur de construction pour générer les fonctions wrapper, il suffit donc de remplir les blancs.

@josharian

Si un package contient exactement un fichier .go , static.go , et que ce fichier ne contient que des commentaires et une clause de package, alors un package est un package de données.

Je peux imaginer les packages de "données" comme ayant leur propre fonctionnalité spécifique à un domaine, telle qu'une recherche de code postal. L'approche que vous venez de proposer n'autoriserait rien d'autre que les données brutes, et annulerait ainsi les avantages de pouvoir emballer la logique avec les données.

Je peux imaginer les packages de "données" comme ayant leur propre fonctionnalité spécifique à un domaine, telle qu'une recherche de code postal.

Vous pouvez exposer des fonctionnalités dans my.pkg/postalcode et mettre des données dans my.pkg/postalcode/data (ou my.pkg/postalcode/internal/data).

Je vois l'intĂ©rĂȘt de procĂ©der comme vous le suggĂ©rez, mais cela soulĂšve un tas de questions : comment fonctionne la rĂ©trocompatibilité ? Comment marquer un paquet de donnĂ©es en tant que tel ? Que faites-vous si le package contient des fonctions qui entreraient en conflit avec celles que cmd/go ajouterait ? (Je ne dis pas que ceux-ci n'ont pas de rĂ©ponses, juste qu'il est plus simple de ne pas avoir Ă  y rĂ©pondre.)

@josharian , veuillez considérer le commentaire de vérification de type ci-dessus (https://github.com/golang/go/issues/35950#issuecomment-561443566).

@bradfitz oui, il s'agirait d'un changement de langue et nécessiterait la prise en charge de go/types.

En fait, il existe un moyen de le faire sans que ce soit un changement de langue - exiger que static.go contienne des fonctions sans corps correspondant exactement Ă  ce que cmd/go remplirait.

nécessite que static.go contienne des fonctions sans corps correspondant exactement à ce que cmd/go remplirait.

S'il génÚre des fonctions par fichier au lieu d'un catch all embed.File() , cela permettrait des contrÎles d'exportation faciles par actif.

Ainsi, les éléments générés ressembleraient à :

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

Un article de blog que j'ai Ă©crit sur les fichiers statiques il y a 4 mois. Voir la derniĂšre phrase dans les conclusions :-)

@josharian

Vous pouvez exposer des fonctionnalités dans my.pkg/postalcode et mettre des données dans my.pkg/postalcode/data (ou my.pkg/postalcode/internal/data).

Cela – bien que peu Ă©lĂ©gant – pourrait rĂ©pondre Ă  mes prĂ©occupations.

Comment fonctionne la rétrocompatibilité ?

Je ne vois pas en quoi les préoccupations de la Colombie-Britannique s'appliquent ici. Peux-tu élaborer?

Comment marquer un paquet de données en tant que tel ?

Avec l'instruction embed dans le go.mod ?

Peut-ĂȘtre que je ne suis pas ce que vous demandez.

Mais je vais le retourner; comment marquer un paquet avec uniquement des données en tant que telles ?

Que faites-vous si le package contient des fonctions qui entreraient en conflit avec celles que cmd/go ajouterait ?

  1. En utilisant l'approche proposée, je ne pense pas que cmd/go ait besoin d'ajouter des fonctions.

  2. MĂȘme si cmd/go a besoin d'ajouter des fonctions, j'imagine que le comportement des conflits dans un package _existant_ serait _non dĂ©fini_.

    La proposition suppose que le dĂ©veloppeur suit le principe de responsabilitĂ© unique et ne devrait donc crĂ©er qu'un package avec des donnĂ©es pour ĂȘtre un package centrĂ© sur les donnĂ©es et ne pas ajouter de donnĂ©es Ă  un package centrĂ© sur la logique existant.

    Bien sûr, un développeur _pourrait_ ajouter à un package existant, auquel cas le comportement serait indéfini. IOW, si un développeur ignore l'idiome, il serait en territoire inconnu.

Je ne dis pas que ceux-ci n'ont pas de réponses, juste qu'il est plus simple de ne pas avoir à y répondre.

Sauf que je pense que les réponses sont simples. Au moins pour ceux que vous avez posés jusqu'à présent.

Je pense que toute solution qui ajoute des symboles ou des valeurs pour les symboles doit ĂȘtre Ă©tendue au package, et non au module. Parce que l'unitĂ© de compilation pour Go est package, pas module.

Cela laisse donc de cÎté toute utilisation de go.mod pour spécifier la liste des fichiers à importer.

@dolmen si le rĂ©sultat final est dans son propre package, alors la portĂ©e elle-mĂȘme serait un module.

@urandom Non, la portée est le ou les packages qui importent ce package généré. Mais de toute façon, je ne pense pas qu'un package généré complet soit dans le cadre de cette proposition.

@urandom Non, la portée est le ou les packages qui importent ce package généré. Mais de toute façon, je ne pense pas qu'un package généré complet soit dans le cadre de cette proposition.

quelle que soit la maniĂšre dont cette proposition serait implĂ©mentĂ©e, Ă©tant donnĂ© que divers packages de modules utiliseront le rĂ©sultat final, il est logique que la dĂ©finition de ce qui est intĂ©grĂ© soit spĂ©cifiĂ©e au niveau du module. un prĂ©cĂ©dent existe dĂ©jĂ  dans l'Ă©cosystĂšme Java, oĂč les fichiers intĂ©grĂ©s sont de portĂ©e de module et ajoutĂ©s Ă  partir d'un rĂ©pertoire magique.

de plus, go.mod présente le moyen le plus propre d'ajouter une telle fonctionnalité (pas de commentaires magiques, ni de répertoires magiques) sans casser les programmes existants.

Voici quelque chose qui n'a pas encore vu mentionné : l'API pour les outils de traitement des sources Go (compilateur, analyseurs statiques) est aussi importante que l'API d'exécution. Ce type d'API est une valeur fondamentale de Go qui aide à développer l'écosystÚme (comme go/ast / go/format et go mod edit ).

Cette API pourrait ĂȘtre utilisĂ©e par des outils de prĂ©-processeur (en pas go:generate notamment) pour obtenir la liste des fichiers qui seront embarquĂ©s ou elle pourrait ĂȘtre utilisĂ©e pour gĂ©nĂ©rer la rĂ©fĂ©rence.

Dans le cas d'un package spécial, je ne vois rien à changer dans l'analyse go.mod ( go mod tools) ou l'analyseur go/ast .

@dolmen

_"Je pense que toute solution qui ajoute des symboles ou des valeurs pour les symboles doit ĂȘtre Ă©tendue au package, pas au module. Parce que l'unitĂ© de compilation pour Go est le package, pas le module. Cela exclut donc toute utilisation de go.mod pour spĂ©cifier la liste des fichiers Ă  importer."_

Qu'est-ce qu'un module ? Les modules sont _" une collection de packages Go associĂ©s qui sont versionnĂ©s ensemble en une seule unitĂ© ."_ Ainsi, un module peut consister en un seul package, et un seul package peut ĂȘtre l'intĂ©gralitĂ© d'un module.

En tant que tel, go.mod est le bon endroit pour spécifier la liste des fichiers à importer en supposant que l'équipe Go adopte go.mod sur les commentaires spéciaux et les packages magiques. C'est à moins que et jusqu'à ce que l'équipe Go décide d'ajouter un fichier go.pkg .

De plus, si l'équipe Go acceptait go.mod comme emplacement pour spécifier les fichiers d'intégration, toute personne souhaitant intégrer des fichiers devrait fournir un go.mod avec les instructions embed , et le package qui est représenté par le répertoire dans lequel réside le fichier go.mod serait le package qui contient les fichiers intégrés.

Mais si ce n'est pas ce que le développeur veut, il doit créer un autre fichier go.mod et le mettre dans le répertoire du package qu'il souhaite contenir ses fichiers intégrés.

Y a-t-il un scĂ©nario lĂ©gitime que vous envisagez oĂč ces contraintes ne seraient pas rĂ©alisables ?

@mikeschinkel , un module est une collection de packages _related_. Cependant, il est possible (et raisonnable !) d'utiliser un package d'un module sans extraire les dépendances transitives (et les données !) d'autres packages au sein de ce module.

Les fichiers de donnĂ©es sont gĂ©nĂ©ralement des dĂ©pendances par package, et non par module, de sorte que les informations sur la façon de localiser ces dĂ©pendances doivent ĂȘtre colocalisĂ©es avec le package, et non stockĂ©es en tant que mĂ©tadonnĂ©es distinctes au niveau du module.

@bcmills

Il semble que l'on puisse remplacer « fichiers de données » dans votre message par « modules » et cela restera vrai.
Il est assez courant d'avoir des modules spécifiques en tant que dépendances pour vos propres packages spécifiques.
Pourtant, nous les mettons tous dans le go.mod.

@urandom , tous les packages des modules indiqués dans le fichier go.mod ne sont pas liés dans le binaire final. (Mettre une dépendance dans le fichier go.mod n'est _pas_ équivalent à lier cette dépendance au programme.)

MĂ©ta-point

Il est clair que c'est quelque chose qui intĂ©resse beaucoup de gens, et le commentaire original de Brad en haut Ă©tait moins une proposition finie qu'un croquis initial / un point de dĂ©part / un appel Ă  l'action. Je pense qu'il serait logique Ă  ce stade de mettre fin Ă  cette discussion spĂ©cifique, de faire collaborer Brad et peut-ĂȘtre quelques autres personnes sur un document de conception dĂ©taillĂ©, puis de commencer un nouveau numĂ©ro pour une discussion sur ce document spĂ©cifique (Ă  rĂ©diger). . Il semble que cela aiderait Ă  concentrer ce qui est devenu une conversation un peu tentaculaire.

Les pensées?

Je ne suis pas sĂ»r d'ĂȘtre d'accord avec la fermeture de celui-ci, mais nous pouvons le maintenir en proposition jusqu'Ă  ce qu'il y ait un document de conception et verrouiller les commentaires pendant un moment. (Presque tous les commentaires sont redondants Ă  ce stade, car il y a trop de commentaires pour que les gens puissent les lire pour voir si leur commentaire est redondant...)

Peut-ĂȘtre que lorsqu'il y a un document de conception, celui-ci peut ĂȘtre fermĂ©.

Ou fermez celui-ci et rouvrez #3035 (avec les commentaires gelés) afin qu'au moins un problÚme ouvert le suive.

Désolé de le faire juste aprÚs avoir parlé de la fermeture, mais la discussion étroite a commencé juste aprÚs le commentaire de

"_Cependant, il est possible (et raisonnable !) d'utiliser un package d'un module sans extraire les dépendances transitives (et les données !) d'autres packages au sein de ce module."_

Oui, c'est clairement _possible._ Mais comme pour toute bonne pratique, une bonne pratique pour les packages de donnĂ©es pourrait ĂȘtre de crĂ©er un seul package pour un module, ce qui rĂ©sout votre problĂšme. Si cela signifie un go.mod Ă  la racine et un autre go.mod dans le sous-rĂ©pertoire contenant le paquet de donnĂ©es, qu'il en soit ainsi.

Je suppose que je prĂ©conise que vous ne faites pas du parfait l'ennemi du bien ici, oĂč le bien ici est identifiĂ© comme go.mod Ă©tant un endroit parfait pour spĂ©cifier des fichiers d'intĂ©gration Ă©tant donnĂ© que, par leur nature, ils sont une liste des composants d'un module.

Désolé, mais les packages sont le concept fondamental de Go, pas les modules.
Les modules sont simplement des groupes de packages qui sont versionnés en tant qu'unité.
Les modules n'apportent pas de sémantique supplémentaire au-delà de celles des packages individuels.
Cela fait partie de leur simplicité.
Tout ce que nous faisons ici doit ĂȘtre liĂ© Ă  des packages, pas Ă  des modules.

OĂč que cela se passe et quelle que soit la maniĂšre dont cela est fait, il devrait y avoir un moyen d'obtenir une liste de tous les actifs qui seront intĂ©grĂ©s Ă  l'aide de la liste go (pas seulement les modĂšles utilisĂ©s).

Mettre en attente jusqu'Ă  ce que Brad et d'autres Ă©laborent un document de conception formel.

Le blocage de certains fichiers ne devrait pas ĂȘtre trop difficile, surtout si vous utilisez un rĂ©pertoire static ou embed . Les liens symboliques peuvent compliquer un peu cela, mais vous pouvez simplement l'empĂȘcher d'intĂ©grer quoi que ce soit en dehors du module actuel ou, si vous ĂȘtes sur GOPATH, en dehors du package contenant le rĂ©pertoire.

Je ne suis pas particuliĂšrement fan d'un commentaire qui compile en code, mais je trouve Ă©galement un peu Ă©trange le pseudo-paquet qui affecte la compilation. Si l'approche par rĂ©pertoire n'est pas utilisĂ©e, il serait peut-ĂȘtre plus logique d'avoir une sorte embed dĂ©claration de niveau supĂ©rieur import , mais ne prendrait en charge que les chemins locaux et nĂ©cessiterait un nom pour lui ĂȘtre attribuĂ©. Par exemple,

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 : Tu m'as devancé, @jayconrod.

Ceci est propre et lisible, mais je ne suis pas sûr que l'équipe de go veuille introduire un nouveau mot-clé

L'idée, si je me souviens bien, était d'avoir simplement un nom de répertoire spécial comme "static" contenant les données statiques et de les rendre automatiquement disponibles via une API, sans annotations nécessaires.

Utiliser static comme nom de répertoire spécial est un peu déroutant et je préfÚrerais utiliser assets .
Une autre idée que je n'ai pas vue dans le fil est de permettre l'importation de assets tant que package, par exemple import "example.com/internal/assets" . L'API exposée a toujours besoin d'une conception, mais au moins elle a l'air plus propre que les commentaires spéciaux ou les nouveaux packages de style runtime/files .

Une autre idée que je n'ai pas vue dans le fil est d'autoriser l'importation d'actifs en tant que package

Cela a été proposé ici : https://github.com/golang/go/issues/35950#issuecomment -562966654

Une complication est que pour permettre la vérification des types, vous devez soit changer de langue, soit fournir des fonctions sans corps à remplir par cmd/go .

C'est une idĂ©e similaire, mais la conception static.go permet de transformer un chemin d'importation arbitraire en paquet de donnĂ©es, tandis que le rĂ©pertoire assets fonctionne plutĂŽt comme testdata , internal ou vendor en termes d'ĂȘtre "spĂ©cial". L'une des exigences possibles de assets est de ne contenir aucun paquet Go (ou de n'autoriser que les docs), c'est-Ă -dire pour une rĂ©trocompatibilitĂ© implicite.

Cela pourrait Ă©galement ĂȘtre combinĂ© avec l'API runtime/files -thingy pour obtenir les fichiers. C'est-Ă -dire en utilisant des import bruts pour incorporer des arborescences de rĂ©pertoires avec des fichiers, puis en utilisant un package d'exĂ©cution pour y accĂ©der. Peut-ĂȘtre mĂȘme os.Open , mais il est peu probable que cela soit acceptĂ©.

Le shurcooL/vfsgen ensemble shurcooL/httpgzip a une fonctionnalitĂ© intĂ©ressante oĂč le contenu peut ĂȘtre servi sans dĂ©compression.

par exemple

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

Une fonctionnalité similaire est proposée pour le C++ : std::embed :

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

Il peut ĂȘtre utile comme source d'inspiration pour un design, et pour collecter des cas d'utilisation possibles.

Je suis un peu en retard pour la fĂȘte, mais j'ai eu une idĂ©e. Au lieu de commentaires spĂ©ciaux, un rĂ©pertoire spĂ©cial fixe (statique) ou l'approche apparemment sans aucun de l'extension de go.mod : que diriez-vous d'un nouveau fichier manifeste par package : go.res

  • Contient une liste de fichiers. Pas de chemins ni de globs, juste des noms dans le rĂ©pertoire du package actuel. GĂ©nĂ©rez-le Ă  partir d'un glob avant de vous engager si vous en avez besoin.

    • __Edit__ il pourrait avoir besoin d'une seule ligne package mypackagename en haut, comme un fichier go l'aurait fait. Alternativement, vous pouvez inclure le nom du package dans le nom de fichier (par exemple mypackagename.go.res). Personnellement, j'aime mieux la ligne d'en-tĂȘte package .

  • Un nouveau package de base appelĂ© « ressource » ou peut-ĂȘtre « io/resource ». PossĂšde au moins une fonction : func Read(name string) (io.Reader, bool) pour lire les ressources intĂ©grĂ©es dans le package actuel.

    • __Edit__ Pas sĂ»r que les packages principaux fonctionnent de cette façon. Peut-ĂȘtre qu'il s'agit d'une fonction privĂ©e de package gĂ©nĂ©rĂ©e (par exemple, func readresource(name string) (io.Reader, bool) )

  • Si vous voulez des ressources dans un sous-rĂ©pertoire, faites du sous-rĂ©pertoire un package en ajoutant un fichier go.res et au moins un fichier .go . Le fichier go exporte votre propre API publique pour accĂ©der aux ressources du package de sous-rĂ©pertoire. Le fichier go et l'API exportĂ©e sont nĂ©cessaires, car les ressources d'autres packages ne sont pas automatiquement exportĂ©es (par conception). Vous pouvez Ă©galement personnaliser la façon dont ils sont exportĂ©s de cette façon.

    • __Edit__ alternativement, si vous avez besoin d'une structure de rĂ©pertoire et/ou d'une compression, utilisez une ressource tar. Cela permet des choses comme les bundles webpack, qui nĂ©cessitent dĂ©jĂ  une compilation (et pourraient bĂ©nĂ©ficier de la prĂ©-compression). Les amener un peu plus loin vers un goudron est simple.

  • __Modifier__ Besoin d'un manifeste ? Incluez simplement le fichier go.res lui-mĂȘme en tant que ressource. Nous n'avons mĂȘme pas besoin de crĂ©er une fonction listresources.

ExtrĂȘmement simple. Une nouvelle fonction. Un nouveau fichier. Pas de chemins. Pas de compression. Pas de nouvelle syntaxe. Pas de magie. Extensible. AccĂšs en lecture seule via le lecteur (mais ouvert Ă  d'autres modĂšles d'accĂšs Ă  l'avenir). PossibilitĂ© quasi nulle de casser les packages existants. Le package reste la construction de base dans go.

__Edit__ AprÚs une recherche github language:go filename:go.res extension:res , il semble que go.res serait un nom de fichier assez sûr à utiliser. Il n'y a pas de correspondances dans les repos go, et seulement quelques-uns dans les repos non go.

J'aime l'idée de @chris.ackermanm. Mais je préférerais une combinaison :

Un fichier go.res spécifiant l'espace de noms dans un répertoire.

Cela permet de

  • multiple comprend tant que l'espace de noms diffĂšre
  • ne pas connaĂźtre les fichiers avant et devoir gĂ©nĂ©rer une liste

Ce dernier devrait s'attaquer à la sortie de webpack et autres qui peuvent changer la mise en page en raison des mises à jour, des différentes options, tout ce à quoi vous pouvez penser.

En ce qui concerne la compression : je pense que c'est plus une fonctionnalitĂ© en termes de non-explosion des tailles binaires et devrait ĂȘtre transparente pour le code d'utilisation.

Plus tard, vous pourrez autoriser des réécritures telles que

filename => stored-as.png

Juste mon 2Âą

@sascha-andres On dirait de l'ultra simplicité et zéro magie est le ton de ce fil. Voir les modifications que j'ai apportées à mon commentaire concernant vos suggestions.

Je n'aime pas la cartographie. Ce n'est pas nécessaire. C'est possible en exposant de toute façon votre propre fonction de lecture à partir d'un package séparé, et maintenant nous avons besoin d'une nouvelle syntaxe de fichier, ou de quelque chose de plus complexe que le fichier par ligne.

salut

Cette proposition est géniale !

Et j'ai mon approche pour intégrer des actifs. pas besoin d'introduire d'autres outils que GNU bintools. C'est un peu sale, mais ça marche bien pour moi pour le moment. Je veux juste le partager et voir si cela aide.

mon approche consiste simplement à intégrer mes actifs (compressés avec tar&gz) dans une section elf/pe32 avec objcopy, et à les lire via les packages debug/elf et debug/pe32 avec zip si nécessaire. tout ce dont je dois me souvenir, c'est de ne toucher à aucune section existante. tous les actifs sont immuables puis le code lit le contenu et le traite en mémoire.

Je suis assez inexpérimenté en conception de langage ou en conception de compilateur. donc j'utiliserais simplement l'approche décrite ci-dessus et utiliserais .goassets ou quelque chose comme ça comme nom de section. et rendre la compression facultative.

mon approche consiste simplement à intégrer mes actifs (compressés avec tar&gz) dans une section elf/pe32 avec objcopy, et à les lire via les packages debug/elf et debug/pe32 avec zip si nécessaire. tout ce dont je dois me souvenir, c'est de ne toucher à aucune section existante. tous les actifs sont immuables puis le code lit le contenu et le traite en mémoire.

On dirait que ça marche sur elf / pe32 mais qu'en est-il de mach-o / plan9 ?

Un autre problÚme est qu'il repose sur l'ouverture d'un descripteur de fichier sur l'exécutable, si l'exécutable a été écrasé/mis à jour/supprimé, cela renverra des données différentes, sans savoir s'il s'agit d'un problÚme légitime ou d'une fonctionnalité inattendue.

J'ai essayĂ© moi-mĂȘme (en utilisant debug/macho ), mais je ne vois pas comment faire fonctionner cette multiplateforme, je construis sur macOS et les binutils GNU installĂ©s semblent juste corrompre le mach-o-x86-64 (cela pourrait simplement ĂȘtre mon manque de comprĂ©hension de la structure mach-o et trop long puisque j'ai mĂȘme regardĂ© objcopy ).

Un autre problÚme est qu'il repose sur l'ouverture d'un descripteur de fichier sur l'exécutable

Je suis presque sûr que le chargeur de programme chargera (ou pourrait) charger la section des ressources en mémoire, il n'est donc pas nécessaire d'utiliser des packages de débogage. Bien que l'accÚs aux données nécessiterait beaucoup plus de bricolage avec les fichiers objets que cela n'en vaut la peine.

Pourquoi ne pas suivre ce qui fonctionne -- par exemple comment Java le fait. J'aurais besoin que les choses soient un gros go-ish, mais quelque chose dans les lignes:

  • crĂ©er un fichier go.res ou modifier go.mod pour pointer vers le rĂ©pertoire oĂč se trouvent les ressources
  • tous les fichiers de ce rĂ©pertoire sont automatiquement inclus, aucune exception par le compilateur dans l'exĂ©cutable final
  • language fournit une API de type chemin pour accĂ©der Ă  ces ressources

La compression, etc. doit ĂȘtre en dehors de la portĂ©e de ce regroupement de ressources et peut aller jusqu'Ă  n'importe quel script // go:generate si nĂ©cessaire.

Quelqu'un a-t-il regardé markbates/pkger ? C'est une solution assez simple d'utiliser go.mod comme répertoire de travail actuel. En supposant qu'un index.html soit intégré, son ouverture serait pkger.Open("/index.html") . Je pense que c'est une meilleure idée que de coder en dur un répertoire static/ dans le projet.

Il convient également de mentionner que Go n'a pas d'exigences de structure significatives pour un projet d'aprÚs ce que j'ai pu voir. go.mod n'est qu'un fichier et peu de gens utilisent vendor/ . Personnellement, je ne pense pas qu'un répertoire static/ serait bon.

Comme nous avons dĂ©jĂ  un moyen d'injecter des donnĂ©es (bien que limitĂ©es) dans une version via le drapeau de lien ldflags existant -X importpath.name=value , ce chemin de code pourrait-il ĂȘtre ajustĂ© pour accepter -X importpath.name=@filename Ă  injecter donnĂ©es arbitraires externes ?

Je me rends compte que cela ne couvre pas tous les objectifs énoncés du numéro d'origine, mais en tant qu'extension de la fonctionnalité -X existante, cela semble-t-il un pas en avant raisonnable ?

(Et si cela fonctionne, alors l'extension de la syntaxe go.mod comme moyen plus ordonné de spécifier les valeurs ldflags -X est une prochaine étape raisonnable ?)

C'est une idée trÚs intéressante, mais je m'inquiÚte des implications pour la sécurité.

C'est assez courant de faire -X 'pkg.BuildVersion=$(git rev-parse HEAD)' , mais nous ne voudrions pas laisser go.mod exécuter des commandes arbitraires, n'est-ce pas ? (Je suppose que go generate le fait, mais ce n'est pas quelque chose que vous exécutez généralement pour les packages OSS téléchargés.) Si go.mod ne peut pas gérer cela, il manque un cas d'utilisation majeur, donc les ldflags seraient toujours trÚs courants.

Ensuite, il y a l'autre problĂšme de s'assurer que @filename n'est pas un lien symbolique vers /etc/passwd ou autre.

L'utilisation de l'Ă©diteur de liens empĂȘche la prise en charge de WASM et Ă©ventuellement d'autres cibles qui n'utilisent pas d'Ă©diteur de liens.

Sur la base de la discussion ici, @bradfitz et moi avons Ă©laborĂ© une conception qui se situe quelque part au milieu des deux approches considĂ©rĂ©es ci-dessus, en prenant ce qui semble ĂȘtre le meilleur de chacune. J'ai publiĂ© un projet de document de conception, une vidĂ©o et un code (liens ci-dessous). Au lieu de commentaires sur ce problĂšme, veuillez utiliser les questions-rĂ©ponses de Reddit pour des commentaires sur ce projet de conception spĂ©cifique - Reddit gĂšre les discussions et les met Ă  l'Ă©chelle mieux que GitHub. Merci!

Vidéo : https://golang.org/s/draft-embed-video
Conception : https://golang.org/s/draft-embed-design
Questions et réponses : https://golang.org/s/draft-embed-reddit
Code : https://golang.org/s/draft-embed-code

@rsc À mon avis, la proposition go:embed est infĂ©rieure Ă  l'exĂ©cution de code Go _universel_ en bac Ă  sable au moment de la compilation, ce qui inclurait la lecture des fichiers et la transformation des donnĂ©es lues dans un _format optimal_ le mieux adaptĂ© Ă  la consommation au moment de l'exĂ©cution.

@atomsymbol Cela ressemble Ă  quelque chose qui sort du cadre de ce problĂšme.

@atomsymbol Cela ressemble Ă  quelque chose qui sort du cadre de ce problĂšme.

Je suis au courant de ça.

J'ai lu la proposition et scanné le code, mais je n'ai pas trouvé de réponse à cette question : ce schéma d'intégration contiendra-t-il des informations sur le fichier sur le disque (~os.Stat) ? Ou ces horodatages seront-ils réinitialisés en fonction de l'heure de construction ? Quoi qu'il en soit, ce sont des informations utiles qui sont utilisées à divers endroits, par exemple, nous pouvons envoyer un 304 pour les actifs inchangés en fonction de cela.

Merci!

Edit: trouvé dans le fil reddit.

L'heure de modification pour tous les fichiers intĂ©grĂ©s est l'heure zĂ©ro, pour exactement les problĂšmes de reproductibilitĂ© que vous avez Ă©numĂ©rĂ©s. (Les modules n'enregistrent mĂȘme pas les heures de modification, encore une fois pour la mĂȘme raison.)

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

Quoi qu'il en soit, ce sont des informations utiles qui sont utilisées à divers endroits, par exemple, nous pouvons envoyer un 304 pour les actifs inchangés en fonction de cela.

Un en-tĂȘte ETag basĂ© sur le hachage des donnĂ©es du fichier rĂ©soudrait ce problĂšme sans avoir Ă  connaĂźtre quoi que ce soit sur les dates. Mais cela devrait ĂȘtre connu par http.HandlerFS ou quelque chose pour pouvoir fonctionner et pour ne pas gaspiller de ressources, cela ne devrait ĂȘtre fait qu'une seule fois par fichier.

Mais cela devrait ĂȘtre connu par http.HandlerFS ou quelque chose pour pouvoir fonctionner et pour ne pas gaspiller de ressources, cela ne devrait ĂȘtre fait qu'une seule fois par fichier.

Comment http.HandlerFS saurait-il que le fichier fs.FS était immuable ? Devrait-il y avoir une interface optionnelle IsImmutable() bool ?

Comment http.HandlerFS saurait-il que le fichier fs.FS était immuable ? Devrait-il y avoir une interface optionnelle IsImmutable() bool ?

Je ne veux pas entrer dans les dĂ©tails de l'implĂ©mentation car je ne suis pas le concepteur de ces choses, mais http.HandlerFS pourrait vĂ©rifier s'il s'agit d'un type embed.FS et agir sur cela comme un cas particulier, je ne pense pas que quiconque veuille le faire dĂ©veloppez l'API FS dĂšs maintenant. Il pourrait Ă©galement y avoir un argument d'option Ă  HandlerFS spĂ©cifiquement pour lui dire de traiter un systĂšme de fichiers comme immuable. De plus, si cela est fait au dĂ©marrage de l'application et que tous les ctime/mtime ont une valeur nulle, handlerFS pourrait utiliser ces informations pour "savoir" que le fichier n'a pas changĂ©, mais il existe Ă©galement des systĂšmes de fichiers qui pourraient ne pas avoir mtime ou le dĂ©sactiver. peut-ĂȘtre aussi des problĂšmes lĂ -bas.

Je ne regardais pas les commentaires sur cette question.

@atomsymbol bon retour ! C'est super de te revoir commenter ici.
Je suis d'accord sur le principe que si nous avions du sandboxing, beaucoup de choses seraient plus faciles.
D'un autre cĂŽtĂ©, beaucoup de choses peuvent ĂȘtre plus difficiles - les builds peuvent ne jamais se terminer.
Quoi qu'il en soit, nous n'avons certainement pas ce genre de sandbox aujourd'hui. :-)

@kokes je ne suis pas sûr des détails,
mais nous veillerons à ce que la diffusion d'un embed.Files sur HTTP obtienne les ETags correctement par défaut.

J'ai déposé le numéro 41191 pour avoir accepté le projet de conception publié en juillet.
Je vais clore ce sujet comme remplacé par celui-là.
Merci pour la grande discussion préliminaire ici.

Cette page vous a été utile?
0 / 5 - 0 notes