001 /* 002 * Copyright 2010-2014 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.jet.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.jet.analyzer.AnalyzeExhaust; 034 import org.jetbrains.jet.asJava.FilteredJvmDiagnostics; 035 import org.jetbrains.jet.cli.common.CLIConfigurationKeys; 036 import org.jetbrains.jet.cli.common.CompilerPlugin; 037 import org.jetbrains.jet.cli.common.CompilerPluginContext; 038 import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport; 039 import org.jetbrains.jet.cli.common.messages.MessageCollector; 040 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 041 import org.jetbrains.jet.codegen.*; 042 import org.jetbrains.jet.codegen.inline.InlineCodegenUtil; 043 import org.jetbrains.jet.codegen.optimization.OptimizationUtils; 044 import org.jetbrains.jet.codegen.state.GenerationState; 045 import org.jetbrains.jet.codegen.state.Progress; 046 import org.jetbrains.jet.config.CommonConfigurationKeys; 047 import org.jetbrains.jet.config.CompilerConfiguration; 048 import org.jetbrains.jet.lang.descriptors.impl.ModuleDescriptorImpl; 049 import org.jetbrains.jet.lang.parsing.JetScriptDefinition; 050 import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider; 051 import org.jetbrains.jet.lang.psi.JetFile; 052 import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; 053 import org.jetbrains.jet.lang.resolve.BindingTrace; 054 import org.jetbrains.jet.lang.resolve.BindingTraceContext; 055 import org.jetbrains.jet.lang.resolve.ScriptNameUtil; 056 import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM; 057 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 058 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalCache; 059 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalCacheProvider; 060 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalPackage; 061 import org.jetbrains.jet.lang.resolve.name.FqName; 062 import org.jetbrains.jet.plugin.MainFunctionDetector; 063 import org.jetbrains.jet.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 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 105 CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, outputFiles, messageCollector); 106 } 107 108 public static boolean compileModules( 109 @NotNull CompilerConfiguration configuration, 110 @NotNull List<Module> chunk, 111 @NotNull File directory, 112 @Nullable File jarPath, 113 boolean jarRuntime 114 ) { 115 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap(); 116 117 CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory); 118 119 Disposable parentDisposable = Disposer.newDisposable(); 120 JetCoreEnvironment environment = null; 121 try { 122 environment = JetCoreEnvironment.createForProduction(parentDisposable, compilerConfiguration); 123 124 AnalyzeExhaust exhaust = analyze(environment); 125 if (exhaust == null) { 126 return false; 127 } 128 129 exhaust.throwIfError(); 130 131 for (Module module : chunk) { 132 List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles( 133 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() { 134 @Override 135 public Unit invoke(String s) { 136 throw new IllegalStateException("Should have been checked before: " + s); 137 } 138 } 139 ); 140 GenerationState generationState = 141 generate(environment, exhaust, jetFiles, module.getModuleName(), new File(module.getOutputDirectory())); 142 outputFiles.put(module, generationState.getFactory()); 143 } 144 } 145 finally { 146 if (environment != null) { 147 Disposer.dispose(parentDisposable); 148 } 149 } 150 151 for (Module module : chunk) { 152 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null); 153 } 154 return true; 155 } 156 157 @NotNull 158 private static CompilerConfiguration createCompilerConfiguration( 159 @NotNull CompilerConfiguration base, 160 @NotNull List<Module> chunk, 161 @NotNull File directory 162 ) { 163 CompilerConfiguration configuration = base.copy(); 164 for (Module module : chunk) { 165 configuration.addAll(CommonConfigurationKeys.SOURCE_ROOTS_KEY, getAbsolutePaths(directory, module)); 166 167 for (String classpathRoot : module.getClasspathRoots()) { 168 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot)); 169 } 170 171 for (String annotationsRoot : module.getAnnotationsRoots()) { 172 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 173 } 174 175 configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName()); 176 } 177 178 return configuration; 179 } 180 181 @Nullable 182 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) { 183 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 184 FqName mainClass = null; 185 for (JetFile file : files) { 186 if (mainFunctionDetector.hasMain(file.getDeclarations())) { 187 if (mainClass != null) { 188 // more than one main 189 return null; 190 } 191 FqName fqName = file.getPackageFqName(); 192 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 193 } 194 } 195 return mainClass; 196 } 197 198 public static boolean compileBunchOfSources( 199 @NotNull JetCoreEnvironment environment, 200 @Nullable File jar, 201 @Nullable File outputDir, 202 boolean includeRuntime 203 ) { 204 205 GenerationState generationState = analyzeAndGenerate(environment); 206 if (generationState == null) { 207 return false; 208 } 209 210 FqName mainClass = findMainClass(generationState, environment.getSourceFiles()); 211 212 try { 213 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass); 214 return true; 215 } 216 finally { 217 generationState.destroy(); 218 } 219 } 220 221 public static void compileAndExecuteScript( 222 @NotNull KotlinPaths paths, 223 @NotNull JetCoreEnvironment environment, 224 @NotNull List<String> scriptArgs 225 ) { 226 Class<?> scriptClass = compileScript(paths, environment); 227 if (scriptClass == null) return; 228 229 try { 230 scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)}); 231 } 232 catch (RuntimeException e) { 233 throw e; 234 } 235 catch (Exception e) { 236 throw new RuntimeException("Failed to evaluate script: " + e, e); 237 } 238 } 239 240 @Nullable 241 public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) { 242 List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS); 243 if (!scriptParameters.isEmpty()) { 244 JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition( 245 new JetScriptDefinition(".kts", scriptParameters) 246 ); 247 } 248 GenerationState state = analyzeAndGenerate(environment); 249 if (state == null) { 250 return null; 251 } 252 253 GeneratedClassLoader classLoader; 254 try { 255 classLoader = new GeneratedClassLoader(state.getFactory(), 256 new URLClassLoader(new URL[] { 257 // TODO: add all classpath 258 paths.getRuntimePath().toURI().toURL() 259 }, AllModules.class.getClassLoader()) 260 ); 261 262 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript()); 263 return classLoader.loadClass(nameForScript.asString()); 264 } 265 catch (Exception e) { 266 throw new RuntimeException("Failed to evaluate script: " + e, e); 267 } 268 } 269 270 @Nullable 271 public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) { 272 AnalyzeExhaust exhaust = analyze(environment); 273 274 if (exhaust == null) { 275 return null; 276 } 277 278 exhaust.throwIfError(); 279 280 return generate(environment, exhaust, environment.getSourceFiles(), null, null); 281 } 282 283 @Nullable 284 private static AnalyzeExhaust analyze(@NotNull final JetCoreEnvironment environment) { 285 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport( 286 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)); 287 analyzerWithCompilerReport.analyzeAndReport( 288 environment.getSourceFiles(), new Function0<AnalyzeExhaust>() { 289 @NotNull 290 @Override 291 public AnalyzeExhaust invoke() { 292 CliLightClassGenerationSupport support = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()); 293 BindingTrace sharedTrace = support.getTrace(); 294 ModuleDescriptorImpl sharedModule = support.getModule(); 295 296 IncrementalCacheProvider incrementalCacheProvider = IncrementalCacheProvider.OBJECT$.getInstance(); 297 File incrementalCacheBaseDir = environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR); 298 final IncrementalCache incrementalCache; 299 if (incrementalCacheProvider != null && incrementalCacheBaseDir != null) { 300 incrementalCache = incrementalCacheProvider.getIncrementalCache(incrementalCacheBaseDir); 301 Disposer.register(environment.getApplication(), new Disposable() { 302 @Override 303 public void dispose() { 304 incrementalCache.close(); 305 } 306 }); 307 } 308 else { 309 incrementalCache = null; 310 } 311 312 313 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( 314 environment.getProject(), 315 environment.getSourceFiles(), 316 sharedTrace, 317 Predicates.<PsiFile>alwaysTrue(), 318 sharedModule, 319 environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS), 320 incrementalCache 321 ); 322 } 323 } 324 ); 325 326 AnalyzeExhaust exhaust = analyzerWithCompilerReport.getAnalyzeExhaust(); 327 assert exhaust != null : "AnalyzeExhaust should be non-null, compiling: " + environment.getSourceFiles(); 328 329 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), exhaust.getBindingContext(), 330 environment.getSourceFiles()); 331 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 332 plugin.processFiles(context); 333 } 334 335 return analyzerWithCompilerReport.hasErrors() ? null : exhaust; 336 } 337 338 @NotNull 339 private static GenerationState generate( 340 @NotNull JetCoreEnvironment environment, 341 @NotNull AnalyzeExhaust exhaust, 342 @NotNull List<JetFile> sourceFiles, 343 @Nullable String moduleId, 344 File outputDirectory 345 ) { 346 CompilerConfiguration configuration = environment.getConfiguration(); 347 File incrementalCacheDir = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR); 348 IncrementalCacheProvider incrementalCacheProvider = IncrementalCacheProvider.OBJECT$.getInstance(); 349 350 Collection<FqName> packagesWithRemovedFiles; 351 if (incrementalCacheDir == null || moduleId == null || incrementalCacheProvider == null) { 352 packagesWithRemovedFiles = null; 353 } 354 else { 355 IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(incrementalCacheDir); 356 try { 357 packagesWithRemovedFiles = IncrementalPackage.getPackagesWithRemovedFiles( 358 incrementalCache, moduleId, environment.getSourceFiles()); 359 } 360 finally { 361 incrementalCache.close(); 362 } 363 } 364 BindingTraceContext diagnosticHolder = new BindingTraceContext(); 365 GenerationState generationState = new GenerationState( 366 environment.getProject(), 367 ClassBuilderFactories.BINARIES, 368 Progress.DEAF, 369 exhaust.getModuleDescriptor(), 370 exhaust.getBindingContext(), 371 sourceFiles, 372 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false), 373 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false), 374 GenerationState.GenerateClassFilter.GENERATE_ALL, 375 configuration.get(JVMConfigurationKeys.ENABLE_INLINE, InlineCodegenUtil.DEFAULT_INLINE_FLAG), 376 configuration.get(JVMConfigurationKeys.ENABLE_OPTIMIZATION, OptimizationUtils.DEFAULT_OPTIMIZATION_FLAG), 377 packagesWithRemovedFiles, 378 moduleId, 379 diagnosticHolder, 380 outputDirectory); 381 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 382 AnalyzerWithCompilerReport.reportDiagnostics( 383 new FilteredJvmDiagnostics( 384 diagnosticHolder.getBindingContext().getDiagnostics(), 385 exhaust.getBindingContext().getDiagnostics() 386 ), 387 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 388 ); 389 return generationState; 390 } 391 }