複数の環境で動作するバージョン切り替えツールのgrnenv-rsを作ってみたお話

[Groonga Advent Calendar 2016](http://qiita.com/advent-calendar/2016/groonga)の9日目の記事です。

# はじめに

GroongaはWindowsでも動かすことができます。Groongaは月ごとに新しいバージョンがリリースされるため、ある特定のバージョンを簡単にインストールして切り替えられる仕組みがあるとより便利になると感じていました。

@myokoym さんが既にWindows以外の環境に向けて[grnenv](https://github.com/myokoym/grnenv) を作成されていますが、これはあくまでもbashスクリプトのため、Windowsでネイティブで動くものではなく導入の敷居が高いものとなっていました。

## Windowsでのお作法

基本的にWindowsはユーザーランド（と一部カーネル空間）には高いバイナリ互換性があります。そして、Windows向けにはPortable Executables形式で提供するコマンドとすることで、ユーザーが導入する敷居を下げることができます。

### Rustとは

ここではMozillaが開発しているRust言語[^1]を用います。RustではWindowsへはPE(いわゆるexe)、macOSへはMach-O、LinuxなどにはELFバイナリとして動く実行ファイルを作成することができます。

[^1]: https://www.rust-lang.org/en-US/

## 実装

### Windows編

Windows向けにはmingwによりビルドされたバイナリやインストーラーが提供されているため、こちらを引っ張ってきてインストールするようにします。
HTTPプロトコルを扱う必要があるため、[Hyper](https://github.com/hyperium/hyper)と言うcrateを用いてファイルをダウンロードすることにします。

コードの一部を抜粋すると、

```rust
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;

use hyper::Client;
use hyper::error::Error as HyperError;
use hyper::header::{Connection, UserAgent};

pub fn file_download<'a>(client: &'a Client,
                         url: &str,
                         mut base_dir: PathBuf,
                         filename: &'a str)
                         -> Result<PathBuf, HyperError> {

    let mut res = client.get(url)
        .header(Connection::close())
        .header(UserAgent(format!("grnenv-rs {}", crate_version!())))
        .send()?;
    println!("{:?}", res);
    let mut body = vec![];
    res.read_to_end(&mut body)?;
    base_dir.push(filename);
    let mut f = File::create(base_dir.to_str().unwrap()).expect("Could not create file.");
    println!("{:?}", f);
    f.write_all(&body)?;
    f.sync_data()?;
    Ok(base_dir)
}
```

のようなコードになります。

Rustの場合、エラーが起きる可能性のある操作を行うときには[Result](https://doc.rust-lang.org/std/result/enum.Result.html)というバリアントで包んで値を返すのが一般的です。

あとは、ダウンロードしてきたzipを所定の位置に展開することで、インストールが完了となります。

また、Windowsにおいてなんらかのシェルを立ち上げたときにprofileを読み込む方法を探すこととなるのですが、これはPowerShellの機能を使うことにより解決します。

PowerShellでは以下の位置にあるパワーシェルスクリプトをbashrcやzshrcと同じ要領で読み込みます。

```powershell
$Env:USERPROFILE\Documents\WindowsPowerShell\profile.ps1
```

### macOS/Linux編

macOSやLinuxでは、ビルド済みのバイナリをそのまま使用することはパッケージ化されたものを除いて通常はできません。そのため、指定されたバージョンのソースコードをダウンロードした上でビルドしてインストールする必要があります。

その様子が [install subcommand](https://github.com/cosmo0920/grnenv-rs/blob/6e8d89e1ed23c36715297a279038edc6743dc966/src/command/unix.rs#L38) に書かれています。

指定したバージョンのGroongaを切り替えて扱うツールを作成するにあたってのプラットフォームごとの注意点を挙げました。

## grnenv-rs

そうして出来上がったツールが[grnenv-rs](https://github.com/cosmo0920/grnenv-rs)です。

grnenv-rsはWindows 10 64bitを使う分にはコンパイル済みのバイナリを[リリースページ| GitHub -- grnenv-rs](https://github.com/cosmo0920/grnenv-rs/releases) からgrnenv-rs-VERSION-x86_64-pc-windows-msvc.zipをダウンロードして `$Env:USERPROFILE\bin` に放り込み、[Visual C++ 2015再頒布可能パッケージ](https://www.microsoft.com/ja-JP/download/details.aspx?id=48145) をインストールすることで使い始めることができます。

使い始めるには、profile.ps1 を以下の場所に配置します。

```powershell
$Env:USERPROFILE\Documents\WindowsPowerShell\profile.ps1
```

profile.ps1へ以下を書き込みます:

```powershell
. $Env:USERPROFILE\.groonga\shims\bin\source-groonga.ps1
```

そして、

```powershell
PS> $Env:USERPROFILE\bin\grnenv install VERSION [--arch (x86|x64)]
PS> $Env:USERPROFILE\bin\grnenv switch VERSION [--arch (x86|x64)]
```

として、使いたいGroongaのバージョンを指定してインストールすることができます。
その後、PowerShellを再起動します。
また、PowerShellを再起動したときに「スクリプトの実行がシステムで無効になっているため、ファイル～を読み込めません。」と言うエラーが表示された場合は、

```powershell
Set-ExecutionPolicy RemoteSigned
```

を指定してみてください。

macOSやLinuxの場合はbashrcやzshrcにて `grnenv init` を実行したときに表示されるシェルスクリプトを読み込むようにします。

## おまけ

このツールにはPATH環境変数の中に `grnenv-` のプレフィックスを持つ実行ファイルがあった場合、Rust言語のパッケージマネージャのcargoのサブコマンドの機能を真似たプロセスをまたいだプラグイン機構を載せてみています。
Windowsではexeを作成する必要があり、活用するには敷居が高いプラグイン機能ですがWindowsを考えないのであれば単に実行フラグが立っているかをみているだけなのでGroongaのバージョンの切り替えに関連するちょっとしたタスクをgrnenv-rsのプラグインにしてみるのはいかがでしょうか。

## おわりに

Groongaのバージョンを手軽に切り替えるgrnenv-rsの紹介をしました。
このツールを作っている最中に遭遇したRustのTipsは色々あるのですが、ここでは作成したGroongaのバージョンを切り替えるツールについてまとめました。

WindowsとGroongaの親和性が高まっていくとよいですね。
それではWindowsでも良い全文検索を！