こちらは「makeの基礎(Java)」の続きです。
目次記事はこちらです。
より実践的なタスク
makeの基礎について学んだら、より実践的なタスクを構築していきます。
まずはフォルダ構造について考えます。現在の makeTestProject のフォルダ構造は次のようになっています。
makeTestProject
├── Human.java
├── Human.class
├── HumanTester.java
├── HumanTester.class
└── Makefile
このままクラスを作成し続けると、フォルダの中がぐちゃぐちゃになりそうですね。そこで以下のフォルダを新規で作成します。
-
src フォルダ
Java のソースコードを格納するフォルダです。中に .java ファイルが並びます。 -
target フォルダ
Java ファイルをコンパイルした後の .class フォルダを格納するフォルダです。
新規作成した src、target フォルダの中に Java ファイル、.class ファイルを移動します。最終的なツリーは以下のようになります。
makeTestProject
├── src
│ ├── Human.java
│ └── HumanTester.java
├── target
│ ├── Human.class
│ └── HumanTester.class
└── Makefile
ではこれから Makefile を編集して次のタスクを追加していきます。
-
init(初期化)タスク
target ディレクトリ自体の生成を行うタスクです。誤って target ディレクトリを削除してしまった場合や、target ディレクトリに問題が生じたときに使用します。 -
clean(クリーン)タスク
生成された .class ファイルを削除して、target ディレクトリをきれいにするタスクです。 -
run(実行)タスク
main メソッドがある HumanTester クラスを実行するタスクです。
上記のタスクを追加した Makefile が以下になります。
突然長くなったMakefileにビビるかもしれません。その場合はソースコードの後にある説明を見てからMakefileを編集しましょう。
# Makefile内で使用する変数の列挙
JC = javac
J = java
TARGETDIR = target
CP = -cp $(TARGETDIR)
SRCDIR = src
ENTRYPOINTCLASS = HumanTester
# 独自タスク名の列挙
.PHONY: init clean compile run
# 初期化タスク
init:
@echo [initialize task start...]
@if exist $(TARGETDIR) ( rmdir /s /q $(TARGETDIR) ) else ( echo $(TARGETDIR) is not exist )
@mkdir $(TARGETDIR)
@echo [initialize task end...]
# cleanタスク
clean:
@echo [clean task start...]
del /s $(TARGETDIR)\*.class
@echo [clean task end...]
# compileタスク
compile: clean
@echo [compile task start...]
$(JC) -d $(TARGETDIR) $(SRCDIR)/*.java
@echo [compile task end...]
# 実行タスク
run: compile
@echo [run task start...]
$(J) $(CP) $(ENTRYPOINTCLASS)
@echo [run task end...]
※ Makefileに詳しい人が見たら「 $@
とか $<
とか使った方が汎用性高くなるんじゃない?」って思われそうですが、Ant や Maven を学ぶ前の導入として Makefile を使いたいのでこんな形で書いてます
完成したMakefileについてですが、Makefileでは # 以降の一行をコメントとして認識し実行に影響を与えません。わかりやすくするためにタスクごとに簡単なコメントを入れておきました。
それでは以下で Makefile の中身を各コメントごとに見ていきます。
JC = javac
J = java
TARGETDIR = target
CP = -cp $(TARGETDIR)
SRCDIR = src
ENTRYPOINTCLASS = HumanTester
Makefile では Makefile 内で利用する変数を定義することができます。よく使うであろう値はここで変数宣言をするべきです。変数に型を設定することはできずすべて文字列となります。
変数が持つ値へのアクセスは $(変数名)
で行います。
.PHONY: init clean compile run
独自で作成したタスクの名前を列挙するのが.PHONY(フォニーターゲット)です。
make コマンドの実行は make [ターゲット名]
で行いますが、この時に [ターゲット名] と同じ名前のファイルやフォルダがある場合、make コマンドの引数にファイル名が与えられたと認識されてしまいます。ターゲット名とファイル・フォルダ名の衝突を避けるために、独自で作成したターゲット名はフォニーターゲットに列挙しておくのが無難です。
init:
@echo [initialize task start...]
@if exist $(TARGETDIR) ( rmdir /s /q $(TARGETDIR) ) else ( echo $(TARGETDIR) is not exist )
@mkdir $(TARGETDIR)
@echo [initialize task end...]
target ディレクトリの初期化を行うタスクです。コンポーネントは存在しません。
コマンドの先頭にある @
は、標準出力(今回はコマンドプロンプト)にコマンド自体(例えば echo とか)を表示させたくないときに使用します。
echo
コマンドは標準出力に文字を出力するコマンドです。echo
の後に半角スペースをおいて、そのあとに書いた文字列が標準出力に表示されます。
if exist [フォルダまたはファイル名]
は条件分岐になります。今回は *if exist $(TARGETDIR)*
と書いているので「$(TARGETDIR)が存在するかどうか」を調べています。$(TARGETDIR)
は①の変数宣言のところから「target」という文字列が入っていることがわかります。
rmdir [フォルダ名]
はフォルダを削除するコマンドです。/s
は指定したフォルダの中にあるサブフォルダもすべて消すという意味です。/q
は削除の際に「本当に消していいですか?」という確認をなくすという意味です。
以上をまとめると以下のようになります。
if exist $(TARGETDIR) ( rmdir /s /q $(TARGETDIR) ) else ( echo $(TARGETDIR) is not exist )
「もしtargetフォルダがあるならそれを削除して、ないのなら標準出力に「target is not exist」と表示する」
最後に、mkdir [フォルダ名]
はフォルダを新規作成するコマンドです。
要はinitタスクでは古い target ディレクトリを削除して、新しく target ディレクトリを作るって作業を行っているのですな。
clean:
@echo [clean task start...]
del /s $(TARGETDIR)\*.class
@echo [clean task end...]
target フォルダの中身をきれいにするタスクです。
del [ファイル名]
は指定したファイルを削除するコマンドです。/s
は指定したファイルをすべてのサブディレクトリからも削除することを表します。*
はワイルドカードと呼ばれ、任意の文字列を表します。del /s $(TARGETDIR)\*.class
で「target ディレクトリの下にあるすべてのディレクトリの中の、拡張子が .class のファイルを削除する」という意味になります。
compile: clean
@echo [compile task start...]
$(JC) -d $(TARGETDIR) $(SRCDIR)/*.java
@echo [compile task end...]
Java ファイルのコンパイルを行うタスクです。コンポーネントに clean タスクが書いてあります。そのため、make compile
を実行すると、自動的に clean タスクも実行されます。
$(JC)
は「javac
」です(①で定義)。Java ファイルのコンパイルを実行しています。
javac
コマンドの -d
オプションは、生成される .class ファイルを置くフォルダの指定を行うオプションです。今回は「-d $(TARGETDIR)
」とあるので、target フォルダに .class ファイルが置かれることになります。
$(SRCDIR)/*.java
は「src フォルダの下の拡張子が .java の任意のファイル」という意味です。($(SRCDIR)
は①で「src」という文字列と定義しています。)
run: compile
@echo [run task start...]
$(J) $(CP) $(ENTRYPOINTCLASS)
@echo [run task end...]
最後が実行を行うタスクです。$(J)
は「java
」です(①で定義)。カレントディレクトリに実行したい .class ファイルがない場合、-cp
または -classpath
オプションで実行したい .class ファイルがあるフォルダを指定します。今回はクラスパスの指定を $(CP)
という変数で行ったのでこれをjavaコマンドに渡しています。
$(ENTRYPOINTCLASS)
は main メソッドを持つクラス「HumanTester」を表しています。
以上で Makefile の編集は終了です。実際に動かしてみましょう。
コマンドプロンプトを立ち上げて、makeTestProject に移動します。そこで下記のコマンドを入力します。
make compile
[clean task start...]
del /s *. class
削除したファイル - C:\Users\[ユーザ名]\Desktop\makeTestProject\target\Human.class
削除したファイル - C:\Users\[ユーザ名]\Desktop\makeTestProject\target\HumanTester.class
[clean task end...]
[compile task start...]
Javac -d target src/*.java
[compile task end...]
compileタスクが実行される前にcleanタスクが動いていることが確認できます。([clean task start...]のところ)
targetフォルダの中にある .class ファイルを削除してから、改めてコンパイルを行い .class ファイルを生成していることがわかります。次に下記のコマンドを入力します。
make run
[clean task start...]
del /s *. class
削除したファイル - C:\Users\[ユーザ名]\Desktop\makeTestProject\target\Human.class
削除したファイル - C:\Users\[ユーザ名]\Desktop\makeTestProject\target\HumanTester.class
[clean task end...]
[compile task start...]
Javac -d target src/*.java
[compile task end...]
[run task start...]
java -cp target HumanTester
名前:名無しの権兵衛
年齢: 20
-----歳をとりました-----
1年後...
名前:名無しの権兵衛
年齢: 21
[run task end...]
target フォルダをきれいにして(clean タスク)、Java ファイルのコンパイルを行って(compile タスク)から実行タスクが動いているのがわかります。
eclipse などの IDE にある「実行」ボタンとほぼ同様の実行タスクを作成することができました!
せっかくなので少し Java ファイルを編集して make
コマンドを動かします。(下記はちょっとした例なので好きに Java ファイルを編集してください。)
import java.util.Date; // 追加部分
public class HumanTester{
public static void main(String[] args){
System.out.println("Humanクラスのテストを行います(実行日" + new Date() + ")"); // 追加部分
Human h = new Human("名無しの権兵衛", 20);
System.out.println("名前:" + h.getName());
System.out.println("年齢:" + h.getAge());
System.out.println("-----歳をとりました-----");
h.grow();
System.out.println("名前:" + h.getName());
System.out.println("年齢:" + h.getAge());
}
}
Java ファイルを編集したら、make run
を実行します。これぞ「自動ビルド」って感じですね。
test・package・siteタスクの作成
Javaによるアプリ開発では最終的に他の人に配布するための jar ファイルにまとめる作業が行われます(WEBアプリでは war ファイルか ear ファイルにまとめますが、今回は割愛)。
また、ただ配布するだけでは不十分で、多くの場合にそのjarファイルの使い方を示したサイトドキュメントが必要になります。Javaの場合 APIリファレンスのやつ とかが得に有名ですね。
今から作るのは「ほかの人に公開する成果物を作るタスク」です。これはただローカルで実行(run)していた頃とは大きな違いです。公開に当たっては Java ファイルが正しく動作するのかテストする必要があります。テスト用のソースコード置き場となるフォルダが欲しくなりますね。
testタスク
まずはテストコードをおけるように「src」フォルダを編集します。実際に稼働させるソースコードを src フォルダの下の main フォルダに、テストコードを置くフォルダを src フォルダの下の test フォルダに配置します。
すでに作成したソースコードについて、Human クラスは実際にアプリケーションで使用することが想定されますが、HumanTester クラスは Human クラスの動作をテストするためのクラスであると考えられます。そこで、以下のようなコードの配置を行うこととします。
makeTestProject
├── src
│ ├── main
│ │ └── Human.java
│ └── test
│ └── HumanTester.java
├── target
│ ├── Human.class
│ └── HumanTester.class
└── Makefile
実際にはテストコードの作成はJunitというライブラリを利用するのが一般的です。今回は省略して Ant 以降で扱うことにします。
次に HumanTester クラスをよりテストクラスらしく変形します。テスト用のメソッドを用意して、main メソッドでテストメソッドをコールして正しく動作しているか検証するという形です。
public class HumanTester{
public static void main(String[] args){
// インスタンス化テスト
instantiateHuman();
// growメソッドのテスト
testGrowMethod();
}
// インスタンス化テスト
public static void instantiateHuman(){
Human h = new Human("山田太郎", 20);
boolean isEqualName = (h.getName().equals("山田太郎"));
boolean isSameAge = (h.getAge() == 20);
if(isEqualName && isSameAge){
System.out.println("インスタンス化テスト成功!");
}else{
System.out.println("インスタンス化テスト失敗!");
}
}
// growメソッドのテスト
public static void testGrowMethod(){
Human h = new Human("大海めぐみ", 22);
h.grow();
boolean isGrewAge = (h.getAge() == 23);
if(isGrewAge){
System.out.println("growメソッドのテスト成功!");
}else{
System.out.println("growメソッドのテスト失敗!");
}
}
}
テストクラスの作成ができたら、make ファイルの編集を行います。target フォルダの下に、テストクラス用の .class ファイル置き場(target/test)と、main 用の .class ファイル置き場(target/main)を作成するように変更します。
前の状態の Makefile を保存したい場合、過去の Makefile の名前を「Makefile_old」などと名前を変更してバックアップを取っておきましょう。
make
コマンドは「Makefile」という名前のファイルに反応する為、名前を変えておけば実行に影響を与えません。
# Makefile内で使用する変数の列挙
JC = javac
J = java
TARGETDIR = target
TARGET_MAIN = $(TARGETDIR)\main
TARGET_TEST = $(TARGETDIR)\test
CP = -cp $(TARGET_MAIN);$(TARGET_TEST)
SRCDIR = src
ENTRYPOINTCLASS = HumanTester
# 独自タスク名の列挙
.PHONY: init subinit clean compile test-compile test
# 初期化タスク
init: subinit
@echo [initialize task start...]
@if exist $(TARGETDIR) ( rmdir /s /q $(TARGETDIR) ) else ( echo $(TARGETDIR) is not exist )
@mkdir $(TARGET_MAIN)
@mkdir $(TARGET_TEST)
@echo [initialize task end...]
# cleanタスク
clean:
@echo [clean task start...]
del /s $(TARGETDIR)\*.class
@echo [clean task end...]
# compileタスク
compile: clean
@echo [compile task start...]
$(JC) -d $(TARGET_MAIN) $(SRCDIR)/main/*.java
@echo [compile task end...]
# test-compileタスク
test-compile: compile
@echo [test-compile task start...]
$(JC) -d $(TARGET_TEST) $(CP) $(SRCDIR)/test/*.java
@echo [test-compile task end...]
# testタスク
test: test-compile
@echo [test task start...]
$(J) $(CP) $(ENTRYPOINTCLASS)
@echo [test task end...]
それぞれのタスクの説明は割愛します。
packageタスク
次に、実際にユーザに配布するための jar ファイルを作成するタスクを作成します(WEBアプリとかならデプロイするための war ファイルの作成を行うタスクになります)。 名前は packageタスク とします。
package タスクは test が成功していることを前提とします。コンポーネントに test が入る感じですね。Makefile に以下のコードを追記してください。
# packageタスク
package: test
@echo [package task start...]
jar cvf human.jar -C $(TARGET_MAIN)/Human.class .
@echo [package task end...]
jar コマンドの詳しい説明は別ページにありますのでそちらを参照してください。(今回は簡易的なもので META-INF フォルダ等も作成しません)
siteタスク
Java には HTML 形式でドキュメントを生成する javadoc という仕組みがあります。javadoc について詳しくは別ページで解説しているので参照してください。
最後に作成するタスクは、javadoc を利用して作成した java ファイルの説明用 HTML を作成する site タスクです。
まずは Human.java に文書化コメント(javadocコメント)を追記します。プログラム自体の変更はありません。
/**
* <p>「人間」を表すクラスです。</p>
* @author Oka Shamoo (自分の名前)
* @version 1.0
*/
public class Human{
/**
* <p>人間の名前を表すフィールドです</p>
*/
private String name;
/**
* <p>人間の年齢を表すフィールドです</p>
*/
private int age;
/**
* 人間クラスのコンストラクタです。人間オブジェクトの生成には名前と年齢の指定が必要です。
* @param name 名前
* @param age 年齢
*/
public Human(String name, int age){
this.name = name;
this.age = age;
}
/**
* 人間の名前を取得します。
* @return 名前
*/
public String getName(){
return this.name;
}
/**
* 人間の年齢を取得します。
* @return 年齢
*/
public int getAge(){
return this.age;
}
/**
* 人間オブジェクトの年齢フィールドの値を1増加します。
*/
public void grow(){
System.out.println("1年後...");
this.age++;
}
}
Human.java に文書化コメントを追加したら、次に site タスクの作成を行います。Makefile に以下のコードを追記してください。
# siteタスク
site:
@echo [site task start...]
javadoc -d doc $(SRCDIR)\main\Human.java
@echo [site task end...]
以上で Makefile の編集はおしまいです!
動作確認
makeTestProject の動作確認を行います。コマンドプロンプトを立ち上げ、カレントディレクトリをmakeTestProjectに変更します。
まずは test タスクの動作確認を行います。下記のコマンドを入力してみましょう。
make test
[clean task start...]
del /s *. class
削除したファイル - C:\Users\[ユーザ名]\Desktop\makeTestProject\target\Human.class
削除したファイル - C:\Users\[ユーザ名]\Desktop\makeTestProject\target\HumanTester.class
[clean task end...]
[compile task start...]
Javac -d target\main src/main/*.java
[compile task end...]
[test—compile task start...]
javac —d target\test —cp target\main;target\test src/test/*.java
[test—compile task end...]
[test task start...]
java —cp target\main;target\test HumanTester
インスタンス化テスト成功!
1年後...
growメソッドのテスト成功!
[test task end...]
テストクラスの実行結果まで確認できたと思います。次に jar ファイルを生成してみましょう。下記のコマンドを入力してください。
make test
jar ファイルが生成されます。次に javadoc による HTML 出力も確認します。下記のコマンドを入力してください。
make site
これで HTML 出力を得ることができます。今回は doc フォルダにドキュメントを出力するようにしています。ファイルエクスプローラから doc フォルダの index.html をダブルクリックしましょう。Human クラスを説明する HTML 出力を確認できます。
以上で make プロジェクトの説明は終わりです。しかし自動ビルドの強さはこれだけでは伝わらないと思います。Human.java の内容を変更して再度 test タスクを実行してみたりして、より理解を深めておくとよいでしょう。
make という自動ビルドツールは今後扱うことになる Ant や Maven、Gradle の祖先にあたります。また、C言語プログラミングではいまだに現役のツールです。がっつりやって損はない!
次の記事