Let's Go Further ساخت، versioning و کنترل کیفیت › ساخت باینری‌ها
قبلی · فهرست مطالب · بعدی
فصل ۱۹.۵.

ساخت باینری‌ها

تا اینجا ما API خود را با استفاده از دستور go run (یا به‌تازگی make run/api) اجرا کرده‌ایم. اما در این فصل تمرکز ما روی توضیح نحوه ساخت یک باینری قابل اجرا است که بتوانید آن را توزیع کرده و روی ماشین‌های دیگر بدون نیاز به نصب Go toolchain اجرا کنید.

برای ساخت یک باینری باید از دستور go build استفاده کنیم. به‌عنوان یک مثال ساده، استفاده به این صورت است:

$ go build -o=./bin/api ./cmd/api

وقتی این دستور را اجرا می‌کنیم، go build پکیج cmd/api (و هر پکیج وابسته‌ای) را کامپایل کرده و فایل‌هایی حاوی machine code تولید می‌کند و سپس آن‌ها را لینک می‌کند تا یک باینری قابل اجرا تشکیل شود. در دستور بالا، باینری قابل اجرا در مسیر ./bin/api خروجی داده خواهد شد.

برای راحتی کار، بیایید یک قانون جدید build/api به makefile اضافه کنیم که این دستور را اجرا کند، به این صورت:

فایل: Makefile
...

# ==================================================================================== #
# BUILD
# ==================================================================================== #

## build/api: build the cmd/api application
.PHONY: build/api
build/api:
	@echo 'Building cmd/api...'
	go build -o=./bin/api ./cmd/api

پس از اتمام کار، قانون make build/api را اجرا کنید. باید ببینید که یک فایل باینری قابل اجرا در مسیر ./bin/api ایجاد شده است.

$ make build/api 
Building cmd/api...
go build -o=./bin/api ./cmd/api
$ ls -l ./bin/
total 10228
-rwxrwxr-x 1 alex alex 10470419 Apr 18 16:05 api

و باید بتوانید این فایل اجرایی را برای راه‌اندازی برنامه API خود اجرا کرده و در صورت نیاز مقادیر command-line flag را وارد کنید. به‌عنوان مثال:

$ ./bin/api -port=4040 -db-dsn=postgres://greenlight:pa55word@localhost/greenlight
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established"
time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4040 env=development

کاهش حجم باینری

اگر با دقت به باینری قابل اجرا نگاه کنید، می‌بینید که حجم آن ۱۰۴۷۰۴۱۹ بایت (حدود ۱۰.۵ مگابایت) است.

$ ls -l ./bin/api 
-rwxrwxr-x 1 alex alex 10470419 Apr 18 16:05 ./bin/api

امکان کاهش حجم باینری حدود ۲۵٪ با دستور به Go linker برای حذف symbol table‌ها و اطلاعات دیباگ DWARF از باینری وجود دارد. می‌توانیم این کار را به‌عنوان بخشی از دستور go build با استفاده از linker flag -ldflags="-s" به این صورت انجام دهیم:

فایل: Makefile
...

# ==================================================================================== #
# BUILD
# ==================================================================================== #

## build/api: build the cmd/api application
.PHONY: build/api
build/api:
	@echo 'Building cmd/api...'
	go build -ldflags='-s' -o=./bin/api ./cmd/api

اگر دوباره make build/api را اجرا کنید، باید ببینید که باینری به‌طور قابل‌توجهی کوچک‌تر شده است (در مورد من حدود ۷.۶ مگابایت).

$ make build/api 
Building cmd/api...
go build -ldflags='-s' -o=./bin/api ./cmd/api
$ ls -l ./bin/api 
-rwxrwxr-x 1 alex alex 7618560 Apr 18 16:08 ./bin/api

مهم است بدانید که حذف این اطلاعات، دیباگ کردن اجرا با ابزارهایی مانند Delve یا gdb را سخت‌تر می‌کند. اما به‌طور کلی، نیازی به انجام این کار نیست — و حتی یک پیشنهاد باز از طرف Rob Pike وجود دارد که حذف اطلاعات DWARF را به‌صورت پیش‌فرض رفتار linker در آینده قرار دهد.

کامپایل متقاطع

به‌صورت پیش‌فرض، دستور go build یک باینری مناسب برای استفاده روی سیستم‌عامل و معماری ماشین محلی شما تولید می‌کند. اما همچنین از کامپایل متقاطع (cross-compilation) پشتیبانی می‌کند، بنابراین می‌توانید یک باینری مناسب برای استفاده روی ماشین دیگری تولید کنید. این ویژگی به‌ویژه زمانی مفید است که روی یک سیستم‌عامل توسعه می‌دهید و روی سیستم‌عامل دیگری مستقر می‌کنید.

برای مشاهده لیست تمام ترکیبات سیستم‌عامل/معماری که Go پشتیبانی می‌کند، می‌توانید دستور go tool dist list را به این صورت اجرا کنید:

$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
...

و می‌توانید سیستم‌عامل و معماری مورد نظر خود برای ساخت باینری را با تنظیم متغیرهای محیطی GOOS و GOARCH هنگام اجرای go build مشخص کنید. به‌عنوان مثال:

$ GOOS=linux GOARCH=amd64 go build {args}

در بخش بعدی کتاب، نحوه استقرار یک باینری قابل اجرا روی یک سرور Ubuntu Linux میزبانی‌شده توسط DigitalOcean را بررسی خواهیم کرد. برای این کار، به باینری نیاز داریم که برای اجرا روی ماشینی با ترکیب سیستم‌عامل و معماری linux/amd64 طراحی شده باشد.

پس بیایید قانون make build/api خود را به‌روز کنیم تا دو باینری تولید کند — یکی برای استفاده روی ماشین محلی و دیگری برای استقرار روی سرور Ubuntu Linux.

فایل: Makefile
...

# ==================================================================================== #
# BUILD
# ==================================================================================== #

## build/api: build the cmd/api application
.PHONY: build/api
build/api:
	@echo 'Building cmd/api...'
	go build -ldflags='-s' -o=./bin/api ./cmd/api
	GOOS=linux GOARCH=amd64 go build -ldflags='-s' -o=./bin/linux_amd64/api ./cmd/api

اگر در حال دنبال کردن مراحل هستید، دوباره make build/api را اجرا کنید.

باید ببینید که اکنون دو باینری تولید شده‌اند — باینری cross-compiled در دایرکتوری ./bin/linux_amd64 قرار دارد، به این صورت:

$ make build/api 
Building cmd/api...
go build -ldflags='-s' -o=./bin/api ./cmd/api
GOOS=linux GOARCH=amd64 go build -ldflags='-s' -o=./bin/linux_amd64/api ./cmd/api
$ tree ./bin
./bin
├── api
└── linux_amd64
    └── api

به‌عنوان یک قانون کلی، احتمالاً نمی‌خواهید باینری‌های Go خود را در کنار کد منبع در version control ثبت کنید، زیرا حجم مخزن شما را به‌طور قابل‌توجهی افزایش می‌دهند.

پس، اگر در حال دنبال کردن مراحل هستید، بیایید یک قانون اضافی به فایل .gitignore اضافه کنیم که به Git دستور دهد محتوای دایرکتوری bin را نادیده بگیرد.

$ echo 'bin/' >> .gitignore
$ cat .gitignore
.envrc
bin/

اطلاعات تکمیلی

کش کردن build

مهم است بدانید که دستور go build خروجی build را در build cache Go ذخیره می‌کند. این خروجی کش‌شده در ساخت‌های آینده در صورت مناسب بودن دوباره استفاده خواهد شد، که می‌تواند زمان کلی build برنامه شما را به‌طور قابل‌توجهی افزایش دهد.

اگر مطمئن نیستید build cache شما کجا قرار دارد، می‌توانید با اجرای دستور go env GOCACHE بررسی کنید:

$ go env GOCACHE
/home/alex/.cache/go-build

همچنین باید بدانید که build cache به‌صورت خودکار تغییرات کتابخانه‌های C را که کد شما با cgo وارد می‌کند تشخیص نمی‌دهد. بنابراین، اگر از آخرین build یک کتابخانه C را تغییر داده‌اید، باید از flag -a استفاده کنید تا تمام پکیج‌ها هنگام اجرای go build بازسازی شوند. به‌طور جایگزین، می‌توانید از go clean برای پاک کردن cache استفاده کنید:

$ go build -a -o=/bin/foo ./cmd/foo        # Force all packages to be rebuilt
$ go clean -cache                          # Remove everything from the build cache