Ngày 29 tháng 09 năm 2019 - Công nghệ thông tin
Trước đây tôi đã viết một game nhà cái tặng tiền cược miễn phí bài về Golang Modules, giới thiệu cơ bản về mục đích thiết kế và cách hoạt động của Module. Bài viết này sẽ phân tích sâu hơn về cách sử dụng Module dựa trên tài liệu chính thức mới nhất của Go 1.13.
Module là một tập hợp các gói liên quan, được tạo ra để dễ dàng tham chiếu và quản lý phiên bản. Từ phiên bản Go 1.13, các lệnh mặc định đã hỗ trợ phương thức phụ thuộc và xây dựng dựa trên Module, do đó việc cài đặt GOPATH không còn bắt buộc nữa.
1. Biến GO111MODULE
Go 1.13 tiếp tục sử dụng biến tạm thời GO111MODULE. Giá trị của biến này quyết định việc sử dụng chế độ Module hay chế độ GOPATH cũ.
a) GO111MODULE=auto (chế độ mặc định) Khi GO111MODULE được đặt thành auto hoặc trống, lệnh go sẽ kiểm tra thư mục hiện tại để quyết định sử dụng chế độ nào. Nếu thư mục hiện tại hoặc thư mục cha (hoặc nhiều cấp trên) chứa tệp go.mod, nó sẽ sử dụng chế độ Module; ngược lại sẽ dùng chế độ GOPATH.
b) GO111MODULE=on GOPATH sẽ không có hiệu lực, lệnh go sẽ chỉ làm việc ở chế độ Module mà không sử dụng GOPATH.
c) GO111MODULE=off Chúng ta gọi đây là chế độ làm việc với GOPATH. Lệnh go sẽ tìm kiếm các phụ thuộc trong thư mục GOPATH hoặc thư mục vendor.
Lưu ý: Khi kích hoạt chế độ Module, biến GOPATH sẽ không còn ảnh hưởng đến quá trình xây dựng phụ thuộc, mà chỉ được sử dụng để xác định vị trí lưu trữ các Module phụ thuộc (GOPATH/pkg/mod) hoặc vị trí lệnh cài đặt (GOPATH/bin, nếu thiết lập GOBIN thì sẽ ưu tiên).
2. Tệp go.mod
Thư mục chứa tệp go.mod là thư mục gốc của mô-đun. Các gói mã nguồn Go hoặc thư mục con nằm trong thư mục này đều thuộc mô-đun này. Ngoài ra, cây mã nguồn con trong thư mục này cũng có thể có tệp go.mod riêng để định nghĩa một sub-module độc lập.
Tệp go.mod xác định đường dẫn của mô-đun hiện tại và liệt kê các mô-đun phụ thuộc cùng phiên bản tương ứng.
Ví dụ dưới đây cho thấy tệp go.mod xác định đường dẫn root của mô-đun là github.com/leileiluoluo/test
và yêu cầu hai gói với phiên bản cụ thể (golang.org/x/text v0.3.0
và gopkg.in/yaml.v2 v2.1.0
):
module github.com/leileiluoluo/test
require (
golang.org/x/text v0.3.0
gopkg.in/yaml.v2 v2.1.0
)
Ngoài ra, tệp go.mod cũng có thể chỉ định các gói cần thay thế (replace) hoặc loại trừ (exclude).
Việc tạo Module chỉ cần một lệnh đơn giản, ví dụ: go mod init github.com/leileiluoluo/test
.
Ngay khi tệp go.mod tồn tại, các lệnh go (như go build, go test, thậm chí là go list) sẽ tự động thêm các phụ thuộc mới vào tệp này.
3. Mô-đun chính và danh sách xây dựng
Mô-đun chính là mô-đun chứa thư mục mà lệnh go đang chạy. Lệnh go sẽ lần lượt kiểm tra từ thư mục hiện tại, thư mục cha, rồi lên từng cấp trên để tìm tệp go.mod ở thư mục gốc của mô-đun.
Tệp go.mod sử dụng các câu lệnh require, replace và exclude để mô tả tập hợp các gói phụ thuộc và phiên bản tương ứng. Lệnh go có thể tìm thấy tất cả các mô-đun phụ thuộc qua câu lệnh require trong tệp go.mod của mô-đun chính. Các phụ thuộc của mô-đun phụ thuộc cũng trở thành phụ thuộc của mô-đun chính, nhưng lệnh go chỉ quét câu lệnh require trong tệp go.mod của mô-đun phụ thuộc, bỏ qua các câu lệnh replace và exclude. Do đó, các câu lệnh replace và exclude cho phép kiểm soát hoàn toàn trong quá trình xây dựng mô-đun chính.
Danh sách các gói được sử dụng để xây dựng được gọi là danh sách xây dựng. Ban đầu, danh sách này chỉ có mô-đun chính, sau đó sẽ được cập nhật dựa trên các mô-đun phụ thuộc theo phiên bản được chỉ định, lặp lại cho đến khi không còn phụ thuộc mới. Nếu có nhiều phiên bản khác nhau của cùng một mô-đun, chỉ phiên bản gần nhất (theo thứ tự semver) sẽ được sử dụng.
Lệnh go list
cung cấp thông tin về mô-đun chính và danh sách xây dựng:
go list -m # In mô-đun chính
go list -m -f={{.Dir}} # In thư mục gốc của mô-đun chính
go list -m all # In danh sách xây dựng
4. Duy trì các mô-đun cần thiết
Tệp go.mod có thể đọc và viết bởi cả nhà phát triển lẫn công cụ. Lệnh go sẽ tự động cập nhật tệp này để duy trì định dạng chuẩn và các câu lệnh require chính xác.
Khi bất kỳ lệnh go nào được thực thi, nếu phát hiện đường dẫn mới được tham chiếu, phiên bản mới nhất của mô-đun chứa đường dẫn đó sẽ được thêm vào tệp go.mod. Vì vậy, trong hầu hết các trường hợp phát triển, bạn chỉ cần tham chiếu một gói mới trong tệp nguồn, sau đó chạy go build, go test hoặc go list, lệnh go sẽ tự động phát hiện và giải quyết các phụ thuộc, đồng thời cập nhật tệp go.mod.
Lệnh go mod tidy
sẽ tự động thêm các mô-đun bị thiếu và loại bỏ các mô-đun không còn sử dụng.
Nhà phát triển go cũng theo dõi các gói nào được mô-đun hiện tại trực tiếp tham chiếu và các gói nào được gián tiếp tham chiếu (tức là được mô-đun phụ thuộc của mô-đun hiện tại tham chiếu). Các gói được tham chiếu gián tiếp trong tệp go.mod sẽ được đánh dấu bằng chú thích " // indirect". Nếu một gói được tham chiếu gián tiếp sau đó được tham chiếu trực tiếp trong tệp go.mod, chú thích “indirect” sẽ bị xóa.
Lệnh go get
có thể thay đổi phiên bản của mô-đun phụ thuộc. Việc nâng cấp một mô-đun có thể dẫn đến việc nâng cấp các mô-đun khác liên quan, và hạ cấp một mô-đun cũng có thể dẫn đến việc hạ cấp các mô-đun khác liên quan. Lệnh go get sẽ thực hiện các cập nhật ẩn này. Tương tự, nếu tệp go.mod được chỉnh sửa thủ công, lệnh go build hoặc go list cũng sẽ tự động thực hiện một số cập nhật liên quan ẩn.
Đánh dấu xây dựng -mod
cung cấp kiểm soát bổ sung đối với việc cập nhật hoặc sử dụng tệp go.mod.
Khi sử dụng -mod=readonly
, lệnh go sẽ không cho phép cập nhật ngầm tàng tệp go.mod, và sẽ báo lỗi khi cần cập nhật tệp này. Thiết lập này hữu ích khi tệp go.mod không cần cập nhật, chẳng hạn như trong hệ thống tích hợp liên tục hoặc thử nghiệm. Ngoại lệ là khi thiết lập -mod=readonly
, lệnh go get vẫn được phép cập nhật tệp go.mod. Lệnh go mod không sử dụng đánh dấu -mod
.
Nếu bật -mod=vendor
, lệnh go sẽ giả định rằng tất cả các phụ thuộc đã được sao chép vào thư mục vendor, bỏ qua các mô tả phụ thuộc trong tệp go.mod.
5. Phiên bản giả
Tệp go.mod và lệnh go thường sử dụng định dạng phiên bản chuẩn semver để mô tả các phiên bản mô-đun, cho phép so sánh phiên bản nào sớm hơn hoặc muộn hơn. Một phiên bản mô-đun như v1.2.3 có thể được tạo bằng cách gắn thẻ cho một commit cụ thể trong kho mã nguồn. Các commit chưa gắn thẻ có thể được tham chiếu bằng “phiên bản giả” theo định dạng “v0.0.0-yyyymmddhhmmss-abcdefabcdef” (trong đó thời gian là giờ UTC của commit, phần hậu tố là hash commit), cho phép sử dụng thời gian commit để so sánh thứ tự phiên bản và hash để chỉ định commit cụ thể, còn tiền tố (ví dụ v0.0.0-) biểu 99win club thị phiên bản gắn nhãn gần nhất trước commit này.
Có ba định dạng “phiên bản giả”:
a) vX.0.0-yyyymmddhhmmss-abcdefabcdef Biểu thị không có phiên bản gắn nhãn phù hợp trước commit mục tiêu.
b) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef Biểu thị phiên bản gắn nhãn gần nhất trước commit mục tiêu là vX.Y.Z-pre.
c) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef Biểu thị phiên bản gắn nhãn gần nhất trước commit mục tiêu là vX.Y.Z.
Không nên nhập tay phiên bản giả, lệnh go sẽ tự động chuyển đổi.
6. Tra cứu mô-đun
Hỗ trợ tra cứu mô-đun qua dòng lệnh go hoặc chỉnh sửa tệp go.mod (nếu phát hiện câu lệnh tra cứu, lệnh go sẽ thay thế kết quả tra cứu vào tệp).
Ví dụ, nếu tệp go.mod hiện đang tham chiếu phiên bản v1.4.0 của github.com/gorilla/mux
:
require (
github.com/gorilla/mux v1.4.0 // indirect
)
Muốn tham chiếu phiên bản mới hơn, có thể chỉnh sửa tệp:
require (
github.com/gorilla/mux >v1.4.0 // indirect
)
Sau đó chạy go get
, nội dung tệp sẽ trở thành:
require (
github.com/gorilla/mux v1.5.0 // indirect
)
Câu lệnh tra cứu hỗ trợ sử dụng phiên bản xác định, tiền tố phiên bản hoặc phạm vi phiên bản.
a) Lấy phiên bản xác định, ví dụ v1.6.2:
$ go get github.com/gorilla/mux@v1.6.2
b) Lấy phiên bản mới nhất mang tiền tố phiên bản:
$ go get github.com/gorilla/mux@v1 // Lấy phiên bản mới nhất mang tiền tố v1
$ go get github.com/gorilla/mux@v1.5 // Lấy phiên bản mới nhất mang tiền tố v1.5
c) Lấy theo phạm vi phiên bản:
$ go get github.com/gorilla/mux@'>v1.5.0' // Lấy phiên bản gần nhất >v1.5.0
d) Lấy phiên bản mới nhất:
$ go get github.com/gorilla/mux@latest // Lấy phiên bản mới nhất
e) Lấy phiên bản vá mới nhất:
$ go get github.com/gorilla/mux@patch // Lấy phiên bản vá mới nhất
Nếu phiên bản hiện tại là v1.6.0, sau khi lấy phiên bản patch, phiên bản sẽ là v1.6.2.
f) Lấy theo hash commit:
$ go get github.com/gorilla/mux@e3702bed2
Lưu ý, việc chọn phiên bản ưu tiên phiên bản chính thức hơn là phiên bản thử nghiệm, ví dụ “<v1.2.3” sẽ lấy “v1.2.2” thay vì “v1.2.3-pre1”, mặc dù “v1.2.3-pre1” gần hơn.
Tất cả các lệnh tra cứu sau đây đều hợp lệ:
$ go get github.com/gorilla/mux@latest # Mặc định của go get là @latest
$ go get github.com/gorilla/mux@v1.6.2 # Chỉ định phiên bản v1.6.2
$ go get github.com/gorilla/mux@e3702bed2 # Chỉ định commit e3702bed2
$ go get github.com/gorilla/mux@c856192 # Chỉ định commit c856192
$ go get github.com/gorilla/mux@master # Chỉ định branch master
$ go get github.com/gorilla/mux@'>v1.6.2' # Tìm phiên bản >v1.6.2
7. Tải xuống và kiểm tra mô-đun
Lệnh go có thể tải mô-đun từ dịch vụ proxy hoặc trực tiếp từ kho mã nguồn dựa trên biến môi trường GOPROXY. GOPROXY mặc định là “sum.golang.org”.
Biến môi trường GOPRIVATE và GONOPROXY có thể được sử dụng để chỉ định các mô-đun cần bỏ qua proxy.
Lệnh go sẽ kiểm tra tổng hợp mã hóa của các mô-đun đã tải để đảm bảo tính nhất quán của quá trình xây dựng và phát hiện các cập nhật độc hại.
Tập tin go.sum nằm cùng thư mục gốc với tệp go.mod chứa thông tin tổng hợp mã hóa của các mô-đun phụ thuộc.
Mỗi dòng trong tệp go.sum có ba trường:
<module> <version>[/go.mod] <hash>
Mỗi mô-đun phụ thuộc có hai dòng ghi nhận trong tệp go.sum. Dòng đầu tiên cho biết giá trị hash của cây file version. Dòng thứ hai có “/go.mod” sau phiên bản, cho biết giá trị hash của tệp go.mod tương ứng với phiên bản đó.
Kiểm tra tổng hợp trước tiên sẽ kiểm tra tệp go.sum của mô-đun hiện tại, sau đó quay lại cơ sở dữ liệu tổng hợp của Go. Cơ sở dữ liệu tổng hợp có thể được cấu hình qua biến môi trường GOSUMDB và GONOSUMDB.
Nếu một mô-đun đã tải không xuất hiện trong tệp go.sum và mô-đun đó là mô-đun truy cập mở, lệnh go sẽ truy vấn cơ sở dữ liệu tổng hợp của Go để tìm dòng go.sum mong muốn. Nếu tổng hợp mã nguồn tải xuống không khớp với tổng hợp được truy vấn, lệnh go sẽ báo lỗi không khớp và dừng. Lưu ý rằng nếu mô-đun đã được liệt kê trong tệp go.sum, sẽ không kiểm tra thêm từ cơ sở dữ liệu.
Biến môi trường GOSUMDB được sử dụng để chỉ định tên cơ sở dữ liệu tổng hợp và hai trường tùy chọn là khóa công khai và URL.
Ví dụ:
GOSUMDB="sum.golang.org"
GOSUMDB="sum.golang.org+<publickey>"
GOSUMDB="sum.golang.org+<publickey>"
Mặc định GOSUMDB là sum.golang.org
, cơ sở dữ liệu tổng hợp chạy trên Google, lệnh go biết khóa công khai của sum.golang.org, sử dụng cơ sở dữ liệu khác cần đưa rõ khóa công khai, URL mặc định là “”.
Nếu thiết lập GOSUMDB thành off, hoặc sử dụng “go get” với nhãn “-insecure”, sẽ không sử dụng cơ sở dữ liệu tổng hợp, chấp nhận tất cả các mô-đun chưa xác định và bỏ qua bảo mật. Cách tốt hơn để bỏ qua kiểm tra cho các mô-đun cụ thể là sử dụng biến môi trường GOPRIVATE hoặc GONOSUMDB.
Lệnh “go mod verify” được sử dụng để kiểm tra tổng hợp của bộ đệm mô-đun có khớp với các ghi nhận trong tệp go.sum hay không.
8. Cấu hình mô-đun riêng tư
Theo những gì đã đề cập, lệnh go mặc định tải mô-đun từ kho mô-đun Go proxy.golang.org và kiểm tra từ cơ sở dữ liệu tổng hợp Go sum.golang.org.
Biến môi trường GOPRIVATE được sử dụng để kiểm soát các mô-đun nào là riêng tư và không sử dụng proxy mô-đun cũng như cơ sở dữ liệu tổng hợp. GOPRIVATE là một chuỗi các mẫu phân cách bằng dấu phẩy. Ví dụ:
GOPRIVATE=*.corp.example.com,rsc.io/private
Để kiểm soát chi tiết hơn về tải xuống và kiểm tra mô-đun, có thể kết hợp sử dụng biến môi trường GONOPROXY và GONOSUMDB. Hai biến này sẽ phủ蓋 thiết lập của GOPRIVATE.
Ví dụ, một công ty có thể sử dụng cấu hình sau để cung cấp dịch vụ cho mô-đun riêng tư:
GOPRIVATE=*.corp.example.com
GOPROXY=proxy.example.com
GONOPROXY=none
Như vậy, lệnh go và các công cụ khác sẽ coi các mô-đun khớp với tên miền con corp.example.com là riêng tư, và vì GONOPROXY được thiết lập là none, sẽ phủ蓋 thiết lập của GOPRIVATE, sử dụng thiết lập của GOPROXY để tải cả mô-đun công cộng và riêng tư.