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.cli.jvm.config.JVMConfigurationKeys;
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(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    
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    }