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.google.common.base.Predicates;
020    import com.intellij.openapi.Disposable;
021    import com.intellij.openapi.project.Project;
022    import com.intellij.openapi.util.Disposer;
023    import com.intellij.psi.PsiFile;
024    import jet.Function0;
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.analyzer.AnalyzeExhaust;
030    import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
031    import org.jetbrains.jet.cli.common.CompilerPlugin;
032    import org.jetbrains.jet.cli.common.CompilerPluginContext;
033    import org.jetbrains.jet.cli.common.messages.*;
034    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
035    import org.jetbrains.jet.codegen.*;
036    import org.jetbrains.jet.codegen.state.GenerationState;
037    import org.jetbrains.jet.codegen.state.Progress;
038    import org.jetbrains.jet.config.CommonConfigurationKeys;
039    import org.jetbrains.jet.config.CompilerConfiguration;
040    import org.jetbrains.jet.lang.parsing.JetScriptDefinition;
041    import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider;
042    import org.jetbrains.jet.lang.psi.JetFile;
043    import org.jetbrains.jet.lang.psi.JetPsiUtil;
044    import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter;
045    import org.jetbrains.jet.lang.resolve.BindingTrace;
046    import org.jetbrains.jet.lang.resolve.ScriptNameUtil;
047    import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
048    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
049    import org.jetbrains.jet.lang.resolve.name.FqName;
050    import org.jetbrains.jet.plugin.JetMainDetector;
051    import org.jetbrains.jet.utils.ExceptionUtils;
052    import org.jetbrains.jet.utils.KotlinPaths;
053    import org.jetbrains.jet.utils.PathUtil;
054    
055    import java.io.File;
056    import java.net.URISyntaxException;
057    import java.net.URL;
058    import java.net.URLClassLoader;
059    import java.util.Collection;
060    import java.util.Collections;
061    import java.util.LinkedList;
062    import java.util.List;
063    
064    public class KotlinToJVMBytecodeCompiler {
065    
066        private KotlinToJVMBytecodeCompiler() {
067        }
068    
069        @Nullable
070        public static ClassFileFactory compileModule(CompilerConfiguration configuration, Module module, File directory) {
071            if (module.getSourceFiles().isEmpty()) {
072                throw new CompileEnvironmentException("No source files where defined in module " + module.getModuleName());
073            }
074    
075            CompilerConfiguration compilerConfiguration = configuration.copy();
076            for (String sourceFile : module.getSourceFiles()) {
077                File source = new File(sourceFile);
078                if (!source.isAbsolute()) {
079                    source = new File(directory, sourceFile);
080                }
081    
082                if (!source.exists()) {
083                    throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName());
084                }
085    
086                compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, source.getPath());
087            }
088    
089            for (String classpathRoot : module.getClasspathRoots()) {
090                compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
091            }
092    
093            for (String annotationsRoot : module.getAnnotationsRoots()) {
094                compilerConfiguration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
095            }
096    
097            Disposable parentDisposable = CompileEnvironmentUtil.createMockDisposable();
098            JetCoreEnvironment moduleEnvironment = null;
099            try {
100                moduleEnvironment = new JetCoreEnvironment(parentDisposable, compilerConfiguration);
101    
102    
103                GenerationState generationState = analyzeAndGenerate(moduleEnvironment);
104                if (generationState == null) {
105                    return null;
106                }
107                return generationState.getFactory();
108            } finally {
109                if (moduleEnvironment != null) {
110                    Disposer.dispose(parentDisposable);
111                }
112            }
113        }
114    
115        private static void writeOutput(
116                CompilerConfiguration configuration,
117                ClassFileFactory moduleFactory,
118                CompileEnvironmentUtil.OutputDirector outputDir,
119                File jarPath,
120                boolean jarRuntime,
121                FqName mainClass
122        ) {
123            MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
124            CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, moduleFactory, messageCollector);
125        }
126    
127        public static boolean compileModules(
128                CompilerConfiguration configuration,
129                @NotNull final ModuleChunk modules,
130                @NotNull File directory,
131                @Nullable File jarPath,
132                boolean jarRuntime
133        ) {
134            for (Module module : modules.getModules()) {
135                ClassFileFactory moduleFactory = compileModule(configuration, module, directory);
136                if (moduleFactory == null) {
137                    return false;
138                }
139                CompileEnvironmentUtil.OutputDirector outputDir = new CompileEnvironmentUtil.OutputDirector() {
140                    @NotNull
141                    @Override
142                    public File getOutputDirectory(@NotNull Collection<File> sourceFiles) {
143                        for (File sourceFile : sourceFiles) {
144                            Module module = modules.findModuleBySourceFile(sourceFile);
145                            if (module != null) {
146                                return new File(module.getOutputDirectory());
147                            }
148                        }
149                        throw new IllegalStateException("No module found for source files: " + sourceFiles);
150                    }
151                };
152    
153                writeOutput(configuration, moduleFactory, outputDir, jarPath, jarRuntime, null);
154            }
155            return true;
156        }
157    
158        @Nullable
159        private static FqName findMainClass(@NotNull List<JetFile> files) {
160            FqName mainClass = null;
161            for (JetFile file : files) {
162                if (JetMainDetector.hasMain(file.getDeclarations())) {
163                    if (mainClass != null) {
164                        // more than one main
165                        return null;
166                    }
167                    FqName fqName = JetPsiUtil.getFQName(file);
168                    mainClass = PackageClassUtils.getPackageClassFqName(fqName);
169                }
170            }
171            return mainClass;
172        }
173    
174        public static boolean compileBunchOfSources(
175                JetCoreEnvironment environment,
176                @Nullable File jar,
177                @Nullable File outputDir,
178                boolean includeRuntime
179        ) {
180    
181            FqName mainClass = findMainClass(environment.getSourceFiles());
182    
183            GenerationState generationState = analyzeAndGenerate(environment);
184            if (generationState == null) {
185                return false;
186            }
187    
188            try {
189                CompileEnvironmentUtil.OutputDirector outputDirector = CompileEnvironmentUtil.singleDirectory(outputDir);
190                writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDirector, jar, includeRuntime, mainClass);
191                return true;
192            }
193            finally {
194                generationState.destroy();
195            }
196        }
197    
198        public static boolean compileAndExecuteScript(
199                @NotNull KotlinPaths paths,
200                @NotNull JetCoreEnvironment environment,
201                @NotNull List<String> scriptArgs) {
202            Class<?> scriptClass = compileScript(paths, environment, null);
203            if(scriptClass == null)
204                return false;
205    
206            try {
207                scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])});
208            }
209            catch (RuntimeException e) {
210                throw e;
211            }
212            catch (Exception e) {
213                throw new RuntimeException("Failed to evaluate script: " + e, e);
214            }
215            return true;
216        }
217    
218        private static Class<?> compileScript(
219                @NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment, @Nullable ClassLoader parentLoader) {
220    
221            GenerationState generationState = analyzeAndGenerate(environment);
222            if (generationState == null) {
223                return null;
224            }
225    
226            GeneratedClassLoader classLoader = null;
227            try {
228                ClassFileFactory factory = generationState.getFactory();
229                classLoader = new GeneratedClassLoader(factory,
230                        new URLClassLoader(new URL[] {
231                            // TODO: add all classpath
232                            paths.getRuntimePath().toURI().toURL()
233                        },
234                        parentLoader == null ? AllModules.class.getClassLoader() : parentLoader));
235    
236                JetFile scriptFile = environment.getSourceFiles().get(0);
237                return classLoader.loadClass(ScriptNameUtil.classNameForScript(scriptFile));
238            }
239            catch (Exception e) {
240                throw new RuntimeException("Failed to evaluate script: " + e, e);
241            }
242            finally {
243                if (classLoader != null) {
244                    classLoader.dispose();
245                }
246                generationState.destroy();
247            }
248        }
249    
250        @Nullable
251        public static GenerationState analyzeAndGenerate(
252                JetCoreEnvironment environment
253        ) {
254            AnalyzeExhaust exhaust = analyze(environment);
255    
256            if (exhaust == null) {
257                return null;
258            }
259    
260            exhaust.throwIfError();
261    
262            return generate(environment, exhaust);
263        }
264    
265        @Nullable
266        private static AnalyzeExhaust analyze(final JetCoreEnvironment environment) {
267            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
268                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
269            analyzerWithCompilerReport.analyzeAndReport(
270                    new Function0<AnalyzeExhaust>() {
271                        @NotNull
272                        @Override
273                        public AnalyzeExhaust invoke() {
274                            BindingTrace sharedTrace = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()).getTrace();
275                            return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
276                                    environment.getProject(),
277                                    environment.getSourceFiles(),
278                                    sharedTrace,
279                                    environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS),
280                                    Predicates.<PsiFile>alwaysTrue(),
281                                    false
282                            );
283                        }
284                    }, environment.getSourceFiles()
285            );
286    
287            return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust();
288        }
289    
290        @NotNull
291        private static GenerationState generate(
292                JetCoreEnvironment environment,
293                AnalyzeExhaust exhaust
294        ) {
295            Project project = environment.getProject();
296            CompilerConfiguration configuration = environment.getConfiguration();
297            GenerationState generationState = new GenerationState(
298                    project, ClassBuilderFactories.BINARIES, Progress.DEAF, exhaust.getBindingContext(), environment.getSourceFiles(),
299                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
300                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
301                    /*generateDeclaredClasses = */true
302            );
303            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
304    
305            CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles());
306            for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
307                plugin.processFiles(context);
308            }
309            return generationState;
310        }
311    
312        public static Class compileScript(
313                @NotNull ClassLoader parentLoader,
314                @NotNull KotlinPaths paths,
315                @NotNull String scriptPath,
316                @Nullable List<AnalyzerScriptParameter> scriptParameters,
317                @Nullable List<JetScriptDefinition> scriptDefinitions) {
318            MessageRenderer messageRenderer = MessageRenderer.PLAIN;
319            GroupingMessageCollector messageCollector = new GroupingMessageCollector(new PrintingMessageCollector(System.err, messageRenderer, false));
320            Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable();
321            try {
322                CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
323                compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector);
324                compilerConfiguration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, getClasspath(parentLoader));
325                compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar());
326                compilerConfiguration.addAll(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, Collections.singletonList(
327                        paths.getJdkAnnotationsPath()));
328                compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, scriptPath);
329                compilerConfiguration.addAll(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY,
330                                             scriptDefinitions != null ? scriptDefinitions : Collections.<JetScriptDefinition>emptyList());
331                compilerConfiguration.put(JVMConfigurationKeys.SCRIPT_PARAMETERS, scriptParameters);
332    
333                JetCoreEnvironment environment = new JetCoreEnvironment(rootDisposable, compilerConfiguration);
334    
335                try {
336                    JetScriptDefinitionProvider.getInstance(environment.getProject()).markFileAsScript(environment.getSourceFiles().get(0));
337                    return compileScript(paths, environment, parentLoader);
338                }
339                catch (CompilationException e) {
340                    messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(e),
341                                            MessageUtil.psiElementToMessageLocation(e.getElement()));
342                    return null;
343                }
344                catch (Throwable t) {
345                    MessageCollectorUtil.reportException(messageCollector, t);
346                    return null;
347                }
348    
349            }
350            finally {
351                messageCollector.flush();
352                Disposer.dispose(rootDisposable);
353            }
354        }
355    
356        private static Collection<File> getClasspath(ClassLoader loader) {
357            return getClasspath(loader, new LinkedList<File>());
358        }
359    
360        private static Collection<File> getClasspath(ClassLoader loader, LinkedList<File> files) {
361            ClassLoader parent = loader.getParent();
362            if(parent != null)
363                getClasspath(parent, files);
364    
365            if(loader instanceof URLClassLoader) {
366                for (URL url : ((URLClassLoader) loader).getURLs()) {
367                    String urlFile = url.getFile();
368    
369                    if (urlFile.contains("%")) {
370                        try {
371                            urlFile = url.toURI().getPath();
372                        }
373                        catch (URISyntaxException e) {
374                            throw ExceptionUtils.rethrow(e);
375                        }
376                    }
377    
378                    File file = new File(urlFile);
379                    if(file.exists() && (file.isDirectory() || file.getName().endsWith(".jar"))) {
380                        files.add(file);
381                    }
382                }
383            }
384            return files;
385        }
386    }