*RubyでScalaスクリプトをコンパイル補助するツールを作成する。 [#vd683dab] **目的 [#t615c205] HelloWorld程度ならば、コンパイルツールをつくることは必要ないのでしないのですが、 使えるプログラムというものは、はたくさんのjarをインクルードしたものになりがちです。 標準のコンパイル方法ではクラスパスにJARファイルを追加するのが手作業では、 追加するクラスパスやJARファイルが増えるにしたがって手作業では困難になってきます。 かといって、Mavenをつかってみたんですが無駄な動作をしているし、 Eclipseはコンパイル結果にちょっと癖があって使いづらい。 となれば、自分で簡潔に動作するツールをつくってしまおうとおもったわけです。 **目次 [#k5be5712] #contents *仕様 [#df26398e] **ツール1 タイムスタンプスナップ機能 [#ac9ea477] フォルダ内の.scalaファイルのタイプスタンプのスナップを取得する。 **ツール2 更新ファイル抽出機能 [#g2830135] 既存のタイムスタンプと現在のファイルのタイムスタンプを比較し更新されたファイルを取得しコンパイル可能なバッチファイルを生成する *scalacc.batの例 [#n0b705be] -説明 このバッチファイルはSCALAファイルをコンパイルする際、ライブラリを自動的に取り込んでからコンパイルする BASEが2回出てきているが、どちらも自動的にJarファイルを読み込みたいフォルダである。下記の例のように追加したいフォルダの数だけ繰り返し記述するとよいでしょう。 @echo off setlocal ENABLEDELAYEDEXPANSION set CP=. set BASE=C:\scala\lib for %%f in (%BASE%\*) do set CP=!CP!;%%f set BASE=C:\scala\lib\user for %%f in (%BASE%\*) do set CP=!CP!;%%f SET SCALA_DOC_HOME="C:\scala\doc" ECHO %CP% scalac -classpath %CP% %1 *ツール1 タイムスタンプスナップ機能 [#la5cc9ff] **rubyのディレクトリ関連の処理 [#q1973441] ***ディレクトリ中のファイル一覧を取得する [#ic08cdb2] -コード例 p Dir::entries("/home/take") --結果 [".", "..", "WorkbookScala.scala"] ***ワイルドカードにマッチしたファイル全てに処理を行う [#nda0b89a] -コード例 p Dir::entries("/home/take") --結果 [".", "..", "WorkbookScala.scala"] ---注意点 フォルダの区切り文字は\ではなく、/を使うこと ***ファイルのタイムスタンプを取得する [#ud2674ec] -例 s = File.stat("c:/scala/user/src/WorkbookScala.scala") puts File.mtime(fname).strftime("%Y/%m/%d %H:%M:%S") # 最終更新時刻 -結果 c:/scala/user/src/WorkbookScala.scala 2009/12/05 22:16:11 ***snap_timestamp.rb [#hffed54a] path=ARGV[0].gsub("\\","/") ext="scala" outfile="timestamp.txt" buffer = "" # **は再帰的検索。\0は複数パターンの区切り。.*は隠しファイル取得 Dir.glob("#{path}/**/*\0#{path}/**/.*" + ext).each{|name| puts name buffer = buffer + name + "\n" s = File.stat(name) timestamp = File.mtime(fname).strftime("%Y/%m/%d %H:%M:%S") # 最終更新時刻 puts timestamp buffer = buffer + timestamp + "\n" } # タイムスタンプスナップを上書き更新します。 file = File.open(path + "/" + outfile, "w"); # 書き込む file.write(buffer); file.close -使い方 ruby c:\scala\bin\snap_timestamp.rb "c:\scala\user\src" --指定したフォルダにtimestamp.txtが出力されファイルのタイムスタンプのスナップショットがとれます。 **snap_timestamp_diff.rb [#a7a5adf5] 更新したファイルを出力する #第一引数はフォルダ名 path=ARGV[0].gsub("\\","/") ext=/.*\.scala$/ outfile="timestamp.txt" current={} # **は再帰的検索。\0は複数パターンの区切り。.*は隠しファイル取得 Dir.glob("#{path}/**/*\0#{path}/**/.*" ).each{|name| if ext =~ name s = File.stat(name) current.store(name,File.mtime(name).strftime("%Y/%m/%d %H:%M:%S")) end } # タイムスタンプ保存用ファイル読み込み before={} file = open(path+"/"+outfile) buffer = file.read().split("\n"); file.close #2行を1つの配列にまとめた配列を作成 newItem="" newItem = nil buffer.each{|line| if newItem == nil newItem=line else if ext =~ newItem before.store(newItem,line) end newItem = nil end } #タイムスタンプの比較 更新・新規追加したファイルを出力 current.each{|filepath, timestamp| if timestamp != before.fetch(filepath) puts filepath end } -使い方 ruby snap_timestamp_diff.rb "c:\scala\user\src" > updatelist.txt ***備考 [#w8c99c3c] RubyをWindowsのDOSで動作させるとRubyからバッチファイルを動作させる際に使用する システムのShellが使用している -ruby\lib\ruby\1.8\shell\command-processor.rb のPathの分割処理がバグっているというかcygwinしか想定していないので、 使えません。 動かしてもwindows版rubyにはfork関数が最初から実装されていないっぽい。 つまり複数プロセスの同時実行には対応していない。 *Scala でコマンドの実行結果を文字列として取得 [#m4deff0b] 別途prototype.jarをつくって、簡単に取得できるようにつくっておきました。 -コマンド結果を取得する例 "dir".exeresult 簡潔でしょ? 下記の例は簡潔にかけているんだけど、実際にはInputStreamを閉じないとそのうち不具合がでるので、しっかりとcloseする処理が必要です。 その点上のprototype.jarを使えば、その点が考慮されているばかりか、文字列、配列の基本クラスの拡張までやってくれるので、 開発効率がアップしますよん。 **「java -version」を実行する例 [#ddc5fad5] Runtime r = Runtime.getRuntime(); Process p = r.exec("java -version"); **複数の引数の指定方法 [#dfda1e31] Runtime r = Runtime.getRuntime(); Process p = r.exec(new String[] { "C:\\Program Files\\Java\\j2re1.4.2_13\\bin\\java", "-version" }); **「echo zzz」を実行する例 [#sc8628a2] Runtime r = Runtime.getRuntime(); Process p = r.exec("cmd /c echo zzz"); // Process p = r.exec(new String[]{ "cmd", "/c", "echo", "zzz" }); **環境変数の指定 [#jab4f667] String[] env = new String[2]; env[0] = "TEST=サンプル"; env[1] = "PATH=" + System.getProperty("java.library.path"); Runtime r = Runtime.getRuntime(); Process p = r.exec("cmd /c echo %TEST%", env); ***参考URL 外部プロセス起動 [#qeb4d144] http://www.ne.jp/asahi/hishidama/home/tech/java/process.html#Runtime ***Javaで,子プロセスを使うときの注意点 [#f56f123d] http://isolinear.info/wiki/index.php/Java/Tips/Java%A4%C7%A1%A4%BB%D2%A5%D7%A5%ED%A5%BB%A5%B9%A4%F2%BB%C8%A4%A6%A4%C8%A4%AD%A4%CE%C3%ED%B0%D5%C5%C0.html *バッチファイルからだと連続して2つめのコンパイル以降が無視される。 [#z9001eca] バッチファイルからだと、どういうわけかわかりませんが、1つめのコンパイルが終わるとバッチファイルが終了してしまいます。 startコマンドをつかうと、非同期にコンパイルされてしまい、コンパイルが正しくできない場合があります。 そこで、対策として、SCALAでコンパイルごとにプロセスをたて、なおかつ同期をとりながらコンパイルするコードを作成しました。 -scala import prototype.Prototype._ args(0).read{line :String => ("scalacc " + line).exe } **説明 [#z100501f] --prototype.Prototypeは自作のprototype.jarファイルをクラスパスにいれることで使える拡張です。 --readは文字列をファイル名と見なし、該当するファイルから1行ずつブロック変数として取得します。 --exe は文字列をコマンドラインの文字列とみなし実行し結果を出力します。 *バッチファイルの例 [#i2203c7c] copy "C:\Documents and Settings\Administrator\workspace\prjPrototype\src\prototype\Prototype.scala" . :CHECK_UPDATE ruby "c:\scala\bin\snap_timestamp_diff.rb" . >updatelist.txt scalac "c:\scala\bin\scalacc.scala" -説明 コンパイルしたい対象をコピーしてカレントディレクトリにもってきています。 更新したファイルがないか確かめています。 **余談 コマンドラインの実行結果を環境変数に格納する方法での怪しい現象 [#j3f16ff5] コマンドラインの実行結果を環境変数に格納する方法に FOR /F %t IN ('TIME /T') DO SET NOWTIME=%t というサンプルがある。これをDOS窓から実行すると,DOS窓が閉じてしまうのだ。ところが、DOS窓からさらに cmd と打ち込んで、もう一段DOS窓をかませて実行すると、DOS窓は閉じなくなるのだ。不思議だ。 バッチファイル実行は、どうなるんだろうか。。。 -結果 コマンドの構文が誤っています。 と出てきました。OSはWindowsXPです。 なんでやねん。って言いたいです。 色々サイトをみていたときに、%tではなく%%t表記の表現があることを思い出してバッチファイルを下記のように修正してみました。 FOR /F %%t IN ('TIME /T') DO SET NOWTIME=%%t -結果 画面が閉じる ただし cmd でコマンド画面を1段かませた場合には画面は閉じない -考察 --FOR文をつかうとDOSの起動オプション/cと同様の状態になり、コマンドが完了するとDOS画面を閉じる処理を走らせてしまう。 -対策 --FOR文はBATファイルからの起動専用と認識し、%%t表記を使い、さらにそのバッチファイルの起動には cmd /c バッチファイル という起動方法が良い