Skip to content

Commit

Permalink
Applied common Spotless Eclipse framework to Groovy-Eclipse (#244)
Browse files Browse the repository at this point in the history
Upgraded from groovy-eclipse version 2.9.1 to 2.9.2. Using Spotless Eclipse Framework. Introduced property to ignore formatter warnings/errors.
  • Loading branch information
fvgh authored Jul 19, 2018
1 parent aa04c02 commit 28a6073
Show file tree
Hide file tree
Showing 17 changed files with 952 additions and 923 deletions.
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

0 comments on commit 28a6073

Please sign in to comment.