プログラミングユーティリティ

ソフトウェアプロジェクトの管理

make は、ソフトウェアプロジェクトがプログラムとライブラリのシステムで構成されている場合に特に便利です。入れ子にした make コマンドを使用すると、ディレクトリ階層全体に渡って、オブジェクトファイル、実行可能ファイル、ライブラリを管理できます。make を SCCS と合わせて使用すると、ソースを一貫して管理し、そのソースから整合性のとれたプログラムを構築することができます。必要に応じて、ディレクトリ階層を複製して他のプログラマに提供し、複数のプログラマが並行して開発およびテストを同時に行うことができます (ただし、考慮すべき点はあります)。

また、make を使用して、プロジェクト全体を構築し、完成したさまざまなモジュールを統合して配布するために他のファイルシステムにコピーすることができます。

プロジェクトを整理して管理を簡単にする

前述のように、プロジェクトを整理するには、プロジェクトを主な構成要素ごとにいくつかのディレクトリに分割するのが適しています。このようにして分割したプロジェクトは、通常は 1 つのファイルシステム上またはディレクトリ階層内で管理します。ヘッダーファイル、ライブラリ、プログラムはそれぞれ別のサブディレクトリに置きます。参照マニュアルなどのドキュメントも、別のサブディレクトリに置いて管理します。

次の図に示すように、プロジェクトが 1 つの実行可能プログラム、1 つのユーザー定義ライブラリ、ライブラリのルーチン用の 1 組のヘッダー、複数のマニュアルで構成されているものとします。

Graphic

各サブディレクトリにあるメークファイルは、これまでの節で説明したものを使用できますが、プロジェクト全体を管理するためのメークファイルがさらに必要です。プロジェクトのルートディレクトリにあるメークファイルには、プロジェクトを 1 つの構成要素として一括管理するためのターゲットエントリを指定します。

プロジェクトが大きくなると、簡単に使用できる整合性のあるメークファイルが必要になります。マクロおよびターゲット名は、どのメークファイルでも意味が同じである必要があります。出力形式を決定する条件付きマクロ定義およびコンパイルオプションは、プロジェクト全体で一貫している必要があります。

可能であれば、テンプレートを使用してメークファイルを記述します。テンプレートを使用して、プロジェクトがどのように構築されるかを監視します。モジュール用のディレクトリを作成し、適切なメークファイルをそのディレクトリにコピーして、数行を編集するだけで、新しい種類のモジュールを追加することができます。また、ルートのメークファイルで構築する新しいモジュールを追加する必要があります。

デフォルトのメークファイルなどで使用されるマクロおよびターゲットの名前の付け方は、プロジェクト全体で統一する必要があります。ニーモニック名とは、ターゲットの機能やマクロの値を正確に覚えていなくても、その名前から機能または値の種類を判断することができるような名前です。ニーモニックは、メークファイルを解読する際にも便利です。

メークファイルをインクルードする

一貫したコンパイル環境を保ちながら、メークファイルを簡潔にする方法として、以下のように make を使用します。

	include filename 

この include 指令は、filename に指定した名前のメークファイルの内容を読み取ります。指定した名前のファイルがない場合は、make/etc/default でその名前のファイルを検査します。

たとえば、以下のようにターゲットエントリをインクルードすると、各メークファイルごとに troff ソースを処理するパターンマッチングの規則を重複して記述する必要はありません。

SOURCES= doc.ms spec.ms 
...
clean: $(SOURCES) 
include ../pm.rules.mk

この例では、make../pm.rules.mk ファイルの内容を読み取ります。

# pm.rules.mk 
# 
# パターンマッチング規則用の "include" makefile
# 

%.tr: %.ms 
         	troff -t -ms $< > $@ 
%.nr: %.ms 
         	nroff -ms $< > $@

完成したプログラムおよびライブラリをインストールする

外部でのテストまたは通常の使用を目的として、プログラムをリリースする際には、make を使用してプログラムをインストールできます。インストールを行うための新しいターゲットおよびマクロ定義は、以下のように簡単に追加できます。

DESTDIR= /proto/project/bin 

install: functions 
        	-mkdir $(DESTDIR) 
        	cp functions $(DESTDIR)

ライブラリまたはヘッダーをインストールする際にも、同様のターゲットを使用できます。

プロジェクト全体の構築

ある時点のソースおよびそれによって構築されるオブジェクトファイルを、プロジェクト開発中に何度か保存する必要があります。プロジェクト全体を構築するには、make を各サブディレクトリごとに実行して、各モジュールを構築し、それをインストールします。以下の例は、入れ子にした make コマンドを使用して単純なプロジェクトを構築する方法を示しています。

プロジェクトが binlib という 2 つの異なるサブディレクトリに置かれていて、両方のサブディレクトリで、make を使用して、プロジェクトのデバッグ、テスト、インストールを行うものとします。

最初に、プロジェクトのルートディレクトリ (プロジェクトの一番上のディレクトリ) に、以下のようなメークファイルを置きます。

# プロジェクトのルート makefile

TARGETS= debug test install 
SUBDIRS= bin lib

all: $(TARGETS)
$(TARGETS):
        	@for i in $(SUBDIRS) ; ¥
        	do ¥
               cd $$i ; ¥
               echo "Current directory:  $$i" ;¥
               $(MAKE) $@ ; ¥
               cd .. ; ¥
        	done

次に、各サブディレクトリ (この場合は bin) に、以下の一般的な形式のメークファイルを作成します。

#サブディレクトリ中の makefile
debug:
        	@echo "			Building debug target"
        	@echo
test:
        	@echo "			Building test target"
        	@echo
install:
        	@echo "			Building install target"
        	@echo

プロジェクトのルートディレクトリで make と入力すると、以下のように出力されます。

$ make
Current directory:  bin
         	Building debugging target

Current directory:  lib
          Building debugging target

Current directory:  bin
         	Building testing target

Current directory:  lib
         	Building testing target

Current directory:  bin
         	Building install target

Current directory:  lib
         	Building install target
$

再帰的なメークファイルを使用してディレクトリ階層を管理する

プロジェクトの階層を拡張する場合は、各中間ディレクトリのメークファイルが、ターゲットファイルを生成するだけでなく、その各メークファイルのサブディレクトリ用に入れ子にした make コマンドを呼び出す必要があります。

現在のディレクトリのファイルは、サブディレクトリのファイルに依存する場合があります。その場合は、ターゲットエントリはサブディレクトリにある対応するターゲットエントリに依存している必要があります。

各サブディレクトリ用の入れ子にした make コマンドは、ローカルディレクトリのコマンドの前に実行する必要があります。入れ子にしたコマンドとローカルディレクトリのコマンドに対して別々にエントリを作成すると、コマンドを正しい順序で実行できます。このような新しいターゲットを、元のターゲットの依存関係リストに追加すると、オリジナルのターゲットの動作と新しいターゲットの動作の両方が実行されます。

再帰的なターゲットの管理

ローカルディレクトリとサブディレクトリの両方で同一の動作を行うターゲットを、再帰的なターゲットと呼びます。


注 -

厳密には、ターゲット自身の名前を引数として指定して make を呼び出すターゲットは、すべて再帰的です。ただしここでは、入れ子になった動作とローカルの動作の両方を持つターゲットのみを「再帰的なターゲット」と呼びます。入れ子にした動作のみを持つターゲットは、「入れ子にしたターゲット」と呼びます。


再帰的なターゲットを含むメークファイルは、再帰的なメークファイルと呼びます。

以下の例の all の場合は、入れ子にした依存関係は NESTED_TARGETS、ローカルの依存関係は LOCAL_TARGETS です。

NESTED_TARGETS=  debug test install 
SUBDIRS= bin lib
LOCAL_TARGETS= functions 

all: $(NESTED_TARGETS) $(LOCAL_TARGETS) 

$(NESTED_TARGETS): 
        	@ for i in $(SUBDIRS) ; ¥
        	do ¥
               echo "Current directory:  $$i" ;¥
               cd $$i ; ¥
               $(MAKE) $@ ; ¥
               cd .. ; ¥
        	done

$(LOCAL_TARGETS):
        	@ echo "Building $@ in local directory."
       (local directory commands)

入れ子にした make も、最下位の階層にある場合を除き、再帰的である必要があります。末端のディレクトリ (サブディレクトリを持たないディレクトリ) のメークファイルでは、ローカルターゲットのみを構築します。

大規模なライブラリを分割して管理する

大規模なライブラリは、複数の補助ライブラリに分割し、完全なパッケージを構築する際に make を使用して 1 つに結合すると、管理が簡単になる場合があります。ar を使用してライブラリを直接結合することはできませんが、以下の例に示すように、まず各補助ライブラリのメンバーファイルを取り出し、次に別の手順でそれらのファイルをアーカイブにまとめることができます。

$ ar xv libx.a 
x - x1.o 
x - x2.o 
x - x3.o 
$ ar xv liby.a 
x - y1.o 
x - y2.o 
$ ar rv libz.a *.o 
a - x1.o 
a - x2.o 
a - x3.o 
a - y1.o 
a - y2.o 
ar: creating libz.a

補助ライブラリは、そのライブラリを構築する (オブジェクト) ファイルとともに、そのディレクトリにあるメークファイルを使用して管理します。完成ライブラリ用のメークファイルは、各補助ライブラリアーカイブへのシンボリックリンクを作成し、アーカイブの内容を一時的なサブディレクトリに展開し、生成されたファイルをアーカイブして完全なパッケージを作成します。

次の例は、補助ライブラリを更新し、展開したファイルを保存する一時ディレクトリを作成して、補助ライブラリを展開します。一時ディレクトリでは、ワイルドカード * (シェル) を使用して、照合したファイルのリストを生成します。通常は、ファイル名にワイルドカードは使用しない方がよいですが、この例ではターゲットが構築されるたびに新しいディレクトリが作成されるため、使用が可能です。これによって、実行中make により展開されたファイルだけが一時ディレクトリに含まれます。


注 -

通常は、メークファイル中でシェルファイル名にワイルドカードを使用することは避けてください。使用する場合は、必要なファイルを一時サブディレクトリに移動することによって対称外のファイルを除外するような処理手順にしてください。


この例は、ディレクトリの命名規約を使用しています。ディレクトリ名は、そのディレクトリにあるライブラリのベース名から決定されます。たとえば、補助ライブラリの名前が libx.a の場合は、その補助ライブラリがあるディレクトリの名前は libx になります。

この例は、動的なマクロ参照での接尾辞置換を使用して、各サブディレクトリのディレクトリ名を取り出します。各ライブラリを順次取り出すためのループとしてシェルを使用します。また、ライブラリからパッケージを作成する際に、シェルコマンド置換を使用して、オブジェクトファイルを正しいリンク順に並び替えます (lordertsort を利用します)。最後に、一時ディレクトリおよびその内容を削除します。

# サブディレクトリで生成されたライブラリをまとめる Makefile

CFLAGS= -O 

.KEEP_STATE:
.PRECIOUS:  libz.a

all: lib.a 

libz.a: libx.a liby.a 
        	-rm -rf tmp 
        	-mkdir tmp 
        	set -x ; for i in libx.a liby.a ; ¥ 
              		do ( cd tmp ; ar x ../$$i ) ; done 
        	( cd tmp ; rm -f *_*_.SYMDEF ; ar cr ../$@ `lorder * | tsort` ) 
        	-rm -rf tmp libx.a liby.a 

libx.a liby.a: FORCE 
        	-cd $(@:.a=) ; $(MAKE) $@ 
        	-ln -s $(@:.a=)/$@ $@ 
FORCE: 

説明を簡潔にするため、この例では他の形式のプログラムと、cleaninstalltest ターゲットはサポートしていません (ソースファイルがサブディレクトリにあるため、適用できません)。

インデントされた行にある rm -f *_*_.SYMDEF というコマンドは、補助ライブラリのシンボルテーブル (補助ライブラリに対して ar を実行すると生成されます) がこの完成ライブラリ libz.a にアーカイブされないようにします。

入れ子にした make コマンドは現在のライブラリを処理する前に補助ライブラリを構築するため、現在のディレクトリにある補助ライブラリとオブジェクトファイルの両方から構築されるライブラリ用にこのメークファイルを拡張できます。オブジェクトファイルのリストを、ライブラリの依存関係リストに追加する必要があります。また、そのオブジェクトファイルを、補助ライブラリから展開したオブジェクトファイルと照合するための一時サブディレクトリにコピーするためのコマンドを追加する必要があります。

# サブディレクトリで生成されたライブラリと、オブジェクトをまとめる Makefile

CFLAGS= -O 

.KEEP_STATE:
.PRECIOUS:  libz.

OBJECTS= map.o calc.o draw.o

all: libz.a 

libz.a: libx.a liby.a $(OBJECTS) 
         	-rm -rf tmp 
         	-mkdir tmp 
         	-cp $(OBJECTS) tmp 
          set -x ; for i in libx.a liby.a ; ¥ 
              		do ( cd tmp ; ar x ../$$i ) ; done 
         	( cd tmp ; rm -f *_*_.SYMDEF ; ar cr ../$@ ¥
                `lorder * | tsort` ) 
          -rm -rf tmp lix.a liby.a 

libx.a liby.a: FORCE 
         	-cd $(@:.a=) ; $(MAKE) $@ 
         	-ln -s $(@:.a=)/$@ $@ 
FORCE:

隠れた依存関係を make にレポートする

たとえば、.so を要求して、troff ドキュメントにインクルードされているソースファイルをトレースする必要があるなどの場合は、隠れた依存関係を処理するコマンドを記述する必要があります。.KEEP_STATE が有効な場合は、make は環境変数 SUNPRO_DEPENDENCIES を次のように設定します。

SUNPRO_DEPENDENCIES='report-file target'

コマンドが終了すると、make はファイルが作成されたかどうかを調べ、作成されていれば、そのファイルを読み取り、報告された依存関係を以下の形式で .make.state に書き込みます。

target:dependency ...

ここで、target は環境変数に指定されているものと同じです。