Go: الاقتراح: cmd / go: دعم تضمين الأصول الثابتة (الملفات) في الثنائيات

تم إنشاؤها على ٤ ديسمبر ٢٠١٩  ·  176تعليقات  ·  مصدر: golang/go

هناك العديد من الأدوات لتضمين ملفات الأصول الثابتة في الثنائيات:

في الواقع ، يسرد https://tech.townsourced.com/post/embedding-static-files-in-go/ المزيد:

عرض

أعتقد أن الوقت قد حان للقيام بذلك جيدًا مرة واحدة وتقليل التكرار ، وإضافة دعم رسمي لتضمين موارد الملفات في أداة cmd / go.

مشاكل الوضع الحالي:

  • هناك الكثير من الأدوات
  • استخدام go: ينفخ الحل المستند إلى إنشاء سجل git بنسخة ثانية (وأكبر قليلاً) من كل ملف.
  • عدم استخدام go: إنشاء يعني عدم إمكانية استخدام go install أو جعل الأشخاص يكتبون ملفات Makefiles الخاصة بهم ، وما إلى ذلك.

الأهداف:

  • لا تحقق في الملفات التي تم إنشاؤها
  • لا تنشئ ملفات * .go على الإطلاق (على الأقل ليس في مساحة عمل المستخدم)
  • اجعل 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: تضمين النهج

بالنسبة لمقاربة go:embed ، يمكن للمرء أن يقول أن أي ملف go/build مختار *.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 * مقابل حزمة سحرية).
  • حظر ملفات معينة؟

    • من المحتمل حظر تضمين ../../../../../../../../../../etc/shadow

    • ربما يصل الحظر إلى .git أيضًا

Proposal Proposal-Hold

التعليق الأكثر فائدة

تحدثت مع نعد أبدًا لفعل أي شيء. لقد كان يزعجني لسنوات لأننا لم ننتهي من فعل ذلك. كانت الفكرة كما أتذكرها هي الحصول على اسم دليل خاص مثل "ثابت" يحتوي على البيانات الثابتة وإتاحتها تلقائيًا من خلال واجهة برمجة التطبيقات ، دون الحاجة إلى التعليقات التوضيحية.

لست مقتنعًا بمدى تعقيد مقبض "مضغوط مقابل ليس". إذا فعلنا ذلك ، فسيريدنا الناس أن نضيف تحكمًا في مستوى الضغط والضغط وما إلى ذلك. كل ما نحتاج إلى إضافته هو القدرة على تضمين ملف من البايت العادي. إذا كان المستخدمون يرغبون في تخزين البيانات المضغوطة في هذا الملف ، فهذا رائع ، فإن التفاصيل متروكة لهم وليس هناك حاجة إلى واجهة برمجة تطبيقات من جانب Go على الإطلاق.

ال 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 أو وقت التشغيل ... يمكن أن يوفر net / http محولًا.)

bradfitz كلاهما http.الملف نفسه هو واجهة لا تحتوي على تبعيات فنية لحزمة http . قد تكون فكرة جيدة لأي طريقة Open لتوفير تطبيق يتوافق مع تلك الواجهة ، لأن كلا الأسلوبين Stat و Readdir مفيدان جدًا لمثل هذه الأصول

urandom ، لا يمكنه تنفيذ http.FileSystem ، رغم ذلك ، دون الرجوع إلى اسم "http.File" (https://play.golang.org/p/-r3KjG1Gp-8).

تحدثت مع نعد أبدًا لفعل أي شيء. لقد كان يزعجني لسنوات لأننا لم ننتهي من فعل ذلك. كانت الفكرة كما أتذكرها هي الحصول على اسم دليل خاص مثل "ثابت" يحتوي على البيانات الثابتة وإتاحتها تلقائيًا من خلال واجهة برمجة التطبيقات ، دون الحاجة إلى التعليقات التوضيحية.

لست مقتنعًا بمدى تعقيد مقبض "مضغوط مقابل ليس". إذا فعلنا ذلك ، فسيريدنا الناس أن نضيف تحكمًا في مستوى الضغط والضغط وما إلى ذلك. كل ما نحتاج إلى إضافته هو القدرة على تضمين ملف من البايت العادي. إذا كان المستخدمون يرغبون في تخزين البيانات المضغوطة في هذا الملف ، فهذا رائع ، فإن التفاصيل متروكة لهم وليس هناك حاجة إلى واجهة برمجة تطبيقات من جانب Go على الإطلاق.

زوجان من الأفكار:

  • لا ينبغي أن يكون من الممكن تضمين أي ملف خارج الوحدة النمطية للقيام بالتضمين. نحتاج إلى التأكد من أن الملفات جزء من ملفات مضغوطة للوحدة النمطية عندما نقوم بإنشائها ، وهذا يعني أيضًا عدم وجود روابط رمزية ، أو تعارضات في الحالة ، وما إلى ذلك. لا يمكننا تغيير الخوارزمية التي تنتج ملفات مضغوطة دون كسر المبالغ.
  • أعتقد أنه من الأسهل تقييد التضمين في نفس الدليل (إذا تم استخدام تعليقات //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 ، هناك لغز حول واجهة برمجة التطبيقات المكشوفة. الطرق الواضحة لكشف البيانات هي واجهات []byte و string و Read -ish.

الحالة النموذجية هي أنك تريد أن تكون البيانات المضمنة غير قابلة للتغيير. ومع ذلك ، يجب على جميع الواجهات التي تعرض []byte (والتي تتضمن io.Reader ، io.SectionReader ، إلخ.) إما (1) عمل نسخة ، (2) تسمح بالتغيير ، أو (3) تكون ثابتة على الرغم من كونها []byte . يؤدي عرض البيانات على أنها string حل ذلك ، ولكن على حساب واجهة برمجة التطبيقات التي غالبًا ما تتطلب النسخ على أي حال ، نظرًا لأن الكثير من التعليمات البرمجية التي تستهلك الملفات المضمنة تتطلب في النهاية شرائح البايت بطريقة أو بأخرى.

أقترح المسار (3): كن ثابتًا على الرغم من كونه []byte . يمكنك فرض هذا بثمن بخس باستخدام رمز للقراءة فقط لصفيف الدعم. يتيح لك هذا أيضًا الكشف بأمان عن نفس البيانات مثل []byte و string ؛ ستفشل محاولات تعديل البيانات. لا يستطيع المترجم الاستفادة من الثبات ، لكن هذا ليس خسارة كبيرة. هذا شيء يمكن أن يجلبه دعم toolchain إلى الجدول والذي (على حد علمي) لا تفعله أي من حزم codegen الحالية.

(يمكن لحزمة برامج ترميز الطرف الثالث القيام بذلك عن طريق إنشاء ملف تجميع عام يحتوي على رموز DATA التي تم تمييزها على أنها للقراءة فقط ، ثم ملفات التجميع القصيرة الخاصة بالقوس والتي تعرض هذه الرموز في شكل string s و []byte s. لقد كتبت CL 163747 على وجه التحديد مع وضع حالة الاستخدام هذه في الاعتبار ، لكنني لم أتمكن من دمجها في أي حزم codegen.)

لست متأكدًا مما تتحدث عنه من حيث الثبات. يفرض io.Reader الثبات بالفعل. هذا هو بيت القصيد. عند استدعاء Read(buf) ، فإنه ينسخ البيانات إلى المخزن المؤقت الذي قدمته. تغيير buf بعد ذلك ليس له تأثير على العناصر الداخلية لـ io.Reader .

أنا أتفق معDeedleFake. لا أريد أن ألعب ألعابًا باستخدام مصفوفة سحرية []byte . لا بأس في النسخ من الملف الثنائي إلى المخازن المؤقتة التي يوفرها المستخدم.

مجرد تجعد آخر هنا - لدي مشروع مختلف يستخدم شفرة مصدر DTrace (مضمن). هذا حساس للاختلافات بين \ n و \ r \ n. (يمكننا أن نناقش ما إذا كان هذا أمرًا غبيًا في DTrace أم لا - هذا بجانب النقطة وهو الوضع اليوم.)

من المفيد جدًا أن تعامل السلاسل ذات العلامات الخلفية كلاهما على أنهما \ n بغض النظر عن كيفية ظهورهما في المصدر ، وأنا أعتمد على هذا باستخدام أداة إنشاء لتضمين DTrace.

لذلك إذا كان هناك ملف تضمين مضاف إلى الأمر go ، فإنني أقترح بلطف أن خيارات تغيير التعامل مع CR / CRLF قد تكون مفيدة جدًا ، خاصة للأشخاص الذين قد يطورون على أنظمة مختلفة حيث يمكن أن تكون نهايات الأسطر الافتراضية مسكتك.

كما هو الحال مع الضغط ، أود حقًا التوقف عند "نسخ ملف بايت إلى الملف الثنائي". تطبيع CR / CRLF ، تطبيع Unicode ، gofmt'ing ، كل ما ينتمي إلى مكان آخر. قم بإيداع الملفات التي تحتوي على وحدات البايت التي تريدها بالضبط. (إذا كان التحكم في الإصدار الخاص بك لا يمكنه تركهم بمفردهم ، فربما تحقق من محتوى بتنسيق gzip وقم بإدخاله في وقت التشغيل.) هناك العديد من مقابض تغيير الملفات التي يمكننا تخيل إضافتها. دعنا نتوقف عند 0.

قد يكون الوقت قد فات لتقديم اسم دليل محجوز جديد ، بقدر ما أرغب في ذلك.
(لم يكن الوقت قد فات في عام 2014 ، ولكن ربما فات الأوان الآن.)
لذلك قد يكون من الضروري وجود نوع من تعليق التمكين.

لنفترض أننا حددنا نوع وقت التشغيل. ثم يمكنك أن تتخيل الكتابة:

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

ثم في وقت التشغيل تقوم فقط باستدعاء الملفات. افتح لاستعادة interface { io.ReadSeeker; io.ReaderAt } مع البيانات. لاحظ أن var غير مُصدَّر ، لذا لا يمكن لحزمة واحدة أن تتنقل في الملفات المضمنة في حزمة أخرى.

يتم تحديد الأسماء TBD ولكن فيما يتعلق بالآلية التي يبدو أنها يجب أن تكون كافية ، ولا أرى كيف أبسطها. (التبسيط مرحب به بالطبع!)

أيا كان ما نقوم به ، يجب أن يكون من الممكن دعم Bazel و Gazelle أيضًا. قد يعني ذلك جعل Gazelle يتعرف على التعليق ويكتب قاعدة Bazel تقول الكرات ، وبعد ذلك سنحتاج إلى كشف أداة (go tool embedgen أو أي شيء آخر) لإنشاء ملف إضافي لتضمينه في الإنشاء (الأمر go سيكون القيام بذلك تلقائيًا وعدم إظهار الملف الإضافي مطلقًا). هذا يبدو واضحا بما فيه الكفاية.

إذا لم تفلح طرق العلاج المختلفة في حل المشكلة ، فهذه حجة ضد استخدام هذه الوسيلة الجديدة. إنها ليست سدادة بالنسبة لي - يمكنني استخدام go إنشاء كما كنت أفعل ، لكن هذا يعني أنه لا يمكنني الاستفادة من الميزة الجديدة.

فيما يتعلق بالتمرير بشكل عام - يمكنني تخيل حل حيث يوفر شخص ما تنفيذًا لواجهة (شيء مثل القارئ () من جانب ، وشيء لاستلام الملف على الجانب الآخر - ربما يتم تشغيله باستخدام قارئ io.Reader. من الملف نفسه) - الذي سيبنيه go cmd ويديره لتصفية الملف مسبقًا قبل التضمين. ثم يمكن للناس توفير أي مرشح يريدونه. أتخيل أن بعض الأشخاص سيوفرون مرشحات شبه قياسية مثل تنفيذ dos2unix والضغط وما إلى ذلك (ربما يجب أن تكون قابلة للتسلسل حتى.)

أعتقد أنه يجب أن يكون هناك افتراض بأنه مهما كان المعالج المضمن ، يجب أن يكون قابلاً للتجميع على كل نظام بناء ، حيث ستعمل go على إنشاء أداة أصلية مؤقتة لهذا الغرض.

قد يكون الوقت قد فات لتقديم اسم دليل محجوز جديد ، بقدر ما أرغب في ذلك. [...] قد يكون من الضروري وجود نوع من تعليق التمكين.

إذا كانت الملفات لا يمكن الوصول إليها إلا من خلال حزمة خاصة ، على سبيل المثال runtime/embed ، فقد يكون استيراد تلك الحزمة هو إشارة الاشتراك.

يبدو أن نهج io.Read يمكن أن يضيف مصروفات كبيرة (من حيث كل من النسخ وبصمة الذاكرة) للعمليات الخطية البسيطة من الناحية المفاهيمية مثل strings.Contains (مثل cmd/go/internal/cfg ) أو ، بشكل حاسم ، template.Parse .

بالنسبة لحالات الاستخدام هذه ، يبدو أنه من المثالي السماح للمتصل باختيار ما إذا كان سيعامل النقطة بأكملها على أنها (يفترض أنها معينة بالذاكرة) 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 دعم ضغط ، مما يشير إلى أنه مطلب شائع جدًا.

صحيح أنه يمكن للمرء أن يقوم بالضغط كخطوة ما قبل الإنشاء في الخارج ، ولكن هذا لا يزال يتطلب 1) أداة للقيام بالضغط 2) فحص نوع من assets.zip blob في vcs 3) ربما يكون أداة مساعدة مكتبة حول تضمين api للتراجع عن الضغط. عند هذه النقطة ليس من الواضح ما هي الفائدة على الإطلاق.

ثلاثة من الأهداف المدرجة في الاقتراح الأولي هي:

  • لا تحقق في الملفات التي تم إنشاؤها
  • اجعل go install / go build قم بالتضمين تلقائيًا
  • تخزين الأصول مضغوطة في الثنائي عند الاقتضاء

إذا قرأنا الثانية من هذه على أنها "لا تتطلب أداة منفصلة للتضمين" ، فإن عدم دعم الملفات المضغوطة بشكل مباشر أو غير مباشر يفشل في تحقيق كل هذه الأهداف الثلاثة.

هل هذا يجب أن يكون مستوى الحزمة؟ يبدو مستوى الوحدة النمطية أفضل من حيث الدقة حيث أن وحدة واحدة على الأرجح = مشروع واحد.

نظرًا لأن هذا الدليل لن يحتوي على كود Go † فهل يمكن أن يكون شيئًا مثل _static ؟

† أو ، إذا كان الأمر كذلك ، فسيتم التعامل معها على أنها وحدات بايت عشوائية ينتهي اسمها بـ ".go" بدلاً من كود Go الذي سيتم تجميعه

إذا كان دليلًا خاصًا واحدًا ، فيمكن أن يكون المنطق مجرد إهدار في أي شيء وكل شيء في شجرة الدليل تلك. يمكن أن تتيح لك حزمة التضمين السحرية القيام بشيء مثل embed.Open("img/logo.svg") لفتح ملف في دليل فرعي لشجرة الأصول.

تبدو الأوتار جيدة بما فيه الكفاية. يمكن نسخها بسهولة إلى []byte أو تحويلها إلى Reader . يمكن استخدام إنشاء الكود أو المكتبات لتوفير واجهات برمجة تطبيقات أكثر تخصصًا والتعامل مع الأشياء خلال 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. يستخدم واجهة برمجة تطبيقات المكتبة القياسية للعمل مع الملفات.

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: ينفخ الحل المستند إلى إنشاء سجل 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 الملفات اللازمة للإصدار

أعتقد أن التدفق المعتاد لذلك ، والذي استخدمته بشكل عام ، على الرغم من أن الأمر استغرق قليلاً من التعود عليه ، هو استخدام go generate بالكامل كأداة نهاية التطوير والالتزام بالملفات التي يولد.

bradfitz لا تحتاج إلى تنفيذ http.FileSystem نفسها. إذا كان التطبيق يوفر نوعًا يستخدم http.File ، فسيكون من التافه لأي شخص ، بما في ذلك حزمة stdlib http ، توفير غلاف حول الوظيفة 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 ميغا بايت - وهو أمر رائع ، ولكنه أيضًا ليس أداة فصل واضحة للوظائف لمعظم التطبيقات التي يمكنني التفكير فيها . لا سيما إذا كانت معظم الأصول "الأثقل" عبارة عن ملفات مثل JPEGs أو PNGs مضغوطة جيدًا بالفعل.

ماذا عن إبقاء الضغط خارجًا في الوقت الحالي وإضافته إذا غاب عنه الكثير من الناس؟ (ويمكن القيام به بدون تكاليف لا داعي لها)

للإضافة إلى تعليق sakjur أعلاه: يبدو الضغط متعامدًا بالنسبة لي. أرغب عمومًا في ضغط أرشيف ثنائي أو إصدار كامل ، وليس فقط الأصول. لا سيما عندما يمكن أن تصل ثنائيات Go في Go بسهولة إلى عشرات الميجابايت دون أي أصول.

mvdan أعتقد أن أحد الرموز المتحركة . الاستثناء الوحيد هو مواقع الويب التي تستخدم html/template . لذلك ، في النهاية ، قد ينتهي بك الأمر باستخدام نوع من "Makefile" على أي حال أو تحميل محتوى تمت معالجته مسبقًا. بهذا المعنى ، أعتقد أن علامة سطر الأوامر ستعمل بشكل أفضل مع أدوات أخرى غير التعليقات.

أعتقد أن أحد مخاوفي هو أنه في كثير من الأحيان عندما أرى التضمين مع بعض المعالجة المسبقة الأخرى: التصغير ، وتجميع الحروف المطبوعة ، وضغط البيانات ، وتكسير الصور ، وتغيير حجم الصورة ، وأوراق الرموز المتحركة. الاستثناء الوحيد هو مواقع الويب التي تستخدم html / template فقط.

شكرًا ، هذه نقطة بيانات مفيدة. ربما لم تكن الحاجة إلى الضغط شائعة كما كانت تبدو. إذا كان الأمر كذلك ، فأنا أوافق على أنه من المنطقي ترك الأمر.

ليس الأمر كما لو أن تضمين الملف سيكون عديم الفائدة بدون ضغط ، فالضغط أمر جيد يجب أن يتم تقليل الحجم الثنائي من 100 ميغا بايت إلى 50 ميغا بايت - وهو أمر رائع ، ولكنه أيضًا ليس أداة فصل واضحة للوظائف لمعظم التطبيقات التي يمكنني التفكير فيها .

الحجم الثنائي هو صفقة كبيرة للعديد من مطوري go (https://github.com/golang/go/issues/6853). اذهب إلى معلومات تصحيح أخطاء 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.

ومع ذلك ، فإن هذا يعني أيضًا أنه يتعين على المستخدمين كتابة ماهية مسار الدليل الثابت يدويًا.

أعتقد أن دليل البائع واصطلاحات _test.go هي أمثلة رائعة على كيفية تسهيل العمل مع Go وهاتين الميزتين كثيرًا.

لا أذكر أن العديد من الأشخاص طلبوا خيار تخصيص اسم دليل البائع أو لديهم القدرة على تغيير اصطلاح _test.go إلى شيء آخر. ولكن إذا لم يقدم Go أبدًا ميزة _test.go ، فسيبدو الاختبار في Go مختلفًا كثيرًا اليوم.

لذلك ، ربما يعطي اسم أقل عمومية من static فرصًا أفضل لعدم الاصطدام ، وبالتالي فإن وجود دليل تقليدي (على غرار البائع و _test.go) يمكن أن يكون تجربة مستخدم أفضل مقارنة بالتعليقات السحرية.

أمثلة على الأسماء التي يحتمل أن تكون منخفضة التصادم:

  • _embed - يتبع اصطلاح _test.go
  • go_binary_assets
  • يتبع .gobin اصطلاح git
  • runtime_files - بحيث يتطابق مع البنية runtime.Files

أخيرًا ، تمت إضافة الدليل vendor في Go 1.5. سو ، ربما ليس من السوء إضافة اتفاقية جديدة الآن؟ 😅

أعتقد أنه يجب أن يفضح قراءة mmap فقط []byte . مجرد وصول أولي إلى الصفحات من الملف القابل للتنفيذ ، مقسمًا إلى صفحات بواسطة نظام التشغيل حسب الحاجة. يمكن توفير كل شيء آخر علاوة على ذلك ، فقط bytes.NewReader .

إذا كان هذا غير مقبول لسبب ما ، يرجى تقديم ReaderAt وليس فقط ReadSeeker ؛ هذا الأخير تافه للبناء من الأول ، لكن الطريقة الأخرى ليست جيدة: ستحتاج إلى كائن المزامنة (mutex) لحماية الإزاحة الفردية ، وإفساد الأداء.

ليس الأمر كما لو أن تضمين الملف سيكون عديم الفائدة بدون ضغط ، فالضغط أمر جيد يجب أن يتم تقليل الحجم الثنائي من 100 ميغا بايت إلى 50 ميغا بايت - وهو أمر رائع ، ولكنه أيضًا ليس أداة فصل واضحة للوظائف لمعظم التطبيقات التي يمكنني التفكير فيها .

الحجم الثنائي هو صفقة كبيرة للعديد من مطوري go (# 6853). انتقل إلى ضغط معلومات تصحيح أخطاء DWARF خصيصًا لتقليل الحجم الثنائي ، على الرغم من أن هذا يأتي بتكلفة لربط الوقت (# 11799 ، # 26074). إذا كانت هناك طريقة سهلة لخفض الحجم الثنائي إلى النصف ، أعتقد أن المطورين سوف يقفزون في هذه الفرصة (على الرغم من أنني أشك في أن المكاسب هنا ستكون بهذا القدر تقريبًا).

هذه بالتأكيد نقطة عادلة ويمكنني أن أرى كيف يمكن النظر إلى حجتي على أنها حجة لصالح الإهمال فيما يتعلق بأحجام الملفات. كان ذلك ليس في نيتي. تتماشى وجهة نظري بشكل أكبر مع شحن هذه الميزة بدون ضغط والتي ستظل مفيدة للبعض ، ويمكن أن توفر ملاحظات مفيدة ورؤى حول كيفية إضافة الضغط بشكل صحيح بطريقة تبدو مناسبة على المدى الطويل. قد تتضخم الأصول بطريقة لا يرجح أن تفعلها معلومات تصحيح الأخطاء ويسهل على مطوري الحزم التي تم تثبيتها / استيرادها من قبل الآخرين تقليل أداء الإنشاء دون داع إذا كان التنفيذ يجعل من السهل القيام بذلك.

هناك خيار آخر يتمثل في جعل ضغط الأصول علامة بناء وترك الحل الوسط بين حجم الإنشاء والوقت للمُنشئ بدلاً من المطور. سيؤدي ذلك إلى تقريب القرار من المستخدم النهائي للثنائي الذي يمكنه اتخاذ قرار بشأن ما إذا كان الضغط يستحق العناء. Otoh ، هذا من شأنه أن يجازف بإنشاء مساحة أكبر للاختلافات بين التطوير والإنتاج ، لذلك فهي ليست طريقة واضحة أفضل من أي شيء آخر ، وهي ليست شيئًا أشعر أنني أرغب في الدفاع عنه.

تقوم أداة تضمين أصولي الحالية بتحميل المحتوى من ملفات الأصول عند إنشائها باستخدام -tags dev . من المحتمل أن تكون اتفاقية كهذه مفيدة هنا أيضًا ؛ تقصر دورة التطوير بشكل ملحوظ عند العبث بـ HTML أو قالب.

إذا لم يكن الأمر كذلك ، فسيتعين على المتصل أن يلتف هذه الآلية ذات المستوى الأدنى ببعض أغلفة *_dev.go و *_nodev.go وتنفيذ تحميل غير مضمن لسيناريو dev . ليس بالأمر الصعب ، ولكن هذا الطريق سيؤدي فقط إلى انفجار مماثل للأدوات التي يصفها التعليق الأول على هذه المسألة. سيتعين على هذه الأدوات أن تفعل أقل من اليوم ، لكنها ستظل تتضاعف.

أعتقد -tags dev فشله في العمل عند تشغيل خارج وحدة العودة ستكون معقولة (لا يمكن معرفة من أين لتحميل الأصول من).

ماذا عن go tool embed الذي يأخذ المدخلات وينتج ملفات الإخراج Go بتنسيق خاص يتعرف عليه الكمبيوتر كملفات مضمنة يمكن الوصول إليها بعد ذلك من خلال runtime/emved أو شيء ما. ثم يمكنك فقط عمل //go:generate gzip -o - static.txt | go tool embed -o static.go بسيط.

الجانب السلبي الكبير ، بالطبع ، هو أنه يتعين عليك بعد ذلك الالتزام بالملفات التي تم إنشاؤها.

DeedleFake بدأت هذه المشكلة بـ

استخدام go: ينفخ الحل المستند إلى إنشاء سجل git بنسخة ثانية (وأكبر قليلاً) من كل ملف.

Woops. لا تهتم. آسف.

ليس الأمر كما لو أن تضمين الملف سيكون عديم الفائدة بدون ضغط ، فالضغط أمر جيد يجب أن يتم تقليل الحجم الثنائي من 100 ميغا بايت إلى 50 ميغا بايت - وهو أمر رائع ، ولكنه أيضًا ليس أداة فصل واضحة للوظائف لمعظم التطبيقات التي يمكنني التفكير فيها .

الحجم الثنائي هو صفقة كبيرة للعديد من مطوري go (# 6853). انتقل إلى ضغط معلومات تصحيح أخطاء DWARF خصيصًا لتقليل الحجم الثنائي ، على الرغم من أن هذا يأتي بتكلفة لربط الوقت (# 11799 ، # 26074). إذا كانت هناك طريقة سهلة لخفض الحجم الثنائي إلى النصف ، أعتقد أن المطورين سوف يقفزون في هذه الفرصة (على الرغم من أنني أشك في أن المكاسب هنا ستكون بهذا القدر تقريبًا).

إذا كانت هناك حاجة لذلك ، فسيكون لدى الأشخاص البيانات المضغوطة الملتزمة والمضمنة ، وستكون هناك حزم لتوفير طبقة بين runtime.Embed والمستهلك النهائي الذي يقوم بإلغاء الضغط بشكل مضمّن.

وبعد عام أو عامين من الآن سيكون هناك مشكلة جديدة حول إضافة الضغط ويمكن فرزها بعد ذلك.

أقول هذا كواحد من 15 معيارًا متنافسًا عندما كتبت goembed :)

كتب @ tv42 :

أعتقد أنه يجب أن يفضح قراءة mmap فقط []byte . مجرد وصول أولي إلى الصفحات من الملف القابل للتنفيذ ، مقسمًا إلى صفحات بواسطة نظام التشغيل حسب الحاجة.

هذا التعليق ضائع بسهولة وهو ذو قيمة مذهلة.

@ tv42 ،

أعتقد أنه يجب أن يفضح بايت للقراءة فقط []. مجرد وصول أولي إلى الصفحات من الملف القابل للتنفيذ ، مقسمًا إلى صفحات بواسطة نظام التشغيل حسب الحاجة. يمكن توفير كل شيء آخر فوق ذلك ، فقط بالبايت.

النوع الذي هو بالفعل للقراءة فقط هو string . أيضًا: يوفر حجمًا ، بخلاف io.ReaderAt ، ولا يعتمد على المكتبة القياسية. ربما هذا ما نريد الكشف عنه.

النوع الذي هو بالفعل للقراءة فقط هو string .

لكن النظام البيئي بالكامل Write إلخ يعمل على []byte . إنها براغماتية بسيطة. لا أرى أن هذه الخاصية للقراءة فقط تمثل مشكلة أكثر مما تقوله مستندات io.Writer.Write

يجب ألا تقوم الكتابة بتعديل بيانات الشريحة ، حتى مؤقتًا.

هناك جانب سلبي محتمل آخر وهو أنه عند تضمين دليل بـ go:generate يمكنني التحقق من ناتج git diff ومعرفة ما إذا كانت هناك أي ملفات عن طريق الخطأ. مع هذا الاقتراح -؟ ربما يقوم الأمر go بطباعة قائمة الملفات التي يتم تضمينها؟

@ tv42

لكن النظام البيئي الكامل لـ Write etc يعمل على [] بايت.

html/template الرغم من ذلك ، يعمل

يتيح لك Go بالفعل استخدام -ldflags -X لتعيين بعض السلاسل (مفيد لضبط إصدار git ، ووقت الترجمة ، والخادم ، والمستخدم ، وما إلى ذلك) ، فهل يمكن تمديد هذه الآلية لتعيين io.Readers بدلاً من السلاسل؟

bradfitz هل تقترح استخدام السلاسل هنا حتى للبيانات التي ليست نصية؟ من الشائع تضمين ملفات ثنائية صغيرة مثل الرموز والصور الصغيرة وما إلى ذلك.

@ tv42 قلت Write لكنني أفترض أنك تقصد Read . يمكنك تحويل string إلى io.ReaderAt باستخدام strings.NewReader ، لذلك لا يبدو استخدام سلسلة بمثابة حاجز هناك.

andreynering يمكن أن string على أي تسلسل من البايت.

يمكن أن تحتوي A string على أي تسلسل من البايت.

نعم ، لكن نيتها الأساسية هي الاحتفاظ بالنص ، وليس البيانات التعسفية. أفترض أن هذا قد يسبب بعض الالتباس ، لا سيما لمطوري Go عديمي الخبرة.

لقد فهمت الفكرة تمامًا ، على الرغم من ذلك. شكرا للتوضيح.

تضمين التغريدة

Read المفترض أن يقوم Read بتحويل الشريحة التي تم تمريرها. ولكن Write ليس كذلك. ومن ثم فإن الوثائق Write تقول أن هذا غير مسموح به. لا أرى أي شيء أحتاجه أكثر من توثيق أنه يجب على المستخدمين عدم الكتابة إلى []byte إرجاعه.

فقط لأن strings.Reader موجود لا يعني أن io.WriteString سيجد تنفيذًا فعالًا لكتابة السلاسل. على سبيل المثال ، لا يحتوي TCPConn على WriteString .

أنا أكره أن يقوم Go بتضمين ميزة جديدة مثل هذه فقط لفرض نسخ جميع البيانات فقط لكتابتها على المقبس.

أيضًا ، الافتراض العام هو أن السلاسل قابلة للطباعة من قِبل الإنسان وأن []byte غالبًا ليست كذلك. سيؤدي وضع ملفات JPEG في سلاسل إلى حدوث الكثير من المحطات الطرفية الفاسدة.

تضمين التغريدة

يعمل html / template مع السلاسل.

نعم ، هذا غريب ، فهو يأخذ الملفات فقط باسم المسار وليس كقراء. ردان:

  1. لا يوجد سبب لعدم احتواء البيانات المضمنة على كلتا الطريقتين Bytes() []byte و String() string .

  2. نأمل ألا تقوم بتحليل قالب في كل طلب ؛ بينما يتعين عليك حقًا إرسال بيانات JPEG إلى مقبس TCP لكل طلب يطلب ذلك.

@ tv42 يمكننا إضافة طرق WriteString حسب الحاجة.

لا أعتقد أن الاستخدام الأكثر شيوعًا لهذه الوظيفة هو كتابة البيانات غير المعدلة ، لذلك لا أعتقد أنه يجب علينا تحسين هذه الحالة.

لا أعتقد أن الاستخدام الأكثر شيوعًا لهذه الوظيفة هو كتابة البيانات غير المعدلة ،

أعتقد أن الاستخدام الأكثر شيوعًا لهذه الوظيفة سيكون لخدمة أصول الويب ، الصور / js / css ، غير المعدلة.

لكن لا تأخذ كلامي في ذلك ، فلنلقِ نظرة على بعض مستوردي ملفات Brad's:

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



md5-f8b48fccd03599094034bf2b507e9e67



#fileembed pattern .*\.js$

وما إلى ذلك وهلم جرا..

بالنسبة إلى البيانات القصصية: أعلم أنه إذا تم تنفيذ ذلك ، فسأستخدمه على الفور في مكانين في العمل ، وسيكون كلاهما لتوفير وصول غير معدل إلى الملفات النصية الثابتة. نستخدم الآن خطوة //go:generate لتحويل الملفات إلى سلاسل ثابتة (تنسيق سداسي عشري).

سأصوت للحزمة الجديدة بدلاً من التوجيه. أسهل بكثير في الإمساك بها ، وأسهل في التعامل معها / إدارتها ، وأسهل بكثير في التوثيق والتوسيع. على سبيل المثال ، هل يمكنك بسهولة العثور على وثائق لتوجيه Go مثل "go: create"؟ ماذا عن التوثيق الخاص بحزمة "FMT"؟ هل ترى إلى أين أذهب بهذا؟

لذلك ، بدلاً من ذلك ، يمكن أن يكون لدى Go أداة التضمين "الرسمية" الخاصة بها والتي يتم تشغيلها افتراضيًا على go build

andreynering أعلم أن مديري الحزم وأدوات اللغة الأخرى تسمح بذلك ، لكن تشغيل التعليمات البرمجية / الأوامر العشوائية في وقت

يتبادر إلى ذهني شيئان إضافيان عند التفكير في هذه الميزة:

  • كيف يعمل تضمين الملفات تلقائيًا مع ذاكرة التخزين المؤقت للإصدار؟
  • هل تمنع التكوينات القابلة للتكرار؟ إذا تم تغيير البيانات بأي شكل من الأشكال (مثل ضغطها) ، فيجب أن تأخذ قابلية التكاثر في الاعتبار.

تم إنشاء stuffbin ، المرتبط في التعليق الأول ، للسماح لتطبيقات الويب المستضافة ذاتيًا بتضمين أصول ثابتة (HTML ، JS ...). يبدو أن هذه حالة استخدام شائعة.

باستثناء مناقشة الترجمة / الضغط ، هناك نقطة أخرى مؤلمة تتمثل في عدم وجود تجريد لنظام الملفات في stdlib للأسباب التالية:

  • على جهاز المطوِّر ، لا يلزم تحميل العديد من عمليات الإنشاء والبنيات go run عبء النفقات العامة لتضمين الأصول (أثناء ضغطها اختياريًا). سيسمح تجريد نظام الملفات بسهولة _failover_ لنظام الملفات المحلي أثناء التطوير.

  • يمكن أن تتغير الأصول بنشاط أثناء التطوير ، على سبيل المثال ، واجهة Javascript كاملة في تطبيق ويب. ستسمح القدرة على التبديل بسلاسة بين نظام التضمين ونظام الملفات المحلي بدلاً من الأصول المضمنة بتجنب تجميع وإعادة تشغيل ثنائي Go لمجرد تغيير الأصول.

تحرير: في الختام ، إذا كانت حزمة التضمين يمكن أن تعرض واجهة تشبه نظام الملفات ، فهذا أفضل من http.FileSystem ، فسيحل هذه المشكلات.

القدرة على التبديل بسلاسة بين التضمين ونظام الملفات المحلي

بالتأكيد هذا يمكن تنفيذه على مستوى التطبيق ، وهو خارج نطاق هذا الاقتراح ، أليس كذلك؟

بالتأكيد هذا يمكن تنفيذه على مستوى التطبيق ، وهو خارج نطاق هذا الاقتراح ، أليس كذلك؟

آسف ، لقد أدركت للتو ، الطريقة التي صاقتها بها غامضة. لم أكن أقترح تطبيق نظام ملفات داخل حزمة التضمين ، ولكن مجرد واجهة ، شيء أفضل من http.FileSystem . سيمكن ذلك التطبيقات من تنفيذ أي نوع من التجريد.

تحرير: مطبعي.

knadh أوافق تمامًا على أنه يجب أن يعمل عندما تستخدم فقط go run أيضًا ، الطريقة التي تتعامل بها Packr مع هذا أمر رائع حقًا. إنه يعرف مكان ملفاتك ، إذا لم تكن مضمنة في التطبيق ، فإنه يقوم بتحميلها من القرص لأنه يتوقع أن يكون "وضع التطوير" بشكل أساسي.

أصدر مؤلف Packr أيضًا أداة جديدة Pkger تركز بشكل أكبر على Go Modules. جميع الملفات هناك مرتبطة بجذر Go Module. تعجبني هذه الفكرة حقًا ، ولكن يبدو أن Pkger لم تنفذ تحميل التطوير المحلي من القرص. مزيج من كلاهما سيكون IMO مذهلاً.

لا أعرف ما إذا كان قد انتهى بالفعل من التشغيل ، ولكن في حين أن "نهج الحزمة المضمنة" سحري جدًا ، فإنه يوفر أيضًا بعض الذهول لأن الأداة يمكن أن تستنتج ما يجب فعله بالملف بناءً على المكالمة. على سبيل المثال ، قد يكون 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 المناسبة. لكنني أردت أن أذكر هذا ، لأنه يسمح بالحصول على مزايا الضغط ودمج النسخة الصفرية مع عدم وجود أي مقابض فعلية.

[عدل] أيضًا ، بالطبع ، دعنا نضيف هذه الأشياء المشوشة الإضافية بعد الحقيقة ، مع التركيز على مجموعة ميزات أكثر بساطة أولاً [/ تحرير]

إذا وجد فقط استدعاءات FileReader ...

هذا من شأنه أن يمنع استخدام الطرق الأخرى عن طريق التفكير.

[عدل] في الواقع ، أعتقد أن التداعيات أوسع من ذلك. إذا كان استخدام FileReaderAt يشير إلى أن البيانات يجب أن تكون غير مضغوطة ، فإن استخدام FileReaderAt() مع أي إدخال غير const يعني أنه يجب تخزين جميع الملفات غير مضغوطة.

لا أعرف ما إذا كان هذا جيدًا أم سيئًا. أعتقد فقط أن الاستدلال السحري لن يكون مفيدًا كما قد يبدو للوهلة الأولى.

وسيطة واحدة لصالح تعليق pragma ( //go:embed ) بدلاً من اسم دليل خاص ( static/ ): يتيح لنا التعليق تضمين ملف في أرشيف الاختبار لحزمة (أو أرشيف xtest ) ولكن ليس المكتبة قيد الاختبار. يجب أن يظهر التعليق فقط في ملف _test.go .

أتوقع أن يعالج هذا مشكلة شائعة مع الوحدات: من الصعب الوصول إلى بيانات الاختبار لحزمة أخرى إذا كانت هذه الحزمة في وحدة نمطية أخرى. يمكن أن توفر الحزمة بيانات لاختبارات أخرى بتعليق مثل //go:embedglob testdata/* في ملف _test.go . يمكن استيراد الحزمة إلى ملف ثنائي عادي بدون اختبار دون سحب هذه الملفات.

@ fd0 ،

كيف يعمل تضمين الملفات تلقائيًا مع ذاكرة التخزين المؤقت للإصدار؟

ستظل تعمل. سيتم خلط تجزئات محتوى الملف المضمّن بمفتاح ذاكرة التخزين المؤقت.

هل من الممكن (أو حتى فكرة جيدة) أن يكون لديك وحدة / حزمة / آلية من شأنها أن تكون شفافة عمليًا ، كما هو الحال من داخل التطبيق الخاص بك ، فأنت تحاول ببساطة فتح مسار مثل

internal://static/default.css

وستقوم وظائف الملف بقراءة البيانات من داخل الثنائي أو من موقع بديل
على سبيل المثال Package.Mount("internal[/<folder>.]", binary_path + "/resources/")

لإنشاء "داخلي: //" مع جميع الملفات في الملف الثنائي ، ارجع إلى المسار / الموارد القابلة للتنفيذ / إذا كان في وضع dev أو إذا لم يتم العثور على الملف في النظام الثنائي (وربما يتم إرسال تحذير أو شيء ما لأغراض التسجيل)

هذا من شأنه أن يسمح على سبيل المثال أن يكون

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

من الأفضل على الأرجح قفل الموقع البديل لمسار الملف التنفيذي عندما تكون في وضع "الإصدار" ، ولكن استرخِ في وضع التطوير (اسمح فقط للمجلدات في go_path أو شيء من هذا القبيل)

بشكل افتراضي ، فإن الحزمة "mounts" internal: // أو بعض الكلمات الرئيسية الأخرى ولكن دع المستخدم يعيد تسميتها إذا كان يريد .. ex .ReMount ("داخلي" ، "myCustomName") أو شيء من هذا القبيل.

شيء آخر ... هل من المنطقي التحقق من وقت التغيير / التعديل الأخير على موقع بديل وتجاوز الملف الداخلي تلقائيًا إذا كان هناك مثل هذا الملف خارج التطبيق (ربما يكون لديك علامة تسمح بذلك ، قابل للتكوين بواسطة المبرمج قبل الإنشاء)
قد يكون هذا مطلوبًا من أجل تصحيحات التطبيقات فائقة السرعة ، حيث لا تريد الانتظار حتى يتم إنشاء وتوزيع بنية جديدة .. يمكنك فقط إنشاء المجلد ونسخ الملف هناك ، وسوف يتحول الثنائي إلى الجديد ملف.

على نظام Windows ، هل من الممكن أو المنطقي استخدام الموارد (كما هو الحال في blob البيانات الثنائية في أحد الموارد)
وقليلاً غير ذي صلة ، ولكن ربما يمكن أن تتعامل هذه الحزمة أيضًا مع تجميع الرموز في الملف القابل للتنفيذ ، أو البيانات الواضحة ، أو ربما حتى الموارد الأخرى؟ أدرك أنه نظام Windows فقط ...
أتخيل أن المنشئ يمكنه تسجيل آخر تعديل / تغيير تواريخ الملفات في المجلدات البديلة وتشغيل "إنشاء blob من البيانات" فقط إذا تغير الملف وخزن blob مؤقتًا في مكان ما.
ربما يتم إنشاء ملف "ذاكرة التخزين المؤقت" فقط عندما يختار المستخدم تمكين الضغط على هذه الملفات المجمعة (إذا تقرر في النهاية ضغطها) ... إذا تم اختيار الضغط ، فيجب إعادة ضغط الملف المعين الذي تم تعديله فقط في وقت الإنشاء ، سيتم فقط نسخ الملفات الأخرى إلى الملف الثنائي من ذاكرة التخزين المؤقت.

إحدى المشكلات التي أراها هي أنه إذا كانت الحزمة تسمح بأسماء مخصصة ، فستحتاج إلى قائمة سوداء من نوع ما ، كما هو الحال في عدم السماح بـ "udp ، و file ، و ftp ، و http ، و https والعديد من الكلمات الرئيسية الشائعة الأخرى"

بالنسبة للتخزين كمصفوفة / سلسلة بايت أو ضغط ... أيًا كان القرار الذي يتم اتخاذه ، يجب أن يترك مجالًا للتحديث بسهولة في المستقبل ... على سبيل المثال ، يمكنك البدء بدون ضغط ولديك فقط قائمة بالإزاحات وأحجام الملفات و أسماء الملفات ولكن اجعل من الممكن إضافة الضغط بسهولة في المستقبل (مثل الطريقة zlib ، lzma ، الحجم المضغوط ، الحجم غير المضغوط إذا كان هناك حاجة لتخصيص ذاكرة كافية لفك حزم القطع وما إلى ذلك.

أنا شخصياً سأكون سعيدًا إذا كان الملف القابل للتنفيذ يمكن تعبئته بـ UPX أو ما يعادله ، أفترض أن الملف الثنائي سيتم تفريغه في الذاكرة وسيعمل كل شيء.

بعض الأفكار التي تتعلق بشكل غير مباشر:

  • أحب أسلوب package embed لاستخدامه بناء جملة Go
  • أعتقد أن الحاجة إلى الضغط والمعالجات الأخرى لا تتعلق بالحجم الثنائي ، بل تتعلق بالرغبة في تخزين أكثر أشكال المحتوى ملاءمةً للفروق في المستودع ، حتى لا تكون هناك حالة "غير متزامنة" حيث ينسى شخص ما التجديد والالتزام بصيغة مضغوطة عند تغيير "المصدر" ، ولكي تظل الحزمة "قابلة للشراء". بدون معالجة هذه النقاط ، نقوم فقط بحل مشكلة التقييس ، والتي قد تكون مقبولة ، لكنها لا تبدو مثالية.
  • أعتقد أننا يمكن أن نتجنب الحاجة إلى سلسلة الأدوات التي تحتاج إلى دعم فعال لضغط / تحويلات محددة إذا كان التفاعل 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 في الوقت الحالي على العديد من الميزات التي تسمح للملف المصدر بتوجيه toolchain لاستخدام ملفات مصدر إضافية ، وأعتقد أن الشيء الوحيد الذي لا يمثل تعليقًا سحريًا في الوقت الحالي هو الكلمة الأساسية import .

إذا قمنا بالضغط ، فلن يكون هناك نوع ترميز أو مقابض مستوى ضغط على الإطلاق. وهذا يعني أن وجود أي مقابض على الإطلاق هو أكبر حجة لعدم دعم الضغط على الإطلاق.

الخيار الوحيد الذي أرغب في الكشف عنه ، إن وجد ، هو: الوصول العشوائي أم لا. إذا لم تكن بحاجة إلى وصول عشوائي ، يمكن للأدوات ووقت التشغيل اختيار أي ضغط مناسب وعدم تعريضه للمستخدمين. ومن المحتمل أن يتغير / يتحسن بمرور الوقت.

لكنني وجدت جانب rsc في عدم وجود ضغط بسبب إدراكي: المحتوى الأكثر قابلية للضغط (HTML ، JS ، CSS ، إلخ) هو المحتوى الذي ما زلت تريد الوصول العشوائي إليه (إلى يتم تقديمها عبر ، على سبيل المثال ، http.FileServer ، والذي يدعم طلبات النطاق)

وبالنظر إلى الحجم المجمع لـ Perkeep's HTML / CSS / JS الذي قمنا بتضمينه ، فهو 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 / إلخ). وربما سواء كانوا يدعمون أو لا يدعمون الوصول العشوائي ، إذا انتهى بنا الأمر إلى القيام بأي ضغط.

gdamore ،

مجرد تجعد آخر هنا - لدي مشروع مختلف يستخدم شفرة مصدر DTrace (مضمن). هذا حساس للاختلافات بين n و rn.
...
إذا لم تفلح طرق العلاج المختلفة في حل المشكلة ، فهذه حجة ضد استخدام هذه الوسيلة الجديدة.

يمكنك أيضًا الغطس في وقت التشغيل لإزالة أي إرجاع يتم تضمينه من مستخدمي Windows قيد التشغيل ، انتقل إلى التثبيت. لقد كتبت مرشح io.Reader عدة مرات.

لكنني وجدت جانب

الضغط والوصول العشوائي لا يستبعد أحدهما الآخر تمامًا. انظر على سبيل المثال بعض المناقشات هنا: https://stackoverflow.com/questions/429987/compression-formats-with-good-support-for-random-access-within-archives

الضغط والوصول العشوائي لا يستبعد أحدهما الآخر تمامًا

نعم ، إذا أردنا البحث عن الحبيبات الخشنة مع بعض النفقات العامة للوصول إلى الموضع الصحيح. لقد قمت ببعض الأعمال في هذا الفضاء بتنسيق stargz الخاص بـ CRFS . لكني أخشى أن تكون النفقات العامة كبيرة بما يكفي بحيث لا نرغب في القيام بذلك تلقائيًا للناس. أفترض أنه يمكنك أيضًا تضخيمها ببطء في الذاكرة (وتكون قادرًا على إسقاطها على GCs ، مثل المزامنة. Pool) ، لكن لا يبدو الأمر يستحق ذلك.

أخشى أن تكون النفقات العامة كبيرة بما يكفي بحيث لا نرغب في القيام بذلك تلقائيًا للناس.

عادل بما يكفي. السؤال المهم هو ما إذا كنا نفضل واجهة برمجة تطبيقات تسمح لنا بتغيير آراءنا حول هذا الأمر بثمن بخس لاحقًا ، إذا تغيرت الاحتياجات أو إذا أظهرت التجارب أن النفقات العامة مقبولة.

bradfitz نقطة جيدة. ويمكنني بالتأكيد فعل ذلك. FWIW ، في الريبو الخاص بي ، قمت أيضًا بتكوين git ليكون أقل سمية عند رؤية ملفات .d. ما زلت أجد أن خاصية السلاسل المضمنة ذات الاقتباسات الخلفية مفيدة ، من حيث أنها يمكن التنبؤ بها ولا تخضع لأهواء git أو النظام.

ما كنت أتوصل إليه مع فكرة برنامج الترميز هو أن الضغط ليس هو التحويل الوحيد الذي قد يرغب فيه المرء وأن نوع برنامج الترميز الذي يوفره المستخدم يسمح لسلسلة الأدوات بتجاهل العلامات بخلاف "أي برنامج ترميز". أي مستويات ضغط ، أو الخوارزمية على الإطلاق ، ضغط أو غير ذلك ، يجب أن تكون خاصة بالبرنامج الترميز المستخدم. أتفق تمامًا على أن محاولة "دعم الضغط" بمعنى توفير مجموعة محددة من التنسيقات والمقابض ستكون بمثابة مطاردة جامحة مع جميع الاختلافات التي يمكن أن يطلبها الناس. في الواقع ، سأكون متحمسًا للغاية بشأن الاستخدامات غير الشائعة ، مثل المعالجة المسبقة لبيانات i18n ، ربما ، أو معالجة مجموعات البيانات كما هو الحال في latlong ، لذلك أعتقد أنه لا يزال من المفيد التفكير في الخيارات حولها.

لقد فكرت في طريقة أخرى لتوفير نفس المرونة التي قد تكون أكثر إمتاعًا. قد يكون التوجيه // go:embed استدعاء أمر ، تمامًا مثل // go:generate . لأبسط حالة ، شيء من هذا القبيل

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

يتمثل الاختلاف الرئيسي ، بالطبع ، في أن stdout استدعاء الأمر مضمن تحت الاسم المقدم. يستخدم المثال أيضًا حزمة تخيلية مع go run لإظهار كيف يتم ذلك على الأرجح لجعل نظام تشغيل الأمر مستقلاً ، نظرًا لأن cat قد لا يكون متاحًا في كل مكان تجميعات Go.

هذا يعتني بخطوة "التشفير" للتحويل ، وربما يمكن ترك مهمة "فك التشفير" للمستخدم. يمكن أن توفر حزمة وقت التشغيل / التضمين وحدات البايت التي طلب المستخدم من سلسلة الأدوات تضمينها ، بغض النظر عن الترميز. هذا جيد لأن المستخدم يعرف ما يجب أن تكون عليه عملية فك التشفير.

أحد الجوانب السلبية الكبيرة لهذا هو أنني لا أرى طريقة جيدة لتضمين كرة متعددة من الملفات بهذه الطريقة ، بخلاف البايتات المضمنة كونها مضغوطة أو شيء من هذا القبيل. قد يكون هذا جيدًا بما فيه الكفاية ، نظرًا لأنه لا يزال من الممكن استخدام الكرة الأرضية بواسطة أمر مضغوط ، وهي في الجانب المحدد حيث تهتم حقًا بالكرة الأرضية. ولكن يمكننا أيضًا الحصول على ميزتين من هذا الاقتراح ، واحدة للقيام ببساطة مضمنة والأخرى لتشغيل مولد التضمين.

أحد الجوانب السلبية المحتملة التي حدثت لي هو أنها تضيف خطوة مفتوحة إلى الإنشاء ، بافتراض أنه يجب معالجة التضمينات بواسطة go build ولا تتطلب استدعاء toolchain إضافيًا مثل go generate يفعل. أعتقد أن هذا جيد ، رغم ذلك. ربما يُتوقع من الأداة إدارة ذاكرة التخزين المؤقت الخاصة بها لتجنب تكرار العمليات باهظة الثمن ، أو ربما يمكنها الاتصال بسلسلة الأدوات لاستخدام ذاكرة التخزين المؤقت الخاصة بـ Go. يبدو هذا وكأنه مشكلة يمكن حلها وتتناسب مع الموضوع العام go build تفعل المزيد لنا (مثل جلب الوحدات).

هل أحد أهداف هذا المشروع هو التأكد من أن إنشاءات Go لا تتطلب أدوات خارجية ولا تذهب: إنشاء خطوط؟

إذا لم يكن الأمر كذلك ، فيبدو أنه من المفيد الحفاظ على الأشياء بسيطة ودعم شريحة أو سلسلة بايت فقط لأنه إذا كان المستخدم يريد الضغط مع الكثير من المقابض ، فيمكنه القيام بذلك في ملف التكوين الخاص به (أو ما شابه) ، والانتقال إلى إنشاء سطر ، وما إلى ذلك قبل البناء على أي حال ، لذلك لا يبدو أنه يستحق إضافتها إلى أي نتيجة ينتهي بها هذا الاقتراح.

إذا كان لا يتطلب الأمر Make أو ما شابه ذلك هدفًا ، فأنا أفترض أنه قد يكون من المنطقي استخدام الضغط ، لكنني شخصياً سأستخدم Make ، go ، وما إلى ذلك في أقرب وقت لإجراء الضغط ، ثم حافظ على التضمين البسيط وقم فقط بتضمين بعض البايت .

MustafaHosny اللهم امين

هل أحد أهداف هذا المشروع هو التأكد من أن إنشاءات Go لا تتطلب أدوات خارجية ولا تذهب: إنشاء خطوط؟

نعم فعلا.

إذا أراد الأشخاص استخدام go: إنشاء أو ملفات Makefiles أو أدوات أخرى ، فلديهم عشرات الخيارات اليوم.

نريد شيئًا محمولًا وآمنًا وصحيحًا يعمل بشكل افتراضي. (ولكي نكون واضحين: يعني الأمان أنه لا يمكننا تشغيل تعليمات برمجية عشوائية في وقت "بدء التثبيت" ، للسبب نفسه: لا يعمل الإنشاء افتراضيًا)

يارب احفظها

أعتقد أنه يمكننا تجنب الحاجة إلى سلسلة الأدوات التي تحتاج إلى دعم ضغط / تحويلات محددة بشكل فعال إذا كان تفاعل التضمين يمكن أن يوفر اختياريًا "برنامج ترميز".

لم يتم تنفيذ تعليمات برمجية عشوائية خلال go build .

لا يوجد تنفيذ تعليمات برمجية تعسفية أثناء إنشاء go.

نعم ، أرى ذلك الآن. أفترض أنه لا توجد طريقة للتوفيق بين وجود ملفات "مصدر" فقط ملتزمة بـ repo ، والرغبة في تضمين الملفات "المعالجة" ، وجعل الحزمة "قابلة للإصلاح" ، واحتفظ بـ go build بسيطًا وآمنًا. ما زلت أؤيد الضغط من أجل التوحيد هنا ، لكنني كنت أتمنى أن أحصل على كعكتتي وأكلها أيضًا ، على ما أعتقد. يستحق المحاولة! شكرا لإيجاد المشكلة!

تضمين التغريدة

هذا من شأنه أن يمنع استخدام الطرق الأخرى عن طريق التفكير.

لا توجد طرق فيما ذكرته ، فقط وظائف. لا يمكن اكتشافها في وقت التشغيل ولا توجد طريقة للإشارة إليها دون ذكرها بالاسم في المصدر. ولاحظ أن قيم الواجهة التي تُرجعها الوظائف المختلفة لا يجب أن تكون من نفس النوع - في الواقع أتوقع أن تكون إما أنواعًا غير مُصدرة بالطريقة المطلوبة بالضبط لتنفيذ تلك الواجهة أو مثيل *strings.Reader وما إلى ذلك ، مهما كان منطقيًا في السياق.

على الرغم من ذلك ، يمكن القول إن الفكرة تعاني من تمرير الوظائف المصدرة لحزمة التضمين كقيم. على الرغم من أن هذا لن يكون مشكلة على الأرجح - يحتوي التوقيع على نوع غير مُصدّر (انظر أدناه) ، لذلك لا يمكنك التصريح عن متغير أو وسيطة أو إرجاع من نوعه. يمكنك تمريرها إلى reflect.ValueOf أنفسهم، من الناحية النظرية. لا أعرف حتى ما إذا كان ذلك سيسمح لك بالاتصال بهم فعليًا (لا يزال يتعين عليك إنشاء قيمة لنوع المعلمة الخاص بهم ، وهو غير مُصدَّر. Dunno إذا كان الانعكاس يسمح بذلك).

ولكن مهما كان الأمر: سيظل من الممكن (والأبسط) أن تكون متشائمًا ببساطة في حالة استخدام أي دالة من المستوى الأعلى embed كقيمة وتفترض القيود التي تنشئها على جميع الملفات المضمنة. مما يعني أنك إذا قررت القيام بأشياء غريبة للغاية وغير مجدية باستخدام الحزمة المضمنة ، فستفقد بعض التحسينات (التي لا نقدم بها بالضرورة أي وعود بشأنها على أي حال). يبدو عادلا.

في الواقع ، أعتقد أن التداعيات أوسع من ذلك. إذا كان استخدام FileReaderAt يشير إلى أن البيانات يجب أن تكون غير مضغوطة ، فإن استخدام FileReaderAt () مع أي إدخال غير ثابت يعني أنه يجب تخزين جميع الملفات غير مضغوطة.

ليس من المنطقي السماح بمدخلات غير ثابتة ، حيث يجب أن يعرف اسم الملف بشكل ثابت لإجراء التضمين. لم يكن دقيقًا مني استخدام string كنوع من معلمات اسم الملف على الرغم من: كان يجب أن تكون بالفعل type filename string غير مُصدرة ولا تُستخدم أي شيء سوى وسيطات وظيفية. بهذه الطريقة ، من المستحيل تمرير أي شيء ليس ثابتًا خيطيًا غير مكتوب.

تضمين التغريدة

ليس من المنطقي السماح بمدخلات غير ثابتة

أعتقد أننا نتحدث عن أشياء مختلفة. أعني مدخلات وظائف الموصل (مثل FileReaderAt() ) أنا متأكد من أنك ستوافق على أن المدخلات غير الثابتة منطقية هناك.

ووجهة نظري هي: لنفترض أننا قمنا بتضمين 100 ملف ، ولكن لدينا استدعاء FileReaderAt(filename) ، حيث filename ليس ثابتًا ؛ لا توجد طريقة لمعرفة أي من الملفات المضمنة (إن وجدت) سيتم الوصول إليها بهذه الطريقة ، وبالتالي يجب تخزينها جميعًا غير مضغوطة.

flimzy كنا نتحدث عن نفس الشيء ، لم أكن أعتقد بجدية أن أسماء الملفات غير الثابتة ستكون منطقية :) الذي ، بالتفكير فيه ، كان خطأ وسهوًا. اسف بشأن ذلك. تعتبر التسهيلات الخاصة بالكرة الأرضية أو تضمين الدلائل بأكملها ثم تكرارها مهمة جدًا ، نعم. ما زلت أعتقد أن هذا يمكن حله - على سبيل المثال عن طريق اتخاذ القرار لكل مجموعة (dir / glob) والسماح فقط بتحديد هؤلاء بأسماء ثابتة - ولكن كما قلت: إنها ليست في الواقع واجهة برمجة تطبيقات سأعتبرها مناسبة جدًا لأداة Go بسبب كم هو السحر. لذا فإن الخوض في الأعشاب مثل هذا ربما يعطي المفهوم مساحة أكبر في المناقشة مما يستحق :)

هناك حالة أخرى لم أراها في الرسائل السابقة والتي جعلتني أفكر في تضمين ملف في ثنائي Go وهو استحالة توزيع حزمة مجمعة من مكتبة مشتركة C بشكل صحيح باستخدام إنشاء / تثبيت go عادي (تظل المكتبة المشتركة في مصادر).

لم أفعل ذلك في النهاية ، لكن هذا بالتأكيد سيجعلني أعيد النظر فيه لهذه الحالة. تحتوي مكتبة C بالفعل على الكثير من التبعيات التي سيكون من الأسهل توزيعها كمكتبة مشتركة. يمكن تضمين هذه المكتبة المشتركة بواسطة روابط Go.

رائع!!!

@ جوليو غيرا
أنا متأكد تمامًا من أنه لا يزال يتعين عليك استخراجها إلى القرص ثم استخدام dlopen و dlsym لاستدعاء وظائف C.

تحرير: أساء فهم منشورك قليلاً ، فقط أدركت أنك تتحدث عن إنشاء ثنائي للتوزيع

خارج أصول http الثابتة ، بالنسبة للنقاط الكبيرة المدمجة التي تحتاجها في مؤشر في الذاكرة ، سيكون من الجيد أن يكون لديك وظيفة تعيد المؤشر إلى الذاكرة المضمنة قيد التشغيل بالفعل. وإلا فسيتعين على المرء تخصيص ذاكرة جديدة وعمل نسخة من io.Reader. هذا من شأنه أن يستهلك ضعف الذاكرة.

glycerine ، مرة أخرى ، هذا هو string . A 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 . حاليا ليس لدينا حل جيد لهذا.

هذه هي حالات الاستخدام والتحديات. ربما يمكن للآخرين أن يحددوا بشكل صريح حالات الاستخدام الأخرى ومجموعة التحديات ذات الصلة ، لذلك يمكن لهذه المناقشات أن تتناول بوضوح مساحات المشاكل المختلفة و / أو تشير صراحة إلى أن هذا الجهد لن يعالج الاحتياجات المحددة لمساحة مشكلة معينة؟

شكرا مقدما للنظر.

نظرًا لأنني لا أرى أنه تم ذكره كحالة استخدام ، فسنستفيد أيضًا من هذا في دليل القوالب التي نصل إليها عبر القالب.

سأجد الطريقة الأنظف لتكون طريقة للاشتراك عبر go.mod . سيضمن ذلك توافقه مع الإصدارات السابقة (حيث سيتعين على المشاريع الحالية الاشتراك لاستخدامه) والسماح للأدوات (مثل go proxies) لتحديد الملفات المطلوبة. يمكن تحديث الأمر go mod init ليشمل إصدارًا افتراضيًا للمشاريع الجديدة لتسهيل استخدامه في المستقبل.

أستطيع أن أرى الحجج الخاصة بأن يكون الدليل اسمًا قياسيًا (إذا طلبنا الاشتراك ، فيمكن أن يكون اسمًا أكثر وضوحًا / أبسط) أو تحديد اسم الدليل في go.mod نفسه والسماح للمستخدمين اختر الاسم (ولكن مع وجود اسم افتراضي مقدم من go mod init .

في رأيي ، يحقق حل مثل هذا توازنًا في سهولة الاستخدام وأقل "سحرًا".

كتب jayconrod :

حجة واحدة لصالح تعليق pragma (// go: embed) بدلاً من اسم دليل خاص (ثابت /): يتيح لنا التعليق تضمين ملف في أرشيف الاختبار لحزمة (أو أرشيف xtest) ولكن ليس المكتبة تحت الاختبار.

هذه حقا ملاحظة جميلة. على الرغم من أننا إذا أردنا استخدام اسم الدليل الخاص ، فيمكننا استخدام آلية مألوفة: static لجميع الإصدارات ، و static_test لإصدارات الاختبار ، و static_amd64 لإصدارات amd64 ، و هكذا. لا أرى طريقة واضحة لتقديم دعم تعسفي لعلامة البناء.

قد يكون هناك ملف بيان في الدليل الثابت (الافتراضي عند إعطاء بيان فارغ يتضمن كل شيء باستثناء البيان) يتضمن الكرات ويسمح بتحديد علامات البناء وربما الضغط لاحقًا وما إلى ذلك.

أحد الجوانب الإيجابية هو أنه إذا وصلت قائمة go إلى dir الذي يحتوي على بيان ، فيمكنه تخطي تلك الشجرة على طريقة # 30058

أحد الجوانب السلبية هو أنه يمكن أن يحصل على هزات شديدة ولا شكرًا

يمكن أن تكون آلية المقبض الصفري البسيطة لتجميع الملفات في حزمة دليلًا خاصًا 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" بالفعل بتعيين سابقة لمسارات الاستيراد "السحرية". لقد عملت المنظمة البحرية الدولية بشكل جيد.

نظرًا لأنني لا أرى أنه تم ذكره كحالة استخدام ، فسنستفيد أيضًا من هذا في دليل القوالب التي نصل إليها عبر القالب.

هناك تحد آخر: بينما قد يحتوي البرنامج الثنائي على جميع الملفات المطلوبة ، فإن هذه الملفات ذاتها ستكون هي الإعدادات الافتراضية التي يوفرها المطور. ومع ذلك ، يجب أن تكون النماذج مثل النشر أو سياسات الخصوصية قابلة للتخصيص من قبل المستخدم النهائي. بقدر ما أرى أن هذا يعني أنه يجب أن يكون هناك طريقة ما لتصدير ملفاتي الافتراضية ومن ثم إما السماح للثنائي باستخدام الملفات المخصصة في وقت التشغيل ، أو طريقة ما لاستبدال الإصدارات المضمنة بالنسخ المخصصة.

أعتقد أنه يمكن القيام بذلك من خلال توفير واجهة برمجة تطبيقات بوظائف "تصدير" و "استبدال" مورد مضمن. يمكن للمطور بعد ذلك توفير بعض خيارات سطر الأوامر للمستخدم النهائي (باستخدام استدعاءات API المذكورة داخليًا).

كل هذا ، بالطبع ، بناءً على افتراض أنه سيكون هناك بالفعل نوع من التضمين الذي من شأنه أن يسهل النشر بالتأكيد.

شكرا لفتح القضية. في العمل ، فكرنا في نفس فكرة الميزة ، حيث نحتاج إلى تضمين الملفات في كل مشروع من مشاريع Golang تقريبًا. تعمل المكتبات الحالية بشكل جيد ، لكنني أعتقد أن هذه ميزة يصرخ عليها Golang. إنها لغة تحولت إلى ثنائي ثابت واحد. يجب أن تتبنى ذلك من خلال السماح لنا بتحميل ملفات الأصول المطلوبة في الثنائي ، مع واجهة برمجة تطبيقات صديقة وعالمية للمطورين.

أريد فقط تقديم تفاصيل التنفيذ المفضلة لدي بسرعة. تحدث العديد من الأشخاص عن توفير واجهة برمجة تطبيقات تلقائيًا لقراءة الملفات المضمنة منها ، بدلاً من الحاجة إلى إشارة أخرى مثل تعليق سحري. أعتقد أنه ينبغي أن يكون هذا هو الطريق الذي يجب اتباعه ، حيث إنه يوفر صياغة مألوفة وبرمجية للنهج. الذهاب للحصول على حزمة خاصة ، ربما 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)

أعتقد أيضًا أن مشكلات الأمان وإمكانية إعادة الإنتاج لا ينبغي أن تكون مشكلة إذا قصرنا الاستيراد على الملفات عند مستوى دليل واحد أو ربما أقل من دليل go.mod ، والذي تم ذكره سابقًا في سلسلة المحادثات بالفعل. ستحل مسارات التضمين المطلقة بالنسبة إلى مستوى الدليل هذا.

سيؤدي الفشل في الوصول إلى الملفات أثناء عملية الترجمة إلى حدوث خطأ في المترجم.

من الممكن أيضًا إنشاء أرشيف مضغوط خلف ثنائي ، بحيث يمكن أن يصبح بشكل فعال ثنائي استخراج ذاتي. ربما هذا مفيد في بعض حالات الاستخدام؟ هل هذا كتجربة هنا: https://github.com/sanderhahn/gozip

Go يحتوي بالفعل على "بيانات الاختبار". تستخدم اختبارات الوحدة IO العادية للقيام بكل ما يريدون القيام به. نطاق الاختبار يعني عدم شحن المحتوى. هذا كل ما يجب معرفته ، لا زخرفة ، ولا سحر ، ولا منطق حاوية مضغوطة ، ولا مراوغات قابلة للتكوين ، ولا معلومات META-INF. جميل وبسيط وأنيق. لماذا لا يكون لديك مجلد "بيانات" لتبعيات نطاق وقت التشغيل المجمعة؟

يمكننا بسهولة مسح مشاريع Go الموجودة في Github ea والتوصل إلى عدد من المشاريع التي تستخدم بالفعل مجلد "بيانات" ، وبالتالي تتطلب التكيف.

شيء آخر غير واضح بالنسبة لي. لمناقشة دليل static ليس واضحًا بنسبة 100٪ إذا كنا نناقش دليل static _source_ ، أو دليل static حيث سيتم توفير الملفات _at وقت التشغيل ؟

وهذا التمييز مهم بشكل خاص لأنه يتعلق بعملية التطوير ورمز التصحيح قيد التطوير.

mikeschinkel يتضح من الإنشاء :

اجعل 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. محول نظام الملفات)

يبدو أنه من الممكن اليوم ، بدون تنفيذ تعليمات برمجية للمستخدم ، تنفيذ وكيل وحدة عملي اليوم يزيل الملفات غير المستخدمة في الإنشاء. قد تعني بعض التصاميم أعلاه أنه يجب على وكلاء الوحدة النمطية تنفيذ كود المستخدم لمعرفة الملفات الثابتة التي سيتم تضمينها أيضًا.

لا أعتقد أنه سيكون هناك تغيير كبير هنا. على وجه الخصوص ، يمكن أن تتضمن C-code بالفعل أي ملف في الشجرة ، لذا فإن وكيل الوحدة الذي يرغب في القيام بذلك سيحتاج إلى تحليل C. يبدو في هذه المرحلة ، مهما كانت التعليقات السحرية أو واجهة برمجة التطبيقات التي نقدمها ستكون خطوة صغيرة.

قد تعني بعض التصاميم أعلاه أنه يجب على وكلاء الوحدة النمطية تنفيذ كود المستخدم لمعرفة الملفات الثابتة التي سيتم تضمينها أيضًا.

أعتقد أنه من الواضح تمامًا أن "أداة go يجب ألا تنفذ كود المستخدم أثناء الإنشاء" عبارة عن خط مرسوم في الرمال ولن يتم تجاوزه هنا. وإذا لم تتمكن أداة go من تنفيذ كود المستخدم ، فيجب أن يكون من الممكن معرفة الملفات التي يجب تضمينها بدونها.

لقد كنت أحاول تلخيص أفكاري المختلفة حول حالة الاستخدام هذه في شيء مقنع ، وبالتالي إجراء 1+ كبير لما اقترحه embed بدلاً من الاسم data .

  1. تبدو الملفات المضمنة وكأنها شيء يجب استيراده مقابل مجرد تعليق خاص أو حزمة سحرية. وفي مشروع Go ، الملف go.mod هو المكان الذي يمكن للمطور من خلاله تحديد الوحدات / الملفات المطلوبة ، لذلك من المنطقي توسيعها لدعم التضمين.

  2. علاوة على ذلك ، تشعر لي مجموعة من الملفات المضمنة أنها ستكون أكثر قيمة وقابلة لإعادة الاستخدام إذا كان من الممكن تضمين حزمة بدلاً من إضافة شيء مخصص إلى مشروع Go باستخدام بناء جملة لمرة واحدة. الفكرة هنا هي أنه إذا تم تنفيذ التضمينات كحزم ، فيمكن للأشخاص تطويرها ومشاركتها عبر Github ويمكن للآخرين استخدامها في مشاريعهم. تخيل الحزم التي يديرها المجتمع ومجانية الاستخدام على GitHub والتي تحتوي على:

    أ. ملفات البلدان التي يحتوي كل ملف فيها على جميع الرموز البريدية في ذلك البلد ،
    ب. ملف بكل سلاسل وكيل المستخدم المعروفة لتحديد المتصفحات ،
    ج. صور علم كل دولة في العالم
    د. معلومات مساعدة تفصيلية تصف الأخطاء الشائعة في برنامج Go ،
    ه. وما إلى ذلك وهلم جرا...

  3. مخطط عنوان URL جديد مثل goembed:// - أو ربما مخطط موجود - يمكن استخدامه لفتح الملفات وقراءتها من الحزمة ، مما يسمح بالاستفادة من واجهات برمجة تطبيقات معالجة الملفات الحالية بدلاً من إنشاء ملفات جديدة منها ، شيء مشابه لما يلي والذي سيكون متعلقًا بالتضمين الموجود في الحزمة الحالية:

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

مع المفاهيم المذكورة أعلاه ، لا شيء يشبه _ "السحر" _ ؛ سيتم التعامل مع كل شيء بأناقة من خلال آلية تبدو وكأنها مصممة لغرض معين. سيكون هناك القليل من التمديد المطلوب ؛ فعل جديد في go.mod ونظام URL جديد يمكن التعرف عليه داخليًا بواسطة Go. سيتم توفير كل شيء آخر كما هو من Go.

ماذا أفعل الآن

أستخدم code.soquee.net/pkgzip لهذا الآن (إنه تفرع من statik يغير واجهة برمجة التطبيقات لتجنب الحالة العامة والآثار الجانبية للاستيراد). سير العمل العادي (في تطبيق ويب على الأقل) هو تضمين الأصول المجمعة في ملف ZIP ثم عرضها باستخدام golang.org/x/tools/godoc/vfs/zipfs و golang.org/x/tools/godoc/vfs/httpfs .

go: تضمين النهج

هناك شيئان من شأنهما أن يمنعني من اعتماد نهج go:embed :

  1. لن تظهر التعليمات البرمجية التي تم إنشاؤها في الوثائق
  2. قد تكون الأصول مبعثرة في جميع أنحاء قاعدة الكود (هذا ينطبق على استخدام الأدوات الخارجية و go:generate أيضًا ، وهذا هو السبب في أنني أفضل استخدام ملف makefile لإنشاء مجموعات مختلفة من الأصول قبل البناء ، ثم أنا يمكن رؤيتهم جميعًا في makefile)

هناك أيضًا مشكلة لم أقم بتضمينها أعلاه لأنها قد تكون ميزة للبعض أن امتلاك الأصول جزء من حزمة (على عكس الوحدة بأكملها) يعني أن كل تعقيدات علامات الإنشاء ، واختبار الحزم ، إلخ. . تنطبق عليهم ، فنحن بحاجة إلى طريقة لتحديد ما إذا كانت عامة أو خاصة لتلك الحزمة ، وما إلى ذلك. يبدو أن هذا يمثل الكثير من التعقيد الإضافي للبناء.

الشيء الذي يعجبني فيه هو أنه يمكن كتابة المكتبات التي تجعل استيراد الأصول أسهل. على سبيل المثال. مكتبة تحتوي على ملف Go واحد يدمج خطًا فقط ، أو يمكن نشر بعض الرموز ويمكنني استيرادها مثل أي حزمة go أخرى. في المستقبل ، يمكنني الحصول على خط رمز بمجرد استيراده:

import "forkaweso.me/forkawesome/v2"

نهج الحزمة المضمنة

على الرغم من أنني أحب فكرة أن يكون كل هذا رمزًا واضحًا وعاديًا لـ Go ، إلا أنني أكره فكرة أن هذه ستكون حزمة سحرية أخرى لا يمكن تنفيذها خارج المكتبة القياسية.

هل سيتم تعريف مثل هذه الحزمة كجزء من مواصفات اللغة؟ إذا لم يكن هذا مكانًا آخر ، فسيقوم Go code بالتوقف بين التطبيقات المختلفة ، والتي تشعر أيضًا بالضعف. من المحتمل أن أستمر في استخدام أداة خارجية لمنع هذا الكسر.

أيضًا ، كما ذكر آخرون ، حقيقة أن هذا يتم إجراؤه في وقت الإنشاء يعني أن هذه الحزمة يمكن أن تأخذ فقط سلسلة حرفية أو ثوابت كوسيطات. لا توجد طريقة حاليًا لتمثيل هذا في نظام الكتابة ، وأظن أنه سيكون نقطة ارتباك. يمكن حل هذا عن طريق إدخال شيء مثل الوظائف الثابتة ، لكننا الآن نتحدث عن تغييرات لغوية رئيسية تجعلها غير بداية. لا أرى طريقة جيدة لإصلاح هذا بخلاف ذلك.

هجين

تعجبني فكرة النهج الهجين. بدلاً من إعادة استخدام التعليقات (التي تنتشر في كل مكان ، وفي ملاحظة شخصية ، أشعر فقط بالإجمال) ، أود أن أرى جميع الأصول موضوعة في مكان واحد ، على الأرجح الملف go.mod مثل الآخرين قال:

module forkaweso.me/forkawesome/v2

go 1.15

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

هذا يعني أنه لا يمكن تضمين الأصول أو استبعادها بواسطة علامات إنشاء عشوائية ، أو في حزم عشوائية (مثل حزمة الاختبار) بدون إنشاء وحدة منفصلة. أعتقد أن هذا التقليل من التعقيد قد يكون أمرًا مرغوبًا فيه (لا توجد علامة بناء مخفية في مكتبة تحاول استيرادها ، ولا يمكنك معرفة سبب عدم امتلاكك للأصل الصحيح لأن استيراد المكتبة يجب أن يتضمنها) ، ولكن YMMV. إذا كان هذا مرغوبًا فيه ، فلا يزال من الممكن استخدام تعليقات pragma like باستثناء أنها لا تنشئ رمزًا وبدلاً من ذلك استخدم نفس الأسلوب الذي أنا على وشك وصفه للإصدار go.mod .

وخلافا للاقتراح الأصلي فإن هذا لن يولد أي رمز. بدلا من ذلك ، وظائف على سبيل المثال. قراءة قسم البيانات في ملف ELF (أو مع ذلك يتم تخزين هذا على أي نظام تشغيل تستخدمه) ستتم إضافته عند الاقتضاء (على سبيل المثال ، os أو debug/elf ، إلخ.) و ثم ، اختياريًا ، سيتم إنشاء حزمة جديدة تتصرف تمامًا مثل الحزمة الموضحة في OP باستثناء أنه بدلاً من أن تكون سحرية وتقوم بالتضمين نفسه ، فإنها تقرأ فقط الملفات المضمنة (مما يعني أنه يمكن تنفيذها خارج المكتبة القياسية اذا رغب).

يعمل هذا حول مشكلات مثل الاضطرار إلى تقييد الحزمة السحرية للسماح فقط بالحرفيات المتسلسلة كوسيطات ، ولكن هذا يعني أنه من الصعب التحقق مما إذا كانت الأصول المضمنة مستخدمة بالفعل في أي مكان أو ينتهي بها الأمر إلى كونها ثقيلة الوزن. كما أنه يتجنب أي تبعيات جديدة بين حزم المكتبة القياسية ، لأن الحزمة الوحيدة التي تحتاج إلى استيراد أي شيء إضافي هي الحزمة الجديدة نفسها.

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

كما رأينا أعلاه ، فإن وضع الموارد في الوحدة لا يزال من الممكن أن يشمل نطاقها لتلك الوحدة المحددة إذا رغبت في ذلك. قد تحتاج واجهة برمجة التطبيقات الفعلية وكيفية تحديد أحد الأصول إلى بعض العمل.

فكرة أخرى: بدلاً من إضافة نوع جديد من الفعل embed في go.mod ، يمكننا تقديم نوع جديد من الحزم ، حزمة بيانات ، يتم استيرادها واستخدامها في go.mod في بالطريقة المعتادة. هذا رسم رجل القش.

إذا كانت الحزمة تحتوي بالضبط على ملف .go ، static.go ، وكان هذا الملف يحتوي فقط على تعليقات وعبارة حزمة ، فإن الحزمة هي حزمة بيانات. عند الاستيراد ، يملأ cmd / go الحزمة بالوظائف المصدرة مما يوفر الوصول إلى الملفات المضمنة فيها ، والتي يتم تضمينها في الملف الثنائي الناتج.

إذا كانت حزمة فعلية ، فهذا يعني أنه سيتم تطبيق قواعد internal ويمكن أن يكون لدينا عناصر تحكم في الوصول دون الإضافة إلى واجهة برمجة التطبيقات.

ماذا عن تضمين جميع الملفات والمجلدات الفرعية غير .go تلقائيًا (باتباع قواعد التعليمات البرمجية غير الفعلية) في الدليل بعد ذلك؟

إذا كانت الحزمة تحتوي بالضبط على ملف .go ، static.go ، وكان هذا الملف يحتوي فقط على تعليقات وعبارة حزمة ، فإن الحزمة هي حزمة بيانات.

هل سيتم إجراء هذا الفحص قبل تطبيق علامات البناء؟ إذا كان الأمر كذلك ، فهذا يبدو وكأنه حالة خاصة أخرى قد ترغب في تجنبها. إذا لم يكن الأمر كذلك ، فمن المحتمل جدًا أن يُنظر إلى الحزمة على أنها حزمة Go قياسية لبعض علامات الإنشاء ، وكحزمة بيانات للآخرين. يبدو هذا غريباً ، لكن ربما يكون مرغوبًا فيه؟

تضمين التغريدة
هذا من شأنه أن يسمح لأحد باستخدام الملفات المضمنة بعلامة واحدة وتحديد نفس fns / vars مثل الحزمة التي تم إنشاؤها وخدمة الملفات بطريقة أخرى (ربما عن بُعد؟) مع علامة أخرى.

سيكون من الجيد إذا كانت هناك علامة بناء لتوليد وظائف الغلاف بحيث يتعين على المرء فقط ملء الفراغات.

تضمين التغريدة

إذا كانت الحزمة تحتوي بالضبط على ملف .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 /

في الواقع ، هناك طريقة للقيام بذلك دون أن يكون تغييرًا في اللغة - تتطلب static.go أن تحتوي على وظائف لا تحتوي على أجسام تتطابق تمامًا مع ما سيملأه cmd / go.

تتطلب static.go أن تحتوي على وظائف لا تحتوي على هيكل تطابق تمامًا ما سيملأه cmd / go.

إذا كان ينشئ وظائف لكل ملف بدلاً من التقاط الكل embed.File() ، فسيتيح ذلك سهولة التحكم في تصدير كل أصل.

لذلك مثل الأشياء التي تم إنشاؤها ستبدو كما يلي:

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

منشور مدونة كتبته عن الملفات الثابتة منذ 4 أشهر. انظر الجملة الأخيرة في الاستنتاجات :-)

تضمين التغريدة

يمكنك عرض الوظائف في my.pkg / postalcode ووضع البيانات في my.pkg / postalcode / data (أو my.pkg / postalcode / Internal / data).

هذا - على الرغم من كونه غير أنيق - يمكن أن يعالج مخاوفي.

كيف يعمل التوافق العكسي؟

لا أرى كيف تنطبق مخاوف BC هنا. هل يمكنك التفصيل؟

كيف تضع علامة على حزمة البيانات على هذا النحو؟

مع كشف حساب embed في go.mod ؟

ربما لا أتبع ما تطلبه.

لكني سأقلبها. كيف تضع علامة على حزمة تحتوي على بيانات فقط على هذا النحو؟

ماذا تفعل إذا كانت الحزمة تحتوي على وظائف تتعارض مع تلك التي يضيفها cmd / go؟

  1. باستخدام النهج المقترح ، لا أعتقد أن هناك حاجة إلى cmd / go لإضافة أي وظائف.

  2. حتى لو احتاج cmd / go إلى إضافة وظائف ، أتصور أن سلوك التعارضات في الحزمة _ الموجودة_ سيكون _undefined_.

    يفترض الاقتراح أن المطور يتبع مبدأ المسؤولية الفردية وبالتالي يجب فقط إنشاء حزمة بها بيانات لتكون حزمة تتمحور حول البيانات وليس إضافة بيانات إلى حزمة قائمة على المنطق.

    بالطبع يمكن للمطور أن يضيف إلى حزمة موجودة وفي هذه الحالة يكون السلوك غير محدد. IOW ، إذا تجاهل المطور المصطلح فسيكون في منطقة مجهولة.

أنا لا أقول أن هذه ليس لديها إجابات ، فقط أنه من الأسهل عدم الاضطرار إلى الإجابة عليها.

إلا أنني أعتقد أن الإجابات بسيطة. على الأقل بالنسبة للأشخاص الذين طرحتهم حتى الآن.

أعتقد أن أي حل يضيف رموزًا أو قيمًا للرموز يجب أن يتم تحديد نطاقه ، وليس نطاق الوحدة. لأن وحدة الترجمة لـ Go عبارة عن حزمة وليست وحدة.

لذا فإن هذا يترك أي استخدام لـ go.mod لتحديد قائمة الملفات المراد استيرادها.

dolmen إذا كانت النتيجة النهائية في الحزمة الخاصة بها ، فسيكون النطاق نفسه وحدة نمطية.

urandom لا ، النطاق هو الحزمة (الحزم) التي تستورد الحزمة التي تم إنشاؤها. لكن على أي حال ، لا أعتقد أن الحزمة التي تم إنشاؤها بالكامل تقع في نطاق هذا الاقتراح.

urandom لا ، النطاق هو الحزمة (الحزم) التي تستورد الحزمة التي تم إنشاؤها. لكن على أي حال ، لا أعتقد أن الحزمة التي تم إنشاؤها بالكامل تقع في نطاق هذا الاقتراح.

بغض النظر عن كيفية تنفيذ هذا الاقتراح ، نظرًا لأن حزم الوحدات المختلفة ستستخدم النتيجة النهائية ، فمن المنطقي أن يتم تحديد تعريف ما يتم تضمينه على مستوى الوحدة النمطية. توجد سابقة لهذا بالفعل أيضًا ، في نظام جافا البيئي ، حيث تكون الملفات المضمنة محددة النطاق وتتم إضافتها من دليل سحري.

علاوة على ذلك ، يقدم go.mod أنظف طريقة لإضافة مثل هذه الميزة (لا توجد تعليقات سحرية أو أدلة سحرية) دون كسر البرامج الموجودة.

إليك شيء لم يتم ذكره بعد: أدوات معالجة مصدر API for Go (مترجم ، محللات ثابتة) لا تقل أهمية عن واجهة برمجة تطبيقات وقت التشغيل. هذا النوع من API هو القيمة الأساسية لـ Go التي تساعد على تنمية النظام البيئي (مثل go/ast / go/format و go mod edit ).

يمكن استخدام واجهة برمجة التطبيقات هذه بواسطة أدوات ما قبل المعالجة (بخطوات go:generate وجه الخصوص) للحصول على قائمة بالملفات التي سيتم تضمينها أو يمكن استخدامها لإنشاء المرجع.

في حالة الحزمة الخاصة ، لا أرى أي تغيير في التحليل اللغوي go.mod (أدوات go mod ) أو المحلل اللغوي go/ast .

تضمين التغريدة

_ "أعتقد أن أي حل يضيف رموزًا أو قيمًا للرموز يجب أن يكون محددًا بنطاق الحزمة ، وليس نطاق الوحدة. لأن وحدة الترجمة لـ Go هي حزمة ، وليست وحدة. لذا فإن هذا يترك أي استخدام لـ go.mod لتحديد قائمة من الملفات المراد استيرادها ".

ما هي الوحدة؟ الوحدات النمطية هي _ " مجموعة من حزم Go ذات الصلة والتي تم إصدارها معًا كوحدة واحدة ." _ وبالتالي يمكن أن تتكون الوحدة من حزمة واحدة ، ويمكن أن تكون الحزمة الواحدة عبارة عن وحدة نمطية بأكملها.

على هذا النحو ، فإن go.mod هو المكان الصحيح لتحديد قائمة الملفات المراد استيرادها بافتراض أن فريق Go يتبنى go.mod على التعليقات الخاصة والحزم السحرية. هذا ما لم وحتى يقرر فريق Go إضافة ملف go.pkg .

علاوة على ذلك ، إذا كان فريق Go سيقبل go.mod كمكان لتحديد ملفات التضمين ، فيجب على أي شخص يريد تضمين الملفات تقديم go.mod مع التعليمات embed ، والحزمة والذي يمثله الدليل الذي يوجد فيه الملف go.mod سيكون الحزمة التي تحتوي على الملفات المضمنة.

ولكن إذا لم يكن هذا ما يريده المطور ، فيجب عليهم إنشاء ملف go.mod ووضعه في دليل الحزمة التي يريدون أن تحتوي على ملفاتهم المضمنة.

هل هناك سيناريو مشروع تتخيله حيث لن تكون هذه القيود قابلة للتطبيق؟

mikeschinkel ، الوحدة النمطية هي مجموعة من الحزم المرتبطة. ومع ذلك ، فمن الممكن (ومن المعقول!) استخدام حزمة واحدة من وحدة دون سحب التبعيات المتعدية (والبيانات!) للحزم الأخرى داخل تلك الوحدة.

تكون ملفات البيانات بشكل عام تبعيات لكل حزمة ، وليست لكل وحدة نمطية ، لذلك يجب أن يتم تجميع المعلومات حول كيفية تحديد موقع تلك التبعيات مع الحزمة - وليس تخزينها كبيانات وصفية منفصلة على مستوى الوحدة النمطية.

تضمين التغريدة

يبدو أنه يمكن للمرء استبدال "ملفات البيانات" في رسالتك بـ "وحدات" وسيظل صحيحًا.
من الشائع جدًا أن يكون لديك وحدات نمطية معينة كاعتمادات لحزم معينة خاصة بك.
ومع ذلك ، فإننا نضعهم جميعًا في وضع التنقل.

urandom ، ليست كل الحزم في الوحدات المشار إليها في ملف go.mod مرتبطة بالثنائي النهائي. (وضع تبعية في ملف go.mod _not_ يعادل ربط تلك التبعية بالبرنامج.)

ميتا نقطة

من الواضح أن هذا شيء يهتم به الكثير من الناس ، وتعليق براد الأصلي في الجزء العلوي لم يكن اقتراحًا نهائيًا بقدر ما كان رسمًا أوليًا / نقطة بداية / دعوة للعمل. أعتقد أنه سيكون من المنطقي في هذه المرحلة إنهاء هذه المناقشة المحددة ، وجعل براد وربما شخصين آخرين يتعاونون في مستند تصميم مفصل ، ثم بدء عدد جديد لمناقشة هذا المستند المحدد (الذي لم تتم كتابته بعد) . يبدو أن هذا من شأنه أن يساعد في التركيز على ما أصبح محادثة مترامية الأطراف.

أفكار؟

لست متأكدًا من أنني أوافق على إغلاق هذا ، ولكن يمكننا عرض الاقتراح حتى يكون هناك مستند تصميم وقفل التعليقات قليلاً. (جميع التعليقات تقريبًا زائدة عن الحاجة في هذه المرحلة ، نظرًا لوجود عدد كبير جدًا من التعليقات التي يمكن للأشخاص قراءتها لمعرفة ما إذا كانت تعليقاتهم زائدة عن الحاجة ...)

ربما عندما يكون هناك مستند تصميم ، يمكن إغلاق هذا المستند.

أو أغلق هذا وأعد فتح # 3035 (مع تجميد التعليقات) بحيث تتعقبه قضية مفتوحة واحدة على الأقل.

آسف لفعل هذا بعد الحديث عن الإغلاق مباشرة ، لكن المناقشة القريبة قفزت مباشرة بعد تعليق bcmills وقبل أن أتمكن من التوضيح.

"_ومع ذلك ، من الممكن (ومن المعقول!) استخدام حزمة واحدة من وحدة دون سحب التبعيات المتعدية (والبيانات!) للحزم الأخرى داخل تلك الوحدة." _

نعم ، من الواضح أنه _ ممكن. _ ولكن تمامًا مثل أي ممارسة أفضل ، يمكن أن تكون أفضل ممارسة لحزم البيانات هي إنشاء حزمة واحدة لوحدة نمطية ، مما يحل مشكلتك. إذا كان هذا يعني وجود go.mod في الجذر و go.mod في الدليل الفرعي الذي يحتوي على حزمة البيانات ، فليكن.

أعتقد أنني أدافع عن أنك لا تجعل الكمال عدوًا للسلعة هنا ، حيث يتم تحديد الخير هنا على أنه go.mod كونه مكانًا مثاليًا لتحديد ملفات التضمين نظرًا لأنها بطبيعتها عبارة عن قائمة مكونات الوحدة.

عذرًا ، الحزم هي المفهوم الأساسي في Go ، وليس الوحدات النمطية.
الوحدات النمطية هي مجرد مجموعات من الحزم التي يتم إصدارها كوحدة.
لا تساهم الوحدات النمطية في دلالات إضافية بخلاف تلك الخاصة بالحزم الفردية.
هذا جزء من بساطتها.
يجب أن يكون أي شيء نقوم به هنا مرتبطًا بالحزم ، وليس الوحدات.

أينما حدث هذا ومع ذلك ، يجب أن تكون هناك طريقة للحصول على قائمة بجميع الأصول التي سيتم تضمينها باستخدام قائمة go (وليس فقط الأنماط المستخدمة).

التأجيل حتى يعمل "براد" وآخرون على إعداد مستند تصميم رسمي.

لا ينبغي أن يكون حظر ملفات معينة صعبًا للغاية ، خاصة إذا كنت تستخدم دليلًا 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 كاسم دليل خاص أمر محير بعض الشيء وأنا أفضل استخدام assets .
هناك فكرة أخرى لم أرها في سلسلة الرسائل وهي السماح باستيراد assets كحزمة ، على سبيل المثال import "example.com/internal/assets" . لا تزال واجهة برمجة التطبيقات المكشوفة بحاجة إلى تصميم ، ولكنها على الأقل تبدو أنظف من التعليقات الخاصة أو حزم جديدة على غرار runtime/files .

هناك فكرة أخرى لم أرها في الموضوع وهي السماح باستيراد الأصول كحزمة

تم اقتراح هذا هنا: https://github.com/golang/go/issues/35950#issuecomment -562966654

أحد المضاعفات هو أنه لتمكين التحقق من الكتابة ، تحتاج إما أن يكون هذا تغييرًا في اللغة أو توفير وظائف خالية من الجسد ليتم ملؤها بواسطة cmd / go .

هذه فكرة مشابهة ، لكن تصميم static.go يسمح بتحويل مسار استيراد عشوائي إلى حزمة بيانات ، بينما يعمل دليل assets مثل testdata ، internal أو vendor من حيث كونها "خاصة". أحد المتطلبات المحتملة التي يمكن أن يتطلبها assets هو عدم احتواء حزم Go (أو السماح فقط بالمستندات) ، أي للتوافق الضمني مع الإصدارات السابقة.

يمكن أيضًا دمج هذا مع واجهة برمجة تطبيقات runtime/files -thingy للحصول على الملفات. أي استخدام خام import s لتضمين أشجار الدليل مع الملفات ثم استخدام بعض حزم وقت التشغيل للوصول إليها. يمكن أن يكون حتى os.Open ، لكن من غير المحتمل أن يتم قبوله.

يتميز shurcooL/vfsgen معًا 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 / مورد". لها وظيفة واحدة على الأقل: func Read(name string) (io.Reader, bool) لقراءة الموارد المضمنة في الحزمة الحالية.

    • __تعديل__ لست متأكدًا من أن الحزم الأساسية تعمل بهذه الطريقة. قد تكون وظيفة حزمة خاصة تم إنشاؤها (على سبيل المثال ، func readresource(name string) (io.Reader, bool) )

  • إذا كنت تريد موارد في دليل فرعي ، فاجعل الدليل الفرعي حزمة عن طريق إضافة ملف go.res وملف واحد على الأقل .go . يقوم ملف go بتصدير واجهة برمجة التطبيقات العامة الخاصة بك للوصول إلى الموارد في حزمة الدليل الفرعي. يعد ملف go و API المُصدَّر ضروريين ، لأنه لا يتم تصدير الموارد من الحزم الأخرى تلقائيًا (حسب التصميم). يمكنك أيضًا تخصيص كيفية تصديرها بهذه الطريقة.

    • __Edit__ بدلاً من ذلك ، إذا كنت بحاجة إلى بنية دليل و / أو ضغط ، فاستخدم مورد tar. يسمح هذا بأشياء مثل حزم الويب ، والتي تتطلب بالفعل تجميعًا (وقد تستفيد من الضغط المسبق). أخذهم خطوة واحدة إلى القطران أمر بسيط.

  • __تعديل__ هل تحتاج إلى بيان؟ ما عليك سوى تضمين ملف go.res نفسه كمورد. لا نحتاج حتى إلى إنشاء وظيفة listresources.

بسيط للغاية. وظيفة جديدة واحدة. ملف جديد. لا توجد مسارات. لا يوجد ضغط. لا توجد صيغة جديدة. لا سحر. توسع. الوصول للقراءة فقط عن طريق القارئ (ولكن مفتوح لأنماط الوصول البديلة في المستقبل). تقترب من الصفر إمكانية كسر الحزم الموجودة. يتماشى مع الحزمة كونها البناء الأساسي في الذهاب.

__Edit__ بعد البحث في github language:go filename:go.res extension:res ، يبدو أن go.res سيكون اسم ملف آمنًا جدًا للاستخدام. لا توجد مطابقات في go repos ، ولا يوجد سوى عدد قليل منها في مستودعات non-go.

تعجبني فكرة @ chris.ackermanm. لكنني أفضل مزيجًا:

ملف go.res يحدد مساحة الاسم داخل الدليل.

هذا يسمح ل

  • متعدد ما دامت مساحة الاسم مختلفة
  • عدم معرفة الملفات من قبل والاضطرار إلى إنشاء قائمة

يجب أن يعالج الأخير إخراج webpack والإعجابات التي قد تغير التخطيط بسبب التحديثات والخيارات المختلفة ، كل ما يمكنك التفكير فيه.

فيما يتعلق بالضغط: أعتقد أنه ميزة أكثر من حيث عدم انفجار الأحجام الثنائية ويجب أن تكون شفافة بالنسبة للكود المستخدم.

في وقت لاحق يمكنك السماح بإعادة الكتابة مثل

filename => stored-as.png

فقط بلدي 2

@ sascha-andres يبدو أن البساطة الفائقة والسحر الصفري هو نغمة هذا الموضوع. اطلع على التعديلات التي أجريتها على تعليقي بخصوص اقتراحاتك.

أنا لا أحب التعيين. لا حاجة. هذا ممكن عن طريق كشف وظيفة القراءة الخاصة بك من حزمة منفصلة على أي حال ، والآن نحتاج إلى صيغة ملف جديدة ، أو شيء أكثر تعقيدًا من ملف لكل سطر.

أهلا

هذا الاقتراح رائع!

ولدي منهجي في التعامل مع الأصول المستخرجة. لا حاجة لإدخال أي أدوات أخرى غير GNU bintools. إنه نوع من القذرة ، لكنه يعمل جيدًا بالنسبة لي في الوقت الحالي. أريد فقط مشاركته ومعرفة ما إذا كان يساعد.

أسلوبي هو فقط تضمين الأصول الخاصة بي (مضغوطة باستخدام tar & gz) في قسم elf / pe32 مع objcopy ، وقراءتها عبر حزمة debug / elf و debug / pe32 مع الرمز البريدي عند الحاجة. كل ما أريد أن أتذكره هو عدم لمس أي قسم موجود. جميع الأصول غير قابلة للتغيير ثم يقوم الكود بقراءة المحتوى ومعالجته في الذاكرة.

أنا عديم الخبرة في تصميم اللغة أو تصميم المترجم. لذلك سأستخدم الطريقة الموضحة أعلاه واستخدام .goassets أو شيء من هذا القبيل كاسم القسم. وجعل الضغط اختياريًا.

أسلوبي هو فقط تضمين الأصول الخاصة بي (مضغوطة باستخدام tar & gz) في قسم elf / pe32 مع objcopy ، وقراءتها عبر حزمة debug / elf و debug / pe32 مع الرمز البريدي عند الحاجة. كل ما أريد أن أتذكره هو عدم لمس أي قسم موجود. جميع الأصول غير قابلة للتغيير ثم يقوم الكود بقراءة المحتوى ومعالجته في الذاكرة.

يبدو أنه يعمل على elf / pe32 لكن ماذا عن mach-o / plan9 ؟

هناك مشكلة أخرى وهي أنها تعتمد على فتح مقبض ملف على الملف القابل للتنفيذ ، إذا تمت الكتابة فوق / تحديث / حذف الملف القابل للتنفيذ ، فسيؤدي ذلك إلى إرجاع بيانات مختلفة ، ولست متأكدًا مما إذا كانت هذه مشكلة مشروعة أو ميزة غير متوقعة.

لقد مررت قليلاً بنفسي (باستخدام التصحيح / مفتول العضلات ) ، لكن لا يمكنني رؤية طريقة للحصول على هذا النظام الأساسي العامل ، فأنا أقوم بالبناء على macOS ويبدو أن ثنائيات GNU المثبتة تفسد mach-o-x86-64 ملف mach-o وطويل جدًا منذ أن نظرت إلى objcopy ).

مشكلة أخرى هي أنه يعتمد على فتح مقبض ملف على الملف القابل للتنفيذ

أنا متأكد تمامًا من أن أداة تحميل البرامج ستقوم (أو يمكنها) تحميل قسم الموارد في الذاكرة ، لذلك ليست هناك حاجة لاستخدام حزم تصحيح الأخطاء. على الرغم من أن الوصول إلى البيانات سيتطلب المزيد من العبث بملفات الكائنات مما يستحق.

لماذا لا تتبع ما يعمل - على سبيل المثال كيف تفعل Java ذلك. أود أن أطلب أن تكون الأشياء رائعة ، لكن شيئًا ما في السطور:

  • إنشاء ملف go.res أو تعديل go.mod للإشارة إلى الدليل حيث توجد الموارد
  • يتم تضمين جميع الملفات من هذا الدليل تلقائيًا ، ولا استثناءات من قبل المترجم في الملف التنفيذي النهائي
  • توفر اللغة واجهة برمجة تطبيقات تشبه المسار للوصول إلى هذه الموارد

يجب أن يكون الضغط وما إلى ذلك خارج نطاق تجميع الموارد هذا وأن يصل إلى أي نصوص برمجية // go:generate إذا لزم الأمر.

هل نظر أي شخص إلى Markbates / pkger ؟ إنه حل بسيط جدًا لاستخدام go.mod كدليل العمل الحالي. بافتراض تضمين index.html ، فإن فتحه سيكون pkger.Open("/index.html") . أعتقد أن هذه فكرة أفضل من ترميز دليل static/ في المشروع.

من الجدير بالذكر أيضًا أن Go ليس لديها أي متطلبات هيكلية كبيرة لمشروع بقدر ما أستطيع رؤيته. go.mod هو مجرد ملف ولا يستخدم الكثير من الأشخاص vendor/ . أنا شخصياً لا أعتقد أن الدليل static/ سيكون مفيدًا.

نظرًا لأن لدينا بالفعل طريقة لحقن البيانات (وإن كانت محدودة) في إصدار عبر علامة الارتباط ldflags -X importpath.name=value ، فهل يمكن تعديل مسار الرمز هذا لقبول -X importpath.name=@filename للحقن بيانات تعسفية خارجية؟

أدرك أن هذا لا يغطي جميع الأهداف المعلنة للإصدار الأصلي ، ولكن كإمتداد للوظيفة الحالية -X هل تبدو خطوة معقولة إلى الأمام؟

(وإذا نجح ذلك ، فإن تمديد بناء الجملة go.mod كطريقة أكثر إتقانًا لتحديد قيم ldflags -X هي الخطوة المعقولة التالية؟)

هذه فكرة شيقة للغاية ، لكني قلق من التداعيات الأمنية.

من الشائع جدًا أن تفعل -X 'pkg.BuildVersion=$(git rev-parse HEAD)' ، لكننا لا نريد أن نتخلى عن الأمر. (أعتقد أن go Generator يفعل ، لكن هذا ليس شيئًا تقوم بتشغيله عادةً لحزم OSS التي تم تنزيلها.) إذا لم يستطع go.mod التعامل مع ذلك ، فسينتهي به الأمر إلى فقدان حالة استخدام رئيسية ، لذلك ستظل ldflags شائعة جدًا.

ثم هناك مشكلة أخرى تتمثل في التأكد من أن @filename ليس رابطًا رمزيًا لـ / etc / passwd أو أيًا كان.

استخدام الرابط يحول دون دعم WASM ، وربما أهداف أخرى لا تستخدم رابط.

بناءً على المناقشة هنا ، قمت أنا و

فيديو: 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 _universal_ sandboxed Go في وقت الترجمة والذي يتضمن قراءة الملفات وتحويل البيانات المقروءة إلى تنسيق مثالي مناسب للاستهلاك في وقت التشغيل.

atomsymbol هذا يبدو وكأنه شيء waaay خارج نطاق هذه المشكلة.

atomsymbol هذا يبدو وكأنه شيء waaay خارج نطاق هذه المشكلة.

وأنا على علم بذلك.

لقد قرأت الاقتراح وفحصت الكود ضوئيًا ، لكن لم أجد إجابة لهذا: هل سيحتوي مخطط التضمين هذا على معلومات حول الملف الموجود على القرص (~ 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 الآن. يمكن أن يكون هناك أيضًا حجة خيار لـ HandlerFS على وجه التحديد لإخبارها بمعاملة نظام الملفات على أنه غير قابل للتغيير. أيضًا إذا تم ذلك عند بدء تشغيل التطبيق وكان لكل ctime / mtime معالجات ذات قيمة صفرية ، يمكن لـFS استخدام هذه المعلومات "لمعرفة" أن الملف لم يتغير ولكن هناك أيضًا أنظمة ملفات قد لا تحتوي على mtime أو تم تعطيله لذلك هناك قد تكون هناك مشاكل هناك أيضًا.

لم أكن أشاهد التعليقات على هذه المسألة.

atomsymbol أهلا بكم من جديد! إنه لأمر رائع أن أراك تعلق هنا مرة أخرى.
أوافق من حيث المبدأ على أنه إذا كان لدينا وضع الحماية لكان العديد من الأشياء أسهل.
من ناحية أخرى ، قد تكون العديد من الأشياء أكثر صعوبة - فقد لا تنتهي عمليات الإنشاء أبدًا.
على أي حال ، بالتأكيد ليس لدينا هذا النوع من وضع الحماية اليوم. :-)

kokes لست متأكدا من التفاصيل ،
لكننا سنتأكد من تقديم embed.Files عبر HTTP يحصل على ETAG بشكل صحيح افتراضيًا.

لقد قدمت # 41191 لقبول مسودة التصميم التي تم نشرها مرة أخرى في يوليو.
سأغلق هذه القضية كما حلت محلها تلك.
شكرا على المناقشة التمهيدية الرائعة هنا.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات