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.JavaRootPath; 053 import org.jetbrains.kotlin.modules.Module; 054 import org.jetbrains.kotlin.modules.TargetId; 055 import org.jetbrains.kotlin.modules.TargetIdKt; 056 import org.jetbrains.kotlin.name.FqName; 057 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; 058 import org.jetbrains.kotlin.psi.KtFile; 059 import org.jetbrains.kotlin.psi.KtScript; 060 import org.jetbrains.kotlin.resolve.BindingTrace; 061 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 062 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 063 import org.jetbrains.kotlin.util.PerformanceCounter; 064 import org.jetbrains.kotlin.utils.ExceptionUtilsKt; 065 import org.jetbrains.kotlin.utils.KotlinPaths; 066 067 import java.io.File; 068 import java.io.PrintStream; 069 import java.lang.reflect.Constructor; 070 import java.lang.reflect.InvocationTargetException; 071 import java.net.URL; 072 import java.net.URLClassLoader; 073 import java.util.*; 074 import java.util.concurrent.TimeUnit; 075 076 public class KotlinToJVMBytecodeCompiler { 077 078 private KotlinToJVMBytecodeCompiler() { 079 } 080 081 @NotNull 082 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) { 083 List<String> result = Lists.newArrayList(); 084 085 for (String sourceFile : module.getSourceFiles()) { 086 File source = new File(sourceFile); 087 if (!source.isAbsolute()) { 088 source = new File(directory, sourceFile); 089 } 090 result.add(source.getAbsolutePath()); 091 } 092 return result; 093 } 094 095 private static void writeOutput( 096 @NotNull CompilerConfiguration configuration, 097 @NotNull ClassFileFactory outputFiles, 098 @Nullable File outputDir, 099 @Nullable File jarPath, 100 boolean jarRuntime, 101 @Nullable FqName mainClass 102 ) { 103 if (jarPath != null) { 104 CompileEnvironmentUtil.writeToJar(jarPath, jarRuntime, mainClass, outputFiles); 105 } 106 else { 107 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 108 OutputUtilsKt.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector); 109 } 110 } 111 112 public static boolean compileModules( 113 @NotNull KotlinCoreEnvironment environment, 114 @NotNull CompilerConfiguration configuration, 115 @NotNull List<Module> chunk, 116 @NotNull File directory, 117 @Nullable File jarPath, 118 @NotNull List<String> friendPaths, 119 boolean jarRuntime 120 ) { 121 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap(); 122 123 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 124 125 ModuleVisibilityManager moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.getProject()); 126 127 for (Module module: chunk) { 128 moduleVisibilityManager.addModule(module); 129 } 130 131 for (String path : friendPaths) { 132 moduleVisibilityManager.addFriendPath(path); 133 } 134 135 String targetDescription = "in targets [" + Joiner.on(", ").join(Collections2.transform(chunk, new Function<Module, String>() { 136 @Override 137 public String apply(@Nullable Module input) { 138 return input != null ? input.getModuleName() + "-" + input.getModuleType() : "<null>"; 139 } 140 })) + "] "; 141 AnalysisResult result = analyze(environment, targetDescription); 142 if (result == null) { 143 return false; 144 } 145 146 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 147 148 result.throwIfError(); 149 150 for (Module module : chunk) { 151 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 152 List<KtFile> jetFiles = CompileEnvironmentUtil.getKtFiles( 153 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() { 154 @Override 155 public Unit invoke(String s) { 156 throw new IllegalStateException("Should have been checked before: " + s); 157 } 158 } 159 ); 160 File moduleOutputDirectory = new File(module.getOutputDirectory()); 161 GenerationState generationState = 162 generate(environment, result, jetFiles, module, moduleOutputDirectory, 163 module.getModuleName()); 164 outputFiles.put(module, generationState.getFactory()); 165 } 166 167 for (Module module : chunk) { 168 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 169 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null); 170 } 171 return true; 172 } 173 174 @NotNull 175 public static CompilerConfiguration createCompilerConfiguration( 176 @NotNull CompilerConfiguration base, 177 @NotNull List<Module> chunk, 178 @NotNull File directory 179 ) { 180 CompilerConfiguration configuration = base.copy(); 181 182 for (Module module : chunk) { 183 ContentRootsKt.addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module)); 184 } 185 186 for (Module module : chunk) { 187 for (JavaRootPath javaRootPath : module.getJavaSourceRoots()) { 188 JvmContentRootsKt.addJavaSourceRoot(configuration, new File(javaRootPath.getPath()), javaRootPath.getPackagePrefix()); 189 } 190 } 191 192 for (Module module : chunk) { 193 for (String classpathRoot : module.getClasspathRoots()) { 194 JvmContentRootsKt.addJvmClasspathRoot(configuration, new File(classpathRoot)); 195 } 196 } 197 198 for (Module module : chunk) { 199 configuration.add(JVMConfigurationKeys.MODULES, module); 200 } 201 202 return configuration; 203 } 204 205 @Nullable 206 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<KtFile> files) { 207 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 208 FqName mainClass = null; 209 for (KtFile file : files) { 210 if (mainFunctionDetector.hasMain(file.getDeclarations())) { 211 if (mainClass != null) { 212 // more than one main 213 return null; 214 } 215 FqName fqName = file.getPackageFqName(); 216 mainClass = JvmFileClassUtil.getFileClassInfoNoResolve(file).getFacadeClassFqName(); 217 } 218 } 219 return mainClass; 220 } 221 222 public static boolean compileBunchOfSources( 223 @NotNull KotlinCoreEnvironment environment, 224 @Nullable File jar, 225 @Nullable File outputDir, 226 @NotNull List<String> friendPaths, 227 boolean includeRuntime 228 ) { 229 230 ModuleVisibilityManager moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.getProject()); 231 232 for (String path : friendPaths) { 233 moduleVisibilityManager.addFriendPath(path); 234 } 235 236 GenerationState generationState = analyzeAndGenerate(environment); 237 if (generationState == null) { 238 return false; 239 } 240 241 FqName mainClass = findMainClass(generationState, environment.getSourceFiles()); 242 243 try { 244 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass); 245 return true; 246 } 247 finally { 248 generationState.destroy(); 249 } 250 } 251 252 public static void compileAndExecuteScript( 253 @NotNull CompilerConfiguration configuration, 254 @NotNull KotlinPaths paths, 255 @NotNull KotlinCoreEnvironment environment, 256 @NotNull List<String> scriptArgs 257 ) { 258 Class<?> scriptClass = compileScript(configuration, paths, environment); 259 if (scriptClass == null) return; 260 Constructor<?> scriptConstructor = getScriptConstructor(scriptClass); 261 262 try { 263 scriptConstructor.newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)}); 264 } 265 catch (Throwable e) { 266 reportExceptionFromScript(e); 267 } 268 } 269 270 private static void reportExceptionFromScript(@NotNull Throwable exception) { 271 // expecting InvocationTargetException from constructor invocation with cause that describes the actual cause 272 PrintStream stream = System.err; 273 Throwable cause = exception.getCause(); 274 if (!(exception instanceof InvocationTargetException) || cause == null) { 275 exception.printStackTrace(stream); 276 return; 277 } 278 stream.println(cause); 279 StackTraceElement[] fullTrace = cause.getStackTrace(); 280 int relevantEntries = fullTrace.length - exception.getStackTrace().length; 281 for (int i = 0; i < relevantEntries; i++) { 282 stream.println("\tat " + fullTrace[i]); 283 } 284 } 285 286 @NotNull 287 private static Constructor<?> getScriptConstructor(Class<?> scriptClass) { 288 try { 289 return scriptClass.getConstructor(String[].class); 290 } 291 catch (NoSuchMethodException e) { 292 throw ExceptionUtilsKt.rethrow(e); 293 } 294 } 295 296 @Nullable 297 public static Class<?> compileScript( 298 @NotNull CompilerConfiguration configuration, 299 @NotNull KotlinPaths paths, 300 @NotNull KotlinCoreEnvironment environment 301 ) { 302 GenerationState state = analyzeAndGenerate(environment); 303 if (state == null) { 304 return null; 305 } 306 307 GeneratedClassLoader classLoader; 308 try { 309 List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL()); 310 for (File file : JvmContentRootsKt.getJvmClasspathRoots(configuration)) { 311 classPaths.add(file.toURI().toURL()); 312 } 313 //noinspection UnnecessaryFullyQualifiedName 314 classLoader = new GeneratedClassLoader(state.getFactory(), 315 new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]), null) 316 ); 317 318 KtScript script = environment.getSourceFiles().get(0).getScript(); 319 assert script != null : "Script must be parsed"; 320 FqName nameForScript = script.getFqName(); 321 return classLoader.loadClass(nameForScript.asString()); 322 } 323 catch (Exception e) { 324 throw new RuntimeException("Failed to evaluate script: " + e, e); 325 } 326 } 327 328 @Nullable 329 public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) { 330 AnalysisResult result = analyze(environment, null); 331 332 if (result == null) { 333 return null; 334 } 335 336 if (!result.getShouldGenerateCode()) return null; 337 338 result.throwIfError(); 339 340 return generate(environment, result, environment.getSourceFiles(), null, null, null); 341 } 342 343 @Nullable 344 private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment, @Nullable String targetDescription) { 345 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 346 assert collector != null; 347 348 long analysisStart = PerformanceCounter.Companion.currentTime(); 349 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector); 350 analyzerWithCompilerReport.analyzeAndReport( 351 environment.getSourceFiles(), new Function0<AnalysisResult>() { 352 @NotNull 353 @Override 354 public AnalysisResult invoke() { 355 BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 356 ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject(), 357 ModuleNameKt 358 .getModuleName(environment)); 359 360 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext( 361 moduleContext, 362 environment.getSourceFiles(), 363 sharedTrace, 364 environment.getConfiguration().get(JVMConfigurationKeys.MODULES), 365 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS), 366 new JvmPackagePartProvider(environment) 367 ); 368 } 369 } 370 ); 371 long analysisNanos = PerformanceCounter.Companion.currentTime() - analysisStart; 372 String message = "ANALYZE: " + environment.getSourceFiles().size() + " files (" + 373 environment.getSourceLinesOfCode() + " lines) " + 374 (targetDescription != null ? targetDescription : "") + 375 "in " + TimeUnit.NANOSECONDS.toMillis(analysisNanos) + " ms"; 376 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message); 377 378 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult(); 379 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles(); 380 381 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(), 382 environment.getSourceFiles()); 383 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 384 plugin.processFiles(context); 385 } 386 387 return analyzerWithCompilerReport.hasErrors() ? null : result; 388 } 389 390 @NotNull 391 private static GenerationState generate( 392 @NotNull KotlinCoreEnvironment environment, 393 @NotNull AnalysisResult result, 394 @NotNull List<KtFile> sourceFiles, 395 @Nullable Module module, 396 File outputDirectory, 397 String moduleName 398 ) { 399 CompilerConfiguration configuration = environment.getConfiguration(); 400 IncrementalCompilationComponents incrementalCompilationComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS); 401 402 Collection<FqName> packagesWithObsoleteParts; 403 List<FqName> obsoleteMultifileClasses; 404 TargetId targetId = null; 405 406 if (module == null || incrementalCompilationComponents == null) { 407 packagesWithObsoleteParts = Collections.emptySet(); 408 obsoleteMultifileClasses = Collections.emptyList(); 409 } 410 else { 411 targetId = TargetIdKt.TargetId(module); 412 IncrementalCache incrementalCache = incrementalCompilationComponents.getIncrementalCache(targetId); 413 414 packagesWithObsoleteParts = new HashSet<FqName>(); 415 for (String internalName : incrementalCache.getObsoletePackageParts()) { 416 packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName()); 417 } 418 419 obsoleteMultifileClasses = new ArrayList<FqName>(); 420 for (String obsoleteFacadeInternalName : incrementalCache.getObsoleteMultifileClasses()) { 421 obsoleteMultifileClasses.add(JvmClassName.byInternalName(obsoleteFacadeInternalName).getFqNameForClassNameWithoutDollars()); 422 } 423 } 424 GenerationState generationState = new GenerationState( 425 environment.getProject(), 426 ClassBuilderFactories.BINARIES, 427 result.getModuleDescriptor(), 428 result.getBindingContext(), 429 sourceFiles, 430 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false), 431 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false), 432 GenerationState.GenerateClassFilter.GENERATE_ALL, 433 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false), 434 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false), 435 /* useTypeTableInSerializer = */ false, 436 packagesWithObsoleteParts, 437 obsoleteMultifileClasses, 438 targetId, 439 moduleName, 440 outputDirectory, 441 incrementalCompilationComponents 442 ); 443 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 444 445 long generationStart = PerformanceCounter.Companion.currentTime(); 446 447 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 448 449 long generationNanos = PerformanceCounter.Companion.currentTime() - generationStart; 450 String desc = module != null ? "target " + module.getModuleName() + "-" + module.getModuleType() + " " : ""; 451 String message = "GENERATE: " + sourceFiles.size() + " files (" + 452 environment.countLinesOfCode(sourceFiles) + " lines) " + desc + "in " + TimeUnit.NANOSECONDS.toMillis(generationNanos) + " ms"; 453 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message); 454 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 455 456 AnalyzerWithCompilerReport.reportDiagnostics( 457 new FilteredJvmDiagnostics( 458 generationState.getCollectedExtraJvmDiagnostics(), 459 result.getBindingContext().getDiagnostics() 460 ), 461 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 462 ); 463 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 464 return generationState; 465 } 466 }