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    }