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 017package org.jetbrains.jet.cli.jvm.compiler; 018 019import com.google.common.base.Predicate; 020import com.google.common.base.Predicates; 021import com.intellij.openapi.Disposable; 022import com.intellij.openapi.project.Project; 023import com.intellij.openapi.util.Disposer; 024import com.intellij.psi.PsiFile; 025import jet.Function0; 026import jet.modules.AllModules; 027import jet.modules.Module; 028import org.jetbrains.annotations.NotNull; 029import org.jetbrains.annotations.Nullable; 030import org.jetbrains.jet.analyzer.AnalyzeExhaust; 031import org.jetbrains.jet.cli.common.CLIConfigurationKeys; 032import org.jetbrains.jet.cli.common.CompilerPlugin; 033import org.jetbrains.jet.cli.common.CompilerPluginContext; 034import org.jetbrains.jet.cli.common.messages.*; 035import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 036import org.jetbrains.jet.codegen.*; 037import org.jetbrains.jet.codegen.state.GenerationState; 038import org.jetbrains.jet.codegen.state.Progress; 039import org.jetbrains.jet.config.CommonConfigurationKeys; 040import org.jetbrains.jet.config.CompilerConfiguration; 041import org.jetbrains.jet.lang.parsing.JetScriptDefinition; 042import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider; 043import org.jetbrains.jet.lang.psi.JetFile; 044import org.jetbrains.jet.lang.psi.JetPsiUtil; 045import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; 046import org.jetbrains.jet.lang.resolve.BindingTrace; 047import org.jetbrains.jet.lang.resolve.ScriptNameUtil; 048import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM; 049import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 050import org.jetbrains.jet.lang.resolve.name.FqName; 051import org.jetbrains.jet.plugin.JetMainDetector; 052import org.jetbrains.jet.utils.ExceptionUtils; 053import org.jetbrains.jet.utils.KotlinPaths; 054import org.jetbrains.jet.utils.PathUtil; 055 056import java.io.File; 057import java.io.FileNotFoundException; 058import java.io.FileOutputStream; 059import java.io.IOException; 060import java.net.URISyntaxException; 061import java.net.URL; 062import java.net.URLClassLoader; 063import java.util.Collection; 064import java.util.Collections; 065import java.util.LinkedList; 066import java.util.List; 067 068public class KotlinToJVMBytecodeCompiler { 069 070 private KotlinToJVMBytecodeCompiler() { 071 } 072 073 @Nullable 074 public static ClassFileFactory compileModule(CompilerConfiguration configuration, Module moduleBuilder, File directory) { 075 if (moduleBuilder.getSourceFiles().isEmpty()) { 076 throw new CompileEnvironmentException("No source files where defined in module " + moduleBuilder.getModuleName()); 077 } 078 079 CompilerConfiguration compilerConfiguration = configuration.copy(); 080 for (String sourceFile : moduleBuilder.getSourceFiles()) { 081 File source = new File(sourceFile); 082 if (!source.isAbsolute()) { 083 source = new File(directory, sourceFile); 084 } 085 086 if (!source.exists()) { 087 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + moduleBuilder.getModuleName()); 088 } 089 090 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, source.getPath()); 091 } 092 093 for (String classpathRoot : moduleBuilder.getClasspathRoots()) { 094 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot)); 095 } 096 097 for (String annotationsRoot : moduleBuilder.getAnnotationsRoots()) { 098 compilerConfiguration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 099 } 100 101 Disposable parentDisposable = CompileEnvironmentUtil.createMockDisposable(); 102 JetCoreEnvironment moduleEnvironment = null; 103 try { 104 moduleEnvironment = new JetCoreEnvironment(parentDisposable, compilerConfiguration); 105 106 107 GenerationState generationState = analyzeAndGenerate(moduleEnvironment); 108 if (generationState == null) { 109 return null; 110 } 111 return generationState.getFactory(); 112 } finally { 113 if (moduleEnvironment != null) { 114 Disposer.dispose(parentDisposable); 115 } 116 } 117 } 118 119 public static boolean compileModules( 120 CompilerConfiguration configuration, 121 @NotNull List<Module> modules, 122 @NotNull File directory, 123 @Nullable File jarPath, 124 @Nullable File outputDir, 125 boolean jarRuntime) { 126 127 for (Module moduleBuilder : modules) { 128 ClassFileFactory moduleFactory = compileModule(configuration, moduleBuilder, directory); 129 if (moduleFactory == null) { 130 return false; 131 } 132 if (outputDir != null) { 133 CompileEnvironmentUtil.writeToOutputDirectory(moduleFactory, outputDir); 134 } 135 else { 136 File path = jarPath != null ? jarPath : new File(directory, moduleBuilder.getModuleName() + ".jar"); 137 FileOutputStream outputStream = null; 138 try { 139 outputStream = new FileOutputStream(path); 140 CompileEnvironmentUtil.writeToJar(moduleFactory, outputStream, null, jarRuntime); 141 outputStream.close(); 142 } 143 catch (FileNotFoundException e) { 144 throw new CompileEnvironmentException("Invalid jar path " + path, e); 145 } 146 catch (IOException e) { 147 throw ExceptionUtils.rethrow(e); 148 } 149 finally { 150 ExceptionUtils.closeQuietly(outputStream); 151 } 152 } 153 } 154 return true; 155 } 156 157 @Nullable 158 private static FqName findMainClass(@NotNull List<JetFile> files) { 159 FqName mainClass = null; 160 for (JetFile file : files) { 161 if (JetMainDetector.hasMain(file.getDeclarations())) { 162 if (mainClass != null) { 163 // more than one main 164 return null; 165 } 166 FqName fqName = JetPsiUtil.getFQName(file); 167 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 168 } 169 } 170 return mainClass; 171 } 172 173 public static boolean compileBunchOfSources( 174 JetCoreEnvironment environment, 175 @Nullable File jar, 176 @Nullable File outputDir, 177 boolean includeRuntime 178 ) { 179 180 FqName mainClass = findMainClass(environment.getSourceFiles()); 181 182 GenerationState generationState = analyzeAndGenerate(environment); 183 if (generationState == null) { 184 return false; 185 } 186 187 try { 188 ClassFileFactory factory = generationState.getFactory(); 189 if (jar != null) { 190 FileOutputStream os = null; 191 try { 192 os = new FileOutputStream(jar); 193 CompileEnvironmentUtil.writeToJar(factory, new FileOutputStream(jar), mainClass, includeRuntime); 194 os.close(); 195 } 196 catch (FileNotFoundException e) { 197 throw new CompileEnvironmentException("Invalid jar path " + jar, e); 198 } 199 catch (IOException e) { 200 throw ExceptionUtils.rethrow(e); 201 } 202 finally { 203 ExceptionUtils.closeQuietly(os); 204 } 205 } 206 else if (outputDir != null) { 207 CompileEnvironmentUtil.writeToOutputDirectory(factory, outputDir); 208 } 209 else { 210 throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk"); 211 } 212 return true; 213 } 214 finally { 215 generationState.destroy(); 216 } 217 } 218 219 public static boolean compileAndExecuteScript( 220 @NotNull KotlinPaths paths, 221 @NotNull JetCoreEnvironment environment, 222 @NotNull List<String> scriptArgs) { 223 Class<?> scriptClass = compileScript(paths, environment, null); 224 if(scriptClass == null) 225 return false; 226 227 try { 228 scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])}); 229 } 230 catch (RuntimeException e) { 231 throw e; 232 } 233 catch (Exception e) { 234 throw new RuntimeException("Failed to evaluate script: " + e, e); 235 } 236 return true; 237 } 238 239 private static Class<?> compileScript( 240 @NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment, @Nullable ClassLoader parentLoader) { 241 242 GenerationState generationState = analyzeAndGenerate(environment); 243 if (generationState == null) { 244 return null; 245 } 246 247 GeneratedClassLoader classLoader = null; 248 try { 249 ClassFileFactory factory = generationState.getFactory(); 250 classLoader = new GeneratedClassLoader(factory, 251 new URLClassLoader(new URL[] { 252 // TODO: add all classpath 253 paths.getRuntimePath().toURI().toURL() 254 }, 255 parentLoader == null ? AllModules.class.getClassLoader() : parentLoader)); 256 257 JetFile scriptFile = environment.getSourceFiles().get(0); 258 return classLoader.loadClass(ScriptNameUtil.classNameForScript(scriptFile)); 259 } 260 catch (Exception e) { 261 throw new RuntimeException("Failed to evaluate script: " + e, e); 262 } 263 finally { 264 if (classLoader != null) { 265 classLoader.dispose(); 266 } 267 generationState.destroy(); 268 } 269 } 270 271 @Nullable 272 public static GenerationState analyzeAndGenerate(JetCoreEnvironment environment) { 273 return analyzeAndGenerate(environment, environment.getConfiguration().get(JVMConfigurationKeys.STUBS, false), 274 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS)); 275 } 276 277 @Nullable 278 public static GenerationState analyzeAndGenerate( 279 JetCoreEnvironment environment, 280 boolean stubs 281 ) { 282 return analyzeAndGenerate(environment, stubs, 283 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS)); 284 } 285 286 @Nullable 287 public static GenerationState analyzeAndGenerate( 288 JetCoreEnvironment environment, 289 boolean stubs, 290 List<AnalyzerScriptParameter> scriptParameters 291 ) { 292 AnalyzeExhaust exhaust = analyze(environment, scriptParameters, stubs); 293 294 if (exhaust == null) { 295 return null; 296 } 297 298 exhaust.throwIfError(); 299 300 return generate(environment, exhaust, stubs); 301 } 302 303 @Nullable 304 private static AnalyzeExhaust analyze( 305 final JetCoreEnvironment environment, 306 final List<AnalyzerScriptParameter> scriptParameters, 307 boolean stubs) { 308 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport( 309 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)); 310 final Predicate<PsiFile> filesToAnalyzeCompletely = 311 stubs ? Predicates.<PsiFile>alwaysFalse() : Predicates.<PsiFile>alwaysTrue(); 312 analyzerWithCompilerReport.analyzeAndReport( 313 new Function0<AnalyzeExhaust>() { 314 @NotNull 315 @Override 316 public AnalyzeExhaust invoke() { 317 BindingTrace sharedTrace = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()).getTrace(); 318 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( 319 environment.getProject(), 320 environment.getSourceFiles(), 321 sharedTrace, 322 scriptParameters, 323 filesToAnalyzeCompletely, 324 false 325 ); 326 } 327 }, environment.getSourceFiles() 328 ); 329 330 return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust(); 331 } 332 333 @NotNull 334 private static GenerationState generate( 335 JetCoreEnvironment environment, 336 AnalyzeExhaust exhaust, 337 boolean stubs) { 338 Project project = environment.getProject(); 339 final CompilerConfiguration configuration = environment.getConfiguration(); 340 Progress backendProgress = new Progress() { 341 @Override 342 public void reportOutput(@NotNull Collection<File> sourceFiles, @Nullable File outputFile) { 343 if (outputFile == null) return; 344 345 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 346 if (messageCollector == null) return; 347 348 messageCollector.report( 349 CompilerMessageSeverity.OUTPUT, 350 OutputMessageUtil.formatOutputMessage(sourceFiles, outputFile), 351 CompilerMessageLocation.NO_LOCATION); 352 } 353 }; 354 GenerationState generationState = new GenerationState( 355 project, ClassBuilderFactories.binaries(stubs), backendProgress, exhaust.getBindingContext(), environment.getSourceFiles(), 356 configuration.get(JVMConfigurationKeys.BUILTIN_TO_JAVA_TYPES_MAPPING_KEY, BuiltinToJavaTypesMapping.ENABLED), 357 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false), 358 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false), 359 /*generateDeclaredClasses = */true 360 ); 361 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 362 363 CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles()); 364 for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 365 plugin.processFiles(context); 366 } 367 return generationState; 368 } 369 370 public static Class compileScript( 371 @NotNull ClassLoader parentLoader, 372 @NotNull KotlinPaths paths, 373 @NotNull String scriptPath, 374 @Nullable List<AnalyzerScriptParameter> scriptParameters, 375 @Nullable List<JetScriptDefinition> scriptDefinitions) { 376 MessageRenderer messageRenderer = MessageRenderer.PLAIN; 377 GroupingMessageCollector messageCollector = new GroupingMessageCollector(new PrintingMessageCollector(System.err, messageRenderer, false)); 378 Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable(); 379 try { 380 CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); 381 compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector); 382 compilerConfiguration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, getClasspath(parentLoader)); 383 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar()); 384 compilerConfiguration.addAll(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, Collections.singletonList( 385 paths.getJdkAnnotationsPath())); 386 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, scriptPath); 387 compilerConfiguration.addAll(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY, 388 scriptDefinitions != null ? scriptDefinitions : Collections.<JetScriptDefinition>emptyList()); 389 compilerConfiguration.put(JVMConfigurationKeys.SCRIPT_PARAMETERS, scriptParameters); 390 391 JetCoreEnvironment environment = new JetCoreEnvironment(rootDisposable, compilerConfiguration); 392 393 try { 394 JetScriptDefinitionProvider.getInstance(environment.getProject()).markFileAsScript(environment.getSourceFiles().get(0)); 395 return compileScript(paths, environment, parentLoader); 396 } 397 catch (CompilationException e) { 398 messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(e), 399 MessageUtil.psiElementToMessageLocation(e.getElement())); 400 return null; 401 } 402 catch (Throwable t) { 403 MessageCollectorUtil.reportException(messageCollector, t); 404 return null; 405 } 406 407 } 408 finally { 409 messageCollector.flush(); 410 Disposer.dispose(rootDisposable); 411 } 412 } 413 414 private static Collection<File> getClasspath(ClassLoader loader) { 415 return getClasspath(loader, new LinkedList<File>()); 416 } 417 418 private static Collection<File> getClasspath(ClassLoader loader, LinkedList<File> files) { 419 ClassLoader parent = loader.getParent(); 420 if(parent != null) 421 getClasspath(parent, files); 422 423 if(loader instanceof URLClassLoader) { 424 for (URL url : ((URLClassLoader) loader).getURLs()) { 425 String urlFile = url.getFile(); 426 427 if (urlFile.contains("%")) { 428 try { 429 urlFile = url.toURI().getPath(); 430 } 431 catch (URISyntaxException e) { 432 throw ExceptionUtils.rethrow(e); 433 } 434 } 435 436 File file = new File(urlFile); 437 if(file.exists() && (file.isDirectory() || file.getName().endsWith(".jar"))) { 438 files.add(file); 439 } 440 } 441 } 442 return files; 443 } 444}