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