From 8db5f6741cc2e0da6f911152288df697c0e827e3 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Fri, 9 Nov 2012 16:00:22 +0400 Subject: [PATCH] Initial implementation for external build #KT-2751 Fixed #KT-3017 Fixed #KT-3021 Fixed Original commit: 9592bfd62fa6e346c965ebc4a923d8c934de9bd2 --- jps/jps-plugin/jps-plugin.iml | 2 + .../jet/jps/build/KotlinBuilder.java | 143 +++++++++++++++--- .../KotlinBuilderModuleScriptGenerator.java | 133 ++++++++++++++++ .../jps/build/KotlinSourceFileCollector.java | 82 ++++++++++ 4 files changed, 338 insertions(+), 22 deletions(-) create mode 100644 jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java create mode 100644 jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java diff --git a/jps/jps-plugin/jps-plugin.iml b/jps/jps-plugin/jps-plugin.iml index 8aa91671dfd..e58e9906a3c 100644 --- a/jps/jps-plugin/jps-plugin.iml +++ b/jps/jps-plugin/jps-plugin.iml @@ -8,6 +8,8 @@ + + diff --git a/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java b/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java index 86113e5e85f..fde3fcdbcdc 100644 --- a/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java +++ b/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilder.java @@ -16,50 +16,149 @@ package org.jetbrains.jet.jps.build; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation; +import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity; +import org.jetbrains.jet.cli.common.messages.MessageCollector; +import org.jetbrains.jet.compiler.runner.*; import org.jetbrains.jps.ModuleChunk; +import org.jetbrains.jps.builders.ChunkBuildOutputConsumer; import org.jetbrains.jps.builders.DirtyFilesHolder; -import org.jetbrains.jps.builders.FileProcessor; import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor; import org.jetbrains.jps.incremental.*; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; import java.io.File; import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERROR; public class KotlinBuilder extends ModuleLevelBuilder { + + private static final String KOTLIN_COMPILER_NAME = "Kotlin"; + private static final String KOTLIN_BUILDER_NAME = "Kotlin Builder"; + protected KotlinBuilder() { super(BuilderCategory.SOURCE_PROCESSOR); } + @NotNull + @Override + public String getPresentableName() { + return KOTLIN_BUILDER_NAME; + } + @Override public ExitCode build( - CompileContext context, ModuleChunk chunk, DirtyFilesHolder dirtyFilesHolder - ) throws ProjectBuildException { - try { - dirtyFilesHolder.processDirtyFiles(new FileProcessor() { - @Override - public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor root) throws IOException { - if (file.getName().endsWith(".kt") || file.getName().endsWith(".java")) { + CompileContext context, + ModuleChunk chunk, + DirtyFilesHolder dirtyFilesHolder, + ChunkBuildOutputConsumer outputConsumer + ) throws ProjectBuildException, IOException { - } - return true; - } - }); + MessageCollector messageCollector = new MessageCollectorAdapter(context); + + if (chunk.getModules().size() > 1) { + messageCollector.report( + ERROR, "Circular dependencies are not supported: " + chunk.getModules(), + CompilerMessageLocation.NO_LOCATION); + return ExitCode.ABORT; } - catch (IOException e) { - throw new ProjectBuildException(e); + + ModuleBuildTarget representativeTarget = chunk.representativeTarget(); + + // For non-incremental build: take all sources + List sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(representativeTarget); + //List sourceFiles = getDirtySourceFiles(dirtyFilesHolder); + + if (sourceFiles.isEmpty()) { + return ExitCode.NOTHING_DONE; } - //context.processMessage(new CompilerMessage()); - //JpsJavaExtensionService.dependencies().recursively().productionOnly().classes().getRoots() + + File scriptFile = KotlinBuilderModuleScriptGenerator.generateModuleScript(context, representativeTarget, sourceFiles); + + File outputDir = representativeTarget.getOutputDir(); + + CompilerEnvironment environment = CompilerEnvironment.getEnvironmentFor(outputDir); + if (!environment.success()) { + environment.reportErrorsTo(messageCollector); + return ExitCode.ABORT; + } + + assert outputDir != null : "CompilerEnvironment must have checked for outputDir to be not null, but it didn't"; + + OutputItemsCollectorImpl outputItemCollector = new OutputItemsCollectorImpl(outputDir); + + KotlinCompilerRunner.runCompiler( + messageCollector, + environment, + scriptFile, + outputItemCollector, + /*runOutOfProcess = */false); + + for (SimpleOutputItem outputItem : outputItemCollector.getOutputs()) { + outputConsumer.registerOutputFile( + representativeTarget, + outputItem.getOutputFile().getPath(), + paths(outputItem.getSourceFiles())); + } + return ExitCode.OK; } - @Override - public String getName() { - return "KotlinBuilder"; + private static Collection paths(Collection files) { + Collection result = ContainerUtil.newArrayList(); + for (File file : files) { + result.add(file.getPath()); + } + return result; } - @Override - public String getDescription() { - return "KotlinBuilder"; + public static class MessageCollectorAdapter implements MessageCollector { + + private final CompileContext context; + + public MessageCollectorAdapter(@NotNull CompileContext context) { + this.context = context; + } + + @Override + public void report( + @NotNull CompilerMessageSeverity severity, + @NotNull String message, + @NotNull CompilerMessageLocation location + ) { + context.processMessage(new CompilerMessage( + KOTLIN_COMPILER_NAME, + kind(severity), + message, + location.getPath(), + -1, -1, -1, + location.getLine(), + location.getColumn() + )); + } + + @NotNull + private static BuildMessage.Kind kind(@NotNull CompilerMessageSeverity severity) { + switch (severity) { + case INFO: + return BuildMessage.Kind.INFO; + case ERROR: + case EXCEPTION: + return BuildMessage.Kind.ERROR; + case WARNING: + return BuildMessage.Kind.WARNING; + case LOGGING: + return BuildMessage.Kind.PROGRESS; + default: + throw new IllegalArgumentException("Unsupported severity: " + severity); + } + } + } } diff --git a/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java b/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java new file mode 100644 index 00000000000..860ea5ddf8a --- /dev/null +++ b/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinBuilderModuleScriptGenerator.java @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2012 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.jps.build; + +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.KotlinModuleScriptGenerator; +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.JpsAnnotationRootType; +import org.jetbrains.jps.model.java.JpsJavaClasspathKind; +import org.jetbrains.jps.model.java.JpsJavaDependenciesEnumerator; +import org.jetbrains.jps.model.java.JpsJavaExtensionService; +import org.jetbrains.jps.model.library.JpsLibrary; +import org.jetbrains.jps.model.library.JpsLibraryRoot; +import org.jetbrains.jps.model.module.JpsDependencyElement; +import org.jetbrains.jps.model.module.JpsLibraryDependency; +import org.jetbrains.jps.model.module.JpsModule; +import org.jetbrains.jps.model.module.JpsSdkDependency; +import org.jetbrains.jps.util.JpsPathUtil; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static org.jetbrains.jet.compiler.runner.KotlinModuleScriptGenerator.DependencyProvider; + +public class KotlinBuilderModuleScriptGenerator { + public static File generateModuleScript(CompileContext context, ModuleBuildTarget target, List sourceFiles) + throws IOException + { + CharSequence moduleScriptText = KotlinModuleScriptGenerator.generateModuleScript( + target.getModuleName(), + getKotlinModuleDependencies(target), + sourceFiles, + target.isTests(), + // this excludes the output directory from the class path, to be removed for true incremental compilation + Collections.singleton(target.getOutputDir()) + ); + + File scriptFile = new File(target.getOutputDir(), "script.kts"); + + writeScriptToFile(context, moduleScriptText, scriptFile); + + return scriptFile; + } + + private static DependencyProvider getKotlinModuleDependencies(final ModuleBuildTarget target) { + return new DependencyProvider() { + @Override + public void processClassPath(@NotNull KotlinModuleScriptGenerator.DependencyProcessor processor) { + processor.processClassPathSection("All", findClassPathRoots(target)); + processor.processAnnotationRoots(findAnnotationRoots(target)); + } + }; + } + + private static void writeScriptToFile(CompileContext context, CharSequence moduleScriptText, File scriptFile) throws IOException { + FileUtil.writeToFile(scriptFile, moduleScriptText.toString()); + context.processMessage(new CompilerMessage( + "Kotlin", + BuildMessage.Kind.INFO, + "Created script file: " + scriptFile + )); + } + + @NotNull + private static Collection findClassPathRoots(@NotNull ModuleBuildTarget target) { + JpsModule module = target.getModule(); + JpsJavaDependenciesEnumerator dependencies = JpsJavaExtensionService.dependencies(module) + .includedIn(JpsJavaClasspathKind.compile(target.isTests())); + + return dependencies.classes().getRoots(); + } + + @NotNull + private static List findAnnotationRoots(@NotNull ModuleBuildTarget target) { + JpsModule module = target.getModule(); + List dependencies = module.getDependenciesList().getDependencies(); + + List annotationRootFiles = ContainerUtil.newArrayList(); + for (JpsDependencyElement dependencyElement : dependencies) { + JpsLibrary library = getLibrary(dependencyElement); + if (library == null) continue; + + List annotationRoots = library.getRoots(JpsAnnotationRootType.INSTANCE); + for (JpsLibraryRoot root : annotationRoots) { + File file = new File(JpsPathUtil.urlToPath(root.getUrl())); + + annotationRootFiles.add(file); + } + } + + return annotationRootFiles; + } + + @Nullable + private static JpsLibrary getLibrary(@NotNull JpsDependencyElement dependencyElement) { + if (dependencyElement instanceof JpsSdkDependency) { + JpsSdkDependency sdkDependency = (JpsSdkDependency) dependencyElement; + return sdkDependency.resolveSdk(); + } + + if (dependencyElement instanceof JpsLibraryDependency) { + JpsLibraryDependency libraryDependency = (JpsLibraryDependency) dependencyElement; + return libraryDependency.getLibrary(); + } + + return null; + } + + private KotlinBuilderModuleScriptGenerator() {} +} diff --git a/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java b/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java new file mode 100644 index 00000000000..d92306714e1 --- /dev/null +++ b/jps/jps-plugin/src/org/jetbrains/jet/jps/build/KotlinSourceFileCollector.java @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2012 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.jps.build; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.builders.DirtyFilesHolder; +import org.jetbrains.jps.builders.FileProcessor; +import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor; +import org.jetbrains.jps.incremental.ModuleBuildTarget; +import org.jetbrains.jps.model.java.JavaSourceRootType; +import org.jetbrains.jps.model.module.JpsModuleSourceRoot; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class KotlinSourceFileCollector { + // For incremental compilation + public static List getDirtySourceFiles(DirtyFilesHolder dirtyFilesHolder) + throws IOException + { + final List sourceFiles = ContainerUtil.newArrayList(); + + dirtyFilesHolder.processDirtyFiles(new FileProcessor() { + @Override + public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor root) throws IOException { + if (isKotlinSourceFile(file)) { + sourceFiles.add(file); + } + return true; + } + }); + return sourceFiles; + } + + @NotNull + public static List getAllKotlinSourceFiles(@NotNull ModuleBuildTarget target) { + final List result = ContainerUtil.newArrayList(); + for (JpsModuleSourceRoot sourceRoot : getRelevantSourceRoots(target)) { + FileUtil.processFilesRecursively(sourceRoot.getFile(), new Processor() { + @Override + public boolean process(File file) { + if (file.isFile() && isKotlinSourceFile(file)) { + result.add(file); + } + return true; + } + }); + } + return result; + } + + private static Iterable getRelevantSourceRoots(ModuleBuildTarget target) { + JavaSourceRootType sourceRootType = target.isTests() ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE; + + //noinspection unchecked + return (Iterable) target.getModule().getSourceRoots(sourceRootType); + } + + private static boolean isKotlinSourceFile(File file) { + return file.getPath().endsWith(".kt"); + } + + private KotlinSourceFileCollector() {} +}