Java, JavaScriptのチーム開発環境をつくる（gradle環境構築編）

# シリーズ案内
　本稿はシリーズものになる予定です。（未完成）
　今考えているのは以下のような構成です。

　・[問題提起編](https://qiita.com/y-takano/items/c4422fd966afe5b29c5e)　- (投稿済)
　　本稿掲載の経緯とテーマ、その完成図について説明する。
　　
　・Gradle環境構築編　- 本記事
　　gradleによるバージョン管理およびjava/jsビルド環境の統合について説明する。

　・IDE環境構築編　- (未投稿)
　　開発者がプログラミング・単体テスト・手動デバッグを実施擦る際の環境構築手順について解説する。

# 構築手順
　語る内容の趣旨は[前回記事](https://qiita.com/y-takano/items/c4422fd966afe5b29c5e)を参照。本稿では、例えばプロジェクトが標準化チーム(または)手順について掘り下げていきます。
　ただし、gradleは奥が深すぎて説明しきれないところがありますので、とりあえずは全体感が読み取れる程度にしたいと思います。

# workspaceレイアウト

　今回説明するworkspace配下の要素はこんな感じです。

```console:workspaceのファイル構造案(windows環境を想定)
/workspace        : gradle, eclipse, VSCode共通のワークスペース
  /gradle         : gradleホーム
    /wrapper      : gradlewrapperの格納場所
    /tasks        : 自作タスクを作る場合
      /xxTask.gradle
  /buildSrc       : 自作プラグインを作る場合
    /src          :
      /main       :
        /groovy   :
          /xxPlugin.groovy
  /build.gradle   : プロジェクト横断で適用できるビルド定義
  /gradle.properties: プロパティが必要であれば
  /settings.gradle: プロジェクトフォルダを列挙
  /gradlew.bat    : 親のgradle-wrapper
```

## 目標地点: tasksの表示
　まずはコマンドインターフェースの設計です。利用者がgradlew tasksしたときにこのような表示になっている状態を目指して作業を進めます。

### workspaceのとき
```console:workspace
C:\develop\workspace>gradlew tasks

> Task :tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Help tasks
----------
dependencies - Displays all dependencies declared in root project 'workspace'.
help - Displays a help message.
projects - Displays the sub-projects of root project 'workspace'.
properties - Displays the properties of root project 'workspace'.
tasks - Displays the tasks runnable from root project 'workspace' (some of the displayed tasks may belong to subprojects).

バージョン管理 tasks
------------
pull - ワークスペース上をリモートリポジトリの内容で最新化します
push - ワークスペース上の変更内容をリモートリポジトリへ反映します

ビルド tasks
---------
clean - ワークスペース及び各プロジェクトのビルド用ファイルをクリアします
assemble - 各プロジェクトのライブラリまたはアプリケーションを構築します
test - 各プロジェクトのライブラリまたはアプリケーションをテストします
apidoc - 各プロジェクトのAPI仕様書を出力します
package - 各プロジェクトのライブラリを他プロジェクトから参照可能になるように配置します

ワークスペース設定 tasks
---------------
init - ワークスペースを初期化します
clone - リモートリポジトリからローカルリポジトリを複製します
```
重要なのはgradleからバージョン管理コマンドを使えるようにすることと、ビルド用コマンドをjava/jsプロジェクトで共通にしておくことでしょうか。もちろん他の言語が入ってきても一緒です。これにより、バージョン管理がSVNでもGitでも、言語がjavaでもjsでも他の言語でも、利用者が叩くコマンドは常に同じようになります。

### java/jsプロジェクトのとき
```console:workspace/projectX
C:\develop\workspace\projectX>gradlew tasks

> Task :projectX:tasks

------------------------------------------------------------
All tasks runnable from project :projectX
------------------------------------------------------------

Help tasks
----------
dependencies - Displays all dependencies declared in project ':projectX'.
help - Displays a help message.
projects - Displays the sub-projects of project ':projectX'.
properties - Displays the properties of project ':projectX'.
tasks - Displays the tasks runnable from project ':projectX'.

ビルド tasks
---------
clean - プロジェクト内のビルド用ファイルをクリアします
assemble - プロジェクト内のライブラリまたはアプリケーションを構築します
test - プロジェクト内のライブラリまたはアプリケーションをテストします
apidoc - プロジェクトのAPI仕様書を出力します
package - プロジェクト内のライブラリを他プロジェクトから参照可能になるように配置します
```
これは例ですが、開発者が頻繁に利用するであろうプロジェクト内のコマンドはある程度機能制限をかけておきます。この例ではビルド用のコマンドしか使えないようにしてみました。

# Taskのカスタマイズ(記載要領)
　さて、どのようにして上記のようなメニューを作り上げるのか掘り下げてみましょう。因みに、上述の通り要点に絞るためTIPS的な記載になります。ソースコード全量みたいなコーナーはないのでご了承ください。

## configureのスコープを指定する
　これだけで大分イメージができるかと思います

```workspace/settings.gradle
// project1: javaプロジェクト
// project2; jsプロジェクト
include 'project1', 'project2'
```

```workspace/gradle.properties
project1.type=java
project2.type=js
```

```gradle:workspace/build.gradle

// rootproject(workspace)を含めたすべてのプロジェクト
configure(allprojects) {
    println "全プロジェクト共通の設定 > $project"
    ...
}

// rootproject
configure(rootproject) {
    println "workspaceの設定 > $project"
    ...
}


// rootproject(workspace)を含めないすべてのプロジェクト
configure(subprojects) {
    println "root以外のプロジェクト共通の設定 > $project"
    ...
}

// javaプロジェクト
configure(subprojects.findAll{ projectType(it) == 'java'}) {
    println "javaプロジェクト共通の設定 > $project"
    ...
}

// jsプロジェクト
configure(subprojects.findAll{ projectType(it) == 'js'}) {
    println "jsプロジェクト共通の設定 > $project"
    ...
}

// プロジェクトタイプを取得する関数
// gradle.propertiesに定義した内容を取得
String projectType(Project p) {
    def t
    try { t = getProperty("${p.name}.type") } catch (e) {}
    return t ?: 'others'
}
```

上記の設定でタスクを実行すると以下のような出力が得られます。

```console:workspace
C:\develop\workspace>gradlew projects

> Configure project :
全プロジェクト共通の設定 > root project 'workspace'
全プロジェクト共通の設定 > project ':project1'
全プロジェクト共通の設定 > project ':project2'
rootプロジェクトの設定 > root project 'workspace'
javaプロジェクト共通の設定 > project ':project1'
javascriptプロジェクト共通の設定 > project ':project2'

> Task :projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'workspace'
+--- Project ':project1'
\--- Project ':project2'
```

これで好きなスコープでタスクの設定を適用することができますね。


## 不要タスクの無効化
　不慣れな利用者にとっては余りに多機能なインターフェースは理解の妨げになりかねません。無効化によってスッキリさせましょう。

```gradle:workspace/build.gradle
configure(allprojects) {
    println "全プロジェクト共通の設定 > $project"

    task('buildEnvironment').enabled     = false
    task('components').enabled           = false
    task('dependencyInsight').enabled    = false
    task('dependentComponents').enabled  = false
    task('model').enabled                = false
    task('properties').enabled           = false
    task('wrapper').enabled              = false
    task('init').enabled                 = false
}
```

上記の設定でtasksを叩いてみます。

```console:workspace
C:\develop\workspace>gradlew tasks

> Configure project :
全プロジェクト共通の設定 > root project 'workspace'

> Task :tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Help tasks
----------
dependencies - Displays all dependencies declared in root project 'workspace'.
help - Displays a help message.
projects - Displays the sub-projects of root project 'workspace'.
tasks - Displays the tasks runnable from root project 'workspace' (some of the displayed tasks may belong to subprojects).
```

スッキリしました。尤もこれでは何もできませんが。

## タスクの上書き
　今回のモデルでは既存のinitタスクはあまり使いたくありません。初期資産を固定にしたくないのと、多言語に対応したいためです。なので上書きしてしまいます。

```gradle:workspace/build.gradle
configure(rootproject) {
    println "rootprojectの設定 > $project"

    // Initタスク上書き
    apply from: 'gradle/InitTask.gradle'
}
configure(subprojects) {
    println "root以外のプロジェクト共通の設定 > $project"

    // Initタスク抑止
    task('init').enabled = false
}
```

```gradle:workspace/gradle/InitTask.gradle

// 元タスクを退避しておく
def __originInitTask__ = init

//--------------------------------------
// プロジェクト初期化タスク定義
// （既存タスクinitを上書きする）
//--------------------------------------
task init(overwrite: true).doLast {
    println ':init '

    // 何某かの設定処理
    // 例えばここでworkspace配下のフォルダ・ファイル全削除する。
    // 因みにタスクの処理中にbuild.gradleが削除されても問題なく動くようです。
    ...   

    // 初期化本処理
    // 例えば最小限のworkspaceの構成をzipにしておき、
    // ダウンロードして展開するような作りがおすすめです。
    // 運用も実装も楽だからです。

    // zipのダウンロード
    ant.get(
        src: 'http://xxx.xxx.xxx/templete.zip',
        dest: "${rootDir}/build/tmp/downloads/templete.zip")

    // zipからworkspaceへコピー
    copy {
       from zipTree "${rootDir}/build/tmp/downloads/templete.zip"
       into "${rootDir}"
    }
}

// 必要な属性は引き継ぐ
//init.description    = __originInitTask__.description
//init.group          = __originInitTask__.group
//init.dependsOn      = __originInitTask__.dependsOn

// 属性の上書き
init.description    = 'ワークスペースを初期化します'
init.group          = 'ワークスペース設定'
```

これでリモートメンテナンスに対応したinitタスクができました。バージョン管理システムへ接続する準備タスクとして、あるいは不意に環境が壊れてしまった場合の復元コマンドとして効果が期待できそうです。
もちろんinit以外のタスクも必要があれば上書きしてしまいましょう。
因みに、

```console:workspace
gradlew init --type java-library --test-framework spock
```

javaに関してはこれで予めspockの環境を作っておいてバージョン管理システムに入れておくとかはやっておいてもいいかもしれません。jsは手作りですかね。

## タスクの新規作成
　バージョン管理システムと連携してプロジェクトフォルダ一式を構築するcloneタスクを作成したいです。
　gitのインストールとwindowsOS前提であれば、こんな感じでできそうです。

```properties:workspace/gradle.properties
project1.type=java
project2.type=js

remoteRepos=https://github.com/XXXX/xxx.git
```

```gradle:workspace/build.gradle
// rootプロジェクト
configure(rootProject) {
    // initタスク
    apply from: 'gradle/InitTask.gradle'
    
    // cloneタスク
    apply from: 'gradle/CloneTask.gradle'
}
```

```gradle:workspace/gradle/CloneTask.gradle
task clone(group:'ワークスペース設定', description:'リモートリポジトリからローカルリポジトリを複製します', type:Exec) {
    commandLine 'cmd', '/c', 'git', 'clone', getProperty('remoteRepos')
}
```

利用者にgitのインストールを手動でさせたくないときは、gradleのgit連携用プラグインを検討するのがよいでしょう。

## プラグインの新規作成
　タスク単体で定義するよりもプラグインとして一括でまとめたくなる場合もあります。今回のモデルだと、ビルド用のタスクはプラグインとしてまとめておきたいです。

```gradle:workspace/build.gradle
// javaプロジェクト
configure(subprojects.findAll{ projectType(it) == 'java'}) {
    apply plugin: JavaBuildPlugin
}
```

```groovy:workspace/buildSrc/src/main/groovy/JavaBuildPlugin.groovy
import org.gradle.api.Plugin
import org.gradle.api.Project

class JavaBuildPlugin implements Plugin<Project> {

    void apply(Project project) {
        
        // 独自タスクの定義
        project.task(group:'ビルド', description:'プロジェクト内のビルド用ファイルをクリアします', 'clean') {
            doLast { ... }
        }
        project.task(group:'ビルド', description:'プロジェクト内のビルド用ファイルをクリアします','assemble') {
            doLast { ... }
        }
        project.task(group:'ビルド', description:'プロジェクト内のライブラリまたはアプリケーションをテストします','test') {
            doLast { ... }
        }
        project.task(group:'ビルド', description:'プロジェクトのAPI仕様書を出力します','apidoc') {
            doLast { ... }
        }
        project.task(group:'ビルド', description:'プロジェクト内のライブラリを他プロジェクトから参照可能になるように配置します','package') {
            doLast { ... }
        }
        
        // デフォルトタスクの定義
        project.defaultTasks = ['clean', 'assemble', 'test', 'apidoc', 'package']
        
        // extensionsの定義
        project.extensions.create("option", OptionalExtension)
    }
}

class OptionalExtension {
    String opt1
    String opt2
}

// extensionの使い方:
// ビルドスクリプト側で以下のような定義をすると、プラグイン側で受け取って処理ができる。
// option {
//   opt1 'オプション設定1'
//   opt2 'オプション設定2'
// }
```

この例はjava版ですが、js版や他言語版も同じように実装すれば、利用者はプロジェクト個別のbuild.gradleを一切書かずに同じようにコマンドを扱うことができるようになります。

ある程度完成度が高いプラグインができたら、一般公開してもいいかもしれません。その際はpackage宣言とplugin-idの設定を忘れずに。

# これで一通り完成です
　ここまでの手順を再現すれば、上記の「目標地点」のようなタスク一覧が完成しているはずです。１つのアプリケーションに多言語が混じるようなハイブリット環境に適応した、わりと標準的な開発環境ができたのかと思います。

　いかがでしたでしょうか。絞ったつもりでも全然長くなってしまいましたね。。。わかりにくいところとか、逆にここ書けよみたいなところもあると思いますので、気づきがありましたらコメントください。

　次回はIDEを使ってプログラム・デバッグテストをする手順について掘り下げたいと思います。