Gradle:プロジェクト間の共通処理パターンを実現する

複数のプロジェクトがあり、それぞれにbuild.gradleを作成するわけだが、ほとんど同じことを毎度毎度書かなくてはいけないのは辛い。なんとか簡単に済ませる方法は無いものか。

処理の例

例えば、eclipseプラグインでecipseの.project、.classpathをgradleに作成させるものとする。さらにその制御を細かくしたいのだが、複数プロジェクトでほとんど同じだ。例えば、以下のように記述する。

apply plugin: 'eclipse'
....
eclipse {
  project {
    ....
    file {
      whenMerged {
        ....
      }
    }
  }
  classpath {
    defaultOutputDir = file('output')
    downloadSources = true   
    file {
      whenMerged { cp->
        ....
      }
    }
  }      
}

結論から言えば、以下のようにしたいのである。新たなメソッドeclipseExを導入し、そのクロージャの中でプロジェクトごとに異なる部分だけを記述したい。

さらにこれをcommon.gradleといった共通ファイルを通して行いたい。

apply from: 'common.gradle'
....
eclipseEx {
  output = file('output2')
  extraSources = [ 'src_test' ]
  ....
}

これをどうするか?

実際のコード

以下のようになる。

build.gradle

apply from: 'common.gradle'
apply plugin: 'java'
apply plugin: EclipseExPlugin

repositories {
  mavenCentral()
}

sourceSets {
  main {
    java {
      srcDir 'src'
    }
    resources {
      srcDir 'src'
    }    
  }
}

dependencies {
  compile('javax.inject:javax.inject:1')
  compile('com.google.inject:guice:4.0')
}

eclipseEx {
  output = file('output')
  extraProjects = ['/gwtcenter_bwidget']
  extraSources = ['src_test'];
}

common.gradle


ext.EclipseExPlugin = EclipseExPlugin class EclipseExPlugin implements Plugin<Project> { def void apply(Project project) { project.apply(plugin:'eclipse') project.convention.plugins.eclipseExConvention = new EclipseExConvension(project) } } /* EclipseExのコンベンション */ class EclipseExConvension { /* プロジェクト */ Project project /* 出力先 */ File output; /* 追加プロジェクトリスト */ List extraProjects; /* 追加ソースフォルダリスト */ List extraSources; /* GWT用natureを追加 */ boolean gwtNature; EclipseExConvension(project) { this.project = project } def eclipseEx(Closure closure) { closure.delegate = this closure() project.eclipse { project { gwtNatureBuildCommand(delegate) file { whenMerged { addCommonGradle(delegate) } } } classpath { defaultOutputDir = output downloadSources = true file { whenMerged { cp-> removeOutputFoldersForSourceFolders(cp) removeDuplicatedSources(cp) removeAllExcludes(cp) appendExtraSources(cp) appendExtraProjects(cp) sortProjectEntries(cp) } } } } } // eclipseEx /* GWT用のNature、ビルドコマンドを追加 */ def gwtNatureBuildCommand(p) { if (!gwtNature) return; p.natures 'org.eclipse.wst.common.project.facet.core.nature' p.buildCommand 'org.eclipse.wst.common.project.facet.core.builder' p.buildCommand 'com.gwtplugins.gdt.eclipse.core.webAppProjectValidator' } /* common.gradleへの参照を追加 */ def addCommonGradle(proj) { proj.linkedResources.add(new org.gradle.plugins.ide.eclipse.model.Link( 'common.gradle', '1', 'J:/OwnCloud/CryptoFiles/gradle/common.gradle', null)); } /* ソースフォルダの出力指定を削除する。こうしないと、フォースフォルダごとに別のフォルダに出力されてしまう */ def removeOutputFoldersForSourceFolders(cp) { cp.entries.findAll{ it.kind == 'src' }.each{ it.output = null } } /* ソースフォルダが重複してしまうことがあるので重複分を削除する */ def removeDuplicatedSources(cp) { def newList = new ArrayList() def duplicated = new HashSet() cp.entries.each { e-> if (e.kind != 'src') { newList.add(e); return } if (duplicated.contains(e.path)) return; newList.add(e); duplicated.add(e.path); } cp.entries = newList; } /* 追加のソースフォルダ */ def appendExtraSources(cp) { if (extraSources == null) return; extraSources.each { cp.entries.add(new org.gradle.plugins.ide.eclipse.model.SourceFolder(it, null)) } } /* 追加のプロジェクト */ def appendExtraProjects(cp) { if (extraProjects == null) return; extraProjects.each { cp.entries.add( new org.gradle.plugins.ide.eclipse.model.SourceFolder(it, null) ) } } // srcDir 'src'; exclude '**/*Test.java';のexcludeを削除する。 // IDE上ではテストユニットが見えていて欲しい def removeAllExcludes(cp) { cp.entries.findAll{ it.kind == 'src' }.each{ it.setExcludes(new ArrayList()) } } /* ライブラリ他をアルファベット順にソートする。アルファベット順でないと非常に見つけにくい これは単純にEclipseの.projectファイルの内容をソートするもの。追加・削除等は行わない。 */ def sortProjectEntries(cp) { Map srcMap = new HashMap(); Map libMap = new HashMap(); List others = new ArrayList(); cp.entries.each { if (it.kind == 'lib') { String path = it.path; int index = path.lastIndexOf('/') libMap.put(path.substring(index + 1), it); } else if (it.kind == 'src') { srcMap.put(it.path, it); } else { others.add(it); } } def newList = new ArrayList(); // ソースフォルダをソートして入れる srcMap.keySet().sort({ a, b -> a.compareToIgnoreCase b }).each{ newList.add(srcMap.get(it)) } // ライブラリ以外を入れる newList.addAll(others); // ライブラリをソートして入れる libMap.keySet().sort({ a, b -> a.compareToIgnoreCase b }).each{ newList.add(libMap.get(it)) } cp.entries = newList } }