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