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
017package org.jetbrains.jet.cli.jvm.compiler;
018
019import com.intellij.openapi.Disposable;
020import com.intellij.openapi.util.Disposer;
021import com.intellij.openapi.util.io.FileUtil;
022import com.intellij.openapi.util.io.FileUtilRt;
023import com.intellij.util.Function;
024import com.intellij.util.containers.ContainerUtil;
025import jet.modules.AllModules;
026import jet.modules.Module;
027import org.jetbrains.annotations.NotNull;
028import org.jetbrains.annotations.Nullable;
029import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
030import org.jetbrains.jet.cli.common.messages.MessageCollector;
031import org.jetbrains.jet.cli.common.messages.MessageRenderer;
032import org.jetbrains.jet.cli.common.modules.ModuleDescription;
033import org.jetbrains.jet.cli.common.modules.ModuleXmlParser;
034import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
035import org.jetbrains.jet.codegen.ClassFileFactory;
036import org.jetbrains.jet.codegen.GeneratedClassLoader;
037import org.jetbrains.jet.codegen.state.GenerationState;
038import org.jetbrains.jet.config.CommonConfigurationKeys;
039import org.jetbrains.jet.config.CompilerConfiguration;
040import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
041import org.jetbrains.jet.lang.resolve.name.FqName;
042import org.jetbrains.jet.utils.KotlinPaths;
043import org.jetbrains.jet.utils.PathUtil;
044
045import java.io.File;
046import java.io.FileInputStream;
047import java.io.IOException;
048import java.io.OutputStream;
049import java.lang.reflect.Method;
050import java.net.MalformedURLException;
051import java.net.URL;
052import java.net.URLClassLoader;
053import java.util.ArrayList;
054import java.util.Collections;
055import java.util.List;
056import java.util.jar.*;
057
058import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
059import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERROR;
060
061public 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}