001 /* 002 * Copyright 2010-2015 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.kotlin.cli.jvm.compiler; 018 019 import com.google.common.collect.Lists; 020 import com.google.common.collect.Sets; 021 import com.intellij.openapi.project.Project; 022 import com.intellij.openapi.util.io.FileUtil; 023 import com.intellij.openapi.util.io.FileUtilRt; 024 import com.intellij.openapi.vfs.StandardFileSystems; 025 import com.intellij.openapi.vfs.VirtualFile; 026 import com.intellij.openapi.vfs.VirtualFileManager; 027 import com.intellij.openapi.vfs.VirtualFileSystem; 028 import com.intellij.psi.PsiFile; 029 import com.intellij.psi.PsiManager; 030 import kotlin.Unit; 031 import kotlin.io.FilesKt; 032 import kotlin.jvm.functions.Function1; 033 import kotlin.sequences.SequencesKt; 034 import org.jetbrains.annotations.NotNull; 035 import org.jetbrains.annotations.Nullable; 036 import org.jetbrains.kotlin.backend.common.output.OutputFile; 037 import org.jetbrains.kotlin.cli.common.messages.MessageCollector; 038 import org.jetbrains.kotlin.cli.common.modules.ModuleScriptData; 039 import org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser; 040 import org.jetbrains.kotlin.codegen.ClassFileFactory; 041 import org.jetbrains.kotlin.idea.KotlinFileType; 042 import org.jetbrains.kotlin.name.FqName; 043 import org.jetbrains.kotlin.psi.KtFile; 044 import org.jetbrains.kotlin.utils.ExceptionUtilsKt; 045 import org.jetbrains.kotlin.utils.PathUtil; 046 047 import java.io.*; 048 import java.util.Collection; 049 import java.util.List; 050 import java.util.Set; 051 import java.util.jar.*; 052 053 import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION; 054 import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR; 055 056 public class CompileEnvironmentUtil { 057 058 @NotNull 059 public static ModuleScriptData loadModuleDescriptions(String moduleDefinitionFile, MessageCollector messageCollector) { 060 File file = new File(moduleDefinitionFile); 061 if (!file.exists()) { 062 messageCollector.report(ERROR, "Module definition file does not exist: " + moduleDefinitionFile, NO_LOCATION); 063 return ModuleScriptData.EMPTY; 064 } 065 String extension = FileUtilRt.getExtension(moduleDefinitionFile); 066 if ("xml".equalsIgnoreCase(extension)) { 067 return ModuleXmlParser.parseModuleScript(moduleDefinitionFile, messageCollector); 068 } 069 messageCollector.report(ERROR, "Unknown module definition type: " + moduleDefinitionFile, NO_LOCATION); 070 return ModuleScriptData.EMPTY; 071 } 072 073 // TODO: includeRuntime should be not a flag but a path to runtime 074 private static void doWriteToJar(ClassFileFactory outputFiles, OutputStream fos, @Nullable FqName mainClass, boolean includeRuntime) { 075 try { 076 Manifest manifest = new Manifest(); 077 Attributes mainAttributes = manifest.getMainAttributes(); 078 mainAttributes.putValue("Manifest-Version", "1.0"); 079 mainAttributes.putValue("Created-By", "JetBrains Kotlin"); 080 if (mainClass != null) { 081 mainAttributes.putValue("Main-Class", mainClass.asString()); 082 } 083 JarOutputStream stream = new JarOutputStream(fos, manifest); 084 for (OutputFile outputFile : outputFiles.asList()) { 085 stream.putNextEntry(new JarEntry(outputFile.getRelativePath())); 086 stream.write(outputFile.asByteArray()); 087 } 088 if (includeRuntime) { 089 writeRuntimeToJar(stream); 090 } 091 stream.finish(); 092 } 093 catch (IOException e) { 094 throw new CompileEnvironmentException("Failed to generate jar file", e); 095 } 096 } 097 098 public static void writeToJar(File jarPath, boolean jarRuntime, FqName mainClass, ClassFileFactory outputFiles) { 099 FileOutputStream outputStream = null; 100 try { 101 outputStream = new FileOutputStream(jarPath); 102 doWriteToJar(outputFiles, outputStream, mainClass, jarRuntime); 103 outputStream.close(); 104 } 105 catch (FileNotFoundException e) { 106 throw new CompileEnvironmentException("Invalid jar path " + jarPath, e); 107 } 108 catch (IOException e) { 109 throw ExceptionUtilsKt.rethrow(e); 110 } 111 finally { 112 ExceptionUtilsKt.closeQuietly(outputStream); 113 } 114 } 115 116 private static void writeRuntimeToJar(JarOutputStream stream) throws IOException { 117 File runtimePath = PathUtil.getKotlinPathsForCompiler().getRuntimePath(); 118 if (!runtimePath.exists()) { 119 throw new CompileEnvironmentException("Couldn't find runtime library"); 120 } 121 122 JarInputStream jis = new JarInputStream(new FileInputStream(runtimePath)); 123 try { 124 while (true) { 125 JarEntry e = jis.getNextJarEntry(); 126 if (e == null) { 127 break; 128 } 129 if (FileUtilRt.extensionEquals(e.getName(), "class")) { 130 stream.putNextEntry(e); 131 FileUtil.copy(jis, stream); 132 } 133 } 134 } 135 finally { 136 jis.close(); 137 } 138 } 139 140 @NotNull 141 public static List<KtFile> getKtFiles( 142 @NotNull final Project project, 143 @NotNull Collection<String> sourceRoots, 144 @NotNull Function1<String, Unit> reportError 145 ) { 146 final VirtualFileSystem localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL); 147 148 final Set<VirtualFile> processedFiles = Sets.newHashSet(); 149 final List<KtFile> result = Lists.newArrayList(); 150 151 for (String sourceRootPath : sourceRoots) { 152 if (sourceRootPath == null) { 153 continue; 154 } 155 156 VirtualFile vFile = localFileSystem.findFileByPath(sourceRootPath); 157 if (vFile == null) { 158 reportError.invoke("Source file or directory not found: " + sourceRootPath); 159 continue; 160 } 161 if (!vFile.isDirectory() && vFile.getFileType() != KotlinFileType.INSTANCE) { 162 reportError.invoke("Source entry is not a Kotlin file: " + sourceRootPath); 163 continue; 164 } 165 166 SequencesKt.forEach(FilesKt.walkTopDown(new File(sourceRootPath)), new Function1<File, Unit>() { 167 @Override 168 public Unit invoke(File file) { 169 if (file.isFile()) { 170 VirtualFile virtualFile = localFileSystem.findFileByPath(file.getAbsolutePath()); 171 if (virtualFile != null && !processedFiles.contains(virtualFile)) { 172 processedFiles.add(virtualFile); 173 PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); 174 if (psiFile instanceof KtFile) { 175 result.add((KtFile) psiFile); 176 } 177 } 178 } 179 return Unit.INSTANCE; 180 } 181 }); 182 } 183 184 return result; 185 } 186 }