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.google.common.collect.Sets; 021 import com.intellij.openapi.Disposable; 022 import com.intellij.openapi.project.Project; 023 import com.intellij.openapi.util.Disposer; 024 import com.intellij.openapi.util.io.FileUtil; 025 import com.intellij.openapi.util.io.FileUtilRt; 026 import com.intellij.openapi.vfs.StandardFileSystems; 027 import com.intellij.openapi.vfs.VirtualFile; 028 import com.intellij.openapi.vfs.VirtualFileManager; 029 import com.intellij.openapi.vfs.VirtualFileSystem; 030 import com.intellij.psi.PsiFile; 031 import com.intellij.psi.PsiManager; 032 import kotlin.Function1; 033 import kotlin.Unit; 034 import kotlin.io.IoPackage; 035 import kotlin.modules.AllModules; 036 import kotlin.modules.Module; 037 import org.jetbrains.annotations.NotNull; 038 import org.jetbrains.annotations.Nullable; 039 import org.jetbrains.jet.OutputFile; 040 import org.jetbrains.jet.cli.common.CLIConfigurationKeys; 041 import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity; 042 import org.jetbrains.jet.cli.common.messages.MessageCollector; 043 import org.jetbrains.jet.cli.common.messages.OutputMessageUtil; 044 import org.jetbrains.jet.cli.common.modules.ModuleScriptData; 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.List; 068 import java.util.Set; 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 ModuleScriptData 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 ModuleScriptData.EMPTY; 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 ModuleXmlParser.parseModuleScript(moduleDefinitionFile, messageCollector); 095 } 096 messageCollector.report(ERROR, "Unknown module definition type: " + moduleDefinitionFile, NO_LOCATION); 097 return ModuleScriptData.EMPTY; 098 } 099 100 @NotNull 101 private static ModuleScriptData loadModuleScript(KotlinPaths paths, String moduleScriptFile, MessageCollector messageCollector) { 102 CompilerConfiguration configuration = new CompilerConfiguration(); 103 File runtimePath = paths.getRuntimePath(); 104 if (runtimePath.exists()) { 105 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, runtimePath); 106 } 107 configuration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.getJdkClassesRoots()); 108 File jdkAnnotationsPath = paths.getJdkAnnotationsPath(); 109 if (jdkAnnotationsPath.exists()) { 110 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, jdkAnnotationsPath); 111 } 112 configuration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, moduleScriptFile); 113 configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector); 114 115 List<Module> modules; 116 117 Disposable disposable = Disposer.newDisposable(); 118 try { 119 JetCoreEnvironment scriptEnvironment = 120 JetCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES); 121 GenerationState generationState = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(scriptEnvironment); 122 if (generationState == null) { 123 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " analyze failed:\n" + 124 loadModuleScriptText(moduleScriptFile)); 125 } 126 127 modules = runDefineModules(paths, generationState.getFactory()); 128 } 129 finally { 130 Disposer.dispose(disposable); 131 } 132 133 if (modules == null) { 134 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " compilation failed"); 135 } 136 137 if (modules.isEmpty()) { 138 throw new CompileEnvironmentException("No modules where defined by " + moduleScriptFile); 139 } 140 return new ModuleScriptData(modules); 141 } 142 143 private static List<Module> runDefineModules(KotlinPaths paths, ClassFileFactory factory) { 144 File stdlibJar = paths.getRuntimePath(); 145 GeneratedClassLoader loader; 146 if (stdlibJar.exists()) { 147 try { 148 loader = new GeneratedClassLoader(factory, new URLClassLoader(new URL[]{stdlibJar.toURI().toURL()}, 149 AllModules.class.getClassLoader())); 150 } 151 catch (MalformedURLException e) { 152 throw new RuntimeException(e); 153 } 154 } 155 else { 156 loader = new GeneratedClassLoader(factory, KotlinToJVMBytecodeCompiler.class.getClassLoader()); 157 } 158 try { 159 Class<?> packageClass = loader.loadClass(PackageClassUtils.getPackageClassName(FqName.ROOT)); 160 Method method = packageClass.getDeclaredMethod("project"); 161 162 method.setAccessible(true); 163 method.invoke(null); 164 165 ArrayList<Module> answer = new ArrayList<Module>(AllModules.INSTANCE$.get()); 166 AllModules.INSTANCE$.get().clear(); 167 return answer; 168 } 169 catch (Exception e) { 170 throw new ModuleExecutionException(e); 171 } 172 finally { 173 loader.dispose(); 174 } 175 } 176 177 // TODO: includeRuntime should be not a flag but a path to runtime 178 private static void doWriteToJar(ClassFileFactory outputFiles, OutputStream fos, @Nullable FqName mainClass, boolean includeRuntime) { 179 try { 180 Manifest manifest = new Manifest(); 181 Attributes mainAttributes = manifest.getMainAttributes(); 182 mainAttributes.putValue("Manifest-Version", "1.0"); 183 mainAttributes.putValue("Created-By", "JetBrains Kotlin"); 184 if (mainClass != null) { 185 mainAttributes.putValue("Main-Class", mainClass.asString()); 186 } 187 JarOutputStream stream = new JarOutputStream(fos, manifest); 188 for (OutputFile outputFile : outputFiles.asList()) { 189 stream.putNextEntry(new JarEntry(outputFile.getRelativePath())); 190 stream.write(outputFile.asByteArray()); 191 } 192 if (includeRuntime) { 193 writeRuntimeToJar(stream); 194 } 195 stream.finish(); 196 } 197 catch (IOException e) { 198 throw new CompileEnvironmentException("Failed to generate jar file", e); 199 } 200 } 201 202 public static void writeToJar(File jarPath, boolean jarRuntime, FqName mainClass, ClassFileFactory outputFiles) { 203 FileOutputStream outputStream = null; 204 try { 205 outputStream = new FileOutputStream(jarPath); 206 doWriteToJar(outputFiles, outputStream, mainClass, jarRuntime); 207 outputStream.close(); 208 } 209 catch (FileNotFoundException e) { 210 throw new CompileEnvironmentException("Invalid jar path " + jarPath, e); 211 } 212 catch (IOException e) { 213 throw UtilsPackage.rethrow(e); 214 } 215 finally { 216 UtilsPackage.closeQuietly(outputStream); 217 } 218 } 219 220 private static void writeRuntimeToJar(JarOutputStream stream) throws IOException { 221 File runtimeJarPath = getRuntimeJarPath(); 222 if (runtimeJarPath != null) { 223 JarInputStream jis = new JarInputStream(new FileInputStream(runtimeJarPath)); 224 try { 225 while (true) { 226 JarEntry e = jis.getNextJarEntry(); 227 if (e == null) { 228 break; 229 } 230 if (FileUtilRt.extensionEquals(e.getName(), "class")) { 231 stream.putNextEntry(e); 232 FileUtil.copy(jis, stream); 233 } 234 } 235 } 236 finally { 237 jis.close(); 238 } 239 } 240 else { 241 throw new CompileEnvironmentException("Couldn't find runtime library"); 242 } 243 } 244 245 // Used for debug output only 246 private static String loadModuleScriptText(String moduleScriptFile) { 247 try { 248 return FileUtil.loadFile(new File(moduleScriptFile)); 249 } 250 catch (IOException e) { 251 return "Can't load module script text:\n" + OutputMessageUtil.renderException(e); 252 } 253 } 254 255 static void writeOutputToDirOrJar( 256 @Nullable File jar, 257 @Nullable File outputDir, 258 boolean includeRuntime, 259 @Nullable FqName mainClass, 260 @NotNull ClassFileFactory outputFiles, 261 @NotNull MessageCollector messageCollector 262 ) { 263 if (jar != null) { 264 writeToJar(jar, includeRuntime, mainClass, outputFiles); 265 } 266 else { 267 OutputUtilsPackage.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector); 268 } 269 } 270 271 @NotNull 272 public static List<JetFile> getJetFiles( 273 @NotNull final Project project, 274 @NotNull List<String> sourceRoots, 275 @NotNull Function1<String, Unit> reportError 276 ) { 277 final VirtualFileSystem localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL); 278 279 final Set<VirtualFile> processedFiles = Sets.newHashSet(); 280 final List<JetFile> result = Lists.newArrayList(); 281 282 for (String sourceRootPath : sourceRoots) { 283 if (sourceRootPath == null) { 284 continue; 285 } 286 287 VirtualFile vFile = localFileSystem.findFileByPath(sourceRootPath); 288 if (vFile == null) { 289 reportError.invoke("Source file or directory not found: " + sourceRootPath); 290 continue; 291 } 292 if (!vFile.isDirectory() && vFile.getFileType() != JetFileType.INSTANCE) { 293 reportError.invoke("Source entry is not a Kotlin file: " + sourceRootPath); 294 continue; 295 } 296 297 IoPackage.recurse(new File(sourceRootPath), new Function1<File, Unit>() { 298 @Override 299 public Unit invoke(File file) { 300 if (file.isFile()) { 301 VirtualFile virtualFile = localFileSystem.findFileByPath(file.getAbsolutePath()); 302 if (virtualFile != null && !processedFiles.contains(virtualFile)) { 303 processedFiles.add(virtualFile); 304 PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); 305 if (psiFile instanceof JetFile) { 306 result.add((JetFile) psiFile); 307 } 308 } 309 } 310 return Unit.INSTANCE$; 311 } 312 }); 313 } 314 315 return result; 316 } 317 318 public static void addSourceFilesCheckingForDuplicates(@NotNull CompilerConfiguration configuration, @NotNull List<String> sourceRoots) { 319 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); 320 assert messageCollector != null : "messageCollector should be set: " + configuration; 321 322 Set<String> uniqueSourceRoots = Sets.newLinkedHashSet(); 323 324 for (String sourceRoot : sourceRoots) { 325 if (!uniqueSourceRoots.add(sourceRoot)) { 326 messageCollector.report( 327 CompilerMessageSeverity.WARNING, 328 "Duplicate source roots: " + sourceRoot, 329 NO_LOCATION 330 ); 331 } 332 } 333 334 configuration.put(CommonConfigurationKeys.SOURCE_ROOTS_KEY, new ArrayList<String>(uniqueSourceRoots)); 335 } 336 }