diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionBuilder.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionBuilder.java new file mode 100644 index 00000000000..6ce9d670000 --- /dev/null +++ b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionBuilder.java @@ -0,0 +1,27 @@ +package org.jetbrains.jet.compiler.runner; + +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, + DependencyProvider dependencyProvider, + List sourceFiles, + boolean tests, + Set directoriesToFilterOut); + + CharSequence asText(); + + interface DependencyProvider { + void processClassPath(DependencyProcessor processor); + } + + interface DependencyProcessor { + void processClassPathSection(String sectionDescription, Collection files); + void processAnnotationRoots(List files); + } +} diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionBuilderFactory.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionBuilderFactory.java new file mode 100644 index 00000000000..dbeb4e07903 --- /dev/null +++ b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionBuilderFactory.java @@ -0,0 +1,22 @@ +/* + * Copyright 2010-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.jet.compiler.runner; + +public interface KotlinModuleDescriptionBuilderFactory { + KotlinModuleDescriptionBuilder create(); + String getFileExtension(); +} diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionGenerator.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionGenerator.java deleted file mode 100644 index 7119ce9a452..00000000000 --- a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleDescriptionGenerator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2010-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.jet.compiler.runner; - -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -public interface KotlinModuleDescriptionGenerator { - CharSequence generateModuleScript( - String moduleName, - String outputDir, - DependencyProvider dependencyProvider, - List sourceFiles, - boolean tests, - Set directoriesToFilterOut - ); - - String getFileExtension(); - - interface DependencyProvider { - void processClassPath(@NotNull DependencyProcessor processor); - } - - interface DependencyProcessor { - void processClassPathSection(@NotNull String sectionDescription, @NotNull Collection files); - void processAnnotationRoots(@NotNull List files); - } -} diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleScriptBuilderFactory.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleScriptBuilderFactory.java new file mode 100644 index 00000000000..30c8d04a5bd --- /dev/null +++ b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleScriptBuilderFactory.java @@ -0,0 +1,117 @@ +/* + * Copyright 2010-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.jet.compiler.runner; + +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, + DependencyProvider dependencyProvider, + List sourceFiles, + boolean tests, + final Set 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"); + } + + dependencyProvider.processClassPath(new DependencyProcessor() { + @Override + public void processClassPathSection(@NotNull String sectionDescription, @NotNull Collection files) { + 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"); + } + } + + @Override + public void processAnnotationRoots(@NotNull List files) { + script.append(" // External annotations\n"); + for (File file : files) { + script.append(" annotationsPath += \"").append(toSystemIndependentName(file.getPath())).append("\"\n"); + } + } + }); + + script.append(" }\n"); + return this; + } + + @Override + public CharSequence asText() { + if (!done) { + script.append("}\n"); + done = true; + } + + return script; + } + } + +} diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleScriptGenerator.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleScriptGenerator.java deleted file mode 100644 index be7fcfec1ef..00000000000 --- a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleScriptGenerator.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2010-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.jet.compiler.runner; - -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 KotlinModuleScriptGenerator implements KotlinModuleDescriptionGenerator { - - public static final KotlinModuleScriptGenerator INSTANCE = new KotlinModuleScriptGenerator(); - - private KotlinModuleScriptGenerator() {} - - @Override - public CharSequence generateModuleScript( - String moduleName, - String outputDir, DependencyProvider dependencyProvider, - List sourceFiles, - boolean tests, - final Set directoriesToFilterOut - ) { - final StringBuilder script = new StringBuilder(); - - if (tests) { - script.append("// Module script for tests\n"); - } - else { - script.append("// Module script for production\n"); - } - - script.append("import kotlin.modules.*\n"); - script.append("fun project() {\n"); - script.append(" module(\"" + moduleName + "\", outputDir = \"" + toSystemIndependentName(outputDir) + "\") {\n"); - - for (File sourceFile : sourceFiles) { - script.append(" sources += \"" + toSystemIndependentName(sourceFile.getPath()) + "\"\n"); - } - - dependencyProvider.processClassPath(new DependencyProcessor() { - @Override - public void processClassPathSection(@NotNull String sectionDescription, @NotNull Collection files) { - 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"); - } - } - - @Override - public void processAnnotationRoots(@NotNull List files) { - script.append(" // External annotations\n"); - for (File file : files) { - script.append(" annotationsPath += \"").append(toSystemIndependentName(file.getPath())).append("\"\n"); - } - } - }); - - script.append(" }\n"); - script.append("}\n"); - return script; - } - - @Override - public String getFileExtension() { - return "kts"; - } -} diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleXmlBuilderFactory.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleXmlBuilderFactory.java new file mode 100644 index 00000000000..a17f5c2eca9 --- /dev/null +++ b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleXmlBuilderFactory.java @@ -0,0 +1,143 @@ +/* + * Copyright 2010-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.jet.compiler.runner; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.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.jet.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; + + { + openTag(p, MODULES); + } + + @Override + public KotlinModuleDescriptionBuilder addModule( + String moduleName, + String outputDir, + DependencyProvider dependencyProvider, + List sourceFiles, + boolean tests, + final Set directoriesToFilterOut + ) { + assert !done : "Already done"; + + if (tests) { + p.println(""); + } + else { + p.println(""); + } + + p.println("<", MODULE, " ", + NAME, "=\"", escapeXml(moduleName), "\" ", + OUTPUT_DIR, "=\"", getEscapedPath(new File(outputDir)), "\">"); + p.pushIndent(); + + for (File sourceFile : sourceFiles) { + p.println("<", SOURCES, " ", PATH, "=\"", getEscapedPath(sourceFile), "\"/>"); + } + + dependencyProvider.processClassPath(new DependencyProcessor() { + @Override + public void processClassPathSection(@NotNull String sectionDescription, @NotNull Collection files) { + p.println(""); + for (File file : files) { + boolean isOutput = directoriesToFilterOut.contains(file); + 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(""); + p.println(""); + } + } + } + + @Override + public void processAnnotationRoots(@NotNull List files) { + p.println(""); + for (File file : files) { + p.println("<", EXTERNAL_ANNOTATIONS, " ", PATH, "= \"", getEscapedPath(file), "\"/>"); + } + } + }); + + closeTag(p, MODULE); + return this; + } + + @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(""); + } + + private static String getEscapedPath(File sourceFile) { + return escapeXml(toSystemIndependentName(sourceFile.getPath())); + } +} diff --git a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleXmlGenerator.java b/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleXmlGenerator.java deleted file mode 100644 index 5f9dec940f7..00000000000 --- a/ide-compiler-runner/src/org/jetbrains/jet/compiler/runner/KotlinModuleXmlGenerator.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2010-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.jet.compiler.runner; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.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.jet.cli.common.modules.ModuleXmlParser.*; - -public class KotlinModuleXmlGenerator implements KotlinModuleDescriptionGenerator { - - public static final KotlinModuleXmlGenerator INSTANCE = new KotlinModuleXmlGenerator(); - - private KotlinModuleXmlGenerator() {} - - @Override - public CharSequence generateModuleScript( - String moduleName, - String outputDir, DependencyProvider dependencyProvider, - List sourceFiles, - boolean tests, - final Set directoriesToFilterOut - ) { - StringBuilder xml = new StringBuilder(); - final Printer p = new Printer(xml); - - openTag(p, MODULES); - - if (tests) { - p.println(""); - } - else { - p.println(""); - } - - p.println("<", MODULE, " ", - NAME, "=\"", escapeXml(moduleName), "\" ", - OUTPUT_DIR, "=\"", getEscapedPath(new File(outputDir)), "\">"); - p.pushIndent(); - - for (File sourceFile : sourceFiles) { - p.println("<", SOURCES, " ", PATH, "=\"", getEscapedPath(sourceFile), "\"/>"); - } - - dependencyProvider.processClassPath(new DependencyProcessor() { - @Override - public void processClassPathSection(@NotNull String sectionDescription, @NotNull Collection files) { - p.println(""); - for (File file : files) { - boolean isOutput = directoriesToFilterOut.contains(file); - 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(""); - p.println(""); - } - } - } - - @Override - public void processAnnotationRoots(@NotNull List files) { - p.println(""); - for (File file : files) { - p.println("<", EXTERNAL_ANNOTATIONS, " ", PATH, "= \"", getEscapedPath(file), "\"/>"); - } - } - }); - - closeTag(p, MODULE); - - closeTag(p, MODULES); - return xml; - } - - @Override - public String getFileExtension() { - 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(""); - } - - private static String getEscapedPath(File sourceFile) { - return escapeXml(toSystemIndependentName(sourceFile.getPath())); - } -} diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java index a4e0d3f049d..b0319bfa224 100644 --- a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java +++ b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java @@ -157,14 +157,14 @@ public class JetCompiler implements TranslatingCompiler { if (!tests) { outputDirectoriesToFilter.add(moduleOutputDirectory); } - CharSequence script = KotlinModuleScriptGenerator.INSTANCE.generateModuleScript( + CharSequence script = KotlinModuleScriptBuilderFactory.INSTANCE.create().addModule( moduleName, moduleOutputDirectory.getAbsolutePath(), getDependencyProvider(chunk, tests, mainOutput), sourceFiles, tests, outputDirectoriesToFilter - ); + ).asText(); File scriptFile = new File(outputDir, "script.kts"); try { @@ -177,14 +177,14 @@ public class JetCompiler implements TranslatingCompiler { return scriptFile; } - private static KotlinModuleDescriptionGenerator.DependencyProvider getDependencyProvider( + private static KotlinModuleDescriptionBuilder.DependencyProvider getDependencyProvider( final ModuleChunk chunk, final boolean tests, final VirtualFile mainOutputPath ) { - return new KotlinModuleDescriptionGenerator.DependencyProvider() { + return new KotlinModuleDescriptionBuilder.DependencyProvider() { @Override - public void processClassPath(@NotNull KotlinModuleDescriptionGenerator.DependencyProcessor processor) { + public void processClassPath(@NotNull KotlinModuleDescriptionBuilder.DependencyProcessor processor) { // TODO: have a bootclasspath in script API processor.processClassPathSection("Boot classpath", ioFiles(chunk.getCompilationBootClasspathFiles())); diff --git a/idea/testData/modules.xml/multiple.xml b/idea/testData/modules.xml/multiple.xml new file mode 100644 index 00000000000..e9e7155d60a --- /dev/null +++ b/idea/testData/modules.xml/multiple.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/idea/tests/org/jetbrains/jet/modules/xml/KotlinModuleXmlGeneratorTest.java b/idea/tests/org/jetbrains/jet/modules/xml/KotlinModuleXmlGeneratorTest.java index d6c9d76d871..672906dd89c 100644 --- a/idea/tests/org/jetbrains/jet/modules/xml/KotlinModuleXmlGeneratorTest.java +++ b/idea/tests/org/jetbrains/jet/modules/xml/KotlinModuleXmlGeneratorTest.java @@ -20,8 +20,9 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.testFramework.UsefulTestCase; import junit.framework.TestCase; import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionGenerator; -import org.jetbrains.jet.compiler.runner.KotlinModuleXmlGenerator; +import org.jetbrains.jet.JetTestUtils; +import org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionBuilder; +import org.jetbrains.jet.compiler.runner.KotlinModuleXmlBuilderFactory; import java.io.File; import java.util.Arrays; @@ -29,38 +30,70 @@ import java.util.Collections; public class KotlinModuleXmlGeneratorTest extends TestCase { public void testBasic() throws Exception { - String actual = KotlinModuleXmlGenerator.INSTANCE.generateModuleScript( + String actual = KotlinModuleXmlBuilderFactory.INSTANCE.create().addModule( "name", "output", - new KotlinModuleDescriptionGenerator.DependencyProvider() { + new KotlinModuleDescriptionBuilder.DependencyProvider() { @Override - public void processClassPath(@NotNull KotlinModuleDescriptionGenerator.DependencyProcessor processor) { + public void processClassPath(@NotNull KotlinModuleDescriptionBuilder.DependencyProcessor processor) { processor.processAnnotationRoots(Arrays.asList(new File("a1/f1"), new File("a2"))); processor.processClassPathSection("s1", Arrays.asList(new File("cp1"), new File("cp2"))); } }, Arrays.asList(new File("s1"), new File("s2")), false, - Collections.emptySet()).toString(); + Collections.emptySet()).asText().toString(); String expected = FileUtil.loadFile(new File("idea/testData/modules.xml/basic.xml")); UsefulTestCase.assertSameLines(expected, actual); } public void testFiltered() throws Exception { - String actual = KotlinModuleXmlGenerator.INSTANCE.generateModuleScript( + String actual = KotlinModuleXmlBuilderFactory.INSTANCE.create().addModule( "name", "output", - new KotlinModuleDescriptionGenerator.DependencyProvider() { + new KotlinModuleDescriptionBuilder.DependencyProvider() { @Override - public void processClassPath(@NotNull KotlinModuleDescriptionGenerator.DependencyProcessor processor) { + public void processClassPath(@NotNull KotlinModuleDescriptionBuilder.DependencyProcessor processor) { processor.processAnnotationRoots(Arrays.asList(new File("a1/f1"), new File("a2"))); processor.processClassPathSection("s1", Arrays.asList(new File("cp1"), new File("cp2"))); } }, Arrays.asList(new File("s1"), new File("s2")), false, - Collections.singleton(new File("cp1"))).toString(); + Collections.singleton(new File("cp1"))).asText().toString(); String expected = FileUtil.loadFile(new File("idea/testData/modules.xml/filtered.xml")); UsefulTestCase.assertSameLines(expected, actual); } + + public void testMultiple() throws Exception { + KotlinModuleDescriptionBuilder builder = KotlinModuleXmlBuilderFactory.INSTANCE.create(); + builder.addModule( + "name", + "output", + new KotlinModuleDescriptionBuilder.DependencyProvider() { + @Override + public void processClassPath(@NotNull KotlinModuleDescriptionBuilder.DependencyProcessor processor) { + processor.processAnnotationRoots(Arrays.asList(new File("a1/f1"), new File("a2"))); + processor.processClassPathSection("s1", Arrays.asList(new File("cp1"), new File("cp2"))); + } + }, + Arrays.asList(new File("s1"), new File("s2")), + false, + Collections.singleton(new File("cp1"))); + builder.addModule( + "name2", + "output2", + new KotlinModuleDescriptionBuilder.DependencyProvider() { + @Override + public void processClassPath(@NotNull KotlinModuleDescriptionBuilder.DependencyProcessor processor) { + processor.processAnnotationRoots(Arrays.asList(new File("a12/f12"), new File("a22"))); + processor.processClassPathSection("s12", Arrays.asList(new File("cp12"), new File("cp22"))); + } + }, + Arrays.asList(new File("s12"), new File("s22")), + true, + Collections.singleton(new File("cp12"))); + String actual = builder.asText().toString(); + JetTestUtils.assertEqualsToFile(new File("idea/testData/modules.xml/multiple.xml"), actual); + } } diff --git a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java index 6b8f4db3769..f3feaf01107 100644 --- a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java +++ b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java @@ -39,9 +39,13 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.List; -import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.*; +import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.EXCEPTION; +import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.INFO; +import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.WARNING; public class KotlinBuilder extends ModuleLevelBuilder { @@ -81,29 +85,12 @@ public class KotlinBuilder extends ModuleLevelBuilder { messageCollector.report(INFO, "Kotlin JPS plugin version " + KotlinVersion.VERSION, CompilerMessageLocation.NO_LOCATION); - if (chunk.getModules().size() > 1) { - // We do not support circular dependencies, but if they are present, we should not break the build, - // so we simply yield a warning and report NOTHING_DONE - messageCollector.report( - WARNING, "Circular dependencies are not supported. " + - "The following modules depend on each other: " + StringUtil.join(chunk.getModules(), MODULE_NAME, ", ") + ". " + - "Kotlin is not compiled for these modules", - CompilerMessageLocation.NO_LOCATION); - return ExitCode.NOTHING_DONE; - } - ModuleBuildTarget representativeTarget = chunk.representativeTarget(); // For non-incremental build: take all sources if (!dirtyFilesHolder.hasDirtyFiles()) { return ExitCode.NOTHING_DONE; } - List sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(representativeTarget); - //List sourceFiles = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder); - - if (sourceFiles.isEmpty()) { - return ExitCode.NOTHING_DONE; - } File outputDir = representativeTarget.getOutputDir(); @@ -118,6 +105,24 @@ public class KotlinBuilder extends ModuleLevelBuilder { OutputItemsCollectorImpl outputItemCollector = new OutputItemsCollectorImpl(outputDir); if (JpsUtils.isJsKotlinModule(representativeTarget)) { + if (chunk.getModules().size() > 1) { + // We do not support circular dependencies, but if they are present, we do our best should not break the build, + // so we simply yield a warning and report NOTHING_DONE + messageCollector.report( + WARNING, "Circular dependencies are not supported. " + + "The following JS modules depend on each other: " + StringUtil.join(chunk.getModules(), MODULE_NAME, ", ") + ". " + + "Kotlin is not compiled for these modules", + CompilerMessageLocation.NO_LOCATION); + return ExitCode.NOTHING_DONE; + } + + List sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(representativeTarget); + //List sourceFiles = KotlinSourceFileCollector.getDirtySourceFiles(dirtyFilesHolder); + + if (sourceFiles.isEmpty()) { + return ExitCode.NOTHING_DONE; + } + File outputFile = new File(outputDir, representativeTarget.getModule().getName() + ".js"); KotlinCompilerRunner.runK2JsCompiler( @@ -129,7 +134,19 @@ public class KotlinBuilder extends ModuleLevelBuilder { outputFile); } else { - File moduleFile = KotlinBuilderModuleScriptGenerator.generateModuleDescription(context, representativeTarget, sourceFiles); + if (chunk.getModules().size() > 1) { + messageCollector.report( + WARNING, "Circular dependencies are only partially supported. " + + "The following modules depend on each other: " + StringUtil.join(chunk.getModules(), MODULE_NAME, ", ") + ". " + + "Kotlin will compile them, but some strange effect may happen", + CompilerMessageLocation.NO_LOCATION); + } + + File moduleFile = KotlinBuilderModuleScriptGenerator.generateModuleDescription(context, chunk); + if (moduleFile == null) { + // No Kotlin sources found + return ExitCode.NOTHING_DONE; + } KotlinCompilerRunner.runK2JvmCompiler( messageCollector, diff --git a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java index 810fbe7633b..f2195085954 100644 --- a/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java +++ b/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java @@ -20,14 +20,17 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionGenerator; -import org.jetbrains.jet.compiler.runner.KotlinModuleXmlGenerator; +import org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionBuilder; +import org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionBuilderFactory; +import org.jetbrains.jet.compiler.runner.KotlinModuleXmlBuilderFactory; +import org.jetbrains.jps.ModuleChunk; import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor; import org.jetbrains.jps.incremental.CompileContext; import org.jetbrains.jps.incremental.ModuleBuildTarget; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; -import org.jetbrains.jps.model.java.*; +import org.jetbrains.jps.model.java.JpsAnnotationRootType; +import org.jetbrains.jps.model.java.JpsJavaSdkType; import org.jetbrains.jps.model.library.JpsLibrary; import org.jetbrains.jps.model.library.sdk.JpsSdk; import org.jetbrains.jps.model.library.sdk.JpsSdkType; @@ -42,42 +45,61 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import static org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionGenerator.DependencyProvider; +import static org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionBuilder.DependencyProcessor; +import static org.jetbrains.jet.compiler.runner.KotlinModuleDescriptionBuilder.DependencyProvider; import static org.jetbrains.jet.jps.build.JpsUtils.getAllDependencies; public class KotlinBuilderModuleScriptGenerator { - public static final KotlinModuleDescriptionGenerator GENERATOR = KotlinModuleXmlGenerator.INSTANCE; + public static final KotlinModuleDescriptionBuilderFactory FACTORY = KotlinModuleXmlBuilderFactory.INSTANCE; - public static File generateModuleDescription(CompileContext context, ModuleBuildTarget target, List sourceFiles) + @Nullable + public static File generateModuleDescription(CompileContext context, ModuleChunk chunk) throws IOException { + KotlinModuleDescriptionBuilder builder = FACTORY.create(); + + boolean noSources = true; + + for (ModuleBuildTarget target : chunk.getTargets()) { + File outputDir = getOutputDir(target); + + List sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(target); + noSources &= sourceFiles.isEmpty(); + + builder.addModule( + target.getId(), + outputDir.getAbsolutePath(), + getKotlinModuleDependencies(context, target), + sourceFiles, + target.isTests(), + // this excludes the output directory from the class path, to be removed for true incremental compilation + Collections.singleton(outputDir) + ); + } + + if (noSources) return null; + + File scriptFile = new File(getOutputDir(chunk.representativeTarget()), "script." + FACTORY.getFileExtension()); + + writeScriptToFile(context, builder.asText(), scriptFile); + + return scriptFile; + } + + @NotNull + private static File getOutputDir(@NotNull ModuleBuildTarget target) { File outputDir = target.getOutputDir(); if (outputDir == null) { throw new IllegalStateException("No output directory found for " + target); - } - CharSequence moduleScriptText = GENERATOR.generateModuleScript( - target.getId(), - outputDir.getAbsolutePath(), - getKotlinModuleDependencies(context, target), - sourceFiles, - target.isTests(), - // this excludes the output directory from the class path, to be removed for true incremental compilation - Collections.singleton(outputDir) - ); - - File scriptFile = new File(outputDir, "script." + GENERATOR.getFileExtension()); - - writeScriptToFile(context, moduleScriptText, scriptFile); - - return scriptFile; + return outputDir; } private static DependencyProvider getKotlinModuleDependencies(final CompileContext context, final ModuleBuildTarget target) { return new DependencyProvider() { @Override - public void processClassPath(@NotNull KotlinModuleDescriptionGenerator.DependencyProcessor processor) { + public void processClassPath(@NotNull DependencyProcessor processor) { processor.processClassPathSection("Classpath", findClassPathRoots(target)); processor.processClassPathSection("Java Source Roots", findSourceRoots(context, target)); processor.processAnnotationRoots(findAnnotationRoots(target)); diff --git a/jps-plugin/test/org/jetbrains/jet/jps/build/KotlinJpsBuildTestCase.java b/jps-plugin/test/org/jetbrains/jet/jps/build/KotlinJpsBuildTestCase.java index 599312d91c1..1a7320ee144 100644 --- a/jps-plugin/test/org/jetbrains/jet/jps/build/KotlinJpsBuildTestCase.java +++ b/jps-plugin/test/org/jetbrains/jet/jps/build/KotlinJpsBuildTestCase.java @@ -19,8 +19,11 @@ package org.jetbrains.jet.jps.build; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.jps.model.java.*; +import org.jetbrains.jps.builders.BuildResult; +import org.jetbrains.jps.model.java.JpsJavaDependencyScope; +import org.jetbrains.jps.model.java.JpsJavaExtensionService; import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.util.JpsPathUtil; import java.io.File; @@ -89,6 +92,22 @@ public class KotlinJpsBuildTestCase extends AbstractKotlinJpsBuildTestCase { doTest(); } + public void testCircularDependenciesWithKotlinFilesDifferentPackages() { + initProject(); + BuildResult result = makeAll(); + + // Check that outputs are located properly + for (JpsModule module : myProject.getModules()) { + if (module.getName().equals("module2")) { + assertFileExistsInOutput(module, "kt1/Kt1Package.class"); + } + if (module.getName().equals("kotlinProject")) { + assertFileExistsInOutput(module, "kt2/Kt2Package.class"); + } + } + result.assertSuccessful(); + } + public void testTestDependencyLibrary() throws Throwable { initProject(); addKotlinRuntimeDependency(JpsJavaDependencyScope.TEST, myProject.getModules(), false); @@ -109,4 +128,24 @@ public class KotlinJpsBuildTestCase extends AbstractKotlinJpsBuildTestCase { makeAll().assertSuccessful(); } + private static void assertFileExistsInOutput(JpsModule module, String relativePath) { + String outputUrl = JpsJavaExtensionService.getInstance().getOutputUrl(module, false); + assertNotNull(outputUrl); + File outputDir = new File(JpsPathUtil.urlToPath(outputUrl)); + File outputFile = new File(outputDir, relativePath); + assertTrue("Output not written: " + outputFile.getAbsolutePath() + "\n Directory contents: \n" + dirContents(outputFile.getParentFile()), + outputFile.exists()); + } + + private static String dirContents(File dir) { + File[] files = dir.listFiles(); + if (files == null) { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (File file : files) { + builder.append(" * ").append(file.getName()).append("\n"); + } + return builder.toString(); + } } diff --git a/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/kotlinProject.iml b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/kotlinProject.iml new file mode 100644 index 00000000000..5559c7ad919 --- /dev/null +++ b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/kotlinProject.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/kotlinProject.ipr b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/kotlinProject.ipr new file mode 100644 index 00000000000..03619a4f72c --- /dev/null +++ b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/kotlinProject.ipr @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/module2.iml b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/module2.iml new file mode 100644 index 00000000000..eace69878a2 --- /dev/null +++ b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/module2.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/src/JSecond.java b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/src/JSecond.java new file mode 100644 index 00000000000..e034bf38792 --- /dev/null +++ b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/src/JSecond.java @@ -0,0 +1,5 @@ +public class JSecond { + public static void main(String[] args) { + new JFirst().foo(); + } +} diff --git a/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/src/kt1.kt b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/src/kt1.kt new file mode 100644 index 00000000000..90c4941858e --- /dev/null +++ b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/module2/src/kt1.kt @@ -0,0 +1,7 @@ +package kt1 + +fun foo() {} + +fun bar() { + kt2.bar() +} \ No newline at end of file diff --git a/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/src/JFirst.java b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/src/JFirst.java new file mode 100644 index 00000000000..12140d2a7e4 --- /dev/null +++ b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/src/JFirst.java @@ -0,0 +1,11 @@ +public class JFirst { + JSecond s = null; + + public void foo() { + + } + + public static void main(String[] args) { + System.out.println(1); + } +} \ No newline at end of file diff --git a/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/src/kt2.kt b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/src/kt2.kt new file mode 100644 index 00000000000..96dfcda26c4 --- /dev/null +++ b/jps-plugin/testData/CircularDependenciesWithKotlinFilesDifferentPackages/src/kt2.kt @@ -0,0 +1,7 @@ +package kt2 + +fun foo() { + kt1.foo() +} + +fun bar() {} \ No newline at end of file