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