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