Skip to content

Commit

Permalink
Resolve dependencies per-project (#983)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Nov 9, 2021
2 parents 7cc87da + 33fba68 commit 24d1ea5
Show file tree
Hide file tree
Showing 17 changed files with 206 additions and 164 deletions.
2 changes: 1 addition & 1 deletion plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]
### Changed
* **BREAKING** Previously, many projects required `buildscript { repositories { mavenCentral() }}` at the top of their root project, because Spotless resolved its dependencies using the buildscript repositories. Spotless now resolves its dependencies from the normal project repositories of the root project, which means that you can remove the `buildscript {}` block, but you still need `repositories { mavenCentral() }` (or similar) in the root project.
* **BREAKING** Previously, many projects required `buildscript { repositories { mavenCentral() }}` at the top of their root project, because Spotless resolved its dependencies using the buildscript repositories. Spotless now resolves its dependencies from the normal project repositories of each project with a `spotless {...}` block. This means that you can remove the `buildscript {}` block, but you still need a `repositories { mavenCentral() }` (or similar) in each project which is using Spotless.
* **BREAKING** `createIndepentApplyTask(String taskName)` now requires that `taskName` does not end with `Apply`
* Bump minimum required Gradle from `6.1` to `6.1.1`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public FormatExtension(SpotlessExtension spotless) {
}

protected final Provisioner provisioner() {
return spotless.getRegisterDependenciesTask().rootProvisioner;
return spotless.getRegisterDependenciesTask().getTaskService().get().provisionerFor(spotless.project);
}

private String formatName() {
Expand Down Expand Up @@ -733,11 +733,9 @@ protected void setupTask(SpotlessTask task) {
}
task.setSteps(steps);
task.setLineEndingsPolicy(getLineEndings().createPolicy(getProject().getProjectDir(), () -> totalTarget));
if (spotless.project != spotless.project.getRootProject()) {
spotless.getRegisterDependenciesTask().hookSubprojectTask(task);
}
spotless.getRegisterDependenciesTask().hookSubprojectTask(task);
if (getRatchetFrom() != null) {
task.setupRatchet(spotless.getRegisterDependenciesTask().getGitRatchet().get(), getRatchetFrom());
task.setupRatchet(getRatchetFrom());
}
}

Expand All @@ -763,7 +761,7 @@ public SpotlessApply createIndependentApplyTask(String taskName) {
Preconditions.checkArgument(!taskName.endsWith(SpotlessExtension.APPLY), "Task name must not end with " + SpotlessExtension.APPLY);
// create and setup the task
SpotlessTaskImpl spotlessTask = spotless.project.getTasks().create(taskName + SpotlessTaskService.INDEPENDENT_HELPER, SpotlessTaskImpl.class);
spotlessTask.init(spotless.getTaskService());
spotlessTask.init(spotless.getRegisterDependenciesTask().getTaskService());
setupTask(spotlessTask);
// enforce the clean ordering
Task clean = spotless.project.getTasks().getByName(BasePlugin.CLEAN_TASK_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,10 @@

import javax.annotation.Nullable;

import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.gradle.tooling.events.FinishEvent;
import org.gradle.tooling.events.OperationCompletionListener;

import com.diffplug.spotless.extra.GitRatchet;

/** Gradle implementation of GitRatchet. */
public abstract class GitRatchetGradle extends GitRatchet<File> implements BuildService<BuildServiceParameters.None>, OperationCompletionListener {
public class GitRatchetGradle extends GitRatchet<File> {
@Override
protected File getDir(File project) {
return project;
Expand All @@ -37,9 +32,4 @@ protected File getDir(File project) {
protected @Nullable File getParent(File project) {
return project.getParentFile();
}

@Override
public void onFinish(FinishEvent finishEvent) {
// NOOP
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,49 +27,45 @@
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;

import com.diffplug.common.base.Preconditions;
import com.diffplug.common.collect.ImmutableList;
import com.diffplug.spotless.Provisioner;

/** Should be package-private. */
class GradleProvisioner {
private GradleProvisioner() {}

/** The provisioner used for the root project. */
static class RootProvisioner implements Provisioner {
private final Project rootProject;
static Provisioner newDedupingProvisioner(Project project) {
return new DedupingProvisioner(project);
}

static class DedupingProvisioner implements Provisioner {
private final Project project;
private final Map<Request, Set<File>> cache = new HashMap<>();

RootProvisioner(Project rootProject) {
Preconditions.checkArgument(rootProject == rootProject.getRootProject());
this.rootProject = rootProject;
DedupingProvisioner(Project project) {
this.project = project;
}

@Override
public Set<File> provisionWithTransitives(boolean withTransitives, Collection<String> mavenCoordinates) {
Request req = new Request(withTransitives, mavenCoordinates);
Set<File> result;
synchronized (cache) {
result = cache.get(req);
}
Set<File> result = cache.get(req);
if (result != null) {
return result;
} else {
synchronized (cache) {
result = cache.get(req);
if (result != null) {
return result;
} else {
result = GradleProvisioner.forProject(rootProject).provisionWithTransitives(req.withTransitives, req.mavenCoords);
cache.put(req, result);
return result;
}
result = cache.get(req);
if (result != null) {
return result;
} else {
result = forProject(project).provisionWithTransitives(req.withTransitives, req.mavenCoords);
cache.put(req, result);
return result;
}
}
}
}

static Provisioner forProject(Project project) {
private static Provisioner forProject(Project project) {
Objects.requireNonNull(project);
return (withTransitives, mavenCoords) -> {
try {
Expand All @@ -82,10 +78,13 @@ static Provisioner forProject(Project project) {
config.setTransitive(withTransitives);
return config.resolve();
} catch (Exception e) {
String projName = project.getPath();
String projName = project.getPath().substring(1).replace(':', '/');
if (!projName.isEmpty()) {
projName = projName + "/";
}
logger.log(
Level.SEVERE,
"You probably need to add a repository containing the '" + mavenCoords + "' artifact in the 'build.gradle' of the " + projName + " project.\n" +
"You need to add a repository containing the '" + mavenCoords + "' artifact in '" + projName + "build.gradle'.\n" +
"E.g.: 'repositories { mavenCentral() }'",
e);
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ static void performHook(SpotlessTaskImpl spotlessTask) {
}
if (spotlessTask.getTarget().contains(file)) {
try (Formatter formatter = spotlessTask.buildFormatter()) {
if (spotlessTask.ratchet != null) {
if (spotlessTask.ratchet.isClean(spotlessTask.getProjectDir().get().getAsFile(), spotlessTask.rootTreeSha, file)) {
if (spotlessTask.getRatchet() != null) {
if (spotlessTask.getRatchet().isClean(spotlessTask.getProjectDir().get().getAsFile(), spotlessTask.getRootTreeSha(), file)) {
dumpIsClean();
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,19 @@
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import org.gradle.api.DefaultTask;
import org.gradle.api.execution.TaskExecutionGraph;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.services.BuildServiceRegistry;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.build.event.BuildEventsListenerRegistry;

import com.diffplug.common.base.Preconditions;
import com.diffplug.common.io.Files;
import com.diffplug.spotless.FormatterStep;

/**
* NOT AN END-USER TASK, DO NOT USE FOR ANYTHING!
Expand All @@ -50,26 +45,17 @@
public abstract class RegisterDependenciesTask extends DefaultTask {
static final String TASK_NAME = "spotlessInternalRegisterDependencies";

@Input
public List<FormatterStep> getSteps() {
List<FormatterStep> allSteps = new ArrayList<>();
TaskExecutionGraph taskGraph = getProject().getGradle().getTaskGraph();
tasks.stream()
.filter(taskGraph::hasTask)
.sorted()
.forEach(task -> allSteps.addAll(task.getSteps()));
return allSteps;
}

private List<SpotlessTask> tasks = new ArrayList<>();

@Internal
public List<SpotlessTask> getTasks() {
return tasks;
}

void hookSubprojectTask(SpotlessTask task) {
tasks.add(task);
// TODO: in the future, we might use this hook to add an optional perf improvement
// spotlessRoot {
// java { googleJavaFormat('1.2') }
// ...etc
// }
// The point would be to reuse configurations from the root project,
// with the restriction that you have to declare every formatter in
// the root, and you'd get an error if you used a formatter somewhere
// which you didn't declare in the root. That's a problem for the future
// though, not today!
task.dependsOn(this);
}

Expand All @@ -80,30 +66,23 @@ public File getUnitOutput() {
return unitOutput;
}

GradleProvisioner.RootProvisioner rootProvisioner;

@Internal
public GradleProvisioner.RootProvisioner getRootProvisioner() {
return rootProvisioner;
}

void setup() {
Preconditions.checkArgument(getProject().getRootProject() == getProject(), "Can only be used on the root project");
unitOutput = new File(getProject().getBuildDir(), "tmp/spotless-register-dependencies");
rootProvisioner = new GradleProvisioner.RootProvisioner(getProject());
Provider<GitRatchetGradle> gitRatchetProvider = getProject().getGradle().getSharedServices().registerIfAbsent("GitRatchetGradle", GitRatchetGradle.class, unused -> {});
getBuildEventsListenerRegistry().onTaskCompletion(gitRatchetProvider);
getGitRatchet().set(gitRatchetProvider);

BuildServiceRegistry buildServices = getProject().getGradle().getSharedServices();
getTaskService().set(buildServices.registerIfAbsent("SpotlessTaskService", SpotlessTaskService.class, spec -> {}));
getBuildEventsListenerRegistry().onTaskCompletion(getTaskService());
}

@TaskAction
public void trivialFunction() throws IOException {
Files.createParentDirs(unitOutput);
Files.write(Integer.toString(getSteps().size()), unitOutput, StandardCharsets.UTF_8);
Files.write(Integer.toString(1), unitOutput, StandardCharsets.UTF_8);
}

@Internal
public abstract Property<GitRatchetGradle> getGitRatchet();
abstract Property<SpotlessTaskService> getTaskService();

@Inject
protected abstract BuildEventsListenerRegistry getBuildEventsListenerRegistry();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

import com.diffplug.spotless.LineEnding;

Expand All @@ -47,8 +46,6 @@ protected SpotlessExtension(Project project) {
this.project = requireNonNull(project);
}

abstract Provider<SpotlessTaskService> getTaskService();

abstract RegisterDependenciesTask getRegisterDependenciesTask();

/** Line endings (if any). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@
import org.gradle.api.Project;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;

public class SpotlessExtensionImpl extends SpotlessExtension {
private final TaskProvider<RegisterDependenciesTask> registerDependenciesTask;
private final Provider<SpotlessTaskService> taskService;

public SpotlessExtensionImpl(Project project) {
super(project);
Expand Down Expand Up @@ -54,13 +52,6 @@ public SpotlessExtensionImpl(Project project) {
.configure(task -> task.dependsOn(rootCheckTask));
}
});

taskService = project.getGradle().getSharedServices().registerIfAbsent("SpotlessTaskService", SpotlessTaskService.class, spec -> {});
}

@Override
Provider<SpotlessTaskService> getTaskService() {
return taskService;
}

final TaskProvider<?> rootCheckTask, rootApplyTask, rootDiagnoseTask;
Expand All @@ -78,7 +69,7 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) {
// create the SpotlessTask
String taskName = EXTENSION + SpotlessPlugin.capitalize(name);
TaskProvider<SpotlessTaskImpl> spotlessTask = tasks.register(taskName, SpotlessTaskImpl.class, task -> {
task.init(taskService);
task.init(getRegisterDependenciesTask().getTaskService());
task.setEnabled(!isIdeHook);
// clean removes the SpotlessCache, so we have to run after clean
task.mustRunAfter(cleanTask);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
Expand All @@ -44,6 +45,9 @@
import com.diffplug.spotless.LineEnding;

public abstract class SpotlessTask extends DefaultTask {
@Internal
abstract Property<SpotlessTaskService> getTaskService();

// set by SpotlessExtension, but possibly overridden by FormatExtension
protected String encoding = "UTF-8";

Expand All @@ -56,7 +60,7 @@ public void setEncoding(String encoding) {
this.encoding = Objects.requireNonNull(encoding);
}

protected LineEnding.Policy lineEndingsPolicy;
protected transient LineEnding.Policy lineEndingsPolicy;

@Input
public LineEnding.Policy getLineEndingsPolicy() {
Expand All @@ -69,21 +73,21 @@ public void setLineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) {

/*** API which performs git up-to-date tasks. */
@Nullable
GitRatchetGradle ratchet;
private transient GitRatchetGradle ratchet;
/** The sha of the tree at repository root, used for determining if an individual *file* is clean according to git. */
ObjectId rootTreeSha;
private transient ObjectId rootTreeSha;
/**
* The sha of the tree at the root of *this project*, used to determine if the git baseline has changed within this folder.
* Using a more fine-grained tree (rather than the project root) allows Gradle to mark more subprojects as up-to-date
* compared to using the project root.
*/
private ObjectId subtreeSha = ObjectId.zeroId();
private transient ObjectId subtreeSha = ObjectId.zeroId();

public void setupRatchet(GitRatchetGradle gitRatchet, String ratchetFrom) {
ratchet = gitRatchet;
public void setupRatchet(String ratchetFrom) {
ratchet = getTaskService().get().getRatchet();
File projectDir = getProjectDir().get().getAsFile();
rootTreeSha = gitRatchet.rootTreeShaOf(projectDir, ratchetFrom);
subtreeSha = gitRatchet.subtreeShaOf(projectDir, rootTreeSha);
rootTreeSha = ratchet.rootTreeShaOf(projectDir, ratchetFrom);
subtreeSha = ratchet.subtreeShaOf(projectDir, rootTreeSha);
}

@Internal
Expand Down Expand Up @@ -139,7 +143,7 @@ public File getOutputDirectory() {
return outputDirectory;
}

protected List<FormatterStep> steps = new ArrayList<>();
protected transient List<FormatterStep> steps = new ArrayList<>();

@Input
public List<FormatterStep> getSteps() {
Expand Down
Loading

0 comments on commit 24d1ea5

Please sign in to comment.