Eclipseを使いたくない人のための無理やりJARファイル作成術 for FUN情報処理演習I

# 概要

- 本記事は[FUN Advent Calendar 2022 part2](https://adventar.org/calendars/7426)の19日目の記事です。
    - 昨日はたろうくんの[\[かわいいは正義\] tiikawaコマンドを作った件 \[そのプロセスは殺さない，殺したくない\]](https://qiita.com/GodMax/items/b53d385d505f4ff8f591)でした。
        - 癒やしは大事ですね。しかし卒論スペースは定期的に片付けましょう。でないと `kill -KILL` でプロセスを殺したくなります。
    - 明日はkazu8823さんの「二郎の美味しい作り方、そして二郎を食べることによるその効果」です。
    - ちなみに[Part1](https://adventar.org/calendars/7402)、[Part3](https://adventar.org/calendars/7655)もあります。

本記事では、公立はこだて未来大学の2年生の講義「情報処理演習I」でEclipseを使いたくない、Javaもインストールしたくない、という人に贈る「dockerのopenjdkを使ってCUIで無理やりJARファイルを作成する」術を紹介します。

お前誰だよ！という人はきっとOB/OGの方ですね。

# はじめに

公立はこだて未来大学の2年生の講義「情報処理演習I」はJavaの演習講義です。Javaでプログラミングの勉強をします。
正直に言います。

Eclipse重すぎ。
Eclipse重すぎ。

大事なことなのでチャ○アスなみに何度も言います。

Eclipse重すぎ。

いいマシンをお持ちの皆さんは感じないかもしれませんが、筆者が使っている非力なマシンではEclipseさんはとても重いです。筆者のような残念なマシンを使っている人や、

- 俺はvimで書くぜ
- 至高emacsしか認めない
- とりまvscodeがあればええやろ

という方々は、演習の提出ファイルであるJARファイルの作成にゴニョゴニョする必要があります。面倒なのでEclipseにコピペしてビルドしているのではないでしょうか。筆者もそうでした。vscodeで書いた方が書きやすいのに、Eclipseでビルドしないといけないのが手間でした。

そこで、dockerのopenjdkを使って課題に提出可能なJARファイルを生成することにしました。情報処理演習Iを担当していますが、この成果物のおかげで普段使っているマシンにEclipseをインストールしていません。講義で使うマシンは学生さんに見せるためだけにEclipseがインストールされています。

# 情報処理演習Iの提出ファイルに関して

情報処理演習Iをすでに受けたことがある方はご存知かとは思いますが、情報処理演習Iでは書いたプログラムをJARファイルとしてエクスポートし、提出します。すると、自動的にプログラムがチェックされて点数が出力されます。

提出用のJARファイルの作成は、Eclipseさんのエクスポート機能で行えます。おおよその手順は以下です。

1. パッケージまたはクラスを選択して「エクスポート」
1. 出力形式としてJARファイルを選択
1. ソースを含める設定
1. JARファイル名を指定のものに変更
1. メインクラスの指定
1. エクスポート

これらを全てキーボードだけで行うのは（できなくはないけど）面倒です。
そこで、

1. ソースコードをお好きなエディタで書く
1. コマンドを実行してJARファイルを生成

という手順でJARファイルを生成できるようにします。

なお、本記事の手順で生成したJARファイルを提出して0点になっても責任は一切負いかねます。自己責任でご使用ください。と言っても、何度でも提出できるので0点になったら素直にEclipseを使えばよろしいです。

ところで筆者はあまりJavaには詳しくないです。講義でしか使っていませんので、実はもっと簡単な手順があるならぜひ教えて下さい。特に、MANIFEST.MFを1ステップで作成する手順が分からず、スクリプト内では一度作成してからアップデートするという手順を踏んでいます。

# 準備

以下の手順では情報処理演習I用のディレクトリを作成して、全てその下で作業します。ここでは `~/ipp1/` を作成したものとします。


## テンプレートファイルの作成

情報処理演習Iでは署名が無いファイルは自動チェックで0点になります。このため、署名入りのテンプレートファイルを作成します。 `YOUR NAME` と `bxxxxxxx` の部分は自分の情報を入れておきましょう。これらに加えてconstructorなどを追加しておいてもいいでしょう。
なお、エディタのsnippet機能などで作成しても構いません。


```~/ipp1/Template.java
package template;

/**
 * @author YOUR NAME bxxxxxxx
 */
public class Template {
    public static void main(String[] args) {
    }
}
```

## dockerのインストール

dockerをインストールしておきましょう。この記事を見て実際にやろうとしている人はきっとdockerはすでにインストールされていると思いますので、インストール手順は割愛します。

openjdkを使いますのでpullしておきます。

```bash
docker pull openjdk
```

## スクリプトの作成

以下のファイルを作ります。パーミッションも適切に設定しておきましょう。

```~/ipp1/ipp1-build.sh
#!/bin/sh

if [ $# -lt 3 ]; then
    echo "Usage: $0 <jar_file> <main_class> <path_to_src>"
    exit 1
fi

DOCKER_IMG=openjdk
JAR=$1
MAIN=$2
SRC_PATH=$3

docker run --rm -it \
    -v $PWD:/app \
    -v $(realpath ${SRC_PATH}):/src \
    -w /work \
    ${DOCKER_IMG} \
    sh -c "cp -r /src/* /work/ &&
    javac **/*.java &&
    jar cvf /app/${JAR} ./ &&
    echo Main-Class: ${MAIN} > main.mf &&
    jar uvfm /app/${JAR} main.mf || /bin/bash"
```

# 使い方

情報処理演習Iでは演習の中で新しいパッケージを作成し、その中にクラスファイルを格納していきます。

ここでは `sample` パッケージに `Main` クラスを作成し、 `sample.jar` というJARファイルにエクスポートして提出するという場合を仮定して説明します。

## パッケージとなるディレクトリの作成

`src` ディレクトリの中にパッケージ名のディレクトリを作成します。

```bash
cd ~/ipp1
mkdir -p src/sample
```

## クラスファイルの作成

テンプレートをコピーしてクラスファイルを作成します。

```bash
cp Template.java src/sample/Main.java
```

課題に従ってクラスファイルを書き換えます。
ここでは、`Hello!` を出力する課題が出たとします。以下のようになるかと思います。パッケージ名を書き換え忘れることが多いので注意しましょう。


```Main.java
package sample;

/**
 * @author YOUR NAME bxxxxxxx
 */
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello!");
    }
}
```

## JARファイルの作成

準備で作成した `ipp1-build.sh` を使ってJARファイルを作成します。
JARファイル名、メインクラス名、パッケージのディレクトリが格納された `src` ディレクトリへのパスを指定します。

パッケージごと提出する必要があるため、以下の2点が必要です。

- メインクラスの指定がパッケージ名を含む
- JARファイルにはパッケージがディレクトリごと含まれる

このため、 `src` ディレクトリの中身を全て格納し、そこに入っている.javaファイルを全てコンパイルするようにしています。また、dockerを使って（手抜きした実装をして）いるため、JARファイルへのパスはカレントディレクトリより下のみを指定可能です。
色々と手抜きをしているため、パスの指定は柔軟ではありません。どうぞ改造して使ってください。

```bash
./ipp1-build.sh sample.jar sample.Main src
```

成功すれば以下のような出力が表示されてカレントディレクトリ（= `~/ipp1` ）に `sample.jar` が作成されます。

```text:出力
added manifest
adding: sample/(in = 0) (out= 0)(stored 0%)
adding: sample/Main.class(in = 415) (out= 287)(deflated 30%)
adding: sample/Main.java(in = 166) (out= 138)(deflated 16%)
updated manifest
```

なお、Javaファイルにエラーがあると、docker内のbashで停止します。ソースコードを修正し、コンパイル・実行して確認しながらソースコードを完成させましょう。コンパイルコマンドは書きませんが、きっと皆さん分かりますよね（というか、スクリプトに書いてあります）。
以下の例は `Main.java` でクラス名を修正し忘れた場合のエラーです。 `exit` すればdockerコンテナを終了できます。

```text:出力
sample/Main.java:6: error: class Template is public, should be declared in a file named Template.java
public class Template {
       ^
1 error
bash-4.4# 
```

## 実行

スクリプトを作っていないですが、dockerのopenjdkコンテナ上でjavaコマンドを実行すれば実行できます。

```bash
docker run --rm -it -v $PWD:/app -w /app openjdk java -jar sample.jar
```
```text:出力
Hello!
```

問題なく実行できるのであれば提出しましょう。

# 運用上のTips

## 各週の演習が混ざらないようにする

各週でディレクトリを作って、その中に `src/パッケージ名` ディレクトリを作るとよいです。

```bash
mkdir ~/ipp1/01
cd !$
mkdir -p src/sample
cp ../Template.java src/sample/Main.java
...
```

パッケージをコピーして使用する場合は前の週からコピーしましょう。

```bash
mkdir ~/ipp1/02/src
cd ~/ipp1/02
cp -r ../01/src/sample src/
```

パッケージ名を変更する場合には手動で変更する必要があります。割愛しますが、sedなどで書き換ると良いです。

## 提供されたテンプレートファイルの使用

テンプレートとして .jar ファイルが提供される場合があります。その場合には手動で展開して使います。

```bash
mkdir ~/ipp1/xx/src
cd ~/ipp1/xx
docker run --rm -it -v $PWD/src:/app -v $(realpath ~/Downloads):/down -w /app openjdk /bin/bash
bash-4.4# jar xvf /down/hoge.jar
```
```text:出力
  created: hoge/
 inflated: hoge/Hoge.java
 inflated: hoge/Fuga.java
```
```bash
bash-4.4# exit
```

# 生成されたJARファイルの中身

JARファイルはアーカイブファイルであり、その中身はこんな感じになっています。

```text
.
├── META-INF
│   └── MANIFEST.MF
└── sample
    ├── Main.class
    └── Main.java
```

パッケージ名のディレクトリ `sample` の中に `Main.java` ファイルと `Main.class` ファイルが格納されています。複数のパッケージが含まれる場合には複数のディレクトリが格納されます。

`META-INF/MANIFEST.MF` にはメインクラスの情報が書かれています。

```META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: 18.0.1.1 (Oracle Corporation)
Main-Class: sample.Main
```

# おわりに

maven使えばええやん、などと思った人もいるかもしれませんが、情報処理演習Iで作成するプログラムはそれほど複雑ではなく、演習問題のたびにいちいちmaven設定するのは正直だるいです。
サクッと書いてサクッとJARファイルを作成して提出、という意味では、それなりに需要があるのではないかと思い、今年度構築した手抜きスクリプトについて記述しました。

しかし、ゲッター・セッターの自動生成やクラスファイルの自動生成、Comparableな実装のテンプレート生成などはEclipseでないと面倒かもしれません。
なお、ご使用は自己責任でお願いします。