D言語＋Travis-CIでリリースのデプロイ自動化する話

# 概要
D言語でもTravis-CIやCircle-CIをはじめとする各種CIサービスでテスト・デプロイが可能です。
今回はTravis-CIを用いてReleaseのデプロイを自動化するテンプレを紹介します。
ちなみにビルドにはdubを使う前提です。

1. [D言語＋Travis-CIでテスト自動化する話➀](https://qiita.com/mono_shoo/items/724a514b83f2c181d603)
2. [D言語＋Travis-CIでテスト自動化する話②](https://qiita.com/mono_shoo/items/4c0017d6cffa53dfd743)
3. D言語＋Travis-CIでリリースのデプロイ自動化する話 ← この記事
4. [D言語+Travis-CIでドキュメントのデプロイ自動化する話](https://qiita.com/mono_shoo/items/81a912b54c7be1d1e5f7)


# .travis.yml のテンプレ

```yml:.travis.yml
language: d

# ここにテストの記述

jobs:
    include:
        - stage: GitHub Release
          d: ldc
          os: linux
          script: echo "Deploying to GitHub releases ..." && ./.travis-release.sh
          deploy:
            provider: releases
            api_key: $GH_REPO_TOKEN
            file_glob: true
            file: <プロジェクト名>-*.tar.gz
            after_success: true
            skip_cleanup: true
            on:
              repo: <アカウント名>/<プロジェクト名>
              tags: true
        - stage: GitHub Release
          d: ldc
          os: osx
          script: echo "Deploying to GitHub releases ..." && ./.travis-release.sh
          deploy:
            provider: releases
            api_key: $GH_REPO_TOKEN
            file_glob: true
            file: gendoc-*.tar.gz
            after_success: true
            skip_cleanup: true
            on:
              repo: <アカウント名>/<プロジェクト名>
              tags: true
        - stage: GitHub Release
          d: dmd
          os: linux
          language: generic
          sudo: yes
          script: echo "Deploying to GitHub releases ..." && ARCH=32 ./.travis-release-windows.sh
          addons:
            apt:
              packages:
                - p7zip-full
          deploy:
            provider: releases
            api_key: $GH_REPO_TOKEN
            file_glob: true
            file: <プロジェクト名>-*.zip
            after_success: true
            skip_cleanup: true
            on:
              repo: <アカウント名>/<プロジェクト名>
              tags: true
        - stage: GitHub Release
          d: dmd
          os: linux
          language: generic
          sudo: yes
          script: echo "Deploying to GitHub releases ..." && ARCH=64 ./.travis-release-windows.sh
          addons:
            apt:
              packages:
                - p7zip-full
          deploy:
            provider: releases
            api_key: $GH_REPO_TOKEN
            file_glob: true
            file: <プロジェクト名>-*.zip
            after_success: true
            skip_cleanup: true
            on:
              repo: <アカウント名>/<プロジェクト名>
              tags: true
```

## 解説

この設定で、タグをつけてpushした際に、GitHubのReleaseページに以下4つのアーカイブファイルを追加することができます。

| OS      | アーキテクチャ | ファイル名 |
|:-------:|:-------------:|:---------:|
| Linux   | x86_64        | <プロジェクト名>-<バージョン>-linux-x86_64.tar.gz |
| OSX     | x86_64        | <プロジェクト名>-<バージョン>-osx-x86_64.tar.gz |
| Windows | x86_64        | <プロジェクト名>-<バージョン>-windows-x86_64.tar.gz |
| Windows | x86           | <プロジェクト名>-<バージョン>-windows-x86.tar.gz |

ポイントは以下：

- $GH_REPO_TOKEN はデプロイに必要なトークン。  
GitHubにて、ユーザーアカウントの「Settings」→「Developer settings」→「Personal access tokens」へとたどり発行できます。権限はSelect scopesで設定し、「repo_deployment / Access deployment status」をチェックします。  
このキーをTravis-CIの各リポジトリの設定で「more options」→「Environment Variables」の欄に登録します。
- `./.travis-release.sh` は、Linux / OSX 向け
- `./.travis-release-windows.sh` および `./.travis-release-windows-setup.sh`はWindows向け(Travis-CIのLinuxサーバー上でクロスコンパイルする)です。
- Windows向けでは後述の7z解凍用に`p7zip-full`が必要です。
- 上記テンプレの設定の場合、毎回ビルド(アーカイブの作成)は行うが、デプロイはしない、という設定になる。「テストは通っているけどアーカイブにする段になって何かしら問題が生じたためタグをつけたのにデプロイできず、さらにはタグをつけたところが build failed になる」という恥ずかしい状況を防ぐのに効果があると思います。
- 毎回ビルドしたくない場合 → [TravisCI タグをプッシュした時だけ動くビルドステージの設定](https://qiita.com/masakurapa/items/47ab6642e9b6882f8e06) By @masakurapa

以下実際のプロジェクトで使われている設定です。大いに参考にしました。

- [実装1: D-Scanner](https://github.com/dlang-community/D-Scanner/blob/master/release.sh)
- [実例2: dub](https://github.com/dlang/dub/blob/master/release.sh)
- [実例3: DCD](https://github.com/dlang-community/DCD/blob/master/release.sh)
- [実例4: gendoc(私が作ったプロジェクト)](https://github.com/shoo/gendoc/blob/master/.travis-release.sh)

# .travis-release.sh

```sh
#!/usr/bin/env bash
set -eux -o pipefail

PROJECTNAME=<プロジェクト名>

VERSION=$(git describe --abbrev=0 --tags)
ARCH="${ARCH:-64}"
LDC_FLAGS=()
unameOut="$(uname -s)"
case "$unameOut" in
    Linux*) OS=linux; LDC_FLAGS=("-flto=full" "-linker=gold" "-static") ;;
    Darwin*) OS=osx; LDC_FLAGS+=("-L-macosx_version_min" "-L10.7" "-L-lcrt1.o"); ;;
    *) echo "Unknown OS: $unameOut"; exit 1
esac

case "$ARCH" in
    x86_64) ARCH_SUFFIX="x86_64";;
    64) ARCH_SUFFIX="x86_64";;
    i386) ARCH_SUFFIX="x86";;
    i686) ARCH_SUFFIX="x86";;
    x86) ARCH_SUFFIX="x86";;
    32) ARCH_SUFFIX="x86";;
    *) echo "Unknown ARCH: $ARCH"; exit 1
esac

archiveName="$PROJECTNAME-$VERSION-$OS-$ARCH_SUFFIX.tar.gz"

echo "Building $archiveName"

dub build -b=release

cd build
tar cvfz "../$archiveName" -C . *
cd ..
```

ポイントは以下:

- `PROJECTNAME=<プロジェクト名>`としているところでプロジェクト名を入力します。アーカイブ名に使われます。
- `dub build -b=release` でビルド。このプロジェクトは`targetPath`に`build`を指定しているので、buildディレクトリ以下をアーカイブする。
- `cd build` ～ `cd ..` の箇所はアーカイブを作成するところ。ライセンスを入れたりディレクトリの構造いじったりする場合はここでやります。



# .travis-release-windows.sh, .travis-release-windows-setup.sh

```sh:.travis-release-windows.sh
#!/usr/bin/env bash
# Build the Windows binaries under Linux
set -eux -o pipefail

PROJECTNAME=<プロジェクト名>

# Allow the script to be run from anywhere
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR

source .travis-release-windows-setup.sh

# Run LDC with cross-compilation
archiveName="$PROJECTNAME-$VERSION-$OS-$ARCH_SUFFIX.zip"
echo "Building $archiveName"

dub build -a=$TARGET_MTRIPLE -b=release -c=default --compiler=ldc2

cd build
zip　-r "../$archiveName" "./"
cd ..
```

<details><summary>※リリース前・マージ後のやり方</summary><div>

```sh:.travis-release-windows.sh
#!/usr/bin/env bash
# Build the Windows binaries under Linux
set -eux -o pipefail

PROJECTNAME=<プロジェクト名>

# Allow the script to be run from anywhere
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR

source .travis-release-windows-setup.sh

# Run LDC with cross-compilation
archiveName="$PROJECTNAME-$VERSION-$OS-$ARCH_SUFFIX.zip"
echo "Building $archiveName"

git clone https://github.com/dlang/dub.git --depth=1 && dub run --root=dub -- build -a=$TARGET_MTRIPLE -b=release -c=default --compiler=ldc2

cd build
zip　-r "../$archiveName" "./"
cd ..
```
</div></details>

- Windows向けのクロスコンパイルを行い、zipでアーカイブします。
- ~~ただし、[現在 dub がクロスコンパイルに対応しておらず、Pull Requestが出ています](https://github.com/dlang/dub/pull/1755)のでリリースを待ちましょう。~~
  - [マージされました。次回リリースで導入されるでしょう。](https://twitter.com/dlang_ng/status/1165734707016994823)
  - マージされたので、dubのmasterをcloneしてビルドしつつ、ビルドしたdubで`-a=$TARGET_MTRIPLE`を指定することでクロスコンパイルできるようにしています。
  - [リリースされました。](https://github.com/ldc-developers/ldc/releases/tag/v1.18.0) これに伴い、上記例も修正しました。v1.18.0より前のLDCを使用してクロスコンパイルをする場合は、「※リリース前・マージ後のやり方」を参考にして、デプロイ中にクロスコンパイル対応のdubをビルド・実行してください。
- makeなどを使う場合は、ここで `-mtriple=$TARGET_MTRIPLE` を指定したldc2でビルドすることでクロスコンパイルできます。ちなみに、後述しますが、途中で読んでいる `source .travis-release-windows-setup.sh` の部分で `$TARGET_MTRIPLE` に適切な `-mtriple` の値が設定されています。

以下実装例

- [実装1: D-Scanner](https://github.com/dlang-community/D-Scanner/blob/master/release-windows.sh)
- [実例2: dub](https://github.com/dlang/dub/blob/master/release-windows.sh)
- [実例3: DCD](https://github.com/dlang-community/DCD/blob/master/release-windows.sh)
- [実例4: gendoc(私が作ったプロジェクト)](https://github.com/shoo/gendoc/blob/master/.travis-release-windows.sh)



```sh:.travis-release-windows-windows.sh
#!/usr/bin/env bash

# sets up LDC for cross-compilation. Source this script, s.t. the new LDC is in PATH

ARCH=${ARCH:-32}
VERSION=$(git describe --abbrev=0 --tags)
OS=windows

case "$ARCH" in
    x86_64) ARCH_SUFFIX="x86_64";;
    64) ARCH_SUFFIX="x86_64";;
    i386) ARCH_SUFFIX="x86";;
    i686) ARCH_SUFFIX="x86";;
    x86) ARCH_SUFFIX="x86";;
    32) ARCH_SUFFIX="x86";;
    *) echo "Unknown ARCH: $ARCH"; exit 1
esac

# Step 0: install ldc
if [ ! -f install.sh ] ; then
	wget https://dlang.org/install.sh
fi
LDC_VERSION=${LDC_VERSION:-$(bash ./install.sh -a ldc | sed -E 's/.+\/ldc-([0-9.]+)\/activate/\1/')}
. $(bash ./install.sh -a "ldc-${LDC_VERSION}")

# for the install.sh script only
LDC_PATH="$(dirname $(dirname $(which ldc2)))"

# Step 1a: download the LDC x64 windows binaries
if [ "${ARCH_SUFFIX}" == "x86_64" ] && [ ! -d "ldc2-${LDC_VERSION}-windows-x64" ] ; then
	wget "https://github.com/ldc-developers/ldc/releases/download/v${LDC_VERSION}/ldc2-${LDC_VERSION}-windows-x64.7z"
	7z x "ldc2-${LDC_VERSION}-windows-x64.7z" > /dev/null
	# Step 2a: Add LDC windows binaries to LDC Linux
	if [ ! -d "${LDC_PATH}/lib-win64" ] ; then
		cp -r ldc2-${LDC_VERSION}-windows-x64/lib "${LDC_PATH}/lib-win64"
		cat >> "$LDC_PATH"/etc/ldc2.conf <<EOF
"x86_64-.*-windows-msvc":
{
	switches = [
		"-defaultlib=phobos2-ldc,druntime-ldc",
		"-link-defaultlib-shared=false",
	];
	lib-dirs = [
		"%%ldcbinarypath%%/../lib-win64",
	];
};
EOF
	fi
fi
# Step 1b: download the LDC x86 windows binaries
if [ "${ARCH_SUFFIX}" == "x86" ] && [ ! -d "ldc2-${LDC_VERSION}-windows-x86" ] ; then
	wget "https://github.com/ldc-developers/ldc/releases/download/v${LDC_VERSION}/ldc2-${LDC_VERSION}-windows-x86.7z"
	7z x "ldc2-${LDC_VERSION}-windows-x86.7z" > /dev/null
	# Step 2b: Add LDC windows binaries to LDC Linux
	if [ ! -d "${LDC_PATH}/lib-win32" ] ; then
		cp -r ldc2-${LDC_VERSION}-windows-x86/lib "${LDC_PATH}/lib-win32"
		cat >> "$LDC_PATH"/etc/ldc2.conf <<EOF
"i686-.*-windows-msvc":
{
	switches = [
		"-defaultlib=phobos2-ldc,druntime-ldc",
		"-link-defaultlib-shared=false",
	];
	lib-dirs = [
		"%%ldcbinarypath%%/../lib-win32",
	];
};
EOF
	fi
fi

# set suffices and compilation flags
if [ "$ARCH_SUFFIX" == "x86_64" ] ; then
	export TARGET_MTRIPLE="x86_64-windows-msvc"
else
	export TARGET_MTRIPLE="i686-windows-msvc"
fi
```

ポイント(というか何をやっているかの概要)は以下

- インストールスクリプトでldc2をインストール
- Windows向けのldc2(中身のlibファイルが目当て)をダウンロード
- 配布形態の7zipを解凍してlibファイルをコピー(ここで先述のp7zip-fullが必要)
- ldc2.confにクロスコンパイル用の設定を追加(*-*-windows-msvcのときのlibファイルの場所の指定など)
- `TARGET_MTRIPLE`環境変数に `-mtriple=` の中身になる`x86_64-windows-msvc`や`i686-windows-msvc`を設定

以下実装例

- [実装1: D-Scanner](https://github.com/dlang-community/D-Scanner/blob/master/setup-ldc-windows.sh)
- [実例2: dub](https://github.com/dlang/dub/blob/master/setup-ldc-windows.sh)
- [実例3: DCD](https://github.com/dlang-community/DCD/blob/master/setup-ldc-windows.sh)
- [実例4: gendoc(私が作ったプロジェクト)](https://github.com/shoo/gendoc/blob/master/.travis-release-windows-setup.sh)


# ライセンス
今回紹介したスクリプトは多分 [BSL-1.0](https://github.com/dlang-community/D-Scanner/blob/master/LICENSE_1_0.txt) です。使用する場合はご注意ください。[D-Scanner (BSD-1.0)](https://github.com/dlang-community/D-Scanner)で使われているスクリプトがベースになっています。