Posted at

Nimのコードから実行可能バイナリをTravisとAppVeyorで各プラットフォーム向けに生成してGitHub Releasesへリリースする

高速に動作する実行可能バイナリを生成できるといわれているNimなので

複数プラットフォーム向けにバイナリを生成して、コマンドを配布したいと考えました。

ここではTravisCIとAppVeyorを利用して、

Nimのコードから各プラットフォーム(Linux, Mac, Windows)向けのバイナリを生成できるようにするまでの手順を書きます。

参考までに、実際にこの記事に書いてある内容を実践したリポジトリを記載します。


検証環境

今回の記事は以下の環境で検証しました。

Name
Version

Linux Mint
19.1

ruby
2.5.1p57

gem
2.7.6

travis
1.8.10

nim
0.20.2

nimble
0.10.2


travisコマンドのインストール

TravisCIからGitHub Releasesへリリースするトークン取得のためにtravisコマンドのインストールします。

LinuxMintにデフォルトで入ってるRubyだとtravisのインストールで失敗しました。

ruby-devが必要だったのでインストールします。

sudo apt update -y

sudo apt install -y ruby-dev
sudo apt install -y build-essential
sudo gem install travis

リリースしたいリポジトリで.travis.ymlを生成し、travisコマンドを実行します。

touch .travis.yml

travis setup releases --org -r GitHubアカウント名/リポジトリ名

# 筆者(jiro4989)の場合だと
travis setup releases --org -r jiro4989/foobar

以下のようなYAMLファイルが生成されます。


travis.yml

deploy:

provider: releases
api_key:
secure: 省略
file: ''
on:
repo: jiro4989/foobar # ここはリポジトリによってかわる


GitHubReleasesへアップするTravisのタスクを書く

.travis.ymlの設定を変更してLinuxとMacOS向けのバイナリを生成するタスクを書きます。

書いたものが以下。languageでNim用のものが存在しないので、公式の.travis.ymlを参考にlanguage cとしました。

あとrepo: jiro4989/foobarの部分は不要だったので消しました。


travis.yml

sudo: false

language: c

os:
- linux
- osx

env:
- PATH=$HOME/.nimble/bin:$PATH
APP_NAME=ここにパッケージ名

cache:
directories:
- $HOME/.nimble
- $HOME/.choosenim

addons:
apt:
packages:
- libcurl4-openssl-dev
- libsdl1.2-dev
- libgc-dev
- libsfml-dev

before_install:
- if [ ! -e $HOME/.nimble/bin/nim ]; then curl https://nim-lang.org/choosenim/init.sh -sSf -o init.sh && bash init.sh -y; fi

before_script:
- set -e
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then unset -f cd; fi
- echo PATH:${PATH}
- set +e

script:
- set -e
- nimble ci
- set +e

before_deploy:
- mkdir -p dist/${APP_NAME}_${TRAVIS_OS_NAME}
- cp -rf LICENSE README* bin dist/${APP_NAME}_${TRAVIS_OS_NAME}/
- tar -C dist -czf ${APP_NAME}_${TRAVIS_OS_NAME}{.tar.gz,}

deploy:
provider: releases
api_key:
secure: 省略
keep-history: false
skip_cleanup: true
file: ${APP_NAME}_${TRAVIS_OS_NAME}.tar.gz
on:
tags: true


before_installでchoosenimをインストールしています。

choosenimでのnimのインストールは10分以上かかります。

Travisで毎回コンパイラをインストールしていると時間がかかるのでキャッシュするようにしています。

scriptではnimble ciを実行しています。

これは独自に定義したnimbleのタスクです。

以下がタスクを定義しているそのnimbleファイルです。nimjsonという自作のコマンドからの流用です。


nimjson.nimble

# Package

version = "1.2.1"
author = "jiro4989" # ここは作者の名前によって変わる
description = "nimjson generates nim object definitions from json documents." # 必要に応じて変更する
license = "MIT"
srcDir = "src"
bin = @["nimjson"] # ここはパッケージの名前によって変わる
binDir = "bin"
installExt = @["nim"]

# Dependencies

requires "nim >= 0.20.0"

task docs, "Generate documents":
exec "nimble doc src/nimjson.nim -o:docs/nimjson.html"

task examples, "Run examples":
for dir in ["readfile", "mapping"]:
withDir "examples/" & dir:
exec "nim c -d:release main.nim"
exec "./main"

task buildjs, "Generate JS lib":
exec "nimble js js/nimjson_js.nim -o:docs/js/nimjson.js"

task ci, "Run CI":
exec "nim -v"
exec "nimble -v"
exec "nimble install -Y"
exec "nimble test -Y"
exec "nimble docs -Y"
exec "nimble build -d:release -Y"
exec "nimble examples"
exec "nimble buildjs"
exec "./bin/nimjson -h"
exec "./bin/nimjson -v"


task ci, "Run CI"ではTravisCIにやらせたい諸々のタスクを全て定義しています。

nimble docsnimble examplesnimble buildjsも独自に定義したタスクです。

このnimbleのciタスクは僕が必要だったから追加したものなので、

クロスプラットフォーム向けバイナリするという目的のみを達成するのでciタスクの追加は不要です。

この目的を達成するためだけならば.travis.ymlのscriptに以下を記述するだけで十分です。


travis.yml

script:

- nimble build -d:release -Y

before_deployではnimble build -d:releaseで生成したファイルとLICENSE、READMEをバイナリを圧縮しています。

これでタグを切ったタイミングでLinuxとMacOS向けのバイナリがリリースされます。


GitHub ReleasesへアップするAppVeyorのタスクを書く

Windows向けバイナリを生成するためにAppVeyorを利用します。

まず、deployに必要なトークンを生成します。

GitHub「Personal access tokens」の設定方法などを参考にGitHubへのアクセストークンを生成します。

生成されたトークンをメモしたら、AppVeyor側の設定に移ります。

AppVeyorでリポジトリ設定画面の「Deployment」を表示して

Add Deploymentします。

「Deployment Provider」にGitHub Releases を選択して

「GitHub authentication token」に先程メモしたトークンを追加します。

Saveしたら、左サイドバーの「Export YAML」をクリックします。

するとauth_tokenのセットされたYAMLファイルが生成されます。

生成されたYAMLをappveyor.ymlとして一旦リポジトリにpushしたら、

今度はnimのコードをビルドするタスクをappveyor.ymlに書き足します。

以下が完成したappveyor.ymlです。

こちらも前述のnimjsonのリポジトリで使用している設定です。

ベースはNim公式リポジトリのappveyor.ymlです。


appveyor.yml

version: '{build}'

environment:
APP_NAME: ここにパッケージ名を入力
MINGW_DIR: mingw64
MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z/download
MINGW_ARCHIVE: x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z
NIM_DIR: nim-0.20.0
NIM_URL: https://nim-lang.org/download/nim-0.20.0_x64.zip
NIM_ARCHIVE: nim-0.20.0_x64.zip
platform: x64

cache:
- '%MINGW_ARCHIVE%'
- '%NIM_ARCHIVE%'

matrix:
fast_finish: true

install:
- MKDIR %CD%\DIST
- IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
- 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\DIST"> nul
- IF not exist "%NIM_ARCHIVE%" appveyor DownloadFile "%NIM_URL%" -FileName "%NIM_ARCHIVE%"
- 7z x -y "%NIM_ARCHIVE%" -o"%CD%\DIST"> nul
- SET PATH=%CD%\DIST\%NIM_DIR%\BIN;%CD%\DIST\%MINGW_DIR%\BIN;%CD%\BIN;%PATH%

build: off

build_script:
- nimble ci
- mkdir %APP_NAME%_windows
- xcopy bin %APP_NAME%_windows\bin\
- copy README.md %APP_NAME%_windows\
- copy LICENSE %APP_NAME%_windows\
- 7z a %APP_NAME%_windows.zip %APP_NAME%_windows

artifacts:
- path: '*_windows.zip'
name: zip

deploy:
- provider: GitHub
auth_token:
secure: 省略
artifacts: zip
on:
branch: master
appveyor_repo_tag: true


前述の.travis.ymlの説明の通り、nimble ciの部分はnimble build -d:release -Yだけでも十分です。

欠点ですが、Nimのバージョンを変数でベタ打ちしているので、最新版に追従できません。

なんとかこの問題も解決したいと考えていますが、現状良い方法が思いつかないです。


動作確認

GitHubのリポジトリを開いてReleasesを表示します。

「Draft a new release」のボタンをクリックして、諸々テキストを入力して

タグを追加してみます。

タグを追加するとTravisCIとAppVeyorのタスクが走り初めますので完了するのを待ちます。

完了したらGitHub Releasesに各プラットフォーム向けのバイナリを含んだ圧縮ファイルが配置されます。


まとめ

TravisCIとAppVeyorを利用してNimのコードから各プラットフォーム向けのバイナリを生成する設定について説明しました。

せっかく高速に動作するバイナリが作れると言われているNimなので

Rustでいうbats、Goでいうpecoのように

Nimでももっとコマンド作成する流れが生まれてほしいなぁと思っています。

以上です。