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