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