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

License derived from .baseline/copyright #1217

Merged
merged 19 commits into from
Feb 10, 2020
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,10 @@ as that file will be overridden on updates.

### Copyright Checks

By default Baseline enforces Palantir copyright at the beginning of files. To change this, edit the template copyright
in `.baseline/copyright/*.txt` and the RegexpHeader checkstyle configuration in `.baseline/checkstyle/checkstyle.xml`
Baseline enforces Palantir copyright at the beginning of files when applying `com.palantir.baseline-format`. To change this, edit the template copyrights
in `.baseline/copyright/*.txt`. The largest file (sorted lexicographically) will be used to generate a new copyright if one is missing, or none of the existing templates match.￿

To automatically update all files with mismatching/missing copyrights, run `./gradlew format`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./gradlew format feels a bit odd here. I think I'd almost recommend spotlessApply here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it the same thing though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommending another command that format intentionally aliases feels like it might confuse people.


## com.palantir.baseline-class-uniqueness
When applied to a java project, this inspects all the jars in your `runtimeClasspath` configuration and records any conflicts to a `baseline-class-uniqueness.lock` file. For example:
Expand Down
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-1217.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: improvement
improvement:
description: Enforcing copyright based on the first file (lexicographically) founds
in `.baseline/copyright`.
links:
- https://github.com/palantir/gradle-baseline/pull/1217
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@
<module name="NewlineAtEndOfFile"> <!-- Java Style Guide: Line ending: LF -->
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpHeader">
<property name="header" value="^/\*$\n^ \* \(c\) Copyright \d{4} Palantir Technologies Inc\. All rights reserved\.$"/>
<property name="fileExtensions" value=".java,.ts"/>
</module>
<module name="RegexpMultiline"> <!-- Development Practices: Writing good unit tests -->
<property name="fileExtensions" value="java"/>
<property name="format" value="@VisibleForTesting\s+(protected|public)"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,19 @@
package com.palantir.baseline.plugins;

import com.diffplug.gradle.spotless.SpotlessExtension;
import com.diffplug.spotless.FormatterStep;
import com.google.common.base.Splitter;
import com.google.common.collect.Streams;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.Task;
Expand All @@ -41,6 +51,109 @@ class BaselineFormat extends AbstractBaselinePlugin {
public void apply(Project project) {
this.project = project;

project.getPluginManager().apply("com.diffplug.gradle.spotless");

SpotlessExtension spotlessExtension = project.getExtensions().getByType(SpotlessExtension.class);
// Keep spotless from eagerly configuring all other tasks. We do the same thing as the enforceCheck
// property below by making the check task depend on spotlessCheck.
// See https://github.com/diffplug/spotless/issues/444
spotlessExtension.setEnforceCheck(false);

// Allow disabling copyright for tests
if (!"false".equals(project.findProperty("com.palantir.baseline-format.copyright"))) {
configureCopyrightStep(project, spotlessExtension);
}

// necessary because SpotlessPlugin creates tasks in an afterEvaluate block
TaskProvider<Task> formatTask = project.getTasks().register("format", task -> {
task.setGroup("Formatting");
});
project.afterEvaluate(p -> {
formatTask.configure(t -> {
t.dependsOn("spotlessApply");
});

// re-enable spotless checking, but lazily so it doesn't eagerly configure everything else
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME).configure(t -> {
t.dependsOn(project.getTasks().named("spotlessCheck"));
});

// The copyright step configures itself lazily to allow for baselineUpdateConfig to potentially create the
// right files. Therefore, also make sure that these will run in the right order.
project.getPluginManager().withPlugin("com.palantir.baseline-config", baselineConfig -> {
project.getTasks()
.matching(t -> t.getName().startsWith("spotless"))
.configureEach(t -> t.mustRunAfter("baselineUpdateConfig"));
});
});

project.getPluginManager().withPlugin("java", plugin -> {
configureSpotlessJava(project, spotlessExtension);
});
}

/**
* Necessary in order to not fail right away if the copyright folder doesn't exist yet, because it would be created
* by {@code baselineUpdateConfig}.
*/
private FormatterStep createLazyLicenseHeaderStep(Project project) {
// Spotless will consider the license header to be the file prefix up to the first line starting with delimiter
String delimiter = "(?! \\*|/\\*| \\*/)";

return new LazyFormatterStep(MultiLicenseHeaderStep.name(), () -> {
List<String> headers = computeCopyrightHeaders(project);
return MultiLicenseHeaderStep.createFromHeaders(headers, delimiter);
});
}

private void configureCopyrightStep(Project project, SpotlessExtension spotlessExtension) {
project.getPluginManager().withPlugin("java", javaPlugin -> {
spotlessExtension.java(java -> java.addStep(createLazyLicenseHeaderStep(project)));
});

// This is tricky as configuring this naively yields the following error:
// > You must apply the groovy plugin before the spotless plugin if you are using the groovy extension.
project.getPluginManager().withPlugin("groovy", groovyPlugin -> {
spotlessExtension.groovy(groovy -> groovy.addStep(createLazyLicenseHeaderStep(project)));
});
}

/**
* Computes all the copyright headers based on the files inside baseline's {@code copyright} directory. This list
* is sorted lexicographically by the file names.
*/
private List<String> computeCopyrightHeaders(Project project) {
File copyrightDir = project.getRootProject().file(getConfigDir() + "/copyright");
Stream<Path> files;
try {
files = Files.list(copyrightDir.toPath()).sorted(Comparator.comparing(Path::getFileName));
} catch (IOException e) {
throw new RuntimeException("Couldn't list copyright directory: " + copyrightDir);
}

return files.map(BaselineFormat::computeCopyrightComment).collect(Collectors.toList());
}

private static String computeCopyrightComment(Path copyrightFile) {
String copyrightContents;
try {
copyrightContents = new String(Files.readAllBytes(copyrightFile), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Couldn't read copyright file " + copyrightFile, e);
}
// Spotless expects '$YEAR' but our current patterns use ${today.year}
String copyright = copyrightContents
.replaceAll(Pattern.quote("${today.year}"), "\\$YEAR")
.trim();
// Spotless expects the literal header so we have to add the Java comment guard and prefixes
return Streams.concat(
Stream.of("/*"),
Streams.stream(Splitter.on('\n').split(copyright)).map(line -> " " + ("* " + line).trim()),
Stream.of(" */"))
.collect(Collectors.joining("\n"));
}

private static void configureSpotlessJava(Project project, SpotlessExtension spotlessExtension) {
if (palantirJavaFormatterState(project) == FormatterState.ON) {
project.getPlugins().apply(PJF_PLUGIN); // provides the formatDiff task
}
Expand All @@ -56,58 +169,35 @@ public void apply(Project project) {
});
}

project.getPluginManager().withPlugin("java", plugin -> {
project.getPluginManager().apply("com.diffplug.gradle.spotless");
Path eclipseXml = eclipseConfigFile(project);

SpotlessExtension spotlessExtension = project.getExtensions().getByType(SpotlessExtension.class);
spotlessExtension.java(java -> {
// Configure a lazy FileCollection then pass it as the target
ConfigurableFileCollection allJavaFiles = project.files();
project.getConvention()
.getPlugin(JavaPluginConvention.class)
.getSourceSets()
.all(sourceSet -> allJavaFiles.from(
sourceSet.getAllJava().filter(file ->
!file.toString().contains(GENERATED_MARKER))));

java.target(allJavaFiles);
java.removeUnusedImports();
// use empty string to specify one group for all non-static imports
java.importOrder("");

if (eclipseFormattingEnabled(project)) {
java.eclipse().configFile(project.file(eclipseXml.toString()));
}

java.trimTrailingWhitespace();
});
Path eclipseXml = eclipseConfigFile(project);
spotlessExtension.java(java -> {
// Configure a lazy FileCollection then pass it as the target
ConfigurableFileCollection allJavaFiles = project.files();
project.getConvention()
.getPlugin(JavaPluginConvention.class)
.getSourceSets()
.all(sourceSet -> allJavaFiles.from(sourceSet.getAllJava().filter(file ->
!file.toString().contains(GENERATED_MARKER))));

java.target(allJavaFiles);
java.removeUnusedImports();
// use empty string to specify one group for all non-static imports
java.importOrder("");

if (eclipseFormattingEnabled(project)) {
java.eclipse().configFile(project.file(eclipseXml.toString()));
}

java.trimTrailingWhitespace();
});

// Keep spotless from eagerly configuring all other tasks. We do the same thing as the enforceCheck
// property below by making the check task depend on spotlessCheck.
// See https://github.com/diffplug/spotless/issues/444
spotlessExtension.setEnforceCheck(false);
project.afterEvaluate(p -> {
Task spotlessJava = project.getTasks().getByName("spotlessJava");
if (eclipseFormattingEnabled(project) && !Files.exists(eclipseXml)) {
spotlessJava.dependsOn(":baselineUpdateConfig");
}

// necessary because SpotlessPlugin creates tasks in an afterEvaluate block
TaskProvider<Task> formatTask = project.getTasks().register("format", task -> {
task.setGroup("Formatting");
});
project.afterEvaluate(p -> {
Task spotlessJava = project.getTasks().getByName("spotlessJava");
Task spotlessApply = project.getTasks().getByName("spotlessApply");
if (eclipseFormattingEnabled(project) && !Files.exists(eclipseXml)) {
spotlessJava.dependsOn(":baselineUpdateConfig");
}
formatTask.configure(t -> {
t.dependsOn(spotlessApply);
});
project.getTasks().withType(JavaCompile.class).configureEach(spotlessJava::mustRunAfter);

// re-enable spotless checking, but lazily so it doesn't eagerly configure everything else
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME).configure(t -> {
t.dependsOn(project.getTasks().named("spotlessCheck"));
});
});
project.getTasks().withType(JavaCompile.class).configureEach(spotlessJava::mustRunAfter);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* (c) Copyright 2020 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.baseline.plugins;

import com.diffplug.spotless.FormatterStep;
import com.google.common.base.Suppliers;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/** A lazy {@link FormatterStep} that instantiates its delegate only when methods are called. */
class LazyFormatterStep implements FormatterStep {
private final String name;
private transient Supplier<FormatterStep> delegate;

LazyFormatterStep(String name, Supplier<FormatterStep> delegate) {
this.name = name;
this.delegate = Suppliers.memoize(delegate::get);
}

@Override
public String getName() {
return name;
}

@Nullable
@Override
public String format(String rawUnix, File file) throws Exception {
return delegate.get().format(rawUnix, file);
}

public final void writeObject(ObjectOutputStream output) throws IOException {
output.defaultWriteObject();
output.writeObject(delegate.get());
}

public void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
input.defaultReadObject();
FormatterStep serialized = (FormatterStep) input.readObject();
delegate = () -> serialized;
}
}
Loading