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