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