2013年1月18日金曜日

make のお話

山本です。

今回は古くて旧い make を取り上げてみます。
昔と違って、今ならビルド自動化ツールの選択肢として rake も ant も grunt も xbuild もありますが、それでも敢えて make です。

make とは

make とは、あらかじめ Makefile に生成物 とその材料との間の依存関係を書いておくことで、生成物を得るのに必要十分なプロセスを自動的に行うツールです。
特に、中間生成物が存在するケースで再生成漏れを防ぐのに有用です。
make の特徴
  • 基本的に、何かを生成するために外部のプログラム(コンパイラ etc.)を起動する。シェルスクリプトに思想が似ています。
  • 自力でソースを解析してまで依存関係を判断することはない。ただし、外部ツール(mkdep 等)の支援のもとで依存関係の記述を自動化させる事例はあります。
make の種類
make には、主に2種類のメジャーな方言があります。 基本的な記述方法は一緒ですが、条件分岐やinclude等の特殊な構文や、変数展開時に使えるオプション指定に違いがみられます。
  • GNU make
  • BSD make (a.k.a. pmake)

make 基礎篇

依存関係(ルール)
ターゲット:依存物
        レシピ
ターゲット、依存物ともに複数個指定ができます。(その場合はスペース区切り)
レシピは行頭にタブ文字。複数行指定ができます。行頭にタブ文字以外が出現すると、そこでルールの記述が終わります。
 ターゲット以外はいずれも省略可能。
レシピを省略した場合は、そのターゲットが指定された依存物に依存している事実だけが記述されたことになり、 具体的なレシピは他の記述か拡張子をベースにしたデフォルト(推論規則)になります。
変数展開
$V
もしくは
${VARIABLE}
ただし、{}で括らない書式は、変数名が1文字な場合に限られます。
変数定義
普通に代入する
VAR = the value to be set
 行頭に空白は書けません。そのかわり、等号の前後に空白文字を入れることは可能です。
なお、変数に別の変数の値を代入することもできますが、代入された変数の値が実際に展開されるのは後になります。したがって、
A=$B
B=hoge
dummy:
        echo $A
は、 hoge が出力されます。
その場で変数展開したものを代入する
前述した挙動が望ましくなく、その時点での値を代入したい場合は、
A:=$B
とします。
未定義な場合に代入する
A ?= some default value
とすると、コマンドライン(後述)や環境変数などで値が与えられていない場合に限って代入が行われます。
追加する
 A+= append to A
とすると、変数の値に追加することができます。例えば、
A= Unique
A+= Vision
とした場合、 $A は Unique Vision と展開されます。(Unique と Vision の間に空白が入っている点に注目)
コマンドラインから
make VAR=value
のように、コマンドラインから変数を指定することもできます。

make 応用篇

記述量を減らそう!
自動変数
make が勝手に設定してくれる変数がいくつかあります。
主なものを挙げてみます。
  • $< 依存物
  • $@ 生成物
注意点としては、これらの変数はレシピ実行中に今まさに生成しようとしているターゲットに関する値として展開されます。
つまり、中間生成物の生成レシピを実行している最中では、それぞれ中間生成物の依存物と中間生成物そのものの名前に展開されるということです。
パターンによるルール
all: foo.css bar.css
foo.css: foo.styl
        @stylus foo.styl
bar.css: bar.styl
        @stylus bar.styl
と書く代わりに、
all: foo.css bar.css
%.css: %.styl
        @stylus $<
と書けます。($< は依存物を表す自動変数)
変数の展開時パターン置換
SRCS= foo.styl bar.styl
と書いてあるとすると、
${SRCS:%.styl=%.css}

foo.css bar.css
に展開されます。

共通部分の別ファイルへの切り出し
変数置換を駆使するなどして、ルールから具体的な値が排除できたら、ルールを別ファイルに切り出して、本体 Makefile から include させることで Makefile をまたいだ共通化が図れます。
all: foo.css bar.css
%.css: %.styl
        @stylus $<
の例を取り上げると、
  • Makefile:
    STYLES= foo.styl bar.styl
    include stylus-common.mk
  • stylus-common.mk:
    all: ${STYLES:%.styl=%.css}
    %.css: %.styl
            @stylus $<
のような2ファイルに分割できます。(GNU make の場合)
この例ではあまりありがたみはありませんが、もう少し複雑なルール集合を、いろいろな Makefile で使いたくなってきた時に威力を発揮します。
(なお、 BSD make 系では、 include filename.mk の代わりに .include "filename.mk" などとします)
風変わりなレシピ
常に失敗するレシピ
例えば、仕様書が更新されたらテストを更新しなければいけないことを明確化することを考えましょう。
具体的にテストをコマンドで更新することは一般的には不可能ですが、更新の必要があることを開発者が知る必要もあるわけです。
その場合、
testcase.js: specification.doc
        @echo 仕様が更新されています。テストケースも更新しましょう。 >&2
        @false
 のように、最後に false を実行させることで無理やり失敗させる手が有効です。

2013年1月9日水曜日

Backbone.jsとYiiフレームワークの連携


高瀬です。

Backbone.jsとYiiフレームワークを使用してアプリケーションを開発する場合の、ソースコードの配置や書き方について検討してみる。

サンプルアプリケーションのソースコードはこちらを参照。


サンプルアプリケーションの概要

Webブラウザ上でデータベースのER図が書けないものかと思い立ったので、それをテーマにアプリケーションを作成する。

アプリケーションはプロジェクトを1つの単位として、その中にスキーマやテーブルの定義を書いていく、という形にする。ここで、プロジェクトにリビジョンを付けて、編集履歴を残せるようにしたい。リビジョンはプロジェクトの作成直後を1とし、DB管理者のレビューを経て確定。その後さらに編集を開始する時に2に更新する、というサイクルを想定する。

画面構成や機能はまだまだ未検討で、今回はひとまずプロジェクトの作成、編集、削除ができるところまでを実装する。

画面は以下のとおり。まず、作成済みプロジェクトの一覧が画面の左上に表示される。


プロジェクト名にカーソルを重ねると、そのプロジェクト名の右側にメニューが表示される。メニュー項目は「編集」「プロパティ」「削除」とあり、それぞれER図の編集画面への遷移、プロジェクト名などのプロパティ編集、プロジェクトの削除、を行う。「レビュー」というメニュー項目を追加することを考えているが、今回は未実装。


プロジェクト一覧にカーソルを重ねた時、メニューと同時に、一覧の下側に「新しいプロジェクト」というリンクが表示される。「新しいプロジェクト」をクリックすると、画面の中央に「プロジェクトの作成」というダイアログを表示する。ダイアログにプロジェクト名を入力し、「作成」をクリックするとプロジェクトが作成される。作成されたプロジェクトは自動的にプロジェクト一覧に表示される。



プロジェクトのメニュー項目「編集」は現在未実装。「プロパティ」は「プロジェクトの作成」とほぼ同様のダイアログが表示され、プロジェクト名を変更できる。プロジェクト名が変更されると自動的にプロジェクト一覧に反映される。

メニュー項目「削除」をクリックすると、画面中央に確認メッセージが表示される。「OK」をクリックするとプロジェクトの削除が行われる。削除されたプロジェクトは自動的にプロジェクト一覧から消える。


画面には表示されないが、プロジェクトを作成すると、自動的にリビジョン1を表すデータが作成される。プロジェクトを削除するとリビジョンのデータがすべて削除される。現時点ではリビジョンは1固定。

開発するアプリケーションの名前はtprefix(ティー・プレフィックス)とする。以下、この名前で記述する。

tprefixは以下のデータベース、開発言語、フレームワークで開発する。

構成 DB/言語 フレームワーク
データベース PostgreSQL 9.x -
サーバ側 PHP 5.4 Yii
ブラウザ側 JavaScript Backbone.js

なお、JavaScriptのHTMLテンプレートエンジンとして、Handlebars.jsを使用する。



データベース

tprefixが記憶、管理するデータにはプロジェクトやリビジョン、スキーマ、テーブル、カラムなどが考えられる。その他にもユーザやカラム定義などを予定しているが、今回は「プロジェクト」「プロジェクトリビジョン」のテーブルのみを用意する。その他のテーブルの内容は検討中。




プロジェクトテーブルにはプロジェクトの名前を登録する。
プロジェクトリビジョンテーブルは、プロジェクトのリビジョンを登録する。今回はリビジョンIDは前述のとおり1固定とする。説明内容、レビュー日時、レビュー結果内容は未使用。

どちらのテーブルにもある「削除日時」は、レコードの削除が行われた日時を登録する。すなわち、「レコードの削除」操作はテーブルからレコードを消してしまうのではなく、「削除日時」を登録することにより、削除済みであるとみなすようにする。



ソースコードの配置

ソースコードはYiiフレームワークのディレクトリ構造を元に配置した。

サーバ側(PHP) ブラウザ側(JavaScript)

上図において、tprefixがルートを表す。protected以下にサーバ側のPHP、js以下にブラウザ側のJavaScriptが配置されている。

protected以下はMVCに従ってmodels、controllers、viewsにそれぞれソースコードを作成。これに対して、js以下はBackbone.jsのクラスに従って、models、collections、viewsを作成。

tprefixでプロジェクト一覧が表示されるページとなるのは、projectコントローラのindexアクション。URLでは http://(サーバ名)/index.php?r=project/index となる。



サーバ側

サーバ側はMVCフレームワークに従って開発する。

■モデル

モデルでは、今回はプロジェクトとプロジェクトのリビジョンを扱うので、Project.phpProjectRevision.phpを作成。これらは特にBackbone.jsとの関連は少ない。データベースへのアクセスにはCActiveRecordを使用。

Project.phpのsave()メソッドでトランザクションを使用しているが、トランザクション内の処理はtry/catchで例外を捕らえ、例外発生時にrollbackされるようにするべき。

■コントローラ

コントローラは、2種類のアクションを用意する。1つはブラウザ側で必要になるJavaScript、CSS、HTMLテンプレートを用意するアクション。もう一つは、ブラウザ側からのAjaxでの要求に対応する応答を返すアクション。

ProjectControllerでは、JavaScriptなどを用意するアクションはIndexAction.phpが担当。JavaScript、CSSのファイルを配列で定義する。このソースコード内にあるregisterPackage()メソッドはController.phpに定義しているメソッドで、こちらの記事を参照。

HTMLテンプレートも配列で定義する。これらはYiiのパーシャルビューとして、Controller::renderPartial()で描画させる。

よって、このproject/indexアクションを呼び出すと、表示する内容が何もない、YiiのレイアウトのみのHTMLが生成される。生成されたHTMLのheadタグ内には配列で指定したJavaScriptとCSSのファイルが含まれ、bodyタグ内にはHTMLテンプレートが含まれる。レイアウト部分以外の画面の描画はJavaScriptで行う。

※レイアウト部分と言うのは、前述「サンプルアプリケーションの概要」の図にある、「My Web Application」や「Home About ...」などが表示されている部分。それ以外の部分というのは、プロジェクトの一覧が表示されている部分。

もう一つのAjax要求に対応する応答を返すアクションでは、ListAction.phpのようにモデルを使ってデータを抽出・登録し、結果をJSON形式で出力する。

JavaScriptのファイルをYii::app()->clientScript->registerPackage()でHTMLに反映させるようにしており、この場合headタグ内にscriptタグが列挙される。しかし、headタグ内でJavaScriptを読み込むと、ページ全体の描画が始まるまでに時間がかかるため、ページの表示が遅い。bodyの終了タグの直前でJavaScriptが読み込まれるようにするとページの表示が速くなる。表示を速くするためにはもう少し工夫が必要。

■ビュー

ビューではHTMLテンプレートのみを定義した。このテンプレートはHandlebars.jsによって実際のDOMに変換される。Handlebars.jsのテンプレートはscriptタグ内に記述するようになっているので、それぞれのパーシャルビューは_project_delete_dialog.phpのようにscriptタグで囲まれた内容となっている。

■例外の出力

Yiiではtry/catchによってハンドリングされなかった例外はsiteコントローラのerrorアクションで処理される。ここで、SiteController.phpのとおり、もともとこのアクションは要求がAjaxであればエラー内容をechoで出力し、そうでなければrender()で描画するようになっている。この仕組みをそのまま使ってみようと思う。

Project.phpのprepare()メソッドで、更新または削除対象のレコードが見つからなかった場合は例外とした。この例外はsite/errorアクションに到達するが、NotFoundException.php内で表示するべきエラーメッセージを定めており、要求がAjaxだった場合はJSON形式、そうでない場合は文字列のみとしている。

これにより、JavaScriptからの要求は通常Ajaxで行うようにするので、エラーが発生した場合でもJSON形式の応答がある、という前提でブラウザ側の開発ができるはず。

JavaScriptからの要求がすべてAjaxであるとして開発をするのであれば、NotFoundExceptionクラス内でJSON形式か文字列かを決めるのではなく、site/errorアクション内でやってしまった方がよいだろう。



ブラウザ側

■名前空間とプログラムの開始

Backbone.jsやUnderscore.jsのファイルはmain.phpに記述しており、Ajax用ではないアクションで共通に読み込まれるようにしている。app.jsというファイルも共通としており、これは名前空間の定義用。

プログラムの開始は、プロジェクト一覧を表示する画面の場合、startupディレクトリ内にあるs_project.jsとなる。Webアプリケーション全体をシングルページとして開発する場合はわざわざstartupというサブディレクトリにこのファイルを配置する必要はないが、実験として、プロジェクト一覧の画面と、各プロジェクト内のER図を編集する画面を別のページとしてみようと考えており、起点となるソースコードを配置するディレクトリを用意した。

■コレクション

c_Project.jsでプロジェクトの一覧を扱う。urlに、サーバ側のproject/listアクションを指定。これにより、fetch()メソッドを実行するとプロジェクト一覧が取得される。一覧取得が完了すると自動的にresetイベントが発生するので、v_ProjectList.jsでハンドリングし、画面に描画する。

■モデル

今回扱うモデルはプロジェクトのみ。m_Project.jsで定義している。ここで、困ったことが2点あった。

1点目は、プロジェクトを新規作成する時、サーバ側のproject/saveアクションへプロジェクト名をPOSTしたいのだが、Backbone.Modelクラスのデフォルトのsave()メソッドを呼び出すと、サーバ側(PHP)の$_POST変数に送信しているはずの値が入らない。仕方がないので、jQueryのpost()で送信している。

$_POSTで受け取れないのは、Backbone.sync()により送信されるデータのエンコードがデフォルトではapplication/jsonになっており、これをPHPが受け取れていないため。Backbone.emulateJSON = true を指定することにより、$_POSTで受け取れるようになるかも、とのこと。

2点目は、Yiiとの連携とは関係ないが、プロジェクトの新規作成・編集・削除を行った際に自動的にプロジェクト一覧を再描画したいが、モデルが更新されたというイベントをどのようにしてコレクションに伝えればいいのかが分からなかった。無理やりな感じだが、プロジェクト一覧を描画しているビューのelが参照している要素に対してchangeイベントを発火して伝えている。

と、これを書きながら思ったが、もしかしてコレクションがモデルのaddイベントをハンドリングしていればいいだけの話だろうか。うむむ、気づくのが遅かった。

オブジェクト同士に関連がなく、ハンドリングの指定ができないような場合のために、前述のapp.jsで定義しているappオブジェクトを、Backbone.Eventの派生クラスにしておき、appオブジェクトにtrigger()させる、という方法もある。


■ビュー

ビューはv_ProjectItem.jsのようにモデルやコレクションに対応するものと、v_ProjectEditDialog.jsのようにダイアログなどの表示する部品に対応するものを作成した。ダイアログなどはサーバ側のビューで定義されているHTMLテンプレートから生成する。

ビュー同士や、別のビューが持っているモデルへのイベントの伝達方法が分からず、至らない点が多くあるので、追々直していくことにする。



おわり

ER図の編集画面くらいまでは作りたかったのに、勉強不足でいろいろ苦戦してようやくここまで、といったところ。

バリデーションをしていなかったり、エラー処理が不十分だったりと、実用レベルではない部分もあるので、そのあたりも身に付けていきたい。

tprefixの開発はまだまだ続く。はず。