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.AnalysisResult; 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.CompilerConfiguration; 045 import org.jetbrains.jet.context.ContextPackage; 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.PackageClassUtils; 055 import org.jetbrains.jet.lang.resolve.java.TopDownAnalyzerFacadeForJVM; 056 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalPackage; 057 import org.jetbrains.jet.lang.resolve.kotlin.incremental.cache.IncrementalCache; 058 import org.jetbrains.jet.lang.resolve.kotlin.incremental.cache.IncrementalCacheProvider; 059 import org.jetbrains.jet.lang.resolve.name.FqName; 060 import org.jetbrains.jet.plugin.MainFunctionDetector; 061 import org.jetbrains.jet.utils.KotlinPaths; 062 063 import java.io.File; 064 import java.net.URL; 065 import java.net.URLClassLoader; 066 import java.util.Collection; 067 import java.util.List; 068 import java.util.Map; 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 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 103 CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, outputFiles, messageCollector); 104 } 105 106 public static boolean compileModules( 107 @NotNull CompilerConfiguration configuration, 108 @NotNull List<Module> chunk, 109 @NotNull File directory, 110 @Nullable File jarPath, 111 boolean jarRuntime 112 ) { 113 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap(); 114 115 CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory); 116 117 Disposable parentDisposable = Disposer.newDisposable(); 118 JetCoreEnvironment environment = null; 119 try { 120 environment = JetCoreEnvironment 121 .createForProduction(parentDisposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES); 122 123 AnalysisResult result = analyze(environment); 124 if (result == null) { 125 return false; 126 } 127 128 result.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, result, 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 164 List<String> sourceRoots = Lists.newArrayList(); 165 166 for (Module module : chunk) { 167 sourceRoots.addAll(getAbsolutePaths(directory, module)); 168 169 for (String classpathRoot : module.getClasspathRoots()) { 170 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot)); 171 } 172 173 for (String annotationsRoot : module.getAnnotationsRoots()) { 174 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 175 } 176 177 configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName()); 178 } 179 180 CompileEnvironmentUtil.addSourceFilesCheckingForDuplicates(configuration, sourceRoots); 181 182 return configuration; 183 } 184 185 @Nullable 186 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) { 187 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 188 FqName mainClass = null; 189 for (JetFile file : files) { 190 if (mainFunctionDetector.hasMain(file.getDeclarations())) { 191 if (mainClass != null) { 192 // more than one main 193 return null; 194 } 195 FqName fqName = file.getPackageFqName(); 196 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 197 } 198 } 199 return mainClass; 200 } 201 202 public static boolean compileBunchOfSources( 203 @NotNull JetCoreEnvironment environment, 204 @Nullable File jar, 205 @Nullable File outputDir, 206 boolean includeRuntime 207 ) { 208 209 GenerationState generationState = analyzeAndGenerate(environment); 210 if (generationState == null) { 211 return false; 212 } 213 214 FqName mainClass = findMainClass(generationState, environment.getSourceFiles()); 215 216 try { 217 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass); 218 return true; 219 } 220 finally { 221 generationState.destroy(); 222 } 223 } 224 225 public static void compileAndExecuteScript( 226 @NotNull KotlinPaths paths, 227 @NotNull JetCoreEnvironment environment, 228 @NotNull List<String> scriptArgs 229 ) { 230 Class<?> scriptClass = compileScript(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(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) { 246 List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS); 247 if (!scriptParameters.isEmpty()) { 248 JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition( 249 new JetScriptDefinition(".kts", scriptParameters) 250 ); 251 } 252 GenerationState state = analyzeAndGenerate(environment); 253 if (state == null) { 254 return null; 255 } 256 257 GeneratedClassLoader classLoader; 258 try { 259 classLoader = new GeneratedClassLoader(state.getFactory(), 260 new URLClassLoader(new URL[] { 261 // TODO: add all classpath 262 paths.getRuntimePath().toURI().toURL() 263 }, AllModules.class.getClassLoader()) 264 ); 265 266 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript()); 267 return classLoader.loadClass(nameForScript.asString()); 268 } 269 catch (Exception e) { 270 throw new RuntimeException("Failed to evaluate script: " + e, e); 271 } 272 } 273 274 @Nullable 275 public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) { 276 AnalysisResult result = analyze(environment); 277 278 if (result == null) { 279 return null; 280 } 281 282 result.throwIfError(); 283 284 return generate(environment, result, environment.getSourceFiles(), null, null); 285 } 286 287 @Nullable 288 private static AnalysisResult analyze(@NotNull final JetCoreEnvironment environment) { 289 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 290 assert collector != null; 291 292 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector); 293 analyzerWithCompilerReport.analyzeAndReport( 294 environment.getSourceFiles(), new Function0<AnalysisResult>() { 295 @NotNull 296 @Override 297 public AnalysisResult invoke() { 298 BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(); 299 ModuleDescriptorImpl analyzeModule = TopDownAnalyzerFacadeForJVM.createSealedJavaModule(); 300 301 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext( 302 environment.getProject(), 303 ContextPackage.GlobalContext(), 304 environment.getSourceFiles(), 305 sharedTrace, 306 Predicates.<PsiFile>alwaysTrue(), 307 analyzeModule, 308 environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS), 309 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER) 310 ); 311 } 312 } 313 ); 314 315 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult(); 316 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles(); 317 318 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(), 319 environment.getSourceFiles()); 320 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 321 plugin.processFiles(context); 322 } 323 324 return analyzerWithCompilerReport.hasErrors() ? null : result; 325 } 326 327 @NotNull 328 private static GenerationState generate( 329 @NotNull JetCoreEnvironment environment, 330 @NotNull AnalysisResult result, 331 @NotNull List<JetFile> sourceFiles, 332 @Nullable String moduleId, 333 File outputDirectory 334 ) { 335 CompilerConfiguration configuration = environment.getConfiguration(); 336 IncrementalCacheProvider incrementalCacheProvider = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER); 337 338 Collection<FqName> packagesWithRemovedFiles; 339 if (moduleId == null || incrementalCacheProvider == null) { 340 packagesWithRemovedFiles = null; 341 } 342 else { 343 IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(moduleId); 344 packagesWithRemovedFiles = IncrementalPackage.getPackagesWithRemovedFiles(incrementalCache, environment.getSourceFiles()); 345 } 346 BindingTraceContext diagnosticHolder = new BindingTraceContext(); 347 GenerationState generationState = new GenerationState( 348 environment.getProject(), 349 ClassBuilderFactories.BINARIES, 350 Progress.DEAF, 351 result.getModuleDescriptor(), 352 result.getBindingContext(), 353 sourceFiles, 354 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false), 355 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false), 356 GenerationState.GenerateClassFilter.GENERATE_ALL, 357 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false), 358 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false), 359 packagesWithRemovedFiles, 360 moduleId, 361 diagnosticHolder, 362 outputDirectory 363 ); 364 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 365 AnalyzerWithCompilerReport.reportDiagnostics( 366 new FilteredJvmDiagnostics( 367 diagnosticHolder.getBindingContext().getDiagnostics(), 368 result.getBindingContext().getDiagnostics() 369 ), 370 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 371 ); 372 return generationState; 373 } 374 }