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