Строим pipeline для доставки Graal Native Image конечным пользователям

—

от автора

В данной статье я хочу описать свой опыт по доставке Graal Native Image конечным пользователям Mac OS и Linux в одну команду при помощи Travis CI.

Когда я столкнулся с данной задачей, я ощутил нехватку информации по этой теме, поэтому я решил описать свой опыт здесь.

Пожалуй начнем.

У меня есть:

  • Java приложение со всеми конфигурациями для сборки native image.

Что хочу получить:

  • Собрать native image при помощи Travis
  • Собрать rpm и deb дистрибутивы, а также архив для mac версии
  • Залить полученные пакеты на Bintray
  • Дать пользователям возможность загрузить приложение через пакетный менеджер
  • Максимально автоматизировать процесс.

Поехали

Для начала создадим простой Hello world.

package release.test;  public class Application {      public static void main(String[] args) {         System.out.println("Hello world");     } } 

А также добавим базовые плагины в pom.xml.

 <build>         <plugins>             <plugin>                 <groupId>org.apache.maven.plugins</groupId>                 <artifactId>maven-compiler-plugin</artifactId>                 <version>3.8.0</version>             </plugin>             <plugin>                 <!-- Build an executable JAR -->                 <groupId>org.apache.maven.plugins</groupId>                 <artifactId>maven-jar-plugin</artifactId>                 <version>3.1.0</version>                 <configuration>                     <archive>                         <manifest>                             <addClasspath>true</addClasspath>                             <classpathPrefix>lib/</classpathPrefix>                             <mainClass>release.test.Application</mainClass>                         </manifest>                     </archive>                 </configuration>             </plugin>             <plugin>                 <groupId>org.apache.maven.plugins</groupId>                 <artifactId>maven-release-plugin</artifactId>                 <version>2.5.3</version>                 <configuration>                     <tagNameFormat>${project.version}</tagNameFormat>                 </configuration>             </plugin>         </plugins>     </build> 

Дальше я создам два профиля для сборки mac и linux соответственно. Буду использовать maven-assembly-plugin. Я не буду засорять статью тоннами xml, вместо этого я прикреплю ссылку с полным примером.

Перейдем к Travis CI. Именно там я хочу собрать native image, упаковать его в нужные пакеты и задеплоить.

Создадим файл .travis.yml для описания деплой плана и будем постепенно его наполнять.
Для начала он будет выглядеть так:

os:  - linux  - osx  language: java install: true  script:  - if [ $TRAVIS_OS_NAME == "osx" ]; then     chmod +x ./prepare-mac.sh;     ./prepare-mac.sh;    fi  - if [ $TRAVIS_OS_NAME == "linux" ]; then     chmod +x ./prepare-linux.sh;     ./prepare-linux.sh;    fi

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

Для Mac OS скрипт будет выглядеть так:

#!/usr/bin/env bash curl -OL https://github.com/oracle/graal/releases/download/vm-19.1.1/graalvm-ce-darwin-amd64-19.1.1.tar.gz tar zxf graalvm-ce-darwin-amd64-19.1.1.tar.gz sudo mv graalvm-ce-19.1.1 /Library/Java/JavaVirtualMachines export PATH=/Library/Java/JavaVirtualMachines/graalvm-ce-19.1.1/Contents/Home/bin:$PATH /Library/Java/JavaVirtualMachines/graalvm-ce-19.1.1/Contents/Home/bin/gu install native-image mvn clean package -P mac NAME=$(basename $(find . -type f -name 'release-test-*.jar')) native-image -jar target/${NAME} -H:Name=release-test mvn package -P assembly

Пройдемся по шагам. Грузим, распаковываем и устанавливаем GraalVM, а также догружаем native-image, так как его просто нет в сборке Graal для Mac. После этого запускаем сборку проекта, чтобы получить jar, а далее запускаем native-image и снова упаковываем все в архив, в котором уже будет лежать наш исполняемый файл.

Для Linux скрипт будет выглядеть немного иначе, так как в нем мы сразу соберем rpm и deb пакеты:

#!/usr/bin/env bash curl -OL https://github.com/oracle/graal/releases/download/vm-19.1.1/graalvm-ce-linux-amd64-19.1.1.tar.gz tar zxf graalvm-ce-linux-amd64-19.1.1.tar.gz sudo mv graalvm-ce-19.1.1 /usr/lib/jvm/ export JAVA_HOME=/usr/lib/jvm/graalvm-ce-19.1.1 export PATH=$PATH:${JAVA_HOME}/bin  mvn clean install ${JAVA_HOME}/bin/gu install native-image NAME=$(basename $(find . -type f -name 'release-test-*.jar'))  mkdir release-deb cd release-deb native-image -jar ../target/${NAME} -H:Name=release-test PACK_NAME=$(ls) chmod +x ${PACK_NAME} mkdir packageroot mkdir packageroot/DEBIAN touch packageroot/DEBIAN/control VERSION=$(echo "${NAME%.*}" | cut -d'-' -f 3) echo "Package: $PACK_NAME Version: $VERSION Architecture: amd64 Maintainer: John Doe <john@doe.com> Description: test " > packageroot/DEBIAN/control cat packageroot/DEBIAN/control mkdir -p packageroot/usr/bin cp ${PACK_NAME} packageroot/usr/bin/ dpkg-deb -b packageroot ${PACK_NAME}-${VERSION}.deb  sudo dpkg -i ./${PACK_NAME}-${VERSION}.deb sudo apt-get install -f  DEB_PACK=$(find . -type f -name 'release-test-*.deb') echo ${DEB_PACK} cp ${DEB_PACK} ../target/  cd ../target  # convert to rpm sudo apt-get update sudo apt-get install rpm sudo apt-get install ruby ruby-dev rubygems build-essential gem install --no-ri --no-rdoc fpm  fpm -t rpm -s deb ${PACK_NAME}-${VERSION}.deb

Здесь также грузим GraalVM и устанавливаем native-image. Далее каждый участок кода, я думаю, имеет смысл рассмотреть в отдельности.

Создаем debian пакет. В интернете есть информация, каким образом создаются пакеты, поэтому я не буду углубляться в подробности.

mkdir release-deb cd release-deb native-image -jar ../target/${NAME} -H:Name=release-test PACK_NAME=$(ls) chmod +x ${PACK_NAME} mkdir packageroot mkdir packageroot/DEBIAN touch packageroot/DEBIAN/control VERSION=$(echo "${NAME%.*}" | cut -d'-' -f 3) echo "Package: $PACK_NAME Version: $VERSION Architecture: amd64 Maintainer: John Doe <john@doe.com> Description: test " > packageroot/DEBIAN/control cat packageroot/DEBIAN/control mkdir -p packageroot/usr/bin cp ${PACK_NAME} packageroot/usr/bin/ dpkg-deb -b packageroot ${PACK_NAME}-${VERSION}.deb  sudo dpkg -i ./${PACK_NAME}-${VERSION}.deb sudo apt-get install -f  DEB_PACK=$(find . -type f -name 'release-test-*.deb') echo ${DEB_PACK} cp ${DEB_PACK} ../target/

Далее я хочу конвертировать debian пакет в rpm. Мне не хотелось прогонять все те же действия еще раз, поэтому я решил использовать fpm. Данная тулза позволяет в одну команду создать rpm пакет из debian.

# convert to rpm sudo apt-get update sudo apt-get install rpm sudo apt-get install ruby ruby-dev rubygems build-essential gem install --no-ri --no-rdoc fpm  fpm -t rpm -s deb ${PACK_NAME}-${VERSION}.deb

Далее я добавлю небольшой before_deploy блок в .travis.yml, который будет обновлять служебные файлы.

before_deploy:   - if [ $TRAVIS_OS_NAME == "linux" ]; then     chmod +x ./update-version.sh;     ./update-version.sh;     fi

update-version.sh

#!/usr/bin/env bash  cd target  NAME=$(basename $(find . -type f -name 'release-test-*.jar')) VERSION=$(echo "${NAME%.*}" | cut -d'-' -f 3)  cd ../  sed -i "s/template_version/$VERSION/g" deploy-deb.json sed -i "s/template_tag/$VERSION/g" deploy-deb.json  sed -i "s/template_version/$VERSION/g" deploy-rpm.json sed -i "s/template_tag/$VERSION/g" deploy-rpm.json

Файлы deploy-deb.json и deploy-rpm.json — это описание деплоя на bintray. Я также не буду добавлять их сюда. Пример можно найти в официальной документации, а также в примере в конце статьи. Скрипт update-version.sh исходя из названия всего лишь подменяет переменные в json файлах на актуальную версию.

Переходим к этапу деплоя. Здесь мы будем загружать все пакеты в отдельные bintray репозитории. Для этого необходимо создать generic, rpm и debian репозитории на bintray. Для каждой системы у нас будет отдельный блок, релизить будем по тегу и только с master-ветки. В случае с mac воспользуемся Rest API. Rest API Bintray позволяет заливать архив прямо в репозиторий. Для linux сборок будем использовать bintray provider, который предоставляет Travis CI.

deploy:   - provider: script     skip_cleanup: true     script:       cd target;       curl -X PUT -H X-GPG-PASSPHRASE:$PASSPHRASE_BINTRAY --basic -u aarrsseni:$BINTRAY_API_KEY https://api.bintray.com/content/aarrsseni/release-test/release-test/$TRAVIS_TAG/release-test-$TRAVIS_TAG.zip?publish=1     on:       branch: master       condition: $TRAVIS_OS_NAME == "osx"       tags: true   - provider: bintray     file: deploy-deb.json     user: aarrsseni     key:       secure: your_key     passphrase:       secure: your_passphrase     skip_cleanup: true     on:       branch: master       condition: $TRAVIS_OS_NAME == "linux"       tags: true   - provider: bintray     file: deploy-rpm.json     user: aarrsseni     key:       secure: your_key     passphrase:       secure: your_passphrase     skip_cleanup: true     on:       branch: master       condition: $TRAVIS_OS_NAME == "linux"       tags: true

В результате у нас есть zip архив, rpm и deb пакеты, которые уже лежат на bintray.
В случае rpm и deb пользователь уже может загрузить их через apt-get и yum.

Для mac мы будем использовать brew в качестве пакетного менеджера. Для этого необходимо создать еще один репозиторий. На официальном сайте можно найти документацию, в которой описана последовательность действий по добавлению пакетов в менеджер. Как он выглядит можно посмотреть здесь.

Основной файл для brew находится в папке Formula. Мой файл называется release-test.rb. Он содержит ссылку на zip архив на bintray, некоторое описание и чексумму, которую необходимо менять при каждом релизе.

class ReleaseTest < Formula   desc "Your desc"   homepage "your website"   url "https://bintray.com/aarrsseni/release-test/download_file?file_path=release-test-1.70.zip"   sha256 "84b09c9c1c45ef0b040811be58c9f14cf8ef7237139f0a8483ccd85c6ea1bb64"    bottle :unneeded    def install     libexec.install Dir["*"]     bin.install_symlink libexec/"bin/release-test"   end    test do     system "release-test"   end end

Чтобы не менять чексумму руками, в .travis.yml добавляем финальный блок after_deploy.

after_deploy:  - if [ $TRAVIS_OS_NAME == "osx" ]; then   chmod +x ./update-homebrew.sh;   ./update-homebrew.sh;   fi

В скрипте update-homebrew.sh мы клонируем brew репозиторий, вычисляем новую чексумму для только задеплоенного приложения, а затем обновляем ее.

#!/usr/bin/env bash NAME=$(basename $(find . -type f -name 'release-test-*.zip')) echo ${NAME} mkdir temp-homebrew  CHECKSUM=$(echo "$(shasum -a 256 target/${NAME})" | awk '{print $1;}') echo ${CHECKSUM}  cd temp-homebrew git clone https://github.com/aarrsseni/homebrew-test-release cd homebrew-test-release git remote add origin-deploy https://${GITHUB_TOKEN}@github.com/aarrsseni/homebrew-test-release.git cd Formula  cat release-test.rb  PREV_NAME=$(grep -o 'file_path=.*$' release-test.rb | cut -c11-) PREV_NAME=${PREV_NAME%?}  PREV_CHECKSUM_FROM_FILE=$(grep -o 'sha256.*$' release-test.rb | cut -c9-) PREV_CHECKSUM=${PREV_CHECKSUM_FROM_FILE%?}  echo ${PREV_NAME} echo ${PREV_CHECKSUM}  sed -i '.bak' "s/$PREV_NAME/$NAME/g" release-test.rb  sed -i '.bak' "s/$PREV_CHECKSUM/$CHECKSUM/g" release-test.rb  cat release-test.rb  git add . git commit -m "Update formula version" git push origin-deploy master

После этого, чтобы скачать и установить к себе на mac готовое приложение, необходимо запустить команду brew install USERNAME/REPO_NAME/TOOL_NAME, где USERNAME — имя на github, REPO_NAME — соответственно репозиторий, а TOOL_NAME — название вашего приложения.

Стоит добавить, что для доступа на github и bintray в .travis.yml надо добавить ваши ключи.

Подведем итог

Теперь по команде mvn release:prepare или просто по коммиту с тегом мы стартуем сборку native image для mac и linux, а также доставку прямиком к конечному пользователю.

Репозиторий с исходым кодом
Репозиторий для homebrew

ссылка на оригинал статьи https://habr.com/ru/post/501306/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *