Browse Source
- 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
pull/98/head
4 changed files with 161 additions and 136 deletions
@ -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<Project> { |
||||||
|
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<Project> 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<String, List<Project>> 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<Project, Set<String>> mapPackagesByProject() { |
||||||
|
def packagesByProject = [:] |
||||||
|
this.projectsToScan.each { Project p -> |
||||||
|
def packages = new HashSet<String>() |
||||||
|
p.sourceSets.main.java.srcDirs.each { File dir -> |
||||||
|
findPackages(packages, dir, "") |
||||||
|
} |
||||||
|
if (!packages.isEmpty()) { |
||||||
|
packagesByProject.put(p, packages) |
||||||
|
} |
||||||
|
} |
||||||
|
return packagesByProject; |
||||||
|
} |
||||||
|
|
||||||
|
private Map<Project, Project> 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<String> 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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -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<Project> { |
|
||||||
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<Project> projectsToScan |
|
||||||
|
|
||||||
@TaskAction |
|
||||||
public final void diagnoseSplitPackages() { |
|
||||||
def Map<Project, Project> 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<Project, Project> mergeMap |
|
||||||
|
|
||||||
private final Map<Project, Set<String>> 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<String> getPackagesInDirectory(File dir) { |
|
||||||
def pkgs = new HashSet<String>() |
|
||||||
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<String> 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()) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue