バッチでMAKE

2005-05-30

MAKEしよう

MAKE ユーティリティ。 プログラミングに親しんでいる人には当たり前。 知らない人は聞いたこともない。 そんな感じではないでしょうか。 もっとも、最近は IDE1 に組み込まれているので意識する機会は少なくなっているかも知れません。 例えば MS VisualStudio だと、NMAKE と言う MS版の MAKE が入っています。

たいていの開発環境であれば、MAKE が使えるように整備されていると思います。 しかし Chararina のキャラクター開発に使用する「綾織」言語においては、コンパイラは用意されているものの MAKE は用意されていません。 どうやるのかと言うと、人間が判断して個別にコンパイルするのです。 開発サンプルでは、そうなっています。 このためか、コンパイルもれと思しき問題が散見されます。 ここは是非、MAKE を使って貰いたいところです。

MAKEって何?

知らない人がいるといけないので、ごく簡単に MAKE の動作を説明しておきましょう。

ひと言で表現するなら、target と source のタイムスタンプを比較して、target よりも新しい source があれば command を実行します。 この様なルールを "MAKEファイル" に記述しておいて、make コマンドを実行すれば、ルールに従った処理を行ないます。 主たる用途は、ソフト開発のビルド(build)という作業の自動化です。

たとえばソースファイル(*.c*.h)をコンパイルしてオブジェクトファイル(*.obj)を生成する時、target は *.obj、source は *.c およびインクルードされる *.h になります。 command は、source をコンパイルして target を生成するためのコマンドです。 早い話が、「どれか1つでもソースを修正したらコンパイルしてね」って事です。

綾織でも、ソースファイル(*.aya)から実行ファイル(*.exa)を作るという意味では一般のプログラミング言語といっしょです。 また、Chararina では実行ファイルのタイムスタンプを見て更新の有無を判断しますから、内容が同じなのにタイムスタンプが変わるのは良くありません。 MAKE を使えば無用なコンパイルはしないので、この様な問題も解消できます。

バッチファイル(のコマンド拡張)を見ていると、ファイル名をタイムスタンプに展開することができる様です。 バッチパラメータ(%n)の置換と、FOR変数参照の置換です。 これを使えば、MAKE に相当するバッチファイルが作れそうです。

タイムスタンプを得る %~t構文

ファイル名からタイムスタンプを得られなければ話が始まりません。 ここで使うのが %~t 構文です。 バッチパラメータ(%n)の場合は call /? で、FOR変数参照の場合は for /? で説明を参照できます。

まずはバッチパラメータのサンプルです。

test1.bat
@echo off
:check
    if "%1"=="" goto noparam
    if not exist %1 goto nofile
    
    echo %~t1  %1
    goto :EOF

:noparam
    echo 第1パラメータにファイル名を指定してください.
    goto :EOF

:nofile
    echo  %1 が見つかりません.
    goto :EOF

これを実行すると、次の結果が得られます。
%1 がファイル名、%~t1%1 のタイムスタンプを echo しています。

>>test1 c:\msdos.sys
2002/02/21 21:35  c:\msdos.sys

つぎに FOR変数参照のサンプルです。

test2.bat
@echo off
:check
    if "%~1"=="" goto noparam
    
    for %%f in ( %~1 ) do echo %%~tf %%f
    goto :EOF

:noparam
    echo 第1パラメータにファイル名を指定してください.
    echo 複数指定のときは二重引用符(")で囲ってください.
    goto :EOF

実行すると、次の結果が得られます。
%%f がファイル名、%%~tf がタイムスタンプを echo しています。

>>test2 "c:\io.sys c:\msdos.sys c:\config.sys"
2002/02/21 21:35  c:\io.sys
2002/02/21 21:35  c:\msdos.sys
2002/02/21 21:35  c:\config.sys

値を比較する比較演算子

従来から IF コマンドは、ERRORLEVEL の判定2,テキストの一致3,ファイルの存在判定4ができました。 比較は "==" のみで、つまりは等しいか等しくないかという事でした。 これがコマンド拡張によって、大小の比較が可能になりました。 構文は次の通り。 /I スイッチは、大文字と小文字を同一視するオプションです。

IF [/I] 文字列1 比較演算子 文字列2 コマンド

次の比較演算子が用意されています。

演算子意味備考
EQU等しい equal
NEQ等しくないnot equal
LSSより小さいless than
LEQ以下 less than or equal
GTRより大きいgreater than
GEQ以上 greater than or equal

文字列の大小比較は、頭から順に文字コードを比較する事で行ないます。 例えば "2005/05/30" と "2005/05/31" を比較すると、2005/05/3 までは同じ。 最後の 0 と 1 では 1 の方が大きいので、ここで大小が決まります。 つまり「新しい」ものが「大きい」事になります。

make.bat

以上をふまえ、make するバッチファイルです。

このバッチファイルは、ひと組の依存関係の判断を行ないます。 プロジェクト全体の依存関係は別のバッチファイルで記述し、このバッチファイルを CALL で呼び出す形になります。 source と command は空白を含みますから、二重引用符(")で囲みます。 この様な仕様から、フォルダ名/ファイル名に空白を含んではいけません。

用法としては、例えば次のような感じでしょうか。

calling sample
set target=main.exa
set source=main.aya extern.h profile.h
set operate=ayac main.aya
call make.bat %target% "%source%" "%operate%"

make.bat
@echo off
rem -----------------------------------
rem パラメータチェックとヘルプ
rem -----------------------------------
:help
    if not "%~3"=="" goto start
    rem  ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
    echo/
    echo %0 -- usage
    echo/
    echo [書式]
    echo     %0 target source command
    echo/
    echo     target  = targetのファイル名
    echo     source  = sourceのファイル名
    echo               複数の場合は二重引用符で囲み空白で区切る.
    echo     command = sourceからtargetを生成するコマンド
    echo               command引数は二重引用符で囲み空白で区切る.
    echo [説明]
    echo     target と1つ以上の source のタイムスタンプを比較し、source の方が
    echo     新しい物が1つでもあれば command を実行する.
    echo     「1つ以上のファイル名」の区切に空白を使用するため、パス名/ファイル名は
    echo     空白を含んではならない.
    echo/
    echo     command 実行時、環境変数 resultcode に返り値を返す.
    echo     環境変数 verbose が yes の時、target/source の日付を表示する.
    goto :EOF
    
rem -----------------------------------
rem make処理
rem -----------------------------------
:start
    call :updatechk %1 %2
    if not "%update%"=="yes" goto skip
    %~3
    set resultcode=%ERRORLEVEL%
    goto end

:skip
    if "%verbose%"=="yes" echo make  : skipped.
    goto end

rem -----------------------------------
rem target:source 日付チェック
rem -----------------------------------
:updatechk
    if "%verbose%"=="yes" echo/
    if "%verbose%"=="yes" echo target: %~t1  %~1
    
    set update=no
    for %%s in ( %~2 ) do call :chkdate %1 %%s
    goto :EOF

:chkdate
    if "%verbose%"=="yes" echo source: %~t2  %~2
    
    if not exist %2 echo make: source file [%2] not found.
    if "%~t2" GEQ "%~t1" set update=yes
    goto :EOF

rem -----------------------------------
rem 終了
rem -----------------------------------
:end
    set update=

タイムスタンプは秒単位で記録されますが、%~t 構文で参照できるのは分単位までです。 したがって、同じ「分」の間に ソース編集→MAKE→ソース再編集→再MAKE なんて事をすると付いて行けません。(^^; 仕方ないので、日時が同じ場合は「更新あり」と判断しています。

厳密にやりたければ、例えば Archive 属性を組み合わせると良いかも知れません。 ファイル属性は %~a 構文で参照できます。 ファイル属性は ATTRIB コマンドで操作できます。 一般的なテキストエディタは保存操作で Archive 属性を設定します。 参考まで。

  1. IDE
    統合開発環境(Integrated Development Environment)。 エディタ、コンパイラ、デバッガなどのソフト開発に必要なツールをひとつのインターフェースで統合して扱える環境のこと。 本文に出てくる VisualStudio も IDE のひとつ。

  2. ERRORLEVELの判定
    IF [NOT] ERRORLEVEL 番号 コマンド
    最後に実行したコマンドの終了値が番号以上の時、コマンドを実行。 NOT で条件の真偽を反転できます。

  3. テキストの一致
    IF [NOT] 文字列1==文字列2 コマンド
    文字列1と文字列2が一致する時、コマンドを実行。 NOT で条件の真偽を反転できます。

  4. ファイルの存在
    IF [NOT] EXIST ファイル名 コマンド
    ファイルが存在する時、コマンドを実行。 NOT で条件の真偽を反転できます。

Copyright© 1998-2006 Hira