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