Phân phối script của riêng bạn qua Homebrew
(justin.searls.co)- Homebrew là trình quản lý gói trên macOS giúp cài đặt và quản lý công cụ CLI dễ dàng, cho phép nhà phát triển cấu hình môi trường hệ thống hiệu quả bằng các công cụ họ thường dùng
- Hướng dẫn này giải thích quy trình phân phối các script CLI cá nhân bằng Homebrew, đồng thời chỉ ra cách đơn giản hóa việc bảo trì thông qua tích hợp GitHub và quy trình tự động hóa
- Quy trình phân phối diễn ra theo chuỗi tạo CLI → phát hành GitHub Release → tạo Tap → viết và cập nhật Formula, và cuối cùng có thể cài đặt chỉ với các lệnh
brew tapvàbrew install - Nếu hiểu hệ thống thuật ngữ và best practice của Homebrew, bạn có thể triển khai phân phối ổn định với khả năng tái lập cao hơn và bảo mật chuỗi cung ứng tốt hơn
- Có thể tự động hóa bằng workflow GitHub Actions, và một khi đã thiết lập xong thì việc phát hành các CLI khác về sau sẽ rất đơn giản
Bối cảnh và động lực
- Homebrew là trình quản lý gói được ưa chuộng khi cài đặt công cụ CLI và được nhiều nhà phát triển sử dụng
- Tuy nhiên, khi phân phối CLI do chính mình tạo ra, nhiều người thường chọn npm hoặc RubyGem, còn cách phân phối qua Homebrew có thể tạo cảm giác xa lạ về mặt quy trình
- Kho
corechính thức của Homebrew có chủ trương không mặn mà với việc đưa các công cụ tự tạo vào, nên nhà phát triển thông thường sẽ phân phối qua tap và formula riêng - Hướng dẫn này được giải thích dựa trên kinh nghiệm phân phối một CLI đơn giản viết bằng Ruby
Giải thích thuật ngữ
- Homebrew dùng bộ thuật ngữ đặc trưng theo chủ đề nấu bia, nên nếu hiểu chúng thì sẽ dễ nắm cấu trúc hệ thống hơn
- Formula là tệp định nghĩa gói, chứa hướng dẫn cài đặt mã nguồn hoặc binary
- Tap là kho Git chứa các Formula, dùng để quản lý các gói tùy chỉnh theo người dùng hoặc tổ chức
- Cask là manifest cài đặt cho ứng dụng GUI hoặc binary lớn; tương tự Formula nhưng xử lý các tệp đã build sẵn
- Bottle là dạng gói binary build sẵn được sao chép thay vì build từ mã nguồn, giúp tăng tốc cài đặt
- Cellar là thư mục nơi các Formula đã cài được đặt vào, ví dụ đường dẫn
/opt/homebrew/Cellar - Keg là thư mục instance cài đặt của một Formula cụ thể, được bố trí theo từng phiên bản bên trong Cellar
Tổng quan
- Kho Homebrew core không nhận nội dung ngách hoặc do cá nhân gửi lên, vì vậy người dùng phải tạo kho tap riêng để phân phối CLI
- 1. Tạo CLI, đưa lên GitHub và phát hành theo tag
- 2. Tạo Tap bằng
brew tap-newrồi push lên GitHub - 3. Viết Formula bằng
brew create(gồm URL tarball và SHA256) - 4. Mỗi khi phát hành phiên bản mới thì cập nhật Formula để người dùng có thể dễ dàng cài bằng lệnh
brew install
- Sau khi phân phối xong, người dùng có thể cài CLI chỉ với hai lệnh:
brew tap your_github_handle/tapvàbrew install your_cool_cli- Hướng dẫn này bỏ qua phần phát triển CLI và tập trung vào việc tạo tap, tạo Formula và quy trình cập nhật
- Ví dụ sử dụng CLI
imsg, công cụ tạo web archive tương tác từ cơ sở dữ liệu iMessage
Tạo tap
- Làm theo hướng dẫn tạo tap của Homebrew, thay thế bằng tên người dùng GitHub hoặc tên tổ chức của bạn
- Để gom tất cả công cụ CLI sau này vào một tap, tác giả khuyến nghị đặt tên
homebrew-tap; tiền tốhomebrewđược CLI xử lý đặc biệt và hậu tốtaplà thông lệ phổ biến
- Để gom tất cả công cụ CLI sau này vào một tap, tác giả khuyến nghị đặt tên
- Chạy lệnh tạo tap:
brew tap-new searlsco/homebrew-tap- Lệnh này tạo scaffold tại
/opt/homebrew/Library/Taps/searlsco/homebrew-tap - Tạo kho tương ứng trên GitHub rồi push nội dung vừa sinh ra:
cd /opt/homebrew/Library/Taps/searlsco/homebrew-tap,git remote add origin git@github.com:searlsco/homebrew-tap.git,git push -u origin main
- Lệnh này tạo scaffold tại
- Sau khi sở hữu tap, người dùng khác có thể clone kho này bằng lệnh
brew tap searlsco/tapđể đặt vào/opt/homebrew/Library/Taps- Ban đầu chưa có nội dung hữu ích, nhưng có thể dùng để xác nhận hành vi cơ bản
Tạo Formula
- Homebrew có thể tham chiếu trực tiếp kho GitHub, nhưng khuyến nghị dùng tarball theo phiên bản và checksum để tăng khả năng tái lập và bảo mật chuỗi cung ứng mã nguồn mở
- GitHub sẽ host tarball với URL có thể dự đoán khi push tag; ví dụ với kho
imsg, sau khi chạygit tag v0.0.5,git push --tagsthì sẽ cóhttps://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gz
- GitHub sẽ host tarball với URL có thể dự đoán khi push tag; ví dụ với kho
- Lệnh tạo Formula:
brew create https://github.com/searlsco/imsg/archive/refs/tags/v0.0.5.tar.gz --tap searlsco/homebrew-tap --set-name imsg --ruby- Cờ
--tapchỉ định tap tùy chỉnh và đặt Formula vào/opt/homebrew/Library/Taps/searlsco/homebrew-tap/Formula --set-name imsgđặt tên Formula một cách tường minh; nên chọn tên đủ độc nhất để tránh trùng (ví dụ lưu ý xung đột với các CLI như TLDR hay standard đã có)--rubylà preset template cho Ruby CLI, một trong nhiều tùy chọn giúp đơn giản hóa việc tùy biến
- Cờ
- Formula tạo ra lúc đầu có thể chưa chạy được, nên có thể dùng LLM để chỉnh sửa: chạy
brew install --verbose imsg, đưa lỗi vào ChatGPT rồi lặp lại việc cập nhật Formula- Tệp cuối cùng Formula/imsg.rb có thể được sao chép làm điểm khởi đầu cho việc phân phối Ruby CLI
- Khi phân phối qua Homebrew thay vì trình quản lý gói đặc thù ngôn ngữ, người dùng vẫn có thể nâng cấp mượt mà ngay cả khi bạn thay đổi ngôn ngữ triển khai
Những điểm nổi bật chính của Formula
- Mọi Formula đều được viết bằng Ruby, vì trước thời JavaScript hay AI thì nhiều công cụ phát triển phổ biến được xây trên Ruby
- Có thể chỉ định kho Git bằng phương thức
head, nhưng hiệu quả thực tế của nó không rõ ràng - Việc thêm
livecheckrất đáng giá vì giúp cập nhật phiên bản Formula dễ hơn - Bài kiểm tra chạy binary có thể được triển khai đơn giản bằng cách kiểm tra đầu ra trợ giúp; đừng bị các comment được sinh ra làm nản lòng
- Dùng lệnh
brew style searlsco/tapđể kiểm tra lỗi style - Mặc định
uses_from_macos "ruby"trong template--rubydùng phiên bản 2.6.10 (phát hành từ trước COVID, đã EOL 3 năm), nên tác giả khuyến nghị phụ thuộc vào Formula ruby mới hơn bằngdepends_on "ruby@3"
- Có thể chỉ định kho Git bằng phương thức
- Khi đã hài lòng với Formula, chỉ cần
git pushđể phát hành live; người dùng có thể cài bằngbrew tap searlsco/tapvàbrew install imsg
Cập nhật Formula cho từng bản phát hành CLI
- Việc phải cập nhật thủ công
urlvà hashsha256ở đầu Formula sau mỗi lần phát hành khá phiền; tác giả còn cho rằng ngay cả thao tác push tag hay tạo GitHub Release cũng đã mệt mỏi- Có thể tạo PR bằng lệnh
bump-formula-prcủa Homebrew hoặc bằng GitHub Actions, nhưng quy trình fork và PR lại phức tạp một cách không cần thiết - Nếu bạn là chủ sở hữu tap, cách đơn giản hơn là commit trực tiếp vào nhánh
main
- Có thể tạo PR bằng lệnh
- Để tránh điều này, tác giả khuyến nghị thêm workflow GitHub vào kho Formula để tự động cập nhật tap khi có release
- Có thể sao chép và dùng ví dụ workflow
- Cần cấu hình: tạo GitHub Personal Access Token (PAT), cấp quyền
Content→Writecho khohomebrew-tap, rồi lưu vào Secrets của kho Formula dưới tênHOMEBREW_TAP_TOKEN - Chỉ định tap và Formula bằng biến môi trường (ví dụ dòng 13-15)
- Nên dùng cấu hình tài khoản bot GitHub để cập nhật:
GH_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com,GH_NAME: github-actions[bot]
- Sau khi tạo release rồi chạy
git push --tags, bản cập nhật sẽ được áp dụng tự động chỉ trong vài giây; người dùng có thể nâng cấp bằngbrew updatevàbrew upgrade imsg
Điểm hay nhất
- Quy trình này tuy phức tạp, nhưng sau khi đã thiết lập tap và hoàn thành một ví dụ Formula thì việc phân phối thêm CLI gần như trở nên rất nhỏ nhặt
- Có thể xuất bản Formula mới chỉ trong vài phút nên khá tiện lợi
- Quy trình chính thức của Homebrew có phần rườm rà, nhưng tự động hóa sẽ giúp mọi thứ dễ chịu hơn
- Nó giảm bớt sự phiền hà giữa mỗi lần release và phân phối công cụ, đồng thời có thể mở rộng để hỗ trợ CLI viết bằng nhiều ngôn ngữ khác nhau
- Tác giả không chắc mình có thực sự đăng thêm Formula nào nữa hay không, nhưng cảm thấy hài lòng vì khả năng đó giờ đã rộng mở
2 bình luận
Có tùy chọn
--no-fork, nên có thể push trực tiếp lên nhánh và merge, đồng thời cung cấp tính năng tự động cập nhật.Ý kiến trên Hacker News
Quy tắc đặt tên của Homebrew đôi khi cũng khiến mình thấy hơi rối, nhưng càng dùng càng thấy đây thực sự là một công cụ rất hữu ích
Ngoài ra mình cũng không ngờ quy trình tạo tap riêng để phân phối công cụ của mình lại đơn giản đến vậy
Mình tò mò không biết so với các trình quản lý gói theo ngôn ngữ (ví dụ: uv) thì nó tốt hơn ở điểm nào
Đặc biệt là liệu nó có dễ hơn cho những người không thuộc một hệ sinh thái cụ thể nào hay không, tức là có lợi thế hơn về tính phổ quát hay không
Xin gửi lời cảm ơn; các công cụ khác dùng package registry thường cần tạo tài khoản, xác thực hai bước và quy trình ký các thứ
Homebrew thì dựa vào điều khoản dịch vụ (ToS) của GitHub như một nền tảng tin cậy, nên tổng thể được đơn giản hóa hơn rất nhiều
Nhờ cách này mà đội ngũ Homebrew cũng có thể giảm được rất nhiều độ phức tạp
Nếu nói theo tiêu chuẩn package Python, thì việc cố gắng đóng gói mọi thứ cùng lúc như uv là điều khá khó trong thực tế
Vì vậy thông thường người ta dùng cách chỉ cài các dependency đã được cố định trong môi trường venv
Có thể xem ví dụ cụ thể ở formula này
Về uv, mình đã thử hỗ trợ package riêng tư bằng các công cụ chính thức (
brew update-python-resources,homebrew-pypi-poet) nhưng không hoạt động đúng như mong muốn,nên đã tự tạo uvbrew để hỗ trợ tạo resource
Cũng có tài liệu chính thức để tham khảo khi viết formula Python cho Homebrew
Nếu là lập trình viên Go thì mình khuyên dùng công cụ Goreleaser
Nó giúp phân phối binary trong tap cá nhân cực kỳ dễ dàng (dù cách này bị cấm trong core chính thức)
Nó khá hữu dụng trong việc quản lý dự án theo từng ngôn ngữ
Cá nhân mình nghĩ việc quản lý cập nhật trực tiếp từ phía tap sẽ lý tưởng hơn một chút
Nói chung nó tương tự cách cập nhật từ upstream
Nếu tham khảo workflow này, bạn có thể dễ dàng cập nhật cả formula/cask mà mình không sở hữu
Có thể quét tất cả bằng lệnh
brew bump, tự động tạo PR và còn chạybrew test-botđể kiểm thửCó thể xem ví dụ PR thực tế ở đây
Bình thường mình cứ ngại vì nghĩ thời lượng GitHub Actions là thứ cần tiết kiệm, nhưng vì mã nguồn mở thì được miễn phí nên dùng như vậy cũng hợp lý
Mình đã tự viết homebrew-bump-revision làm workflow tự động bump phiên bản cho Homebrew tap của riêng mình
Mình đang dùng nó khá tốt cho nhiều dự án cá nhân
Mình lười nên chưa thử, nhưng đúng là một công cụ tốt
Trong podcast Ruby Rogues từng có một tập nói về nhiều mẹo khác nhau khi phân phối CLI bằng Homebrew
Có thể nghe thêm ở liên kết tập này
Mình phát hiện một điểm thú vị về việc đóng gói công cụ Python
Một số package Python tạo ra vòng lặp dependency trong quá trình build nên không tương thích với Homebrew
pip thì không gặp vấn đề này vì tải binary release, nhưng Homebrew phải tự build cả dependency nên quá trình này mất nhiều thời gian hơn rất nhiều
Vì vậy ngay cả một dự án Python cỡ trung bình cũng có thể mất hơn một giờ để build "bottle"
Từ khi bắt đầu dùng nix để quản lý hệ thống, mình chưa hối hận lấy một lần
Điều duy nhất mình thấy tiếc là vẫn phải phụ thuộc vào Windows vì game nhiều người chơi