Пример создания Makefile для Go-приложений

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
В этом руководстве мы рассмотрим, как разработчик Go может использовать Makefile при разработке собственных приложений.

image

Что такое Makefile-ы?


Makefile — невероятно полезный инструмент автоматизации, который можно использовать для запуска и сборки приложений не только на Go, но и на большинстве других языков программирования.

Его часто можно увидеть в корневом каталоге множества Go приложений на Github и Gitlab. Он широко используются в качестве инструмента для автоматизации задач, которые часто сопровождают разработчиков.

Если вы используете Go для создания веб-сервисов, то Makefile поможет решить следующие задачи:

  • Автоматизация вызова простых команд, таких как: compile, start, stop, watch и т. д.
  • Управление специфичными для проекта переменными окружения. Он должен подключать файл .env.
  • Режим разработки, который автоматически компилируется при изменении.
  • Режим разработки, который показывает ошибки компиляции.
  • Определение GOPATH для конкретного проекта, чтобы мы могли хранить зависимости в папке vendor.
  • Упрощенный мониторинг файлов, например, make watch run = «go test. / ...»

Вот типичная структура каталогов для проекта:

.env
Makefile
main.go
bin/
src/
vendor/

Если мы вызовем команду make в этом каталоге, то получим следующий вывод:

$  make

 Choose a command run in my-web-server:

 install   Install missing dependencies. Runs `go get` internally.
 start     Start in development mode. Auto-starts when code changes.
 stop      Stop development mode.
 compile   Compile the binary.
 watch     Run given command when code changes. e.g; make watch run="go test ./..."
 exec      Run given command, wrapped with custom GOPATH. e.g; make exec run="go test ./..."
 clean     Clean build files. Runs `go clean` internally.

Переменные окружения


Первое, чего мы хотим от Makefile — подключать переменные окружения, которые мы определили для проекта. Поэтому первая строчка будет выглядеть так:

include .env

Далее мы определяем имя проекта, папки/файлы Go, пути к pid…

PROJECTNAME=$(shell basename "$(PWD)")

# Go переменные.
GOBASE=$(shell pwd)
GOPATH=$(GOBASE)/vendor:$(GOBASE):/home/azer/code/golang  #Вы можете удалить или изменить путь после двоеточия.
GOBIN=$(GOBASE)/bin
GOFILES=$(wildcard *.go)

# Перенаправление вывода ошибок в файл, чтобы мы показывать его в режиме разработки.
STDERR=/tmp/.$(PROJECTNAME)-stderr.txt

# PID-файл будет хранить идентификатор процесса, когда он работает в режиме разработки
PID=/tmp/.$(PROJECTNAME)-api-server.pid

# Make пишет работу в консоль Linux. Сделаем его silent.
MAKEFLAGS += --silent

В оставшейся части Makefile мы будем часто использовать переменную GOPATH. Все наши команды должны быть связаны с GOPATH конкретного проекта, иначе они не будут работать. Это обеспечивает чистую изоляцию наших проектов, но при этом усложняет работу. Чтобы упростить задачу, мы можем добавить команду exec, которая выполнит любую команду с нашим GOPATH.

# exec: Запускает команду с кастомным GOPATH. Пример:  make exec run = " go test ./...”
exec:
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) $(run)

Однако стоит помнить, что использовать exec нужно только в том случае, если требуется сделать то, что нельзя прописать в makefile.

Режим разработки


Режим разработки должен:

  • Очищать кеш сборки
  • Компилировать код
  • Запускать сервис в бэкграунде
  • Повторять это шаги, когда код изменяется.

Звучит просто. Однако, сложность заключается в том, что мы одновременно запускаем и сервис, и файл-watcher. Перед запуском нового процесса, мы должны обеспечить корректную остановку, а также не нарушить обычное поведение командной строки при нажатии Control-C или Control-D.

start:
	bash -c "trap 'make stop' EXIT; $(MAKE) compile start-server watch run='make compile start-server'"

stop: stop-server

Описанный выше код решает следующие задачи:

  • Компилирует и запускает сервис в фоновом режиме.
  • Основной процесс работает не в фоновом режиме, поэтому мы можем его прервать, используя Control-C.
  • Останавливает фоновые процессы, когда основной процесс прерывается. trap нужна как раз для этого.
  • Рекомпилирует и перезапускает сервер при изменении кода.

В следующих разделах я объясню эти команды подробнее.

Компиляция


Команда compile не просто вызывает go compile в фоновом режиме — она очищает вывод ошибок и печатает упрощенную версию.

Вот как выглядит вывод командной строки, когда мы внесли «ломающие» правки:

image

compile:
	@-touch $(STDERR)
	@-rm $(STDERR)
	@-$(MAKE) -s go-compile 2> $(STDERR)
	@cat $(STDERR) | sed -e '1s/.*/\nError:\n/'  | sed 's/make\[.*/ /' | sed "/^/s/^/     /" 1>&2

Запуск/остановка сервера


start-server запускает бинарник, скомпилированный в фоновом режиме, сохраняя свой PID во временный файл. stop-server читает PID и убивает процесс при необходимости.

start-server:
	@echo "  >  $(PROJECTNAME) is available at $(ADDR)"
	@-$(GOBIN)/$(PROJECTNAME) 2>&1 & echo $$! > $(PID)
	@cat $(PID) | sed "/^/s/^/  \>  PID: /"

stop-server:
	@-touch $(PID)
	@-kill `cat $(PID)` 2> /dev/null || true
	@-rm $(PID)

restart-server: stop-server start-server

Мониторинг изменений


Нам нужен файл-watcher для отслеживания изменений. Я перепробовал многие, но не смог найти подходящего, поэтому написал свой собственный инструмент для мониторинга файлов — yolo. Установите его с помощью команды:

$  go get github.com/azer/yolo

После установки мы можем наблюдать за изменениями в каталоге проекта, исключая папки vendor и bin.

## watch: Запустите данную команду при изменении кода, например make watch run="echo 'hey'"
watch:
	@yolo -i . -e vendor -e bin -c $(run)

Теперь у нас есть команда watch, которая рекурсивно отслеживает изменения в каталоге проекта, за исключением каталога vendor. Мы можем просто передать любую команду в run.
Например, start вызывает make-start-server при изменении кода:

make watch run="make compile start-server"

Мы можем использовать его для запуска тестов или проверки race conditions автоматически. Переменные окружения будут установлены при исполнении, поэтому вам не нужно беспокоиться о GOPATH:

make watch run="go test ./..."

Приятной особенностью Yolo является его веб-интерфейс. Если его включить, вы сможете сразу увидеть вывод вашей команды в веб-интерфейсе. Все, что вам нужно, это передать параметр -a:

yolo -i . -e vendor -e bin -c "go run foobar.go" -a localhost:9001

Откройте localhost: 9001 в браузере и сразу же увидите результат работы:

image

Установка зависимостей


Когда мы вносим изменения в код, мы бы хотели, чтобы отсутствующие зависимости были загружены до компиляции. Команда install сделает эту работу за нас:

install: go-get

Мы будем автоматизировать вызов install при изменении файла перед компиляцией, поэтому зависимости будут установлены автоматически. Если вы хотите установить зависимость вручную, можете запустить:

make install get="github.com/foo/bar"

Внутри эта команда будет преобразована в:

$ GOPATH=~/my-web-server GOBIN=~/my-web-server/bin go get github.com/foo/bar

Как это работает? Смотрите следующий раздел, где мы добавляем обычные команды Go для реализации команд более высокого уровня.

Команды Go


Поскольку мы хотим установить GOPATH в каталог проекта, чтобы упростить управление зависимостями, которое до сих пор официально не решено в экосистеме Go, нам нужно обернуть все команды Go в Makefile.

go-compile: go-clean go-get go-build

go-build:
	@echo "  >  Building binary..."
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build -o $(GOBIN)/$(PROJECTNAME) $(GOFILES)

go-generate:
	@echo "  >  Generating dependency files..."
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go generate $(generate)

go-get:
	@echo "  >  Checking if there is any missing dependencies..."
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go get $(get)

go-install:
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install $(GOFILES)

go-clean:
	@echo "  >  Cleaning build cache"
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go clean

Help


Наконец, нам нужна команда help, чтобы увидеть список доступных команд. Мы можем автоматически генерировать красиво отформатированный вывод справки, используя команды sed и column:

help: Makefile
	@echo " Choose a command run in "$(PROJECTNAME)":"
	@sed -n 's/^##//p' $< | column -t -s ':' |  sed -e 's/^/ /'

Следующая команда сканирует Makefile на строки, начинающиеся с ##, и выводит их. Таким образом, вы можете просто комментировать определенные команды, и комментарии будут выводиться командой help.

Если мы добавим несколько комментариев:

## install: Install missing dependencies. Runs `go get` internally.
install: go-get

## start: Start in development mode. Auto-starts when code changes.
start:

## stop: Stop development mode.
stop: stop-server

Мы получим:

$  make help

 Choose a command run in my-web-server:

 install   Install missing dependencies. Runs `go get` internally.
 start     Start in development mode. Auto-starts when code changes.
 stop      Stop development mode.

Окончательный вариант


include .env

PROJECTNAME=$(shell basename "$(PWD)")

# Go related variables.
GOBASE=$(shell pwd)
GOPATH="$(GOBASE)/vendor:$(GOBASE)"
GOBIN=$(GOBASE)/bin
GOFILES=$(wildcard *.go)

# Redirect error output to a file, so we can show it in development mode.
STDERR=/tmp/.$(PROJECTNAME)-stderr.txt

# PID file will keep the process id of the server
PID=/tmp/.$(PROJECTNAME).pid

# Make is verbose in Linux. Make it silent.
MAKEFLAGS += --silent

## install: Install missing dependencies. Runs `go get` internally. e.g; make install get=github.com/foo/bar
install: go-get

## start: Start in development mode. Auto-starts when code changes.
start:
    bash -c "trap 'make stop' EXIT; $(MAKE) compile start-server watch run='make compile start-server'"

## stop: Stop development mode.
stop: stop-server

start-server: stop-server
	@echo "  >  $(PROJECTNAME) is available at $(ADDR)"
	@-$(GOBIN)/$(PROJECTNAME) 2>&1 & echo $$! > $(PID)
	@cat $(PID) | sed "/^/s/^/  \>  PID: /"

stop-server:
	@-touch $(PID)
	@-kill `cat $(PID)` 2> /dev/null || true
	@-rm $(PID)

## watch: Run given command when code changes. e.g; make watch run="echo 'hey'"
watch:
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) yolo -i . -e vendor -e bin -c "$(run)"

restart-server: stop-server start-server

## compile: Compile the binary.
compile:
	@-touch $(STDERR)
	@-rm $(STDERR)
	@-$(MAKE) -s go-compile 2> $(STDERR)
	@cat $(STDERR) | sed -e '1s/.*/\nError:\n/'  | sed 's/make\[.*/ /' | sed "/^/s/^/     /" 1>&2

## exec: Run given command, wrapped with custom GOPATH. e.g; make exec run="go test ./..."
exec:
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) $(run)

## clean: Clean build files. Runs `go clean` internally.
clean:
	@(MAKEFILE) go-clean

go-compile: go-clean go-get go-build

go-build:
	@echo "  >  Building binary..."
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build -o $(GOBIN)/$(PROJECTNAME) $(GOFILES)

go-generate:
	@echo "  >  Generating dependency files..."
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go generate $(generate)

go-get:
	@echo "  >  Checking if there is any missing dependencies..."
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go get $(get)

go-install:
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install $(GOFILES)

go-clean:
	@echo "  >  Cleaning build cache"
	@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go clean

.PHONY: help
all: help
help: Makefile
	@echo
	@echo " Choose a command run in "$(PROJECTNAME)":"
	@echo
	@sed -n 's/^##//p' $< | column -t -s ':' |  sed -e 's/^/ /'
	@echo
Источник: https://habr.com/ru/post/461467/


Интересные статьи

Интересные статьи

К 2020 году вы не могли не заметить, что миром правят данные. И, как только речь заходит о работе с ощутимыми объёмами, появляется необходимость в сложном многоэтапном ко...
Сегодня мы хотим рассказать об одном из наших новых продуктов – SSD-накопителе Seagate FireCuda 520. Но не спешите листать ленту дальше с мыслями «ну вот, очередной хвалебный обзор гаджета от бре...
Предлагаю ознакомиться с рашифровкой доклада Андрей Сальников из Data Egret "Инструменты создания бэкапов PostgreSQL" . В конце обновленная сводная таблица по инстрментам Данный доклад...
Устраивать конкурсы в инстаграме сейчас модно. И удобно. Инстаграм предоставляет достаточно обширный API, который позволяет делать практически всё, что может сделать обычный пользователь ручками.
Некоторое время назад мне довелось пройти больше десятка собеседований на позицию php-программиста (битрикс). К удивлению, требования в различных организациях отличаются совсем незначительно и...