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 static final boolean COMPILE_CHUNK_AS_ONE_MODULE = true; 067 068 private KotlinToJVMBytecodeCompiler() { 069 } 070 071 @Nullable 072 public static ClassFileFactory compileModule(CompilerConfiguration configuration, Module module, File directory) { 073 List<String> sourceFiles = module.getSourceFiles(); 074 if (sourceFiles.isEmpty()) { 075 throw new CompileEnvironmentException("No source files where defined in module " + module.getModuleName()); 076 } 077 078 CompilerConfiguration compilerConfiguration = configuration.copy(); 079 for (String sourceFile : sourceFiles) { 080 File source = new File(sourceFile); 081 if (!source.isAbsolute()) { 082 source = new File(directory, sourceFile); 083 } 084 085 if (!source.exists()) { 086 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName()); 087 } 088 089 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, source.getPath()); 090 } 091 092 for (String classpathRoot : module.getClasspathRoots()) { 093 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot)); 094 } 095 096 for (String annotationsRoot : module.getAnnotationsRoots()) { 097 compilerConfiguration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot)); 098 } 099 100 Disposable parentDisposable = CompileEnvironmentUtil.createMockDisposable(); 101 JetCoreEnvironment moduleEnvironment = null; 102 try { 103 moduleEnvironment = new JetCoreEnvironment(parentDisposable, compilerConfiguration); 104 105 106 GenerationState generationState = analyzeAndGenerate(moduleEnvironment); 107 if (generationState == null) { 108 return null; 109 } 110 return generationState.getFactory(); 111 } finally { 112 if (moduleEnvironment != null) { 113 Disposer.dispose(parentDisposable); 114 } 115 } 116 } 117 118 private static void writeOutput( 119 CompilerConfiguration configuration, 120 ClassFileFactory moduleFactory, 121 CompileEnvironmentUtil.OutputDirector outputDir, 122 File jarPath, 123 boolean jarRuntime, 124 FqName mainClass 125 ) { 126 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE); 127 CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, moduleFactory, messageCollector); 128 } 129 130 public static boolean compileModules( 131 CompilerConfiguration configuration, 132 @NotNull final ModuleChunk chunk, 133 @NotNull File directory, 134 @Nullable File jarPath, 135 boolean jarRuntime 136 ) { 137 List<Module> modules = chunk.getModules(); 138 if (COMPILE_CHUNK_AS_ONE_MODULE && modules.size() > 1) { 139 modules = Collections.<Module>singletonList(new ChunkAsOneModule(chunk)); 140 } 141 for (Module module : modules) { 142 ClassFileFactory moduleFactory = compileModule(configuration, module, directory); 143 if (moduleFactory == null) { 144 return false; 145 } 146 CompileEnvironmentUtil.OutputDirector outputDir = new CompileEnvironmentUtil.OutputDirector() { 147 @NotNull 148 @Override 149 public File getOutputDirectory(@NotNull Collection<File> sourceFiles) { 150 for (File sourceFile : sourceFiles) { 151 // Note that here we track original modules: 152 Module module = chunk.findModuleBySourceFile(sourceFile); 153 if (module != null) { 154 return new File(module.getOutputDirectory()); 155 } 156 } 157 throw new IllegalStateException("No module found for source files: " + sourceFiles); 158 } 159 }; 160 161 writeOutput(configuration, moduleFactory, outputDir, jarPath, jarRuntime, null); 162 } 163 return true; 164 } 165 166 @Nullable 167 private static FqName findMainClass(@NotNull List<JetFile> files) { 168 FqName mainClass = null; 169 for (JetFile file : files) { 170 if (JetMainDetector.hasMain(file.getDeclarations())) { 171 if (mainClass != null) { 172 // more than one main 173 return null; 174 } 175 FqName fqName = JetPsiUtil.getFQName(file); 176 mainClass = PackageClassUtils.getPackageClassFqName(fqName); 177 } 178 } 179 return mainClass; 180 } 181 182 public static boolean compileBunchOfSources( 183 JetCoreEnvironment environment, 184 @Nullable File jar, 185 @Nullable File outputDir, 186 boolean includeRuntime 187 ) { 188 189 FqName mainClass = findMainClass(environment.getSourceFiles()); 190 191 GenerationState generationState = analyzeAndGenerate(environment); 192 if (generationState == null) { 193 return false; 194 } 195 196 try { 197 CompileEnvironmentUtil.OutputDirector outputDirector = CompileEnvironmentUtil.singleDirectory(outputDir); 198 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDirector, jar, includeRuntime, mainClass); 199 return true; 200 } 201 finally { 202 generationState.destroy(); 203 } 204 } 205 206 public static boolean compileAndExecuteScript( 207 @NotNull KotlinPaths paths, 208 @NotNull JetCoreEnvironment environment, 209 @NotNull List<String> scriptArgs) { 210 Class<?> scriptClass = compileScript(paths, environment, null); 211 if(scriptClass == null) 212 return false; 213 214 try { 215 scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])}); 216 } 217 catch (RuntimeException e) { 218 throw e; 219 } 220 catch (Exception e) { 221 throw new RuntimeException("Failed to evaluate script: " + e, e); 222 } 223 return true; 224 } 225 226 private static Class<?> compileScript( 227 @NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment, @Nullable ClassLoader parentLoader) { 228 229 GenerationState generationState = analyzeAndGenerate(environment); 230 if (generationState == null) { 231 return null; 232 } 233 234 GeneratedClassLoader classLoader = null; 235 try { 236 ClassFileFactory factory = generationState.getFactory(); 237 classLoader = new GeneratedClassLoader(factory, 238 new URLClassLoader(new URL[] { 239 // TODO: add all classpath 240 paths.getRuntimePath().toURI().toURL() 241 }, 242 parentLoader == null ? AllModules.class.getClassLoader() : parentLoader)); 243 244 JetFile scriptFile = environment.getSourceFiles().get(0); 245 return classLoader.loadClass(ScriptNameUtil.classNameForScript(scriptFile)); 246 } 247 catch (Exception e) { 248 throw new RuntimeException("Failed to evaluate script: " + e, e); 249 } 250 finally { 251 if (classLoader != null) { 252 classLoader.dispose(); 253 } 254 generationState.destroy(); 255 } 256 } 257 258 @Nullable 259 public static GenerationState analyzeAndGenerate( 260 JetCoreEnvironment environment 261 ) { 262 AnalyzeExhaust exhaust = analyze(environment); 263 264 if (exhaust == null) { 265 return null; 266 } 267 268 exhaust.throwIfError(); 269 270 return generate(environment, exhaust); 271 } 272 273 @Nullable 274 private static AnalyzeExhaust analyze(final JetCoreEnvironment environment) { 275 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport( 276 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)); 277 analyzerWithCompilerReport.analyzeAndReport( 278 new Function0<AnalyzeExhaust>() { 279 @NotNull 280 @Override 281 public AnalyzeExhaust invoke() { 282 BindingTrace sharedTrace = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()).getTrace(); 283 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( 284 environment.getProject(), 285 environment.getSourceFiles(), 286 sharedTrace, 287 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS), 288 Predicates.<PsiFile>alwaysTrue(), 289 false 290 ); 291 } 292 }, environment.getSourceFiles() 293 ); 294 295 return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust(); 296 } 297 298 @NotNull 299 private static GenerationState generate( 300 JetCoreEnvironment environment, 301 AnalyzeExhaust exhaust 302 ) { 303 Project project = environment.getProject(); 304 CompilerConfiguration configuration = environment.getConfiguration(); 305 GenerationState generationState = new GenerationState( 306 project, ClassBuilderFactories.BINARIES, Progress.DEAF, exhaust.getBindingContext(), environment.getSourceFiles(), 307 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false), 308 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false), 309 /*generateDeclaredClasses = */true 310 ); 311 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION); 312 313 CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles()); 314 for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) { 315 plugin.processFiles(context); 316 } 317 return generationState; 318 } 319 320 public static Class compileScript( 321 @NotNull ClassLoader parentLoader, 322 @NotNull KotlinPaths paths, 323 @NotNull String scriptPath, 324 @Nullable List<AnalyzerScriptParameter> scriptParameters, 325 @Nullable List<JetScriptDefinition> scriptDefinitions) { 326 MessageRenderer messageRenderer = MessageRenderer.PLAIN; 327 GroupingMessageCollector messageCollector = new GroupingMessageCollector(new PrintingMessageCollector(System.err, messageRenderer, false)); 328 Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable(); 329 try { 330 CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); 331 compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector); 332 compilerConfiguration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, getClasspath(parentLoader)); 333 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar()); 334 compilerConfiguration.addAll(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, Collections.singletonList( 335 paths.getJdkAnnotationsPath())); 336 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, scriptPath); 337 compilerConfiguration.addAll(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY, 338 scriptDefinitions != null ? scriptDefinitions : Collections.<JetScriptDefinition>emptyList()); 339 compilerConfiguration.put(JVMConfigurationKeys.SCRIPT_PARAMETERS, scriptParameters); 340 341 JetCoreEnvironment environment = new JetCoreEnvironment(rootDisposable, compilerConfiguration); 342 343 try { 344 JetScriptDefinitionProvider.getInstance(environment.getProject()).markFileAsScript(environment.getSourceFiles().get(0)); 345 return compileScript(paths, environment, parentLoader); 346 } 347 catch (CompilationException e) { 348 messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(e), 349 MessageUtil.psiElementToMessageLocation(e.getElement())); 350 return null; 351 } 352 catch (Throwable t) { 353 MessageCollectorUtil.reportException(messageCollector, t); 354 return null; 355 } 356 357 } 358 finally { 359 messageCollector.flush(); 360 Disposer.dispose(rootDisposable); 361 } 362 } 363 364 private static Collection<File> getClasspath(ClassLoader loader) { 365 return getClasspath(loader, new LinkedList<File>()); 366 } 367 368 private static Collection<File> getClasspath(ClassLoader loader, LinkedList<File> files) { 369 ClassLoader parent = loader.getParent(); 370 if(parent != null) 371 getClasspath(parent, files); 372 373 if(loader instanceof URLClassLoader) { 374 for (URL url : ((URLClassLoader) loader).getURLs()) { 375 String urlFile = url.getFile(); 376 377 if (urlFile.contains("%")) { 378 try { 379 urlFile = url.toURI().getPath(); 380 } 381 catch (URISyntaxException e) { 382 throw ExceptionUtils.rethrow(e); 383 } 384 } 385 386 File file = new File(urlFile); 387 if(file.exists() && (file.isDirectory() || file.getName().endsWith(".jar"))) { 388 files.add(file); 389 } 390 } 391 } 392 return files; 393 } 394 }