.. 3-build-examples .. index:: single: OMakefile single: OMakeroot single: OSTYPE .. _label3: 3. OMakeビルドサンプル ================================== .. Let's explain the OMake build model a bit more. One issue that dominates this discussion is that OMake is based on global project analysis. That means you define a configuration for the entire project, and you run one instance of omake. この章ではOMakeのビルド体系をさらにもう少し解説します。この議論を決定するただ一つの結論としては、OMakeは全体のプロジェクト解析を元にしているということです。これはあなたがプロジェクト全体の設定を決めて、そしてOMakeのインスタンスを実行することを意味しています。 .. For single-directory projects this doesn't mean much. For multi-directory projects it means a lot. With GNU make, you would usually invoke the make program recursively for each directory in the project. For example, suppose you had a project with some project root directory, containing a directory of sources src, which in turn contains subdirectories lib and main. So your project looks like this nice piece of ASCII art. 一つのディレクトリで構成されたプロジェクトではあまり意味がないかもしれませんが、多数のディレクトリからなるプロジェクトでは大きな意味を持ちます。 ``GNU make`` では、あなたは通常、プロジェクトの各々のディレクトリにおいて ``make`` プログラムを再帰的に呼び出すでしょう。例えば、あなたが現在サブディレクトリ ``lib`` と ``main`` が入っているソースディレクトリ ``src`` を含んでいるプロジェクトをいくつか持っているものとしましょう。具体的には、あなたのプロジェクト構成は以下のアスキーアートのようになります。 :: my_project/ |--> Makefile `--> src/ |---> Makefile |---> lib/ | |---> Makefile | `---> source files... `---> main/ |---> Makefile `---> source files... .. Typically, with GNU make, you would start an instance of make in my_project/; this would in term start an instance of make in the src/ directory; and this would start new instances in lib/ and main/. Basically, you count up the number of Makefiles in the project, and that is the number of instances of make processes that will be created. 一般的に ``GNU make`` では、初めに ``my_project/`` の ``make`` インスタンスを呼び出します。この ``make`` インスタンスは ``src/`` ディレクトリ内の ``make`` インスタンスを呼び出し、そして ``lib/`` と ``main/`` の新しいインスタンスを呼び出します。つまり、 ``GNU make`` では単純に、プロジェクト内の ``Makefile`` の数だけ ``make`` インスタンスが生成されることになります。 .. The number of processes is no big deal with today's machines (sometimes contrary the the author's opinion, we no longer live in the 1970s). The problem with the scheme was that each make process had a separate configuration, and it took a lot of work to make sure that everything was consistent. Furthermore, suppose the programmer runs make in the main/ directory, but the lib/ is out-of-date. In this case, make would happily crank away, perhaps trying to rebuild files in lib/, perhaps just giving up. 大量のプロセスを処理することは今日のコンピュータにとってさほど大きな問題ではありません(ときどき著者の意見に反対する人もいるようですが、私たちはもはや1970年代に住んでいるわけではないのです)。この構造に関する問題としては、各々の ``make`` プロセスの設定が分離されており、そしてそのすべてが調和のとれたものにするには、非常に多くの負担となってしまう点です。さらには、例えばプログラマが ``main/`` ディレクトリで ``make`` プログラムを実行するが、 ``lib/`` はもう時代遅れの代物であった場合を考えてみましょう。この場合、 ``make`` は楽しそうにあちこち曲がりくねった挙句、恐らく ``lib/`` 内のファイルをリビルドしようと奮起して、恐らく諦めることとなるでしょう。 .. With OMake this changes entirely. Well, not entirely. The source structure is quite similar, we merely add some Os to the ASCII art. OMakeではこの構造を抜本的に変更します。とは言っても実際の変更点はそれほどありません。ソース構造は非常に似通っています。私たちは単純にいくつかの"O"を以下のアスキーアートのように加えただけです。 :: my_project/ |--> OMakeroot (or Root.om) |--> OMakefile `--> src/ |---> OMakefile |---> lib/ | |---> OMakefile | `---> source files... `---> main/ |---> OMakefile `---> source files... .. The role of each /OMakefile plays the same role as each /Makefile: it describes how to build the source files in . The OMakefile retains much of syntax and structure of the Makefile, but in most cases it is much simpler. 各々の ``/OMakefile`` の役割は各々の ``/Makefile`` の役割と同様で、どのように ```` のソースファイルをビルドするのかについて設定します。 ``OMakefile`` は ``Makefile`` の構造や構文を保持しておりますが、ほとんどの場合 ``make`` よりも簡単に記述することができます。 .. One minor difference is the presence of the OMakeroot in the project root. The main purpose of this file is to indicate where the project root is in the first place (in case omake is invoked from a subdirectory). The OMakeroot serves as the bootstrap file; omake starts by reading this file first. Otherwise, the syntax and evaluation of OMakeroot is no different from any other OMakefile. 一つ小さな違いがあるとすれば、プロジェクトのルートディレクトリに ``OMakeroot`` が存在している点です。このファイルの主な目的は、第一にプロジェクトのルートディレクトリがどこにあるか示すことです(omakeがサブディレクトリから呼び出されたときのためです)。 ``OMakeroot`` はブートストラップとして働きます。omakeはこのファイルを最初に読み込んで実行されます。それ以外では、 ``OMakeroot`` の構文と機能は他の ``OMakefile`` と全く同様です。 .. The big difference is that OMake performs a global analysis. Here is what happens when omake starts. *大きな* 違いは、OMakeは *グローバルな* 解析を行うという点です。以下はどのようにomakeが動作するのかについて示します。 .. 1. omake locates that OMakeroot file, and reads it. 2. Each OMakefile points to its subdirectory OMakefiles using the .SUBDIRS target. For example, my_project/OMakefile has a rule, .SUBDIRS: src and the my_project/src/OMakefile has a rule, .SUBDIRS: lib main omake uses these rules to read and evaluate every OMakefile in the project. Reading and evaluation is fast. This part of the process is cheap. 3. Now that the entire configuration is read, omake determines which files are out-of-date (using a global analysis), and starts the build process. This may take a while, depending on what exactly needs to be done. 1. omakeは ``OMakeroot`` ファイルがあるディレクトリに移動し、読んでいきます。 2. 各々の ``OMakefile`` は ``.SUBDIRS`` ターゲットを使用して ``OMakefile`` があるサブディレクトリを指し示します。例えば、 ``my_project/OMakefile`` は以下のルールを持っていたとします。 :: .SUBDIRS: src omakeはこれらのルールを読んで、プロジェクト内のすべての ``OMakefile`` を評価します。読み込みや評価は高速に行われるので、このプロセスは早期に終わります。 3. 全体の設定が読まれたら、omakeはどのファイルが使われていないのかを決定し(グローバルな解析を使用します)、ビルド作業を開始します。これはどのファイルのビルドが実際に必要なのかに依存した、ビルド時間がかかります。 .. There are several advantages to this model. First, since analysis is global, it is much easier to ensure that the build configuration is consistent–after all, there is only one configuration. Another benefit is that the build configuration is inherited, and can be re-used, down the hierarchy. Typically, the root OMakefile defines some standard boilerplate and configuration, and this is inherited by subdirectories that tweak and modify it (but do not need to restate it entirely). The disadvantage of course is space, since this is global analysis after all. In practice rarely seems to be a concern; omake takes up much less space than your web browser even on large projects. このモデルではいくつかの利点があります。初めに、解析をグローバルにしたことで、単一のビルド設定が用いられることとなり、ビルド設定を一定にするよう保証することがより簡単になるという点です。別の利点はビルドに関する設定が継承されて、再利用可能となり、さらに階層構造となる点です。概して、ルートの ``OMakefile`` はいくつかの標準的な決まり文句と設定を定義しており、これはサブディレクトリによって継承されて、調整したり、変更することができます(全体を書き換える必要はありません)。この方法の欠点は容量が増大することで、これは結局グローバルな解析を行っているためです。が、実際にはめったに考慮する必要があるようには見えません。OMakeは大きなプロジェクトにおいてでさえ、あなたが使っているウェブブラウザよりもはるかに小さい容量しか使いません。 .. Some notes to the GNU/BSD make user. GNU/BSDのmakeユーザは以下の点に留意してください。 .. * OMakefiles are a lot like Makefiles. The syntax is similar, and there many of the builtin functions are similar. However, the two build systems are not the same. Some evil features (in the authors' opinions) have been dropped in OMake, and some new features have been added. * OMake works the same way on all platforms, including Win32. The standard configuration does the right thing, but if you care about porting your code to multiple platforms, and you use some tricky features, you may need to condition parts of your build config on the $(OSTYPE) variable. * A minor issue is that OMake dependency analysis is based on MD5 file digests. That is, dependencies are based on file contents, not file modification times. Say goodbye to false rebuilds based on spurious timestamp changes and mismatches between local time and fileserver time. * OMakeは ``Makefile`` と同じくらい多くのファイルを作ります。構文は似ており、makeと同様に多くのビルドイン関数が用意されています。しかしながら、この2つのビルドシステムは同じではありません。OMakeではいくつかの酷い機能(これは著者の意見です)が外されており、それに代わって新しい機能が追加されています。 * OMakeはWin32を含んだ、複数のプラットフォーム上で同様に動きます。あなたは複数のプラットフォーム上で動かすためにコードを変更したり、いくつかのトリッキーなテクを使ったり、 ``$(OSTYPE)`` 変数を使ってビルド設定を調節する必要はありません。 * OMakeの依存関係の解析はMD5によるファイルの要約(digest)を元にしています。これはつまり、依存関係の解析はファイルの『修正日時』ではなくファイルの『内容』を元にしていることを表しています。さあ、ローカル日時とファイルサーバの日時から生じるミスマッチや、間違ったタイムスタンプの変更によるビルドミスからおさらばしましょう。 .. index:: single: OMakeroot single: OMakefile .. _label3.1: 3.1 OMakeroot vs. OMakefile ---------------------------------- .. Before we begin with examples, let's ask the first question, “What is the difference between the project root OMakeroot and OMakefile?” A short answer is, there is no difference, but you must have an OMakeroot file (or Root.om file). さて、例を見せる前に、一つ質問をしてみましょう。それは「プロジェクトルートの ``OMakeroot`` と ``OMakefile`` の違いは何か?」というものです。その質問に関する端的な答えは「違いはないが、あなたは必ず ``OMakeroot`` ファイル(あるいは ``Root.om`` ファイル)を作らなければならない」です。 .. However, the normal style is that OMakeroot is boilerplate and is more-or-less the same for all projects. The OMakefile is where you put all your project-specific stuff. しかしながら、通常の範囲で用いるならば ``OMakeroot`` は変更する必要のない決まり文句が並んでいるファイルであり、すべてのプロジェクトの ``OMakeroot`` は多かれ少なかれ同じような内容となるでしょう。 .. To get started, you don't have to do this yourself. In most cases you just perform the following step in your project root directory. OMakeを始めるために、あなたがこのような決まり文句を入力する必要はありません。ほとんどの場合において、あなたは以下の手順をプロジェクトのルートディレクトリで実行するだけです。 .. Run omake --install in your project root. * ``omake --install`` をプロジェクトのルートで実行する。 .. This will create the initial OMakeroot and OMakefile files that you can edit to get started. これで最初の ``OMakeroot`` と ``OMakefile`` が生成されて、編集できるようになりました。 .. index:: single: DefineCommandVars() .. _label3.2: 3.2 Cプロジェクトのサンプル ------------------------------ .. To begin, let's start with a simple example. Let's say that we have a full directory tree, containing the following files. OMakeを始めるために、まずは簡単なサンプルから始めてみましょう。いま私たちは以下のファイルを含んだディレクトリツリーを持っているものとします。 :: my_project/ |--> OMakeroot |--> OMakefile `--> src/ |---> OMakefile |---> lib/ | |---> OMakefile | |---> ouch.c | |---> ouch.h | `---> bandaid.c `---> main/ |---> OMakefile |---> horsefly.c |---> horsefly.h `---> main.c .. Here is an example listing. 以下は ``OMakeroot`` , ``OMakefile`` リストのサンプルです。 :: my_project/OMakeroot: # Cアプリケーションの標準的な設定をインクルード open build/C # コマンドライン上の変数を処理 DefineCommandVars() # このディレクトリのOMakefileをインクルード .SUBDIRS: . my_project/OMakefile: # 標準的なコンパイルオプションを設定 CFLAGS += -g # srcディレクトリをインクルード .SUBDIRS: src my_project/src/OMakefile: # あなたの好きなようにオプションを付け加えます CFLAGS += -O2 # サブディレクトリをインクルード .SUBDIRS: lib main my_project/src/lib/OMakefile: # 静的ライブラリとしてライブラリをビルドします。 # これはUnix/OSX上ではlibbug.aとして、 # Win32上ではlibbug.libとしてビルドされます。 # 引数にはソースファイルの拡張子を入れていないことに注意してください。 StaticCLibrary(libbug, ouch bandaid) my_project/src/main/OMakefile: # いくつかのファイルは../libディレクトリ上の # .hファイルをインクルードしています。 INCLUDES += ../lib # どのライブラリに対してリンクしたいのかを指定 LIBS[] += ../lib/libbug # プログラムをビルドします。 # Win32上ではhorsefly.exe、 # Unix上ではhorseflyとしてビルドされます。 # 最初の引数はアプリケーション名を指定します。 # 二番目の引数はソースファイルの配列を指定します(拡張子は抜いてください)。 # これらの配列はビルドするプログラムの一部となります。 CProgram(horsefly, horsefly main) # デフォルトでこのプログラムをビルドします # (他の引数を指定しないでomakeが実行される場合です)。 # EXE変数はWin32上では.exeとして定義されていますが、 # 他のプラットフォーム上では空の変数です。 .DEFAULT: horsefly$(EXE) .. Most of the configuration here is defined in the file build/C.om (which is part of the OMake distribution). This file takes care of a lot of work, including: ほとんどの設定は ``build/C.om`` (これはOMakeがもつ全機能の一部です)ファイルに定義されています。このファイルはほとんどの作業の面倒を見てくれます。具体的には、 .. * Defining the StaticCLibrary and CProgram functions, which describe the canonical way to build C libraries and programs. * Defining a mechanism for scanning each of the source programs to discover dependencies. That is, it defines .SCANNER rules for C source files. * Cライブラリやプログラムを正当な方法でビルドするための、 ``StaticCLibrary`` と ``CProgram`` 関数を定義しています。 * 依存関係を特定するために各々のソースコードを調べていくメカニズムを定義しています。これはつまり、Cソースファイルのための ``.SCANNER`` ルールを定義していることを意味しています。変数はサブディレクトリにも継承されていき、例えば、 ``src/main/OMakefile`` の ``CFLAGS`` 変数の値は ``"-g -O2"`` となります。 .. index:: single: OCaml single: OCamlLibrary() single: OCamlProgram() .. _label3.3: 3.3 OCamlプロジェクトのサンプル ------------------------------------ .. Let's repeat the example, assuming we are using OCaml instead of C. This time, the directory tree looks like this. 前回のCの代わりにOCamlを使った状態で、簡単なサンプルを作ってみましょう。今回では、ディレクトリツリーは以下のようになります。 :: my_project/ |--> OMakeroot |--> OMakefile `--> src/ |---> OMakefile |---> lib/ | |---> OMakefile | |---> ouch.ml | |---> ouch.mli | `---> bandaid.ml `---> main/ |---> OMakefile |---> horsefly.ml |---> horsefly.mli `---> main.ml .. The listing is only a bit different. ``OMakeroot`` , ``OMakefile`` のリストは前回と少し異なります。 :: my_project/OMakeroot: # OCamlアプリケーションの標準的な設定をインクルード open build/OCaml # コマンドライン上の変数を処理 DefineCommandVars() # このディレクトリのOMakefileをインクルード .SUBDIRS: . my_project/OMakefile: # 標準的なコンパイルオプションを設定 OCAMLFLAGS += -Wa # バイトコードのコンパイラを使いたいですか? # それともネイティブコードのコンパイラを使いたいですか? # 今回は両方とも使ってみましょう。 NATIVE_ENABLED = true BYTE_ENABLED = true # srcディレクトリをインクルード .SUBDIRS: src my_project/src/OMakefile: # サブディレクトリをインクルード .SUBDIRS: lib main my_project/src/lib/OMakefile: # ネイティブコードにおいて、積極的にインライン化を行う OCAMLOPTFLAGS += -inline 10 # 静的ライブラリとしてライブラリをビルドします。 # これはUnix/OSX上ではlibbug.aとして、 # Win32上ではlibbug.libとしてビルドされます。 # 引数にはソースファイルの拡張子を入れていないことに注意してください。 OCamlLibrary(libbug, ouch bandaid) my_project/src/main/OMakefile: # いくつかのファイルは../libディレクトリ上の # インターフェースに依存しています。 OCAMLINCLUDES += ../lib # どのライブラリに対してリンクしたいのかを指定 OCAML_LIBS[] += ../lib/libbug # プログラムをビルドします。 # Win32上ではhorsefly.exe、 # Unix上ではhorseflyとしてビルドされます。 # 最初の引数はアプリケーション名を指定します。 # 二番目の引数はソースファイルの配列を指定します(拡張子は抜いてください)。 # これらの配列はビルドするプログラムの一部となります。 OCamlProgram(horsefly, horsefly main) # デフォルトでこのプログラムをビルドします # (他の引数を指定しないでomakeが実行される場合です)。 # EXE変数はWin32上では.exeとして定義されていますが、 # 他のプラットフォーム上では空の変数です。 .DEFAULT: horsefly$(EXE) .. In this case, most of the configuration here is defined in the file build/OCaml.om. In this particular configuration, files in my_project/src/lib are compiled aggressively with the option -inline 10, but files in my_project/src/lib are compiled normally. この場合、ほとんどの設定は ``build/OCaml.om`` ファイルで定義されています。今回は特に、 ``my_project/src/lib`` ファイルを ``-inline 10`` オプションを用いて積極的にコンパイルするが、 ``my_project/src/lib`` は普通にコンパイルする設定となっています。 .. _label3.4: 3.4 新しい言語を扱う ------------------------ .. The previous two examples seem to be easy enough, but they rely on the OMake standard library (the files build/C and build/OCaml) to do all the work. What happens if we want to write a build configuration for a language that is not already supported in the OMake standard library? 前回の二つのサンプルは十分簡単なように見えますが、これはOMakeの標準ライブラリ( ``build/C`` と ``/build/OCaml`` ファイル)がすべての仕事を行ってしまったためです。もし私たちがOMakeの標準ライブラリでサポートされていないような言語のビルド設定を書こうとしたら、一体どのようにすれば良いのでしょうか? .. For this example, let's suppose we are adopting a new language. The language uses the standard compile/link model, but is not in the OMake standard library. Specifically, let's say we have the following setup. 例えば、私たちはOMakeに新しい言語を適用させているものとしましょう。この言語は標準的なコンパイル/リンクモデルを用いていますが、OMakeの標準ライブラリには存在していません。今回は問題をはっきりさせるため、以下のように動作する手順について考えてみましょう。 .. * Source files are defined in files with a .cat suffix (for Categorical Abstract Terminology). * .cat files are compiled with the catc compiler to produce .woof files (Wicked Object-Oriented Format). * .woof files are linked by the catc compiler with the -c option to produce a .dog executable (Digital Object Group). The catc also defines a -a option to combine several .woof files into a library. * Each .cat can refer to other source files. If a source file a.cat contains a line open b, then a.cat depends on the file b.woof, and a.cat must be recompiled if b.woof changes. The catc function takes a -I option to define a search path for dependencies. * ``.cat`` 拡張子(Categorical Abstract Terminology)では複数あるファイルの中のソースファイルを定義します。 * ``catc`` コンパイラを用いて ``.cat`` ファイルは ``.woof`` (Wicked Object-Oriented Format)ファイルにコンパイルされます。 * ``.woof`` ファイルは実行可能な ``.dog`` (Digital Object Group)ファイルを生成するために、 ``-c`` オプションを用いた ``catc`` コンパイラによってリンクされます。 ``catc`` はまた ``-a`` オプションで、いくつかの ``.woof`` ファイルをライブラリに結合させることができます。 * 各々の ``.cat`` ファイルは他のソースファイルに関連付けることができます。もしソースファイル ``a.cat`` が行 ``open b`` を含んでいたのなら、 ``a.cat`` は ``b.woof`` ファイルに依存しており、 ``a.cat`` は ``b.woof`` が変更されたときに再コンパイルしなければなりません。 ``catc`` 関数は ``-I`` オプションで依存関係を示した行を探索することができます。 .. note:: 訳注: これはcat, woof, dogにもじって作られた仮のソースコードであり、実際に存在しているわけではありません .. To define a build configuration, we have to do three things. ビルド設定を定義するために、私たちは以下の3つの作業を行う必要があります。 .. 1. Define a .SCANNER rule for discovering dependency information for the source files. 2. Define a generic rule for compiling a .cat file to a .woof file. 3. Define a rule (as a function) for linking .woof files to produce a .dog executable. 1. 依存関係の情報をソースファイルから探索するための ``.SCANNER`` ルールを定義する。 2. ``.cat`` ファイルを ``.woof`` ファイルにコンパイルするための普遍的なビルドルールを定義する。 3. 実行可能な ``.dog`` ファイルを生成するために、 ``.woof`` ファイルをリンクするためのルールを一つの関数として定義する。 .. Initially, these definitions will be placed in the project root OMakefile. 初めに、これらの定義はプロジェクトルートの ``OMakefile`` に置くことになります。 .. index:: single: 遅延評価変数 single: mapprefix() .. _label3.4.1: 3.4.1 通常の編集ルールの定義 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. Let's start with part 2, defining a generic compilation rule. We'll define the build rule as an implicit rule. To handle the include path, we'll define a variable CAT_INCLUDES that specifies the include path. This will be an array of directories. To define the options, we'll use a lazy variable (Section 7.5). In case there are any other standard flags, we'll define a CAT_FLAGS variable. さて、パート2に移って、通常の編集ルールを定義していきましょう。今回私たちはビルドルールについて、ソースコードに直接ルールを定義することにします。まずはインクルードパスを扱うために、インクルードパスを指定した変数 ``CAT_INCLUDES`` を定義します。これはディレクトリが格納されている配列です。そしてオプションを定義するために、私たちは『遅延評価変数(lazy variable)(":ref:`label7.5`"を参照)』を使用します。この場合は他にも標準的なフラグが存在していますので、 ``CAT_FLAGS`` 変数も定義することにしましょう。 :: # 私たちは今回CATC変数ををオーバーライドしたいので、catcコマンドを定義します CATC = catc # 通常のフラグは空にします CAT_FLAGS = # インクルードパスの辞書(通常は空です) INCLUDES[] = # インクルードパスによるインクルードオプションを計算します PREFIXED_INCLUDES[] = $`(mapprefix -I, $(INCLUDES)) # 通常の方法で.woofファイルをビルドします %.woof: %.cat $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) -c $< .. note:: 訳注: 今回の場合、 ``$`(mapprefix -I, $(INCLUDES))`` という表記法がまさに遅延評価に相当しています。 ``$`(v)`` は ``v`` を遅延評価するための表記法です。 .. The final part is the build rule itself, where we call the catc compiler with the include path, and the CAT_FLAGS that have been defined. The $< variable represents the source file. 最後の部分では、インクルードパスと前に定義されている ``CAT_FLAGS`` 変数を含んだ、 ``catc`` コンパイラを呼び出すというビルドルールを定義しています。 ``$<`` 変数はソースファイル名に置き換わります。 .. index:: single: addsuffix() .. _label3.4.2: 3.4.2 リンクするためのルールを定義 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. For linking, we'll define another rule describing how to perform linking. Instead of defining an implicit rule, we'll define a function that describes the linking step. The function will take two arguments; the first is the name of the executable (without suffix), and the second is the files to link (also without suffixes). Here is the code fragment. .woofファイルをリンクするために、どのようにビルド作業を行うのかについて記述した、別のルールを記述します。ここでソースに直接ルールを定義するかわりに、リンク作業を記述した関数を定義することにします。この関数は二つの引数をとり、最初の引数は実行ファイル名(拡張子なし)で、二つ目の引数はリンクするためのファイル名(これもまた拡張子はなし)を指定します。以下はコードの断片です。 :: # 副次的なリンクオプション CAT_LINK_FLAGS = # どのように.dogプログラムをビルドするのかを定義した関数 CatProgram(program, files) = # 拡張子を追加 file_names = $(addsuffix .woof, $(files)) prog_name = $(addsuffix .dog, $(files)) # ビルドルール $(prog_name): $(file_names) $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -o $@ $+ # プログラム名を返す value $(prog_name) .. The CAT_LINK_FLAGS variable is defined just in case we want to pass additional flags specific to the link step. Now that this function is defined, whenever we want to define a rule for building a program, we simply call the rule. The previous implicit rule specifies how to compile each source file, and the CatProgram function specifies how to build the executable. ``CAT_LINK_FLAGS`` 変数はちょうど私たちがリンク作業において、追加フラグを渡したいような場合に定義される変数です。さて、新しく関数が定義されましたので、私たちがプログラムをビルドするためのルールを定義したいと思った場合はいつでも、単純にこの関数を呼ぶだけで完了します。前回のような暗黙のルールを記述する場合ですと、どのように各々のソースファイルがコンパイルされるのかについていちいち指定する必要がありましたが、 ``CatProgram`` 関数はどのように実行ファイルをビルドするのか指定するだけで完了します。 .. note:: 訳注: ``$@`` , ``$+`` はそれぞれ『ターゲットの名前』と『依存ファイルのリスト』を表しています。詳細は ":ref:`label8`" を参照してください。 :: # rover.dogプログラムをソースファイルneko.catとchat.catからビルドします。 # rover.dogは普通にコンパイルされます。 .DEFAULT: $(CatProgram rover, neko chat) .. index:: single: .SCANNER single: 暗黙的な依存関係 single: 正規表現 single: find-in-path() single: awk() .. _label3.4.3: 3.4.3 依存関係の解析 ^^^^^^^^^^^^^^^^^^^^^^^^^ .. That's it, almost. The part we left out was automated dependency scanning. This is one of the nicer features of OMake, and one that makes build specifications easier to write and more robust. Strictly speaking, it isn't required, but you definitely want to do it. これでほとんどの作業が終わりましたが、まだ依存関係の解析を自動的に行わせる部分が残っています。これはOMakeの利点の一つであり、さらにロバストで書きやすいビルド設定を作る手助けとなっています。厳密に言うと、この箇所は必要というわけではありませんが、あなたは切実にこの機能を欲しがっていると思います。 .. The mechanism is to define a .SCANNER rule, which is like a normal rule, but it specifies how to compute dependencies, not the target itself. In this case, we want to define a .SCANNER rule of the following form. このメカニズムは通常のルールのように、 ``.SCANNER`` ルールを定義することで得られます。しかし ``.SCANNER`` ルールはどのように依存関係を解析するのかについて指定するのであって、ターゲット自身を指定しているわけではありません。私たちは以下のような形で ``.SCANNER`` ルールを定義したいものとします。 :: .SCANNER: %.woof: %.cat .. This rule specifies that a .woof file may have additional dependencies that can be extracted from the corresponding .cat file by executing the . The result of executing the should be a sequence of dependencies in OMake format, printed to the standard output. このルールでは、「 ``.woof`` ファイルは収集した ``.cat`` ファイルから、 ```` を実行することで展開できる」という、新しい依存関係を追加することを指定しています。 ```` の実行結果は、通常の端末で出力できる、OMake形式の依存関係の配列である必要があります。 .. As we mentioned, each .cat file specifies dependencies on .woof files with an open directive. For example, if the neko.cat file contains a line open chat, then neko.woof depends on chat.woof. In this case, the should print the following line. すでに述べた通り、各々の ``.cat`` ファイルは ``open`` 構文を用いて、 ``.woof`` ファイルに依存していることを指定していたとします。例えば、もし ``neko.cat`` ファイルが行 ``open chat`` コマンドを含んでいたとするならば、 ``neko.woof`` ファイルは ``chat.woof`` ファイルに依存しています。この場合、 ```` は以下の行を出力しなければなりません。 :: neko.woof: chat.woof .. For an analogy that might make this clearer, consider the C programming language, where a .o file is produced by compiling a .c file. If a file foo.c contains a line like #include "fum.h", then foo.c should be recompiled whenever fum.h changes. That is, the file foo.o depends on the file fum.h. In the OMake parlance, this is called an implicit dependency, and the .SCANNER would print a line like the following. この類推は、 ``.o`` ファイルが ``.c`` ファイルをコンパイルすることで生成されるC言語について考えるとより明瞭になります。もしファイル ``foo.c`` が ``#include "fum.h"`` のような行を含んでいたとすると、 ``foo.c`` は ``fum.c`` が変更されたときはいつでも再コンパイルを行う必要があります。これはつまり、ファイル ``foo.o`` がファイル ``fum.h`` に依存していることを表しています。OMakeの用語では、このことを『暗黙的な依存関係(implicit dependency)』と呼んでおり、 ``.SCANNER `` は以下のような行を出力する必要があるでしょう。 :: foo.o: fum.h .. Now, returning to the animal world, to compute the dependencies of neko.woof, we should scan neko.cat, line-by-line, looking for lines of the form open . We could do this by writing a program, but it is easy enough to do it in omake itself. We can use the builtin awk function to scan the source file. One slight complication is that the dependencies depend on the INCLUDE path. We'll use the find-in-path function to find them. Here we go. それでは動物の世界へと戻ってみましょう。 ``neko.woof`` の依存関係を解析するために、私たちは一行一行 ``neko.cat`` ファイルをスキャンして、 ``open `` のような形の構文を含んだ行を探す必要があります。私たちはこのようなプログラムを書かなければなりませんが、OMakeはこのような作業を簡略化することができます。この例ですと、ソースファイルをスキャンする ``awk`` 関数がビルドインで用意されているので、これを使ってみましょう。一つ難しいことがあるとするならば、それは依存関係が ``INCLUDE`` パスに依存していることです。そのためにOMakeでは探索するための ``find-in-path`` 関数を用意しています。それでは以下のように書いてみます。 :: .SCANNER: %.woof: %.cat section # ファイルをスキャン deps[] = awk($<) case $'^open' deps[] += $2 export # 重複を削除し、インクルードパスのファイルを探索する deps = $(find-in-path $(INCLUDES), $(set $(deps))) # 依存関係を出力 println($"$@: $(deps)") .. Let's look at the parts. First, the entire body is defined in a section because we are computing it internally, not as a sequence of shell commands. それでは上のソースコードを見てみましょう。初めに、全体の文はシェルコマンドのシーケンスとして扱われずに内部で計算されるよう、 ``section`` 文の中で定義されています。 .. We use the deps variable to collect all the dependencies. The awk function scans the source file ($<) line-by-line. For lines that match the regular expression ^open (meaning that the line begins with the word open), we add the second word on the line to the deps variable. For example, if the input line is open chat, then we would add the chat string to the deps array. All other lines in the source file are ignored. 今回私たちはすべての依存関係を集めるために、 ``deps`` 変数を用いました。 ``awk`` 関数はソースファイル ``($<)`` を一行一行スキャンしていきます。正規表現 ``^open`` (これはこの行が単語 ``open`` で始まることを表しています)が見つかった場合、 ``deps`` 変数に二番目の単語を追加します。具体的には、入力された行が ``open chat`` であった場合、 ``deps`` 配列に ``chat`` 文字列を追加することになります。ソースファイル中のその他すべての行は無視されます。 .. Next, the $(set $(deps)) expression removes any duplicate values in the deps array (sorting the array alphabetically in the process). The find-in-path function then finds the actual location of each file in the include path. 次に、 ``$(set $(deps))`` 文によって ``deps`` 配列の重複された文字列は削除されます(このとき、アルファベット順に配列をソートします)。 ``find-in-path`` 関数はインクルードパス中の各々のファイルの絶対パスを探索します。 .. The final step is print the result as the string $"$@: $(deps)" The quotations are added to flatten the deps array to a simple string. 最後に、文字列 ``$"$@: $(deps)"`` を結果として出力します。クオーテーションには ``deps`` 配列を単純な文字列に変換した状態で追加されます。 .. _label3.4.4: 3.4.4 まとめ ^^^^^^^^^^^^^^^^^^^ .. To complete the example, let's pull it all together into a single project, much like our previous example. 例がすべて終わったので、この成果を一つのプロジェクトにまとめてみましょう。前回の例は以下のような構成とします。 :: my_project/ |--> OMakeroot |--> OMakefile `--> src/ |---> OMakefile |---> lib/ | |---> OMakefile | |---> neko.cat | `---> chat.cat `---> main/ |---> OMakefile `---> main.cat .. The listing for the entire project is as follows. Here, we also include a function CatLibrary to link several .woof files into a library. この全体のプロジェクトのリストは以下のようになります。私たちはまたライブラリにいくつかの ``.woof`` ファイルをリンクさせるために、 ``CatLibrary`` 関数を定義していることに注意してください。 :: my_project/OMakeroot: # コマンドライン上の変数を処理 DefineCommandVars() # このディレクトリのOMakefileをインクルード .SUBDIRS: . my_project/OMakefile: ######################################################################## # .catファイルをコンパイルするための標準設定 # # 私たちは今回CATC変数ををオーバーライドしたいので、catcコマンドを定義します CATC = catc # 通常のフラグは空にします CAT_FLAGS = # インクルードパスの辞書(通常は空です) INCLUDES[] = # インクルードパスによるインクルードオプションを計算します PREFIXED_INCLUDES[] = $`(mapprefix -I, $(INCLUDES)) # .catファイルの依存関係を解析するスキャナ .SCANNER: %.woof: %.cat section # ファイルをスキャン deps[] = awk($<) case $'^open' deps[] += $2 export # 重複を削除し、インクルードパスのファイルを探索する deps = $(find-in-path $(INCLUDES), $(set $(deps))) # 依存関係を出力 println($"$@: $(deps)") # 通常の方法で.catファイルをコンパイルする %.woof: %.cat $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) -c $< # 副次的なリンクオプション CAT_LINK_FLAGS = # いくつかの.woofファイルを用いてライブラリをビルド CatLibrary(lib, files) = # 拡張子を追加 file_names = $(addsuffix .woof, $(files)) lib_name = $(addsuffix .woof, $(lib)) # ビルドルール $(lib_name): $(file_names) $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -a $@ $+ # プログラム名を返す value $(lib_name) # どのように.dogプログラムをビルドするのかを定義した関数 CatProgram(program, files) = # 拡張子を追加 file_names = $(addsuffix .woof, $(files)) prog_name = $(addsuffix .dog, $(program)) # ビルドルール $(prog_name): $(file_names) $(CATC) $(PREFIXED_INCLUDES) $(CAT_FLAGS) $(CAT_LINK_FLAGS) -o $@ $+ # プログラム名を返す value $(prog_name) ######################################################################## # これで正しくプログラムが動きます # # srcサブディレクトリをインクルード .SUBDIRS: src my_project/src/OMakefile: .SUBDIRS: lib main my_project/src/lib/OMakefile: CatLibrary(cats, neko chat) my_project/src/main/OMakefile: # ../libディレクトリからのインクルードを許可 INCLUDES[] += ../lib # プログラムをビルド .DEFAULT: $(CatProgram main, main ../cats) .. Some notes. The configuration in the project OMakeroot defines the standard configuration, including the dependency scanner, the default rule for compiling source files, and functions for building libraries and programs. 注意点としては、 ``OMakeroot`` では依存関係の解析や、ソースファイルをコンパイルするための通常のルール、ライブラリやプログラムをビルドするいくつかの関数を含んだ、標準的な設定を定義しています。 .. These rules and functions are inherited by subdirectories, so the .SCANNER and build rules are used automatically in each subdirectory, so you don't need to repeat them. これらのルールや関数はサブディレクトリに継承されていますので、 ``.SCANNER`` とビルドルールは自動的に各々のサブディレクトリに使われます。よってあなたはこれらを繰り返し記述する必要はありません。 .. index:: single: OMAKEPATH .. _label3.4.5: 3.4.5 終わりに ^^^^^^^^^^^^^^^^^^ .. At this point we are done, but there are a few things we can consider. これで一通りの作業は終わりましたが、まだ考慮すべき点はいくつか残っています。 .. First, the rules for building cat programs is defined in the project OMakefile. If you had another cat project somewhere, you would need to copy the OMakeroot (and modify it as needed). Instead of that, you should consider moving the configuration to a shared library directory, in a file like Cat.om. That way, instead of copying the code, you could include the shared copy with an OMake command open Cat. The share directory should be added to your OMAKEPATH environment variable to ensure that omake knows how to find it. まず、 ``cat`` プログラムをビルドするためのルールはプロジェクトの ``OMakefile`` に定義しました。もしあなたがどこか別の ``cat`` プロジェクトを持っていたとすると、 ``OMakeroot`` をコピー(そしてもし必要ならば修正も)するかもしれません。その代わりに、あなたは設定ファイルを ``Cat.om`` のように名称を変更して、ライブラリの共有ディレクトリに移すべきです。これで、コードをコピーする代わりに、OMakeコマンド ``open Cat`` を用いてインクルードできるようになります。そのためには、あなたは共有ディレクトリを ``OMAKEPATH`` 環境変数に追加することで、omakeがどこを探せば良いのか分かるようにすべきです。 .. Better yet, if you are happy with your work, consider submitting it as a standard configuration (by sending a request to omake@metaprl.org) so that others can make use of it too. もしあなたが満足する仕事をしたのなら、標準の設定となるようにあなたの設定ファイルを送ることを考えてみてください(``omake@metaprl.org`` 宛にリクエストを送ることで)。他の人の作業を省力化することになります。 .. index:: single: .SUBDIRS single: absname() .. _label3.5: 3.5 階層構造、.SUBDIRSの内容を並列化させる ------------------------------------------- .. Some projects have many subdirectories that all have the same configuration. For instance, suppose you have a project with many subdirectories, each containing a set of images that are to be composed into a web page. Apart from the specific images, the configuration of each file is the same. いくつかのプロジェクトは同一の設定を有した、数多くのディレクトリで構成されているものです。例えば、あなたは現在サブディレクトリが多数あり、その各々がウェブページの画像の集合であるというプロジェクトを持っているものとしましょう。ある特定の画像を除いて、各々のファイルの設定は同一です。 .. To make this more concrete, suppose the project has four subdirectories page1, page2, page3, and page4. Each contains two files image1.jpg and image2.jpg that are part of a web page generated by a program genhtml. この設定をより強固に構築するため、以下のような場合を考えます。まず、このプロジェクトは4つのサブディレクトリ ``page1, page2, page3, page4`` を含んでいるものとします。また、各々のサブディレクトリは二つのファイル ``image1.jpg, image2.jpg`` を含んでおり、それらはプログラム ``genhtml`` によって生成されるウェブページの一部であるとします。 .. Instead of of defining a OMakefile in each directory, we can define it as a body to the .SUBDIRS command. 各々のディレクトリ中に ``OMakefile`` を定義する代わりに、OMakeでは ``.SUBDIRS`` コマンドの内容として定義することができます。 :: .SUBDIRS: page1 page2 page3 page4 index.html: image1.jpg image2jpg genhtml $+ > $@ .. The body of the .SUBDIRS is interpreted exactly as if it were the OMakefile, and it can contain any of the normal statements. The body is evaluated in the subdirectory for each of the subdirectories. We can see this if we add a statement that prints the current directory ($(CWD)). ``.SUBDIRS`` の内容は、まるで ``OMakefile`` が内部にあるかのように正確にふるまい、通常の命令を任意の数だけ実行することができます。 ``.SUBDIRS`` の内容は各々のサブディレクトリの内部で評価されます。実際に何が行われているのかについては、現在のディレクトリ名を出力する命令 ``($(CWD))`` を追加することでより分かりやすくなるでしょう。 :: .SUBDIRS: page1 page2 page3 page4 println($(absname $(CWD))) index.html: image1.jpg image2jpg genhtml $+ > $@ # 出力 /home/jyh/.../page1 /home/jyh/.../page2 /home/jyh/.../page3 /home/jyh/.../page4 .. index:: single: glob() single: ls() .. _label3.5.1: 3.5.1 globパターンを扱う ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. Of course, this specification is quite rigid. In practice, it is likely that each subdirectory will have a different set of images, and all should be included in the web page. One of the easier solutions is to use one of the directory-listing functions, like glob or ls. The glob function takes a shell pattern, and returns an array of file with matching filenames in the current directory. もちろん、上述した指定は非常に強固なものとなっています。実際に、各々のサブディレクトリが異なった画像の集合であり、そのすべてがウェブページに含まれているような場合でも、記述方法は似ています。この問題に対するより簡単な解法の一つは、 ``glob`` や ``ls`` のようなディレクトリのリストを出力する関数を用いることです。 ``glob`` 関数はシェルのパターンを引数に持ち、現在のディレクトリ上でマッチしているファイル名の配列を返す関数です。 :: .SUBDIRS: page1 page2 page3 page4 IMAGES = $(glob *.jpg) index.html: $(IMAGES) genhtml $+ > $@ .. index:: single: include .. _label3.5.2: 3.5.2 簡略化されたサブディレクトリの設定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. Another option is to add a configuration file in each of the subdirectories that defines directory-specific information. For this example, we might define a file BuildInfo.om in each of the subdirectories that defines a list of images in that directory. The .SUBDIRS line is similar, but we include the BuildInfo file. 別の方法は、各々のサブディレクトリ固有の情報を定義した設定ファイルを、それぞれのディレクトリに追加することです。例えば、私たちは現在、各々のサブディレクトリ中に、ディレクトリ内部にある画像のリストを定義した ``BuildInfo.om`` ファイルを設置しているものとします。 ``.SUBDIRS`` の行は似ていますが、 ``BuildInfo`` ファイルをインクルードしている点が異なっています。 :: .SUBDIRS: page1 page2 page3 page4 include BuildInfo # IMAGES変数を定義 index.html: $(IMAGES) genhtml $+ > $@ .. Where we might have the following configurations. それぞれの ``BuildInfo.om`` の内容は以下のようになっています。 :: page1/BuildInfo.om: IMAGES[] = image.jpg page2/BuildInfo.om: IMAGES[] = ../common/header.jpg winlogo.jpg page3/BuildInfo.om: IMAGES[] = ../common/header.jpg unixlogo.jpg daemon.jpg page4/BuildInfo.om: IMAGES[] = fee.jpg fi.jpg foo.jpg fum.jpg .. index:: single: subdirs() single: find() single: dirof() .. _label3.5.3: 3.5.3 サブディレクトリのリストを計算 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. The other hardcoded specification is the list of subdirectories page1, ..., page4. Rather than editing the project OMakefile each time a directory is added, we could compute it (again with glob). 現在、サブディレクトリのリスト ``page1, ... , page4`` は直接指定しています。他のディレクトリが追加される度に ``OMakefile`` を編集するよりも、( ``glob`` を用いて)計算させたほうがはるかに合理的です。 :: .SUBDIRS: $(glob page*) index.html: $(glob *.jpg) genhtml $+ > $@ .. Alternately, the directory structure may be hierarchical. Instead of using glob, we could use the subdirs function, returns each of the directories in a hierarchy. For example, this is the result of evaluating the subdirs function in the omake project root. The P option, passed as the first argument, specifies that the listing is “proper,” it should not include the omake directory itself. ディレクトリ構造が階層的である場合を考えてみましょう。その場合 ``glob`` 関数を用いる代わりに、階層的に各々のディレクトリを返す ``subdirs`` 関数を使います。例えば、以下はOMakeプロジェクトのルート上で ``subdirs`` 関数を評価した結果です。最初の引数として渡した ``P`` オプションでは、OMakeのディレクトリ自身を含んでいない、『適切な』リストを返すことを指定しています。 :: osh> subdirs(P, .) - : .. Using subdirs, our example is now as follows. ``subdirs`` を使用することで、上の例は以下のように表現できます。 :: .SUBDIRS: $(subdirs P, .) index.html: $(glob *.jpg) genhtml $+ > $@ .. In this case, every subdirectory will be included in the project. この場合ですと、プロジェクト中の *すべての* サブディレクトリが含まれることとなります。 .. If we are using the BuildInfo.om option. Instead of including every subdirectory, we could include only those that contain a BuildInfo.om file. For this purpose, we can use the find function, which traverses the directory hierarchy looking for files that match a test expression. In our case, we want to search for files with the name BuildInfo.om. Here is an example call. 私たちが ``BuildInfo.om`` オプションを使用する場合、すべてのサブディレクトリをインクルードする代わりに、 ``BuildInfo.om`` ファイルが含んであるディレクトリのみインクルードしたいと思うでしょう。これを実現するために、私たちはディレクトリを階層的に全走査し、特定の表現にマッチしたファイルを返す ``find`` 関数を使用します。この場合ですと、 ``BuildInfo.om`` という名前のファイルを探したいことになります。以下は ``find`` 関数を呼び出したサンプルです。 :: osh> FILES = $(find . -name BuildInfo.om) - : osh> DIRS = $(dirof $(FILES)) - : .. In this example, there are three BuildInfo.om files, in the doc/html, src, and tests/simple directories. The dirof function returns the directories for each of the files. この例では、プロジェクト中に3つの ``BuildInfo.om`` ファイルが ``doc/html, src, tests/simple`` ディレクトリに存在しています。また、 ``dirof`` 関数は各々のファイルのディレクトリを返します。 .. Returning to our original example, we modify it as follows. 先の例に戻って、私たちは以下のように修正することにしました。 :: .SUBDIRS: $(dirof $(find . -name BuildInfo.om)) include BuildInfo # IMAGES変数を定義 index.html: $(IMAGES) genhtml $+ > $@ .. index:: single: 一時的なディレクトリ single: CREATE_SUBDIRS .. _label3.5.4: 3.5.4 一時的なディレクトリ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. Sometimes, your project may include temporary directories–directories where you place intermediate results. these directories are deleted whenever the project is cleanup up. This means, in particular, that you can't place an OMakefile in a temporary directory, because it will be removed when the directory is removed. 時々、プロジェクトでは中間ファイルを置いておくための一時的なディレクトリが必要となる場合があります。これらの一時ディレクトリはプロジェクトがクリーンアップされたときはいつでも消去されます。これは特に、ディレクトリが消去されたら同ディレクトリの ``OMakefile`` も消去されるために、 ``OMakefile`` を一時的なディレクトリに置くべきではないことを意味しています。 .. Instead, if you need to define a configuration for any of these directories, you will need to define it using a .SUBDIRS body. もしあなたがこれらのディレクトリに関する設定を行いたいのなら、あなたは ``OMakefile`` を設置する代わりに、 ``.SUBDIRS`` の内容について記述する必要があります。 :: section CREATE_SUBDIRS = true .SUBDIRS: tmp # MD5ハッシュを計算 %.digest: %.comments echo $(digest $<) > $@ # ソースファイルからコメントを展開 %.comments: ../src/%.src grep '^#' $< > $@ .DEFAULT: foo.digest .PHONY: clean clean: rm -rf tmp .. In this example, we define the CREATE_SUBDIRS variable as true, so that the tmp directory will be created if it does not exist. The .SUBDIRS body in this example is a bit contrived, but it illustrates the kind of specification you might expect. The clean phony-target indicates that the tmp directory should be removed when the project is cleaned up. 今回の例では、私たちは ``tmp`` ディレクトリが存在しない場合に新しくディレクトリを生成するため、 ``CREATE_SUBDIRS`` 変数を ``true`` に設定しました。 ``.SUBDIRS`` の内容は少々工夫してありますが、だいたいあなたが期待している通りに動作するはずです。 ``clean phony`` ターゲットでは、プロジェクトがクリーンアップされた場合は ``tmp`` ディレクトリが消去されるように指示しています。