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