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 kotlin.Function0; 026 import kotlin.Function1; 027 import kotlin.Unit; 028 import kotlin.modules.AllModules; 029 import kotlin.modules.Module; 030 import org.jetbrains.annotations.NotNull; 031 import org.jetbrains.annotations.Nullable; 032 import org.jetbrains.jet.analyzer.AnalyzeExhaust; 033 import org.jetbrains.jet.asJava.FilteredJvmDiagnostics; 034 import org.jetbrains.jet.cli.common.CLIConfigurationKeys; 035 import org.jetbrains.jet.cli.common.CompilerPlugin; 036 import org.jetbrains.jet.cli.common.CompilerPluginContext; 037 import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport; 038 import org.jetbrains.jet.cli.common.messages.MessageCollector; 039 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 040 import org.jetbrains.jet.codegen.*; 041 import org.jetbrains.jet.codegen.inline.InlineCodegenUtil; 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.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.IncrementalCacheProvider; 057 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalPackage; 058 import org.jetbrains.jet.lang.resolve.name.FqName; 059 import org.jetbrains.jet.plugin.MainFunctionDetector; 060 import org.jetbrains.jet.utils.KotlinPaths; 061 062 import java.io.File; 063 import java.net.URL; 064 import java.net.URLClassLoader; 065 import java.util.Collection; 066 import java.util.List; 067 import java.util.Map; 068 069 public class KotlinToJVMBytecodeCompiler { 070 071 private KotlinToJVMBytecodeCompiler() { 072 } 073 074 @NotNull 075 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) { 076 List<String> result = Lists.newArrayList(); 077 078 for (String sourceFile : module.getSourceFiles()) { 079 File source = new File(sourceFile); 080 if (!source.isAbsolute()) { 081 source = new File(directory, sourceFile); 082 } 083 084 if (!source.exists()) { 085 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName()); 086 } 087 088 result.add(source.getAbsolutePath()); 089 } 090 return result; 091 } 092 093 private static void writeOutput( 094 CompilerConfiguration configuration, 095 ClassFileFactory outputFiles, 096 File outputDir, 097 File jarPath, 098 boolean jarRuntime, 099 FqName mainClass 100 ) { 101 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 102 CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, outputFiles, messageCollector); 103 } 104 105 public static boolean compileModules( 106 @NotNull CompilerConfiguration configuration, 107 @NotNull List<Module> chunk, 108 @NotNull File directory, 109 @Nullable File jarPath, 110 boolean jarRuntime 111 ) { 112 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap(); 113 114 CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory); 115 116 Disposable parentDisposable = Disposer.newDisposable(); 117 JetCoreEnvironment environment = null; 118 try { 119 environment = JetCoreEnvironment.createForProduction(parentDisposable, compilerConfiguration); 120 121 AnalyzeExhaust exhaust = analyze(environment); 122 if (exhaust == null) { 123 return false; 124 } 125 126 exhaust.throwIfError(); 127 128 for (Module module : chunk) { 129 List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles( 130 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() { 131 @Override 132 public Unit invoke(String s) { 133 throw new IllegalStateException("Should have been checked before: " + s); 134 } 135 } 136 ); 137 GenerationState generationState = generate(environment, exhaust, jetFiles, module.getModuleName()); 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 for (Module module : chunk) { 161 configuration.addAll(CommonConfigurationKeys.SOURCE_ROOTS_KEY, getAbsolutePaths(directory, module)); 162 163 for (String classpathRoot : module.getClasspathRoots()) { 164 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot)); 165 } 166 167 for (String annotationsRoot : module.getAnnotationsRoots()) { 168 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 169 } 170 171 configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName()); 172 } 173 174 return configuration; 175 } 176 177 @Nullable 178 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) { 179 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext()); 180 FqName mainClass = null; 181 for (JetFile file : files) { 182 if (mainFunctionDetector.hasMain(file.getDeclarations())) { 183 if (mainClass != null) { 184 // more than one main 185 return null; 186 } 187 FqName fqName = file.getPackageFqName(); 188 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 189 } 190 } 191 return mainClass; 192 } 193 194 public static boolean compileBunchOfSources( 195 JetCoreEnvironment environment, 196 @Nullable File jar, 197 @Nullable File outputDir, 198 boolean includeRuntime 199 ) { 200 201 GenerationState generationState = analyzeAndGenerate(environment); 202 if (generationState == null) { 203 return false; 204 } 205 206 FqName mainClass = findMainClass(generationState, environment.getSourceFiles()); 207 208 try { 209 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass); 210 return true; 211 } 212 finally { 213 generationState.destroy(); 214 } 215 } 216 217 public static void compileAndExecuteScript( 218 @NotNull KotlinPaths paths, 219 @NotNull JetCoreEnvironment environment, 220 @NotNull List<String> scriptArgs 221 ) { 222 Class<?> scriptClass = compileScript(paths, environment); 223 if (scriptClass == null) return; 224 225 try { 226 scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[scriptArgs.size()])}); 227 } 228 catch (RuntimeException e) { 229 throw e; 230 } 231 catch (Exception e) { 232 throw new RuntimeException("Failed to evaluate script: " + e, e); 233 } 234 } 235 236 @Nullable 237 public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) { 238 List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS); 239 if (!scriptParameters.isEmpty()) { 240 JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition( 241 new JetScriptDefinition(".kts", scriptParameters) 242 ); 243 } 244 GenerationState state = analyzeAndGenerate(environment); 245 if (state == null) { 246 return null; 247 } 248 249 GeneratedClassLoader classLoader; 250 try { 251 classLoader = new GeneratedClassLoader(state.getFactory(), 252 new URLClassLoader(new URL[] { 253 // TODO: add all classpath 254 paths.getRuntimePath().toURI().toURL() 255 }, AllModules.class.getClassLoader()) 256 ); 257 258 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript()); 259 return classLoader.loadClass(nameForScript.asString()); 260 } 261 catch (Exception e) { 262 throw new RuntimeException("Failed to evaluate script: " + e, e); 263 } 264 } 265 266 @Nullable 267 public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) { 268 AnalyzeExhaust exhaust = analyze(environment); 269 270 if (exhaust == null) { 271 return null; 272 } 273 274 exhaust.throwIfError(); 275 276 return generate(environment, exhaust, environment.getSourceFiles(), null); 277 } 278 279 @Nullable 280 private static AnalyzeExhaust analyze(@NotNull final JetCoreEnvironment environment) { 281 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport( 282 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)); 283 analyzerWithCompilerReport.analyzeAndReport( 284 new Function0<AnalyzeExhaust>() { 285 @NotNull 286 @Override 287 public AnalyzeExhaust invoke() { 288 CliLightClassGenerationSupport support = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()); 289 BindingTrace sharedTrace = support.getTrace(); 290 ModuleDescriptorImpl sharedModule = support.getModule(); 291 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( 292 environment.getProject(), 293 environment.getSourceFiles(), 294 sharedTrace, 295 Predicates.<PsiFile>alwaysTrue(), 296 sharedModule, 297 environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS), 298 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR) 299 ); 300 } 301 }, environment.getSourceFiles() 302 ); 303 304 AnalyzeExhaust exhaust = analyzerWithCompilerReport.getAnalyzeExhaust(); 305 assert exhaust != null : "AnalyzeExhaust should be non-null, compiling: " + environment.getSourceFiles(); 306 307 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), exhaust.getBindingContext(), 308 environment.getSourceFiles()); 309 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 310 plugin.processFiles(context); 311 } 312 313 return analyzerWithCompilerReport.hasErrors() ? null : exhaust; 314 } 315 316 @NotNull 317 private static GenerationState generate( 318 @NotNull JetCoreEnvironment environment, 319 @NotNull AnalyzeExhaust exhaust, 320 @NotNull List<JetFile> sourceFiles, 321 @Nullable String moduleId 322 ) { 323 CompilerConfiguration configuration = environment.getConfiguration(); 324 File incrementalCacheDir = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR); 325 IncrementalCacheProvider incrementalCacheProvider = IncrementalCacheProvider.object$.getInstance(); 326 327 Collection<FqName> packagesWithRemovedFiles = 328 incrementalCacheDir == null || moduleId == null || incrementalCacheProvider == null 329 ? null 330 : IncrementalPackage.getPackagesWithRemovedFiles( 331 incrementalCacheProvider.getIncrementalCache(incrementalCacheDir), moduleId, environment.getSourceFiles()); 332 BindingTraceContext diagnosticHolder = new BindingTraceContext(); 333 GenerationState generationState = new GenerationState( 334 environment.getProject(), 335 ClassBuilderFactories.BINARIES, 336 Progress.DEAF, 337 exhaust.getModuleDescriptor(), 338 exhaust.getBindingContext(), 339 sourceFiles, 340 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false), 341 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false), 342 GenerationState.GenerateClassFilter.GENERATE_ALL, 343 configuration.get(JVMConfigurationKeys.ENABLE_INLINE, InlineCodegenUtil.DEFAULT_INLINE_FLAG), 344 packagesWithRemovedFiles, 345 moduleId, 346 diagnosticHolder 347 ); 348 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 349 AnalyzerWithCompilerReport.reportDiagnostics( 350 new FilteredJvmDiagnostics( 351 diagnosticHolder.getBindingContext().getDiagnostics(), 352 exhaust.getBindingContext().getDiagnostics() 353 ), 354 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) 355 ); 356 return generationState; 357 } 358 }