Go infratuzilma jamoalarida mustahkam o'rnashib olganining eng muhim, ammo kam e'tirof etiladigan sabablaridan biri: u "tarqatiladigan artefaktni build qilish" jarayonini murakkab tadqiqot loyihasidan bir qatorli buyruqqa aylantiradi. Yonida alohida o'rnatiladigan runtime yo'q, maqsadli mashinada tashqi bog'liqliklarni o'rnatib yurish yo'q, boshqa interpretator versiyasidan kelib chiqadigan "mening kompyuterimda ishlagandi" degan muammo yo'q. go build ishlayotgan platforma uchun bitta to'liq statik bog'langan binar fayl hosil qiladi — va ikkita muhit (environment) o'zgaruvchisi orqali deyarli har qanday boshqa platforma uchun ham xuddi shunday binar yarata oladi. Ushbu bob shu imkoniyatlar ortidagi mexanika haqida: kross-kompilyatsiya aslida qanday ishlashi, build teglar (build tags) bitta kod bazasidan turli xususiyatga ega binarlar hosil qilishga qanday yordam berishi va loyihani versiyalangan, qayta takrorlanadigan relizga aylantirish yo'llari ko'rib chiqiladi.
Ko'pchilik kompilyatsiya qilinadigan tillar yo har bir maqsadli platforma uchun alohida toolchain, yo murakkab kross-kompilyator sozlamalarini talab qiladi. Go bu muammoni oson chetlab o'tadi, chunki uning kompilyatori, linkeri va standart kutubxonasi to'liq Go tilida yozilgan va oddiy (cgo ishlatilmagan) build jarayonida platformaga xos tashqi C toolchain'ini chaqirmaydi. GOOS=linux uchun butun kompilyatsiya konveyeri Mac, Windows yoki Linux tizimida turganingizdan qat'i nazar mutlaqo bir xil ishlaydi: u faqat qaysi tizim chaqiruvlari (syscall stub) va chaqiruv qoidalari (calling conventions) ulanishini o'zgartiradi xolos.
Amalda bu go builddan oldin ikkita environment o'zgaruvchisini ko'rsatishni anglatadi:
GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64
GOOS=windows GOARCH=amd64 go build -o myapp-windows-amd64.exe
GOOS=darwin GOARCH=arm64 go build -o myapp-darwin-arm64
GOOS operatsion tizimning chaqiruv qoidalari va syscall jadvalini tanlaydi; GOARCH esa protsessor instruksiyalari to'plami va ABI'ni tanlaydi. Bu kombinatsiya Go distributivi qo'llab-quvvatlaydigan juftliklardan biri bo'lishi kerak. Barcha yaroqli juftliklar ro'yxatini quyidagi buyruq bilan ko'rish mumkin:
go tool dist list
Ushbu buyruq linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, freebsd/amd64, js/wasm kabi o'nlab yozuvlarni chiqaradi. Ular ommabop server va desktop arxitekturalaridan tortib riscv64 hamda IBM'ning s390x kabi tizimlarigacha barchasini qamrab oladi. Runtime paytida ishlayotgan dastur o'zi aynan qaysi platforma uchun yig'ilganini quyidagicha tekshirishi mumkin:
fmt.Printf("OS: %s, Arch: %s\n", runtime.GOOS, runtime.GOARCH)
To'liq reliz matritsasini yaratadigan build skripti odatda kerakli platformalar ro'yxati bo'ylab aylanuvchi oddiy sikldan iborat bo'ladi:
#!/bin/bash
set -e
APP_NAME="myapp"
VERSION="1.0.0"
PLATFORMS=("linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64")
for platform in "${PLATFORMS[@]}"; do
IFS='/' read -r GOOS GOARCH <<< "$platform"
ext=""
[ "$GOOS" = "windows" ] && ext=".exe"
out="dist/${APP_NAME}-${VERSION}-${GOOS}-${GOARCH}${ext}"
echo "building $out"
GOOS=$GOOS GOARCH=$GOARCH go build -o "$out"
done
Yuqoridagi qulaylik va soddalik dasturingiz sof Go (pure Go) tilida yozilgan muddatda to'liq amal qiladi. cgo paydo bo'lgan zahoti — bog'liqlik C kutubxonasiga tayangani sababli yoki uni to'g'ridan-to'g'ri chaqirayotganingiz uchun — kross-kompilyatsiya shunchaki ikkita environment o'zgaruvchisini o'rnatish bilan bitmaydi. Endi maqsadli platforma uchun haqiqiy C kross-kompilyatori talab qilinadi, chunki cgo haqiqiy C kompilyatorini chaqirishi shart:
CC=x86_64-linux-gnu-gcc GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build
Go ekotizimida "sof Go (pure Go) bog'liqliklarini afzal ko'ring" degan tavsiyaning keng tarqalishi shunchaki C tiliga nisbatan falsafiy qarash emas, balki aynan mana shu amaliy sababga tayanadi. modernc.org/sqlite kabi sof Go SQLite drayveri reliz matritsangizni bir qatorli buyruq shaklida saqlab qoladi; SQLite C kutubxonasini cgo orqali o'rab ishlatadigan mattn/go-sqlite3 esa matritsangizdagi har bir maqsadli platforma uchun alohida C kross-kompilyatorini sozlash va sinashni talab qiladi. 56-bob, Cgo va Foreign Function Interface'lar, bu masalani chuqurroq ko'rib chiqadi; hozircha cgo ishlatilganda kross-kompilyatsiya ancha murakkablashishini tushunib olish kifoya.
Ba'zan bir xil kodni turli platformalar uchun shunchaki kompilyatsiya qilish kifoya qilmaydi; platformaga, xususiyatlar to'plamiga yoki build muhitiga qarab mutlaqo boshqa kod fayllari tanlanishi, keraksiz variantlar esa yakuniy binarga umuman kirmasligi talab etiladi. Kompilyatsiya cheklovlari (build constraints yoki norasmiy ravishda "build teglar") aynan shu maqsadda xizmat qiladi.
Zamonaviy sintaksis Go 1.17 da standartlashtirilgan — //go:build qatori:
//go:build linux
package main
Go hali ham 1.17 dan oldingi kodlar bilan orqaga moslik uchun eski // +build linux shaklini taniydi va faylda ikkalasi ham bo'lsa, gofmt ularni sinxron saqlaydi. Biroq yangi kodlarda faqat //go:build ishlatilishi kerak: eski sintaksis eskirgan (deprecated) deb hisoblanadi va gofmt uni yangi fayllarga qo'shmaydi. Muhim qoida: cheklov direktivasidan so'ng, package e'lonidan oldin albatta bo'sh qator tashlanishi shart, aks holda kompilyator uni oddiy izoh deb o'ylab, build direktivasi sifatida tanimaydi. Bu dasturchilar orasida uchraydigan "nega bu fayl doim kompilyatsiyaga qo'shilyapti?" degan xatolarning keng tarqalgan sabablaridan biridir.
Buning eng yaqqol amaliy qo'llanilishi — bir xil funksiya signaturasining har bir platformaga xos implementatsiyasini alohida fayllarga ajratishdir:
//go:build linux
package sys
import "syscall"
func setNonBlocking(fd int) error {
return syscall.SetNonBlocking(fd, true)
}
//go:build windows
package sys
func setNonBlocking(fd int) error {
// Windows ekvivalenti, butunlay boshqa syscall
return nil
}
Hosil bo'ladigan binar faylga ushbu fayllardan faqat bittasi qo'shiladi, ikkinchisi esa tegishli build jarayonida mutlaqo inobatga olinmaydi. Bu bitta fayl ichidagi if runtime.GOOS == "windows" tekshiruvidan sezilarli darajada ustun: shartli tarmoq baribir barcha platformalarda kompilyatsiyadan o'tishi kerak, ya'ni u boshqa operatsion tizimda mavjud bo'lmagan tizim API'larini chaqira olmaydi.
Build teglar faqat GOOS/GOARCH bilan cheklanmaydi: ular siz belgilagan har qanday maxsus teglarni (custom tags) ham tekshirishi mumkin. Funksional bayroqlar (feature flags) va ixtiyoriy bog'liqliklar dasturni ishga tushirish vaqtida emas, balki kompilyatsiya bosqichida aynan shu usulda filtrlanadi:
//go:build debug
package main
const debugMode = true
//go:build !debug
package main
const debugMode = false
go build # debugMode == false
go build -tags debug # debugMode == true
Cheklov ifodasi mantiqiy amallarni qo'llab-quvvatlaydi — AND uchun &&, OR uchun || va NOT uchun !:
//go:build linux && amd64
Bu fayl faqat x86-64 arxitekturasidagi Linux tizimlaridagina kompilyatsiya qilinadi. Masalan, faqat bitta arxitektura uchun assembly orqali optimallashtirilgan protsedura yozilgan bo'lsa, bu juda qo'l keladi; boshqa barcha platformalar uchun esa muqobil !(linux && amd64) fayli orqali platformalararo ko'chma (portable) sof Go implementatsiyasiga tayaniladi.
Xuddi shu andoza sukut bo'yicha har bir binarga qo'shilishi shart bo'lmagan ixtiyoriy bog'liqliklarni ajratishda ham asqotadi — masalan, cgo'ga tayanadigan ma'lumotlar bazasi drayverini faqat kerak bo'lgandagina ulash:
//go:build with_sqlite
package db
import _ "github.com/mattn/go-sqlite3"
go build -tags with_sqlite
-tags with_sqlite bayrog'isiz yig'ilgan har qanday binar bu drayverni o'ziga bog'lamaydi, natijada standart build yengil va cgo'siz toza bo'lib qoladi.
dist/ papkasida yotgan oddiy binar fayl hali to'laqonli reliz hisoblanmaydi: u versiyalangan, qayta takrorlanadigan (reproducible) va qaysi manba kodidan (commit) kelib chiqqani aniq kuzatiladigan bo'lishi kerak. Go qat'iy reliz qoidalarini majburlamaydi, ammo git teglari, -ldflags va CI konveyeri kombinatsiyasi deyarli barcha loyihalar ehtiyojini to'liq qoplaydi.
Teglarning o'zida semantik versiyalash (semver — MAJOR.MINOR.PATCH) qoidalariga amal qiling: bunda MAJOR — orqaga moslikni buzuvchi o'zgarishlar, MINOR — orqaga mos yangi imkoniyatlar, PATCH esa interfeysni o'zgartirmaydigan xatoliklarni tuzatishni anglatadi. Reliz bo'ladigan commit'ga teg qo'ying:
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
Versiyani manba kodiga qat'iy yozib (hardcode qilib) qo'yish o'rniga, qiymatini o'zgartiradigan linkerni boshqaruvchi -X flagi yordamida build vaqtida binar ichiga muhrlab (inject qilib) keting:
package main
var (
Version = "dev"
BuildTime = "unknown"
GitCommit = "unknown"
)
func main() {
if len(os.Args) > 1 && os.Args[1] == "version" {
fmt.Printf("%s (%s, built %s)\n", Version, GitCommit, BuildTime)
return
}
// ...
}
go build -ldflags "\
-X main.Version=$(git describe --tags --always --dirty) \
-X main.GitCommit=$(git rev-parse HEAD) \
-X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
-X importpath.name=value faqat paket darajasidagi satr (string) o'zgaruvchilarida ishlaydi (konstantalarda ishlamaydi: linker ma'lumotlar bo'limidagi qiymatni o'zgartiradi, konstantalar esa u yerda saqlanmaydi). Shuning uchun yuqoridagi Version o'zgaruvchisi const emas, boshlang'ich qiymatga ega var sifatida e'lon qilingan. Makefile target'i bu uzun buyruqni har safar eslab o'tirmasdan, bir xil va takrorlanadigan tarzda bajarish imkonini beradi:
VERSION := $(shell git describe --tags --always --dirty)
GIT_COMMIT := $(shell git rev-parse HEAD)
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X main.Version=$(VERSION) -X main.GitCommit=$(GIT_COMMIT) -X main.BuildTime=$(BUILD_TIME)
build:
go build -ldflags "$(LDFLAGS)" -o bin/myapp
Reliz matritsasini CI tizimida avtomatlashtiring, shunda reliz chiqarish inson omiliga bog'liq, xatoliklarga moyil uzun cheklist bo'lib qolmay, oddiygina git tegi push qilinganda avtomatik ishga tushadi. Teg yuborilganda ishga tushadigan, har bir platforma uchun binarlarni yig'adigan va natijalarni GitHub Release'ga biriktiradigan GitHub Actions workflow'i buning ommabop va ixcham namunasidir:
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- name: Build all platforms
run: |
mkdir -p dist
VERSION=${GITHUB_REF#refs/tags/}
for GOOS in linux darwin windows; do
for GOARCH in amd64 arm64; do
ext=""; [ "$GOOS" = "windows" ] && ext=".exe"
GOOS=$GOOS GOARCH=$GOARCH go build \
-ldflags "-X main.Version=$VERSION" \
-o "dist/myapp-$VERSION-$GOOS-$GOARCH$ext"
done
done
- uses: softprops/action-gh-release@v2
with: { files: dist/* }
Murakkabroq ehtiyojlari bor loyihalar — nazorat summalari (checksums), paket menejerlari formulalari, konteyner obrazlari, commit tarixidan tuziladigan changelog'lar — odatda bularning barchasini qo'lda yozish o'rniga GoReleaser kabi ixtisoslashgan vositadan foydalanadi. Ammo bu kabi barcha vositalarning negizida aynan yuqoridagi primitivlar yotadi: GOOS/GOARCH sikllari hamda -ldflags -X mexanizmi.
Go 1.24 dan boshlab qo'shimcha versiya ma'lumotlarini hech qanday ortiqcha sozlamalarsiz olish mumkin: go build asosiy modulning versiyasini — u qaysi git teg yoki commit asosida yig'ilganini — -ldflags talab qilmasdan avtomatik ravishda binar ichiga yozib qo'yadi. Shu tarzda yig'ilgan binar faylda go version -m ./myapp buyrug'i ishlatilsa, VCS revision ma'lumotlari va yig'ish vaqtida ish daraxti (working tree) toza bo'lgan-bo'lmagani ko'rinadi. Bu yuqoridagi -ldflags -X main.Version=... andozasini butunlay almashtirmaydi (dasturingiz version buyrug'ida yoki health-check endpoint'ida ko'rsatishi mumkin bo'lgan o'zgaruvchini belgilash uchun hamon linker flaglari kerak), ammo kimdir o'zgaruvchini ulamagan taqdirda ham, binar o'zining kelib chiqish tarixini ishonchli saqlab qolishini ta'minlaydi. Bu kimdir sizga bergan binar fayl qaysi aniq commit asosida kompilyatsiya qilinganini aniqlashda juda qulay zaxira vositasidir. Agar build muhitida VCS metama'lumotlari mavjud bo'lmasa (masalan, CI'dagi sayoz klonlash holatlarida) va toza build kerak bo'lsa, uni -buildvcs=false bayrog'i bilan o'chirib qo'yish mumkin.
Avtomatlashtirilgan vositalar uchun — CI tizimlari, build o'ramlari va odam o'qishi uchun mo'ljallangan matnni parsing qilib yurmasdan go build natijasini dasturiy tarzda tahlil qiluvchi dasturlar uchun — go build -json va go install -json (Go 1.24 dan boshlab) strukturalangan JSON chiqaradi. go test -json esa build xatoliklarini test natijalari bilan bir xil JSON oqimiga birlashtiradi (Go 1.27 dan boshlab "error", "error-continue" va "frame" qatorlarini ajratuvchi "OutputType" maydoni bilan), natijada build va test natijalarini alohida-alohida parse qilishga hojat qolmaydi.
Tarqatish uchun hajm muhim bo'lsa — konteyner obrazlari, o'rnatilgan (embedded) tizimlar yoki shunchaki tezroq yuklab olish maqsadida — binar hajmini quyidagicha qisqartirish mumkin:
go build -ldflags "-s -w" -o myapp
-s simvollar jadvalini (symbol table) olib tashlaydi, -w esa DWARF debug ma'lumotlarini qoldirmaydi. Bu ikkisi binar fayl hajmini sezilarli darajada kichraytiradi. Biroq buning evaziga dasturga debugger ulash yoki favqulodda to'xtash (crash) jurnallarida qatorlarni simvollashtirish (stack trace'da funksiya nomlarini aniq ko'rish) imkoni cheklanadi. Shuning uchun bu optimizatsiya ishlab chiqarishga (production) yuboriladigan relizlar uchun juda mos keladi, ammo lokal ishlab chiqish jarayonida uni ishlatmaslik tavsiya etiladi.
Mahalliy tizimda ishga tushirmasangiz ham, kross-kompilyatsiya qilingan maqsadli binarlar haqiqatan muammosiz yig'ilishini tekshirib boring — bu platformaga xos xatoliklarni erta aniqlovchi tejamkor CI tekshiruvidir:
GOOS=linux GOARCH=arm64 go build ./...
Bu buyruq dasturni ishga tushirmaydi, faqat kompilyatsiyani tekshiradi. Ammo u platformalararo ishlashi kerak bo'lgan umumiy kodga kimdir e'tiborsizlik bilan faqat bitta tizimga (masalan, faqat Linux) xos chaqiruvni kiritib qo'yganini boshqa platforma foydalanuvchilari xabar qilishidan ancha oldin aniqlashga yordam beradi.
Bularning barchasi qandaydir murakkab reliz falsafasiga ko'r-ko'rona ergashishni talab qilmaydi. Reliz qaydlarini commit qilasizmi, GoReleaser vositasidan foydalanasizmi yoki shell skript yozasizmi — asosiy qolip doimo bir xil: har bir platforma uchun ikkita environment o'zgaruvchisi orqali boshqariladigan deterministik yig'ish, linker yordamida kiritilgan dinamik versiya va inson xatolarini chetlab o'tish uchun git teg orqali avtomatlashtirilgan jarayon. 53-bob, Hujjatlashtirish va API'lar, dasturiy ta'minotni boshqalar qulay ishlata oladigan qilib taqdim etishning ikkinchi muhim qismiga bag'ishlanadi: siz yaratgan binar va kutubxonalardan foydalanuvchi dasturchilar uchun to'g'ri API dizayni va hujjatlashtirish qoidalari.