001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.cli.jvm.compiler; 018 019 import com.google.common.base.Function; 020 import com.google.common.base.Joiner; 021 import com.google.common.collect.Collections2; 022 import com.google.common.collect.Lists; 023 import com.google.common.collect.Maps; 024 import com.intellij.util.ArrayUtil; 025 import kotlin.Unit; 026 import kotlin.jvm.functions.Function0; 027 import kotlin.jvm.functions.Function1; 028 import org.jetbrains.annotations.NotNull; 029 import org.jetbrains.annotations.Nullable; 030 import org.jetbrains.kotlin.analyzer.AnalysisResult; 031 import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics; 032 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys; 033 import org.jetbrains.kotlin.cli.common.CompilerPlugin; 034 import org.jetbrains.kotlin.cli.common.CompilerPluginContext; 035 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; 036 import org.jetbrains.kotlin.cli.common.messages.MessageCollector; 037 import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt; 038 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; 039 import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys; 040 import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt; 041 import org.jetbrains.kotlin.cli.jvm.config.ModuleNameKt; 042 import org.jetbrains.kotlin.codegen.*; 043 import org.jetbrains.kotlin.codegen.state.GenerationState; 044 import org.jetbrains.kotlin.config.CompilerConfiguration; 045 import org.jetbrains.kotlin.config.ContentRootsKt; 046 import org.jetbrains.kotlin.context.ModuleContext; 047 import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil; 048 import org.jetbrains.kotlin.idea.MainFunctionDetector; 049 import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager; 050 import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache; 051 import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents; 052 import org.jetbrains.kotlin.modules.Module; 053 import org.jetbrains.kotlin.modules.TargetId; 054 import org.jetbrains.kotlin.modules.TargetIdKt; 055 import org.jetbrains.kotlin.name.FqName; 056 import org.jetbrains.kotlin.parsing.JetScriptDefinition; 057 import org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider; 058 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; 059 import org.jetbrains.kotlin.psi.KtFile; 060 import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter; 061 import org.jetbrains.kotlin.resolve.BindingTrace; 062 import org.jetbrains.kotlin.resolve.BindingTraceContext; 063 import org.jetbrains.kotlin.resolve.ScriptNameUtil; 064 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 065 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 066 import org.jetbrains.kotlin.util.PerformanceCounter; 067 import org.jetbrains.kotlin.utils.KotlinPaths; 068 069 import java.io.File; 070 import java.net.URL; 071 import java.net.URLClassLoader; 072 import java.util.*; 073 import java.util.concurrent.TimeUnit; 074 075 public class KotlinToJVMBytecodeCompiler { 076 077 private KotlinToJVMBytecodeCompiler() { 078 } 079 080 @NotNull 081 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) { 082 List<String> result = Lists.newArrayList(); 083 084 for (String sourceFile : module.getSourceFiles()) { 085 File source = new File(sourceFile); 086 if (!source.isAbsolute()) { 087 source = new File(directory, sourceFile); 088 } 089 result.add(source.getAbsolutePath()); 090 } 091 return result; 092 } 093 094 private static void writeOutput( 095 @NotNull CompilerConfiguration configuration, 096 @NotNull ClassFileFactory outputFiles, 097 @Nullable File outputDir, 098 @Nullable File jarPath, 099 boolean jarRuntime, 100 @Nullable FqName mainClass 101 ) { 102 if (jarPath != null) { 103 CompileEnvironmentUtil.writeToJar(jarPath, jarRuntime, mainClass, outputFiles); 104 } 105 else { 106 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 107 OutputUtilsKt.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector); 108 } 109 } 110 111 public static boolean compileModules( 112 @NotNull KotlinCoreEnvironment environment, 113 @NotNull CompilerConfiguration configuration, 114 @NotNull List<Module> chunk, 115 @NotNull File directory, 116 @Nullable File jarPath, 117 boolean jarRuntime 118 ) { 119 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap(); 120 121 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 122 123 for (Module module: chunk) { 124 ModuleVisibilityManager.SERVICE.getInstance(environment.getProject()).addModule(module); 125 } 126 127 String targetDescription = "in targets [" + Joiner.on(", ").join(Collections2.transform(chunk, new Function<Module, String>() { 128 @Override 129 public String apply(@Nullable Module input) { 130 return input != null ? input.getModuleName() + "-" + input.getModuleType() : "<null>"; 131 } 132 })) + "] "; 133 AnalysisResult result = analyze(environment, targetDescription); 134 if (result == null) { 135 return false; 136 } 137 138 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 139 140 result.throwIfError(); 141 142 for (Module module : chunk) { 143 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 144 List<KtFile> jetFiles = CompileEnvironmentUtil.getJetFiles( 145 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() { 146 @Override 147 public Unit invoke(String s) { 148 throw new IllegalStateException("Should have been checked before: " + s); 149 } 150 } 151 ); 152 File moduleOutputDirectory = new File(module.getOutputDirectory()); 153 GenerationState generationState = 154 generate(environment, result, jetFiles, module, moduleOutputDirectory, 155 module.getModuleName()); 156 outputFiles.put(module, generationState.getFactory()); 157 } 158 159 for (Module module : chunk) { 160 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 161 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null); 162 } 163 return true; 164 } 165 166 @NotNull 167 public static CompilerConfiguration createCompilerConfiguration( 168 @NotNull CompilerConfiguration base, 169 @NotNull List<Module> chunk, 170 @NotNull File directory 171 ) { 172 CompilerConfiguration configuration = base.copy(); 173 174 for (Module module : chunk) { 175 ContentRootsKt.addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module)); 176 } 177 178 for (Module module : chunk) { 179 for (String javaSourceRoot : module.getJavaSourceRoots()) { 180 JvmContentRootsKt.addJavaSourceRoot(configuration, new File(javaSourceRoot)); 181 } 182 } 183 184 for (Module module : chunk) { 185 for (String classpathRoot : module.getClasspathRoots()) { 186 JvmContentRootsKt.addJvmClasspathRoot(configuration, new File(classpathRoot)); 187 } 188 } 189 190 for (Module module : chunk) { 191 configuration.add(JVMConfigurationKeys.MODULES, module); 192 } 193 194 return configuration; 195 } 196 197 @Nullable 198 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<KtFile> files) { 199 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 200 FqName mainClass = null; 201 for (KtFile file : files) { 202 if (mainFunctionDetector.hasMain(file.getDeclarations())) { 203 if (mainClass != null) { 204 // more than one main 205 return null; 206 } 207 FqName fqName = file.getPackageFqName(); 208 mainClass = JvmFileClassUtil.getFileClassInfoNoResolve(file).getFacadeClassFqName(); 209 } 210 } 211 return mainClass; 212 } 213 214 public static boolean compileBunchOfSources( 215 @NotNull KotlinCoreEnvironment environment, 216 @Nullable File jar, 217 @Nullable File outputDir, 218 boolean includeRuntime 219 ) { 220 221 GenerationState generationState = analyzeAndGenerate(environment); 222 if (generationState == null) { 223 return false; 224 } 225 226 FqName mainClass = findMainClass(generationState, environment.getSourceFiles()); 227 228 try { 229 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass); 230 return true; 231 } 232 finally { 233 generationState.destroy(); 234 } 235 } 236 237 public static void compileAndExecuteScript( 238 @NotNull CompilerConfiguration configuration, 239 @NotNull KotlinPaths paths, 240 @NotNull KotlinCoreEnvironment environment, 241 @NotNull List<String> scriptArgs 242 ) { 243 Class<?> scriptClass = compileScript(configuration, paths, environment); 244 if (scriptClass == null) return; 245 246 try { 247 scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)}); 248 } 249 catch (RuntimeException e) { 250 throw e; 251 } 252 catch (Exception e) { 253 throw new RuntimeException("Failed to evaluate script: " + e, e); 254 } 255 } 256 257 @Nullable 258 public static Class<?> compileScript( 259 @NotNull CompilerConfiguration configuration, 260 @NotNull KotlinPaths paths, 261 @NotNull KotlinCoreEnvironment environment 262 ) { 263 List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS); 264 if (!scriptParameters.isEmpty()) { 265 JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition( 266 new JetScriptDefinition(".kts", scriptParameters) 267 ); 268 } 269 GenerationState state = analyzeAndGenerate(environment); 270 if (state == null) { 271 return null; 272 } 273 274 GeneratedClassLoader classLoader; 275 try { 276 List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL()); 277 for (File file : JvmContentRootsKt.getJvmClasspathRoots(configuration)) { 278 classPaths.add(file.toURI().toURL()); 279 } 280 //noinspection UnnecessaryFullyQualifiedName 281 classLoader = new GeneratedClassLoader(state.getFactory(), 282 new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]), null) 283 ); 284 285 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript()); 286 return classLoader.loadClass(nameForScript.asString()); 287 } 288 catch (Exception e) { 289 throw new RuntimeException("Failed to evaluate script: " + e, e); 290 } 291 } 292 293 @Nullable 294 public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) { 295 AnalysisResult result = analyze(environment, null); 296 297 if (result == null) { 298 return null; 299 } 300 301 if (!result.getShouldGenerateCode()) return null; 302 303 result.throwIfError(); 304 305 return generate(environment, result, environment.getSourceFiles(), null, null, null); 306 } 307 308 @Nullable 309 private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment, @Nullable String targetDescription) { 310 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 311 assert collector != null; 312 313 long analysisStart = PerformanceCounter.Companion.currentTime(); 314 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector); 315 analyzerWithCompilerReport.analyzeAndReport( 316 environment.getSourceFiles(), new Function0<AnalysisResult>() { 317 @NotNull 318 @Override 319 public AnalysisResult invoke() { 320 BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 321 ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject(), 322 ModuleNameKt 323 .getModuleName(environment)); 324 325 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext( 326 moduleContext, 327 environment.getSourceFiles(), 328 sharedTrace, 329 environment.getConfiguration().get(JVMConfigurationKeys.MODULES), 330 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS), 331 new JvmPackagePartProvider(environment) 332 ); 333 } 334 } 335 ); 336 long analysisNanos = PerformanceCounter.Companion.currentTime() - analysisStart; 337 String message = "ANALYZE: " + environment.getSourceFiles().size() + " files (" + 338 environment.getSourceLinesOfCode() + " lines) " + 339 (targetDescription != null ? targetDescription : "") + 340 "in " + TimeUnit.NANOSECONDS.toMillis(analysisNanos) + " ms"; 341 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message); 342 343 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult(); 344 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles(); 345 346 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(), 347 environment.getSourceFiles()); 348 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 349 plugin.processFiles(context); 350 } 351 352 return analyzerWithCompilerReport.hasErrors() ? null : result; 353 } 354 355 @NotNull 356 private static GenerationState generate( 357 @NotNull KotlinCoreEnvironment environment, 358 @NotNull AnalysisResult result, 359 @NotNull List<KtFile> sourceFiles, 360 @Nullable Module module, 361 File outputDirectory, 362 String moduleName 363 ) { 364 CompilerConfiguration configuration = environment.getConfiguration(); 365 IncrementalCompilationComponents incrementalCompilationComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS); 366 367 Collection<FqName> packagesWithObsoleteParts; 368 List<FqName> obsoleteMultifileClasses; 369 TargetId targetId = null; 370 371 if (module == null || incrementalCompilationComponents == null) { 372 packagesWithObsoleteParts = Collections.emptySet(); 373 obsoleteMultifileClasses = Collections.emptyList(); 374 } 375 else { 376 targetId = TargetIdKt.TargetId(module); 377 IncrementalCache incrementalCache = incrementalCompilationComponents.getIncrementalCache(targetId); 378 379 packagesWithObsoleteParts = new HashSet<FqName>(); 380 for (String internalName : incrementalCache.getObsoletePackageParts()) { 381 packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName()); 382 } 383 384 obsoleteMultifileClasses = new ArrayList<FqName>(); 385 for (String obsoleteFacadeInternalName : incrementalCache.getObsoleteMultifileClasses()) { 386 obsoleteMultifileClasses.add(JvmClassName.byInternalName(obsoleteFacadeInternalName).getFqNameForClassNameWithoutDollars()); 387 } 388 } 389 BindingTraceContext diagnosticHolder = new BindingTraceContext(); 390 GenerationState generationState = new GenerationState( 391 environment.getProject(), 392 ClassBuilderFactories.BINARIES, 393 result.getModuleDescriptor(), 394 result.getBindingContext(), 395 sourceFiles, 396 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false), 397 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false), 398 GenerationState.GenerateClassFilter.GENERATE_ALL, 399 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false), 400 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false), 401 /* useTypeTableInSerializer = */ false, 402 diagnosticHolder, 403 packagesWithObsoleteParts, 404 obsoleteMultifileClasses, 405 targetId, 406 moduleName, 407 outputDirectory, 408 incrementalCompilationComponents 409 ); 410 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 411 412 long generationStart = PerformanceCounter.Companion.currentTime(); 413 414 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 415 416 long generationNanos = PerformanceCounter.Companion.currentTime() - generationStart; 417 String desc = module != null ? "target " + module.getModuleName() + "-" + module.getModuleType() + " " : ""; 418 String message = "GENERATE: " + sourceFiles.size() + " files (" + 419 environment.countLinesOfCode(sourceFiles) + " lines) " + desc + "in " + TimeUnit.NANOSECONDS.toMillis(generationNanos) + " ms"; 420 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message); 421 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 422 423 AnalyzerWithCompilerReport.reportDiagnostics( 424 new FilteredJvmDiagnostics( 425 diagnosticHolder.getBindingContext().getDiagnostics(), 426 result.getBindingContext().getDiagnostics() 427 ), 428 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 429 ); 430 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 431 return generationState; 432 } 433 }