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