Go: предложение: cmd / go: поддержка встраивания статических ресурсов (файлов) в двоичные файлы

Созданный на 4 дек. 2019  ·  176Комментарии  ·  Источник: golang/go

Существует множество инструментов для встраивания файлов статических ресурсов в двоичные файлы:

На самом деле https://tech.townsourced.com/post/embedding-static-files-in-go/ перечисляет больше:

Предложение

Думаю, пора сделать это один раз и уменьшить дублирование, добавив официальную поддержку встраивания файловых ресурсов в инструмент cmd / go.

Проблемы с текущей ситуацией:

  • Слишком много инструментов
  • Использование решения на основе go: generate раздувает историю git второй (и немного большей) копией каждого файла.
  • Не использовать go: generate означает, что вы не можете использовать go install или заставлять людей писать свои собственные файлы Makefile и т. Д.

Цели:

  • не проверять сгенерированные файлы
  • вообще не генерировать файлы * .go (по крайней мере, не в рабочей области пользователя)
  • make go install / go build выполнить встраивание автоматически
  • позволить пользователю выбирать для каждого файла / глобуса, какой тип доступа требуется (например, [] байт, func() io.Reader , io.ReaderAt и т. д.)
  • Может быть, хранить активы, сжатые в двоичном формате, где это необходимо (например, если пользователю нужен только io.Reader )? ( изменить : но, вероятно, нет; см. комментарии ниже)
  • Отсутствие выполнения кода во время компиляции ; это давняя политика Go. go build или go install не могут запускать произвольный код, так же как go:generate не запускается автоматически во время установки.

Два основных подхода к реализации - это //go:embed Logo logo.jpg или известный пакет ( var Logo = embed.File("logo.jpg") ).

перейти: внедрить подход

Для подхода go:embed можно сказать, что любой файл go/build -selected *.go может содержать что-то вроде:

//go:embed Logo logo.jpg

Что, скажем, компилируется в:

func Logo() *io.SectionReader

(добавление зависимости к пакету io )

Или:

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

компиляция, скажем:

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

Очевидно, что это не совсем конкретизировано. Для сжатых файлов тоже должно быть что-то, что давало бы только io.Reader .

внедрить пакетный подход

Другой высокоуровневый подход - не иметь волшебного синтаксиса //go:embed и вместо этого просто позволить пользователям писать код Go в каком-нибудь новом пакете "embed" или "golang.org/x/foo/embed" :

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

Затем пусть cmd / go распознает вызовы embed.Foo ("foo / *. Js") и т.д., а glob выполняет работу в cmd / go, а не во время выполнения. Или, может быть, определенные теги сборки или флаги могут заставить его вместо этого вернуться к работе во время выполнения. Perkeep (ссылка выше) имеет такой режим, который удобен для ускорения инкрементальной разработки, когда вам не нужно связывать один большой двоичный файл.

Обеспокоенность

  • Выберите стиль (// go: embed * vs a magic package).
  • Заблокировать определенные файлы?

    • Вероятно, заблокировать встраивание ../../../../../../../../../../etc/shadow

    • Возможно также заблокировать доступ к .git

Proposal Proposal-Hold

Самый полезный комментарий

Мы с @robpike обсудили предложение об этом много лет назад (до того, как был процесс предложения), и так и не вернулись к чему-либо. Уже много лет меня беспокоит, что мы так и не закончили делать это. Идея, насколько я помню, заключалась в том, чтобы просто иметь специальное имя каталога, такое как «static», содержащее статические данные, и автоматически делать их доступными через API без аннотаций.

Я не уверен в сложности ручки «сжатый или нет». Если мы это сделаем, люди захотят, чтобы мы добавили контроль над сжатием, уровнем сжатия и т. Д. Все, что нам нужно добавить, - это возможность встроить файл из простых байтов. Если пользователи хотят хранить сжатые данные в этом файле, отлично, детали зависят от них, и на стороне Go вообще не требуется API.

Все 176 Комментарий

Стоит подумать, должно ли embedglob поддерживать полное дерево файлов, возможно, используя синтаксис ** поддерживаемый некоторыми оболочками Unix.

Некоторым людям потребуется возможность обслуживать встроенные ресурсы с помощью HTTP с помощью http.FileServer .

Я лично использую либо mjibson / esc (который делает это), либо в некоторых случаях мою собственную реализацию встраивания файлов, которая переименовывает файлы для создания уникальных путей и добавляет карту из исходных путей к новым, например, "/js/bootstrap.min.js": "/js/bootstrap.min.827ccb0eea8a706c4c34a16891f84e7b.js" . Затем вы можете использовать эту карту в шаблонах следующим образом: href="{{ static_path "/css/bootstrap.min.css" }}" .

Я думаю, что следствием этого было бы то, что было бы нетривиально выяснить, какие файлы необходимы для создания программы.

Подход //go:embed вводит другой уровень сложности. Вам нужно будет проанализировать волшебные комментарии, чтобы даже проверить тип кода. Подход «встроенного пакета» кажется более удобным для статического анализа.

(Здесь просто размышляю вслух.)

@opennota ,

потребуется возможность обслуживать встроенные ресурсы с помощью HTTP с помощью http.FileServer .

Да, первая ссылка выше - это пакет, который я написал ( в 2011 году, до Go 1 ) и все еще использую, и он поддерживает использование http.FileServer: https://godoc.org/perkeep.org/pkg/fileembed#Files.Open

@cespare ,

Подход // go: embed также вводит другой уровень сложности. Вам нужно будет проанализировать волшебные комментарии, чтобы даже проверить тип кода. Подход «встроенного пакета» кажется более удобным для статического анализа.

Да, хорошее замечание. Это очень веский аргумент в пользу использования пакета. Это также делает его более читаемым и документируемым, поскольку мы можем документировать все это с помощью обычного godoc, а не глубоко в документации cmd / go.

@bradfitz - Вы хотите закрыть этот https://github.com/golang/go/issues/3035 ?

@agnivade , спасибо, что нашли это! Я думал, что помню это, но не мог найти. Давайте пока оставим его открытым и посмотрим, что думают другие.

Если мы пойдем с волшебным пакетом, мы могли бы использовать уловку с неэкспортированным типом, чтобы гарантировать, что вызывающие объекты передают константы времени компиляции в качестве аргументов: https://play.golang.org/p/RtHlKjhXcda.

(Это стратегия, указанная здесь: https://groups.google.com/forum/#!topic/golang-nuts/RDA9Hag8RZw/discussion)

У меня есть одно беспокойство: как он будет обрабатывать отдельные или все активы, слишком большие для размещения в памяти, и будет ли, возможно, тег сборки или опция доступа для каждого файла, чтобы выбирать между оценкой времени доступа и объемом памяти или какой-либо промежуточной реализацией.

способ, которым я решил эту проблему (потому что, конечно, у меня также есть собственная реализация :)), - это предоставить реализацию http.FileSystem, которая обслуживает все встроенные ресурсы. Таким образом, вы не должны полагаться на волшебные комментарии, чтобы успокоить проверщика типов, ресурсы можно легко обслуживать с помощью http, резервная реализация может быть предоставлена ​​для целей разработки (http.Dir) без изменения кода, а последний реализация довольно универсальна, поскольку http.FileSystem довольно много охватывает не только чтение файлов, но и перечисление каталогов.

Можно по-прежнему использовать магические комментарии или что-то еще, чтобы указать, что нужно встроить, хотя, вероятно, проще указать все глобусы через простой текстовый файл.

@AlexRouSg Это предложение

@ianlancetaylor , я думаю, что @AlexRouSg проводил различие между предоставлением файлов как глобальным []byte s (незастраиваемая, потенциально доступная для записи память) и предоставлением только для чтения, по требованию просмотра раздела ELF, который может обычно находятся на диске (в исполняемом файле), например, через вызов Open который возвращает *io.SectionReader . (Я не хочу запекать http.File или http.FileSystem в cmd / go или runtime ... net / http может предоставить адаптер.)

@bradfitz как http.File, так и сам по себе является интерфейсом без каких-либо технических зависимостей от пакета http . Для любого метода Open может быть хорошей идеей предоставить реализацию, соответствующую этому интерфейсу, потому что оба метода Stat и Readdir весьма полезны для таких ресурсов.

@urandom ,

Мы с @robpike обсудили предложение об этом много лет назад (до того, как был процесс предложения), и так и не вернулись к чему-либо. Уже много лет меня беспокоит, что мы так и не закончили делать это. Идея, насколько я помню, заключалась в том, чтобы просто иметь специальное имя каталога, такое как «static», содержащее статические данные, и автоматически делать их доступными через API без аннотаций.

Я не уверен в сложности ручки «сжатый или нет». Если мы это сделаем, люди захотят, чтобы мы добавили контроль над сжатием, уровнем сжатия и т. Д. Все, что нам нужно добавить, - это возможность встроить файл из простых байтов. Если пользователи хотят хранить сжатые данные в этом файле, отлично, детали зависят от них, и на стороне Go вообще не требуется API.

Пара мыслей:

  • Не должно быть возможности внедрить какой-либо файл вне модуля, выполняющего внедрение. Нам нужно убедиться, что файлы являются частью zip-файлов модуля, когда мы их создаем, так что это также означает отсутствие символических ссылок, конфликтов регистров и т. Д. Мы не можем изменить алгоритм, который создает zip-файлы, не разбивая суммы.
  • Я думаю, что проще ограничить встраивание в том же каталоге (если используются комментарии //go:embed ) или в конкретном подкаталоге (если используется static ). Это значительно упрощает понимание взаимосвязи между пакетами и встроенными файлами.

В любом случае это блокирует вложение /etc/shadow или .git . Ни то, ни другое не может быть включено в застежку-молнию модуля.

В общем, я беспокоюсь о том, чтобы слишком сильно расширить сферу действия команды go. Однако тот факт, что существует так много решений этой проблемы, означает, что, вероятно, должно быть одно официальное решение.

Я знаком с go_embed_data и go-bindata (из которых есть несколько вилок), и, похоже, это покрывает эти варианты использования. Есть ли какие-то важные проблемы, которые решают другие, но которых это не касается?

Блокировка определенных файлов не должна быть слишком сложной, особенно если вы используете каталог static или embed . Символьные ссылки могут немного усложнить это, но вы можете просто предотвратить встраивание чего-либо за пределами текущего модуля или, если вы используете GOPATH, за пределами пакета, содержащего каталог.

Я не особо люблю комментарии, которые компилируются в код, но я также считаю, что псевдопакет, влияющий на компиляцию, тоже немного странный. Если подход каталогов не используется, возможно, имеет смысл иметь какое-то объявление верхнего уровня embed фактически встроенное в язык. Он будет работать аналогично import , но будет поддерживать только локальные пути и потребует присвоения имени. Например,

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

Изменить: вы меня опередили, @jayconrod.

Чтобы расширить https://github.com/golang/go/issues/35950#issuecomment -561703346, есть загадка, связанная с открытым API. Очевидные способы раскрытия данных - это интерфейсы []byte , string и Read .

Типичный случай состоит в том, что вы хотите, чтобы встроенные данные были неизменными. Однако все интерфейсы, предоставляющие []byte (включая io.Reader , io.SectionReader и т. Д.), Должны либо (1) делать копию, (2) разрешать изменяемость, либо (3) быть неизменным, несмотря на то, что он []byte . Представление данных как string s решает эту проблему, но за счет API, который часто в конечном итоге требует копирования, поскольку для большого количества кода, потребляющего встроенные файлы, в конечном итоге так или иначе требуются байтовые срезы.

Я бы предложил маршрут (3): быть неизменным, несмотря на то, что он []byte . Вы можете обеспечить это дешево, используя символ только для чтения для резервного массива. Это также позволяет безопасно предоставлять те же данные, что и []byte и string ; попытки изменить данные потерпят неудачу. Компилятор не может воспользоваться неизменностью, но это не слишком большая потеря. Это то, что может показать поддержка инструментальной цепочки, чего (насколько мне известно) ни один из существующих пакетов кодогенерации не делает.

(Сторонний пакет codegen может сделать это, сгенерировав общий файл сборки, содержащий символы DATA , помеченные как доступные только для чтения, а затем короткие файлы сборки, специфичные для архитектуры, отображающие эти символы в форме string s и []byte s. Я написал CL 163747 специально с учетом этого удосужился интегрировать его в какие-либо пакеты кодогенерации.)

Я не уверен, о чем вы говорите с точки зрения неизменности. io.Reader уже обеспечивает неизменяемость. В этом вся суть. Когда вы вызываете Read(buf) , он копирует данные в буфер, который вы предоставили. Изменение buf после этого не оказывает никакого влияния на внутреннюю структуру io.Reader .

Я согласен с @DeedleFake. Я не хочу играть в игры с магическим массивом []byte . Можно копировать из двоичного файла в буферы, предоставленные пользователем.

Еще одна морщина - у меня есть другой проект, который использует исходный код DTrace (встроенный). Это чувствительно к различиям между \ n и \ r \ n. (Мы можем спорить, является ли это тупой в DTrace или нет - это не относится к делу, и такая ситуация сложилась сегодня.)

Очень полезно, что строки с обратными кавычками обрабатывают оба символа как \ n независимо от того, как они появляются в исходном коде, и я полагаюсь на это с помощью go-generate для встраивания DTrace.

Поэтому, если к команде go добавлен файл для встраивания, я бы мягко предположил, что параметры для изменения обработки CR / CRLF могут оказаться очень полезными, особенно для людей, которые могут разрабатывать в разных системах, где окончание строк по умолчанию может быть Попался.

Как и в случае со сжатием, я бы очень хотел остановиться на «копировании байтов файла в двоичный файл». CR / CRLF нормализация, Unicode нормализация, gofmt'ing, все, что относится к другому. Зарегистрируйте файлы, содержащие именно те байты, которые вам нужны. (Если ваш контроль версий не может оставить их в покое, возможно, зарегистрируйте содержимое, сжатое с помощью gzip, и заархивируйте его во время выполнения.) Есть _много__ регуляторов для изменения файлов, которые мы могли бы добавить. Остановимся на 0.

Может быть, слишком поздно вводить новое зарезервированное имя каталога, как бы мне ни хотелось.
(Еще в 2014 году было не слишком поздно, но, наверное, уже слишком поздно.)
Так что может потребоваться какой-то дополнительный комментарий.

Предположим, мы определяем тип runtime.Files. Тогда вы могли представить, что пишете:

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

А затем во время выполнения вы просто вызываете files.Open, чтобы вернуть interface { io.ReadSeeker; io.ReaderAt } с данными. Обратите внимание, что эта переменная не экспортируется, поэтому один пакет не может копаться во встроенных файлах другого пакета.

Названия TBD, но что касается механизма, кажется, что этого должно быть достаточно, и я не вижу, как сделать его проще. (Упрощения, конечно, приветствуются!)

Что бы мы ни делали, нужно иметь возможность поддерживать и Базель, и Газель. Это означало бы, что Gazelle распознает комментарий и напишет правило Bazel, говорящее о глобусах, а затем нам нужно будет открыть инструмент (go tool embedgen или что-то еще), чтобы сгенерировать дополнительный файл для включения в сборку (команда go будет делайте это автоматически и никогда не показывайте лишний файл). Это кажется достаточно простым.

Если различные манипуляции не помогут, то это аргумент против использования этого нового средства. Для меня это не препятствие - я могу использовать go generate, как и раньше, но это означает, что я не могу извлечь выгоду из новой функции.

Что касается манипуляций в целом - я могу представить решение, в котором кто-то предоставляет реализацию интерфейса (что-то вроде Reader () с одной стороны и что-то для получения файла с другой - возможно, созданное с помощью io.Reader из самого файла), которые команда go создаст и запустит для предварительной фильтрации файла перед встраиванием. Затем люди могут предоставить любой фильтр, который они хотят. Я предполагаю, что некоторые люди предоставят квазистандартные фильтры, такие как реализация dos2unix, сжатие и т. Д. (Может быть, они даже должны быть объединены в цепочку.)

Я предполагаю, что должно быть предположение, что каким бы ни был встроенный процессор, он должен быть скомпилирован в ~ каждой системе сборки, поскольку go будет создавать временный собственный инструмент для этой цели.

Может быть, слишком поздно вводить новое зарезервированное имя каталога, как бы мне ни хотелось. [...] может потребоваться какой-то дополнительный комментарий.

Если файлы доступны только через специальный пакет, скажем runtime/embed , то импорт этого пакета может быть сигналом согласия.

Подход io.Read кажется, что он может добавить значительные накладные расходы (с точки зрения как копирования, так и объема памяти) для концептуально простых линейных операций, таких как strings.Contains (например, в cmd/go/internal/cfg ) или , критически, template.Parse .

Для этих случаев использования кажется идеальным позволить вызывающей стороне выбрать, рассматривать ли весь BLOB-объект целиком как (предположительно с отображением в памяти) string или io.ReaderAt .

Однако это кажется совместимым с общим подходом runtime.Files : вещь, возвращаемая из runtime.Files.Open может иметь метод ReadString() string который возвращает представление с отображением в память.

может потребоваться какой-то дополнительный комментарий.

Мы могли бы сделать это с помощью версии go в файле go.mod . Перед 1.15 (или чем-то еще) подкаталог static будет содержать пакет, а при 1.15 или выше он будет содержать встроенные активы.

(Впрочем, это не очень помогает в режиме GOPATH .)

Я не уверен в сложности ручки «сжатый или нет». Если мы это сделаем, люди захотят, чтобы мы добавили контроль над сжатием, уровнем сжатия и т. Д. Все, что нам нужно добавить, - это возможность встроить файл из простых байтов.

Хотя я ценю стремление к простоте, мы также должны убедиться, что удовлетворяем потребности пользователей.

12 из 14 инструментов, перечисленных на https://tech.townsourced.com/post/embedding-static-files-in-go/#comparison, поддерживают сжатие, что говорит о том, что это довольно распространенное требование.

Это правда, что можно было бы выполнить сжатие в качестве этапа предварительной сборки за пределами go, но для этого все равно потребуется 1) инструмент для сжатия 2) проверка какого-то assets.zip blob в vcs 3) вероятно, утилита библиотека вокруг встроенного API, чтобы отменить сжатие. В какой момент вообще неясно, в чем заключается выгода.

В первоначальном предложении были перечислены три цели:

  • не проверять сгенерированные файлы
  • make go install / go build сделать встраивание автоматически
  • хранить активы, сжатые в двоичном формате, где это необходимо

Если мы прочитаем вторую из них как «не требуется отдельный инструмент для встраивания», то отказ от поддержки сжатых файлов прямо или косвенно не соответствует всем трем целям.

Это должен быть уровень пакета? Уровень модуля кажется более детализированным, поскольку, скорее всего, один модуль = один проект.

Поскольку в этом каталоге не будет кода Go †, может ли это быть что-то вроде _static ?

† или, если это так, он будет обрабатываться как произвольные байты, имя которых заканчивается на ".go", а не как код Go, который будет скомпилирован.

Если это один специальный каталог, логика могла бы просто захлебнуть все и вся в этом дереве каталогов. Пакет magic embed может позволить вам сделать что-то вроде embed.Open("img/logo.svg") чтобы открыть файл в подкаталоге дерева ресурсов.

Струны кажутся достаточно хорошими. Их можно легко скопировать в []byte или преобразовать в Reader . Генерация кода или библиотеки могут использоваться для предоставления более изящных API и обработки вещей во время init . Это может включать распаковку или создание http.FileSystem .

В Windows нет специального формата для встраивания ресурсов. Следует ли это использовать при создании исполняемого файла Windows? Если да, то имеет ли это какое-либо значение для видов операций, которые могут быть предоставлены?

Не забывайте gitfs 😂

Есть ли причина, по которой он не может быть частью go build / link ... например, go build -embed example=./path/example.txt и некоторый пакет, который предоставляет доступ к нему (например, embed.File("example") вместо использования go:embed ?

вам нужна заглушка для этого в вашем коде, хотя

@egonelbre проблема с go build -embed том, что все пользователи должны использовать его правильно. Это должно быть полностью прозрачным и автоматическим; существующие команды go install или go get не могут перестать действовать правильно.

@bradfitz Я бы порекомендовал https://github.com/markbates/pkger вместо Packr. Он использует стандартную библиотеку API для работы с файлами.

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
}

Или, может быть, определенные теги сборки или флаги могут заставить его вместо этого вернуться к работе во время выполнения. Perkeep (ссылка выше) имеет такой режим, который удобен для ускорения инкрементальной разработки, когда вам не нужно связывать один большой двоичный файл.

mjibson / esc также делает это, и это большое улучшение качества жизни при разработке веб-приложений; вы не только экономите время связывания, но и избегаете перезапуска приложения, что может занять значительное время и / или потребовать повторения дополнительных шагов для проверки ваших изменений, в зависимости от реализации веб-приложения.

Проблемы с текущей ситуацией:

  • Использование решения на основе go: generate раздувает историю git второй (и немного большей) копией каждого файла.

Цели:

  • не проверять сгенерированные файлы

Что ж, эту часть легко решить, просто добавив сгенерированные файлы в файл .gitignore или аналогичный. Я всегда так делал ...

Таким образом, в качестве альтернативы Go мог бы просто иметь свой собственный «официальный» инструмент для встраивания, который по умолчанию запускается на go build и просит людей игнорировать эти файлы по соглашению. Это было бы менее волшебное решение (и обратно совместимое с существующими версиями Go).

Я просто размышляю / думаю вслух ... но мне в целом нравится предложенная идея. 🙂

Кроме того, поскольку директивы //go:generate не запускаются автоматически на go build поведение go build может показаться немного противоречивым: //go:embed будет работать автоматически, но для //go:generate нужно запустить go generate вручную. ( //go:generate уже может прервать поток go get если он генерирует файлы .go необходимые для сборки).

//go:generate уже может прервать поток go get, если он генерирует .go файлы, необходимые для сборки

Я думаю, что обычный поток для этого, и тот, который я обычно использовал, хотя к нему пришлось немного привыкнуть, - это использовать go generate полностью как инструмент для завершения разработки и просто фиксировать файлы, которые он порождает.

@bradfitz не нужно реализовывать сам http.FileSystem . Если реализация предоставляет тип, реализующий http.File , то было бы тривиально для любого, включая http-пакет stdlib, предоставить оболочку вокруг функции Open , преобразовав тип в http.File для соответствия http.FileSystem

Однако @andreynering //go:generate и //go:embed очень разные. Этот механизм может работать без проблем во время сборки, потому что он не запускает произвольный код. Я считаю, что это делает его похожим на то, как cgo может генерировать код как часть go build .

Я не уверен в сложности ручки «сжатый или нет». Если мы это сделаем, люди захотят, чтобы мы добавили контроль над сжатием, уровнем сжатия и т. Д. Все, что нам нужно добавить, - это возможность встроить файл из простых байтов.

Хотя я ценю стремление к простоте, мы также должны убедиться, что удовлетворяем потребности пользователей.

12 из 14 инструментов, перечисленных на https://tech.townsourced.com/post/embedding-static-files-in-go/#comparison, поддерживают сжатие, что говорит о том, что это довольно распространенное требование.

Я не уверен, что согласен с этим рассуждением.

Сжатие, выполняемое другими библиотеками, отличается от добавления его в это предложение тем, что они не снизят производительность при последующих сборках, поскольку альтернативы, как правило, генерируются перед сборкой, а не во время сборки.

Низкое время сборки - очевидная добавленная стоимость с переходом на другие языки, а сжатие использует процессорное время для уменьшения объема хранилища / передачи. Если многие пакеты Go начинают выполнять сжатие на go build мы собираемся добавить еще больше времени сборки, чем время, добавленное простым копированием ресурсов во время сборки. Я скептически отношусь к добавлению сжатия, потому что это делают другие. До тех пор, пока первоначальный дизайн не препятствует будущему расширению, которое добавляет поддержку, например, сжатие, размещение его там, потому что это может быть чем-то, что могло бы принести пользу некоторым, кажется ненужным хеджированием.

Не похоже, что встраивание файлов было бы бесполезным без сжатия, сжатие - это хорошая вещь, чтобы уменьшить двоичный размер, возможно, со 100 МБ до 50 МБ - и это здорово, но также не является явным нарушением функциональности для большинства приложений, о которых я могу думать. . Особенно, если большинство «более тяжелых» ресурсов представляют собой файлы, такие как JPEG или PNG, которые уже достаточно хорошо сжаты.

Как насчет того, чтобы на данный момент не использовать сжатие и добавить его, если многие люди его упускают? (и может быть сделано без неоправданных затрат)

Чтобы добавить к комментарию @sakjur выше: сжатие кажется мне ортогональным. Обычно я хочу сжать весь двоичный архив или архив релизов, а не только активы. В частности, когда двоичные файлы Go в Go могут легко попасть в десятки мегабайт без каких-либо ресурсов.

@mvdan Я думаю, одна из моих проблем заключается в том, что довольно часто, когда я видел, что встраивание происходит вместе с другой предварительной обработкой: минификация, компиляция машинописного текста, сжатие данных, сжатие изображения, изменение размера изображения, спрайт-листы. Единственным исключением являются веб-сайты, которые используют только html/template . Так что, в конце концов, вы все равно можете использовать какой-то «Makefile» или загрузить предварительно обработанный контент. В этом смысле я думаю, что флаг командной строки лучше работает с другими инструментами, чем с комментариями.

Думаю, одна из моих проблем заключается в том, что довольно часто, когда я вижу, что встраивание выполняется вместе с другой предварительной обработкой: минификация, компиляция машинописного текста, сжатие данных, сжатие изображения, изменение размера изображения, спрайт-листы. Единственным исключением являются веб-сайты, использующие только html / template.

Спасибо, это полезные данные. Возможно, необходимость в сжатии встречается не так часто, как казалось. Если это так, я согласен, что имеет смысл оставить это в стороне.

Не похоже, что встраивание файлов было бы бесполезным без сжатия, сжатие - это хорошая вещь, чтобы уменьшить двоичный размер, возможно, со 100 МБ до 50 МБ - и это здорово, но также не является явным нарушением функциональности для большинства приложений, о которых я могу думать. .

Двоичный размер имеет большое значение для многих разработчиков go (https://github.com/golang/go/issues/6853). Go сжимает отладочную информацию DWARF специально для уменьшения двоичного размера, хотя это требует затрат времени на компоновку (https://github.com/golang/go/issues/11799, https://github.com/golang/go/ выпусков / 26074). Если бы существовал простой способ уменьшить размер двоичного файла вдвое, я думаю, разработчики ухватились бы за эту возможность (хотя я сомневаюсь, что выигрыш здесь был бы почти таким значительным).

Это не очень помогает в режиме GOPATH, хотя

Может быть, если вы находитесь в режиме GOPATH, эта функция просто не применима, поскольку я полагаю, что команда Go не планирует навсегда выполнять паритет функций для GOPATH? Уже есть функции, которые не поддерживаются в GOPATH (такие как безопасность с контрольной суммой db, загрузка зависимостей через прокси-сервер и управление версиями семантического импорта)

Как упоминалось в @bcmills , наличие статического имени каталога в файле go.mod - отличный способ представить эту функцию в Go 1.15, поскольку эта функция может быть автоматически отключена в файлах go.mod, в которых есть предложение <= go1.14.

Тем не менее, это также означает, что пользователи должны вручную указать путь к статическому каталогу.

Я думаю, что каталог vendor и соглашения _test.go - отличные примеры того, как они значительно упростили работу с Go и этими двумя функциями.

Я не помню, чтобы многие люди запрашивали возможность настройки имени каталога поставщика или имели возможность изменить соглашение _test.go на что-то другое. Но если Go никогда не представит функцию _test.go, то тестирование в Go сегодня будет выглядеть иначе.

Поэтому, возможно, имя менее общее, чем static дает больше шансов избежать конфликтов, и поэтому наличие обычного каталога (похожего на vendor и _test.go) может быть лучше для пользователя по сравнению с волшебными комментариями.

Примеры имен с потенциально низким уровнем коллизий:

  • _embed - следует соглашению _test.go
  • go_binary_assets
  • .gobin следует соглашению .git
  • runtime_files - чтобы он соответствовал структуре runtime.Files

Наконец, в Go 1.5 был добавлен каталог vendor . Ооочень, может быть, не так уж плохо сейчас добавить новую конвенцию? 😅

Я думаю, он должен выставить mmap-readonly []byte . Просто необработанный доступ к страницам из исполняемого файла, загружаемый ОС по мере необходимости. Все остальное может быть предоставлено вдобавок всего за bytes.NewReader .

Если по какой-то причине это неприемлемо, укажите ReaderAt не просто ReadSeeker ; последнее легко построить из первого, но другой способ не так хорош: потребуется мьютекс для защиты единственного смещения и снижения производительности.

Не похоже, что встраивание файлов было бы бесполезным без сжатия, сжатие - это хорошая вещь, чтобы уменьшить двоичный размер, возможно, со 100 МБ до 50 МБ - и это здорово, но также не является явным нарушением функциональности для большинства приложений, о которых я могу думать. .

Размер двоичного файла имеет большое значение для многих разработчиков го (# 6853). Go сжимает отладочную информацию DWARF специально для уменьшения двоичного размера, хотя это требует затрат времени на компоновку (# 11799, # 26074). Если бы существовал простой способ уменьшить размер двоичного файла вдвое, я думаю, разработчики ухватились бы за эту возможность (хотя я сомневаюсь, что выигрыш здесь был бы почти таким значительным).

Это определенно справедливый аргумент, и я понимаю, что мой аргумент можно рассматривать как аргумент в пользу небрежности в отношении размеров файлов. Это не было моим намерением. Моя точка зрения больше соответствует отправке этой функции без сжатия, которая все еще будет полезна для некоторых, и они могут предоставить полезные отзывы и идеи о том, как правильно добавить сжатие таким образом, чтобы это выглядело правильным в долгосрочной перспективе. Ресурсы могут увеличиваться таким образом, что отладочная информация вряд ли будет иметь место, и разработчикам пакетов, которые устанавливаются / импортируются другими, проще без надобности снижать производительность сборки, если реализация упрощает это.

Другой вариант - сделать сжатие ресурсов флагом сборки и оставить компромисс между размером и временем сборки на усмотрение сборщика, а не разработчика. Это приблизило бы решение к конечному пользователю двоичного файла, который мог бы принять решение о целесообразности сжатия. Ото, это может привести к увеличению площади поверхности для различий между разработкой и производством, поэтому это явно не лучший метод, чем что-либо еще, и я не чувствую, что хотел бы отстаивать его.

Мой текущий инструмент встраивания ресурсов загружает контент из файлов ресурсов при сборке с помощью -tags dev . Некоторые подобные соглашения, вероятно, были бы полезны и здесь; он значительно сокращает цикл разработки, например, при работе с HTML или шаблоном.

В противном случае вызывающий должен будет обернуть этот механизм нижнего уровня некоторыми оболочками *_dev.go и *_nodev.go и реализовать невстроенную загрузку для сценария dev . Даже не сложно, но этот путь просто приведет к тому же взрыву инструментов, который описан в первом комментарии по этой проблеме. Этим инструментам придется делать меньше, чем сегодня, но они все равно умножатся.

Я думаю, что -tags dev не работающее при запуске вне модуля Go, было бы разумным (не могу понять, откуда загружать ресурсы).

А как насчет просто go tool embed который принимает входные данные и создает выходные файлы Go в специальном формате, распознаваемом компьютером как встроенные файлы, к которым затем можно получить доступ через runtime/emved или что-то в этом роде. Тогда вы могли бы просто сделать простой //go:generate gzip -o - static.txt | go tool embed -o static.go .

Конечно, большим недостатком является то, что вам нужно будет зафиксировать сгенерированные файлы.

@DeedleFake эта проблема началась с

Использование решения на основе go: generate раздувает историю git второй (и немного большей) копией каждого файла.

Вупс. Неважно. Извините.

Не похоже, что встраивание файлов было бы бесполезным без сжатия, сжатие - это хорошая вещь, чтобы уменьшить двоичный размер, возможно, со 100 МБ до 50 МБ - и это здорово, но также не является явным нарушением функциональности для большинства приложений, о которых я могу думать. .

Размер двоичного файла имеет большое значение для многих разработчиков го (# 6853). Go сжимает отладочную информацию DWARF специально для уменьшения двоичного размера, хотя это требует затрат времени на компоновку (# 11799, # 26074). Если бы существовал простой способ уменьшить размер двоичного файла вдвое, я думаю, разработчики ухватились бы за эту возможность (хотя я сомневаюсь, что выигрыш здесь был бы почти таким значительным).

Если в этом есть необходимость, тогда люди будут фиксировать и внедрять сжатые данные, и будут пакеты, обеспечивающие уровень между runtime.Embed и конечным потребителем, который выполняет распаковку на лету.

А через год или два появится новый вопрос о добавлении сжатия, и тогда его можно будет отсортировать.

Я сказал это как один из 15 конкурирующих стандартов, когда писал goembed :)

@ tv42 написал:

Я думаю, он должен выставить mmap-readonly []byte . Просто необработанный доступ к страницам из исполняемого файла, загружаемый ОС по мере необходимости.

Этот комментарий очень легко пропустить, и он невероятно ценный.

@ tv42 ,

Я думаю, он должен предоставить байт mmap-readonly []. Просто необработанный доступ к страницам из исполняемого файла, загружаемый ОС по мере необходимости. Все остальное может быть предоставлено сверх этого, используя только bytes.NewReader.

Тип, который уже доступен только для чтения, - string . Также: он предоставляет размер, в отличие от io.ReaderAt , и не зависит от стандартной библиотеки. Вероятно, это то, что мы хотим разоблачить.

Тип, который уже доступен только для чтения, - string .

Но вся экосистема Write т.д. работает на []byte . Это простой прагматизм. Я не считаю, что свойство readonly представляет собой большую проблему, чем io.Writer.Write docs, говорящие

Запись не должна изменять данные среза, даже временно.

Другой потенциальный недостаток заключается в том, что при встраивании каталога с помощью go:generate я могу проверить вывод git diff и посмотреть, есть ли там какие-либо файлы по ошибке. С этим предложением -? Возможно, команда go распечатает список встраиваемых файлов?

@ tv42

Но вся экосистема Write и т.д. работает с [] байтом.

html/template работает со строками.

Go уже позволяет использовать -ldflags -X для установки некоторых строк (полезно для установки версии git, времени компиляции, сервера, пользователя и т. Д.), Можно ли расширить этот механизм для установки io.Readers вместо строк?

@bradfitz Вы предлагаете использовать здесь строки даже для данных, которые не являются текстом? Достаточно распространено встраивание небольших двоичных файлов, таких как значки, небольшие изображения и т. Д.

@ tv42 Вы сказали Write но я предполагаю, что вы имели в виду Read . Вы можете превратить string в io.ReaderAt используя strings.NewReader , поэтому использование строки не кажется здесь препятствием.

@andreynering string может содержать любую последовательность байтов.

string может содержать любую последовательность байтов.

Да, но его основная цель - хранить текст, а не произвольные данные. Полагаю, это может вызвать некоторую путаницу, особенно для неопытных разработчиков Go.

Однако я полностью понял идею. Спасибо за разъяснение.

@ianlancetaylor

Read должен изменять переданный фрагмент. Write - нет. Следовательно, в документации Write сказано, что это запрещено. Я не вижу необходимости больше, чем документировать, что пользователи не должны писать в возвращенный []byte .

То, что strings.Reader существует, не означает, что io.WriteString найдет эффективную реализацию записи строк. Например, в TCPConn нет WriteString .

Я бы не хотел, чтобы Go включал новую функцию, подобную этой, только для принудительного копирования всех данных только для записи их в сокет.

Кроме того, общее предположение состоит в том, что строки могут быть распечатаны человеком, а []byte часто - нет. Помещение JPEG в строки вызовет много путаницы в терминалах.

@opennota

Однако html / template работает со строками.

Да, это странно, он принимает файлы только по пути, а не как читатели. Два ответа:

  1. Нет причин, по которым встроенные данные не могут иметь оба метода Bytes() []byte и String() string .

  2. Надеюсь, вы не разбираете шаблон каждый раз при каждом запросе; тогда как вам действительно нужно отправлять данные JPEG в сокет TCP для каждого запроса, который его запрашивает.

@ tv42 При необходимости мы можем добавить WriteString методы.

Я не думаю, что наиболее распространенным использованием этой функции будет запись неизмененных данных, поэтому я не думаю, что мы должны оптимизировать для этого случая.

Я не думаю, что наиболее частым использованием этой функции будет запись неизмененных данных,

Я думаю, что наиболее распространенным использованием этой функции будет обслуживание веб-ресурсов, изображений / js / css, без изменений.

Но не верьте мне на слово, давайте посмотрим на некоторых импортеров файловой базы Брэда:

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



md5-f8b48fccd03599094034bf2b507e9e67



#fileembed pattern .*\.js$

И так далее..

Для анекдотических данных: я знаю, что если бы это было реализовано, я бы сразу использовал его в двух местах на работе, и оба были бы для обеспечения неизмененного доступа к статическим текстовым файлам. Прямо сейчас мы используем шаг //go:generate для преобразования файлов в постоянные строки (в шестнадцатеричном формате).

Я бы проголосовал за новый пакет, а не за директиву. Намного легче взять в руки, легче обрабатывать / управлять и намного проще документировать и расширять. например. Можно ли легко найти документацию для директивы Go, такой как «go: generate»? А как насчет документации для пакета «fmt»? Вы понимаете, к чему я клоню?

Таким образом, в качестве альтернативы Go может иметь свой собственный «официальный» инструмент для встраивания, который по умолчанию запускается на go build

@andreynering Я знаю, что другие менеджеры пакетов и языковые инструменты позволяют это, но запуск произвольного кода / команд во время сборки - это уязвимость безопасности (по очевидным причинам, я надеюсь).

Когда я думаю об этой функции, мне приходят на ум еще две вещи:

  • Каким образом встраиваемые файлы будут автоматически работать с кешем сборки?
  • Мешает ли это воспроизводимым построениям? Если данные каким-либо образом изменяются (например, при сжатии), следует учитывать воспроизводимость.

stuffbin , ссылка на который приведена в первом комментарии, была создана, в первую очередь, для того, чтобы позволить

За исключением обсуждения компиляции / сжатия, другой проблемой является отсутствие абстракции файловой системы в stdlib, потому что:

  • На машине разработчика многочисленные go run s и сборки не должны обременяться накладными расходами на встраивание (при необязательном сжатии) ресурсов. Абстракция файловой системы позволила бы легко переключиться на локальную файловую систему во время разработки.

  • Активы могут активно меняться во время разработки, например, полный интерфейс Javascript в веб-приложении. Возможность плавного переключения между встроенной и локальной файловой системой вместо встроенных ресурсов позволит избежать компиляции и повторного запуска двоичного файла Go только из-за изменения ресурсов.

Изменить: В заключение, если бы пакет внедрения мог предоставить интерфейс, подобный файловой системе, что-то лучше, чем http.FileSystem, это решило бы эти проблемы.

Возможность плавного переключения между внедрением и локальной файловой системой

Конечно, это можно реализовать на уровне приложений, и это выходит за рамки этого предложения, не так ли?

Конечно, это можно реализовать на уровне приложений, и это выходит за рамки этого предложения, не так ли?

Извините, я только что понял, я сформулировал это неоднозначно. Я предлагал не реализацию файловой системы внутри пакета для встраивания, а просто интерфейс, что-то лучше, чем http.FileSystem . Это позволило бы приложениям реализовать любую абстракцию.

Изменить: опечатка.

@knadh Полностью согласен, что это должно работать, когда вы просто используете go run , способ, которым Packr справляется с этим, действительно хорош. Он знает, где находятся ваши файлы, если они не встроены в приложение, а затем загружает их с диска, поскольку ожидает, что это будет в основном «режим разработки».

Автор Packr также выпустил новый инструмент Pkger, ориентированный на модули Go. Все файлы относятся к корню модуля Go. Мне очень нравится эта идея, но похоже, что Pkger не реализовал локальную загрузку с диска. Комбинация того и другого была бы потрясающей, ИМО.

Я не знаю, выпала ли она уже из работы, но, хотя «подход к внедрению пакета» довольно волшебен, он также дает некоторую удивительность, потому что инструмент может определить, что делать с файлом на основе вызова. например, API может быть чем-то вроде

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

Если инструмент go находит вызов FileReaderAt , он знает, что данные должны быть несжатыми. Если он находит только вызовы FileReader , он знает, что может хранить сжатые данные. Если он находит вызов FileBytes , он знает, что ему нужно сделать копию, если он находит только FileString , он знает, что он может работать из памяти только для чтения. И так далее.

Я не уверен, что это разумный способ реализовать это для самого инструмента go. Но я хотел упомянуть об этом, так как он позволяет получить преимущества сжатия и встраивания с нулевым копированием, не имея при этом никаких фактических регуляторов.

[edit] также, конечно, это позволяет нам добавить эти дополнительные искажения постфактум, сосредоточившись вначале на более минимальном наборе функций [/ edit]

Если он находит только вызовы FileReader ...

Это исключило бы использование других методов через отражение.

[Edit] На самом деле, я думаю, что последствия шире, чем это. Если использование FileReaderAt указывает на то, что данные должны быть несжатыми, то использование FileReaderAt() с любыми входными данными, отличными от const подразумевает, что все файлы должны храниться в несжатом виде.

Не знаю, хорошо это или плохо. Я просто думаю, что магическая эвристика будет не так полезна, как может показаться на первый взгляд.

Один аргумент в пользу прагмы comment ( //go:embed ) вместо специального имени каталога ( static/ ): комментарий позволяет нам встроить файл в тестовый архив для пакета (или в архив xtest ), но не тестируемую библиотеку. Комментарий просто должен появиться в файле _test.go .

Я ожидаю, что это решит общую проблему с модулями: трудно получить доступ к тестовым данным для другого пакета, если этот пакет находится в другом модуле. Пакет может предоставлять данные для других тестов с комментарием типа //go:embedglob testdata/* в файле _test.go . Пакет может быть импортирован в обычный нетестовый двоичный файл, не извлекая эти файлы.

@ fd0 ,

Каким образом встраиваемые файлы будут автоматически работать с кешем сборки?

Это все равно будет работать. Хэши содержимого встроенных файлов будут смешаны с ключом кеша.

Возможно ли (или даже хорошая идея) иметь модуль / пакет / механизм, который был бы практически прозрачным, так как внутри вашего приложения вы просто пытаетесь открыть путь, например

internal://static/default.css

и файловые функции будут читать данные из двоичного файла или из альтернативного места
ex Package.Mount("internal[/<folder>.]", binary_path + "/resources/")

чтобы создать "internal: //" со всеми файлами в двоичном файле, вернуться к исполняемому пути / ресурсам /, если в режиме разработки или если файл не найден в двоичном (и, возможно, выдать предупреждение или что-то еще для целей ведения журнала)

Это позволило бы, например, иметь

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

Вероятно, лучше всего заблокировать альтернативное расположение пути к исполняемому файлу в режиме выпуска, но ослабьте это в режиме разработки (разрешите только папки в go_path или что-то в этом роде)

По умолчанию пакет «монтирует» internal: // или какое-то другое ключевое слово, но позволяет пользователю переименовать его, если он / она хочет .. например .ReMount («internal», «myCustomName») или что-то в этом роде.

Другое дело ... имеет ли смысл проверять время последнего изменения / модификации в альтернативном месте и автоматически переопределять внутренний файл, если такой файл есть вне приложения (возможно, есть флаг, позволяющий это, настраиваемый программистом перед сборкой)
Это может потребоваться для сверхбыстрых патчей приложений, где вы не хотите ждать, пока будет создана и распространена новая сборка .. вы можете просто создать папку и скопировать туда файл, и двоичный файл переключится на новый файл.

В Windows возможно ли или имеет смысл использовать ресурсы (как в двоичном двоичном объекте данных в ресурсе)
И немного не связанный, но, может быть, этот пакет также может иметь дело с объединением значков в исполняемый файл, или данные манифеста, или, может быть, даже другие ресурсы? Я понимаю, что это только Windows ...
Я бы предположил, что построитель может регистрировать даты последнего изменения / изменения файлов в альтернативных папках и запускать «создание большого двоичного объекта данных» только при изменении файла и кэшировать его где-то.
Может быть, только создать файл «кеша», если пользователь выбирает включить сжатие для этих связанных файлов (если он решил в конечном итоге сжать их) ... если выбрано сжатие, только конкретный файл, который был изменен, должен быть повторно сжат во время сборки , другие файлы будут просто скопированы в двоичный файл из кеша.

Одна проблема, которую я вижу, заключается в том, что если пакет допускает настраиваемые имена, он должен иметь какой-то черный список, например, не разрешать «udp, file, ftp, http, https и другие популярные ключевые слова»

Что касается хранения в виде байтового массива / строки или сжатия ... imho, какое бы решение ни было принято, оно должно оставлять место для легкого обновления в будущем ... например, вы можете начать без сжатия и просто иметь список смещений и размеров файлов и имена файлов, но позволяют легко добавлять сжатие в будущем (например, метод zlib, lzma, сжатый размер, несжатый размер, если необходимо выделить достаточно памяти для распаковки фрагментов и т. д.

Я лично был бы счастлив, если бы исполняемый файл можно было упаковать UPX или его эквивалентом, я предполагаю, что двоичный файл будет распакован в память, и все будет работать.

Несколько мыслей, имеющих косвенное отношение:

  • Мне нравится подход package embed за его использование синтаксиса Go
  • Я думаю, что необходимость сжатия и других манипуляций связана не с размером двоичного файла, а с желанием сохранить в репозитории только наиболее удобную для различий форму контента, чтобы не было состояния "рассинхронизация", когда кто-то забывает регенерировать и зафиксировать сжатую форму при изменении «исходного кода», и чтобы пакет оставался «go gettable». Не обращая внимания на эти моменты, мы решаем только проблему стандартизации, что может быть приемлемо, но не кажется идеальным.
  • Я думаю, мы могли бы обойти необходимость в инструментальной цепочке, которая должна активно поддерживать конкретное сжатие / преобразования, если взаимодействие embed может опционально предоставлять «кодек». Как именно определить кодек, зависит от синтаксиса интеграции, но я представляю себе что-то вроде
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
}

Это может охватывать очень конкретные варианты использования, например этот надуманный:

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
}

Использование этого могло бы выглядеть как

// go:embed file.name NewJSONShrinker

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

или возможно

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

Во второй форме возникает сложность, заключающаяся в том, что инструментальная цепочка должна статически понимать, какой кодек использовать, потому что он должен выполнить шаг Encode во время компиляции. Поэтому нам пришлось бы запретить любое значение кодека, которое не может быть легко определено во время компиляции.

Учитывая эти два варианта, я бы выбрал волшебный комментарий плюс кодеки. Это приводит к более мощной функции, которая решает все заявленные здесь цели. К тому же, я не думаю, что магические комментарии здесь недопустимы. Мы уже терпим их через go:generate для этой цели прямо сейчас. Во всяком случае, один только волшебный пакет можно было бы рассматривать как отход от текущих идиом. Экосистема Go прямо сейчас не имеет многих функций, которые позволяют исходному файлу инструктировать инструментальную цепочку использовать дополнительные исходные файлы, и я думаю, что единственное, что сейчас не является волшебным комментарием, - это ключевое слово import .

Если мы сделаем сжатие, не будет никаких регуляторов типа кодека или уровня сжатия. То есть наличие каких-либо регуляторов - самый большой аргумент в пользу отказа от сжатия вообще.

Единственный выбор, который я хотел бы предложить, если он есть, - это произвольный доступ или нет. Если вам не нужен произвольный доступ, инструменты и среда выполнения могут выбрать любое подходящее сжатие и не раскрывать его пользователям. И, вероятно, со временем он изменится / улучшится.

Но я столкнулся с тем, что @rsc не использует сжатие из-за того, что я понял : самый сжимаемый контент (HTML, JS, CSS и т. обслуживаться, скажем, через http.FileServer , который поддерживает запросы диапазона)

И если посмотреть на объединенный размер HTML / CSS / JS Perkeep, который мы встраиваем, он составляет 48 КБ без сжатия. Размер двоичного файла Perkeep составляет 49 МБ. (Я игнорирую размер встроенных изображений, потому что они уже сжаты.) Так что, похоже, это того не стоит, но его можно было бы добавить позже.

Из обсуждения с @rsc кажется, что мы могли бы использовать сочетание вышеперечисленных подходов:

Во время выполнения пакета

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

Затем в вашем коде:

package yourcode

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

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

Затем инструмент cmd / go проанализирует комментарии go:embed и объединит эти шаблоны + хэш этих файлов и зарегистрирует их во время выполнения, используя &website .

Среда выполнения могла бы эффективно иметь карту каждого адреса файлов с тем, каково его содержимое и где они находятся в исполняемом файле (или каковы их имена разделов ELF / etc). И, возможно, поддерживают ли они произвольный доступ или нет, если мы в конечном итоге сделаем какое-либо сжатие.

@gdamore ,

Еще одна морщина - у меня есть другой проект, который использует исходный код DTrace (встроенный). Это чувствительно к различиям между n и rn.
...
Если различные манипуляции не помогут, то это аргумент против использования этого нового средства.

Вы также можете изменить во время выполнения, чтобы удалить любые символы возврата каретки, которые внедряются пользователями Windows, запускающими go install. Я писал этот фильтр io.Reader пару раз.

Но я столкнулся с тем, что @rsc не использует сжатие из-за того, что я

Сжатие и произвольный доступ не являются полностью взаимоисключающими. См., Например, некоторое обсуждение здесь: https://stackoverflow.com/questions/429987/compression-formats-with-good-support-for-random-access-within-archives

Сжатие и произвольный доступ не являются полностью взаимоисключающими

Да, если бы мы хотели грубый поиск с некоторыми накладными расходами, чтобы добраться до нужной позиции. Я поработал в этом пространстве с форматом stargz CRFS . Но я боюсь, что накладные расходы будут настолько большими, что мы не захотим делать это автоматически для людей. Я полагаю, вы также можете лениво накачать его в память (и иметь возможность сбросить его на сборщики мусора, например, sync.Pool), но это просто не стоит того.

Я боюсь, что накладные расходы будут настолько большими, что мы не захотим делать это автоматически для людей.

Справедливо. Важный вопрос заключается в том, предпочтем ли мы API, который позволит нам без больших затрат изменить наше мнение об этом позже, если потребуются изменения или если эксперименты покажут, что накладные расходы приемлемы.

@bradfitz хороший замечание. И я, безусловно, могу это сделать. FWIW, в моем репо я также настроил git, чтобы он был менее токсичным при просмотре файлов .d. Тем не менее, я считаю свойство встроенных строк с обратными кавычками полезным, поскольку оно предсказуемо и не зависит от прихотей git или системы.

С идеей кодека я пришел к выводу, что сжатие - не единственное преобразование, которое может понадобиться, и что предоставляемый пользователем тип кодека позволяет инструментальной цепочке игнорировать флаги, кроме «какой кодек». Любые уровни сжатия или алгоритм вообще, сжатие или иное, должны быть специфичными для используемого кодека. Я полностью согласен с тем, что попытка «поддерживать сжатие» в смысле предоставления определенного набора форматов и регуляторов была бы дикой погоней со всеми вариациями, которые люди могут попросить. На самом деле, я был бы больше всего в восторге от необычных применений, таких как, возможно, предварительная обработка данных i18n или обработка наборов данных, например, в latlong , поэтому я думаю, что все же стоит рассмотреть варианты, связанные с этим.

Я придумал другой способ обеспечить такую ​​же гибкость, который мог бы быть более приятным. Директива // go:embed может быть вызовом команды, как и // go:generate . В простейшем случае что-то вроде

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

Ключевым отличием, конечно же, является то, что стандартный вывод вызова команды встроен под указанное имя. В примере также используется фиктивный пакет с go run чтобы показать, как это, вероятно, сделать, чтобы сделать команду независимой от ОС, поскольку cat может быть недоступен везде, где компилируется Go.

Это касается этапа «кодирования» преобразования, и, возможно, задача этапа «декодирования» может быть оставлена ​​на усмотрение пользователя. Пакет runtime / embed может просто предоставить байты, которые пользователь попросил встроить в инструментальную цепочку, независимо от кодировки. Это нормально, потому что пользователь знает, каким должен быть процесс декодирования.

Одним из больших недостатков этого является то, что я не вижу хорошего способа встроить таким образом группу из нескольких файлов, помимо встроенных байтов, являющихся zip или чем-то в этом роде. На самом деле этого может быть достаточно, поскольку глобус все еще может использоваться командой zip, и это на определяющей стороне, где вы действительно заботитесь о глобусе. Но мы также могли бы включить в это предложение две функции: одну для простого встраивания, а другую для запуска генератора-встраивания.

Один из возможных недостатков, который пришел мне на ум, заключается в том, что он добавляет открытый шаг в сборку, предполагая, что встраивание должно обрабатываться go build и не требовать дополнительного вызова инструментальной цепочки, как это делает go generate . Я думаю, что это нормально. Возможно, можно ожидать, что инструмент будет управлять своим собственным кешем, чтобы избежать повторения дорогостоящих операций, или, возможно, он может взаимодействовать с цепочкой инструментов для использования кеша Go. Это похоже на проблему, которую можно решить, и она соответствует общей теме: go build делает для нас больше (например, выбор модулей).

Является ли одна из целей этого проекта - гарантировать, что сборки Go не требуют внешних инструментов и линий go: generate?

В противном случае, кажется, стоит сохранить простоту и поддерживать только байтовый фрагмент или строку, потому что, если пользователь хочет сжатие с большим количеством ручек, он может сделать это в своем файле make (или аналогичном), сгенерировать строку и т. Д., Прежде чем в любом случае создавать, так что, кажется, не стоит добавлять их к чему бы ни закончился результат этого предложения.

Если целью является отсутствие требования Make или аналогичного, то, я полагаю, имеет смысл использовать сжатие, но лично я бы сразу же использовал Make, go generate и т. Д., Чтобы выполнить сжатие, а затем сохраните простую вставку и просто вставьте несколько байтов .

@SamWhited ,

Является ли одна из целей этого проекта - гарантировать, что сборки Go не требуют внешних инструментов и линий go: generate?

да.

Если люди хотят использовать go: generate, Makefiles или другие инструменты, сегодня у них есть десятки вариантов.

Нам нужно что-то портативное, безопасное и правильное, работающее по умолчанию. (и для ясности: безопасность означает, что мы не можем запускать произвольный код во время установки, по той же причине, что и go: generate не запускается по умолчанию)

@ stephens2424

Я думаю, что мы могли бы обойти необходимость в инструментальной цепочке, которая должна активно поддерживать конкретное сжатие / преобразования, если взаимодействие с внедрением может дополнительно предоставлять «кодек».

Не выполняется произвольный код во время go build .

Во время сборки go не выполняется произвольный код.

Ага, теперь я это вижу. Я полагаю, что нет никакого способа согласовать наличие только "исходных" файлов, переданных в репо, с желанием встроить "обработанные" файлы, чтобы пакет был "go gettable" _и_ сохранял go build простым и безопасным. Я по-прежнему выступаю за стандартизацию здесь, но, полагаю, я надеялся съесть свой торт и тоже его съесть. Стоит попробовать! Спасибо, что уловили проблему!

@flimzy

Это исключило бы использование других методов через отражение.

В том, что я упомянул, нет методов, только функции. Они не обнаруживаются во время выполнения, и нет возможности ссылаться на них, не упоминая их по имени в исходном коде. И обратите внимание, что значения интерфейса, возвращаемые разными функциями, не обязательно должны быть одного и того же типа - действительно, я ожидал бы, что они будут либо неэкспортируемыми типами с точно таким же методом, который требуется для реализации этого интерфейса, либо экземпляром *strings.Reader и т. д., все, что имеет смысл в контексте.

Возможно, однако, идея страдает от передачи экспортируемых функций пакета внедрения в качестве значений. Хотя даже это, вероятно, не будет проблемой - подпись содержит неэкспортированный тип (см. Ниже), поэтому вы не можете объявить переменную, аргумент или возвращаемый тип. Теоретически вы можете передать их самим reflect.ValueOf . Я даже не знаю, позволит ли это вам на самом деле вызывать их (вам все равно придется создать значение их типа параметра, которое не экспортируется. Не знаю, если отражение позволяет это).

Но как бы то ни было: все равно можно (и проще всего) просто быть пессимистичным в случае, если какая-либо функция верхнего уровня embed используется в качестве значения и предполагает ограничения, которые она создает для всех встроенных файлов. Это будет означать, что если вы решите делать чрезвычайно странные и бесполезные вещи с помощью пакета embed-package, вы потеряете некоторые оптимизации (в любом случае мы не обязательно даем какие-либо обещания). Кажется, справедливо.

На самом деле, я думаю, что последствия шире, чем это. Если использование FileReaderAt указывает на то, что данные должны быть несжатыми, то использование FileReaderAt () с любым неконстантным вводом подразумевает, что все файлы должны храниться в несжатом виде.

Нет смысла разрешать неконстантные входы, так как имя файла должно быть известно статически, чтобы выполнить встраивание. С моей стороны было неточно использовать string в качестве типа параметров имени файла: они действительно должны были быть неэкспортируемыми type filename string и использоваться только как аргументы функции. Таким образом, невозможно передать что-либо, кроме нетипизированной строковой константы.

@Merovius

Нет смысла разрешать неконстантные входы

Я думаю, мы говорим о разных вещах. Я имею в виду входные данные для функций доступа (т.е. FileReaderAt() ). Я уверен, что вы согласитесь, что неконстантный ввод имеет смысл.

И моя точка зрения: предположим, мы встроили 100 файлов, но у нас есть вызов FileReaderAt(filename) , где filename не является постоянным; нет способа узнать, к каким (если они вообще есть) из встроенных файлов будет получен доступ таким образом, поэтому все должны храниться в несжатом виде.

@flimzy, мы говорили об одном и том же, я просто серьезно не думал, что неконстантные имена файлов будут иметь смысл :) Что, если подумать, было неправильным и упущением. Прости за это. Да, возможности для глобализации или включения целых каталогов, а затем итерации по ним, на самом деле очень важны. Тем не менее думаю, что это можно решить - например, приняв решение для каждой коллекции (dir / glob) и разрешив выбирать только их по постоянным именам, - но, как я уже сказал: на самом деле это не API, который я считаю супер подходящим для инструмента Go из-за как это волшебство. Таким образом, подобный подход к сорнякам, вероятно, дает концепции больше места в обсуждении, чем она того заслуживает :)

Еще один случай, который я не видел в предыдущих сообщениях и который заставил меня задуматься о встраивании файла в двоичный файл Go, - это невозможность правильно распространить пакет-оболочку общей библиотеки C с помощью обычной сборки / установки go (общая библиотека остается в источники).

В конце концов, я этого не сделал, но это определенно заставит меня пересмотреть это в данном случае. У библиотеки C действительно много зависимостей, которые было бы проще распространять как разделяемую библиотеку. Эта общая библиотека может быть встроена привязками Go.

Вот это да!!!

@ Хулио-Герра
Я совершенно уверен, что вам все равно придется распаковать их на диск, а затем использовать dlopen и dlsym для вызова функций C.

Изменить: немного неправильно понял ваш пост, просто понял, что вы говорите о создании двоичного файла для распространения

Вне статических ресурсов http для встроенных BLOB-объектов, которые вам нужны в указателе в памяти, было бы неплохо иметь функцию, которая возвращала бы указатель на уже находящуюся в процессе встроенную память. В противном случае пришлось бы выделить новую память и сделать копию из io.Reader. Это потребовало бы вдвое больше памяти.

@glycerine , опять же, это string . string - указатель и длина.

Было бы здорово иметь какой-то способ пометить код для выполнения во время компиляции и предоставить результат во время выполнения. Таким образом, вы могли читать любой файл, сжимать его, если хотите, во время компиляции и во время выполнения, вы могли получить к нему доступ. Это сработает для некоторых вычислений, как для предварительной загрузки содержимого файла.

@burka, как было сказано ранее в потоке, go build не будет запускать произвольный код.

@burka , это явно выходит за рамки. Это решение (отсутствие выполнения кода во время компиляции) было принято давно, и это не ошибка, которая меняет эту политику.

Побочным эффектом этого предложения является то, что прокси-серверы go никогда не могут оптимизировать файлы, которые они хранят, чтобы быть только файлами go. Прокси-сервер должен хранить весь репозиторий, потому что он не будет знать, включает ли код Go какие-либо файлы, отличные от Go.

Я не знаю, оптимизированы ли уже прокси для этого, но когда-нибудь они могут захотеть это сделать.

@leighmcculloch Я не думаю, что сегодня это так. Любые файлы, не относящиеся к Go, в пакете Go должны быть включены в архивы модулей, поскольку они могут потребоваться для go test . У вас также могут быть файлы C для cgo, в качестве другого примера.

Это захватывающее направление, оно нам определенно нужно для наших сценариев использования.

Тем не менее, я чувствую, что есть разные варианты использования с разными требованиями, но большинство из тех, кто комментирует _ как_ они думают, что это должно быть сделано, неявно представляют свои собственные варианты использования, но не определяют их явно.

Было бы полезно - по крайней мере, было бы действительно полезно для меня - если бы мы могли очертить различные варианты использования для решения для встраивания файлов и проблемы, которые представляет каждый вариант использования.

Например, наш основной вариант использования - это встраивание HTML + CSS + JS + JPG + и т. Д., Чтобы при запуске приложения go оно могло записывать эти файлы в каталог, чтобы их мог обслуживать http.FileServer . Учитывая этот вариант использования, большинство комментариев, которые я прочитал при обсуждении читателей и писателей, были для меня чужими, потому что нам не нужен доступ к файлам из Go, мы просто позволяем go-bindata копировать их на диск _ (хотя возможно, есть способ использовать лучшие методы, которые мы просто еще не осознали, и мы должны рассмотреть их.) _

Но наши проблемы заключаются в следующем: мы обычно используем GoLand с его отладчиком и будем работать над веб-приложением, постоянно внося изменения. Поэтому во время разработки нам понадобится http.FileServer для загрузки файлов прямо из исходного каталога. Но когда приложение запускается, http.FileServer необходимо прочитать эти файлы из каталога, в который они были записаны решением для встраивания. Это означает, что при компиляции мы должны запустить go-bindata для обновления файлов, а затем зарегистрировать их в Git. И все это обычно работает с go-bindata , хотя, конечно, не идея.

Однако в других случаях нам нужно фактически запустить скомпилированный исполняемый файл, чтобы мы могли присоединить отладчик к запущенной программе, и при этом эта программа по-прежнему загружала файлы из исходного каталога, а не из каталога, в который встроенные файлы записываются с помощью go-bindata . В настоящее время у нас нет подходящего решения для этого.

Таковы наши варианты использования и проблемы. Может быть, другие могли бы явно определить другие варианты использования и связанный с ними набор проблем, чтобы эти обсуждения могли явно затронуть различные проблемные области и / или явно обозначить, что эти усилия не будут отвечать конкретным потребностям данного проблемного пространства?

Заранее благодарим за рассмотрение.

Поскольку я не рассматриваю это как вариант использования, мы также выиграем от этого для нашего каталога шаблонов, к которому мы получаем доступ через template.ParseFiles.

Я бы нашел самый чистый подход - выбрать через go.mod . Это обеспечило бы обратную совместимость (поскольку существующие проекты должны были бы согласиться на его использование) и позволило бы инструментам (таким как прокси-серверы go) определять, какие файлы необходимы. Команду go mod init можно обновить, включив в нее версию по умолчанию для новых проектов, чтобы упростить ее использование в будущем.

Я вижу аргументы в пользу того, чтобы каталог был стандартным именем (если нам требуется согласие, тогда это может быть более чистое / более простое имя) или чтобы имя каталога было определено в самом go.mod и позволяло пользователям выберите имя (но по умолчанию предоставлено go mod init .

На мой взгляд, подобное решение обеспечивает баланс между простотой использования и меньшим количеством «магии».

@jayconrod написал:

Один аргумент в пользу прагмы comment (// go: embed) вместо специального имени каталога (static /): комментарий позволяет нам вставлять файл в тестовый архив для пакета (или в архив xtest), но не в библиотеку под тестом.

Это действительно хорошее наблюдение. Хотя, если бы мы хотели использовать специальное имя каталога, мы могли бы использовать знакомый механизм: static для всех сборок, static_test для тестовых сборок, static_amd64 для сборок amd64 и скоро. Однако я не вижу очевидного способа обеспечить поддержку произвольных тегов сборки.

В статическом каталоге может быть файл манифеста (по умолчанию, когда задан пустой манифест, включает все, кроме манифеста), который включает глобусы и позволяет указывать теги сборки и, возможно, более позднее сжатие и т. Д.

Один положительный момент заключается в том, что если go list попадает в каталог, содержащий манифест, он может пропустить это дерево a la # 30058

Одним из недостатков является то, что он может получить очень много htacces, и нет, спасибо

Простым механизмом с нулевой ручкой для объединения файлов в пакет может быть специальный каталог go.files в каталоге пакета (аналогично go.mod в модуле). Доступ будет ограничен этим пакетом, если он не решит экспортировать символ.

Изменить: однофункциональное предложение runtime/files :

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

Подход import "C" уже создал прецедент для "волшебных" путей импорта. ИМО, это сработало довольно хорошо.

Поскольку я не рассматриваю это как вариант использования, мы также выиграем от этого для нашего каталога шаблонов, к которому мы получаем доступ через template.ParseFiles.

Есть еще одна проблема: хотя двоичный файл может содержать все необходимые файлы, эти самые файлы будут значениями по умолчанию, которые я, как разработчик, предоставляю. Однако такие шаблоны, как, например, выходные данные или политика конфиденциальности, должны настраиваться конечным пользователем. Насколько я понимаю, это означает, что должен быть какой-то способ экспортировать мои файлы по умолчанию, а затем либо позволить двоичному файлу использовать настроенные файлы во время выполнения, либо какой-то способ заменить встроенные версии настроенными.

Я думаю, что это можно сделать, предоставив API с функциями для «экспорта» и «замены» встроенного ресурса. Затем разработчик может предоставить конечному пользователю некоторые параметры командной строки (используя внутри упомянутые вызовы API).

Все это, конечно, основано на предположении, что на самом деле будет какое-то встраивание, которое определенно облегчит развертывание.

Спасибо, что открыли вопрос. На работе мы думали об одной и той же идее функции, поскольку нам нужно встраивать файлы практически в каждый проект Golang. Существующие библиотеки работают нормально, но я думаю, что это функция, о которой кричит Голанг. Это язык, созданный для превращения в один статический двоичный файл. Он должен принять это, позволяя нам загружать необходимые файлы ресурсов в двоичный файл с помощью удобного для разработчиков и универсального API.

Я просто хочу быстро рассказать о моих предпочтительных деталях реализации. Многие люди говорили об автоматическом предоставлении API для чтения встроенных файлов вместо необходимости другого сигнала, такого как волшебный комментарий. Я думаю, что это должно быть подходящим вариантом, поскольку он предлагает знакомый программный синтаксис для подхода. Выбор специального пакета, возможно, runtime/embed как упоминалось ранее, удовлетворит это и обеспечит легкую расширяемость в будущем. Для меня наиболее разумна реализация, подобная следующей:

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

Использование этого пакета где-то в вашем коде должно заставить компилятор предоставить этот файл среде выполнения путем его автоматического встраивания.

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

Также я думаю, что проблемы с безопасностью и воспроизводимостью не должны быть проблемой, если мы ограничим импорт файлами на уровне или, возможно, на 1 уровень каталога ниже каталога go.mod, который также ранее уже упоминался в потоке. Абсолютные пути внедрения будут разрешены относительно этого уровня директории.

Отсутствие доступа к файлам во время процесса компиляции приведет к ошибке компилятора.

Также возможно создать zip-архив за двоичным файлом, чтобы он мог эффективно стать самораспаковывающимся двоичным файлом. Может быть, это полезно в некоторых случаях использования? Сделал это в качестве эксперимента здесь: https://github.com/sanderhahn/gozip

В Go уже есть «testdata». Модульные тесты используют обычный ввод-вывод, чтобы делать все, что они хотят. Объем теста означает, что контент не доставляется. Это все, что нужно знать: никаких излишеств, никакой магии, никакой логики сжатого контейнера, никаких настраиваемых косвенных ссылок, никакого META-INF. Красиво, просто, нарядно. Почему бы не создать папку «data» для связанных зависимостей области выполнения?

Мы можем легко просканировать существующие проекты Go на Github ea и создать ряд проектов, которые уже используют папку «data» и, следовательно, требуют адаптации.

Другое, что мне непонятно. Для обсуждения каталога static мне не на 100% ясно, обсуждаем ли мы каталог static _source_ или каталог static котором файлы будут доступны _ в runtime_ ?

И это различие особенно важно, потому что оно относится к процессу разработки и отладки кода, который находится в разработке.

@mikeschinkel из исходного сообщения довольно ясно, что встраивание произойдет во время сборки:

make go install / go build выполнить встраивание автоматически

В исходном сообщении и некоторых комментариях выше также обсуждается наличие режима «dev» для загрузки файлов во время выполнения.

@mvdan Спасибо за ответ. Итак, вы думаете, что это означает, что предлагаемый каталог /static/ будет относиться к корню репозитория приложения, репозитория пакета и / или, возможно, обоих?

И это расположение файлов времени выполнения будет полностью зависеть от того, где разработчик хотел их разместить?

Если все это правда - а это кажется логичным - было бы полезно, если бы программы, скомпилированные с отладочной информацией, могли опционально загружать файлы из их исходного местоположения, чтобы облегчить отладку без большого количества дополнительной - и нестандартной - логики и кода.

Пара человек выше упомянули прокси-модули. Я считаю, что это отличная лакмусовая бумажка для хорошего дизайна этой функции.

Сегодня кажется возможным без выполнения пользовательского кода реализовать работоспособный прокси-модуль, который удаляет файлы, которые не используются в сборке. Некоторые из приведенных выше схем означают, что прокси-серверы модулей должны выполнять пользовательский код, чтобы выяснить, какие статические файлы также должны быть включены.

Люди также упомянули go.mod в качестве опции.

Идея: спецификация в файле go.mod? Упрощает синтаксический анализ другими инструментами.

module github.com/foo/bar

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

Это создаст пакет во время компиляции с данными файла. Синтаксис Glob может быть здесь хорош, но, возможно, достаточно упрощения и встраивания только каталогов. (Кроме того: +1 для синтаксиса ** glob.)

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

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

Что-то вроде StripPrefix здесь может быть неплохо, но не обязательно. Легко создать пакет-оболочку, который использует любые пути к файлам, которые вы хотите.

Его можно еще упростить:

module github.com/foo/bar

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

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

Но это немного неинтуитивно, что moddata будет вести себя по-разному в зависимости от вызывающего пакета / модуля. Было бы сложнее написать помощников (например, конвертер http.Filesystem)

Сегодня кажется возможным без выполнения пользовательского кода реализовать работоспособный прокси-модуль, который удаляет файлы, которые не используются в сборке. Некоторые из приведенных выше схем означают, что прокси-серверы модулей должны выполнять пользовательский код, чтобы выяснить, какие статические файлы также должны быть включены.

Не думаю, что здесь произойдут существенные изменения. В частности, C-код может уже включать в себя любой файл в дереве, поэтому прокси-сервер модуля, который захочет это сделать, должен будет проанализировать C. Похоже, что в этот момент любые магические комментарии или API, которые мы вводим, будут небольшим шагом.

Некоторые из приведенных выше схем означают, что прокси-серверы модулей должны выполнять пользовательский код, чтобы выяснить, какие статические файлы также должны быть включены.

Я думаю, что совершенно очевидно, что «инструмент go не должен выполнять пользовательский код во время сборки» - это линия, нарисованная на песке, которую здесь нельзя пересекать. И если инструмент go не может выполнить код пользователя, тогда должна быть возможность указать, какие файлы включать без него.

Я пытался объединить свои различные мысли об этом варианте использования во что-то убедительное, и поэтому я @broady . Я думаю, что это по большей части отражает то, о чем я думал. Однако я считаю, что ключевым словом должен быть глагол embed вместо существительного data .

  1. Встроенные файлы кажутся чем-то, что нужно импортировать, а не просто специальным комментарием или волшебным пакетом. А в проекте Go в файле go.mod разработчик может указать необходимые модули / файлы, поэтому имеет смысл расширить его для поддержки встраивания.

  2. Кроме того, мне кажется, что набор встроенных файлов был бы более ценным и пригодным для повторного использования, если бы можно было включить пакет, а не что-то специальное, добавленное в проект Go с использованием одноразового синтаксиса. Идея здесь в том, что если бы встраивание было реализовано в виде пакетов, люди могли бы разрабатывать и делиться ими через Github, а другие могли бы использовать их в своих проектах. Представьте себе поддерживаемые сообществом и бесплатные пакеты на GitHub, содержащие:

    а. Файлы для стран, где каждый файл содержит все почтовые индексы этой страны,
    б. Файл со всеми известными строками пользовательского агента для идентификации браузеров,
    c. Изображения флага каждой страны мира,
    d. Подробная справочная информация, описывающая часто встречающиеся ошибки в программе Go,
    е. и так далее...

  3. Новая схема URL-адресов, такая как goembed:// - или, возможно, существующая, - которая может использоваться для открытия и чтения файлов из пакета, что позволяет использовать _ (все?) _ Существующие API-интерфейсы для работы с файлами вместо создания новых one, что-то вроде следующего, которое будет относиться к встраиванию, содержащемуся в текущем пакете:

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

С приведенными выше концепциями нет ничего похожего на _ "волшебство" _; все будет элегантно обрабатываться механизмом, который кажется, что он предназначен для определенной цели. Потребуется очень небольшое расширение; один новый глагол в go.mod и одна новая схема URL, которая будет распознаваться внутри Go. Все остальное будет предоставлено Go как есть.

Что я делаю сейчас

Я использую для этого code.soquee.net/pkgzip прямо сейчас (это форк statik который изменяет API, чтобы избежать глобального состояния и побочных эффектов импорта). Мой обычный рабочий процесс (по крайней мере, в веб-приложении) заключается в том, чтобы встраивать ресурсы, объединенные в ZIP-файл, а затем обслуживать их с помощью golang.org/x/tools/godoc/vfs/zipfs и golang.org/x/tools/godoc/vfs/httpfs .

перейти: внедрить подход

Есть две вещи, которые, вероятно, могут помешать мне принять подход go:embed :

  1. Сгенерированный код не будет отображаться в документации
  2. Активы могут быть разбросаны по всей базе кода (это справедливо для использования внешних инструментов и go:generate , поэтому я обычно предпочитаю использовать make-файл для генерации различных наборов активов перед сборкой, тогда я можно увидеть их все в make-файле)

Существует также проблема, которую я не включаю выше, потому что для некоторых это может быть особенностью того, что наличие ресурсов как части пакета (в отличие от всего модуля) означает, что вся сложность тегов сборки, пакетов тестирования и т. Д. .Применим к ним, нам нужен способ указать, являются ли они общедоступными или частными для этого пакета и т. д. Это похоже на дополнительную сложность сборки.

Что мне нравится в этом, так это то, что можно писать библиотеки, которые просто упрощают импорт ресурсов. Например. библиотека с одним файлом Go, который просто встраивает шрифт, или несколько значков могут быть опубликованы, и я мог бы импортировать его, как любой другой пакет Go. В будущем я мог бы получить иконочный шрифт, просто импортировав его:

import "forkaweso.me/forkawesome/v2"

внедрить пакетный подход

Хотя мне нравится идея, чтобы все это было явным, обычным кодом Go, мне не нравится идея, что это будет еще один волшебный пакет, который нельзя реализовать за пределами стандартной библиотеки.

Будет ли такой пакет определен как часть спецификации языка? В противном случае это еще одно место, где код Go разрывался бы между разными реализациями, что тоже плохо. Скорее всего, я продолжу использовать внешний инструмент, чтобы предотвратить эту поломку.

Кроме того, как отмечали другие, тот факт, что это делается во время сборки, означает, что этот пакет может принимать только строковые литералы или константы в качестве аргументов. В настоящее время нет способа представить это в системе типов, и я подозреваю, что это вызовет путаницу. Эту проблему можно решить, введя что-то вроде константных функций, но сейчас мы говорим о серьезных изменениях языка, из-за которых это не запускается. В противном случае я не вижу хорошего способа исправить это.

Гибридный

Мне нравится идея гибридного подхода. Вместо повторного использования комментариев (которые в конечном итоге разбросаны повсюду и, в личном плане, кажутся просто отвратительными), я бы хотел, чтобы все ресурсы были помещены в одно место, вероятно, файл go.mod как и другие. сказал:

module forkaweso.me/forkawesome/v2

go 1.15

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

Это означает, что ресурсы не могут быть включены или исключены произвольными тегами сборки или в произвольных пакетах (например, в пакете _testing) без создания отдельного модуля. Я думаю, что это снижение сложности может быть желательным (в библиотеке, которую вы пытаетесь импортировать, нет скрытого тега сборки, и вы не можете понять, почему у вас нет нужного актива, потому что при импорте библиотеки он должен был быть встроен) , но YMMV. Если это желательно, можно по-прежнему использовать прагматические комментарии, за исключением того, что они не генерируют код и вместо этого используют тот же подход, который я собираюсь описать для версии go.mod .

В отличие от исходного предложения, это не приведет к созданию кода. Вместо этого функциональность, например. чтение раздела данных файла ELF (или, тем не менее, оно сохраняется в любой ОС, которую вы используете), будет добавлено там, где это необходимо (например, os или debug/elf и т. д.) и затем, необязательно, будет создан новый пакет, который ведет себя точно так же, как пакет, описанный в OP, за исключением того, что вместо того, чтобы быть волшебным и выполнять встраивание, он просто читает встроенные файлы (что означает, что он может быть реализован вне стандартной библиотеки при желании).

Это позволяет обойти такие проблемы, как необходимость ограничить волшебный пакет только строковыми литералами в качестве аргументов, но это означает, что сложнее проверить, действительно ли встроенные ресурсы используются где-либо или в конечном итоге оказываются мертвым грузом. Это также позволяет избежать любых новых зависимостей между пакетами стандартных библиотек, потому что единственный пакет, который должен импортировать что-либо дополнительное, - это сам новый пакет.

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

Как видно выше, размещение ресурсов в модуле может по-прежнему ограничивать их этим конкретным модулем, если это необходимо. Фактический API и то, как вы выбираете актив, может потребовать некоторой доработки.

Еще одна идея: вместо добавления нового вида глагола embed в go.mod мы могли бы ввести новый вид пакета, пакет данных, который импортируется и используется в go.mod в обычный способ. Вот набросок соломенного человечка.

Если пакет содержит ровно один файл .go , static.go , и этот файл содержит только комментарии и предложение пакета, то пакет является пакетом данных. При импорте cmd / go заполняет пакет экспортированными функциями, обеспечивая доступ к содержащимся в нем файлам, которые встроены в полученный двоичный файл.

Если это реальный пакет, это будет означать, что будут применяться правила internal , и мы можем иметь элементы управления доступом без добавления в API.

Как насчет автоматического включения всех файлов и подпапок, отличных от .go (в соответствии с правилами отсутствия фактического кода), в каталог?

Если пакет содержит ровно один файл .go , static.go , и этот файл содержит только комментарии и предложение пакета, то пакет является пакетом данных.

Будет ли эта проверка выполняться перед применением тегов сборки? Если так, то это похоже на еще один особый случай, которого следует избегать. Если нет, то вполне возможно, что пакет может рассматриваться как стандартный пакет Go для одних тегов сборки и как пакет данных для других. Это кажется странным, но, может быть, это желательно?

@flimzy
Это как бы позволяет использовать встроенные файлы с одним тегом и определять те же fns / vars, что и сгенерированный пакет, и обслуживать файлы другим способом (может быть, удаленным?) С другим тегом.

Было бы неплохо, если бы был флаг сборки для генерации функций-оберток, поэтому нужно просто заполнить пробелы.

@josharian

Если пакет содержит ровно один файл .go , static.go , и этот файл содержит только комментарии и предложение пакета, то пакет является пакетом данных.

Я могу представить пакеты «данных» как имеющие свои собственные специфичные для домена функциональные возможности, такие как поиск по почтовому индексу. Подход, который вы только что предложили, запретил бы что-либо, кроме необработанных данных, и, таким образом, свел бы на нет преимущества возможности упаковывать логику с данными.

Я могу представить пакеты «данных» как имеющие свои собственные специфичные для домена функциональные возможности, такие как поиск по почтовому индексу.

Вы можете раскрыть функциональность в my.pkg / postalcode и поместить данные в my.pkg / postalcode / data (или my.pkg / postalcode / internal / data).

Я вижу привлекательность в том, чтобы сделать это так, как вы предлагаете, но это вызывает множество вопросов: как работает обратная совместимость? Как вы помечаете пакет данных как таковой? Что вы будете делать, если в пакете есть функции, которые будут конфликтовать с теми, которые добавит cmd / go? (Я не говорю, что у них нет ответов, просто проще не отвечать на них.)

@josharian , примите во внимание приведенный выше комментарий проверки типа (https://github.com/golang/go/issues/35950#issuecomment-561443566).

@bradfitz: да, это будет изменение языка и потребуется поддержка go / types.

На самом деле, есть способ сделать это без изменения языка - потребовать, чтобы static.go содержал функции без тела, точно соответствующие тому, что заполнял бы cmd / go.

требуется, чтобы static.go содержал функции без тела, точно соответствующие тому, что заполняет cmd / go.

Если он генерирует функции для каждого файла вместо перехвата всех embed.File() , это позволит упростить управление экспортом для каждого актива.

Итак, сгенерированный материал будет выглядеть так:

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

Сообщение в блоге о статических файлах, которое я написал 4 месяца назад. Смотрите последнее предложение в выводах :-)

@josharian

Вы можете раскрыть функциональность в my.pkg / postalcode и поместить данные в my.pkg / postalcode / data (или my.pkg / postalcode / internal / data).

Это - хотя и неэлегантно - могло решить мои проблемы.

Как работает обратная совместимость?

Я не понимаю, как здесь применимы опасения по поводу BC. Вы можете уточнить?

Как вы помечаете пакет данных как таковой?

С оператором embed в go.mod ?

Возможно, я не понимаю, о чем вы спрашиваете.

Но я переверну его; как пометить пакет только данными как таковыми?

Что вы будете делать, если в пакете есть функции, которые будут конфликтовать с теми, которые добавит cmd / go?

  1. Используя предложенный подход, я не думаю, что cmd / go понадобится для добавления каких-либо функций.

  2. Даже если cmd / go действительно нужно добавлять функции, я предполагаю, что поведение конфликтов в _существующем_ пакете будет _неопределенным_.

    Предложение предполагает, что разработчик следует принципу единой ответственности и, таким образом, должен создавать пакет с данными только для того, чтобы быть ориентированным на данные пакетом, а не добавлять данные в существующий ориентированный на логику пакет.

    Конечно, разработчик _ может_ добавить к существующему пакету, и в этом случае поведение будет неопределенным. IOW, если разработчик игнорирует идиомы, они окажутся на неизведанной территории.

Я не говорю, что у них нет ответов, просто проще не отвечать на них.

За исключением того, что я думаю, что ответы просты. По крайней мере, для тех, что вы позировали до сих пор.

Я думаю, что любое решение, которое добавляет символы или значения для символов, должно быть в пределах пакета, а не модуля. Поскольку единицей компиляции для Go является пакет, а не модуль.

Таким образом, это исключает любое использование go.mod для указания списка файлов для импорта.

@dolmen, если конечный результат находится в его собственном пакете, тогда сама область видимости будет модулем.

@urandom Нет, область действия - это пакет (ы), которые импортируют этот сгенерированный пакет. Но в любом случае я не думаю, что полностью сгенерированный пакет входит в объем этого предложения.

@urandom Нет, область действия - это пакет (ы), которые импортируют этот сгенерированный пакет. Но в любом случае я не думаю, что полностью сгенерированный пакет входит в объем этого предложения.

независимо от того, как это предложение будет реализовано, учитывая, что различные пакеты модулей будут использовать конечный результат, имеет смысл, что определение того, что внедряется, определяется на уровне модуля. прецедент для этого уже существует в экосистеме Java, где встроенные файлы имеют область видимости модуля и добавляются из волшебного каталога.

кроме того, go.mod представляет собой самый чистый способ добавить такую ​​функцию (без волшебных комментариев или волшебных каталогов) без нарушения существующих программ.

Вот кое-что, о чем еще не упоминалось: API для инструментов обработки исходного кода Go (компилятор, статические анализаторы) так же важен, как и API среды выполнения. Этот вид API - основная ценность Go, которая помогает развивать экосистему (например, go/ast / go/format и go mod edit ).

Этот API может использоваться инструментами препроцессора (в частности, в шагах go:generate ) для получения списка файлов, которые будут встроены, или его можно использовать для генерации ссылки.

В случае специального пакета я ничего не вижу в парсере go.mod ( go mod tools) или go/ast parser.

@dolmen

_ "Я думаю, что любое решение, которое добавляет символы или значения для символов, должно иметь область видимости пакета, а не модуля. Поскольку единицей компиляции для Go является пакет, а не модуль. Таким образом, это исключает любое использование go.mod для указания списка файлы для импорта. "_

Что такое модуль? Модули - это _ « набор связанных пакетов Go, которые управляются версиями вместе как единое целое» . _ Таким образом, модуль может состоять из одного пакета, а один пакет может быть целым модулем.

Таким образом, go.mod - это правильное место для указания списка файлов для импорта, если команда Go использует go.mod вместо специальных комментариев и волшебных пакетов. Это до тех пор, пока команда Go не решит добавить файл go.pkg .

Кроме того, если команда Go примет go.mod как место для указания встроенных файлов, тогда любой, кто хочет встроить файлы, должен предоставить go.mod с инструкциями embed и пакет который представлен каталогом, в котором находится файл go.mod будет пакетом, содержащим встроенные файлы.

Но если это не то, чего хочет разработчик, он должен создать еще один файл go.mod и поместить его в каталог для пакета, который они хотят содержать в своих встроенных файлах.

Есть ли у вас законный сценарий, при котором эти ограничения не будут работать?

@mikeschinkel , модуль - это набор _ связанных_ пакетов. Однако возможно (и разумно!) Использовать один пакет из модуля без использования транзитивных зависимостей (и данных!) Других пакетов в этом модуле.

Файлы данных, как правило, являются зависимостями для каждого пакета, а не для каждого модуля, поэтому информацию о том, как найти эти зависимости, следует размещать вместе с пакетом, а не хранить как отдельные метаданные уровня модуля.

@bcmills

Кажется, что можно заменить «Файлы данных» в своем сообщении на «модули», и это все равно останется верным.
Довольно распространено иметь определенные модули в качестве зависимостей для конкретных ваших собственных пакетов.
Тем не менее, мы поместили их все в go.mod.

@urandom , не все пакеты в модулях, указанных в файле go.mod , связаны в окончательный двоичный файл. (Включение зависимости в файл go.mod _не_ эквивалентно связыванию этой зависимости с программой.)

Мета-точка

Понятно, что это то, что волнует многих, и исходный комментарий Брэда вверху был не столько законченным предложением, сколько первоначальным наброском / отправной точкой / призывом к действию. Я думаю, что на этом этапе имеет смысл свернуть это конкретное обсуждение, попросить Брэда и, возможно, еще нескольких человек совместно работать над подробным проектным документом, а затем начать новый выпуск для обсуждения этого конкретного (еще не написанного) документа. . Похоже, это поможет сосредоточиться на том, что превратилось в растянутую беседу.

Мысли?

Я не уверен, что согласен с тем, чтобы закрыть это, но мы можем предложить-удерживать его, пока не будет проектная документация, и на некоторое время заблокируем комментарии. (Почти все комментарии на данный момент избыточны, так как существует слишком много комментариев, чтобы люди могли прочитать, чтобы увидеть, является ли их комментарий избыточным ...)

Может быть, когда есть проектная документация, то ее можно будет закрыть.

Или закройте этот и снова откройте # 3035 (с замороженными комментариями), чтобы его отслеживала хотя бы одна открытая проблема.

Извините, что сделал это сразу после разговора о закрытии, но подробное обсуждение началось сразу после комментария

«_Однако возможно (и разумно!) Использовать один пакет из модуля без использования транзитивных зависимостей (и данных!) Других пакетов в этом модуле.» _

Да, очевидно, что это _возможно ._ Но, как и в любой другой передовой практике, передовой практикой для пакетов данных может быть создание единого пакета для модуля, который решит вашу проблему. Если это означает go.mod в корне и еще один go.mod в подкаталоге, содержащем пакет данных, пусть будет так.

Я полагаю, я защищаю то, что вы не делаете совершенного врагом хорошего здесь, где хорошее здесь определяется как go.mod что является идеальным местом для указания встроенных файлов, учитывая, что по своей природе они являются списком компоненты модуля.

Извините, но основной концепцией Go являются пакеты, а не модули.
Модули - это просто группы пакетов, для которых устанавливается версия как единое целое.
Модули не вносят дополнительной семантики, помимо семантики отдельных пакетов.
Это часть их простоты.
Все, что мы здесь делаем, должно быть связано с пакетами, а не модулями.

Куда бы это ни пошло и как бы это ни было сделано, должен быть способ получить список всех активов, которые будут встроены с помощью go list (а не только используемые шаблоны).

Отложено, пока Брэд и другие не разработают формальную дизайнерскую документацию.

Блокировка определенных файлов не должна быть слишком сложной, особенно если вы используете каталог static или embed . Символьные ссылки могут немного усложнить это, но вы можете просто предотвратить встраивание чего-либо за пределами текущего модуля или, если вы используете GOPATH, за пределами пакета, содержащего каталог.

Я не особо люблю комментарии, которые компилируются в код, но я также считаю, что псевдопакет, влияющий на компиляцию, тоже немного странный. Если подход каталогов не используется, возможно, имеет смысл иметь какое-то объявление верхнего уровня embed фактически встроенное в язык. Он будет работать аналогично import , но будет поддерживать только локальные пути и потребует присвоения имени. Например,

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

Изменить: вы меня опередили, @jayconrod.

Это чисто и читабельно, однако я не уверен, что команда go захотела бы ввести новое ключевое слово.

Идея, насколько я помню, заключалась в том, чтобы просто иметь специальное имя каталога, такое как «static», содержащее статические данные, и автоматически делать их доступными через API без аннотаций.

Использование static в качестве специального имени каталога немного сбивает с толку, и я бы предпочел использовать assets .
Другая идея, которую я не увидел в потоке, - разрешить импорт assets в виде пакета, например import "example.com/internal/assets" . Открытый API все еще нуждается в дизайне, но, по крайней мере, он выглядит чище, чем специальные комментарии или новые пакеты в стиле runtime/files .

Еще одна идея, которую я не видел в потоке, - разрешить импорт ресурсов как пакет.

Это было предложено здесь: https://github.com/golang/go/issues/35950#issuecomment -562966654

Одна из сложностей заключается в том, что для включения проверки типов вам необходимо либо изменить язык, либо предоставить функции без тела, которые будут заполняться командой cmd / go .

Это схожая идея, но дизайн static.go позволяет превратить произвольный путь импорта в пакет данных, тогда как каталог assets больше похож на testdata , internal или vendor с точки зрения «особенного». Одно из возможных требований assets - не содержать пакетов Go (или разрешать только документацию), то есть для неявной обратной совместимости.

Это также можно комбинировать с API runtime/files -thingy для получения файлов. То есть использование необработанных import s для встраивания деревьев каталогов с файлами, а затем использование некоторого пакета времени выполнения для доступа к ним. Может быть даже os.Open , но это вряд ли будет принято.

shurcooL/vfsgen together shurcooL/httpgzip имеет приятную особенность, при которой контент может быть предоставлен без декомпрессии.

например

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

Аналогичная функция предлагается для C ++: std::embed :

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

Это может быть полезно в качестве вдохновения для дизайна и для сбора возможных вариантов использования.

Я немного опоздал на вечеринку, но у меня возникла идея. Вместо специальных комментариев, фиксированный специальный каталог (статический) или явно запрещенный подход к расширению go.mod: как насчет нового файла манифеста для каждого пакета: go.res

  • Содержит список файлов. Никаких путей или глобусов, только имена в текущем каталоге пакета. Если нужно, сгенерируйте его из глобуса перед фиксацией.

    • __Edit__ может потребоваться одна строка package mypackagename вверху, как в файле go. В качестве альтернативы вы можете включить имя пакета в имя файла (например, mypackagename.go.res). Лично мне больше нравится строка заголовка package .

  • Новый основной пакет под названием «ресурс» или, возможно, «io / resource». Имеет как минимум одну функцию: func Read(name string) (io.Reader, bool) для чтения ресурсов, встроенных в текущий пакет.

    • __Edit__ Не уверен, что основные пакеты работают таким образом. Возможно, это должна быть сгенерированная частная функция пакета (например, func readresource(name string) (io.Reader, bool) )

  • Если вам нужны ресурсы в подкаталоге, сделайте подкаталог пакетом, добавив файл go.res и хотя бы один файл .go . Файл go экспортирует ваш собственный общедоступный API для доступа к ресурсам в подкаталоге package. Файл go и экспортированный API необходимы, потому что ресурсы из других пакетов не экспортируются автоматически (по дизайну). Вы также можете настроить способ их экспорта таким образом.

    • __Edit__ в качестве альтернативы, если вам нужна структура каталогов и / или сжатие, используйте ресурс tar. Это позволяет использовать такие вещи, как пакеты веб-пакетов, которые уже требуют компиляции (и могут выиграть от предварительного сжатия). Сделать еще один шаг до дегтя просто.

  • __Edit__ Нужен манифест? Просто включите сам файл go.res в качестве ресурса. Нам даже не нужно создавать функцию listresources.

Очень просто. Одна новая функция. Один новый файл. Никаких путей. Без сжатия. Нет нового синтаксиса. Никакой магии. Расширяемый. Доступ только для чтения через считыватель (но открыт для альтернативных шаблонов доступа в будущем). Почти нулевая вероятность взлома существующих пакетов. Сохраняется пакет, являющийся основной конструкцией на ходу.

__Edit__ После поиска в github language:go filename:go.res extension:res кажется, что go.res было бы довольно безопасным для использования именем файла. В готовых репозиториях совпадений нет, а в непроходных репозиториях только несколько.

Мне нравится идея @ chris.ackermanm. Но я бы предпочел комбинацию:

Файл go.res, определяющий пространство имен в каталоге.

Это позволяет

  • несколько включает в себя, если пространство имен отличается
  • незнание файлов раньше и необходимость создания списка

Последний должен заниматься выводом webpack и подобных вещей, которые могут изменить макет из-за обновлений, различных опций, всего, что вы можете придумать.

Что касается сжатия: я думаю, что это скорее функция с точки зрения того, чтобы не увеличивать двоичные размеры, и она должна быть прозрачной для используемого кода.

Позже вы можете разрешить перезапись, например

filename => stored-as.png

Только мои 2 ¢

@ sascha-andres Кажется, что тон этой темы - это ультра-простота и нулевое волшебство. См. Правки, которые я внес в свой комментарий, по вашим предложениям.

Мне не нравится отображение. Нет надобности. Это возможно, если в любом случае предоставить вашу собственную функцию чтения из отдельного пакета, и теперь нам нужен новый синтаксис файла или что-то более сложное, чем файл на строку.

Привет

Это офигенное предложение!

И у меня есть свой подход к выделенным активам. нет необходимости вводить какие-либо инструменты, кроме GNU bintools. Это немного грязно, но пока мне подходит. Я просто хочу поделиться этим и посмотреть, поможет ли это.

Мой подход состоит в том, чтобы просто встроить мои активы (сжатые tar & gz) в раздел elf / pe32 с objcopy и прочитать его через пакет debug / elf и debug / pe32 вместе с zip, когда это необходимо. все, что мне нужно запомнить, это не трогать существующие разделы. все активы неизменяемы, а затем код считывает содержимое и обрабатывает его в памяти.

Я не очень разбираюсь в дизайне языков или компиляторов. поэтому я бы просто использовал подход, описанный выше, и использовал бы .goassets или что-то в этом роде в качестве имени раздела. и сделайте компрессию необязательной.

Мой подход состоит в том, чтобы просто встроить мои активы (сжатые tar & gz) в раздел elf / pe32 с objcopy и прочитать его через пакет debug / elf и debug / pe32 вместе с zip, когда это необходимо. все, что мне нужно запомнить, это не трогать существующие разделы. все активы неизменяемы, а затем код считывает содержимое и обрабатывает его в памяти.

Похоже, это работает на elf / pe32 но как насчет mach-o / plan9 ?

Другая проблема заключается в том, что он полагается на открытие дескриптора файла в исполняемом файле, если исполняемый файл был перезаписан / обновлен / удален, тогда он вернет другие данные, не уверен, что это законная проблема или неожиданная функция.

Я сам немного попробовал (используя debug / macho ), но я не вижу способа получить эту рабочую кроссплатформенность, я использую macOS, а установленный GNU binutils, похоже, повреждает mach-o-x86-64 файл (это может быть просто моим отсутствием понимания структуры mach-o и слишком давно, когда я даже смотрел на objcopy ).

Другая проблема заключается в том, что он полагается на открытие дескриптора файла исполняемого файла.

Я почти уверен, что загрузчик программ загрузит (или может) загрузить раздел ресурсов в память, поэтому нет необходимости использовать пакеты отладки. Хотя для доступа к данным потребуется гораздо больше работы с объектными файлами, чем это того стоит.

Почему бы не проследить, что работает - например, как это делает Java . Я бы потребовал, чтобы все было в порядке, но что-то в строках:

  • создать файл go.res или изменить go.mod чтобы он указывал на каталог, в котором находятся ресурсы
  • все файлы из этого каталога автоматически включаются, без исключений компилятором в окончательный исполняемый файл
  • language предоставляет API-интерфейс, похожий на путь, для доступа к этим ресурсам

Сжатие и т. Д. Должно выходить за рамки этого объединения ресурсов и при необходимости применимо к любым сценариям // go:generate .

Кто-нибудь смотрел markbates / pkger ? Это довольно простое решение - использовать go.mod в качестве текущего рабочего каталога. Предполагая, что index.html должен быть встроен, открытие будет pkger.Open("/index.html") . Я думаю, что это лучшая идея, чем жесткое кодирование каталога static/ в проекте.

Также стоит упомянуть, что Go, насколько я мог видеть, не предъявляет каких-либо существенных требований к структуре проекта. go.mod - это просто файл, и немногие люди когда-либо используют vendor/ . Я лично не думаю, что каталог static/ был бы хорош.

Поскольку у нас уже есть способ внедрения (хотя и ограниченного) данных в сборку с помощью существующего флага ldflags link -X importpath.name=value , можно ли изменить этот путь кода, чтобы он принимал -X importpath.name=@filename для внедрения внешние произвольные данные?

Я понимаю, что это не охватывает все заявленные цели исходного выпуска, но как расширение существующей функциональности -X кажется ли это разумным шагом вперед?

(И если это сработает, то следующим разумным шагом будет расширение синтаксиса go.mod в качестве более аккуратного способа указания значений ldflags -X ?)

Это очень интересная идея, но меня беспокоят ее последствия для безопасности.

Довольно распространено выполнение -X 'pkg.BuildVersion=$(git rev-parse HEAD)' , но мы бы не хотели, чтобы go.mod запускал произвольные команды, не так ли? (Я полагаю, что go generate работает, но это не то, что вы обычно запускаете для загруженных пакетов OSS.) Если go.mod не может с этим справиться, в нем отсутствует основной вариант использования, поэтому ldflags все еще будет очень распространенным.

Затем есть еще @filename проблема - убедиться, что

Использование компоновщика исключает поддержку WASM и, возможно, других целей, которые не используют компоновщик.

Основываясь на обсуждении здесь, @bradfitz и я разработали дизайн, который находится где-то посередине из двух рассмотренных выше подходов, взяв то, что кажется лучшим из каждого. Я разместил проект документа, видео и кода (ссылки ниже). Вместо комментариев по этой проблеме используйте Reddit Q&A для комментариев по этому конкретному черновику проекта - Reddit ведет обсуждения и масштабирует обсуждения лучше, чем GitHub. Спасибо!

Видео: https://golang.org/s/draft-embed-video
Дизайн: https://golang.org/s/draft-embed-design
Вопросы и ответы: https://golang.org/s/draft-embed-reddit
Код: https://golang.org/s/draft-embed-code

@rsc На мой взгляд, предложение go: embed уступает предоставлению _универсального_ выполнения кода Go в песочнице во время компиляции, что включает чтение файлов и преобразование считанных данных в _оптимальный формат_, наиболее подходящий для использования во время выполнения.

@atomsymbol Похоже, что что-то выходит за рамки данной проблемы.

@atomsymbol Похоже, что что-то выходит за рамки данной проблемы.

Я знаю об этом.

Я прочитал предложение и просмотрел код, но не нашел ответа на этот вопрос: будет ли эта схема встраивания содержать информацию о файле на диске (~ os.Stat)? Или эти временные метки будут сброшены на время сборки? В любом случае, это полезная информация, которая используется в разных местах, например, мы можем отправить 304 для неизмененных активов на основе этого.

Спасибо!

Изменить: нашел в ветке Reddit.

Время модификации для всех встроенных файлов - это нулевое время, поскольку воспроизводимость точно указана вами. (Модули даже не записывают время модификации, опять же по той же причине.)

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

В любом случае, это полезная информация, которая используется в разных местах, например, мы можем отправить 304 для неизмененных активов на основе этого.

Заголовок ETag, основанный на хэше данных файла, решил бы эту проблему без необходимости знать что-либо о датах. Но это должно быть известно http.HandlerFS или чему-то еще, чтобы иметь возможность работать и не тратить ресурсы впустую, это нужно делать только один раз для каждого файла.

Но это должно быть известно http.HandlerFS или чему-то еще, чтобы иметь возможность работать и не тратить ресурсы впустую, это нужно делать только один раз для каждого файла.

Как http.HandlerFS узнает, что fs.FS неизменна? Должен ли быть дополнительный интерфейс IsImmutable() bool ?

Как http.HandlerFS узнает, что fs.FS неизменна? Должен ли быть дополнительный интерфейс IsImmutable() bool ?

Я не хочу вдаваться в подробности реализации, потому что я не являюсь разработчиком этих вещей, но http.HandlerFS может проверить, является ли он типом embed.FS, и действовать как особый случай, я не думаю, что кто-то захочет расширьте FS API прямо сейчас. Также может быть аргумент option для HandlerFS, специально указывающий, что файловая система должна рассматриваться как неизменяемая. Также, если это делается при запуске приложения и все ctime / mtime имеют нулевое значение, обработчик FS может использовать эту информацию, чтобы «знать», что файл не изменился, но есть также файловые системы, которые могут не иметь mtime или отключены, поэтому там там тоже могут быть проблемы.

Я не смотрел комментарии по этому поводу.

@atomsymbol с возвращением! Приятно видеть, что вы снова здесь комментируете.
Я в принципе согласен с тем, что если бы у нас была песочница, многое было бы проще.
С другой стороны, многие вещи могут быть сложнее - сборки могут никогда не завершиться.
В любом случае, сегодня у нас точно нет такой песочницы. :-)

@kokes Я не уверен в деталях,
но мы позаботимся о том, чтобы обслуживать встраивание. Файлы через HTTP по умолчанию получают правильные теги ETags.

Я подал №41191 для принятия проекта дизайна, опубликованного еще в июле.
Я собираюсь закрыть этот выпуск как замененный этим.
Спасибо за отличное предварительное обсуждение здесь.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги