virtual-dom のイベント処理
仮想 DOM を扱うための JavaScript 用ライブラリ virtual-dom では ev-xxx (例. ev-click
) でイベント処理を扱えるようになっていますが、実際に機能させるには dom-delegator が必要なようです。
virtual-dom は bower install が可能で Web ブラウザ上で直接使えるファイル (dist/virtual-dom.js) を用意してくれていますが、今のところ dom-delegator はそうなっていません。(dom-delegator は npm install する)
名称 | bower install | Web ブラウザで直接使用 |
---|---|---|
virtual-dom | ○ | ○ |
dom-delegator | × | × |
そこで、今回は下記ツールを用いて virtual-dom で ev-click
が機能するようにしてみます。
事前準備
Gulp・Bower・Browserify をインストールします。
インストール例
> npm install -g gulp bower browserify
virtual-dom のイベント処理サンプル
virtual-dom で ev-click を実施するサンプルを作成します。
ソースは http://github.com/fits/try_samples/tree/master/blog/20160107/
(1) virtual-dom インストール
virtual-dom を Bower でインストールします。
bower init と virtual-dom インストール例
> bower init ・・・ > bower install virtual-dom --save ・・・
(2) dom-delegator インストール
dom-delegator は npm でインストールします。 (dom-delegator は Bower でインストールできないため ※)
※ dom-delegator の git リポジトリを bower install する事は可能ですが、 その場合は依存ライブラリを手動でインストールする事になります
npm init と dom-delegator インストール例
> npm init ・・・ > npm install dom-delegator --save ・・・
(3) gulp-flatten インストール
Gulp でファイルをコピーする際にディレクトリ構成をフラット化するため gulp-flatten パッケージをインストールしておきます。
gulp-flatten インストール例
> npm install gulp-flatten --save-dev
Gulp で以下のように bower_components/virtual-dom/dist/virtual-dom.js を js ディレクトリへコピーすると、js/virtual-dom/dist/virtual-dom.js が作られます。
Gulp によるコピー例
gulp.task('js-copy', () => { gulp.src('bower_components/*/dist/*.js') .pipe(gulp.dest('js')); });
今回は、js/virtual-dom/dist/virtual-dom.js では無く js/virtual-dom.js へコピーしたかったので gulp-flatten を使いました。
(4) Gulp ビルド定義作成と実行
以下のような処理を行う gulpfile.js を作成します
- (a) bower_components/virtual-dom/dist/virtual-dom.js を js/virtual-dom.js へコピー
- (b) dom-delegator を Browserify で処理して js/dom-delegator.js へ出力 (Web ブラウザ上で DOMDelegator という名称で扱えるように standalone も設定)
gulpfile.js
var fs = require('fs'); var gulp = require('gulp'); var browserify = require('browserify'); var flatten = require('gulp-flatten'); // (a) gulp.task('js-copy', () => { gulp.src('bower_components/*/dist/*.js') .pipe(flatten()) // gulp-flatten でディレクトリをフラット化 .pipe(gulp.dest('js')); }); // (b) gulp.task('browserify', () => { browserify({ require: 'dom-delegator', standalone: 'DOMDelegator' }).bundle().pipe(fs.createWriteStream('js/dom-delegator.js')); }); gulp.task('default', ['js-copy', 'browserify']);
js ディレクトリを手動で作成した後、gulp
コマンドを実行します。
gulp 実行例
> mkdir js > gulp [00:19:17] Using gulpfile ・・・\virtual-dom\gulpfile.js [00:19:17] Starting 'js-copy'... [00:19:17] Finished 'js-copy' after 15 ms [00:19:17] Starting 'browserify'... [00:19:17] Finished 'browserify' after 17 ms [00:19:17] Starting 'default'... [00:19:17] Finished 'default' after 6.16 μs
これで下記のファイルが作成されます。
- js/virtual-dom.js
- js/dom-delegator.js
(5) virtual-dom の ev-click サンプル作成と実行
Gulp で生成した js ファイルを使い virtual-dom で ev-click を扱う単純なサンプルを作成し動作確認してみます。
virtual-dom は概ね以下のようにして使います。
- (1) virtualDom.diff 関数で 2つの VNode (VirtualNode) の差分を VPatch (VirtualPatch) として抽出
- (2) virtualDom.patch 関数で VPatch の内容を実際の DOM へ反映
index.html
<!DOCTYPE html> <html> <script src="js/virtual-dom.js"></script> <script src="js/dom-delegator.js"></script> <body> <h1>virtual-dom click sample</h1> <div id="ct"></div> <script> (function() { // ev-xxx イベントの有効化 (dom-delegator) new DOMDelegator(); var h = virtualDom.h; var create = virtualDom.create; var diff = virtualDom.diff; var patch = virtualDom.patch; var index = 1; var tree = h(); var render = function(v) { // 仮想 DOM の作成 return h('div', [ h('button', { // click イベントの設定 'ev-click': function(ev) { update(index++) } }, ['countUp'] ), h('br'), 'counter: ' + String(v) ]); } var update = function(v) { var newTree = render(v); // tree と newTree の差分を実際の DOM へ反映 patch(document.getElementById('ct'), diff(tree, newTree)); tree = newTree; }; update(index++); })(); </script> </body> </html>
Web ブラウザで実行し countUp ボタンを押下すると counter の数値がカウントアップする事を確認できました。
Web ブラウザ表示例
virtual-dom イベント処理の仕組み
最後に、virtual-dom と dom-delegator がどのようにイベント処理を行っているのか簡単に書いておきます。
まず、VNode の作成時に ev- で始まるプロパティは EvHook へ変換されます。(virtual-hyperscript/index.js の transformProperties)
patch 関数で実際の DOM へ反映する際に、対象の DOM ノードへ特殊なプロパティ __EV_STORE_KEY@7
※ を追加して EvHook の内容 (イベント名とイベント処理関数) を設定します。 (ev-store の機能)
※ プロパティ名は ev-store の index.js で定義されており、 ev-store 7.0.0 では __EV_STORE_KEY@7 となっています
__EV_STORE_KEY@7 が設定されている DOM ノード例
dom-delegator は "blur", "change", "click" 等の多数のイベントを document.documentElement で listen するように処理し、イベント発生時に対象 DOM ノードの __EV_STORE_KEY@7
プロパティの内容をチェックして、該当するイベントが設定されていればその処理関数を実行します。
つまり、上記のようなイベント処理を自前で行えば dom-delegator を使わなくても ev-click を機能させる事が可能です。
index2.html (dom-delegator を使わずに ev-click を有効化する例)
<!DOCTYPE html> <html> <script src="js/virtual-dom.js"></script> <body> <h1>virtual-dom click sample2</h1> <div id="ct"></div> <script> (function() { // dom-delegator の代用処理 document.addEventListener('click', function(ev) { var store = ev.target['__EV_STORE_KEY@7']; if (store && store[ev.type]) { // 該当するイベント処理関数の実行 store[ev.type](ev); } }, true); var h = virtualDom.h; ・・・ var render = function(v) { return h('div', [ h('button', { 'ev-click': function(ev) { update(index++) } }, ['countUp'] ), h('br'), 'counter: ' + String(v) ]); } ・・・ })(); </script> </body> </html>
pulp を使った PureScript の開発
PureScript 用のビルドツールに pulp があります。
pulp を使えば PureScript v0.7 から多少面倒になったビルドや実行が比較的容易になります。
pulp インストール
Node.js の npm で purescript (コンパイラ) と pulp をインストールします。
pulp インストール例
> npm install -g purescript pulp
今回インストールした PureScript コンパイラ・pulp のバージョンは以下の通りです。
なお、PureScript コンパイラに関しては https://github.com/purescript/purescript/releases/ から各 OS 用のバイナリを直接取得する方法もあります。
npm でインストールしたものも実際は node_modules/purescript/vendor
ディレクトリへ配置された各 OS 用のバイナリファイル (例. psc.exe) を使っているようです。
pulp を使った開発
今回作成したソースは http://github.com/fits/try_samples/tree/master/blog/20160105/
プロジェクトの作成
任意のディレクトリ内で pulp init
を実行すると、必要最小限のファイルが生成されます。
その際に Bower を使って PureScript の主要ライブラリ (以下) を自動的に取得しますので、git コマンドを使えるようにしておく必要があります。
- purescript-console
- purescript-eff
- purescript-prelude
プロジェクト作成例
> pulp init * Generating project skeleton in ・・・ bower cached git://github.com/purescript/purescript-console.git#0.1.1 bower validate 0.1.1 against git://github.com/purescript/purescript-console.git#^0.1.0 bower cached git://github.com/purescript/purescript-eff.git#0.1.2 bower validate 0.1.2 against git://github.com/purescript/purescript-eff.git#^0.1.0 bower cached git://github.com/purescript/purescript-prelude.git#0.1.3 bower validate 0.1.3 against git://github.com/purescript/purescript-prelude.git#^0.1.0 bower install purescript-console#0.1.1 bower install purescript-eff#0.1.2 bower install purescript-prelude#0.1.3 purescript-console#0.1.1 bower_components\purescript-console └── purescript-eff#0.1.2 purescript-eff#0.1.2 bower_components\purescript-eff └── purescript-prelude#0.1.3
ディレクトリ・ファイル構成は以下のようになります。
- bower_components
- purescript-console
- purescript-eff
- purescript-prelude
- src/Main.purs
- test/Main.purs
- .gitignore
- bower.json
デフォルトで用意されている src/Main.purs の内容は以下の通りです。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import Control.Monad.Eff.Console main :: forall e. Eff (console :: CONSOLE | e) Unit main = do log "Hello sailor!"
ライブラリの追加には pulp dep install <ライブラリ名>
を実行します。
そうすると bower install が実施されます。
bower.json の依存パッケージ設定へエントリを追加するには --save オプションを付けます。
purescript-tuples の追加例
> pulp dep install purescript-tuples --save ・・・ bower install purescript-control#0.3.2 bower install purescript-invariant#0.3.0 purescript-tuples#0.4.0 bower_components\purescript-tuples └── purescript-foldable-traversable#0.4.2 purescript-foldable-traversable#0.4.2 bower_components\purescript-foldable-traversable ├── purescript-bifunctors#0.4.0 └── purescript-maybe#0.3.5 purescript-maybe#0.3.5 bower_components\purescript-maybe └── purescript-monoid#0.3.2 purescript-bifunctors#0.4.0 bower_components\purescript-bifunctors └── purescript-control#0.3.2 purescript-monoid#0.3.2 bower_components\purescript-monoid ├── purescript-control#0.3.2 └── purescript-invariant#0.3.0 purescript-control#0.3.2 bower_components\purescript-control └── purescript-prelude#0.1.3 purescript-invariant#0.3.0 bower_components\purescript-invariant └── purescript-prelude#0.1.3
ビルドと実行
Main.purs を以下のようにタプルを使った処理に書き換えて実行してみます。
src/Main.purs
module Main where import Prelude import Control.Monad.Eff import Control.Monad.Eff.Console import Data.Tuple main :: forall e. Eff (console :: CONSOLE | e) Unit main = do let t = Tuple "two" 2 print t let r = t >>= \x -> Tuple " + 1" (x + 1) print r
pulp run
を実行するとビルドした後に処理を実施します。
ビルドだけを実施したい場合は pulp build
を使います。
実行
> pulp run * Building project in ・・・\20160105\purescript psc: No files found using pattern: src/**/*.js * Build successful. Tuple ("two") (2) Tuple ("two + 1") (3)
ビルド結果は output ディレクトリへ生成されます。
パッケージング
pulp browserify
を実行すると Browserify を使って output ディレクトリ内のファイルをパッケージングしてくれます。
browserify によるパッケージング
> pulp browserify > sample.js * Browserifying project in ・・・\20160105\purescript * Project unchanged; skipping build step. * Browserifying...
パッケージングしたファイル (sample.js) の実行結果は以下の通りです。
実行結果
> node sample.js Tuple ("two") (2) Tuple ("two + 1") (3)
Web ブラウザで実行する事もできます。
index.html
<!DOCTYPE html> <html> <script src="sample.js"></script> </html>
備考 - pulp を使わない場合
最後に pulp を使わない場合のビルド・実行方法も書いておきます。
まずは、Bower を使って purescript-console 等の必要なライブラリを手動でインストールします。
ライブラリのインストール例
> bower install purescript-console --save
src/Main.purs を psc コマンドでビルドするには以下のようにします。
psc によるビルド例
> psc src/Main.purs bower_components/purescript-*/src/**/*.purs --ffi bower_components/purescript-*/src/**/*.js
bower_components 内の .purs ファイルと --ffi オプションで bower_components 内の .js ファイルを指定します。
ビルド結果はデフォルトで output ディレクトリへ生成されます。(-o オプションで変更する事も可能)
実行する場合は、NODE_PATH 環境変数へ output ディレクトリを設定し、node コマンドで require('Main/index.js').main()
を実行します。
実行例
> set NODE_PATH=output > node -e "require('Main/index.js').main()" Tuple ("two") (2) Tuple ("two + 1") (3)
Google Cloud Print を Web API で操作 - Unirest 使用
リフレッシュトークンを使って Google Cloud Print を Web API で操作してみます。
以前、「Google アカウントで Google API を利用 - google-api-services-gmail」 では Apache HTTPClient を使いましたが、今回は Unirest を使っています。
ソースは http://github.com/fits/try_samples/tree/master/blog/20151222/
はじめに
事前準備としてリフレッシュトークンを取得しておきます。
今回は、「Google アカウントで・・・」 にて発行したクライアント ID をそのまま使用します。
(1) コードの取得
下記パラメータを付け https://accounts.google.com/o/oauth2/auth
へ Web ブラウザ等でアクセスし API の利用を許可する事でコードを取得します。
パラメータ | 値 |
---|---|
response_type | code |
client_id | クライアント ID の client_id 値(※1) |
scope | https://www.googleapis.com/auth/cloudprint |
redirect_uri | oob (※2) |
(※1)クライアント ID 発行時にダウンロードした JSON ファイルの client_id 値 (※2)リダイレクトしない場合の設定。 何らかのアプリケーション内から実行する際は それに合わせたリダイレクト先を指定する
例えば、以下のような URL へアクセスします。
URL 例
https://accounts.google.com/o/oauth2/auth?redirect_uri=oob&response_type=code&client_id=xxx.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/cloudprint
Google アカウントでログインすると以下のような画面が表示されますので、「許可」ボタンを押下します。
コードが表示されるのでコピーしておきます。
(2) リフレッシュトークンの取得
次に、下記パラメータを https://www.googleapis.com/oauth2/v3/token
へ POST し、リフレッシュトークンを含んだ JSON を取得します。
パラメータ | 値 |
---|---|
code | (1) で取得したコード |
client_id | クライアント ID の client_id 値 |
client_secret | クライアント ID の client_secret 値 |
grant_type | authorization_code |
redirect_uri | oob (※1) |
(※1)(1) の場合と同様です。 ただし、以下のスクリプトでは urn:ietf:wg:oauth:2.0:oob を設定しています
Groovy スクリプト化すると以下のようになります。
get_refresh-token.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper addShutdownHook { Unirest.shutdown() } def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def code = args[1] def res = Unirest.post('https://www.googleapis.com/oauth2/v3/token') .field('code', code) .field('client_id', conf.client_id) .field('client_secret', conf.client_secret) .field('grant_type', 'authorization_code') .field('redirect_uri', conf.redirect_uris[0]) .asJson() println res.body
クライアント ID 発行時にダウンロードした JSON (ここでは client_secret.json) を第1引数、(1) で取得したコードを第2引数へ指定して実行します。
取得したリフレッシュトークン JSON は後で使うので保存しておきます。 (ここでは token.json へ保存)
実行例
> groovy get_refresh-token.groovy client_secret.json 4/9ic・・・ > token.json
プリンタ一覧の取得
リフレッシュトークンを使う場合は、以下のような手順で API を呼び出します。
- リフレッシュトークンからアクセストークンを取得
- アクセストークンを使って API を実行
リフレッシュトークンからアクセストークン取得
アクセストークンを取得するには、下記パラメータを https://www.googleapis.com/oauth2/v3/token
へ POST します。
パラメータ | 値 |
---|---|
client_id | クライアント ID の client_id 値 |
client_secret | クライアント ID の client_secret 値 |
grant_type | refresh_token |
refresh_token | リフレッシュトークンの値 (※1) |
(※1)リフレッシュトークン取得で保存した JSON の refresh_token 値
この処理は共通的に使用するため、Groovy の BaseScript として定義しました。
TokenScript.groovy
import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper abstract class TokenScript extends Script { // アクセストークンの取得 def accessToken(String clientId, String clientSecret, String refreshToken) { def res = Unirest.post('https://www.googleapis.com/oauth2/v3/token') .field('client_id', clientId) .field('client_secret', clientSecret) .field('grant_type', 'refresh_token') .field('refresh_token', refreshToken) .asJson() res.body.object } }
API 実行 (プリンタの一覧取得)
アクセストークンは HTTP ヘッダーへ Authorization: Bearer <アクセストークン>
のように設定して使います。(例. Authorization: Bearer ya26.pw・・・)
クラウドプリンタ一覧は https://www.google.com/cloudprint/search
へ GET すれば取得できます。 (q や type パラメータを付けて条件検索することも可能)
get_printers.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper import groovy.transform.BaseScript // TokenScript を BaseScript へ設定 @BaseScript TokenScript baseScript addShutdownHook { Unirest.shutdown() } def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def token = json.parse(new File(args[1])) // アクセストークン取得 def newToken = accessToken( conf.client_id, conf.client_secret, token.refresh_token ) // API の実行 def printers = Unirest.get('https://www.google.com/cloudprint/search') .header('Authorization', "${newToken.token_type} ${newToken.access_token}") .asJson() println printers.body
実行結果は以下の通りです。
まだプリンタを登録していないためデフォルトの 「ドライブに保存」 しかありません。
実行結果 (出力結果は加工済、実際は改行・字下げは無し)
> groovy get_printers.groovy client_secret.json token.json { "request":{・・・}, "printers":[{ "isTosAccepted":false, "displayName":"ドライブに保存", "capsHash":"", "description":"ドキュメントを Google ドライブで PDF として保存します", "updateTime":"1370287324050", "ownerId":"cloudprinting@gmail.com", "type":"DRIVE", "tags":["save","docs","pdf","google","__google__drive_enabled"], "proxy":"google-wide", "ownerName":"Cloud Print", "createTime":"1311368403894", "defaultDisplayName":"ドライブに保存", "connectionStatus":"ONLINE", "name":"Save to Google Docs", "id":"__google__docs", "accessTime":"1316132041869", "status":"" }], "success":true, ・・・ }
また、https://www.google.com/cloudprint/list?proxy=<プロキシ>
へ GET すると、指定プロキシへ属するプリンタ一覧を取得できます。
印刷処理 (Google Drive へ保存)
最後に、「ドライブに保存」 プリンタへファイルを印刷してみます。 (実際は Google Drive へファイル保存)
印刷は、下記の必須パラメータ(他にもパラメータあり)を multipart/form-data
で https://www.google.com/cloudprint/submit
へ POST します。
パラメータ | 値 |
---|---|
printerid | プリンタ ID (※1) |
title | 印刷タイトル |
ticket | 印刷設定 (※2) |
content | 印刷するファイルの内容 |
(※1)今回は __google__docs を使用 (※2)CJT (Cloud Job Ticket) フォーマットで印刷オプションなどを指定
submit_file.groovy
@Grab('com.mashape.unirest:unirest-java:1.4.9') import com.mashape.unirest.http.Unirest import groovy.json.JsonSlurper import groovy.json.JsonBuilder import groovy.transform.BaseScript @BaseScript TokenScript baseScript addShutdownHook { Unirest.shutdown() } def ticketBuilder = new JsonBuilder() // ticket の内容 ticketBuilder ( version: '1.0', print: {} ) def json = new JsonSlurper() def conf = json.parse(new File(args[0])).installed def token = json.parse(new File(args[1])) def file = new File(args[2]) // アクセストークンの取得 def newToken = accessToken( conf.client_id, conf.client_secret, token.refresh_token ) // ticket の JSON 文字列化 def ticket = ticketBuilder.toString() // 印刷 def res = Unirest.post('https://www.google.com/cloudprint/submit') .header('Authorization', "${newToken.token_type} ${newToken.access_token}") .field('printerid', '__google__docs') .field('title', file.name) .field('ticket', ticket) .field('content', file) .asJson() println res.body
PDF ファイルを POST した結果は以下の通りです。
Google Drive で確認すると sample1.pdf ファイルが保存されていました。
実行結果 (出力結果は加工済、実際は改行・字下げは無し)
> groovy submit_file.groovy client_secret.json token.json data/sample1.pdf { "request":{・・・}, "success":true,"xsrf_token":"AIp・・・", "message":"印刷ジョブが追加されました。", "job":{ "ticketUrl":"https://www.google.com/cloudprint/ticket?format=xps&output=xml&jobid=0db・・・", "printerName":"", "errorCode":"", ・・・ "title":"sample1.pdf", "tags":["^own"], ・・・ "printerid":"__google__docs", ・・・ "contentType":"application/pdf", "status":"DONE" } }
また、試しに以下のような拡張子のファイルを POST してみたところ、自動的に PDF へ変換され Google Drive へ保存されました。 (例えば sample1.html は sample1.html.pdf で保存)
- .html
- .docx
- .odt
Android エミュレータの hosts ファイルを編集
Android エミュレータの hosts ファイルを編集する方法です。
以下のような方法が考えられます。
- (a) shell で hosts へ行を追加
- (b) pull と push で hosts を変更
(a) shell で hosts へ行を追加
行を追加するだけなら以下のように shell を使うだけです。
adb remount
しておかないと /system/etc/hosts ファイルを更新できずに Read-only file system
のようなエラーメッセージが表示される点に注意。 (既に adb remount 済みの場合は不要)
hosts への行追加
> adb remount remount succeeded > adb shell "echo 10.0.2.2 server1 >> /system/etc/hosts"
/system/etc/hosts の内容を確認してみます。
hosts の内容確認
> adb shell cat /system/etc/hosts 127.0.0.1 localhost 10.0.2.2 server1
10.0.2.2 はエミュレータから接続する際のホスト OS の IP アドレスなので、ping してみます。
ping の実行
> adb shell ping server1 PING server1 (10.0.2.2) 56(84) bytes of data. 64 bytes from server1 (10.0.2.2): icmp_seq=1 ttl=255 time=0.000 ms ・・・
remount に関して
adb remount
する事で以下のように /system が rw で再マウントされます。
adb remount 前 (ro)
/dev/block/mtdblock0 /system ext4 ro,relatime,data=ordered 0 0
adb remount 後 (rw)
/dev/block/mtdblock0 /system ext4 rw,relatime,data=ordered 0 0
(b) pull と push で hosts を変更
次は hosts ファイルを編集する方法です。
エミュレータには vi 等のエディタが用意されていないようなので、pull で hosts ファイルを取得して、ホスト OS 側で hosts ファイルを編集し、push でエミュレータへ反映します。
まずは adb pull
で hosts ファイルを取得します。
hosts を pull
> adb pull /system/etc/hosts
ホスト OS のカレントディレクトリへ pull された hosts ファイルを編集します。
hosts 変更例
127.0.0.1 localhost 10.0.2.2 server1
変更した hosts を push します。
まだ一度も remount していない場合は remount する必要があります。
hosts を push
> adb remount ・・・ > adb push hosts /system/etc/
Swift のビルド環境を Docker で構築
Ubuntu をベースとした Swift のビルド環境を Docker で構築してみました。
使用したソースは http://github.com/fits/try_samples/tree/master/blog/20151207/
Docker イメージ作成
ubuntu:15.10
のイメージをベースに Swift をセットアップしました。
clang 等の必要なパッケージをインストールした後、swift-2.2-SNAPSHOT-2015-12-01-b-ubuntu15.10.tar.gz をダウンロードして解凍し、環境変数 PATH へ swift-2.2-・・・/usr/bin
のパスを追加するだけです。
また、clang
をインストールしただけだと swiftc の実行時に /usr/bin/ld: warning: libicuuc.so.55, needed by /swift-2.2・・・/usr/lib/swift/linux/libswiftCore.so, not found (try using -rpath or -rpath-link)
のようなメッセージが出力されたので、libicu-dev
もインストールするようにしています。
Dockerfile
FROM ubuntu:15.10 ENV SWIFT_PACKAGE swift-2.2-SNAPSHOT-2015-12-01-b-ubuntu15.10 RUN apt-get update RUN apt-get -y upgrade RUN apt-get -y install curl clang libicu-dev # swift のパッケージをダウンロード RUN curl https://swift.org/builds/ubuntu1510/swift-2.2-SNAPSHOT-2015-12-01-b/$SWIFT_PACKAGE.tar.gz -o $SWIFT_PACKAGE.tar.gz RUN tar zxf $SWIFT_PACKAGE.tar.gz RUN rm -f $SWIFT_PACKAGE.tar.gz RUN apt-get clean # 環境変数 PATH の設定 ENV PATH /$SWIFT_PACKAGE/usr/bin:$PATH
docker build
で Docker イメージを作成します。
Docker イメージの作成
$ docker build --no-cache -t sample/swift:0.1 . ・・・ Successfully built ・・・
動作確認
作成した Docker イメージを使って以下のようなサンプルソースを実行してみます。
/vagrant/work/sample.swift
let a = 11 let b = 2 print("result = \(a * b)")
まず、sample/swift:0.1
イメージを使って Docker コンテナを起動します。
Docker コンテナの起動
$ docker run --rm -it -v /vagrant/work:/work sample/swift:0.1 bash ・・・ # cd /work
コンテナ内で sample.swift を実行します。
直接実行
# swift sample.swift result = 22
ビルドしてから実行
# swiftc sample.swift # ./sample result = 22
Objective-C のビルド環境を Docker で構築
CentOS をベースとした Objective-C のビルド環境を Docker で構築してみました。
使用したソースは http://github.com/fits/try_samples/tree/master/blog/20151130/
Docker イメージ作成
今回は clang と GNUstep を使う事にします。
バージョンは古くなってしまいますが、どちらも yum でインストールするようにしました。
make
をインストールしておかないと GNUstep の gnustep-config
コマンドが機能しない (結果が空になる) のでご注意ください。
また、gcc-objc
をインストールしておかないと、Objective-C のソースをビルドする際に必要なヘッダーファイルを参照できず、fatal error: 'inttypes.h' file not found
のようなエラーが発生しました。
Dockerfile
FROM centos RUN yum -y update RUN yum -y install epel-release RUN yum -y install clang make gcc-objc gnustep-base-devel RUN yum clean all
docker build
で Docker イメージを作成します。
Docker イメージの作成
$ docker build --no-cache -t sample/objc:0.1 . ・・・ Successfully built ・・・
動作確認
以下のようなサンプルソースをビルドしてみます。
/vagrant/work/Sample.h
#import <Foundation/NSObject.h> #import <Foundation/NSString.h> @interface Sample : NSObject { NSString* _name; } @property (nonatomic, copy) NSString* name; - (void)log; @end
/vagrant/work/Sample.m
#import <Foundation/Foundation.h> #import "Sample.h" @implementation Sample : NSObject @synthesize name = _name; - (void)log { NSLog(@"%@", _name); } - (void)dealloc { [_name release]; [super dealloc]; } @end int main(int argc, const char * argv[]) { @autoreleasepool { Sample* s = [[Sample new] autorelease]; s.name = @"test"; [s log]; } return 0; }
まずは sample/objc:0.1
イメージを使って Docker コンテナを起動します。
Docker コンテナの起動
$ docker run --rm -it -v /vagrant/work:/work sample/objc:0.1 bash
コンテナ内で clang を使ってビルドします。
コンパイラオプションに関しては以下で妥当なのかは不明ですが、一応ビルドには成功しました。 (場合によっては -objcmt-migrate-all
等の指定も必要かもしれません)
ビルド
# cd work # clang `gnustep-config --objc-flags` `gnustep-config --objc-libs` -lgnustep-base -I /usr/lib/gcc/x86_64-redhat-linux/4.8.3/include Sample.m -o Sample clang: warning: argument unused during compilation: '-shared-libgcc'
一応、それぞれのオプションを指定しなかった場合に発生したエラーをまとめておきます。
オプション指定 | 指定しなかった場合のエラー例 |
---|---|
`gnustep-config --objc-flags` | 無し(指定しなくてもビルドは成功) |
`gnustep-config --objc-libs` | undefined reference to symbol 'objc_getProperty' |
-lgnustep-base | undefined reference to `NSLog' |
-I /usr/lib/gcc/・・・/include | fatal error: 'objc/objc.h' file not found |
ビルド結果の Sample
を実行してみると一応動作しました。
実行例
# ./Sample 2015-11-30 20:15:00.121 Sample[32] test
なお、clang -v
の実行結果は以下のようになりました。
clang -v
# clang -v clang version 3.4.2 (tags/RELEASE_34/dot2-final) Target: x86_64-redhat-linux-gnu Thread model: posix Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.2 Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.3 Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.8.2 Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.8.3 Selected GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/4.8.3
twemproxy + Redis 環境を Docker で構築
twemproxy(別名 nutcracker) + Redis の環境を Docker で構築してみます。 (twemproxy は memcached・Redis 用の軽量なプロキシです)
使用した設定ファイル等は http://github.com/fits/try_samples/tree/master/blog/20151124/
Redis の Docker イメージを取得
Redis の Docker イメージは docker pull で取得する事にします。
$ docker pull redis
twemproxy の Docker イメージを構築
twemproxy の Docker イメージは、下記 Dockerfile を使って centos のイメージをベースに twemproxy のソースをビルドして作成する事にします。
Dockerfile
FROM centos RUN yum -y update RUN yum -y install make automake libtool git RUN git clone https://github.com/twitter/twemproxy.git RUN cd twemproxy && autoreconf -fvi && ./configure && make && make install RUN rm -fr twemproxy RUN yum clean all
上記 Dockerfile で docker build を実行すれば Docker イメージを作成できます。
Docker イメージの作成
$ docker build --no-cache -t sample/twemproxy:0.1 . ・・・ Successfully built ・・・
twemproxy のビルドで warning は出ましたが、Docker イメージの作成に成功しました。
twemproxy + Redis × 2 の実行
twemproxy と Redis 2台を個別の Docker コンテナで実行してみます。
twemproxy の設定ファイルで接続する Redis サーバーを <ホスト名 or IPアドレス>:<ポート番号>:<重み>
で指定する必要があり、これが課題となります。
twemproxy 設定ファイル例
sample: ・・・ redis: true servers: - 127.0.0.1:6380:1 - 127.0.0.1:6381:1 ・・・
今回は、起動スクリプトを使って twemproxy と Redis の Docker コンテナをまとめて起動するようにしてみました。
該当コンテナを docker start (コンテナを再起動) してみて、失敗した場合は docker run を行うようにしています。 (docker start し易いように --name=<コンテナ名>
で名前を付けています)
また、コンテナ間の接続を容易にするため、docker run 時に -h <ホスト名>
でホスト名も付けました。 (コンテナ名をそのままホスト名に使っています)
/vagrant/tmp/start_twemproxy (twemproxy + Redis コンテナの起動スクリプト)
#!/bin/sh INDENT=" " CONF_DIR=/vagrant/tmp/conf CONTAINER_CONF_DIR=/conf TWEMPROXY_IMAGE=sample/twemproxy:0.1 CONTAINER_REDIS_LIST="redis1 redis2" CONTAINER_TWEMPROXY=twemproxy1 REDIS_SERVERS="" # Redis のコンテナを個々に起動 for s in $CONTAINER_REDIS_LIST do if test -z `docker start $s`; then docker run --name=$s -h $s -d redis else echo start $s fi REDIS_SERVERS="$REDIS_SERVERS$INDENT- $s:6379:1\n" done # twemproxy の設定ファイルを生成 sed -e "s/#{SERVERS}/$REDIS_SERVERS/g" `dirname $0`/nutcracker.yml.tpl > $CONF_DIR/nutcracker.yml # twemproxy のコンテナ起動 if test -z `docker start $CONTAINER_TWEMPROXY`; then docker run --name=$CONTAINER_TWEMPROXY -h $CONTAINER_TWEMPROXY -d -p 6379:6379 -v $CONF_DIR:$CONTAINER_CONF_DIR $TWEMPROXY_IMAGE nutcracker -c $CONTAINER_CONF_DIR/nutcracker.yml else echo start $CONTAINER_TWEMPROXY fi
twemproxy は nutcracker -c <設定ファイル>
で起動しています。
また、twemproxy 設定ファイル(nutcracker.yml)は Redis サーバーの構成に合わせて下記テンプレートの #{SERVERS}
を sed
で置き換えて生成するようにしています。
/vagrant/tmp/nutcracker.yml.tpl (twemproxy 設定ファイルのテンプレート)
sample: listen: 0.0.0.0:6379 hash: fnv1a_64 distribution: ketama timeout: 500 redis: true servers: #{SERVERS}
実行
それでは実行してみます。
初回起動時は docker start に失敗し docker run を実施する事になります。
twemproxy1, redis1, redis2 コンテナの実行例
$ /vagrant/tmp/start_twemproxy Error response from daemon: no such id: redis1 Error: failed to start containers: [redis1] bdc12db5・・・ Error response from daemon: no such id: redis2 Error: failed to start containers: [redis2] fd4546fc・・・ Error response from daemon: no such id: twemproxy1 Error: failed to start containers: [twemproxy1] 87bb567e・・・
redis-cli で twemproxy1 へ接続する Docker コンテナを起動し (Redis の Docker イメージを使っています) 、動作確認してみます。
動作確認
$ docker run --rm -it redis redis-cli -h twemproxy1 twemproxy1:6379> set a 123 OK twemproxy1:6379> get a "123"
twemproxy は正常に動作しているようです。
redis1・2 へ直接接続するコンテナをそれぞれ実行し確認すると、値は redis2 の方に設定されていました。
$ docker run --rm -it redis redis-cli -h redis1 redis1:6379> keys * (empty list or set) redis1:6379> exit $ docker run --rm -it redis redis-cli -h redis2 redis2:6379> keys * 1) "a" redis2:6379> exit
備考 - Vagrant 利用時
Vagrant で起動したゲスト OS 上で Docker を実行している場合、Vagrantfile へ以下のような設定を追加すれば provision で各コンテナを起動しホスト OS から twemproxy へ接続できるようになります。
Vagrantfile
・・・ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| ・・・ # twemproxy 用のポートフォワード設定 config.vm.network "forwarded_port", guest: 6379, host: 6379 # twemproxy + redis の起動スクリプト実行 config.vm.provision :shell, :inline => "/vagrant/tmp/start_twemproxy" end
provision 実施例
> vagrant up --provision ・・・ ==> default: Running provisioner: shell... default: Running: inline script ==> default: start redis1 ==> default: start redis2 ==> default: start twemproxy1
ホスト OS からの接続例
> redis-cli 127.0.0.1:6379> get a "123"