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.collect.Lists; 020 import com.intellij.openapi.Disposable; 021 import com.intellij.openapi.project.Project; 022 import com.intellij.openapi.util.Disposer; 023 import com.intellij.openapi.util.io.FileUtil; 024 import com.intellij.openapi.util.io.FileUtilRt; 025 import com.intellij.openapi.vfs.StandardFileSystems; 026 import com.intellij.openapi.vfs.VirtualFile; 027 import com.intellij.openapi.vfs.VirtualFileManager; 028 import com.intellij.openapi.vfs.VirtualFileSystem; 029 import com.intellij.psi.PsiFile; 030 import com.intellij.psi.PsiManager; 031 import com.intellij.util.Function; 032 import com.intellij.util.containers.ContainerUtil; 033 import kotlin.Function1; 034 import kotlin.Unit; 035 import kotlin.io.IoPackage; 036 import kotlin.modules.AllModules; 037 import kotlin.modules.Module; 038 import org.jetbrains.annotations.NotNull; 039 import org.jetbrains.annotations.Nullable; 040 import org.jetbrains.jet.OutputFile; 041 import org.jetbrains.jet.cli.common.CLIConfigurationKeys; 042 import org.jetbrains.jet.cli.common.messages.MessageCollector; 043 import org.jetbrains.jet.cli.common.messages.MessageRenderer; 044 import org.jetbrains.jet.cli.common.modules.ModuleDescription; 045 import org.jetbrains.jet.cli.common.modules.ModuleXmlParser; 046 import org.jetbrains.jet.cli.common.output.outputUtils.OutputUtilsPackage; 047 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 048 import org.jetbrains.jet.codegen.ClassFileFactory; 049 import org.jetbrains.jet.codegen.GeneratedClassLoader; 050 import org.jetbrains.jet.codegen.state.GenerationState; 051 import org.jetbrains.jet.config.CommonConfigurationKeys; 052 import org.jetbrains.jet.config.CompilerConfiguration; 053 import org.jetbrains.jet.lang.psi.JetFile; 054 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 055 import org.jetbrains.jet.lang.resolve.name.FqName; 056 import org.jetbrains.jet.plugin.JetFileType; 057 import org.jetbrains.jet.utils.KotlinPaths; 058 import org.jetbrains.jet.utils.PathUtil; 059 import org.jetbrains.jet.utils.UtilsPackage; 060 061 import java.io.*; 062 import java.lang.reflect.Method; 063 import java.net.MalformedURLException; 064 import java.net.URL; 065 import java.net.URLClassLoader; 066 import java.util.ArrayList; 067 import java.util.Collections; 068 import java.util.List; 069 import java.util.jar.*; 070 071 import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION; 072 import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERROR; 073 074 public class CompileEnvironmentUtil { 075 076 @Nullable 077 private static File getRuntimeJarPath() { 078 File runtimePath = PathUtil.getKotlinPathsForCompiler().getRuntimePath(); 079 return runtimePath.exists() ? runtimePath : null; 080 } 081 082 @NotNull 083 public static List<Module> loadModuleDescriptions(KotlinPaths paths, String moduleDefinitionFile, MessageCollector messageCollector) { 084 File file = new File(moduleDefinitionFile); 085 if (!file.exists()) { 086 messageCollector.report(ERROR, "Module definition file does not exist: " + moduleDefinitionFile, NO_LOCATION); 087 return Collections.emptyList(); 088 } 089 String extension = FileUtilRt.getExtension(moduleDefinitionFile); 090 if ("ktm".equalsIgnoreCase(extension)) { 091 return loadModuleScript(paths, moduleDefinitionFile, messageCollector); 092 } 093 if ("xml".equalsIgnoreCase(extension)) { 094 return ContainerUtil.map( 095 ModuleXmlParser.parse(moduleDefinitionFile, messageCollector), 096 new Function<ModuleDescription, Module>() { 097 @Override 098 public Module fun(ModuleDescription description) { 099 return new DescriptionToModuleAdapter(description); 100 } 101 }); 102 } 103 messageCollector.report(ERROR, "Unknown module definition type: " + moduleDefinitionFile, NO_LOCATION); 104 return Collections.emptyList(); 105 } 106 107 @NotNull 108 private static List<Module> loadModuleScript(KotlinPaths paths, String moduleScriptFile, MessageCollector messageCollector) { 109 CompilerConfiguration configuration = new CompilerConfiguration(); 110 File runtimePath = paths.getRuntimePath(); 111 if (runtimePath.exists()) { 112 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, runtimePath); 113 } 114 configuration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.getJdkClassesRoots()); 115 File jdkAnnotationsPath = paths.getJdkAnnotationsPath(); 116 if (jdkAnnotationsPath.exists()) { 117 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, jdkAnnotationsPath); 118 } 119 configuration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, moduleScriptFile); 120 configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector); 121 122 List<Module> modules; 123 124 Disposable disposable = Disposer.newDisposable(); 125 try { 126 JetCoreEnvironment scriptEnvironment = JetCoreEnvironment.createForProduction(disposable, configuration); 127 GenerationState generationState = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(scriptEnvironment); 128 if (generationState == null) { 129 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " analyze failed:\n" + 130 loadModuleScriptText(moduleScriptFile)); 131 } 132 133 modules = runDefineModules(paths, generationState.getFactory()); 134 } 135 finally { 136 Disposer.dispose(disposable); 137 } 138 139 if (modules == null) { 140 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " compilation failed"); 141 } 142 143 if (modules.isEmpty()) { 144 throw new CompileEnvironmentException("No modules where defined by " + moduleScriptFile); 145 } 146 return modules; 147 } 148 149 private static List<Module> runDefineModules(KotlinPaths paths, ClassFileFactory factory) { 150 File stdlibJar = paths.getRuntimePath(); 151 GeneratedClassLoader loader; 152 if (stdlibJar.exists()) { 153 try { 154 loader = new GeneratedClassLoader(factory, new URLClassLoader(new URL[]{stdlibJar.toURI().toURL()}, 155 AllModules.class.getClassLoader())); 156 } 157 catch (MalformedURLException e) { 158 throw new RuntimeException(e); 159 } 160 } 161 else { 162 loader = new GeneratedClassLoader(factory, KotlinToJVMBytecodeCompiler.class.getClassLoader()); 163 } 164 try { 165 Class<?> packageClass = loader.loadClass(PackageClassUtils.getPackageClassName(FqName.ROOT)); 166 Method method = packageClass.getDeclaredMethod("project"); 167 168 method.setAccessible(true); 169 method.invoke(null); 170 171 ArrayList<Module> answer = new ArrayList<Module>(AllModules.instance$.get()); 172 AllModules.instance$.get().clear(); 173 return answer; 174 } 175 catch (Exception e) { 176 throw new ModuleExecutionException(e); 177 } 178 finally { 179 loader.dispose(); 180 } 181 } 182 183 // TODO: includeRuntime should be not a flag but a path to runtime 184 private static void doWriteToJar(ClassFileFactory outputFiles, OutputStream fos, @Nullable FqName mainClass, boolean includeRuntime) { 185 try { 186 Manifest manifest = new Manifest(); 187 Attributes mainAttributes = manifest.getMainAttributes(); 188 mainAttributes.putValue("Manifest-Version", "1.0"); 189 mainAttributes.putValue("Created-By", "JetBrains Kotlin"); 190 if (mainClass != null) { 191 mainAttributes.putValue("Main-Class", mainClass.asString()); 192 } 193 JarOutputStream stream = new JarOutputStream(fos, manifest); 194 for (OutputFile outputFile : outputFiles.asList()) { 195 stream.putNextEntry(new JarEntry(outputFile.getRelativePath())); 196 stream.write(outputFile.asByteArray()); 197 } 198 if (includeRuntime) { 199 writeRuntimeToJar(stream); 200 } 201 stream.finish(); 202 } 203 catch (IOException e) { 204 throw new CompileEnvironmentException("Failed to generate jar file", e); 205 } 206 } 207 208 public static void writeToJar(File jarPath, boolean jarRuntime, FqName mainClass, ClassFileFactory outputFiles) { 209 FileOutputStream outputStream = null; 210 try { 211 outputStream = new FileOutputStream(jarPath); 212 doWriteToJar(outputFiles, outputStream, mainClass, jarRuntime); 213 outputStream.close(); 214 } 215 catch (FileNotFoundException e) { 216 throw new CompileEnvironmentException("Invalid jar path " + jarPath, e); 217 } 218 catch (IOException e) { 219 throw UtilsPackage.rethrow(e); 220 } 221 finally { 222 UtilsPackage.closeQuietly(outputStream); 223 } 224 } 225 226 private static void writeRuntimeToJar(JarOutputStream stream) throws IOException { 227 File runtimeJarPath = getRuntimeJarPath(); 228 if (runtimeJarPath != null) { 229 JarInputStream jis = new JarInputStream(new FileInputStream(runtimeJarPath)); 230 try { 231 while (true) { 232 JarEntry e = jis.getNextJarEntry(); 233 if (e == null) { 234 break; 235 } 236 if (FileUtilRt.extensionEquals(e.getName(), "class")) { 237 stream.putNextEntry(e); 238 FileUtil.copy(jis, stream); 239 } 240 } 241 } 242 finally { 243 jis.close(); 244 } 245 } 246 else { 247 throw new CompileEnvironmentException("Couldn't find runtime library"); 248 } 249 } 250 251 // Used for debug output only 252 private static String loadModuleScriptText(String moduleScriptFile) { 253 String moduleScriptText; 254 try { 255 moduleScriptText = FileUtil.loadFile(new File(moduleScriptFile)); 256 } 257 catch (IOException e) { 258 moduleScriptText = "Can't load module script text:\n" + MessageRenderer.PLAIN.renderException(e); 259 } 260 return moduleScriptText; 261 } 262 263 static void writeOutputToDirOrJar( 264 @Nullable File jar, 265 @Nullable File outputDir, 266 boolean includeRuntime, 267 @Nullable FqName mainClass, 268 @NotNull ClassFileFactory outputFiles, 269 @NotNull MessageCollector messageCollector 270 ) { 271 if (jar != null) { 272 writeToJar(jar, includeRuntime, mainClass, outputFiles); 273 } 274 else if (outputDir != null) { 275 OutputUtilsPackage.writeAll(outputFiles, outputDir, messageCollector); 276 } 277 else { 278 throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk"); 279 } 280 } 281 282 @NotNull 283 public static List<JetFile> getJetFiles( 284 @NotNull final Project project, 285 @NotNull List<String> sourceRoots, 286 @NotNull Function1<String, Unit> reportError 287 ) { 288 final VirtualFileSystem localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL); 289 290 final List<JetFile> result = Lists.newArrayList(); 291 292 for (String sourceRootPath : sourceRoots) { 293 if (sourceRootPath == null) { 294 continue; 295 } 296 297 VirtualFile vFile = localFileSystem.findFileByPath(sourceRootPath); 298 if (vFile == null) { 299 reportError.invoke("Source file or directory not found: " + sourceRootPath); 300 continue; 301 } 302 if (!vFile.isDirectory() && vFile.getFileType() != JetFileType.INSTANCE) { 303 reportError.invoke("Source entry is not a Kotlin file: " + sourceRootPath); 304 continue; 305 } 306 307 IoPackage.recurse(new File(sourceRootPath), new Function1<File, Unit>() { 308 @Override 309 public Unit invoke(File file) { 310 if (file.isFile()) { 311 VirtualFile fileByPath = localFileSystem.findFileByPath(file.getAbsolutePath()); 312 if (fileByPath != null) { 313 PsiFile psiFile = PsiManager.getInstance(project).findFile(fileByPath); 314 if (psiFile instanceof JetFile) { 315 result.add((JetFile) psiFile); 316 } 317 } 318 } 319 return Unit.VALUE; 320 } 321 }); 322 } 323 324 return result; 325 } 326 327 private static class DescriptionToModuleAdapter implements Module { 328 private final ModuleDescription description; 329 330 public DescriptionToModuleAdapter(ModuleDescription description) { 331 this.description = description; 332 } 333 334 @NotNull 335 @Override 336 public String getModuleName() { 337 return description.getModuleName(); 338 } 339 340 @NotNull 341 @Override 342 public String getOutputDirectory() { 343 return description.getOutputDir(); 344 } 345 346 @NotNull 347 @Override 348 public List<String> getSourceFiles() { 349 return description.getSourceFiles(); 350 } 351 352 @NotNull 353 @Override 354 public List<String> getClasspathRoots() { 355 return description.getClasspathRoots(); 356 } 357 358 @NotNull 359 @Override 360 public List<String> getAnnotationsRoots() { 361 return description.getAnnotationsRoots(); 362 } 363 } 364 }