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

Applied common Spotless Eclipse framework to Groovy-Eclipse #244

Merged
merged 6 commits into from
Jul 19, 2018
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
File renamed without changes.
14 changes: 14 additions & 0 deletions _ext/eclipse-groovy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# spotless-eclipse-groovy

Groovy-Eclipse is not available in a form which can be easily consumed by maven or gradle.
To fix this, we publish Groovy-Eclipse's formatter and all its dependencies, along with a small amount of glue code, into the `com.diffplug.gradle.spotless:spotless-eclipse-groovy` artifact.

## Build

To publish a new version, update the `_ext/eclipse-groovy/gradle.properties` appropriately and run this from the root directory:

```
gradlew -b _ext/eclipse-groovy/build.gradle publish
```

Spotless at large is under the Apache 2.0 license, but this jar is under the EPL v1.
179 changes: 179 additions & 0 deletions _ext/eclipse-groovy/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import java.io.File

import org.apache.commons.io.filefilter.DirectoryFileFilter

plugins {
// p2 dependencies
id 'com.diffplug.gradle.p2.asmaven' version '3.9.0'
}

apply from: rootProject.file('../gradle/java-setup.gradle')
apply from: rootProject.file('../gradle/java-publish.gradle')

// The dependencies to pull from GrEclipse's p2 repositories
def grEclipseDeps = [
'org.codehaus.groovy.eclipse.refactoring':'+', // GroovyFormatter and related

// The following lists does not reflect the complete transitive required packages, but
// the once used during code formatting
'org.codehaus.groovy':'+', // Groovy compiler patches supporting use within GrEclipse and Groovy itself
'org.codehaus.groovy.eclipse.core':'+', // Groovy core classes (provides central logging used by formatter)
'org.eclipse.jdt.core':"${VER_JDT_PATCH}", // Patches org.eclipse.jdt.core classes supporting use within GrEclipse (provides AST generator)
'org.eclipse.jdt.groovy.core':'+' // Extends org.eclipse.jdt.core for Groovy
]

ext {
developers = [
fvgh: [ name: 'Frank Vennemeyer', email: 'frankgh@zoho.com' ],
]

//Include/Excludes form the JARs, which goes into a fat-jar with the spottless formatter interface.
jarInclude = [
'**/*.class', // Take all classes
'**/*.java', // ... and sources.
'**/*.properties', // Text resources (for messages, etc)
'**/*.xml', // Plugin XML and other resources
'*.html', // License information about the included JARs,
'META-INF/**' // Information about the origin of the individual class files
]
jarExclude = [
'META-INF/*.RSA', // The eclipse jars are signed, and our fat-jar breaks the signatures
'META-INF/*.SF', // ... so all signatures are filtered
]

//Some JARs include JARs themselfs
internalJars = [
//Jars included by org.codehaus.groovy
"**/groovy-all-${VER_GROOVY}-indy", // Use Groovy compiler compatible with GrEclipse instead of localGroovy
'**/groovy-eclipse', // Patches/Overrides some of the Groovy compiler classes
'**/eclipse-trace', // Provides logging capabilities for groovy-eclipse

//Jars included by org.eclipse.jdt.groovy.core
'**/nlcl' //Non locking class loader used by groovy compiler
]

// The directory contains all external classes for the fat-jar
embeddedClassesDirName = 'build/embeddedClasses'
embeddedClassesDir = project.file(embeddedClassesDirName)
embeddedClassesLibDirName = 'build/embeddedClasses/lib'
embeddedClassesLibDir = project.file(embeddedClassesLibDirName)
}

// build a maven repo in our build folder containing these artifacts
p2AsMaven {
group 'p2', {
repo "http://dist.springsource.org/release/GRECLIPSE/e${VER_ECLIPSE}"
grEclipseDeps.keySet.each { p2.addIU(it) }
}
}

configurations {
embeddedJars // GrEclipse JARs the fat-jar is based uppon
}

dependencies {
grEclipseDeps.each { groupArtifact, version ->
embeddedJars "p2:${groupArtifact}:${version}"
}

// The resulting fat-jar includes the classes from GRECLIPSE.
compile files(embeddedClassesDir)

compile "com.diffplug.spotless:spotless-eclipse-base:${VER_SPOTLESS_ECLISPE_BASE}"
// Provides text partitioners for formatters
compile ("org.eclipse.platform:org.eclipse.jface.text:${VER_ECLISPE_JFACE}") {
exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt'
}
}

jar {
// this embeds the Eclipse-Groovy clases into our "fat JAR"
from embeddedClassesDir
}

//////////
// Test //
//////////
sourceSets {
// Use JAR file with all resources for Eclipse-Groovy integration-tests
test.runtimeClasspath = jar.outputs.files + sourceSets.test.output + sourceSets.test.compileClasspath
}

///////////////////
// External Deps //
///////////////////

task unjarEmbeddedClasses {
description = "Copies filtered set of embedded classes from the Eclise/GrEclipse dependencies to '${project.relativePath(embeddedClassesDir)}'."
inputs.files(configurations.embeddedJars)
inputs.property('internalJars', internalJars)
inputs.property('jarInclude', jarInclude)
inputs.property('jarExclude', jarExclude)
outputs.file(embeddedClassesDir)

doLast {
embeddedClassesDir.deleteDir()
embeddedClassesDir.mkdirs()
embeddedClassesLibDir.deleteDir()
embeddedClassesLibDir.mkdirs()
configurations.embeddedJars.each {
unjar(it, embeddedClassesDir)
}
//Unpack internal JARs. Maintain the order defined in internalJars
internalJars.each {
fileTree(embeddedClassesDir).include("${it}.jar").each {
unjar(it, embeddedClassesDir)
delete(it)
}
}
}
}

def unjar(File jarFile, File destDir) {
ant.unjar(src: jarFile, dest: destDir) {
patternset {
jarInclude.each {
include(name: "${it}")
}
internalJars.each {
include(name: "**/${it}.jar")
}
jarExclude.each {
exclude(name: "${it}")
}
}
}
//Provide Fat JAR resources (following naming convention of spotless-eclipse-base)
def fat_jar_resource_dir = jarFile.getName().split('-')[0]
ant.move(todir: "${destDir}/${fat_jar_resource_dir}/META-INF", quiet: 'true', failonerror: 'false') {
fileset(dir: "${destDir}/META-INF")
}
//Keep licenses and other human readable information for transparency
ant.move(todir: "${destDir}/${fat_jar_resource_dir}", quiet: 'true') {
fileset(dir: destDir) {
include(name: 'META-INF')
include(name: '*')
type(type: 'file')
exclude(name: '*jar-*')
exclude(name: '*.jar')
}
}
}

tasks.compileJava.dependsOn(unjarEmbeddedClasses)

/////////
// IDE //
/////////

apply plugin: 'eclipse'

// always create fresh projects
tasks.eclipse.dependsOn(cleanEclipse)
// Encure that the dependent classes are preovided for compilation if project is build via Eclipse instead of command line
tasks.eclipseClasspath.dependsOn(unjarEmbeddedClasses)

apply plugin: 'idea'

// Encure that the dependent classes are preovided for compilation if project is build via Eclipse instead of command line
tasks.idea.dependsOn(unjarEmbeddedClasses)
19 changes: 19 additions & 0 deletions _ext/eclipse-groovy/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Versions correspond to the Eclipse-Groovy version used for th FAT JAR
# See https://github.com/groovy/groovy-eclipse/releases for further information about Eclipse-Groovy versions.
# Patch version can be is incremented independently for backward compatible patches of this library.
ext_version=2.9.2
ext_artifactId=spotless-eclipse-groovy
ext_description=Groovy Eclipse's formatter bundled for Spotless

ext_org=diffplug
ext_group=com.diffplug.spotless

# Build requirements
ext_VER_JAVA=1.8

# Compile
VER_ECLIPSE=4.7
VER_SPOTLESS_ECLISPE_BASE=[3.0.0,4.0.0[
VER_ECLISPE_JFACE=[3.12.0,4.0.0[
VER_GROOVY=2.5.0
VER_JDT_PATCH=3.13.100.xx-201801041714-e47-RELEASE
Original file line number Diff line number Diff line change
Expand Up @@ -13,70 +13,106 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.gradle.spotless.groovy.eclipse;
package com.diffplug.spotless.extra.eclipse.groovy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.codehaus.groovy.eclipse.GroovyLogManager;
import org.codehaus.groovy.eclipse.IGroovyLogger;
import org.codehaus.groovy.eclipse.TraceCategory;
import org.codehaus.groovy.eclipse.core.GroovyCoreActivator;
import org.codehaus.groovy.eclipse.refactoring.formatter.DefaultGroovyFormatter;
import org.codehaus.groovy.eclipse.refactoring.formatter.FormatterPreferencesOnStore;
import org.codehaus.groovy.eclipse.refactoring.formatter.GroovyFormatter;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.ILogListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.equinox.log.ExtendedLogReaderService;
import org.eclipse.equinox.log.ExtendedLogService;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.text.edits.TextEdit;

import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework;

/** Spotless-Formatter step which calls out to the Groovy-Eclipse formatter. */
public class GrEclipseFormatterStepImpl {
/**
* Groovy compiler problems can be ignored.
* <p>
* Value is either 'true' or 'false'
* </p>
*/
public static final String IGNORE_FORMATTER_PROBLEMS = "ignoreFormatterProblems";

private final FormatterPreferencesOnStore preferencesStore;
private final boolean ignoreFormatterProblems;

public GrEclipseFormatterStepImpl(final Properties properties) throws Exception {
SpotlessLogService logService = new SpotlessLogService();
if (SpotlessEclipseFramework.setup(
config -> {
config.applyDefault();
config.add(ExtendedLogService.class, logService);
config.add(ExtendedLogReaderService.class, logService);
},
plugins -> {
plugins.add(new GroovyCoreActivator());
})) {}
PreferenceStore preferences = createPreferences(properties);
preferencesStore = new FormatterPreferencesOnStore(preferences);
ignoreFormatterProblems = Boolean.parseBoolean(properties.getProperty(IGNORE_FORMATTER_PROBLEMS, "false"));
}

/** Formatting Groovy string */
public String format(String raw) throws Exception {
IDocument doc = new Document(raw);
GroovyErrorListener errorListener = new GroovyErrorListener();
TextSelection selectAll = new TextSelection(doc, 0, doc.getLength());
GroovyFormatter codeFormatter = new DefaultGroovyFormatter(selectAll, doc, preferencesStore, false);
TextEdit edit = codeFormatter.format();
if (errorListener.errorsDetected()) {
if (!ignoreFormatterProblems && errorListener.errorsDetected()) {
throw new IllegalArgumentException(errorListener.toString());
}
edit.apply(doc);
return doc.get();
}

private static class GroovyErrorListener implements ILogListener {
/**
* Eclipse Groovy formatter does not signal problems by its return value, but by logging errors.
*/
private static class GroovyErrorListener implements ILogListener, IGroovyLogger {

private final List<String> errors;

public GroovyErrorListener() {
errors = new LinkedList<String>();
/*
* We need a synchronized list here, in case multiple instantiations
* run in parallel.
*/
errors = Collections.synchronizedList(new ArrayList<String>());
ILog groovyLogger = GroovyCoreActivator.getDefault().getLog();
groovyLogger.addLogListener(this);
GroovyLogManager.manager.addLogger(this);
}

@Override
public void logging(final IStatus status, final String plugin) {
if (!status.isOK()) {
errors.add(status.getMessage());
}
errors.add(status.getMessage());
}

public boolean errorsDetected() {
ILog groovyLogger = GroovyCoreActivator.getDefault().getLog();
groovyLogger.removeLogListener(this);
GroovyLogManager.manager.removeLogger(this);
return 0 != errors.size();
}

Expand All @@ -96,6 +132,22 @@ public String toString() {
return string.toString();
}

@Override
public boolean isCategoryEnabled(TraceCategory cat) {
/*
* Note that the compiler errors are just additionally caught here.
* They are also printed directly to System.err.
* See org.codehaus.jdt.groovy.internal.compiler.ast.GroovyCompilationUnitDeclaration.recordProblems
* for details.
*/
return TraceCategory.COMPILER.equals(cat);
}

@Override
public void log(TraceCategory arg0, String arg1) {
errors.add(arg1);
}

}

private static PreferenceStore createPreferences(final Properties properties) throws IOException {
Expand Down
Loading