Go
Wandbox
Go3Day 2

GoでWandboxにコードを送って実行結果を見る

はじめに

これはGo3 Advent Calendar 2017の2日目の記事です.

[Day1|一発でセラーの中身をぶっこ抜く] << [本記事] >> [Day3|cgoでポインタ渡し]

CLIを作りたくなってGoを触り始めました.
Wandboxにローカルのコードを送りつけて、実行結果を取得するというツールを作りました.
ありがたいことにWandboxのAPIが公開されているので, コード等をjsonにしたものを送って実行結果を表示するというツールです.

どうでもいいかもしれないし重要かもしれない情報

Goを選んだ理由

  • C++は極めているが, 絶対C++で作りたくなかった
  • クロスコンパイルが容易
  • ツールが揃ってる
  • なんかイケイケって噂がきこえてくる
  • なんか新しい言語やってみたかった
  • 「CLI作るんだけどなんかオススメの言語ある?」て聞いたらすすめられた(これが決め手

Wandboxとは

wandbox はオンラインコンパイラです.
多数の言語の様々なコンパイラの色々なバージョンを揃えています.

CBT(Cranberries Build Tool)

CBTは今回作ったCLIの名前です.
できることはコードをWandboxに送って実行結果を見ることです.
残念ながら対応言語はC, C++, Goだけです.
C++だけでほとんど満足してるので対応言語増やす気力がありません.
誰かPRしてください(他力本願

コードは混沌としているので今回の記事では登場しません.
みたい人はGitHubで見てね.
リポジトリはこちら.

go get github.com/LoliGothick/cbt

でソース落とせます.
パスが通っていればgo install で使えます.
一応GitHubのreleasesからバイナリを落とせます.

使い方

ローカルで作業していて, 不可解なコンパイルエラーに頭を抱えて30分.
調べても原因がわからず頭痛が痛いあなた.
ツイッターで嘆いていると, 「コードを見せて」と救いの手が!

そんなときあなたならどうしますか?
Gistに貼り付けますか?

ファイル一つだったら良いですけど, いっぱいあるとちょっと面倒ですよね.

コマンド一発でwandboxにコードを公開できたら便利ですよね.

できるんです!
そうCBTならね

Basic Usage

あなたはターミナルを操作しています.
カレントディレクトリに以下のようなGoのコードがあるとします.

hello.go
package main

import (
        "fmt"
)

func main() {
        fmt.Println("Hello, Wandbox!")
}

そしてターミナル上で次のようにcbtのwandboxコマンドを叩くと実行結果が表示されます.
サブコマンドで言語を指定する必要があります.

$ cbt wandbox go ./hello.go
Hello, Wandbox!

Commands & Options

まともなコマンドはwandboxしかない.

オプションはWandboxのオプションを指定するためのもの.

2017-12-01.png

C++だととても多い. Goはコンパイラ, ランタイムオプション, 標準入力しかない.

cbt -h

でコマンドを確認できる.

cbt [command] -h

コマンドのオプションを確認できる.
wandbox go のコマンドヘルプは以下のようになる.
urfave/cliがこの辺自動生成してくれるので便利.

$ cbt wb go -h
NAME:
   cbt wandbox go - Go

USAGE:
   cbt wandbox go [command options] [arguments...]

OPTIONS:
   --compiler value, -x value        specify Go version [fmt: go-x.x] (default: "go-head")
   --compile-option value, -c value  specify compiler options
   --runtime-option value, -r value  specify runtime options
   --stdin value, --in value         specify standard input [text or file both accept]
   --save, -s                        publishing permanent link



  • --compiler, -x :コンパイラのバージョン指定
example
-x=go-1.9
  • --runtime-option, -r :ランタイムオプションの指定

なんか指定できます

  • --save, -s :Wandboxのパーマリンクの発行

実行結果のあとにパーマリンクとURLが表示されます.

$ cbt wandbox go hello.go -s
Hello, Wandbox!


Permlink: Yi5yI5OtL7QGczvl
URL: https://wandbox.org/permlink/Yi5yI5OtL7QGczvl

複数のファイル

Wandboxには複数のファイルを書くことができるので、ファイルが複数あった場合にはそれらをそのまま送ることができます.

C/C++の場合

wandbox c(cpp)コマンドは複数のソースコードをコンパイルさせることができる.

$ cbt wandbox cpp main.cpp func.cpp

のように複数のソースファイルを指定できる.
wandbox goコマンドはできない.

ヘッダーの解析

#include <hoge>
のincludeは標準ライブラリなのでそのまま.
#include "hoge"
のincludeをファイルオープンしながら再帰的に正規表現で全部検索します.
ディレクトリは掘ることしかできないのでmain関数のあるファイルのパスより上層にファイルが置かれていたとき問題になるので, 全部main関数のあるファイルのパスに展開します.
同一のファイル名はアンダースコア任意個つけて置換します.

例えば

main.cpp
 ├ include/hoge.hpp
  |  └ hoge/hoge.hpp
 └ src/hoge.cpp

みたいになってるとしても, Wandboxに送る場合はフラットになります.

main.cpp
hoge.hpp
hoge_.hpp
hoge.cpp

goの場合

importの解析

Goのimportは標準ライブラリかGOPATH以下に存在するかということにします.
まず, GOPATHのディレクトリを解析します.
importされるファイルのうち, GOPATHに存在するものをWandbox側のGOPATHに置換します.
GOPATHが複数設定されている可能性は考慮されています.

実装で苦労したこと

Go入門の苦労

Goはよくできた言語で, 複雑な機能がバッサリ捨てられているというような感じだった.
なので, Go自体簡単で入門に苦労はなかった(良いコードが書けているとは言っていない).
さすがのGoだったので, WandboxにPOSTを送る部分なんかは調べながらでも1時間でできたと思う.

Wandboxの仕様を調べる苦労

当然GitHubでAPIの説明読んだだけでは全部は分からなかった.
よって

$ curl https://wandbox.org/api/list.json

して
prettyすると1.4万行になるjsonを読んだ.

そして, (本来の使い方ではないと思われるが)C++でマルチターゲットのコンパイルを行うためにcompiler-optionsにソースファイルを羅列する方法とかを試しまくっていた.

つまり, Wandboxに何ができて何ができないのかを探っていた.
ここで1週間溶かしました.

ファイルの解析の苦労

インクルードファイルの解析とファイルパス置換.
ひたすら面倒くさかった.
ずっとこれ書いてたきがする.

テスト

網羅テストでC1カバレッジをとろうとした形跡がある.
だいたい以下のような感じ.

一応テストしやすいようにTestRunを作って, テストケースを構造体にしてレンジで回してみたけど, みんなどうやってるんだろう.

func TestWandboxCpp(t *testing.T) {
    cases := []test.TestPattern{
        {In: `cbt wandbox cpp ./test_samples/cpp/multi.cpp ./test_samples/cpp/func.cpp`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -x=clang-head`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -w`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -v`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -o`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -msgpack`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -boost=1.65.1`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -p=no`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -p=yes`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -p=errors`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -p=no -bash`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -p=yes -bash`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -p=errors -bash`, Out: `Hello, cbt`, Err: nil},
        {In: `cbt wandbox cpp ./test_samples/cpp/simple_test.cpp -x=clang-head -w -v -o -msgpack -boost=1.65.1 -p=errors -bash`, Out: `no newline`, Err: nil},
    }
    for _, test := range cases {
        out, err := core.NewCLI().TestRun(strings.Split(test.In, " "))
        if !regexp.MustCompile(test.Out).Match(out) || !reflect.DeepEqual(test.Err, err) {
            t.Errorf("cbt (%q)\nout: %v, %v\nrequire: %v, %v",
                test.In, string(out), err, test.Out, test.Err)
        }
    }
}

なんとなくそれっぽいユニットテストも行っている.

最近のカバレッジです.

リリース

goreleaserコマンド一発でGitHubにリリースしてます.
最高に楽ちんです.

ただ, goreleaserが制御文字で出力に色をつけようとする安易なアレです.
Windowsで残念な表示になります.

↵[033mhoge↵[0m

みたいなやつです

mattn/go-colorable使って一行書き換えたところビューティフルになりました.

面倒なのでPRしません.

まとめ

コマンドラインから簡単に複数のファイルからなるコードをwandboxに送れるようになった.