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.OutputUtilsPackage; 038 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; 039 import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys; 040 import org.jetbrains.kotlin.codegen.*; 041 import org.jetbrains.kotlin.codegen.state.GenerationState; 042 import org.jetbrains.kotlin.config.CompilerConfiguration; 043 import org.jetbrains.kotlin.context.ModuleContext; 044 import org.jetbrains.kotlin.idea.MainFunctionDetector; 045 import org.jetbrains.kotlin.load.kotlin.PackageClassUtils; 046 import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache; 047 import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents; 048 import org.jetbrains.kotlin.modules.Module; 049 import org.jetbrains.kotlin.modules.ModulesPackage; 050 import org.jetbrains.kotlin.modules.TargetId; 051 import org.jetbrains.kotlin.name.FqName; 052 import org.jetbrains.kotlin.parsing.JetScriptDefinition; 053 import org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider; 054 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; 055 import org.jetbrains.kotlin.psi.JetFile; 056 import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter; 057 import org.jetbrains.kotlin.resolve.BindingTrace; 058 import org.jetbrains.kotlin.resolve.BindingTraceContext; 059 import org.jetbrains.kotlin.resolve.ScriptNameUtil; 060 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 061 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 062 import org.jetbrains.kotlin.util.PerformanceCounter; 063 import org.jetbrains.kotlin.utils.KotlinPaths; 064 065 import java.io.File; 066 import java.net.URL; 067 import java.net.URLClassLoader; 068 import java.util.*; 069 import java.util.concurrent.TimeUnit; 070 071 import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.*; 072 import static org.jetbrains.kotlin.config.ConfigPackage.addKotlinSourceRoots; 073 074 public class KotlinToJVMBytecodeCompiler { 075 076 private KotlinToJVMBytecodeCompiler() { 077 } 078 079 @NotNull 080 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) { 081 List<String> result = Lists.newArrayList(); 082 083 for (String sourceFile : module.getSourceFiles()) { 084 File source = new File(sourceFile); 085 if (!source.isAbsolute()) { 086 source = new File(directory, sourceFile); 087 } 088 089 if (!source.exists()) { 090 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName()); 091 } 092 093 result.add(source.getAbsolutePath()); 094 } 095 return result; 096 } 097 098 private static void writeOutput( 099 @NotNull CompilerConfiguration configuration, 100 @NotNull ClassFileFactory outputFiles, 101 @Nullable File outputDir, 102 @Nullable File jarPath, 103 boolean jarRuntime, 104 @Nullable FqName mainClass 105 ) { 106 if (jarPath != null) { 107 CompileEnvironmentUtil.writeToJar(jarPath, jarRuntime, mainClass, outputFiles); 108 } 109 else { 110 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 111 OutputUtilsPackage.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector); 112 } 113 } 114 115 public static boolean compileModules( 116 @NotNull KotlinCoreEnvironment environment, 117 @NotNull CompilerConfiguration configuration, 118 @NotNull List<Module> chunk, 119 @NotNull File directory, 120 @Nullable File jarPath, 121 boolean jarRuntime 122 ) { 123 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap(); 124 125 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 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<JetFile> 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 GenerationState generationState = 153 generate(environment, result, jetFiles, module, new File(module.getOutputDirectory()), 154 module.getModuleName()); 155 outputFiles.put(module, generationState.getFactory()); 156 } 157 158 for (Module module : chunk) { 159 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 160 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null); 161 } 162 return true; 163 } 164 165 @NotNull 166 public static CompilerConfiguration createCompilerConfiguration( 167 @NotNull CompilerConfiguration base, 168 @NotNull List<Module> chunk, 169 @NotNull File directory 170 ) { 171 CompilerConfiguration configuration = base.copy(); 172 173 for (Module module : chunk) { 174 addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module)); 175 } 176 177 for (Module module : chunk) { 178 for (String javaSourceRoot : module.getJavaSourceRoots()) { 179 addJavaSourceRoot(configuration, new File(javaSourceRoot)); 180 } 181 } 182 183 for (Module module : chunk) { 184 for (String classpathRoot : module.getClasspathRoots()) { 185 addJvmClasspathRoot(configuration, new File(classpathRoot)); 186 } 187 } 188 189 for (Module module : chunk) { 190 for (String annotationsRoot : module.getAnnotationsRoots()) { 191 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 192 } 193 194 configuration.add(JVMConfigurationKeys.MODULES, module); 195 } 196 197 return configuration; 198 } 199 200 @Nullable 201 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) { 202 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 203 FqName mainClass = null; 204 for (JetFile file : files) { 205 if (mainFunctionDetector.hasMain(file.getDeclarations())) { 206 if (mainClass != null) { 207 // more than one main 208 return null; 209 } 210 FqName fqName = file.getPackageFqName(); 211 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 212 } 213 } 214 return mainClass; 215 } 216 217 public static boolean compileBunchOfSources( 218 @NotNull KotlinCoreEnvironment environment, 219 @Nullable File jar, 220 @Nullable File outputDir, 221 boolean includeRuntime 222 ) { 223 224 GenerationState generationState = analyzeAndGenerate(environment); 225 if (generationState == null) { 226 return false; 227 } 228 229 FqName mainClass = findMainClass(generationState, environment.getSourceFiles()); 230 231 try { 232 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass); 233 return true; 234 } 235 finally { 236 generationState.destroy(); 237 } 238 } 239 240 public static void compileAndExecuteScript( 241 @NotNull CompilerConfiguration configuration, 242 @NotNull KotlinPaths paths, 243 @NotNull KotlinCoreEnvironment environment, 244 @NotNull List<String> scriptArgs 245 ) { 246 Class<?> scriptClass = compileScript(configuration, paths, environment); 247 if (scriptClass == null) return; 248 249 try { 250 scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)}); 251 } 252 catch (RuntimeException e) { 253 throw e; 254 } 255 catch (Exception e) { 256 throw new RuntimeException("Failed to evaluate script: " + e, e); 257 } 258 } 259 260 @Nullable 261 public static Class<?> compileScript( 262 @NotNull CompilerConfiguration configuration, 263 @NotNull KotlinPaths paths, 264 @NotNull KotlinCoreEnvironment environment 265 ) { 266 List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS); 267 if (!scriptParameters.isEmpty()) { 268 JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition( 269 new JetScriptDefinition(".kts", scriptParameters) 270 ); 271 } 272 GenerationState state = analyzeAndGenerate(environment); 273 if (state == null) { 274 return null; 275 } 276 277 GeneratedClassLoader classLoader; 278 try { 279 List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL()); 280 for (File file : getJvmClasspathRoots(configuration)) { 281 classPaths.add(file.toURI().toURL()); 282 } 283 //noinspection UnnecessaryFullyQualifiedName 284 classLoader = new GeneratedClassLoader(state.getFactory(), 285 new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]), null) 286 ); 287 288 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript()); 289 return classLoader.loadClass(nameForScript.asString()); 290 } 291 catch (Exception e) { 292 throw new RuntimeException("Failed to evaluate script: " + e, e); 293 } 294 } 295 296 @Nullable 297 public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) { 298 AnalysisResult result = analyze(environment, null); 299 300 if (result == null) { 301 return null; 302 } 303 304 if (!result.getShouldGenerateCode()) return null; 305 306 result.throwIfError(); 307 308 return generate(environment, result, environment.getSourceFiles(), null, null, null); 309 } 310 311 @Nullable 312 private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment, @Nullable String targetDescription) { 313 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 314 assert collector != null; 315 316 long analysisStart = PerformanceCounter.Companion.currentTime(); 317 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector); 318 analyzerWithCompilerReport.analyzeAndReport( 319 environment.getSourceFiles(), new Function0<AnalysisResult>() { 320 @NotNull 321 @Override 322 public AnalysisResult invoke() { 323 BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 324 ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject(), 325 getModuleName(environment)); 326 327 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext( 328 moduleContext, 329 environment.getSourceFiles(), 330 sharedTrace, 331 environment.getConfiguration().get(JVMConfigurationKeys.MODULES), 332 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS), 333 new JvmPackagePartProvider(environment) 334 ); 335 } 336 } 337 ); 338 long analysisNanos = PerformanceCounter.Companion.currentTime() - analysisStart; 339 String message = "ANALYZE: " + environment.getSourceFiles().size() + " files (" + 340 environment.getSourceLinesOfCode() + " lines) " + 341 (targetDescription != null ? targetDescription : "") + 342 "in " + TimeUnit.NANOSECONDS.toMillis(analysisNanos) + " ms"; 343 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message); 344 345 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult(); 346 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles(); 347 348 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(), 349 environment.getSourceFiles()); 350 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 351 plugin.processFiles(context); 352 } 353 354 return analyzerWithCompilerReport.hasErrors() ? null : result; 355 } 356 357 @NotNull 358 private static GenerationState generate( 359 @NotNull KotlinCoreEnvironment environment, 360 @NotNull AnalysisResult result, 361 @NotNull List<JetFile> sourceFiles, 362 @Nullable Module module, 363 File outputDirectory, 364 String moduleName 365 ) { 366 CompilerConfiguration configuration = environment.getConfiguration(); 367 IncrementalCompilationComponents incrementalCompilationComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS); 368 369 Collection<FqName> packagesWithObsoleteParts; 370 TargetId targetId = null; 371 372 if (module == null || incrementalCompilationComponents == null) { 373 packagesWithObsoleteParts = Collections.emptySet(); 374 } 375 else { 376 targetId = ModulesPackage.TargetId(module); 377 IncrementalCache incrementalCache = incrementalCompilationComponents.getIncrementalCache(targetId); 378 packagesWithObsoleteParts = new HashSet<FqName>(); 379 for (String internalName : incrementalCache.getObsoletePackageParts()) { 380 packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName()); 381 } 382 } 383 BindingTraceContext diagnosticHolder = new BindingTraceContext(); 384 GenerationState generationState = new GenerationState( 385 environment.getProject(), 386 ClassBuilderFactories.BINARIES, 387 result.getModuleDescriptor(), 388 result.getBindingContext(), 389 sourceFiles, 390 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false), 391 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false), 392 GenerationState.GenerateClassFilter.GENERATE_ALL, 393 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false), 394 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false), 395 configuration.get(JVMConfigurationKeys.PACKAGE_FACADES_AS_MULTIFILE_CLASSES, false), 396 diagnosticHolder, 397 packagesWithObsoleteParts, 398 targetId, 399 moduleName, 400 outputDirectory, 401 incrementalCompilationComponents 402 ); 403 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 404 405 long generationStart = PerformanceCounter.Companion.currentTime(); 406 407 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 408 409 long generationNanos = PerformanceCounter.Companion.currentTime() - generationStart; 410 String desc = module != null ? "target " + module.getModuleName() + "-" + module.getModuleType() + " " : ""; 411 String message = "GENERATE: " + sourceFiles.size() + " files (" + 412 environment.countLinesOfCode(sourceFiles) + " lines) " + desc + "in " + TimeUnit.NANOSECONDS.toMillis(generationNanos) + " ms"; 413 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message); 414 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 415 416 AnalyzerWithCompilerReport.reportDiagnostics( 417 new FilteredJvmDiagnostics( 418 diagnosticHolder.getBindingContext().getDiagnostics(), 419 result.getBindingContext().getDiagnostics() 420 ), 421 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 422 ); 423 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); 424 return generationState; 425 } 426 }