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