Впрыскивание значений переменных во время сборки двоичного файла + создание скрипта сборки
Установка значения для переменных на этапе сборки может быть очень удобной, например, для встраивания номера версии, хэша фиксации. Другой пример - установка некоторого значения переменной, которое вы не хотели бы использовать с помощью параметров конфигурации в сборке выпуска.
В Go можно установить значение переменных на этапе компиляции, используя параметр -ldflags <flags>
, предоставленный для команды go build
.
Флаги сборки
Параметр - ldflags
позволяет указать аргументы, которые затем будут переданы в go link
инструмент, используемый в процессе сборки. В нашем случае особенно интересен один из аргументов:
-X importpath.name=value
Set the value of the string variable in importpath named name to value.
Да, он позволяет установить значение для любой строковой переменной, если выполняются следующие условия:
- Переменная не должна быть инициализирована или инициализирована постоянной строкой. -X не будет работать, если значение инициализировано с помощью:
var (
// Call to external function
and variable
Build = fmt.Sprintf("rc-%d", someIntegerVar)
)
но работает с:
var (
// Call to external function
Build = "rc-4"
)
Давайте создадим скрипт сборки, который автоматически установит номер версии и зафиксирует хеш
Мы создадим простую программу с одной функцией - выводом версии и строкой сборки (фиксации).
Создайте каталог проекта и инициализируйте репозиторий git внутри:
mkdir ~/go-build-variables
cd ~/go-build-variables
git init
Не создавайте простой main.go
файл с простым содержанием:
package main
import ( "fmt" )
var ( Version = "0.0.1" Build = "000000" )
func main() { fmt.Printf("version %s, build: %s\n", Version, Build) }
На этапе сборки мы будем заменять переменные Version
и Build
и изменять их значения по умолчанию.
Вы можете создать и предоставить новые значения для этих переменных с помощью команды:
go build -ldflags "-X main.Version=0.1.0 -X main.Build=12345" main.go
main.Version
указывают на наш пакет, который в данном случае main
, и имена переменных Version
и Build
.
Если вы сейчас запустите скомпилированный main
файл, вы должны увидеть результат, подобный следующему:
$: ./main
version 0.1.0, build: 12345
Но мы можем сделать больше. Давайте создадим сценарий-оболочку для автоматизации процесса сборки!
Создадим удобный bash
скрипт-оболочку для процесса сборки. Такой сценарий может использоваться вашими инструментами CI / CD. Наш скрипт принимает следующие аргументы:
-v
(обязательно) для предоставления версии-e
(необязательно), чтобы предоставить целевую среду, для которой мы хотим создать двоичный файл (разработка или выпуск)-o
(необязательно) для указания имени выходного двоичного файла
Несколько слов об окружающей среде. Почему это здесь? Возможно, вы работаете над проектом, который требует других настроек для целей разработки (например, добавление информации к номеру версии, сборка с разными флагами, отключение некоторых проверок…), но абсолютно не может присутствовать в версии, выпущенной для клиентов (или production
версии). Таким образом, хорошей практикой было бы создание release
сборки по умолчанию и development
сборки по специальному запросу (наоборот, в зависимости от ваших бизнес-потребностей).
Итак, теперь мы создадим bash
скрипт для нашего процесса сборки:
#!/usr/bin/env bash
function print_usage { printf "Usage: build.sh -v [-e] SRC_FILE\n" printf "\t-v\tProvide binary version, example: -v 0.0.1\n" printf "\t-e\tProvide target environment, any value different than \"development\" will result in release build, example: -v release\n" printf "\t-o\tOutput binary filename\n" printf "\t-h\tPrint help and exit\n" exit 1 }
# Iterate through arguments list and process arguments while getopts "v:e:o:h" o; do case "${o}" in v) VERSION=${OPTARG} ;; e) ENV=${OPTARG} ;; o) OUTPUT_FILE=${OPTARG} ;; h) print_usage ;; *) printf "Error: Option undefined\n\n" print_usage ;; esac done
# Discard processed arguments from arguments list ("OPTIND-1" is index of first unprocessed argument - source filename in this case) shift $((OPTIND-1))
SRC_FILE="${BASH_ARGV[0]}"
# Version number and source filename is required if [[ -z $VERSION ]] then print_usage fi
if [[ -z $SRC_FILE ]] then print_usage fi
# If output filename is not set, we will mimic how go by default handles this situation - source main filename without .ext if [[ -z $OUTPUT_FILE ]] then OUTPUT_FILE="${SRC_FILE%.*}" fi
# Any value other than "development" will produce production build. # This is to prevent building and accidental release of development builds which can be unsafe or unstable. # Here we are appending \"rc-\" to version number and setting output filename to indicate if this is release or dev build, # but it can be more here, for example setting another variable to indicate development build or building with different flags. if [[ "$ENV" == "development" ]] then VERSION="rc-$VERSION" OUTPUT_FILE="$OUTPUT_FILE-development" else OUTPUT_FILE="$OUTPUT_FILE-stable" fi
# Enable strict checking for variables initialization and exit codes. set -euo pipefail
# Format \"%h\" for git log prints only short commit hashes in reverse chronological order, # -n 1 returns first row - last commit BUILD_HASH=$(git log --pretty=format:"%h" -n 1|head -1)
echo "Building: $SRC_FILE version: $VERSION from commit: $BUILD_HASH to file: $OUTPUT_FILE" go build -ldflags "-X main.Version=$VERSION -X main.Build=$BUILD_HASH" -o "$OUTPUT_FILE" "$SRC_FILE"
Код прокомментирован, но я добавлю несколько слов:
- Сначала мы перебираем аргументы, переданные скрипту.
shift $((OPTIND-1))
здесь, чтобы отбросить обработанные аргументы. Нам это нужно, потому что имя файла - это последний аргумент, который нужно передать скрипту.OUTPUT_FILE="${SRC_FILE%.*}"
будет выводить исходное имя файла без расширения - если выходное имя файла не было указано (по умолчанию в go)
Теперь, чтобы получить последнюю фиксацию (которую мы будем использовать в качестве информации о сборке), нам нужно зафиксировать изменения:
git add main.go build.sh
git commit -m "Initial application and build script"
Теперь сделайте указанный выше скрипт исполняемым файлом и запустите его, указав версию и исходный файл:
chmod 744 build.sh
./build.sh -v 0.3.0 main.go
Он создаст основной стабильный файл, который является нашим исполняемым файлом. Если вы запустите его, вы должны увидеть:
version 0.3.0, build: <commit hash>
Вы можете проверить свой хеш фиксации с помощью команды (она должна быть первой):
git log --oneline
Теперь давайте построим еще несколько вариантов. Эта команда создаст разрабатываемую версию 0.4.0
нашего приложения под именем api-server-development
:
./build.sh -v 0.4.0 -e development -o api-server main.go
Резюме
Вот и все, мы создали основы для процесса развертывания с управлением версиями. Спасибо за чтение!
Если вам понравилась эта статья, подумайте о подписке на мою рассылку здесь