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.*; 034 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 035 import org.jetbrains.jet.codegen.*; 036 import org.jetbrains.jet.codegen.state.GenerationState; 037 import org.jetbrains.jet.codegen.state.Progress; 038 import org.jetbrains.jet.config.CommonConfigurationKeys; 039 import org.jetbrains.jet.config.CompilerConfiguration; 040 import org.jetbrains.jet.lang.parsing.JetScriptDefinition; 041 import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider; 042 import org.jetbrains.jet.lang.psi.JetFile; 043 import org.jetbrains.jet.lang.psi.JetPsiUtil; 044 import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; 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.ExceptionUtils; 052 import org.jetbrains.jet.utils.KotlinPaths; 053 import org.jetbrains.jet.utils.PathUtil; 054 055 import java.io.File; 056 import java.net.URISyntaxException; 057 import java.net.URL; 058 import java.net.URLClassLoader; 059 import java.util.Collection; 060 import java.util.Collections; 061 import java.util.LinkedList; 062 import java.util.List; 063 064 public class KotlinToJVMBytecodeCompiler { 065 066 private KotlinToJVMBytecodeCompiler() { 067 } 068 069 @Nullable 070 public static ClassFileFactory compileModule(CompilerConfiguration configuration, Module module, File directory) { 071 if (module.getSourceFiles().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 : module.getSourceFiles()) { 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 = CompileEnvironmentUtil.createMockDisposable(); 098 JetCoreEnvironment moduleEnvironment = null; 099 try { 100 moduleEnvironment = new JetCoreEnvironment(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 moduleFactory, 118 CompileEnvironmentUtil.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, moduleFactory, messageCollector); 125 } 126 127 public static boolean compileModules( 128 CompilerConfiguration configuration, 129 @NotNull final ModuleChunk modules, 130 @NotNull File directory, 131 @Nullable File jarPath, 132 boolean jarRuntime 133 ) { 134 for (Module module : modules.getModules()) { 135 ClassFileFactory moduleFactory = compileModule(configuration, module, directory); 136 if (moduleFactory == null) { 137 return false; 138 } 139 CompileEnvironmentUtil.OutputDirector outputDir = new CompileEnvironmentUtil.OutputDirector() { 140 @NotNull 141 @Override 142 public File getOutputDirectory(@NotNull Collection<File> sourceFiles) { 143 for (File sourceFile : sourceFiles) { 144 Module module = modules.findModuleBySourceFile(sourceFile); 145 if (module != null) { 146 return new File(module.getOutputDirectory()); 147 } 148 } 149 throw new IllegalStateException("No module found for source files: " + sourceFiles); 150 } 151 }; 152 153 writeOutput(configuration, moduleFactory, outputDir, jarPath, jarRuntime, null); 154 } 155 return true; 156 } 157 158 @Nullable 159 private static FqName findMainClass(@NotNull List<JetFile> files) { 160 FqName mainClass = null; 161 for (JetFile file : files) { 162 if (JetMainDetector.hasMain(file.getDeclarations())) { 163 if (mainClass != null) { 164 // more than one main 165 return null; 166 } 167 FqName fqName = JetPsiUtil.getFQName(file); 168 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 169 } 170 } 171 return mainClass; 172 } 173 174 public static boolean compileBunchOfSources( 175 JetCoreEnvironment environment, 176 @Nullable File jar, 177 @Nullable File outputDir, 178 boolean includeRuntime 179 ) { 180 181 FqName mainClass = findMainClass(environment.getSourceFiles()); 182 183 GenerationState generationState = analyzeAndGenerate(environment); 184 if (generationState == null) { 185 return false; 186 } 187 188 try { 189 CompileEnvironmentUtil.OutputDirector outputDirector = CompileEnvironmentUtil.singleDirectory(outputDir); 190 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDirector, jar, includeRuntime, mainClass); 191 return true; 192 } 193 finally { 194 generationState.destroy(); 195 } 196 } 197 198 public static boolean compileAndExecuteScript( 199 @NotNull KotlinPaths paths, 200 @NotNull JetCoreEnvironment environment, 201 @NotNull List<String> scriptArgs) { 202 Class<?> scriptClass = compileScript(paths, environment, null); 203 if(scriptClass == null) 204 return false; 205 206 try { 207 scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])}); 208 } 209 catch (RuntimeException e) { 210 throw e; 211 } 212 catch (Exception e) { 213 throw new RuntimeException("Failed to evaluate script: " + e, e); 214 } 215 return true; 216 } 217 218 private static Class<?> compileScript( 219 @NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment, @Nullable ClassLoader parentLoader) { 220 221 GenerationState generationState = analyzeAndGenerate(environment); 222 if (generationState == null) { 223 return null; 224 } 225 226 GeneratedClassLoader classLoader = null; 227 try { 228 ClassFileFactory factory = generationState.getFactory(); 229 classLoader = new GeneratedClassLoader(factory, 230 new URLClassLoader(new URL[] { 231 // TODO: add all classpath 232 paths.getRuntimePath().toURI().toURL() 233 }, 234 parentLoader == null ? AllModules.class.getClassLoader() : parentLoader)); 235 236 JetFile scriptFile = environment.getSourceFiles().get(0); 237 return classLoader.loadClass(ScriptNameUtil.classNameForScript(scriptFile)); 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 generationState.destroy(); 247 } 248 } 249 250 @Nullable 251 public static GenerationState analyzeAndGenerate( 252 JetCoreEnvironment environment 253 ) { 254 AnalyzeExhaust exhaust = analyze(environment); 255 256 if (exhaust == null) { 257 return null; 258 } 259 260 exhaust.throwIfError(); 261 262 return generate(environment, exhaust); 263 } 264 265 @Nullable 266 private static AnalyzeExhaust analyze(final JetCoreEnvironment environment) { 267 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport( 268 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)); 269 analyzerWithCompilerReport.analyzeAndReport( 270 new Function0<AnalyzeExhaust>() { 271 @NotNull 272 @Override 273 public AnalyzeExhaust invoke() { 274 BindingTrace sharedTrace = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()).getTrace(); 275 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( 276 environment.getProject(), 277 environment.getSourceFiles(), 278 sharedTrace, 279 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS), 280 Predicates.<PsiFile>alwaysTrue(), 281 false 282 ); 283 } 284 }, environment.getSourceFiles() 285 ); 286 287 return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust(); 288 } 289 290 @NotNull 291 private static GenerationState generate( 292 JetCoreEnvironment environment, 293 AnalyzeExhaust exhaust 294 ) { 295 Project project = environment.getProject(); 296 CompilerConfiguration configuration = environment.getConfiguration(); 297 GenerationState generationState = new GenerationState( 298 project, ClassBuilderFactories.BINARIES, Progress.DEAF, exhaust.getBindingContext(), environment.getSourceFiles(), 299 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false), 300 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false), 301 /*generateDeclaredClasses = */true 302 ); 303 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 304 305 CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles()); 306 for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 307 plugin.processFiles(context); 308 } 309 return generationState; 310 } 311 312 public static Class compileScript( 313 @NotNull ClassLoader parentLoader, 314 @NotNull KotlinPaths paths, 315 @NotNull String scriptPath, 316 @Nullable List<AnalyzerScriptParameter> scriptParameters, 317 @Nullable List<JetScriptDefinition> scriptDefinitions) { 318 MessageRenderer messageRenderer = MessageRenderer.PLAIN; 319 GroupingMessageCollector messageCollector = new GroupingMessageCollector(new PrintingMessageCollector(System.err, messageRenderer, false)); 320 Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable(); 321 try { 322 CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); 323 compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector); 324 compilerConfiguration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, getClasspath(parentLoader)); 325 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar()); 326 compilerConfiguration.addAll(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, Collections.singletonList( 327 paths.getJdkAnnotationsPath())); 328 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, scriptPath); 329 compilerConfiguration.addAll(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY, 330 scriptDefinitions != null ? scriptDefinitions : Collections.<JetScriptDefinition>emptyList()); 331 compilerConfiguration.put(JVMConfigurationKeys.SCRIPT_PARAMETERS, scriptParameters); 332 333 JetCoreEnvironment environment = new JetCoreEnvironment(rootDisposable, compilerConfiguration); 334 335 try { 336 JetScriptDefinitionProvider.getInstance(environment.getProject()).markFileAsScript(environment.getSourceFiles().get(0)); 337 return compileScript(paths, environment, parentLoader); 338 } 339 catch (CompilationException e) { 340 messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(e), 341 MessageUtil.psiElementToMessageLocation(e.getElement())); 342 return null; 343 } 344 catch (Throwable t) { 345 MessageCollectorUtil.reportException(messageCollector, t); 346 return null; 347 } 348 349 } 350 finally { 351 messageCollector.flush(); 352 Disposer.dispose(rootDisposable); 353 } 354 } 355 356 private static Collection<File> getClasspath(ClassLoader loader) { 357 return getClasspath(loader, new LinkedList<File>()); 358 } 359 360 private static Collection<File> getClasspath(ClassLoader loader, LinkedList<File> files) { 361 ClassLoader parent = loader.getParent(); 362 if(parent != null) 363 getClasspath(parent, files); 364 365 if(loader instanceof URLClassLoader) { 366 for (URL url : ((URLClassLoader) loader).getURLs()) { 367 String urlFile = url.getFile(); 368 369 if (urlFile.contains("%")) { 370 try { 371 urlFile = url.toURI().getPath(); 372 } 373 catch (URISyntaxException e) { 374 throw ExceptionUtils.rethrow(e); 375 } 376 } 377 378 File file = new File(urlFile); 379 if(file.exists() && (file.isDirectory() || file.getName().endsWith(".jar"))) { 380 files.add(file); 381 } 382 } 383 } 384 return files; 385 } 386 }