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