Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standalone gradle plugins #48

Merged
merged 17 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ apply plugin: 'com.palantir.git-version'
apply plugin: 'com.palantir.baseline'
apply plugin: 'com.palantir.consistent-versions'

version = gitVersion()
version System.env.CIRCLE_TAG ?: gitVersion()

allprojects {
group = 'com.palantir.javaformat'
Expand Down
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-48.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: break
break:
description: The standalone `com.palantir.java-format` plugin provides a `formatDiff`
task. IntelliJ setup has been moved to a `com.palantir.java-format-idea` plugin.
links:
- https://github.com/palantir/palantir-java-format/pull/48
34 changes: 33 additions & 1 deletion gradle-palantir-java-format/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,33 @@ dependencies {
implementation gradleApi()
implementation 'com.google.guava:guava'
implementation 'gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext'
implementation project(':palantir-java-format-spi')

testImplementation 'com.netflix.nebula:nebula-test'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.junit.vintage:junit-vintage-engine'
testImplementation 'org.assertj:assertj-core'
testImplementation project(':palantir-java-format')
}

gradlePlugin {
automatedPublishing = false
plugins {
palantirJavaFormat {
id = 'com.palantir.java-format'
implementationClass = 'com.palantir.javaformat.gradle.JavaFormatPlugin'
implementationClass = 'com.palantir.javaformat.gradle.PalantirJavaFormatPlugin'
description = 'A modern, lambda-friendly, 120 character Java formatter.'
}
palantirJavaFormatIdea {
id = 'com.palantir.java-format-idea'
implementationClass = 'com.palantir.javaformat.gradle.PalantirJavaFormatIdeaPlugin'
description = 'Plugin to configure the PalantirJavaFormat IDEA plugin based on an optional implementation version of the formatter.'
}
palantirJavaFormatProvider {
id = 'com.palantir.java-format-provider'
implementationClass = 'com.palantir.javaformat.gradle.PalantirJavaFormatProviderPlugin'
description = 'Exposes a configuration containing the palantir-java-format jarss'
}
}
}

Expand All @@ -26,3 +41,20 @@ idea {
sourceDirs += sourceSets.main.groovy.srcDirs
}
}

configurations {
impl
}

dependencies {
impl project(':palantir-java-format')
}

task writeImplClasspath {
dependsOn configurations.impl
doLast {
file("$buildDir/impl.classpath").text = configurations.impl.asPath
}
}

test.dependsOn tasks.writeImplClasspath
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.javaformat.gradle;

import groovy.util.Node;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.javaformat.gradle

class ConfigureJavaFormatterXml {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.javaformat.gradle;

import groovy.util.Node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.palantir.javaformat.gradle;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.net.URI;
import java.util.List;
Expand All @@ -31,37 +30,22 @@
import org.gradle.plugins.ide.idea.model.IdeaModel;
import org.jetbrains.gradle.ext.TaskTriggersConfig;

public class JavaFormatPlugin implements Plugin<Project> {

private static final String EXTENSION_NAME = "palantirJavaFormat";
public final class PalantirJavaFormatIdeaPlugin implements Plugin<Project> {

@Override
public void apply(Project project) {
public void apply(Project rootProject) {
Preconditions.checkState(
project == project.getRootProject(), "May only apply com.palantir.java-format to the root project");

JavaFormatExtension extension =
project.getExtensions().create(EXTENSION_NAME, JavaFormatExtension.class, project);
rootProject == rootProject.getRootProject(),
"May only apply com.palantir.java-format-idea to the root project");

Configuration implConfiguration = project.getConfigurations().create("palantirJavaFormat", conf -> {
conf.setDescription("Internal configuration for resolving the palantirJavaFormat implementation");
conf.setVisible(false);
conf.setCanBeConsumed(false);
// Using addLater instead of afterEvaluate, in order to delay reading the extension until after the user
// has configured it.
conf.defaultDependencies(deps -> deps.addLater(project.provider(() -> {
String version = extension.getImplementationVersion().get();
rootProject.getPlugins().apply(PalantirJavaFormatProviderPlugin.class);

return project.getDependencies().create(ImmutableMap.of(
"group", "com.palantir.javaformat",
"name", "palantir-java-format",
"version", version));
})));
});
rootProject.getPluginManager().withPlugin("idea", ideaPlugin -> {
Configuration implConfiguration =
rootProject.getConfigurations().getByName(PalantirJavaFormatProviderPlugin.CONFIGURATION_NAME);

project.getPluginManager().withPlugin("idea", ideaPlugin -> {
configureLegacyIdea(project, implConfiguration);
configureIntelliJImport(project, implConfiguration);
configureLegacyIdea(rootProject, implConfiguration);
configureIntelliJImport(rootProject, implConfiguration);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.javaformat.gradle;

import com.google.common.collect.ImmutableMap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,39 @@
* limitations under the License.
*/

package com.palantir.javaformat.java;
package com.palantir.javaformat.gradle;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.collect.Streams;
import com.google.common.collect.TreeRangeSet;
import com.google.common.io.ByteStreams;
import com.palantir.javaformat.java.FormatterException;
import com.palantir.javaformat.java.FormatterService;
import com.palantir.javaformat.java.Replacement;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public final class FormatDiff {
final class FormatDiff {
// each section in the git diff output starts like this
private static final Pattern SEPARATOR = Pattern.compile("diff --git");

Expand All @@ -48,10 +57,8 @@ public final class FormatDiff {
private static final Pattern HUNK =
Pattern.compile("^@@.*\\+(?<startLineOneIndexed>\\d+)(,(?<numLines>\\d+))?", Pattern.MULTILINE);

public static void formatDiff(Path dirToFormat) throws IOException, InterruptedException {
Formatter formatter = Formatter.createFormatter(
JavaFormatterOptions.builder().style(JavaFormatterOptions.Style.PALANTIR).build());

public static void formatDiff(Path dirToFormat, FormatterService formatter)
throws IOException, InterruptedException {
String gitOutput = gitDiff(dirToFormat);
Path gitTopLevelDir = gitTopLevelDir(dirToFormat);

Expand Down Expand Up @@ -86,7 +93,7 @@ static Stream<SingleFileDiff> parseGitDiffOutput(String gitOutput) {
});
}

private static void format(Formatter formatter, SingleFileDiff diff) {
private static void format(FormatterService formatter, SingleFileDiff diff) {
String input;
try {
input = new String(Files.readAllBytes(diff.path), UTF_8);
Expand All @@ -96,11 +103,12 @@ private static void format(Formatter formatter, SingleFileDiff diff) {
return;
}

RangeSet<Integer> charRanges = Formatter.lineRangesToCharRanges(input, diff.lineRanges);
RangeSet<Integer> charRanges = lineRangesToCharRanges(input, diff.lineRanges);

try {
System.err.println("Formatting " + diff.path);
String output = formatter.formatSource(input, charRanges.asRanges());
ImmutableList<Replacement> replacements = formatter.getFormatReplacements(input, charRanges.asRanges());
String output = applyReplacements(input, replacements);
Files.write(diff.path, output.getBytes(UTF_8));
} catch (IOException | FormatterException e) {
System.err.println("Failed to format file " + diff.path);
Expand Down Expand Up @@ -128,6 +136,37 @@ private static String gitCommand(Path dir, String... args) throws IOException, I
return new String(baos.toByteArray(), UTF_8).trim();
}

private static String applyReplacements(String input, Collection<Replacement> replacementsCollection) {
List<Replacement> replacements = new ArrayList<>(replacementsCollection);
replacements.sort(comparing((Replacement r) -> r.getReplaceRange().lowerEndpoint()).reversed());
StringBuilder writer = new StringBuilder(input);
for (Replacement replacement : replacements) {
writer.replace(
replacement.getReplaceRange().lowerEndpoint(),
replacement.getReplaceRange().upperEndpoint(),
replacement.getReplacementString());
}
return writer.toString();
}

/** Converts zero-indexed, [closed, open) line ranges in the given source file to character ranges. */
private static RangeSet<Integer> lineRangesToCharRanges(String input, RangeSet<Integer> lineRanges) {
List<Integer> lines = new ArrayList<>();
Iterators.addAll(lines, new LineOffsetIterator(input));
lines.add(input.length() + 1);

final RangeSet<Integer> characterRanges = TreeRangeSet.create();
for (Range<Integer> lineRange : lineRanges.subRangeSet(Range.closedOpen(0, lines.size() - 1)).asRanges()) {
int lineStart = lines.get(lineRange.lowerEndpoint());
// Exclude the trailing newline. This isn't strictly necessary, but handling blank lines
// as empty ranges is convenient.
int lineEnd = lines.get(lineRange.upperEndpoint()) - 1;
Range<Integer> range = Range.closedOpen(lineStart, lineEnd);
characterRanges.add(range);
}
return characterRanges;
}

// TODO(dfox): replace this with immutables
public static class SingleFileDiff {
private final Path path;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,48 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.javaformat.gradle;

import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import com.google.common.collect.Iterables;
import com.palantir.javaformat.java.FormatterService;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ServiceLoader;
import org.gradle.api.artifacts.Configuration;

public class JavaFormatExtension {
private final Property<String> implementationVersion;
private static final String IMPLEMENTATION_VERSION =
JavaFormatExtension.class.getPackage().getImplementationVersion();
private final Configuration configuration;

public JavaFormatExtension(Project project) {
implementationVersion = project.getObjects().property(String.class);
implementationVersion.set(IMPLEMENTATION_VERSION);
public JavaFormatExtension(Configuration configuration) {
this.configuration = configuration;
}

public final Property<String> getImplementationVersion() {
return implementationVersion;
public FormatterService serviceLoad() {
URL[] jarUris = configuration.getFiles().stream()
.map(file -> {
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("Unable to convert URI to URL: " + file, e);
}
})
.toArray(URL[]::new);

ClassLoader classLoader = new URLClassLoader(jarUris, FormatterService.class.getClassLoader());
return Iterables.getOnlyElement(ServiceLoader.load(FormatterService.class, classLoader));
}
}
Loading