blob: c7af72f999296374064f3b714d115507f71e83c7 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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 org.jetbrains.groovy.compiler.rt;
import groovy.lang.GroovyClassLoader;
import groovyjarjarasm.asm.Opcodes;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.*;
import org.codehaus.groovy.control.messages.WarningMessage;
import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit;
import java.io.*;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
/**
* @author peter
*/
public class DependentGroovycRunner {
public static final String TEMP_RESOURCE_SUFFIX = "___" + new Random().nextInt() + "_neverHappen";
public static final String[] RESOURCES_TO_MASK = {"META-INF/services/org.codehaus.groovy.transform.ASTTransformation", "META-INF/services/org.codehaus.groovy.runtime.ExtensionModule"};
public static boolean runGroovyc(boolean forStubs, String argsPath) {
File argsFile = new File(argsPath);
final CompilerConfiguration config = new CompilerConfiguration();
config.setClasspath("");
config.setOutput(new PrintWriter(System.err));
config.setWarningLevel(WarningMessage.PARANOIA);
final List<CompilerMessage> compilerMessages = new ArrayList<CompilerMessage>();
final List<CompilationUnitPatcher> patchers = new ArrayList<CompilationUnitPatcher>();
final List<File> srcFiles = new ArrayList<File>();
final Map<String, File> class2File = new HashMap<String, File>();
final String[] finalOutputRef = new String[1];
fillFromArgsFile(argsFile, config, patchers, compilerMessages, srcFiles, class2File, finalOutputRef);
if (srcFiles.isEmpty()) return true;
if (forStubs) {
Map<String, Object> options = new HashMap<String, Object>();
options.put("stubDir", config.getTargetDirectory());
options.put("keepStubs", Boolean.TRUE);
config.setJointCompilationOptions(options);
}
System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: loading sources...");
String[] finalOutputs = finalOutputRef[0].split(File.pathSeparator);
renameResources(finalOutputs, "", TEMP_RESOURCE_SUFFIX);
final List<GroovyCompilerWrapper.OutputItem> compiledFiles;
try {
final AstAwareResourceLoader resourceLoader = new AstAwareResourceLoader(class2File);
final CompilationUnit unit = createCompilationUnit(forStubs, config, buildClassLoaderFor(config, resourceLoader));
unit.addPhaseOperation(new CompilationUnit.SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
File file = new File(source.getName());
for (ClassNode aClass : source.getAST().getClasses()) {
resourceLoader.myClass2File.put(aClass.getName(), file);
}
}
}, Phases.CONVERSION);
addSources(forStubs, srcFiles, unit);
runPatchers(patchers, compilerMessages, unit, resourceLoader, srcFiles);
System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: compiling...");
compiledFiles = new GroovyCompilerWrapper(compilerMessages, forStubs).compile(unit);
}
finally {
renameResources(finalOutputs, TEMP_RESOURCE_SUFFIX, "");
System.out.println(GroovyRtConstants.CLEAR_PRESENTABLE);
}
System.out.println();
reportCompiledItems(compiledFiles);
System.out.println();
if (compiledFiles.isEmpty()) {
reportNotCompiledItems(srcFiles);
}
int errorCount = 0;
for (CompilerMessage message : compilerMessages) {
if (message.getCategory() == GroovyCompilerMessageCategories.ERROR) {
if (errorCount > 100) {
continue;
}
errorCount++;
}
printMessage(message);
}
return false;
}
private static void renameResources(String[] finalOutputs, String removeSuffix, String addSuffix) {
for (String output : finalOutputs) {
for (String res : RESOURCES_TO_MASK) {
File file = new File(output, res + removeSuffix);
if (file.exists()) {
file.renameTo(new File(output, res + addSuffix));
}
}
}
}
private static String fillFromArgsFile(File argsFile, CompilerConfiguration compilerConfiguration, List<CompilationUnitPatcher> patchers, List<CompilerMessage> compilerMessages,
List<File> srcFiles, Map<String, File> class2File, String[] finalOutputs) {
String moduleClasspath = null;
BufferedReader reader = null;
FileInputStream stream;
try {
stream = new FileInputStream(argsFile);
reader = new BufferedReader(new InputStreamReader(stream));
reader.readLine(); // skip classpath
String line;
while ((line = reader.readLine()) != null) {
if (!GroovyRtConstants.SRC_FILE.equals(line)) {
break;
}
final File file = new File(reader.readLine());
srcFiles.add(file);
}
while (line != null) {
if (line.equals("class2src")) {
while (!GroovyRtConstants.END.equals(line = reader.readLine())) {
class2File.put(line, new File(reader.readLine()));
}
}
else if (line.startsWith(GroovyRtConstants.PATCHERS)) {
String s;
while (!GroovyRtConstants.END.equals(s = reader.readLine())) {
try {
final CompilationUnitPatcher patcher = (CompilationUnitPatcher)Class.forName(s).newInstance();
patchers.add(patcher);
}
catch (InstantiationException e) {
addExceptionInfo(compilerMessages, e, "Couldn't instantiate " + s);
}
catch (IllegalAccessException e) {
addExceptionInfo(compilerMessages, e, "Couldn't instantiate " + s);
}
catch (ClassNotFoundException e) {
addExceptionInfo(compilerMessages, e, "Couldn't instantiate " + s);
}
}
}
else if (line.startsWith(GroovyRtConstants.ENCODING)) {
compilerConfiguration.setSourceEncoding(reader.readLine());
}
else if (line.startsWith(GroovyRtConstants.OUTPUTPATH)) {
compilerConfiguration.setTargetDirectory(reader.readLine());
}
else if (line.startsWith(GroovyRtConstants.FINAL_OUTPUTPATH)) {
finalOutputs[0] = reader.readLine();
}
line = reader.readLine();
}
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
reader.close();
}
catch (IOException e) {
e.printStackTrace();
}
finally {
argsFile.delete();
}
}
return moduleClasspath;
}
private static void addSources(boolean forStubs, List<File> srcFiles, final CompilationUnit unit) {
for (final File file : srcFiles) {
if (forStubs && file.getName().endsWith(".java")) {
continue;
}
unit.addSource(new SourceUnit(file, unit.getConfiguration(), unit.getClassLoader(), unit.getErrorCollector()));
}
}
private static void runPatchers(List<CompilationUnitPatcher> patchers, List<CompilerMessage> compilerMessages, CompilationUnit unit, final AstAwareResourceLoader loader, List<File> srcFiles) {
if (!patchers.isEmpty()) {
for (CompilationUnitPatcher patcher : patchers) {
try {
patcher.patchCompilationUnit(unit, loader, srcFiles.toArray(new File[srcFiles.size()]));
}
catch (LinkageError e) {
addExceptionInfo(compilerMessages, e, "Couldn't run " + patcher.getClass().getName());
}
}
}
}
private static void reportNotCompiledItems(Collection<File> toRecompile) {
for (File file : toRecompile) {
System.out.print(GroovyRtConstants.TO_RECOMPILE_START);
System.out.print(file.getAbsolutePath());
System.out.print(GroovyRtConstants.TO_RECOMPILE_END);
System.out.println();
}
}
private static void reportCompiledItems(List<GroovyCompilerWrapper.OutputItem> compiledFiles) {
for (GroovyCompilerWrapper.OutputItem compiledFile : compiledFiles) {
/*
* output path
* source file
* output root directory
*/
System.out.print(GroovyRtConstants.COMPILED_START);
System.out.print(compiledFile.getOutputPath());
System.out.print(GroovyRtConstants.SEPARATOR);
System.out.print(compiledFile.getSourceFile());
System.out.print(GroovyRtConstants.COMPILED_END);
System.out.println();
}
}
private static void printMessage(CompilerMessage message) {
System.out.print(GroovyRtConstants.MESSAGES_START);
System.out.print(message.getCategory());
System.out.print(GroovyRtConstants.SEPARATOR);
System.out.print(message.getMessage());
System.out.print(GroovyRtConstants.SEPARATOR);
System.out.print(message.getUrl());
System.out.print(GroovyRtConstants.SEPARATOR);
System.out.print(message.getLineNum());
System.out.print(GroovyRtConstants.SEPARATOR);
System.out.print(message.getColumnNum());
System.out.print(GroovyRtConstants.SEPARATOR);
System.out.print(GroovyRtConstants.MESSAGES_END);
System.out.println();
}
private static void addExceptionInfo(List<CompilerMessage> compilerMessages, Throwable e, String message) {
final StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
compilerMessages.add(new CompilerMessage(GroovyCompilerMessageCategories.WARNING, message + ":\n" + writer, "<exception>", -1, -1));
}
private static CompilationUnit createCompilationUnit(final boolean forStubs,
final CompilerConfiguration config,
final GroovyClassLoader classLoader) {
final GroovyClassLoader transformLoader = new GroovyClassLoader(classLoader);
try {
if (forStubs) {
return createStubGenerator(config, classLoader, transformLoader);
}
}
catch (NoClassDefFoundError ignore) { // older groovy distributions just don't have stub generation capability
}
CompilationUnit unit;
try {
unit = new CompilationUnit(config, null, classLoader, transformLoader) {
public void gotoPhase(int phase) throws CompilationFailedException {
super.gotoPhase(phase);
if (phase <= Phases.ALL) {
System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: " + getPhaseDescription());
}
}
};
}
catch (NoSuchMethodError e) {
//groovy 1.5.x
unit = new CompilationUnit(config, null, classLoader) {
public void gotoPhase(int phase) throws CompilationFailedException {
super.gotoPhase(phase);
if (phase <= Phases.ALL) {
System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: " + getPhaseDescription());
}
}
};
}
return unit;
}
private static CompilationUnit createStubGenerator(final CompilerConfiguration config, final GroovyClassLoader classLoader, final GroovyClassLoader transformLoader) {
JavaAwareCompilationUnit unit = new JavaAwareCompilationUnit(config, classLoader) {
private boolean annoRemovedAdded;
public GroovyClassLoader getTransformLoader() {
return transformLoader;
}
@Override
public void addPhaseOperation(PrimaryClassNodeOperation op, int phase) {
if (!annoRemovedAdded && phase == Phases.CONVERSION && op.getClass().getName().startsWith("org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit$")) {
annoRemovedAdded = true;
super.addPhaseOperation(new PrimaryClassNodeOperation() {
@Override
public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
final ClassCodeVisitorSupport annoRemover = new ClassCodeVisitorSupport() {
@Override
protected SourceUnit getSourceUnit() {
return source;
}
public void visitClass(ClassNode node) {
if (node.isEnum()) {
node.setModifiers(node.getModifiers() & ~Opcodes.ACC_FINAL);
}
super.visitClass(node);
}
@Override
public void visitField(FieldNode fieldNode) {
Expression valueExpr = fieldNode.getInitialValueExpression();
if (valueExpr instanceof ConstantExpression && ClassHelper.STRING_TYPE.equals(valueExpr.getType())) {
fieldNode.setInitialValueExpression(new MethodCallExpression(valueExpr, "toString", new ListExpression()));
}
super.visitField(fieldNode);
}
@Override
public void visitAnnotations(AnnotatedNode node) {
List<AnnotationNode> annotations = node.getAnnotations();
if (!annotations.isEmpty()) {
annotations.clear();
}
super.visitAnnotations(node);
}
};
try {
annoRemover.visitClass(classNode);
}
catch (LinkageError ignored) {
}
}
}, phase);
}
super.addPhaseOperation(op, phase);
}
public void gotoPhase(int phase) throws CompilationFailedException {
if (phase == Phases.SEMANTIC_ANALYSIS) {
System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Generating Groovy stubs...");
// clear javaSources field so that no javac is invoked
try {
Field field = JavaAwareCompilationUnit.class.getDeclaredField("javaSources");
field.setAccessible(true);
LinkedList javaSources = (LinkedList)field.get(this);
javaSources.clear();
}
catch (Exception e) {
e.printStackTrace();
}
}
else if (phase <= Phases.ALL) {
System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovy stub generator: " + getPhaseDescription());
}
super.gotoPhase(phase);
}
};
unit.addSources(new String[]{"SomeClass.java"});
return unit;
}
static GroovyClassLoader buildClassLoaderFor(final CompilerConfiguration compilerConfiguration, final AstAwareResourceLoader resourceLoader) {
GroovyClassLoader classLoader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() {
public GroovyClassLoader run() {
return new GroovyClassLoader(getClass().getClassLoader(), compilerConfiguration) {
public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript)
throws ClassNotFoundException, CompilationFailedException {
Class aClass;
try {
aClass = super.loadClass(name, lookupScriptFiles, preferClassOverScript);
}
catch (NoClassDefFoundError e) {
throw new ClassNotFoundException(name);
}
catch (LinkageError e) {
throw new RuntimeException("Problem loading class " + name, e);
}
ensureWellFormed(aClass, new HashSet<Class>());
return aClass;
}
private void ensureWellFormed(Type aClass, Set<Class> visited) throws ClassNotFoundException {
if (aClass instanceof Class) {
ensureWellFormed((Class)aClass, visited);
}
else if (aClass instanceof ParameterizedType) {
ensureWellFormed(((ParameterizedType)aClass).getOwnerType(), visited);
for (Type type : ((ParameterizedType)aClass).getActualTypeArguments()) {
ensureWellFormed(type, visited);
}
}
else if (aClass instanceof WildcardType) {
for (Type type : ((WildcardType)aClass).getLowerBounds()) {
ensureWellFormed(type, visited);
}
for (Type type : ((WildcardType)aClass).getUpperBounds()) {
ensureWellFormed(type, visited);
}
}
else if (aClass instanceof GenericArrayType) {
ensureWellFormed(((GenericArrayType)aClass).getGenericComponentType(), visited);
}
}
private void ensureWellFormed(Class aClass, Set<Class> visited) throws ClassNotFoundException {
String name = aClass.getName();
if (resourceLoader.getSourceFile(name) != null && visited.add(aClass)) {
try {
for (Method method : aClass.getDeclaredMethods()) {
ensureWellFormed(method.getGenericReturnType(), visited);
for (Type type : method.getGenericExceptionTypes()) {
ensureWellFormed(type, visited);
}
for (Type type : method.getGenericParameterTypes()) {
ensureWellFormed(type, visited);
}
}
for (Constructor method : aClass.getDeclaredConstructors()) {
for (Type type : method.getGenericExceptionTypes()) {
ensureWellFormed(type, visited);
}
for (Type type : method.getGenericParameterTypes()) {
ensureWellFormed(type, visited);
}
}
for (Field field : aClass.getDeclaredFields()) {
ensureWellFormed(field.getGenericType(), visited);
}
Type superclass = aClass.getGenericSuperclass();
if (superclass != null) {
ensureWellFormed(aClass, visited);
}
for (Type intf : aClass.getGenericInterfaces()) {
ensureWellFormed(intf, visited);
}
aClass.getAnnotations();
Package aPackage = aClass.getPackage();
if (aPackage != null) {
aPackage.getAnnotations();
}
}
catch (LinkageError e) {
throw new ClassNotFoundException(name);
}
catch (TypeNotPresentException e) {
throw new ClassNotFoundException(name);
}
}
}
};
}
});
classLoader.setResourceLoader(resourceLoader);
return classLoader;
}
}