Moved parts of ide-compiler-runner to direct modules.

This commit is contained in:
Evgeny Gerashchenko
2015-02-12 17:37:51 +03:00
parent 0f3971323f
commit 3e1b869070
18 changed files with 7 additions and 7 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
<orderEntry type="module" module-name="cli-common" />
<orderEntry type="library" name="jps" level="project" />
<orderEntry type="library" scope="TEST" name="jps-test" level="project" />
<orderEntry type="library" scope="TEST" name="idea-full" level="project" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="backend" scope="TEST" />
<orderEntry type="module" module-name="descriptors" />
<orderEntry type="module" module-name="frontend.java" />
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ComparatorUtil;
import com.sampullara.cli.Argument;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class ArgumentUtils {
private ArgumentUtils() {}
@NotNull
public static List<String> convertArgumentsToStringList(@NotNull CommonCompilerArguments arguments)
throws InstantiationException, IllegalAccessException {
List<String> result = new ArrayList<String>();
convertArgumentsToStringList(arguments, arguments.getClass().newInstance(), arguments.getClass(), result);
result.addAll(arguments.freeArgs);
return result;
}
private static void convertArgumentsToStringList(
@NotNull CommonCompilerArguments arguments,
@NotNull CommonCompilerArguments defaultArguments,
@NotNull Class<?> clazz,
@NotNull List<String> result
) throws IllegalAccessException, InstantiationException {
for (Field field : clazz.getDeclaredFields()) {
Argument argument = field.getAnnotation(Argument.class);
if (argument == null) continue;
Object value;
Object defaultValue;
try {
value = field.get(arguments);
defaultValue = field.get(defaultArguments);
}
catch (IllegalAccessException ignored) {
// skip this field
continue;
}
if (ComparatorUtil.equalsNullable(value, defaultValue)) continue;
String name = getAlias(argument);
if (name == null) {
name = getName(argument, field);
}
Class<?> fieldType = field.getType();
if (fieldType.isArray()) {
Object[] values = (Object[]) value;
if (values.length == 0) continue;
//noinspection unchecked
value = StringUtil.join(values, Function.TO_STRING, argument.delimiter());
}
result.add(argument.prefix() + name);
if (fieldType == boolean.class || fieldType == Boolean.class) continue;
result.add(value.toString());
}
Class<?> superClazz = clazz.getSuperclass();
if (superClazz != null) {
convertArgumentsToStringList(arguments, defaultArguments, superClazz, result);
}
}
private static String getAlias(Argument argument) {
String alias = argument.alias();
return alias.isEmpty() ? null : alias;
}
private static String getName(Argument argument, Field field) {
String name = argument.value();
return name.isEmpty() ? field.getName() : name;
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.config.Services;
import org.jetbrains.kotlin.preloading.ClassCondition;
import org.jetbrains.kotlin.utils.KotlinPaths;
import org.jetbrains.kotlin.utils.PathUtil;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR;
public class CompilerEnvironment {
@NotNull
public static CompilerEnvironment getEnvironmentFor(
@NotNull KotlinPaths kotlinPaths,
@NotNull ClassCondition classesToLoadByParent,
@NotNull Services compilerServices
) {
return new CompilerEnvironment(kotlinPaths, classesToLoadByParent, compilerServices);
}
private final KotlinPaths kotlinPaths;
private final ClassCondition classesToLoadByParent;
private final Services services;
private CompilerEnvironment(
@NotNull KotlinPaths kotlinPaths,
@NotNull ClassCondition classesToLoadByParent,
@NotNull Services services
) {
this.kotlinPaths = kotlinPaths;
this.classesToLoadByParent = classesToLoadByParent;
this.services = services;
}
public boolean success() {
return kotlinPaths.getHomePath().exists();
}
@NotNull
public KotlinPaths getKotlinPaths() {
return kotlinPaths;
}
@NotNull
public ClassCondition getClassesToLoadByParent() {
return classesToLoadByParent;
}
public void reportErrorsTo(@NotNull MessageCollector messageCollector) {
if (!kotlinPaths.getHomePath().exists()) {
messageCollector.report(ERROR, "Cannot find kotlinc home: " + kotlinPaths.getHomePath() + ". Make sure plugin is properly installed, " +
"or specify " + PathUtil.JPS_KOTLIN_HOME_PROPERTY + " system property", NO_LOCATION);
}
}
@NotNull
public Services getServices() {
return services;
}
}
@@ -0,0 +1,188 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation;
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.Reader;
import java.util.Map;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*;
import static org.jetbrains.kotlin.cli.common.messages.MessageCollectorUtil.reportException;
public class CompilerOutputParser {
public static void parseCompilerMessagesFromReader(MessageCollector messageCollector, final Reader reader, OutputItemsCollector collector) {
// Sometimes the compiler doesn't output valid XML.
// Example: error in command line arguments passed to the compiler.
// The compiler will print the usage and the SAX parser will break.
// In this case, we want to read everything from this stream and report it as an IDE error.
final StringBuilder stringBuilder = new StringBuilder();
Reader wrappingReader = new Reader() {
@Override
public int read(@NotNull char[] cbuf, int off, int len) throws IOException {
int read = reader.read(cbuf, off, len);
stringBuilder.append(cbuf, off, len);
return read;
}
@Override
public void close() throws IOException {
// Do nothing:
// If the SAX parser sees a syntax error, it throws an exception
// and calls close() on the reader.
// We prevent hte reader from being closed here, and close it later,
// when all the text is read from it
}
};
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(new InputSource(wrappingReader), new CompilerOutputSAXHandler(messageCollector, collector));
}
catch (Throwable e) {
// Load all the text into the stringBuilder
try {
// This will not close the reader (see the wrapper above)
FileUtil.loadTextAndClose(wrappingReader);
}
catch (IOException ioException) {
reportException(messageCollector, ioException);
}
String message = stringBuilder.toString();
reportException(messageCollector, new IllegalStateException(message, e));
messageCollector.report(ERROR, message, NO_LOCATION);
}
finally {
try {
reader.close();
}
catch (IOException e) {
reportException(messageCollector, e);
}
}
}
private static class CompilerOutputSAXHandler extends DefaultHandler {
private static final Map<String, CompilerMessageSeverity> CATEGORIES = new ContainerUtil.ImmutableMapBuilder<String, CompilerMessageSeverity>()
.put("error", ERROR)
.put("warning", WARNING)
.put("logging", LOGGING)
.put("output", OUTPUT)
.put("exception", EXCEPTION)
.put("info", INFO)
.put("messages", INFO) // Root XML element
.build();
private final MessageCollector messageCollector;
private final OutputItemsCollector collector;
private final StringBuilder message = new StringBuilder();
private final Stack<String> tags = new Stack<String>();
private String path;
private int line;
private int column;
public CompilerOutputSAXHandler(MessageCollector messageCollector, OutputItemsCollector collector) {
this.messageCollector = messageCollector;
this.collector = collector;
}
@Override
public void startElement(@NotNull String uri, @NotNull String localName, @NotNull String qName, @NotNull Attributes attributes)
throws SAXException {
tags.push(qName);
message.setLength(0);
path = attributes.getValue("path");
line = safeParseInt(attributes.getValue("line"), -1);
column = safeParseInt(attributes.getValue("column"), -1);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (tags.size() == 1) {
// We're directly inside the root tag: <MESSAGES>
String message = new String(ch, start, length);
if (!message.trim().isEmpty()) {
messageCollector.report(ERROR, "Unhandled compiler output: " + message, NO_LOCATION);
}
}
else {
message.append(ch, start, length);
}
}
@Override
public void endElement(String uri, @NotNull String localName, @NotNull String qName) throws SAXException {
if (tags.size() == 1) {
// We're directly inside the root tag: <MESSAGES>
return;
}
String qNameLowerCase = qName.toLowerCase();
CompilerMessageSeverity category = CATEGORIES.get(qNameLowerCase);
if (category == null) {
messageCollector.report(ERROR, "Unknown compiler message tag: " + qName, NO_LOCATION);
category = INFO;
}
String text = message.toString();
if (category == OUTPUT) {
reportToCollector(text);
}
else {
messageCollector.report(category, text, CompilerMessageLocation.create(path, line, column));
}
tags.pop();
}
private void reportToCollector(String text) {
OutputMessageUtil.Output output = OutputMessageUtil.parseOutputMessage(text);
if (output != null) {
collector.add(output.sourceFiles, output.outputFile);
}
}
private static int safeParseInt(@Nullable String value, int defaultValue) {
if (value == null) {
return defaultValue;
}
try {
return Integer.parseInt(value.trim());
}
catch (NumberFormatException e) {
return defaultValue;
}
}
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.preloading.ClassPreloadingUtils;
import org.jetbrains.kotlin.utils.KotlinPaths;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.Collections;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR;
public class CompilerRunnerUtil {
private static SoftReference<ClassLoader> ourClassLoaderRef = new SoftReference<ClassLoader>(null);
@NotNull
private static synchronized ClassLoader getOrCreateClassLoader(
@NotNull CompilerEnvironment environment,
@NotNull File libPath
) throws IOException {
ClassLoader classLoader = ourClassLoaderRef.get();
if (classLoader == null) {
classLoader = ClassPreloadingUtils.preloadClasses(
Collections.singletonList(new File(libPath, "kotlin-compiler.jar")),
/* estimatedClassNumber = */ 4096,
CompilerRunnerUtil.class.getClassLoader(),
environment.getClassesToLoadByParent()
);
ourClassLoaderRef = new SoftReference<ClassLoader>(classLoader);
}
return classLoader;
}
@Nullable
private static File getLibPath(@NotNull KotlinPaths paths, @NotNull MessageCollector messageCollector) {
File libs = paths.getLibPath();
if (libs.exists() && !libs.isFile()) return libs;
messageCollector.report(
ERROR,
"Broken compiler at '" + libs.getAbsolutePath() + "'. Make sure plugin is properly installed",
NO_LOCATION
);
return null;
}
@Nullable
public static Object invokeExecMethod(
@NotNull String compilerClassName,
@NotNull String[] arguments,
@NotNull CompilerEnvironment environment,
@NotNull MessageCollector messageCollector,
@NotNull PrintStream out
) throws Exception {
File libPath = getLibPath(environment.getKotlinPaths(), messageCollector);
if (libPath == null) return null;
ClassLoader classLoader = getOrCreateClassLoader(environment, libPath);
Class<?> kompiler = Class.forName(compilerClassName, true, classLoader);
Method exec = kompiler.getMethod(
"execAndOutputXml",
PrintStream.class,
Class.forName("org.jetbrains.kotlin.config.Services", true, classLoader),
String[].class
);
return exec.invoke(kompiler.newInstance(), out, environment.getServices(), arguments);
}
}
@@ -0,0 +1,176 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.Accessor;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.ExitCode;
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorUtil;
import org.jetbrains.kotlin.config.CompilerSettings;
import java.io.*;
import java.util.Collection;
import java.util.List;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR;
import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.INFO;
public class KotlinCompilerRunner {
private static final String K2JVM_COMPILER = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler";
private static final String K2JS_COMPILER = "org.jetbrains.kotlin.cli.js.K2JSCompiler";
private static final String INTERNAL_ERROR = ExitCode.INTERNAL_ERROR.toString();
public static void runK2JvmCompiler(
CommonCompilerArguments commonArguments,
K2JVMCompilerArguments k2jvmArguments,
CompilerSettings compilerSettings,
MessageCollector messageCollector,
CompilerEnvironment environment,
File moduleFile,
OutputItemsCollector collector
) {
K2JVMCompilerArguments arguments = mergeBeans(commonArguments, k2jvmArguments);
setupK2JvmArguments(moduleFile, arguments);
runCompiler(K2JVM_COMPILER, arguments, compilerSettings.getAdditionalArguments(), messageCollector, collector, environment);
}
public static void runK2JsCompiler(
CommonCompilerArguments commonArguments,
K2JSCompilerArguments k2jsArguments,
CompilerSettings compilerSettings,
MessageCollector messageCollector,
CompilerEnvironment environment,
OutputItemsCollector collector,
Collection<File> sourceFiles,
List<String> libraryFiles,
File outputFile
) {
K2JSCompilerArguments arguments = mergeBeans(commonArguments, k2jsArguments);
setupK2JsArguments(outputFile, sourceFiles, libraryFiles, arguments);
runCompiler(K2JS_COMPILER, arguments, compilerSettings.getAdditionalArguments(), messageCollector, collector, environment);
}
private static void runCompiler(
String compilerClassName,
CommonCompilerArguments arguments,
String additionalArguments,
MessageCollector messageCollector,
OutputItemsCollector collector,
CompilerEnvironment environment
) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
PrintStream out = new PrintStream(stream);
String exitCode = execCompiler(compilerClassName, arguments, additionalArguments, environment, out, messageCollector);
BufferedReader reader = new BufferedReader(new StringReader(stream.toString()));
CompilerOutputParser.parseCompilerMessagesFromReader(messageCollector, reader, collector);
if (INTERNAL_ERROR.equals(exitCode)) {
messageCollector.report(ERROR, "Compiler terminated with internal error", NO_LOCATION);
}
}
@NotNull
private static String execCompiler(
String compilerClassName,
CommonCompilerArguments arguments,
String additionalArguments,
CompilerEnvironment environment,
PrintStream out,
MessageCollector messageCollector
) {
try {
messageCollector.report(INFO, "Using kotlin-home = " + environment.getKotlinPaths().getHomePath(), NO_LOCATION);
List<String> argumentsList = ArgumentUtils.convertArgumentsToStringList(arguments);
argumentsList.addAll(StringUtil.split(additionalArguments, " "));
Object rc = CompilerRunnerUtil.invokeExecMethod(
compilerClassName, ArrayUtil.toStringArray(argumentsList), environment, messageCollector, out
);
// exec() returns an ExitCode object, class of which is loaded with a different class loader,
// so we take it's contents through reflection
return getReturnCodeFromObject(rc);
}
catch (Throwable e) {
MessageCollectorUtil.reportException(messageCollector, e);
return INTERNAL_ERROR;
}
}
@NotNull
private static String getReturnCodeFromObject(@Nullable Object rc) throws Exception {
if (rc == null) {
return INTERNAL_ERROR;
}
else if (ExitCode.class.getName().equals(rc.getClass().getName())) {
return rc.toString();
}
else {
throw new IllegalStateException("Unexpected return: " + rc);
}
}
private static <F, T extends F> T mergeBeans(F from, T to) {
T copy = XmlSerializerUtil.createCopy(to);
for (Accessor accessor : XmlSerializerUtil.getAccessors(from.getClass())) {
accessor.write(copy, accessor.read(from));
}
return copy;
}
private static void setupK2JvmArguments(File moduleFile, K2JVMCompilerArguments settings) {
settings.module = moduleFile.getAbsolutePath();
settings.noStdlib = true;
settings.noJdkAnnotations = true;
settings.noJdk = true;
}
private static void setupK2JsArguments(
File outputFile,
Collection<File> sourceFiles,
List<String> libraryFiles,
K2JSCompilerArguments settings
) {
settings.noStdlib = true;
settings.freeArgs = ContainerUtil.map(sourceFiles, new Function<File, String>() {
@Override
public String fun(File file) {
return file.getPath();
}
});
settings.outputFile = outputFile.getPath();
settings.libraryFiles = ArrayUtil.toStringArray(libraryFiles);
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import java.io.File;
import java.util.Collection;
public interface OutputItemsCollector {
void add(Collection<File> sourceFiles, File outputFile);
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Collection;
import java.util.List;
public class OutputItemsCollectorImpl implements OutputItemsCollector {
private final List<SimpleOutputItem> outputs = ContainerUtil.newArrayList();
@Override
public void add(Collection<File> sourceFiles, File outputFile) {
outputs.add(new SimpleOutputItem(sourceFiles, outputFile));
}
@NotNull
public List<SimpleOutputItem> getOutputs() {
return outputs;
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2015 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.kotlin.compilerRunner;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Collection;
public class SimpleOutputItem {
private final Collection<File> sourceFiles;
private final File outputFile;
public SimpleOutputItem(@NotNull Collection<File> sourceFiles, @NotNull File outputFile) {
this.sourceFiles = sourceFiles;
this.outputFile = outputFile;
}
@NotNull
public Collection<File> getSourceFiles() {
return sourceFiles;
}
@NotNull
public File getOutputFile() {
return outputFile;
}
@Override
public String toString() {
return sourceFiles + " -> " + outputFile;
}
}
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.compilerRunner.CompilerEnvironment
import org.jetbrains.kotlin.compilerRunner.CompilerRunnerConstants
import org.jetbrains.kotlin.config.CompilerRunnerConstants
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.config.IncrementalCompilation
@@ -43,7 +43,7 @@ import java.io.File
import java.util.*
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
import org.jetbrains.kotlin.compilerRunner.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX
import org.jetbrains.kotlin.config.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX
import org.jetbrains.kotlin.compilerRunner.KotlinCompilerRunner.runK2JsCompiler
import org.jetbrains.kotlin.compilerRunner.KotlinCompilerRunner.runK2JvmCompiler
import org.jetbrains.kotlin.utils.keysToMap
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2015 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.kotlin.modules;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
public interface KotlinModuleDescriptionBuilder {
KotlinModuleDescriptionBuilder addModule(
String moduleName,
String outputDir,
List<File> sourceFiles,
List<File> javaSourceRoots,
Collection<File> classpathRoots,
List<File> annotationRoots,
boolean tests,
Set<File> directoriesToFilterOut
);
CharSequence asText();
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2015 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.kotlin.modules;
public interface KotlinModuleDescriptionBuilderFactory {
KotlinModuleDescriptionBuilder create();
String getFileExtension();
}
@@ -0,0 +1,125 @@
/*
* Copyright 2010-2015 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.kotlin.modules;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName;
public class KotlinModuleScriptBuilderFactory implements KotlinModuleDescriptionBuilderFactory {
public static final KotlinModuleScriptBuilderFactory INSTANCE = new KotlinModuleScriptBuilderFactory();
private KotlinModuleScriptBuilderFactory() {}
@Override
public KotlinModuleDescriptionBuilder create() {
return new Builder();
}
@Override
public String getFileExtension() {
return "kts";
}
private static class Builder implements KotlinModuleDescriptionBuilder {
private final StringBuilder script = new StringBuilder();
private boolean done = false;
{
script.append("import kotlin.modules.*\n");
script.append("fun project() {\n");
}
@Override
public KotlinModuleDescriptionBuilder addModule(
String moduleName,
String outputDir,
List<File> sourceFiles,
List<File> javaSourceRoots,
Collection<File> classpathRoots,
List<File> annotationRoots,
boolean tests,
Set<File> directoriesToFilterOut
) {
assert !done : "Already done";
if (tests) {
script.append("// Module script for tests\n");
}
else {
script.append("// Module script for production\n");
}
script.append(" module(\"" + moduleName + "\", outputDir = \"" + toSystemIndependentName(outputDir) + "\") {\n");
for (File sourceFile : sourceFiles) {
script.append(" sources += \"" + toSystemIndependentName(sourceFile.getPath()) + "\"\n");
}
if (!javaSourceRoots.isEmpty()) {
processClassPathSection("Java source roots", javaSourceRoots, directoriesToFilterOut);
}
processClassPathSection("Classpath", classpathRoots, directoriesToFilterOut);
processAnnotationRoots(annotationRoots);
script.append(" }\n");
return this;
}
private void processClassPathSection(
@NotNull String sectionDescription,
@NotNull Collection<File> files,
@NotNull Set<File> directoriesToFilterOut
) {
script.append(" // " + sectionDescription + "\n");
for (File file : files) {
if (directoriesToFilterOut.contains(file)) {
// For IDEA's make (incremental compilation) purposes, output directories of the current module and its dependencies
// appear on the class path, so we are at risk of seeing the results of the previous build, i.e. if some class was
// removed in the sources, it may still be there in binaries. Thus, we delete these entries from the classpath.
script.append(" // Output directory, commented out\n");
script.append(" // ");
}
script.append(" classpath += \"" + toSystemIndependentName(file.getPath()) + "\"\n");
}
}
private void processAnnotationRoots(@NotNull List<File> files) {
script.append(" // External annotations\n");
for (File file : files) {
script.append(" annotationsPath += \"").append(toSystemIndependentName(file.getPath())).append("\"\n");
}
}
@Override
public CharSequence asText() {
if (!done) {
script.append("}\n");
done = true;
}
return script;
}
}
}
@@ -0,0 +1,154 @@
/*
* Copyright 2010-2015 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.kotlin.modules;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.config.IncrementalCompilation;
import org.jetbrains.kotlin.utils.Printer;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName;
import static com.intellij.openapi.util.text.StringUtil.escapeXml;
import static org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser.*;
public class KotlinModuleXmlBuilderFactory implements KotlinModuleDescriptionBuilderFactory {
public static final KotlinModuleXmlBuilderFactory INSTANCE = new KotlinModuleXmlBuilderFactory();
private KotlinModuleXmlBuilderFactory() {}
@Override
public KotlinModuleDescriptionBuilder create() {
return new Builder();
}
@Override
public String getFileExtension() {
return "xml";
}
private static class Builder implements KotlinModuleDescriptionBuilder {
private final StringBuilder xml = new StringBuilder();
private final Printer p = new Printer(xml);
private boolean done = false;
public Builder() {
openTag(p, MODULES);
}
@Override
public KotlinModuleDescriptionBuilder addModule(
String moduleName,
String outputDir,
List<File> sourceFiles,
List<File> javaSourceRoots,
Collection<File> classpathRoots,
List<File> annotationRoots,
boolean tests,
Set<File> directoriesToFilterOut
) {
assert !done : "Already done";
if (tests) {
p.println("<!-- Module script for tests -->");
}
else {
p.println("<!-- Module script for production -->");
}
p.println("<", MODULE, " ",
NAME, "=\"", escapeXml(moduleName), "\" ",
OUTPUT_DIR, "=\"", getEscapedPath(new File(outputDir)), "\">"
);
p.pushIndent();
for (File sourceFile : sourceFiles) {
p.println("<", SOURCES, " ", PATH, "=\"", getEscapedPath(sourceFile), "\"/>");
}
if (!javaSourceRoots.isEmpty()) {
processClassPathSection("Java source roots", javaSourceRoots, directoriesToFilterOut);
}
processClassPathSection("Classpath", classpathRoots, directoriesToFilterOut);
processAnnotationRoots(annotationRoots);
closeTag(p, MODULE);
return this;
}
private void processClassPathSection(
@NotNull String sectionDescription,
@NotNull Collection<File> files,
@NotNull Set<File> directoriesToFilterOut
) {
p.println("<!-- ", sectionDescription, " -->");
for (File file : files) {
boolean isOutput = directoriesToFilterOut.contains(file) && !IncrementalCompilation.ENABLED;
if (isOutput) {
// For IDEA's make (incremental compilation) purposes, output directories of the current module and its dependencies
// appear on the class path, so we are at risk of seeing the results of the previous build, i.e. if some class was
// removed in the sources, it may still be there in binaries. Thus, we delete these entries from the classpath.
p.println("<!-- Output directory, commented out -->");
p.println("<!-- ");
p.pushIndent();
}
p.println("<", CLASSPATH, " ", PATH, "=\"", getEscapedPath(file), "\"/>");
if (isOutput) {
p.popIndent();
p.println("-->");
}
}
}
private void processAnnotationRoots(@NotNull List<File> files) {
p.println("<!-- External annotations -->");
for (File file : files) {
p.println("<", EXTERNAL_ANNOTATIONS, " ", PATH, "=\"", getEscapedPath(file), "\"/>");
}
}
@Override
public CharSequence asText() {
if (!done) {
closeTag(p, MODULES);
done = true;
}
return xml;
}
}
private static void openTag(Printer p, String tag) {
p.println("<" + tag + ">");
p.pushIndent();
}
private static void closeTag(Printer p, String tag) {
p.popIndent();
p.println("</" + tag + ">");
}
private static String getEscapedPath(File sourceFile) {
return escapeXml(toSystemIndependentName(sourceFile.getPath()));
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2015 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.kotlin.jvm.compiler
import org.jetbrains.kotlin.test.TestCaseWithTmpdir
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.test.MockLibraryUtil
import org.jetbrains.kotlin.modules.KotlinModuleXmlBuilderFactory
import java.io.File
import org.jetbrains.kotlin.utils.PathUtil
/**
* This test checks that Java classes from sources have higher priority in Kotlin resolution process than classes from binaries.
* To test this, we compile a Kotlin+Java module (in two modes: CLI and module-based) where a runtime Java class was replaced
* with a "newer" version in sources, and check that this class resolves to the one from sources by calling a method absent in the runtime
*/
public class ClasspathOrderTest : TestCaseWithTmpdir() {
class object {
val sourceDir = File(JetTestUtils.getTestDataPathBase() + "/classpathOrder").getAbsoluteFile()
}
public fun testClasspathOrderForCLI() {
MockLibraryUtil.compileKotlin(sourceDir.getPath(), tmpdir)
}
public fun testClasspathOrderForModuleScriptBuild() {
val xmlContent = KotlinModuleXmlBuilderFactory.INSTANCE.create().addModule(
"name",
File(tmpdir, "output").getAbsolutePath(),
listOf(sourceDir),
listOf(sourceDir),
listOf(PathUtil.getKotlinPathsForDistDirectory().getRuntimePath()),
listOf(),
false,
setOf()
).asText().toString()
val xml = File(tmpdir, "module.xml")
xml.writeText(xmlContent)
MockLibraryUtil.compileKotlinModule(xml.getAbsolutePath())
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2015 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.kotlin.modules;
import junit.framework.TestCase;
import org.jetbrains.kotlin.test.JetTestUtils;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
public class KotlinModuleXmlGeneratorTest extends TestCase {
public void testBasic() throws Exception {
String actual = KotlinModuleXmlBuilderFactory.INSTANCE.create().addModule(
"name",
"output",
Arrays.asList(new File("s1"), new File("s2")),
Collections.singletonList(new File("java")),
Arrays.asList(new File("cp1"), new File("cp2")),
Arrays.asList(new File("a1/f1"), new File("a2")),
false,
Collections.<File>emptySet()
).asText().toString();
JetTestUtils.assertEqualsToFile(new File("idea/testData/modules.xml/basic.xml"), actual);
}
public void testFiltered() throws Exception {
String actual = KotlinModuleXmlBuilderFactory.INSTANCE.create().addModule(
"name",
"output",
Arrays.asList(new File("s1"), new File("s2")),
Collections.<File>emptyList(),
Arrays.asList(new File("cp1"), new File("cp2")),
Arrays.asList(new File("a1/f1"), new File("a2")),
false,
Collections.singleton(new File("cp1"))
).asText().toString();
JetTestUtils.assertEqualsToFile(new File("idea/testData/modules.xml/filtered.xml"), actual);
}
public void testMultiple() throws Exception {
KotlinModuleDescriptionBuilder builder = KotlinModuleXmlBuilderFactory.INSTANCE.create();
builder.addModule(
"name",
"output",
Arrays.asList(new File("s1"), new File("s2")),
Collections.<File>emptyList(),
Arrays.asList(new File("cp1"), new File("cp2")),
Arrays.asList(new File("a1/f1"), new File("a2")),
false,
Collections.singleton(new File("cp1"))
);
builder.addModule(
"name2",
"output2",
Arrays.asList(new File("s12"), new File("s22")),
Collections.<File>emptyList(),
Arrays.asList(new File("cp12"), new File("cp22")),
Arrays.asList(new File("a12/f12"), new File("a22")),
true,
Collections.singleton(new File("cp12"))
);
String actual = builder.asText().toString();
JetTestUtils.assertEqualsToFile(new File("idea/testData/modules.xml/multiple.xml"), actual);
}
}