เผยแพร่สคริปต์ของคุณเองผ่าน Homebrew
(justin.searls.co)- Homebrew เป็นตัวจัดการแพ็กเกจที่ช่วยให้ติดตั้งและจัดการ เครื่องมือ CLI บน macOS ได้ง่าย ทำให้นักพัฒนาสามารถจัดสภาพแวดล้อมของระบบได้อย่างมีประสิทธิภาพด้วยเครื่องมือที่ใช้บ่อย
- คู่มือนี้อธิบายกระบวนการเผยแพร่สคริปต์ CLI ส่วนตัวด้วย Homebrew และแสดงวิธีทำให้การดูแลรักษาง่ายขึ้นผ่าน การเชื่อมต่อกับ GitHub และเวิร์กโฟลว์อัตโนมัติ
- กระบวนการเผยแพร่จะเป็นลำดับ สร้าง CLI → GitHub release → สร้าง Tap → เขียนและอัปเดต Formula และสุดท้ายสามารถติดตั้งได้ด้วยคำสั่ง
brew tapและbrew installเท่านั้น - หากเข้าใจ ระบบคำศัพท์ และ แนวทางปฏิบัติที่ดี ของ Homebrew ก็จะสามารถเผยแพร่ได้อย่างเสถียรพร้อมเสริมความสามารถในการทำซ้ำและความปลอดภัยของซัพพลายเชน
- สามารถทำให้เป็นอัตโนมัติด้วยเวิร์กโฟลว์ GitHub Actions ได้ และเมื่อตั้งค่าไว้ครั้งหนึ่งแล้ว การเผยแพร่ CLI ตัวอื่นในภายหลังก็จะ ง่ายมาก
พื้นหลังและแรงจูงใจ
- Homebrew เป็น ตัวจัดการแพ็กเกจที่นิยมใช้เมื่อติดตั้งเครื่องมือ CLI และมีนักพัฒนาจำนวนมากใช้งาน
- แต่หลายคนมักเผยแพร่ CLI ที่สร้างเองผ่าน npm หรือ RubyGem ทำให้แนวทางการเผยแพร่ผ่าน Homebrew อาจรู้สึกไม่คุ้นขั้นตอน
- เนื่องจากคลัง core อย่างเป็นทางการของ Homebrew มีนโยบายไม่ค่อยรับเครื่องมือทำเองเข้าไป ทีม Homebrew จึงทำให้ นักพัฒนาทั่วไปต้องเผยแพร่ผ่าน tap และ formula แยกต่างหาก
- คู่มือนี้อธิบายโดยอิงจาก ประสบการณ์การเผยแพร่ CLI แบบง่ายที่เขียนด้วย Ruby
คำอธิบายคำศัพท์
- Homebrew ใช้ คำศัพท์เฉพาะ ที่สะท้อนธีมการต้มเบียร์ ดังนั้นหากเข้าใจก็จะมองโครงสร้างระบบได้ง่ายขึ้น
- Formula คือไฟล์ นิยามแพ็กเกจ ที่มีคำสั่งสำหรับติดตั้งซอร์สโค้ดหรือไบนารี
- Tap คือ Git repository ของ Formula หลายตัว ใช้จัดการแพ็กเกจแบบกำหนดเองตามผู้ใช้หรือองค์กร
- Cask คือแมนิเฟสต์สำหรับติดตั้ง แอป GUI หรือไบนารีขนาดใหญ่ คล้าย Formula แต่ใช้กับไฟล์ที่สร้างเสร็จแล้ว
- Bottle คือรูปแบบที่คัดลอก แพ็กเกจไบนารีที่ build ไว้ล่วงหน้า แทนการ build จากซอร์ส ช่วยให้ติดตั้งได้เร็วขึ้น
- Cellar คือ ไดเรกทอรี ที่เก็บ Formula ที่ติดตั้งแล้ว เช่นพาธ
/opt/homebrew/Cellar - Keg คือ ไดเรกทอรีของอินสแตนซ์การติดตั้ง ของ Formula เฉพาะตัวหนึ่ง ซึ่งจะถูกจัดวางตามเวอร์ชันภายใน Cellar
ภาพรวม
- เนื่องจากคลัง core ของ Homebrew ไม่รับคอนเทนต์ที่เฉพาะทางมากหรือเป็นการส่งเข้ามาแบบส่วนบุคคล ผู้ใช้จึงต้อง สร้าง tap repository แยกต่างหาก เพื่อเผยแพร่ CLI
- 1. สร้าง CLI แล้วอัปขึ้น GitHub และทำ tag release
- 2. สร้าง Tap ด้วย
brew tap-newแล้ว push ขึ้น GitHub - 3. สร้าง Formula ด้วย
brew create(รวม URL ของ tarball และ SHA256) - 4. ทุกครั้งที่ออกเวอร์ชันใหม่ ให้อัปเดต Formula เพื่อให้ผู้ใช้ติดตั้งได้ง่ายด้วยคำสั่ง
brew install
- เมื่อเผยแพร่เสร็จ ผู้ใช้จะติดตั้ง CLI ได้ด้วยสองคำสั่ง:
brew tap your_github_handle/tapและbrew install your_cool_cli- คู่มือนี้จะข้ามขั้นตอนการพัฒนา CLI และเน้นที่การสร้าง tap, การสร้าง Formula และกระบวนการอัปเดต
- ตัวอย่างที่ใช้คือ CLI ชื่อ
imsgซึ่งสร้าง เว็บอาร์ไคฟ์แบบโต้ตอบได้ จากฐานข้อมูล iMessage
การสร้าง tap
- ให้ทำตาม คู่มือการสร้าง tap ของ Homebrew โดยแทนที่ด้วยชื่อผู้ใช้หรือชื่อองค์กรบน GitHub ของคุณ
- เพื่อรวบรวมเครื่องมือ CLI ทั้งหมดไว้ใน tap เดียวกันในอนาคต จึงแนะนำชื่อ
homebrew-tapโดยคำนำหน้าhomebrewจะถูกจัดการเป็นพิเศษใน CLI และคำนำหน้าtapก็เป็นธรรมเนียมที่ใช้กัน
- เพื่อรวบรวมเครื่องมือ CLI ทั้งหมดไว้ใน tap เดียวกันในอนาคต จึงแนะนำชื่อ
- รันคำสั่งสร้าง tap:
brew tap-new searlsco/homebrew-tap- คำสั่งนี้จะสร้าง scaffold ไว้ที่
/opt/homebrew/Library/Taps/searlsco/homebrew-tap - จากนั้นสร้าง repository ที่สอดคล้องกันบน GitHub และ push เนื้อหาที่สร้างขึ้น:
cd /opt/homebrew/Library/Taps/searlsco/homebrew-tap,git remote add origin git@github.com:searlsco/homebrew-tap.git,git push -u origin main
- คำสั่งนี้จะสร้าง scaffold ไว้ที่
- เมื่อเป็นเจ้าของ tap แล้ว ผู้ใช้อื่นสามารถ clone repository นี้ด้วยคำสั่ง
brew tap searlsco/tapเพื่อนำไปวางใน/opt/homebrew/Library/Tapsได้- ช่วงแรกอาจยังไม่มีอะไรที่มีประโยชน์อยู่ภายใน แต่ก็สามารถยืนยันการทำงานพื้นฐานได้
การสร้าง Formula
- แม้ Homebrew จะอ้างอิง GitHub repository ได้โดยตรง แต่แนะนำให้ใช้ tarball แบบมีเวอร์ชัน และ checksum เพื่อเพิ่มความสามารถในการทำซ้ำและความปลอดภัยของซอฟต์แวร์โอเพนซอร์สในซัพพลายเชน
- เมื่อ push tag แล้ว GitHub จะโฮสต์ tarball ที่มี URL คาดเดาได้ เช่น ใน repository
imsgหลังรันgit tag v0.0.5,git push --tagsจะเกิดhttps://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gz
- เมื่อ push tag แล้ว GitHub จะโฮสต์ tarball ที่มี URL คาดเดาได้ เช่น ใน repository
- คำสั่งสร้าง Formula:
brew create https://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gz --tap searlsco/homebrew-tap --set-name imsg --ruby- แฟล็ก
--tapใช้ระบุ tap แบบกำหนดเอง และวาง Formula ไว้ที่/opt/homebrew/Library/Taps/searlsco/homebrew-tap/Formula --set-name imsgใช้ตั้งชื่อ Formula อย่างชัดเจน ควรเลือกให้ไม่ซ้ำเพื่อหลีกเลี่ยงการชนชื่อ (เช่น ระวังชนกับ TLDR หรือ standard CLI ที่มีอยู่แล้ว)--rubyคือ template preset สำหรับ Ruby CLI ซึ่งเป็นหนึ่งในหลายตัวเลือกที่ช่วยให้การปรับแต่งง่ายขึ้น
- แฟล็ก
- Formula ที่สร้างขึ้นอาจยังใช้งานไม่ได้ในตอนแรก จึงสามารถใช้ LLM ช่วยแก้ไขได้: รัน
brew install --verbose imsgแล้วนำข้อผิดพลาดไปใส่ใน ChatGPT จากนั้นอัปเดต Formula ซ้ำไปเรื่อย ๆ- ไฟล์สุดท้าย Formula/imsg.rb สามารถคัดลอกไปใช้เป็นจุดเริ่มต้นสำหรับการเผยแพร่ Ruby CLI ได้
- การเผยแพร่ผ่าน Homebrew แทนตัวจัดการแพ็กเกจเฉพาะภาษา ช่วยให้แม้จะเปลี่ยนภาษา implementation ในภายหลัง ผู้ใช้ก็ยังอัปเกรดได้อย่างราบรื่น
จุดสำคัญของ Formula
- Formula ทุกตัวจะเขียนด้วย Ruby เพราะเครื่องมือพัฒนาที่เคยได้รับความนิยมก่อนยุค JavaScript หรือ AI นั้นจำนวนมากสร้างอยู่บน Ruby
- สามารถใช้เมธอด
headเพื่อระบุ Git repository ได้ แต่ผลลัพธ์จริงยังไม่แน่ชัด - การเพิ่ม
livecheckมีคุณค่าเพราะช่วยให้อัปเดตเวอร์ชันของ Formula ได้ง่ายขึ้น - การทดสอบการรันไบนารีสามารถทำอย่างง่ายได้ด้วยการตรวจสอบผลลัพธ์ของ help output จึงไม่ต้องกังวลกับคอมเมนต์ที่ถูกสร้างมาให้
- ใช้คำสั่ง
brew style searlsco/tapเพื่อตรวจสอบข้อผิดพลาดด้านสไตล์ - ค่าเริ่มต้น
uses_from_macos "ruby"ของ template--rubyจะใช้เวอร์ชัน 2.6.10 (ออกก่อนช่วง COVID และหมดอายุการสนับสนุนมา 3 ปีแล้ว) ดังนั้นจึงแนะนำให้พึ่งพา Ruby Formula รุ่นใหม่ด้วยdepends_on "ruby@3"
- สามารถใช้เมธอด
- เมื่อพอใจกับ Formula แล้ว ก็เผยแพร่จริงได้ด้วย
git pushและผู้ใช้จะติดตั้งได้ด้วยbrew tap searlsco/tapและbrew install imsg
การอัปเดต Formula สำหรับแต่ละ CLI release
- ค่า
urlและ hashsha256ด้านบนของ Formula ต้องอัปเดตด้วยตนเองทุก release ซึ่งยุ่งยาก และผู้เขียนยังชี้ว่าการ push tag หรือสร้าง GitHub release เองก็ชวนเหนื่อยเช่นกัน- สามารถใช้คำสั่ง
bump-formula-prของ Homebrew หรือ GitHub Action เพื่อสร้าง PR ได้ แต่ขั้นตอน fork และ PR นั้นซับซ้อนเกินจำเป็น - หากคุณเป็นเจ้าของ tap วิธีที่ตรงไปตรงมาและเหมาะกว่าคือ commit เข้า branch main โดยตรง
- สามารถใช้คำสั่ง
- เพื่อหลีกเลี่ยงเรื่องนี้ จึงแนะนำให้เพิ่ม GitHub workflow ใน repository ของ Formula เพื่ออัปเดต tap อัตโนมัติเมื่อมี release
- สามารถคัดลอก workflow example ไปใช้ได้
- ต้องตั้งค่าบางอย่าง: สร้าง GitHub personal access token (PAT) แล้วให้สิทธิ์
Content→Writeกับ repositoryhomebrew-tapจากนั้นเก็บไว้ใน Secrets ของ repository Formula โดยใช้ชื่อHOMEBREW_TAP_TOKEN - ระบุ tap และ Formula ผ่าน environment variables (เช่น บรรทัด 13-15)
- แนะนำให้อัปเดตค่าบัญชีบอตของ GitHub:
GH_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com,GH_NAME: github-actions[bot]
- หลังสร้าง release แล้ว เมื่อรัน
git push --tagsจะมีการอัปเดตอัตโนมัติภายในไม่กี่วินาที และผู้ใช้จะอัปเกรดได้ด้วยbrew updateและbrew upgrade imsg
ส่วนที่ดีที่สุด
- แม้กระบวนการนี้จะซับซ้อน แต่เมื่อทำการตั้งค่า tap และมีตัวอย่าง Formula หนึ่งตัวเสร็จแล้ว การเผยแพร่ CLI เพิ่มเติมจะ แทบเป็นเรื่องเล็กน้อย
- สามารถเผยแพร่ Formula ใหม่ได้ภายในไม่กี่นาที จึงสะดวกมาก
- แม้กระบวนการอย่างเป็นทางการของ Homebrew จะค่อนข้างซับซ้อน แต่ระบบอัตโนมัติช่วยให้ใช้งานสะดวกขึ้น
- ลดความยุ่งยากระหว่างการ release และการเผยแพร่ของแต่ละเครื่องมือ และยังรองรับการขยายไปสู่ CLI ที่เขียนด้วยภาษาหลากหลายได้
- ผู้เขียนเองก็ยังไม่แน่ใจว่าจะเผยแพร่ Formula อื่นเพิ่มอีกหรือไม่ แต่ก็พอใจกับการที่อย่างน้อยมีความเป็นไปได้นี้เปิดอยู่
2 ความคิดเห็น
มีออปชัน
--no-forkจึงสามารถ push ไปยัง branch ได้โดยตรงและ merge ได้ พร้อมทั้งมีฟีเจอร์อัปเดตอัตโนมัติความคิดเห็นจาก Hacker News
แม้ว่ากฎการตั้งชื่อของ Homebrew บางครั้งจะทำให้งงอยู่บ้าง แต่ก็ยังรู้สึกอยู่เสมอว่าโดยรวมแล้วมันเป็นเครื่องมือที่มีประโยชน์มาก
และก็ไม่คิดมาก่อนว่ากระบวนการสร้าง tap ของตัวเองเพื่อแจกจ่ายเครื่องมือจะง่ายขนาดนี้
เลยสงสัยว่าเมื่อเทียบกับตัวจัดการแพ็กเกจตามภาษาแต่ละภาษา (เช่น uv) แล้ว มันดีกว่าในแง่ไหน
โดยเฉพาะอยากรู้ว่ามันง่ายกว่าสำหรับคนที่ไม่ได้อยู่ใน ecosystem ใด ecosystem หนึ่งหรือไม่ หรือก็คือได้เปรียบกว่าในแง่ความเป็นสากลหรือเปล่า
ขอบคุณมาก และเครื่องมืออื่นที่ใช้ package registry โดยทั่วไปมักต้องมีการสร้างบัญชี, ยืนยันตัวตนสองขั้นตอน, ขั้นตอนการเซ็นลายเซ็น ฯลฯ
แต่ Homebrew ใช้ข้อกำหนดการให้บริการของ GitHub (ToS) เป็นฐานของความน่าเชื่อถือ จึงทำให้ภาพรวมง่ายกว่ามาก
ทีม Homebrew เองก็ลดความซับซ้อนได้มากด้วยวิธีนี้
ถ้าพูดในเกณฑ์ของแพ็กเกจ Python ความพยายามแบบ uv ที่จะจัดแพ็กทุกอย่างไว้ในที่เดียวทำได้ยากในทางปฏิบัติ
เพราะแบบนั้นโดยทั่วไปจึงใช้วิธีติดตั้งเฉพาะ dependency ที่ตรึงเวอร์ชันไว้ในสภาพแวดล้อม venv
ดูตัวอย่างแบบเจาะจงได้ที่formula นี้
ส่วนเรื่อง uv นั้น เคยพยายามให้รองรับแพ็กเกจส่วนตัวด้วยเครื่องมือทางการอย่าง
brew update-python-resources,homebrew-pypi-poetแต่ทำได้ไม่สำเร็จเลยสร้าง uvbrew ขึ้นมาเองเพื่อช่วยสร้าง resource
และมีเอกสารทางการสำหรับใช้อ้างอิงเวลาเขียน Python formula บน Homebrew
ถ้าเป็นนักพัฒนา Go ขอแนะนำเครื่องมือ Goreleaser
มันช่วยให้แจกจ่ายไบนารีใน tap ส่วนตัวได้ง่ายมาก (แต่เป็นวิธีที่ห้ามใช้ใน core อย่างเป็นทางการ)
เลยมีประโยชน์มากในการจัดการโปรเจ็กต์ของแต่ละภาษา
ส่วนตัวคิดว่าการให้ฝั่ง tap จัดการอัปเดตโดยตรงน่าจะเป็นแนวทางที่เหมาะกว่า
โดยทั่วไปก็คล้ายกับวิธีที่ upstream เป็นคนอัปเดตเอง
ถ้าดูworkflow นี้ ก็จะเห็นว่าสามารถอัปเดต formula/cask ที่เราไม่ได้เป็นเจ้าของได้ง่าย ๆ เช่นกัน
ใช้คำสั่ง
brew bumpสแกนทั้งหมด แล้วสร้าง PR พร้อมให้brew test-botทดสอบให้อัตโนมัติได้ตัวอย่าง PR จริงดูได้ที่นี่
ปกติลังเลเพราะเสียดายเวลาใช้งาน GitHub Actions แต่ถ้าเป็นโอเพนซอร์สที่ใช้ฟรี แบบนี้ก็น่าใช้เหมือนกัน
ผมเคยเขียน homebrew-bump-revision ขึ้นมาเองเป็น workflow สำหรับ bump เวอร์ชันของ Homebrew tap แบบอัตโนมัติ
ตอนนี้ใช้งานมันได้ดีในหลายโปรเจ็กต์ส่วนตัว
ผมขี้เกียจเลยไม่ได้ลองเอง แต่เป็นเครื่องมือที่ดี
ในพอดแคสต์ Ruby Rogues เคยมีตอนหนึ่งที่พูดถึงทิปต่าง ๆ สำหรับการแจกจ่าย CLI ผ่าน Homebrew
ฟังรายละเอียดเพิ่มเติมได้จากลิงก์ตอนที่เกี่ยวข้อง
พบจุดที่น่าสนใจเกี่ยวกับการแพ็กเครื่องมือ Python
แพ็กเกจ Python บางตัวมี dependency loop ระหว่างขั้นตอน build จึงไม่เข้ากับ Homebrew
ส่วน pip ไม่มีปัญหาเพราะดาวน์โหลดไบนารีรีลีสมาใช้ แต่ Homebrew ต้อง build เองแม้กระทั่ง dependency ทั้งหมด ทำให้ใช้เวลานานกว่ามาก
เพราะงั้นโปรเจ็กต์ Python ขนาดกลางก็อาจใช้เวลามากกว่าหนึ่งชั่วโมงในการ build "bottle"
ตั้งแต่เริ่มใช้ nix เพื่อจัดการระบบ ก็ไม่เคยเสียใจเลยสักครั้ง
มีอย่างเดียวที่น่าเสียดายคือยังต้องพึ่ง windows เพราะเกมแบบมัลติเพลเยอร์