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.Function0; 025 import kotlin.Function1; 026 import kotlin.Unit; 027 import kotlin.modules.AllModules; 028 import kotlin.modules.Module; 029 import org.jetbrains.annotations.NotNull; 030 import org.jetbrains.annotations.Nullable; 031 import org.jetbrains.kotlin.analyzer.AnalysisResult; 032 import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics; 033 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys; 034 import org.jetbrains.kotlin.cli.common.CompilerPlugin; 035 import org.jetbrains.kotlin.cli.common.CompilerPluginContext; 036 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; 037 import org.jetbrains.kotlin.cli.common.messages.MessageCollector; 038 import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsPackage; 039 import org.jetbrains.kotlin.cli.jvm.JVMConfigurationKeys; 040 import org.jetbrains.kotlin.codegen.*; 041 import org.jetbrains.kotlin.codegen.state.GenerationState; 042 import org.jetbrains.kotlin.codegen.state.Progress; 043 import org.jetbrains.kotlin.config.CommonConfigurationKeys; 044 import org.jetbrains.kotlin.config.CompilerConfiguration; 045 import org.jetbrains.kotlin.context.ContextPackage; 046 import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; 047 import org.jetbrains.kotlin.idea.MainFunctionDetector; 048 import org.jetbrains.kotlin.load.kotlin.PackageClassUtils; 049 import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCache; 050 import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCacheProvider; 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.psi.JetFile; 055 import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter; 056 import org.jetbrains.kotlin.resolve.BindingTrace; 057 import org.jetbrains.kotlin.resolve.BindingTraceContext; 058 import org.jetbrains.kotlin.resolve.ScriptNameUtil; 059 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 060 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM; 061 import org.jetbrains.kotlin.utils.KotlinPaths; 062 063 import java.io.File; 064 import java.net.URL; 065 import java.net.URLClassLoader; 066 import java.util.*; 067 068 public class KotlinToJVMBytecodeCompiler { 069 070 private KotlinToJVMBytecodeCompiler() { 071 } 072 073 @NotNull 074 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) { 075 List<String> result = Lists.newArrayList(); 076 077 for (String sourceFile : module.getSourceFiles()) { 078 File source = new File(sourceFile); 079 if (!source.isAbsolute()) { 080 source = new File(directory, sourceFile); 081 } 082 083 if (!source.exists()) { 084 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName()); 085 } 086 087 result.add(source.getAbsolutePath()); 088 } 089 return result; 090 } 091 092 private static void writeOutput( 093 @NotNull CompilerConfiguration configuration, 094 @NotNull ClassFileFactory outputFiles, 095 @Nullable File outputDir, 096 @Nullable File jarPath, 097 boolean jarRuntime, 098 @Nullable FqName mainClass 099 ) { 100 if (jarPath != null) { 101 CompileEnvironmentUtil.writeToJar(jarPath, jarRuntime, mainClass, outputFiles); 102 } 103 else { 104 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 105 OutputUtilsPackage.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector); 106 } 107 } 108 109 public static boolean compileModules( 110 @NotNull CompilerConfiguration configuration, 111 @NotNull List<Module> chunk, 112 @NotNull File directory, 113 @Nullable File jarPath, 114 boolean jarRuntime 115 ) { 116 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap(); 117 118 CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory); 119 120 Disposable parentDisposable = Disposer.newDisposable(); 121 JetCoreEnvironment environment = null; 122 try { 123 environment = JetCoreEnvironment 124 .createForProduction(parentDisposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES); 125 126 AnalysisResult result = analyze(environment); 127 if (result == null) { 128 return false; 129 } 130 131 result.throwIfError(); 132 133 for (Module module : chunk) { 134 List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles( 135 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() { 136 @Override 137 public Unit invoke(String s) { 138 throw new IllegalStateException("Should have been checked before: " + s); 139 } 140 } 141 ); 142 GenerationState generationState = 143 generate(environment, result, jetFiles, module.getModuleName(), new File(module.getOutputDirectory())); 144 outputFiles.put(module, generationState.getFactory()); 145 } 146 } 147 finally { 148 if (environment != null) { 149 Disposer.dispose(parentDisposable); 150 } 151 } 152 153 for (Module module : chunk) { 154 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null); 155 } 156 return true; 157 } 158 159 @NotNull 160 private static CompilerConfiguration createCompilerConfiguration( 161 @NotNull CompilerConfiguration base, 162 @NotNull List<Module> chunk, 163 @NotNull File directory 164 ) { 165 CompilerConfiguration configuration = base.copy(); 166 167 for (Module module : chunk) { 168 configuration.addAll(CommonConfigurationKeys.SOURCE_ROOTS_KEY, getAbsolutePaths(directory, module)); 169 170 for (String classpathRoot : module.getClasspathRoots()) { 171 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot)); 172 } 173 174 for (String annotationsRoot : module.getAnnotationsRoots()) { 175 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 176 } 177 178 configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName()); 179 } 180 181 return configuration; 182 } 183 184 @Nullable 185 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) { 186 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 187 FqName mainClass = null; 188 for (JetFile file : files) { 189 if (mainFunctionDetector.hasMain(file.getDeclarations())) { 190 if (mainClass != null) { 191 // more than one main 192 return null; 193 } 194 FqName fqName = file.getPackageFqName(); 195 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 196 } 197 } 198 return mainClass; 199 } 200 201 public static boolean compileBunchOfSources( 202 @NotNull JetCoreEnvironment environment, 203 @Nullable File jar, 204 @Nullable File outputDir, 205 boolean includeRuntime 206 ) { 207 208 GenerationState generationState = analyzeAndGenerate(environment); 209 if (generationState == null) { 210 return false; 211 } 212 213 FqName mainClass = findMainClass(generationState, environment.getSourceFiles()); 214 215 try { 216 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass); 217 return true; 218 } 219 finally { 220 generationState.destroy(); 221 } 222 } 223 224 public static void compileAndExecuteScript( 225 @NotNull CompilerConfiguration configuration, 226 @NotNull KotlinPaths paths, 227 @NotNull JetCoreEnvironment environment, 228 @NotNull List<String> scriptArgs 229 ) { 230 Class<?> scriptClass = compileScript(configuration, paths, environment); 231 if (scriptClass == null) return; 232 233 try { 234 scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)}); 235 } 236 catch (RuntimeException e) { 237 throw e; 238 } 239 catch (Exception e) { 240 throw new RuntimeException("Failed to evaluate script: " + e, e); 241 } 242 } 243 244 @Nullable 245 public static Class<?> compileScript( 246 @NotNull CompilerConfiguration configuration, 247 @NotNull KotlinPaths paths, 248 @NotNull JetCoreEnvironment environment 249 ) { 250 List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS); 251 if (!scriptParameters.isEmpty()) { 252 JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition( 253 new JetScriptDefinition(".kts", scriptParameters) 254 ); 255 } 256 GenerationState state = analyzeAndGenerate(environment); 257 if (state == null) { 258 return null; 259 } 260 261 GeneratedClassLoader classLoader; 262 try { 263 List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL()); 264 for (File file : configuration.get(JVMConfigurationKeys.CLASSPATH_KEY, Collections.<File>emptyList())) { 265 classPaths.add(file.toURI().toURL()); 266 } 267 classLoader = new GeneratedClassLoader(state.getFactory(), 268 new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]), 269 AllModules.class.getClassLoader()) 270 ); 271 272 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript()); 273 return classLoader.loadClass(nameForScript.asString()); 274 } 275 catch (Exception e) { 276 throw new RuntimeException("Failed to evaluate script: " + e, e); 277 } 278 } 279 280 @Nullable 281 public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) { 282 AnalysisResult result = analyze(environment); 283 284 if (result == null) { 285 return null; 286 } 287 288 result.throwIfError(); 289 290 return generate(environment, result, environment.getSourceFiles(), null, null); 291 } 292 293 @Nullable 294 private static AnalysisResult analyze(@NotNull final JetCoreEnvironment environment) { 295 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 296 assert collector != null; 297 298 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector); 299 analyzerWithCompilerReport.analyzeAndReport( 300 environment.getSourceFiles(), new Function0<AnalysisResult>() { 301 @NotNull 302 @Override 303 public AnalysisResult invoke() { 304 BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 305 ModuleDescriptorImpl analyzeModule = TopDownAnalyzerFacadeForJVM.createSealedJavaModule(); 306 307 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext( 308 environment.getProject(), 309 ContextPackage.GlobalContext(), 310 environment.getSourceFiles(), 311 sharedTrace, 312 analyzeModule, 313 environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS), 314 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER) 315 ); 316 } 317 } 318 ); 319 320 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult(); 321 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles(); 322 323 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(), 324 environment.getSourceFiles()); 325 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 326 plugin.processFiles(context); 327 } 328 329 return analyzerWithCompilerReport.hasErrors() ? null : result; 330 } 331 332 @NotNull 333 private static GenerationState generate( 334 @NotNull JetCoreEnvironment environment, 335 @NotNull AnalysisResult result, 336 @NotNull List<JetFile> sourceFiles, 337 @Nullable String moduleId, 338 File outputDirectory 339 ) { 340 CompilerConfiguration configuration = environment.getConfiguration(); 341 IncrementalCacheProvider incrementalCacheProvider = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER); 342 343 Collection<FqName> packagesWithObsoleteParts; 344 if (moduleId == null || incrementalCacheProvider == null) { 345 packagesWithObsoleteParts = null; 346 } 347 else { 348 IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(moduleId); 349 packagesWithObsoleteParts = new HashSet<FqName>(); 350 for (String internalName : incrementalCache.getObsoletePackageParts()) { 351 packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName()); 352 } 353 } 354 BindingTraceContext diagnosticHolder = new BindingTraceContext(); 355 GenerationState generationState = new GenerationState( 356 environment.getProject(), 357 ClassBuilderFactories.BINARIES, 358 Progress.DEAF, 359 result.getModuleDescriptor(), 360 result.getBindingContext(), 361 sourceFiles, 362 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false), 363 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false), 364 GenerationState.GenerateClassFilter.GENERATE_ALL, 365 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false), 366 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false), 367 packagesWithObsoleteParts, 368 moduleId, 369 diagnosticHolder, 370 outputDirectory 371 ); 372 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 373 AnalyzerWithCompilerReport.reportDiagnostics( 374 new FilteredJvmDiagnostics( 375 diagnosticHolder.getBindingContext().getDiagnostics(), 376 result.getBindingContext().getDiagnostics() 377 ), 378 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 379 ); 380 return generationState; 381 } 382 }