第60章 ビルドロジックの体系化

Gradle offers a variety of ways to organize your build logic. First of all you can put your build logic directly in the action closure of a task. If a couple of tasks share the same logic you can extract this logic into a method. If multiple projects of a multi-project build share some logic you can define this method in the parent project. If the build logic gets too complex for being properly modeled by methods then you likely should implement your logic with classes to encapsulate your logic. [40] Gradle makes this very easy. Just drop your classes in a certain directory and Gradle automatically compiles them and puts them in the classpath of your build script.

Gradleでは、様々な方法でビルドロジックを体系化できます。最初は、すべてのビルドロジックがタスクのクロージャに置かれているかもしれません。しかし、いくつかのタスクでロジックを共有したい場合はそのロジックをメソッドに切り出すことができます。マルチプロジェクトの場合、親プロジェクトでメソッドを定義すれば、サブプロジェクトでそのメソッドを共有できます。そうしてメソッドを切り出していけば、だんだんとビルドロジックが複雑になっていく場合もあるでしょう。そのときはオブジェクト指向のモデルを取り入れるのはどうでしょうか。 [41] Gradleでは、決まったディレクトリにクラスを放り込んでおけば自動的にコンパイルしてクラスパスに追加してくれるので、そういったモデルもとても簡単に実現できます。

Here is a summary of the ways you can organise your build logic:

ビルドロジックを体系化する方法について、いくつか例を挙げます。

60.1. プロパティとメソッドの継承Inherited properties and methods

Any method or property defined in a project build script is also visible to all the sub-projects. You can use this to define common configurations, and to extract build logic into methods which can be reused by the sub-projects.

プロジェクト内で定義されたプロパティとメソッドは、そのプロジェクトのサブプロジェクトでもすべて使用できます。これを使って共通設定を定義したり、よく使うビルドロジックを抽出して複数のサブプロジェクトで再利用したりできます。

例60.1 プロパティとメソッドの継承を使う

build.gradle

// Define an extra property
ext.srcDirName = 'src/java'

// Define a method
def getSrcDir(project) {
    return project.file(srcDirName)
}

child/build.gradle

task show << {
    // Use inherited property
    println 'srcDirName: ' + srcDirName

    // Use inherited method
    File srcDir = getSrcDir(project)
    println 'srcDir: ' + rootProject.relativePath(srcDir)
}

gradle -q show の出力

> gradle -q show
srcDirName: src/java
srcDir: child/src/java

60.2. 設定のインジェクションInjected configuration

You can use the configuration injection technique discussed in 「クロスプロジェクト設定Cross project configuration and 「サブプロジェクトの設定Subproject configuration to inject properties and methods into various projects. This is generally a better option than inheritance, for a number of reasons: The injection is explicit in the build script, You can inject different logic into different projects, And you can inject any kind of configuration such as repositories, plug-ins, tasks, and so on. The following sample shows how this works.

「クロスプロジェクト設定Cross project configuration「サブプロジェクトの設定Subproject configurationで紹介した設定のインジェクションを使って、複数のプロジェクトにプロパティとメソッドを注入できます。設定のインジェクションは、一般的には継承よりいい選択肢になるはずです。たくさん理由はありますが、たとえばインジェクションはスクリプト内で明示的に使用していることが分かりますし、いくつかのプロジェクトだけに別のロジックを注入することもできます。また、リポジトリ設定やGradleプラグイン、タスクなどどんな設定でも注入できます。インジェクションの例は以下のサンプルをご覧ください。

例60.2 プロパティとメソッドのインジェクション

build.gradle

subprojects {
    // Define a new property
    ext.srcDirName = 'src/java'

    // Define a method using a closure as the method body
    ext.srcDir = { file(srcDirName) }

    // Define a task
    task show << {
        println 'project: ' + project.path
        println 'srcDirName: ' + srcDirName
        File srcDir = srcDir()
        println 'srcDir: ' + rootProject.relativePath(srcDir)
    }
}

// Inject special case configuration into a particular project
project(':child2') {
    ext.srcDirName = "$srcDirName/legacy"
}

child1/build.gradle

// Use injected property and method. Here, we override the injected value
srcDirName = 'java'
def dir = srcDir()

gradle -q show の出力

> gradle -q show
project: :child1
srcDirName: java
srcDir: child1/java
project: :child2
srcDirName: src/java/legacy
srcDir: child2/src/java/legacy

60.3. buildSrcプロジェクトのソースをビルドするBuild sources in the buildSrc project

When you run Gradle, it checks for the existence of a directory called buildSrc. Gradle then automatically compiles and tests this code and puts it in the classpath of your build script. You don't need to provide any further instruction. This can be a good place to add your custom tasks and plugins.

Gradleは実行されたときにbuildSrcというディレクトリがないか確認します。 そして、中のコードを自動的にコンパイル、テストして、ビルドスクリプトのクラスパスに追加してくれるのです。 何かを設定する必要は一切ありません。このディレクトリは、カスタムタスクやプラグインを追加するのに適しています。

For multi-project builds there can be only one buildSrc directory, which has to be in the root project directory.

マルチプロジェクトのビルドでは、ルートプロジェクトに一つだけbuildSrcディレクトリを作成できます。

Listed below is the default build script that Gradle applies to the buildSrc project:

buildSrcプロジェクトには、次のビルドスクリプトがデフォルトで適用されます。

図60.1 デフォルトのbuildSrcビルドスクリプトDefault buildSrc build script

apply plugin: 'groovy'
dependencies {
    compile gradleApi()
    compile localGroovy()
}

This means that you can just put your build source code in this directory and stick to the layout convention for a Java/Groovy project (see 表23.4「Javaプラグイン - デフォルトプロジェクトレイアウトJava plugin - default project layout).

つまり、Java/Groovyプロジェクトのレイアウト規約(表23.4「Javaプラグイン - デフォルトプロジェクトレイアウトJava plugin - default project layout参照)に従って、このディレクトリにソースコードを置くだけでいいということです。

If you need more flexibility, you can provide your own build.gradle. Gradle applies the default build script regardless of whether there is one specified. This means you only need to declare the extra things you need. Below is an example. Notice that this example does not need to declare a dependency on the Gradle API, as this is done by the default build script:

さらに柔軟性が必要なときは、自分でbuild.gradleを用意することもできます。 上記のデフォルトビルドスクリプトは、この場合でも適用されます。つまり、必要なものだけを追加で定義できるのです。次の例を見てください。Gradle APIへの依存関係は、デフォルトのビルドスクリプトで宣言されているため、ここでは宣言する必要がありません。

例60.3 カスタムbuildSrcビルドスクリプト

buildSrc/build.gradle

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.11'
}

The buildSrc project can be a multi-project build, just like any other regular multi-project build. However, all of the projects that should be on the classpath of the actual build must be runtime dependencies of the root project in buildSrc. You can do this by adding this to the configuration of each project you wish to export:

buildSrcプロジェクトは、マルチプロジェクトにすることができます。このプロジェクトは、普通のGradleマルチプロジェクトと同じように動作します。

ただし、実際のビルドのクラスパスに置きたい全てのプロジェクトを、buildSrcルートプロジェクトのruntime依存関係にしなければなりません。これは、エクスポートしたい全てのプロジェクトの設定に、以下のコードを追加することで実現できます。

例60.4 buildSrcルートプロジェクトにサブプロジェクトを追加する

buildSrc/build.gradle

rootProject.dependencies {
  runtime project(path)
}

ノート: 本例のソースコードは、Gradleのバイナリ配布物またはソース配布物に含まれています。以下の場所をご参照ください。samples/multiProjectBuildSrc


60.4. 別のGradleビルドを、現在のビルドから呼び出して実行するRunning another Gradle build from a build

You can use the GradleBuild task. You can use either of the dir or buildFile properties to specify which build to execute, and the tasks property to specify which tasks to execute.

GradleBuildで別のビルドを呼び出して実行することができます。そのとき、呼び出すビルドのディレクトリやビルドスクリプトに渡すプロパティを指定したり、実行するタスクのタスクプロパティを設定できます。

例60.5 別のビルドを呼び出す

build.gradle

task build(type: GradleBuild) {
    buildFile = 'other.gradle'
    tasks = ['hello']
}

other.gradle

task hello << {
    println "hello from the other build."
}

gradle -q build の出力

> gradle -q build
hello from the other build.

60.5. ビルドスクリプトで外部ライブラリを使うときの依存関係設定External dependencies for the build script

If your build script needs to use external libraries, you can add them to the script's classpath in the build script itself. You do this using the buildscript() method, passing in a closure which declares the build script classpath.

ビルドスクリプトを実行するときに外部のライブラリを使いたいなら、そのライブラリをビルドスクリプトのクラスパスに追加しなければなりません。buildscript()メソッドにクロージャを渡し、その中でビルドスクリプトのクラスパスを設定できます。

例60.6 ビルドスクリプトのクラスパスを宣言する

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
    }
}

The closure passed to the buildscript() method configures a ScriptHandler instance. You declare the build script classpath by adding dependencies to the classpath configuration. This is the same way you declare, for example, the Java compilation classpath. You can use any of the dependency types described in 「依存関係の定義方法 How to declare your dependencies, except project dependencies.

buildscript()メソッドに渡すクロージャは、ScriptHandlerのインスタンスにパラメータなどを設定するものです。ビルドスクリプトのクラスパスを追加するには、classpathで依存関係を宣言しましょう。形式はJavaでコンパイル時のクラスパスを宣言するときなどと同じです。「依存関係の定義方法 How to declare your dependenciesに記載されているタイプの依存関係は、「プロジェクトへの依存」を除けばすべて使えます。

Having declared the build script classpath, you can use the classes in your build script as you would any other classes on the classpath. The following example adds to the previous example, and uses classes from the build script classpath.

外部ライブラリのクラスをビルドスクリプトのクラスパスを追加すれば、他のクラスと同じようにスクリプト内で使えるようになります。次の例では、先ほどの例に加え、クラスパスに追加したクラスを実際に使っています。

例60.7 外部ライブラリをビルドスクリプトで使用する

build.gradle

import org.apache.commons.codec.binary.Base64

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
    }
}

task encode << {
    def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
    println new String(encodedString)
}

gradle -q encode の出力

> gradle -q encode
aGVsbG8gd29ybGQK

For multi-project builds, the dependencies declared in the a project's build script, are available to the build scripts of all sub-projects.

マルチプロジェクトの場合、プロジェクトのビルドスクリプトでクラスパスを宣言すれば、そのサブプロジェクトすべてで追加したライブラリを使用できるようになります。

60.6. Antオプショナルタスクの依存関係Ant optional dependencies

For reasons we don't fully understand yet, external dependencies are not picked up by Ant's optional tasks. But you can easily do it in another way. [42]

何でかよく分からないのですが、Antのオプショナルタスクでは先ほどの方法で追加した外部ライブラリを拾ってくれないようです。しかし、次のようにすればこの問題は簡単に解決できます。

例60.8 Ant optional dependencies

build.gradle

configurations {
    ftpAntTask
}

dependencies {
    ftpAntTask("org.apache.ant:ant-commons-net:1.9.3") {
        module("commons-net:commons-net:1.4.1") {
            dependencies "oro:oro:2.0.8:jar"
        }
    }
}

task ftp << {
    ant {
        taskdef(name: 'ftp',
                classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
                classpath: configurations.ftpAntTask.asPath)
        ftp(server: "ftp.apache.org", userid: "anonymous", password: "me@myorg.com") {
            fileset(dir: "htdocs/manual")
        }
    }
}

This is also a good example for the usage of client modules. The POM file in Maven Central for the ant-commons-net task does not provide the right information for this use case.

これはクライアントモジュールのいい使用例でもあります。mavenのセントラルリポジトリにあるant-commons-netタスクのpom.xmlは、このユースケースのための正しい情報を提供していません。

60.7. まとめSummary

Gradle offers you a variety of ways of organizing your build logic. You can choose what is right for your domain and find the right balance between unnecessary indirections, and avoiding redundancy and a hard to maintain code base. It is our experience that even very complex custom build logic is rarely shared between different builds. Other build tools enforce a separation of this build logic into a separate project. Gradle spares you this unnecessary overhead and indirection.

Gradleは、ビルドロジックを体系化するための様々な方法を提供しています。自分の分野に合った方法を選択して、不必要に複雑にならないように、一方でコードベースの重複やメンテナンス性の低下を避けられるように、適切なバランスを探っていくことができます。これまでの経験では、非常に複雑なビルドロジックでさえ、異なるビルド間で共有することはあまりありませんでした。いくつかのビルドツールでは、このビルドロジックを、ひとつの別のプロジェクトにして分離します。Gradleでは、そのような無駄なオーバーヘッドや複雑さを省くことができます。



[40] Which might range from a single class to something very complex.

[41] 一つの単純なクラスから、複雑なクラスまで

[42] In fact, we think this is a better solution. Only if your buildscript and Ant's optional task need the same library would you have to define it twice. In such a case it would be nice if Ant's optional task would automatically pick up the classpath defined in the “gradle.settings” file.