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.collect.Lists; 020 import com.google.common.collect.Maps; 021 import com.intellij.openapi.Disposable; 022 import com.intellij.openapi.util.Disposer; 023 import com.intellij.util.ArrayUtil; 024 import kotlin.Unit; 025 import kotlin.jvm.functions.Function0; 026 import kotlin.jvm.functions.Function1; 027 import org.jetbrains.annotations.NotNull; 028 import org.jetbrains.annotations.Nullable; 029 import org.jetbrains.kotlin.analyzer.AnalysisResult; 030 import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics; 031 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys; 032 import org.jetbrains.kotlin.cli.common.CompilerPlugin; 033 import org.jetbrains.kotlin.cli.common.CompilerPluginContext; 034 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; 035 import org.jetbrains.kotlin.cli.common.messages.MessageCollector; 036 import org.jetbrains.kotlin.cli.common.modules.Module; 037 import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsPackage; 038 import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys; 039 import org.jetbrains.kotlin.codegen.*; 040 import org.jetbrains.kotlin.codegen.state.GenerationState; 041 import org.jetbrains.kotlin.codegen.state.Progress; 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.cache.IncrementalCache; 047 import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCacheProvider; 048 import org.jetbrains.kotlin.name.FqName; 049 import org.jetbrains.kotlin.parsing.JetScriptDefinition; 050 import org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider; 051 import org.jetbrains.kotlin.psi.JetFile; 052 import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter; 053 import org.jetbrains.kotlin.resolve.BindingTrace; 054 import org.jetbrains.kotlin.resolve.BindingTraceContext; 055 import org.jetbrains.kotlin.resolve.ScriptNameUtil; 056 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 057 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 058 import org.jetbrains.kotlin.utils.KotlinPaths; 059 060 import java.io.File; 061 import java.net.URL; 062 import java.net.URLClassLoader; 063 import java.util.Collection; 064 import java.util.HashSet; 065 import java.util.List; 066 import java.util.Map; 067 068 import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.*; 069 import static org.jetbrains.kotlin.config.ConfigPackage.addKotlinSourceRoots; 070 071 public class KotlinToJVMBytecodeCompiler { 072 073 private KotlinToJVMBytecodeCompiler() { 074 } 075 076 @NotNull 077 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) { 078 List<String> result = Lists.newArrayList(); 079 080 for (String sourceFile : module.getSourceFiles()) { 081 File source = new File(sourceFile); 082 if (!source.isAbsolute()) { 083 source = new File(directory, sourceFile); 084 } 085 086 if (!source.exists()) { 087 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName()); 088 } 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 OutputUtilsPackage.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector); 109 } 110 } 111 112 public static boolean compileModules( 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 CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory); 122 123 Disposable parentDisposable = Disposer.newDisposable(); 124 KotlinCoreEnvironment environment = null; 125 try { 126 environment = KotlinCoreEnvironment 127 .createForProduction(parentDisposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES); 128 129 AnalysisResult result = analyze(environment); 130 if (result == null) { 131 return false; 132 } 133 134 result.throwIfError(); 135 136 for (Module module : chunk) { 137 List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles( 138 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() { 139 @Override 140 public Unit invoke(String s) { 141 throw new IllegalStateException("Should have been checked before: " + s); 142 } 143 } 144 ); 145 GenerationState generationState = 146 generate(environment, result, jetFiles, module.getModuleName(), new File(module.getOutputDirectory())); 147 outputFiles.put(module, generationState.getFactory()); 148 } 149 } 150 finally { 151 if (environment != null) { 152 Disposer.dispose(parentDisposable); 153 } 154 } 155 156 for (Module module : chunk) { 157 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null); 158 } 159 return true; 160 } 161 162 @NotNull 163 private static CompilerConfiguration createCompilerConfiguration( 164 @NotNull CompilerConfiguration base, 165 @NotNull List<Module> chunk, 166 @NotNull File directory 167 ) { 168 CompilerConfiguration configuration = base.copy(); 169 170 for (Module module : chunk) { 171 addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module)); 172 } 173 174 for (Module module : chunk) { 175 for (String javaSourceRoot : module.getJavaSourceRoots()) { 176 addJavaSourceRoot(configuration, new File(javaSourceRoot)); 177 } 178 } 179 180 for (Module module : chunk) { 181 for (String classpathRoot : module.getClasspathRoots()) { 182 addJvmClasspathRoot(configuration, new File(classpathRoot)); 183 } 184 } 185 186 for (Module module : chunk) { 187 for (String annotationsRoot : module.getAnnotationsRoots()) { 188 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 189 } 190 191 configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName()); 192 } 193 194 return configuration; 195 } 196 197 @Nullable 198 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) { 199 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 200 FqName mainClass = null; 201 for (JetFile 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 = PackageClassUtils.getPackageClassFqName(fqName); 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 : 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()]), 283 kotlin.KotlinPackage.class.getClassLoader()) 284 ); 285 286 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript()); 287 return classLoader.loadClass(nameForScript.asString()); 288 } 289 catch (Exception e) { 290 throw new RuntimeException("Failed to evaluate script: " + e, e); 291 } 292 } 293 294 @Nullable 295 public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) { 296 AnalysisResult result = analyze(environment); 297 298 if (result == null) { 299 return null; 300 } 301 302 if (!result.getShouldGenerateCode()) return null; 303 304 result.throwIfError(); 305 306 return generate(environment, result, environment.getSourceFiles(), null, null); 307 } 308 309 @Nullable 310 private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment) { 311 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 312 assert collector != null; 313 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 323 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext( 324 moduleContext, 325 environment.getSourceFiles(), 326 sharedTrace, 327 environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS), 328 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER) 329 ); 330 } 331 } 332 ); 333 334 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult(); 335 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles(); 336 337 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(), 338 environment.getSourceFiles()); 339 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 340 plugin.processFiles(context); 341 } 342 343 return analyzerWithCompilerReport.hasErrors() ? null : result; 344 } 345 346 @NotNull 347 private static GenerationState generate( 348 @NotNull KotlinCoreEnvironment environment, 349 @NotNull AnalysisResult result, 350 @NotNull List<JetFile> sourceFiles, 351 @Nullable String moduleId, 352 File outputDirectory 353 ) { 354 CompilerConfiguration configuration = environment.getConfiguration(); 355 IncrementalCacheProvider incrementalCacheProvider = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER); 356 357 Collection<FqName> packagesWithObsoleteParts; 358 if (moduleId == null || incrementalCacheProvider == null) { 359 packagesWithObsoleteParts = null; 360 } 361 else { 362 IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(moduleId); 363 packagesWithObsoleteParts = new HashSet<FqName>(); 364 for (String internalName : incrementalCache.getObsoletePackageParts()) { 365 packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName()); 366 } 367 } 368 BindingTraceContext diagnosticHolder = new BindingTraceContext(); 369 GenerationState generationState = new GenerationState( 370 environment.getProject(), 371 ClassBuilderFactories.BINARIES, 372 Progress.DEAF, 373 result.getModuleDescriptor(), 374 result.getBindingContext(), 375 sourceFiles, 376 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false), 377 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false), 378 GenerationState.GenerateClassFilter.GENERATE_ALL, 379 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false), 380 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false), 381 packagesWithObsoleteParts, 382 moduleId, 383 diagnosticHolder, 384 outputDirectory 385 ); 386 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 387 AnalyzerWithCompilerReport.reportDiagnostics( 388 new FilteredJvmDiagnostics( 389 diagnosticHolder.getBindingContext().getDiagnostics(), 390 result.getBindingContext().getDiagnostics() 391 ), 392 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 393 ); 394 return generationState; 395 } 396 }