Skip to content

Commit

Permalink
Add spotless.resolveDependenciesIn as a workaround Gradle 6 constra…
Browse files Browse the repository at this point in the history
…ints
  • Loading branch information
nedtwigg committed Jan 1, 2020
1 parent 957c0f4 commit e3f9f67
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,43 +23,79 @@
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.util.GradleVersion;

import com.diffplug.common.base.StringPrinter;
import com.diffplug.common.base.Unhandled;
import com.diffplug.spotless.Provisioner;

/** Gradle integration for Provisioner. */
public class GradleProvisioner {
/** Determines where Spotless will resolve its dependencies. */
public enum ResolveDependenciesIn {
/** Spotless dependencies will be resolved from the root buildscript (deprecated for multi-project builds in 6.0). */
ROOT_BUILDSCRIPT,
/** Spotless dependencies will be resolved from the project buildscript (by default subprojects don't have even a single repository). */
PROJECT_BUILDSCRIPT,
/** Spotless dependencies will be resolved from the normal repositories of this project. */
PROJECT
}

static final GradleVersion STRICT_CONFIG_ACCESS = GradleVersion.version("6.0");

private GradleProvisioner() {}

public static Provisioner fromProject(Project project) {
ResolveDependenciesIn mode = project.getPlugins().getPlugin(SpotlessPlugin.class).getExtension().getResolveDependenciesIn();
Objects.requireNonNull(project);
return (withTransitives, mavenCoords) -> {
try {
Dependency[] deps = mavenCoords.stream()
.map(project.getBuildscript().getDependencies()::create)
.toArray(Dependency[]::new);

// #372 workaround: Accessing rootProject.configurations from multiple projects is not thread-safe
ConfigurationContainer configContainer;
synchronized (project.getRootProject()) {
configContainer = project.getRootProject().getBuildscript().getConfigurations();
if (mode == null || mode == ResolveDependenciesIn.ROOT_BUILDSCRIPT) {
// #372 workaround: Accessing rootProject.configurations from multiple projects is not thread-safe
synchronized (project.getRootProject()) {
configContainer = project.getRootProject().getBuildscript().getConfigurations();
}
} else if (mode == ResolveDependenciesIn.PROJECT_BUILDSCRIPT) {
configContainer = project.getBuildscript().getConfigurations();
} else if (mode == ResolveDependenciesIn.PROJECT) {
configContainer = project.getConfigurations();
} else {
throw Unhandled.enumException(mode);
}
Configuration config = configContainer.detachedConfiguration(deps);
config.setDescription(mavenCoords.toString());
config.setTransitive(withTransitives);
return config.resolve();
} catch (Exception e) {
logger.log(Level.SEVERE,
StringPrinter.buildStringFromLines("You probably need to add a repository containing the '" + mavenCoords + "' artifact in the 'build.gradle' of your root project.",
"E.g.: 'buildscript { repositories { mavenCentral() }}'",
"Note that included buildscripts (using 'apply from') do not share their buildscript repositories with the underlying project.",
"You have to specify the missing repository explicitly in the buildscript of the root project."),
e);
String errorMsg;
if (mode == null || mode == ResolveDependenciesIn.ROOT_BUILDSCRIPT) {
errorMsg = StringPrinter.buildStringFromLines(
"You need to add a repository containing the '" + mavenCoords + "' artifact to the `buildscript{}` block of the build.gradle of your root project.",
"E.g.: 'buildscript { repositories { mavenCentral() }}'",
"Note that included buildscripts (using 'apply from') do not share their buildscript repositories with the underlying project.",
"You have to specify the missing repository explicitly in the buildscript of the root project.");
} else if (mode == ResolveDependenciesIn.PROJECT_BUILDSCRIPT) {
errorMsg = StringPrinter.buildStringFromLines(
"You need to add a repository containing the '" + mavenCoords + "' artifact to the `buildscript{}` block of the build.gradle of this project.",
"E.g.: 'buildscript { repositories { mavenCentral() }}'",
"By default, subprojects dont have any repositories in their buildscript whatsoever.");
} else if (mode == ResolveDependenciesIn.PROJECT) {
errorMsg = StringPrinter.buildStringFromLines(
"You need to add a repository containing the '" + mavenCoords + "' artifact to the `repositories{}` block of the build.gradle of this project.",
"E.g.: 'repositories { mavenCentral() }'");
} else {
throw Unhandled.enumException(mode);
}
logger.log(Level.SEVERE, errorMsg);
throw e;
}
};
}

private static final Logger logger = Logger.getLogger(GradleProvisioner.class.getName());

static final Logger logger = Logger.getLogger(GradleProvisioner.class.getName());
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import java.util.LinkedHashMap;
import java.util.Map;

import javax.annotation.Nullable;

import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
Expand Down Expand Up @@ -60,6 +62,18 @@ public SpotlessExtension(Project project) {
rootApplyTask.setDescription(APPLY_DESCRIPTION);
}

/** Repository mode. */
GradleProvisioner.ResolveDependenciesIn resolveDependenciesIn = null;

@Nullable
public GradleProvisioner.ResolveDependenciesIn getResolveDependenciesIn() {
return resolveDependenciesIn;
}

public void setResolveDependenciesIn(GradleProvisioner.ResolveDependenciesIn repositoryMode) {
this.resolveDependenciesIn = requireNonNull(repositoryMode);
}

/** Line endings (if any). */
LineEnding lineEndings = LineEnding.GIT_ATTRIBUTES;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.dsl.RepositoryHandler;
import org.gradle.api.artifacts.repositories.ArtifactRepository;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.util.GradleVersion;

import com.diffplug.common.base.StringPrinter;
import com.diffplug.spotless.SpotlessCache;

public class SpotlessPlugin implements Plugin<Project> {
Expand Down Expand Up @@ -56,9 +60,61 @@ public void apply(Project project) {
SpotlessPluginLegacy.enforceCheck(spotlessExtension, project);
}
}

// the user hasn't specified where to resolve deps and they're going to get a deprecation warning
if (spotlessExtension.resolveDependenciesIn == null
&& project != project.getRootProject()
&& GradleVersion.current().compareTo(GradleProvisioner.STRICT_CONFIG_ACCESS) >= 0) {
boolean safeToForceProjectLocal = isMavenCentralSuperset(project.getRootProject().getBuildscript().getRepositories()) &&
isMavenCentralSuperset(project.getRepositories());
if (safeToForceProjectLocal) {
// if the root buildscript is just `gradlePluginPortal()` (a superset of mavenCental)
// and the project repositories are either empty or some superset of mavenCentral
// then there's no harm in defaulting them to project-local resolution
spotlessExtension.resolveDependenciesIn = GradleProvisioner.ResolveDependenciesIn.PROJECT;
} else {
GradleProvisioner.logger.severe(StringPrinter.buildStringFromLines(
"The way that Spotless resolves formatter dependencies was deprecated in Gradle 6.0.",
"To silence this warning, you need to set X: `spotless { resolveDependenciesIn 'X' }`",
" 'ROOT_BUILDSCRIPT' - current behavior, causes gradle to emit deprecation warnings.",
" 'PROJECT' *recommended* - uses this project's repositories to resolve dependencies.",
" Only drawback is for the uncommon case that you want your",
" build tools to draw from a different set of respositories than ",
" than your build artifacts.",
" 'PROJECT_BUILDSCRIPT' - uses this project's buildscript to resolve dependencies.",
" You will probably need to add this:",
" `buildscript { repositories { mavenCentral() } }`",
" to the top of the `build.gradle` in this subproject.",
"We're hoping to find a better resolution in the future, see issue below for more info",
" https://github.com/diffplug/spotless/issues/502"));
}
}
});
}

private boolean isMavenCentralSuperset(RepositoryHandler repositories) {
return repositories.stream().allMatch(SpotlessPlugin::isMavenCentralSuperset);
}

private static boolean isMavenCentralSuperset(ArtifactRepository repository) {
if (!(repository instanceof MavenArtifactRepository)) {
return false;
}
MavenArtifactRepository maven = (MavenArtifactRepository) repository;
if ("MavenRepo".equals(maven.getName())
&& "https://repo.maven.apache.org/maven2/".equals(maven.getUrl().toString())) {
return true;
} else if ("BintrayJCenter".equals(maven.getName())
&& "https://jcenter.bintray.com/".equals(maven.getUrl().toString())) {
return true;
} else if ("Gradle Central Plugin Repository".equals(maven.getName())
&& "https://plugins.gradle.org/m2".equals(maven.getUrl().toString())) {
return true;
} else {
return false;
}
}

/** The extension for this plugin. */
public SpotlessExtension getExtension() {
return spotlessExtension;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2016 DiffPlug
*
* 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 com.diffplug.gradle.spotless;

import java.io.IOException;

import org.assertj.core.api.Assertions;
import org.gradle.testkit.runner.GradleRunner;
import org.junit.Test;

import com.diffplug.spotless.ResourceHarness;

public class DefaultRepoConfig extends ResourceHarness {
@Test
public void integration() throws IOException {
setFile("build.gradle").toLines(
"repositories {",
" mavenCentral()",
" jcenter()",
" gradlePluginPortal()",
"}",
"",
"println('buildscriptSize=' + buildscript.repositories.size())",
"for (repo in buildscript.repositories) {",
" println(repo.toString())",
" println(repo.class)",
"}",
"println('projectSize=' + repositories.size())",
"for (repo in repositories) {",
" println(repo.name)",
" println(repo.url)",
" println(repo.artifactUrls)",
"}");

String output = GradleRunner.create()
.withGradleVersion("6.0")
.withProjectDir(rootFolder())
.build().getOutput();
Assertions.assertThat(output.replace("\r\n", "\n")).startsWith("\n" +
"> Configure project :\n" +
"buildscriptSize=0\n" +
"projectSize=3\n" +
"MavenRepo\n" +
"https://repo.maven.apache.org/maven2/\n" +
"[]\n" +
"BintrayJCenter\n" +
"https://jcenter.bintray.com/\n" +
"[]\n" +
"Gradle Central Plugin Repository\n" +
"https://plugins.gradle.org/m2\n" +
"[]\n" +
"");
}
}

0 comments on commit e3f9f67

Please sign in to comment.