001    /*
002     * Copyright 2010-2013 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.jet.cli.jvm.compiler;
018    
019    import com.intellij.openapi.Disposable;
020    import com.intellij.openapi.util.Disposer;
021    import com.intellij.openapi.util.io.FileUtil;
022    import com.intellij.openapi.util.io.FileUtilRt;
023    import com.intellij.util.Function;
024    import com.intellij.util.containers.ContainerUtil;
025    import jet.modules.AllModules;
026    import jet.modules.Module;
027    import org.jetbrains.annotations.NotNull;
028    import org.jetbrains.annotations.Nullable;
029    import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
030    import org.jetbrains.jet.cli.common.messages.MessageCollector;
031    import org.jetbrains.jet.cli.common.messages.MessageRenderer;
032    import org.jetbrains.jet.cli.common.modules.ModuleDescription;
033    import org.jetbrains.jet.cli.common.modules.ModuleXmlParser;
034    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
035    import org.jetbrains.jet.codegen.ClassFileFactory;
036    import org.jetbrains.jet.codegen.GeneratedClassLoader;
037    import org.jetbrains.jet.codegen.state.GenerationState;
038    import org.jetbrains.jet.config.CommonConfigurationKeys;
039    import org.jetbrains.jet.config.CompilerConfiguration;
040    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
041    import org.jetbrains.jet.lang.resolve.name.FqName;
042    import org.jetbrains.jet.utils.KotlinPaths;
043    import org.jetbrains.jet.utils.PathUtil;
044    
045    import java.io.File;
046    import java.io.FileInputStream;
047    import java.io.IOException;
048    import java.io.OutputStream;
049    import java.lang.reflect.Method;
050    import java.net.MalformedURLException;
051    import java.net.URL;
052    import java.net.URLClassLoader;
053    import java.util.ArrayList;
054    import java.util.Collections;
055    import java.util.List;
056    import java.util.jar.*;
057    
058    import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
059    import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERROR;
060    
061    public class CompileEnvironmentUtil {
062        public static Disposable createMockDisposable() {
063            return new Disposable() {
064                @Override
065                public void dispose() {
066                }
067            };
068        }
069    
070        @Nullable
071        private static File getRuntimeJarPath() {
072            File runtimePath = PathUtil.getKotlinPathsForCompiler().getRuntimePath();
073            return runtimePath.exists() ? runtimePath : null;
074        }
075    
076        @NotNull
077        public static List<Module> loadModuleDescriptions(KotlinPaths paths, String moduleDefinitionFile, MessageCollector messageCollector) {
078            File file = new File(moduleDefinitionFile);
079            if (!file.exists()) {
080                messageCollector.report(ERROR, "Module definition file does not exist: " + moduleDefinitionFile, NO_LOCATION);
081                return Collections.emptyList();
082            }
083            String extension = FileUtilRt.getExtension(moduleDefinitionFile);
084            if ("kts".equalsIgnoreCase(extension)) {
085                return loadModuleScript(paths, moduleDefinitionFile, messageCollector);
086            }
087            if ("xml".equalsIgnoreCase(extension)) {
088                return ContainerUtil.map(
089                        ModuleXmlParser.parse(moduleDefinitionFile, messageCollector),
090                        new Function<ModuleDescription, Module>() {
091                            @Override
092                            public Module fun(ModuleDescription description) {
093                                return new DescriptionToModuleAdapter(description);
094                            }
095                        });
096            }
097            messageCollector.report(ERROR, "Unknown module definition type: " + moduleDefinitionFile, NO_LOCATION);
098            return Collections.emptyList();
099        }
100    
101        @NotNull
102        private static List<Module> loadModuleScript(KotlinPaths paths, String moduleScriptFile, MessageCollector messageCollector) {
103            Disposable disposable = new Disposable() {
104                @Override
105                public void dispose() {
106    
107                }
108            };
109            CompilerConfiguration configuration = new CompilerConfiguration();
110            File runtimePath = paths.getRuntimePath();
111            if (runtimePath.exists()) {
112                configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, runtimePath);
113            }
114            configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar());
115            File jdkAnnotationsPath = paths.getJdkAnnotationsPath();
116            if (jdkAnnotationsPath.exists()) {
117                configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, jdkAnnotationsPath);
118            }
119            configuration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, moduleScriptFile);
120            configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector);
121    
122            List<Module> modules;
123            try {
124                JetCoreEnvironment scriptEnvironment = new JetCoreEnvironment(disposable, configuration);
125                GenerationState generationState = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(scriptEnvironment, false);
126                if (generationState == null) {
127                    throw new CompileEnvironmentException("Module script " + moduleScriptFile + " analyze failed:\n" +
128                                                          loadModuleScriptText(moduleScriptFile));
129                }
130    
131                modules = runDefineModules(paths, moduleScriptFile, generationState.getFactory());
132            }
133            finally {
134                Disposer.dispose(disposable);
135            }
136    
137            if (modules == null) {
138                throw new CompileEnvironmentException("Module script " + moduleScriptFile + " compilation failed");
139            }
140    
141            if (modules.isEmpty()) {
142                throw new CompileEnvironmentException("No modules where defined by " + moduleScriptFile);
143            }
144            return modules;
145        }
146    
147        private static List<Module> runDefineModules(KotlinPaths paths, String moduleFile, ClassFileFactory factory) {
148            File stdlibJar = paths.getRuntimePath();
149            GeneratedClassLoader loader;
150            if (stdlibJar.exists()) {
151                try {
152                    loader = new GeneratedClassLoader(factory, new URLClassLoader(new URL[]{stdlibJar.toURI().toURL()},
153                                                                                  AllModules.class.getClassLoader()));
154                }
155                catch (MalformedURLException e) {
156                    throw new RuntimeException(e);
157                }
158            }
159            else {
160                loader = new GeneratedClassLoader(factory, KotlinToJVMBytecodeCompiler.class.getClassLoader());
161            }
162            try {
163                Class namespaceClass = loader.loadClass(PackageClassUtils.getPackageClassName(FqName.ROOT));
164                Method method = namespaceClass.getDeclaredMethod("project");
165                if (method == null) {
166                    throw new CompileEnvironmentException("Module script " + moduleFile + " must define project() function");
167                }
168    
169                method.setAccessible(true);
170                method.invoke(null);
171    
172                ArrayList<Module> answer = new ArrayList<Module>(AllModules.modules.get());
173                AllModules.modules.get().clear();
174                return answer;
175            }
176            catch (Exception e) {
177                throw new ModuleExecutionException(e);
178            }
179            finally {
180                loader.dispose();
181            }
182        }
183    
184        // TODO: includeRuntime should be not a flag but a path to runtime
185        public static void writeToJar(ClassFileFactory factory, OutputStream fos, @Nullable FqName mainClass, boolean includeRuntime) {
186            try {
187                Manifest manifest = new Manifest();
188                Attributes mainAttributes = manifest.getMainAttributes();
189                mainAttributes.putValue("Manifest-Version", "1.0");
190                mainAttributes.putValue("Created-By", "JetBrains Kotlin");
191                if (mainClass != null) {
192                    mainAttributes.putValue("Main-Class", mainClass.asString());
193                }
194                JarOutputStream stream = new JarOutputStream(fos, manifest);
195                for (String file : factory.files()) {
196                    stream.putNextEntry(new JarEntry(file));
197                    stream.write(factory.asBytes(file));
198                }
199                if (includeRuntime) {
200                    writeRuntimeToJar(stream);
201                }
202                stream.finish();
203            }
204            catch (IOException e) {
205                throw new CompileEnvironmentException("Failed to generate jar file", e);
206            }
207        }
208    
209        private static void writeRuntimeToJar(final JarOutputStream stream) throws IOException {
210            File runtimeJarPath = getRuntimeJarPath();
211            if (runtimeJarPath != null) {
212                JarInputStream jis = new JarInputStream(new FileInputStream(runtimeJarPath));
213                try {
214                    while (true) {
215                        JarEntry e = jis.getNextJarEntry();
216                        if (e == null) {
217                            break;
218                        }
219                        if (FileUtilRt.extensionEquals(e.getName(), "class")) {
220                            stream.putNextEntry(e);
221                            FileUtil.copy(jis, stream);
222                        }
223                    }
224                }
225                finally {
226                    jis.close();
227                }
228            }
229            else {
230                throw new CompileEnvironmentException("Couldn't find runtime library");
231            }
232        }
233    
234        public static void writeToOutputDirectory(ClassFileFactory factory, @NotNull File outputDir) {
235            List<String> files = factory.files();
236            for (String file : files) {
237                File target = new File(outputDir, file);
238                try {
239                    FileUtil.writeToFile(target, factory.asBytes(file));
240                }
241                catch (IOException e) {
242                    throw new CompileEnvironmentException(e);
243                }
244            }
245        }
246    
247        // Used for debug output only
248        private static String loadModuleScriptText(String moduleScriptFile) {
249            String moduleScriptText;
250            try {
251                moduleScriptText = FileUtil.loadFile(new File(moduleScriptFile));
252            }
253            catch (IOException e) {
254                moduleScriptText = "Can't load module script text:\n" + MessageRenderer.PLAIN.renderException(e);
255            }
256            return moduleScriptText;
257        }
258    
259        private static class DescriptionToModuleAdapter implements Module {
260            private final ModuleDescription description;
261    
262            public DescriptionToModuleAdapter(ModuleDescription description) {
263                this.description = description;
264            }
265    
266            @Override
267            public String getModuleName() {
268                return description.getModuleName();
269            }
270    
271            @Override
272            public List<String> getSourceFiles() {
273                return description.getSourceFiles();
274            }
275    
276            @Override
277            public List<String> getClasspathRoots() {
278                return description.getClasspathRoots();
279            }
280    
281            @Override
282            public List<String> getAnnotationsRoots() {
283                return description.getAnnotationsRoots();
284            }
285        }
286    }