Goライブラリもウルトラ簡単に作れる!
RubyGemはめっちゃ簡単に作れる! - 酒と泪とRubyとRailsとでRubyライブラリの公開方法が紹介されていました。
Goライブラリも知らないとハードル高そうに見えますが、実はかなり簡単につくれます。 これから積極的にGoライブラリを作ってOSSの世界に貢献していきたいので簡単な作り方をまとめました。
1. ファイルを用意する
今回はテスト的にHello World!と出力するようにします。hello.goを以下のように作成します。
package hello func Greet() { println("Hello World!")}
2. 公開する
# gitのリモートリポジトリを設定 git remote add origin git@github.com:doloopwhile/hello.git # add & commit & push git add . -A && git commit -m 'first commit' && git push
ライブラリはgo get github.com/doloopwhile/hello
でダウンロードできるようになります。
今回のサンプルソースは以下のリポジトリにあります。良ければForkして試してみてください!
gulpにもmakeにも不満なWebデベロッパーためのRake(コンパイル・パターンマッチ・ファイル監視・通知)
イントロ
21世紀になって登場したフロントエンド向けビルドツールであるgrunt, gulpなどは、最近批判を受けているようだ。
- 【翻訳】Web世代のデベロッパーのためのmake - MOL
- 最近のビルドツールって何なの? - 檜山正幸のキマイラ飼育記
- gulp問題ひきずり:ウォッチがまたおバカ過ぎる - 檜山正幸のキマイラ飼育記
たしかに、コンパイラ毎にプラグインをインストールしたり、毎年のように新しいビルドツールを覚えるのは無駄だ。古き良きmakeで十分という意見も理解できる。
でも、僕はmakeを使う気にもなれない。タブでインデントするのはまだ我慢できる。
しかし、$(JC) $(JCFLAGS) $< -o $@
といった記号を多用したソースは読みやすいとは言えない(可読性は大事だ!)。
また、機能がシンプルすぎて、標準的な使用方法から外れた時にはシェルスクリプトを苦労して書くことになる。
そこで、僕が提案したいのが、Ruby/DSLで記述するビルドツールRakeだ。
Rakeの導入
Rakeはmakeによく似た機能のビルドツールだ。
RakeのビルドスクリプトRakefileは、Rubyのソースコードそのものだ。 Rubyはオブジェクト指向言語として知られているが、 Rakeを使う上で自分でクラスを定義する必要は普通は無いので安心して欲しい。 むしろ、必要に応じて文字列処理機能やコレクションといったRubyのリッチな機能が使えるので、 makeより可読性が高いコードを簡単に書ける。
また、Ruby on Railsはもっとも普及しているWEBフレームワークの一つだ。 RubyはWEBデベロッパーにとって全く未知ということも無いと思う。 もしあなたがRubyに不案内でも、同僚のだれかはRubyを知っているはずだ。
RakeはRubyの処理系に一緒についてくる。 Unix系OSではRuby/Rakeをインストールするのは本当に簡単だ。
$ sudo apt install ruby2.0 # Ubuntuの場合 $ brew install ruby # OS Xの場合
一方、あなたがWindowsを使っているなら、RubyInstaller for Windowsをダウンロードして実行すればいい。
余談:Rubyは難解か?
Rakeと同じくRubyでDSLを書くツールの1つにChefがある。Chefは特にRubyに不慣れなエンジニアからは難しいとの悪評が高かった。でも、Chefの「難しさ」はRuby/DSLのせいだけでなく、そもそも「冪等性」という新しい概念のせいでもある。
ところで、おなじRuby製ツールでもserverspecには「Rubyだから難しい」という評判はあまり聞かない。 これはserverspecがわかりやすいモデルに基づくツールだからだろう。
そして、Rakeのモデルはmakeと同じシンプルでわかりやすいものなので、 「Rubyだから難しいのではないか」という心配をする必要はない。
もちろん、広い世間には必要以上に難しいコードや汚いコードを書いてしまう人がいるわけだけど、 それはRakeが難しいかどうかとは別の問題だ。
ファイルのコンパイル(TypeScript, SCSSなどのコンパイル、JSやCSSの結合など)
以下は、複数のJSファイルを結合して、dist.jsを生成するコードだ。
# Rakefile # パターンにマッチするファイルのリストを生成 Src = FileList['src/*.js'] # dist.jsの生成にはSrcに含まれるファイルが必要と定義 # do〜endの中で具体的な生成方法を定義 file 'dist.js' => Src do |t| # t.sourcesがソースファイルのリストなので、それをスペースで連結したコマンドラインを生成 sh "browserify -o dist.js #{t.sources.join(' ')}" end # デフォルトのタスクはdist.jsの生成と定義 task :default => 'dist.js'
どうだい?Makefileよりずっと親しみやすいだろう?
コマンドライン文字列の中で式展開#{...}
を使っているが、
これはRubyが元々提供していた機能だ。
一方、FileList
はRakeが独自に提供しているもので、
Ruby標準の配列にファイルパス操作機能が追加されている(class Rake::FileList)。
ここで、rake
コマンドでdist.js
を生成するタスクを実行した後、
もう一度rake
を実行すると今度はタスクは実行されない。
Rakeはmakeと同様、生成物とソースのタイムスタンプを比較し、生成物の方が新しければ、
わざわざ生成しなおしたりはしないんだ。
パターンによるファイルのコンパイル
rule
を使うと、規則的な変換が定義できる。例えば、
* 'src/FOO.scss' を 'dist/FOO.scss' に
* 'src/BAR.scss' を 'dist/BAR.scss' に
# Rakefile SRC = FileList['src/*.scss'] # SRC を規則的に変換 DIST = SRC.pathmap('%{^src,dist}X.css') # Rubyの標準機能を使ってやることもできる # DST = SRC.map do |path| # path.sub! /^src\//, 'dist/' # path.sub! /\.scss/, 'css' # end # rule を使うと規則的な変換が定義できる rule /dist\/.*\.css/ => '%{^dist,src}X.scss' do |t| sh "scss #{t.source} #{t}" end task :default => DIST
ここでは、Rakeが提供する機能を使って、パスの変換を定義している。
* FileList
の.pathmap
メソッド(書式文字列を渡している)
* rule
の=>
の左側の正規表現
* rule
の=>
の右側の書式文字列
しかし、ここであえて注意したいのは、.pathmap
メソッドなどは使わなくてもいいということ。
たとえば、コメントに書いたようにRubyの標準機能で用足すこともできるし、
はたまた.pathmap
で再現できない変な変換を書くこともできる。
これはmakeでは不可能だったことだ。
不要ファイルの削除
C言語などの開発では、最終成果物(実行ファイル)の他に、オブジェクトファイル等の中間ファイルができる。 こういった中間ファイルを一括に削除するタスクがあると良い。
そのためにRakeで必要なのは、rake/clean
をインポートし、CLEAN
とCLOBBER
という特別なリストにファイルを追加するだけだ。
# Rakefile require 'rake/clean' Src = FileList["*.c"] Obj = Src.ext('o') CLEAN.include(Obj) CLOBBER.include('hello') file 'hello' => Obj do |t| sh "gcc -o #{t.name} #{t.sources.join(' ')}" end rule '.o' => '.c' do |t| sh "gcc -c #{t.source}" end task :default => 'hello' # rake clean で *.oが削除される # rake clobber で hello も削除される
コンパイル以外のタスク(rsyncによるファイルアップロード等)
コマンド実行の形なら、どんな仕事をさせることもできる。
例えば、ファイルアップロードならrsync
を実行させればいい。
# Rakefile task :rsync do |t| sh "rsync -rax dist/ appsvr:/var/www/html/" end task :default => :rsync
シンプルだ!
ファイル変更の監視・通知
多くの人は、gruntやgulpを使う理由として、ファイルの変更を監視して自動で再コンパイルする機能を挙げるだろう。 残念ながら、Rakeにはファイル監視や通知の機能は無い。
だがファイル監視にfswatch
、デスクトップ通知にnotify-send
といった外部コマンドを使えば、
実現するのは簡単だ。
# Rakefile task :rsync do |t| sh "rsync -rax dist appsvr:/var/www/html/" end task :watch do |t| sh %w{fswatch -r w/ | xargs -I{} sh -c "rake && notify-send 'rsync success' || notify-send 'rsync failure'"} end task :default => :rsync # 以下のコマンドで監視開始 # $ rake watch
ここでは簡単さを優先してrake
を再帰的に呼びだす方法を書いた(安易な方法とも言う)。
もちろん、移植性その他を考慮すれば、別のコマンドやライブラリを使う選択もあるだろう。
まとめ
Gruntなどの21世紀のビルドツールにも、1970年代に登場したmakeにも、それぞれ不満な点があった。
一方、2000年代に開発されたRakeは、 makeのようにUnixの威力を活用でき、Rubyによって自在に拡張できる。 まさに、Gruntとmakeの長所を兼ね備えたツールだ
リンク
基本的なRakeの使い方
- 日本語: http://www2s.biglobe.ne.jp/~idesaku/sss/tech/rake/
- 英語: http://docs.seattlerb.org/rake/doc/rakefile_rdoc.html
FileList
などの機能を使い込みたいと思ったら
Rakeのバグを見つけたり、機能追加を提案したくなったら
DirenvでDotenvのファイル(.env)を読み込む
Dotenvは環境変数をファイル(.env)から読み込むためのRubyライブラリ。 Twelve-Factor Appなどでは、アプリケーションの設定は環境変数に格納することを推奨しており、Dotenvは本番と開発で環境変数を切り替えるのにうってつけ。
一方、開発環境で環境変数と言えばDirenv。 アプリにコードを追加する必要があるDotenvに比べ、Direnvはアプリと完全に独立して動作する!
ここで、Dotenvのファイル(.env)をDirenvから読み込むことさえできれば、Direnvに完全移行できるじゃないか!
と思ったら・・・なんだ、標準で入ってました、という話。
dotenv [dotenv_path]: Loads a ".env" file into the current environment
すばらしい。
go-gitconfigで自作ツールの設定を~/.gitconfigに書く
PythonのfileinputをGoで実装してみた
Pythonの○○をGoで実装してみたシリーズ
fileinputは、行単位で処理を行うコマンド(特にUNIXフィルタ)を作るときなどに便利なライブラリ。
import fileinput for line in fileinput.input(): process(line)
- コマンドライン引数で与えられたファイルの行に順番にアクセス
- 引数が空の場合は標準出力の行にアクセス
- ファイル名として '-' が与えられたときも標準出力の行にアクセス
使用例
sc := fileinput.Lines(os.Args[1:]) for sc.Scan() { process(sc.Text()) // 行を処理 } if sc.Err() != nil { os.Stderr.WriteString(sc.Err().Error() + "\n") os.Exit(1) } // なお、ファイルハンドラを閉じる処理は.Scan()や.Err()の中で自動で行われる
Python版との違い
Go版はbufio.Scannerを真似たインターフェースにしました。 そのため、Pythonほどシンプルではありません(Pythonはfor文に渡すだけで使えるが、Go版は覚えるべきメソッドが3つもある)。
行をchan string
で返せば、Python版に近い書き心地になるような気もしましたが、
標準に合わせたいことと、チャネルを返そうとするとGoroutineを1つ余分に消費してしまうことがあり、
あきらめました。
#リハビリを兼ねて、チョチョイと書くつもりが、なぜか半日もかかってしまった・・・
Dockerの6つのtips
1. イメージを作る方法はDockerfileだけじゃない
Packerもある。
Packerは様々な仮想環境のイメージを統一的な方法で作れるツールで、Virtualbox・AWS・DigitalOceanなどにも対応しています。 したがって、DigitalOceanイメージとDockerイメージを1つのソースから作るといったことができます。
PackerはDockerの全機能が使えるわけではない(差分ビルドができない)のですが、 場合によっては問題にならないかもしれません。
2.Dockerイメージはtarで保存できる
Dockerイメージをtarとして保存すれば、Jenkinsの成果物として保存したり、S3経由で配信したりできます。
tar出力するにはdocker exportを使います。また、packerはデフォルトではイメージをtarで出力します。
3. 出力ファイルをchown
する
docker run
に-v
を付けると、コンテナにホストのディレクトリをマウントできます。
ところで、一般ユーザーがdocker run
を実行しても、コンテナ内部ではrootユーザーとしてコマンドが実行されているため、出力ファイルもオーナー= rootで作られます。
このとき、ホスト側のディレクトリでもファイルのオーナーがrootになってしまうため、ファイルにアクセスできなくなってしまいます。
環境変数としてユーザー名を渡し、出力ファイルのオーナーを変更するとよいと思います。
docker-test.sh
#!/bin/bash go test -v ./... | tee /docker/output/gotest.log EXIT_CODE=$? useradd "${OUTPUT_USER}" chown "${OUTPUT_USER}:${OUTPUT_USER}" /docker/output/gotest.log exit ${EXIT_CODE} docker run \ --env=OUTPUT_USER="${USER}" \ -v "/workspace:/docker" \ centos \ docker-test.sh
4. --rm
でコンテナを削除する
普通にdocker run
するとコマンド終了後もコンテナが残り、いずれこのコンテナが溜まってディスクが溢れてしまいます。
docker run
に--rm
をつけると、コマンド成功時には自動でコンテナを削除します。
5. cidfileで失敗時にもコンテナを削除する
docker run
はコマンドが失敗したときにはコンテナを終了しません。
そのため、--rm
をつけていてもコンテナが残ってしまいます。
docker run
に--cidfile=<file>
をつけるとdocker stop
します。
docker run --rm --cidfile=dockercid centos /bin/false if [ -e dockercid ]; then CID=$(cat dockercid | cut -b -12) (docker ps -aq | grep "${CID}") && docker stop "${CID}" rm -f dockercid fi
6. 古いコンテナ・イメージを削除する
さらに定期的に以下のコマンドを実行すれば、さらに安心です。
for cid in $(docker ps -aq); do docker rm "${cid}" done for iid in $(docker images | awk '/^<none>/ { print $3 }'); do docker rmi "${iid}" done
まとめ
DockerはWordのイルカより有能です。