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.backend.common.output.OutputFileCollection;
039 import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
040 import org.jetbrains.kotlin.cli.common.modules.ModuleScriptData;
041 import org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser;
042 import org.jetbrains.kotlin.config.CompilerConfiguration;
043 import org.jetbrains.kotlin.config.JVMConfigurationKeys;
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 final 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(OutputFileCollection 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, OutputFileCollection 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 File scriptRuntimePath = PathUtil.getKotlinPathsForCompiler().getScriptRuntimePath();
126 if (!scriptRuntimePath.exists()) {
127 throw new CompileEnvironmentException("Couldn't find script runtime library");
128 }
129
130 copyJarImpl(stream, runtimePath);
131 }
132
133 private static void copyJarImpl(JarOutputStream stream, File jarPath) throws IOException {
134 JarInputStream jis = new JarInputStream(new FileInputStream(jarPath));
135 try {
136 while (true) {
137 JarEntry e = jis.getNextJarEntry();
138 if (e == null) {
139 break;
140 }
141 if (FileUtilRt.extensionEquals(e.getName(), "class")) {
142 stream.putNextEntry(e);
143 FileUtil.copy(jis, stream);
144 }
145 }
146 }
147 finally {
148 jis.close();
149 }
150 }
151
152 @NotNull
153 public static List<KtFile> getKtFiles(
154 @NotNull final Project project,
155 @NotNull Collection<String> sourceRoots,
156 @NotNull CompilerConfiguration configuration,
157 @NotNull Function1<String, Unit> reportError
158 ) throws IOException {
159 final VirtualFileSystem localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL);
160
161 final Set<VirtualFile> processedFiles = Sets.newHashSet();
162 final List<KtFile> result = Lists.newArrayList();
163
164 for (String sourceRootPath : sourceRoots) {
165 if (sourceRootPath == null) {
166 continue;
167 }
168
169 VirtualFile vFile = localFileSystem.findFileByPath(sourceRootPath);
170 if (vFile == null) {
171 String message = "Source file or directory not found: " + sourceRootPath;
172
173 File moduleFilePath = configuration.get(JVMConfigurationKeys.MODULE_XML_FILE);
174 if (moduleFilePath != null) {
175 String moduleFileContent = FileUtil.loadFile(moduleFilePath);
176 LOG.warn(message +
177 "\n\nmodule file path: " + moduleFilePath +
178 "\ncontent:\n" + moduleFileContent);
179 }
180
181 reportError.invoke(message);
182 continue;
183 }
184 if (!vFile.isDirectory() && vFile.getFileType() != KotlinFileType.INSTANCE) {
185 reportError.invoke("Source entry is not a Kotlin file: " + sourceRootPath);
186 continue;
187 }
188
189 SequencesKt.forEach(FilesKt.walkTopDown(new File(sourceRootPath)), new Function1<File, Unit>() {
190 @Override
191 public Unit invoke(File file) {
192 if (file.isFile()) {
193 VirtualFile virtualFile = localFileSystem.findFileByPath(file.getAbsolutePath());
194 if (virtualFile != null && !processedFiles.contains(virtualFile)) {
195 processedFiles.add(virtualFile);
196 PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
197 if (psiFile instanceof KtFile) {
198 result.add((KtFile) psiFile);
199 }
200 }
201 }
202 return Unit.INSTANCE;
203 }
204 });
205 }
206
207 return result;
208 }
209 }