Maven-путь построения Go проектов

в 7:58, , рубрики: Go, golang, maven, maven plugin

Сначала небольшая предыстория. В начале 2010-х, я сделал небольшую утилиту-конвертер для BIN файлов эмулятора БК-0010 в WAV файлы. Утилита была написана на Python с целью максимальной переносимости, работала без проблем и я на какое то время забыл о ней. Но в 2016м появился пользователь "неИТшник", понятия не имеющий про Python и как его устанавливать. Он хотел простой исполняемый файл-монолит, который "просто бы работал". Мне его просьба показалась логичной и я решил переработать утилиту в виде набора бинарных исполняемых файлов для основных платформ.

image

Python и Java не давали такую возможность (если конечно не было желания раздуть утилиту на много десятков мегабайт). Потенциально решение можно было сделать на C/C++, но при таком целевом охвате платформ, сложности с кросс-компиляцией выходили бы за рамки отведенного на задачу времени (а мне надо было поддерживать кросс-сборку для Windows, Linux и MacOS в 64 и 32 битных вариантах). Так что я обратил внимание на набирающий популярность язык Go, который к тому времени уже стал достаточно зрелым и единственным, кто без "плясок с бубном" обеспечивает всю требуемую кросс-компиляцию прямо из коробки (!).

Одно неудобство, которое меня сильно раздражало в Go (особенно как Java разработчика) — слабая продуманность организации структуры Go проектов и потребность в постоянной настройке параметров среды. Может быть разработчики Go изначально планировали какое-то специфическое использование или была ориентация на контейнеры, но я привык к "вкусненькому" и так как моим основным инструментом в Java является Maven, то решил сделать плагин осуществляющий вызовы к утилитам и командам GoSDK с автоматическим формированием нужных переменных окружения.

Делать просто плагин, который вызывал бы go утилиту, было неинтересно и я решил пойти с шага — установки GoSDK на хост-платформу. Сразу после старта, проверяется наличие GoSDK в своей конфигурационной директории (я выбрал путь по умолчанию ~/.mvnGoLang) и при отсутствии требуемой версии, производится автоматическая загрузка и распаковка архива GoSDK с официального сайта. Это позволило делать переносимые Go проекты, не заботясь о том, предустановлен и сконфигурирован ли инструмент нужной версии. Конечно, многочисленными настройками я предусмотрел возможность использования предустановленной версии и добавил настройки для всех шагов в процессе, так как для многих критичны такие вопросы как например отключение проверки SSL сертификатов или вообще осуществление HTTP запросов вовне (как например для проекта Keycloak).

Следующим этапом, я обернул в Maven-задачи (goals) все предоставляемые на тот момент команды утилиты go. Так как была вероятность, что с новой версией GoSDK появится еще какая-то новая команда, то была добавлена задача custom позволяющая пользователю определять нужную команду самостоятельно. По умолчанию, в рамках maven-фаз (phases), задачи исполняются в следующем порядке:

  • clean как default-clean в фазe clean
  • fix как default-fix в фазе validate
  • get как default-get в фазе initialize
  • generate как default-generate в фазе generate-sources
  • fmt как default-fmt в фазе process-sources
  • test как default-test в фазе test
  • build как default-build в фазе package
  • в качестве default-install в фазе install выполняется внутренняя задача mvninstall
  • install как default-deploy в фазе deploy

Любой из базовых шагов может быть отключен переводом задачи в несуществующую фазу:

<execution>
   <id>default-fix</id>
   <phase>none</phase>
</execution>

Во внутренней реализации, задачи были разделены на "работающие с зависимостями" и "не требующие разрешения зависимостей", после появления поддержки режима модулей, большинство перекочевало в "работающие с зависимостями".

Структуру Go-maven проекта, я постарался приблизить к стандартной принятой для Go структуре папок (т.е. /src и /bin в корне проекта). Но так как Maven заточен под Java, то напрямую не удалось "сломать" его подход к организации структуры проекта и сделать этот шаг невидимым для пользователя, поэтому базовая конфигурация плагина смотрится немного непривычно даже многим знакомым с maven:

<build>
```${basedir}/src</sourceDirectory>
    <directory>${basedir}/bin</directory>
    <plugins>
        <plugin>
            <groupId>com.igormaznitsa</groupId>
            <artifactId>mvn-golang-wrapper</artifactId>
            <version>2.3.3</version>
            <extensions>true</extensions>
            <configuration>
                 <goVersion>1.12.9</goVersion>
            </configuration>
        </plugin>
    </plugins>
</build>

WARNING! По каким то причинам хабр может неправильно отображать при форматировании XML участок <sourceDirectory>${basedir}/src</sourceDirectory>

Как видите, приходится напрямую определять папку с исходными текстами /src и папку с результатом /bin. Это с одной стороны минус, с другой стороны возможность к изменению их локации.

Целиком минималистичный pom.xml для одномодульного проекта а-ля Hello world, выглядит следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.igormaznitsa</groupId>
  <artifactId>mvn-golang-helloworld</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>mvn-golang</packaging>

  <build>
```${basedir}/src</sourceDirectory>
    <directory>${basedir}/bin</directory>

    <plugins>
          <plugin>
            <groupId>com.igormaznitsa</groupId>
            <artifactId>mvn-golang-wrapper</artifactId>
            <version>2.3.3</version>
            <extensions>true</extensions>
            <configuration>
              <goVersion>1.12.9</goVersion>
            </configuration>
          </plugin>
        </plugins>
  </build>
</project>

Обратите внимание, что packaging проекта обозначен как mvn-golang. Этой конфигурации достаточно, что бы на базе исходных текстов в папке src, построить исполняемый файл и положить его в результирующую папку bin. Go build cache будет так же создан в папке /bin (как /bin/.goBuildCache по умолчанию) и при clean будет стираться вместе с этой временной папкой.

В фазе install вызывается внутренняя задача mvninstall, которая просто пакует весь проект в zip архив и размещает его в maven-репозитории как сгенерированный артефакт. Изначально я просто складировал эти артефакты, но с версии 2.3.2 был добавлен механизм для поддержки их как стандартных maven-зависимостей и появилась возможность разработки проектов с "расшариванием" общего кода через maven репозиторий. Понятно, что класть в репозиторий сгенерированные бинарные результаты как артефакты — плохая идея из-за требований по кросс-платформенности и поэтому содержимое папки /bin в артефакт не пакуется.

Подключение другого проекта с packaging mvn-golang в качестве maven-зависимости (dependency) выглядит примерно так:

<dependency>
   <groupId>com.igormaznitsa</groupId>
   <artifactId>mvn-go-test-mix-terminal</artifactId>
   <version>1.0.0-SNAPSHOT</version>
   <type>mvn-golang</type>
</dependency>

С 2.3.3 версии была добавлена поддержка работы с механизмом Go модулей, но по умолчанию она не активирована (для обратной совместимости) и включается при помощи конфигурационного параметра:

<moduleMode>true</moduleMode>

Когда механизм Go-модулей был еще на стадии экспериментов, я добавил поддержку работы с версиями зависимостей через прямые вызовы CVS утилит, был сделан тестовый пример. Но сейчас думаю, что такой механизм уже не представляет большого интереса и выгоднее пользоваться стандартными зависимостями через модули. Тем более, что плагин умеет препроцессировать go.mod файлы на время построения проекта, подменяя пути к локальным папкам, если идет работа в рамках многомодульного проекта.

Так как Maven предусматривает возможность шаблонизации при помощи архетипов, то я сделал два архетипа:

  • com.igormaznitsa:mvn-golang-hello:2.3.3 для простого одномодульного архетипа
  • com.igormaznitsa:mvn-golang-hello-multi:2.3.3 для многомодульного проекта с разделяемым исходным кодом

Пример работы с архетипом одномодульного проекта, можно увидеть на анимации ниже
image.

Так же можно просто клонировать "Hello World" проект и поиграться:

git clone https://github.com/raydac/mvn-golang-example.git

image

Нередко возникает резонный вопрос — а для чего всё это надо и какие бонусы дает использование Maven в процессе построения Go-проекта? Попробую ответить на него по пунктам:

  1. Maven это очень зрелая, кросс-платформенная среда построения проектов, поддерживаемая практически всеми CI платформами, например Jenkins, при помощи Go2XUnit можно конвертировать результаты тестов в формат отображаемый репортирующими плагинами.
  2. Maven кросс-платформенен и поддерживается всеми ОС, будучи включенным во все репозитории. Наличие гибко активируемых профилей, позволяет легко настраивать процесс под различные платформы.
  3. Maven имеет ОГРОМНОЕ количество плагинов, что позволяет легко получать синергетический эффект скажем скрещивая Go и GWT, подключая в проект ANTLR, генерируя Go на базе Protobuf дескрипторов и даже добавляя препроцессинг. Всё это можно организовать и вручную через командные файлы, но если есть официальные поддерживаемые плагины, то выгоднее использовать их.
  4. Проект становится легко переносимым между машинами и платформами, а переключение версии GoSDK производится изменением одной строки.
  5. Простота организации многомодульных проектов, с разделением исходного кода через Maven репозиторий.
  6. Инструмент знаком и привычен Java-разработчикам, что облегчает их адаптацию при переключении на разработку на Golang или при разработке мультиязыковых решений Go+Java.
  7. Создается возможность псевдо-разработки на Go в средах поддерживающих Maven, даже если для них отсутствует какая-либо поддержка этой платформы, например в NetBeans IDE.

Крупный недостаток решения, на мой взгляд, тут один — ограниченное количество разработчиков знакомых с Maven среди Golang-сообщества. Для перешедших на Golang с C/C++ понятно, что ближе и роднее make сложно что-то найти, так же никто не отменял "нативные" Go-билд системы. Я заметил, что по каким то своим причинам, многие разработчики не любят смешивать платформы.

Итак, я кратко показал один из путей использования Maven при разработке Go проектов с помощью mvn-golang-wrapper плагина. Проект плагина оформлен как OSS-проект и выложен на GitHub. Если кто то заинтересуется и будет использовать в своих проектах, то не стесняйтесь задавать вопросы и "репортить баги". Я постарался сделать набор примеров на разные случаи жизни (на которых плагин и тестирую), но всего не охватить.

Тестовые примеры, идущие в проекте плагина, используют dev-версию, так что если будет желание к их локальному построению, после клонирования проекта, то для этого требуется сначала произвести билд dev-версии плагина, при помощи команды в корневом каталоге проекта:

mvn install

после чего, можно заходить в любой подпроект mvn-golang-examples и строить его при помощи

mvn clean install

так же можно запустить построение всех примеров из корня проекта, при помощи

mvn clean install -Pexamples

Плагин поддерживает многопоточную сборку проектов и её можно ускорить при помощи соответствующего аргумента, разбив например на 6 потоков

mvn clean install -Pexamples -T6

За время разработки, проект накопил приличное количество "наворотов", которые я решил не освещать в этой небольшой статье. Информацию о параметрах с небольшими примерами конфигурации можно найти в mind map данного плагина (исходный файл в формате SciaReto находится здесь):

Автор: raydac

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js