From 654c07db344310d8582c433dc643b0a256c8cc7e Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Fri, 11 Jan 2013 15:54:11 +0100 Subject: [PATCH] Refactor detect-split-packages Gradle plugin - Use conventional plugin naming, i.e. "detect-split-packages" instead of applying plugin based on fully-qualified class name - Rename "diagnose" => "detect" consistently throughout plugin, task and method names and generally refactor naming throughout to follow "detect split packages" phrasing - Add Javadoc to DetectSplitPackagesPlugin - Improve error reporting when split packages are detected Upon detecting one or more split packages, `detectSplitPackages` now fails idiomatically, throwing a GradleException to signal task failure (as opposed to the previous approach of using an assert assertion), and the output reads as follows: $ gradle detectSplitPackages [...] :buildSrc:build UP-TO-DATE :detectSplitPackages FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':detectSplitPackages'. > The following split package(s) have been detected: - org.springframework.beans (split across spring-beans and spring-orm) - org.springframework.core.env (split across spring-context and spring-core) - DetectSplitPackagesTask now automatically attaches itself to `check` task lifecycle if the enclosing project contains a `check` task - DetectSplitPackagesTask adds itself to the 'Verification' task group, ensuring that it shows up correctly in `gradle tasks` task listings - packagesToScan now defaults to all subprojects; users may then customize this by removing individual subprojects from the collection Issue: SPR-9990 --- build.gradle | 8 +- .../gradle/DetectSplitPackagesPlugin.groovy | 157 ++++++++++++++++++ .../gradle/SplitPackageDetectorPlugin.groovy | 131 --------------- .../detect-split-packages.properties | 1 + 4 files changed, 161 insertions(+), 136 deletions(-) create mode 100644 buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy delete mode 100644 buildSrc/src/main/groovy/org/springframework/build/gradle/SplitPackageDetectorPlugin.groovy create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/detect-split-packages.properties diff --git a/build.gradle b/build.gradle index f67e2740c0c..11e9ed8becd 100644 --- a/build.gradle +++ b/build.gradle @@ -752,16 +752,16 @@ configure(rootProject) { apply plugin: "docbook-reference" apply plugin: "groovy" + apply plugin: "detect-split-packages" apply from: "${gradleScriptDir}/jdiff.gradle" - apply plugin: org.springframework.build.gradle.SplitPackageDetectorPlugin reference { sourceDir = file("src/reference/docbook") pdfFilename = "spring-framework-reference.pdf" } - diagnoseSplitPackages { - projectsToScan = project.subprojects - project(":spring-instrument-tomcat") // SPR-10150 + detectSplitPackages { + projectsToScan -= project(":spring-instrument-tomcat") } // don"t publish the default jar for the root project @@ -788,8 +788,6 @@ configure(rootProject) { testCompile("hsqldb:hsqldb:${hsqldbVersion}") } - check.dependsOn diagnoseSplitPackages - task api(type: Javadoc) { group = "Documentation" description = "Generates aggregated Javadoc API documentation." diff --git a/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy b/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy new file mode 100644 index 00000000000..8b73878694e --- /dev/null +++ b/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.build.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +/** + * Gradle plugin that detects identically named, non-empty packages split across multiple + * subprojects, e.g. "org.springframework.context.annotation" existing in both spring-core + * and spring-aspects. Adds a 'detectSplitPackages' task to the current project's task + * collection. If the project already contains a 'check' task (i.e. is a typical Gradle + * project with the "java" plugin applied), the 'check' task will be updated to depend on + * the execution of 'detectSplitPackages'. + * + * By default, all subprojects will be scanned. Use the 'projectsToScan' task property to + * modify this value. Example usage: + * + * apply plugin: 'detect-split-packages // typically applied to root project + * + * detectSplitPackages { + * packagesToScan -= project(":spring-xyz") // scan every project but spring-xyz + * } + * + * @author Rob Winch + * @author Glyn Normington + * @author Chris Beams + */ +public class DetectSplitPackagesPlugin implements Plugin { + public void apply(Project project) { + def tasks = project.tasks + Task detectSplitPackages = tasks.add('detectSplitPackages', DetectSplitPackagesTask.class) + if (tasks.asMap.containsKey('check')) { + tasks.getByName('check').dependsOn detectSplitPackages + } + } +} + +public class DetectSplitPackagesTask extends DefaultTask { + + private static final String JAVA_FILE_SUFFIX = ".java" + private static final String PACKAGE_SEPARATOR = "." + private static final String HIDDEN_DIRECTORY_PREFIX = "." + + @Input + Set projectsToScan = project.subprojects + + public DetectSplitPackagesTask() { + this.group = 'Verification' + this.description = 'Detects packages split across two or more subprojects.' + } + + @TaskAction + public void detectSplitPackages() { + def splitPackages = doDetectSplitPackages() + if (!splitPackages.isEmpty()) { + def message = "The following split package(s) have been detected:\n" + splitPackages.each { pkg, mod -> + message += " - ${pkg} (split across ${mod[0].name} and ${mod[1].name})\n" + } + throw new GradleException(message) + } + } + + private Map> doDetectSplitPackages() { + def splitPackages = [:] + def mergedProjects = findMergedProjects() + def packagesByProject = mapPackagesByProject() + + def projects = packagesByProject.keySet().toArray() + def nProjects = projects.length + + for (int i = 0; i < nProjects - 1; i++) { + for (int j = i + 1; j < nProjects - 1; j++) { + def prj_i = projects[i] + def prj_j = projects[j] + + def pkgs_i = new HashSet(packagesByProject.get(prj_i)) + def pkgs_j = packagesByProject.get(prj_j) + pkgs_i.retainAll(pkgs_j) + + if (!pkgs_i.isEmpty() + && mergedProjects.get(prj_i) != prj_j + && mergedProjects.get(prj_j) != prj_i) { + pkgs_i.each { pkg -> + def readablePkg = pkg.substring(1).replaceAll(File.separator, PACKAGE_SEPARATOR) + splitPackages[readablePkg] = [prj_i, prj_j] + } + } + } + } + return splitPackages; + } + + private Map> mapPackagesByProject() { + def packagesByProject = [:] + this.projectsToScan.each { Project p -> + def packages = new HashSet() + p.sourceSets.main.java.srcDirs.each { File dir -> + findPackages(packages, dir, "") + } + if (!packages.isEmpty()) { + packagesByProject.put(p, packages) + } + } + return packagesByProject; + } + + private Map findMergedProjects() { + def mergedProjects = [:] + this.projectsToScan.findAll { p -> + p.plugins.findPlugin(MergePlugin) + }.findAll { p -> + p.merge.into + }.each { p -> + mergedProjects.put(p, p.merge.into) + } + return mergedProjects + } + + private static void findPackages(Set packages, File dir, String packagePath) { + def scanDir = new File(dir, packagePath) + def File[] javaFiles = scanDir.listFiles({ file -> + !file.isDirectory() && file.name.endsWith(JAVA_FILE_SUFFIX) + } as FileFilter) + + if (javaFiles != null && javaFiles.length != 0) { + packages.add(packagePath) + } + + scanDir.listFiles({ File file -> + file.isDirectory() && !file.name.startsWith(HIDDEN_DIRECTORY_PREFIX) + } as FileFilter).each { File subDir -> + findPackages(packages, dir, packagePath + File.separator + subDir.name) + } + } +} + diff --git a/buildSrc/src/main/groovy/org/springframework/build/gradle/SplitPackageDetectorPlugin.groovy b/buildSrc/src/main/groovy/org/springframework/build/gradle/SplitPackageDetectorPlugin.groovy deleted file mode 100644 index eddfec385c4..00000000000 --- a/buildSrc/src/main/groovy/org/springframework/build/gradle/SplitPackageDetectorPlugin.groovy +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.build.gradle - -import org.gradle.api.DefaultTask -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ProjectDependency -import org.gradle.api.artifacts.maven.Conf2ScopeMapping -import org.gradle.api.plugins.MavenPlugin -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction -import org.gradle.plugins.ide.eclipse.EclipsePlugin -import org.gradle.plugins.ide.eclipse.model.EclipseClasspath -import org.gradle.plugins.ide.idea.IdeaPlugin - -class SplitPackageDetectorPlugin implements Plugin { - public void apply(Project project) { - Task diagnoseSplitPackages = project.tasks.add('diagnoseSplitPackages', SplitPackageDetectorTask.class) - diagnoseSplitPackages.setDescription('Detects packages which will be split across JARs') - } -} - -public class SplitPackageDetectorTask extends DefaultTask { - @Input - Set projectsToScan - - @TaskAction - public final void diagnoseSplitPackages() { - def Map mergeMap = [:] - def projects = projectsToScan.findAll { it.plugins.findPlugin(org.springframework.build.gradle.MergePlugin) }.findAll { it.merge.into } - projects.each { p -> - mergeMap.put(p, p.merge.into) - } - def splitFound = new org.springframework.build.gradle.SplitPackageDetector(projectsToScan, mergeMap, project.logger).diagnoseSplitPackages(); - assert !splitFound // see error log messages for details of split packages - } -} - -class SplitPackageDetector { - - private static final String HIDDEN_DIRECTORY_PREFIX = "." - - private static final String JAVA_FILE_SUFFIX = ".java" - - private static final String SRC_MAIN_JAVA = "src" + File.separator + "main" + File.separator + "java" - - private static final String PACKAGE_SEPARATOR = "." - - private final Map mergeMap - - private final Map> pkgMap = [:] - - private final logger - - SplitPackageDetector(projectsToScan, mergeMap, logger) { - this.mergeMap = mergeMap - this.logger = logger - projectsToScan.each { Project p -> - def dir = p.projectDir - def packages = getPackagesInDirectory(dir) - if (!packages.isEmpty()) { - pkgMap.put(p, packages) - } - } - } - - private File[] dirList(String dir) { - dirList(new File(dir)) - } - - private File[] dirList(File dir) { - dir.listFiles({ file -> file.isDirectory() && !file.getName().startsWith(HIDDEN_DIRECTORY_PREFIX) } as FileFilter) - } - - private Set getPackagesInDirectory(File dir) { - def pkgs = new HashSet() - addPackagesInDirectory(pkgs, new File(dir, SRC_MAIN_JAVA), "") - return pkgs; - } - - boolean diagnoseSplitPackages() { - def splitFound = false; - def projs = pkgMap.keySet().toArray() - def numProjects = projs.length - for (int i = 0; i < numProjects - 1; i++) { - for (int j = i + 1; j < numProjects - 1; j++) { - def pi = projs[i] - def pkgi = new HashSet(pkgMap.get(pi)) - def pj = projs[j] - def pkgj = pkgMap.get(pj) - pkgi.retainAll(pkgj) - if (!pkgi.isEmpty() && mergeMap.get(pi) != pj && mergeMap.get(pj) != pi) { - pkgi.each { pkg -> - def readablePkg = pkg.substring(1).replaceAll(File.separator, PACKAGE_SEPARATOR) - logger.error("Package '$readablePkg' is split between $pi and $pj") - } - splitFound = true - } - } - } - return splitFound - } - - private void addPackagesInDirectory(HashSet packages, File dir, String pkg) { - def scanDir = new File(dir, pkg) - def File[] javaFiles = scanDir.listFiles({ file -> !file.isDirectory() && file.getName().endsWith(JAVA_FILE_SUFFIX) } as FileFilter) - if (javaFiles != null && javaFiles.length != 0) { - packages.add(pkg) - } - dirList(scanDir).each { File subDir -> - addPackagesInDirectory(packages, dir, pkg + File.separator + subDir.getName()) - } - } -} diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/detect-split-packages.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/detect-split-packages.properties new file mode 100644 index 00000000000..10b1e4e981d --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/detect-split-packages.properties @@ -0,0 +1 @@ +implementation-class=org.springframework.build.gradle.DetectSplitPackagesPlugin