001    /*
002     * Copyright 2010-2015 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.base.Function;
020    import com.google.common.base.Joiner;
021    import com.google.common.collect.Collections2;
022    import com.google.common.collect.Lists;
023    import com.google.common.collect.Maps;
024    import com.intellij.util.ArrayUtil;
025    import kotlin.Unit;
026    import kotlin.jvm.functions.Function0;
027    import kotlin.jvm.functions.Function1;
028    import org.jetbrains.annotations.NotNull;
029    import org.jetbrains.annotations.Nullable;
030    import org.jetbrains.kotlin.analyzer.AnalysisResult;
031    import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics;
032    import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
033    import org.jetbrains.kotlin.cli.common.CompilerPlugin;
034    import org.jetbrains.kotlin.cli.common.CompilerPluginContext;
035    import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
036    import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
037    import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
038    import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
039    import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
040    import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
041    import org.jetbrains.kotlin.cli.jvm.config.ModuleNameKt;
042    import org.jetbrains.kotlin.codegen.*;
043    import org.jetbrains.kotlin.codegen.state.GenerationState;
044    import org.jetbrains.kotlin.config.CompilerConfiguration;
045    import org.jetbrains.kotlin.config.ContentRootsKt;
046    import org.jetbrains.kotlin.context.ModuleContext;
047    import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
048    import org.jetbrains.kotlin.idea.MainFunctionDetector;
049    import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager;
050    import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache;
051    import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents;
052    import org.jetbrains.kotlin.modules.JavaRootPath;
053    import org.jetbrains.kotlin.modules.Module;
054    import org.jetbrains.kotlin.modules.TargetId;
055    import org.jetbrains.kotlin.modules.TargetIdKt;
056    import org.jetbrains.kotlin.name.FqName;
057    import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
058    import org.jetbrains.kotlin.psi.KtFile;
059    import org.jetbrains.kotlin.psi.KtScript;
060    import org.jetbrains.kotlin.resolve.BindingTrace;
061    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
062    import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
063    import org.jetbrains.kotlin.util.PerformanceCounter;
064    import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
065    import org.jetbrains.kotlin.utils.KotlinPaths;
066    
067    import java.io.File;
068    import java.io.PrintStream;
069    import java.lang.reflect.Constructor;
070    import java.lang.reflect.InvocationTargetException;
071    import java.net.URL;
072    import java.net.URLClassLoader;
073    import java.util.*;
074    import java.util.concurrent.TimeUnit;
075    
076    public class KotlinToJVMBytecodeCompiler {
077    
078        private KotlinToJVMBytecodeCompiler() {
079        }
080    
081        @NotNull
082        private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) {
083            List<String> result = Lists.newArrayList();
084    
085            for (String sourceFile : module.getSourceFiles()) {
086                File source = new File(sourceFile);
087                if (!source.isAbsolute()) {
088                    source = new File(directory, sourceFile);
089                }
090                result.add(source.getAbsolutePath());
091            }
092            return result;
093        }
094    
095        private static void writeOutput(
096                @NotNull CompilerConfiguration configuration,
097                @NotNull ClassFileFactory outputFiles,
098                @Nullable File outputDir,
099                @Nullable File jarPath,
100                boolean jarRuntime,
101                @Nullable FqName mainClass
102        ) {
103            if (jarPath != null) {
104                CompileEnvironmentUtil.writeToJar(jarPath, jarRuntime, mainClass, outputFiles);
105            }
106            else {
107                MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
108                OutputUtilsKt.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector);
109            }
110        }
111    
112        public static boolean compileModules(
113                @NotNull KotlinCoreEnvironment environment,
114                @NotNull CompilerConfiguration configuration,
115                @NotNull List<Module> chunk,
116                @NotNull File directory,
117                @Nullable File jarPath,
118                @NotNull List<String> friendPaths,
119                boolean jarRuntime
120        ) {
121            Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap();
122    
123            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
124    
125            ModuleVisibilityManager moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.getProject());
126    
127            for (Module module: chunk) {
128                moduleVisibilityManager.addModule(module);
129            }
130    
131            for (String path : friendPaths) {
132                moduleVisibilityManager.addFriendPath(path);
133            }
134    
135            String targetDescription = "in targets [" + Joiner.on(", ").join(Collections2.transform(chunk, new Function<Module, String>() {
136                @Override
137                public String apply(@Nullable Module input) {
138                    return input != null ? input.getModuleName() + "-" + input.getModuleType() : "<null>";
139                }
140            })) + "] ";
141            AnalysisResult result = analyze(environment, targetDescription);
142            if (result == null) {
143                return false;
144            }
145    
146            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
147    
148            result.throwIfError();
149    
150            for (Module module : chunk) {
151                ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
152                List<KtFile> jetFiles = CompileEnvironmentUtil.getKtFiles(
153                        environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() {
154                            @Override
155                            public Unit invoke(String s) {
156                                throw new IllegalStateException("Should have been checked before: " + s);
157                            }
158                        }
159                );
160                File moduleOutputDirectory = new File(module.getOutputDirectory());
161                GenerationState generationState =
162                        generate(environment, result, jetFiles, module, moduleOutputDirectory,
163                                 module.getModuleName());
164                outputFiles.put(module, generationState.getFactory());
165            }
166    
167            for (Module module : chunk) {
168                ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
169                writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null);
170            }
171            return true;
172        }
173    
174        @NotNull
175        public static CompilerConfiguration createCompilerConfiguration(
176                @NotNull CompilerConfiguration base,
177                @NotNull List<Module> chunk,
178                @NotNull File directory
179        ) {
180            CompilerConfiguration configuration = base.copy();
181    
182            for (Module module : chunk) {
183                ContentRootsKt.addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module));
184            }
185    
186            for (Module module : chunk) {
187                for (JavaRootPath javaRootPath : module.getJavaSourceRoots()) {
188                    JvmContentRootsKt.addJavaSourceRoot(configuration, new File(javaRootPath.getPath()), javaRootPath.getPackagePrefix());
189                }
190            }
191    
192            for (Module module : chunk) {
193                for (String classpathRoot : module.getClasspathRoots()) {
194                    JvmContentRootsKt.addJvmClasspathRoot(configuration, new File(classpathRoot));
195                }
196            }
197    
198            for (Module module : chunk) {
199                configuration.add(JVMConfigurationKeys.MODULES, module);
200            }
201    
202            return configuration;
203        }
204    
205        @Nullable
206        private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<KtFile> files) {
207            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
208            FqName mainClass = null;
209            for (KtFile file : files) {
210                if (mainFunctionDetector.hasMain(file.getDeclarations())) {
211                    if (mainClass != null) {
212                        // more than one main
213                        return null;
214                    }
215                    FqName fqName = file.getPackageFqName();
216                    mainClass = JvmFileClassUtil.getFileClassInfoNoResolve(file).getFacadeClassFqName();
217                }
218            }
219            return mainClass;
220        }
221    
222        public static boolean compileBunchOfSources(
223                @NotNull KotlinCoreEnvironment environment,
224                @Nullable File jar,
225                @Nullable File outputDir,
226                @NotNull List<String> friendPaths,
227                boolean includeRuntime
228        ) {
229    
230            ModuleVisibilityManager moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.getProject());
231    
232            for (String path : friendPaths) {
233                moduleVisibilityManager.addFriendPath(path);
234            }
235    
236            GenerationState generationState = analyzeAndGenerate(environment);
237            if (generationState == null) {
238                return false;
239            }
240    
241            FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
242    
243            try {
244                writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass);
245                return true;
246            }
247            finally {
248                generationState.destroy();
249            }
250        }
251    
252        public static void compileAndExecuteScript(
253                @NotNull CompilerConfiguration configuration,
254                @NotNull KotlinPaths paths,
255                @NotNull KotlinCoreEnvironment environment,
256                @NotNull List<String> scriptArgs
257        ) {
258            Class<?> scriptClass = compileScript(configuration, paths, environment);
259            if (scriptClass == null) return;
260            Constructor<?> scriptConstructor = getScriptConstructor(scriptClass);
261    
262            try {
263                scriptConstructor.newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)});
264            }
265            catch (Throwable e) {
266                reportExceptionFromScript(e);
267            }
268        }
269    
270        private static void reportExceptionFromScript(@NotNull  Throwable exception) {
271            // expecting InvocationTargetException from constructor invocation with cause that describes the actual cause
272            PrintStream stream = System.err;
273            Throwable cause = exception.getCause();
274            if (!(exception instanceof InvocationTargetException) || cause == null) {
275                exception.printStackTrace(stream);
276                return;
277            }
278            stream.println(cause);
279            StackTraceElement[] fullTrace = cause.getStackTrace();
280            int relevantEntries = fullTrace.length - exception.getStackTrace().length;
281            for (int i = 0; i < relevantEntries; i++) {
282                stream.println("\tat " + fullTrace[i]);
283            }
284        }
285    
286        @NotNull
287        private static Constructor<?> getScriptConstructor(Class<?> scriptClass) {
288            try {
289                return scriptClass.getConstructor(String[].class);
290            }
291            catch (NoSuchMethodException e) {
292                throw ExceptionUtilsKt.rethrow(e);
293            }
294        }
295    
296        @Nullable
297        public static Class<?> compileScript(
298                @NotNull CompilerConfiguration configuration,
299                @NotNull KotlinPaths paths,
300                @NotNull KotlinCoreEnvironment environment
301        ) {
302            GenerationState state = analyzeAndGenerate(environment);
303            if (state == null) {
304                return null;
305            }
306    
307            GeneratedClassLoader classLoader;
308            try {
309                List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL());
310                for (File file : JvmContentRootsKt.getJvmClasspathRoots(configuration)) {
311                    classPaths.add(file.toURI().toURL());
312                }
313                //noinspection UnnecessaryFullyQualifiedName
314                classLoader = new GeneratedClassLoader(state.getFactory(),
315                                                       new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]), null)
316                );
317    
318                KtScript script = environment.getSourceFiles().get(0).getScript();
319                assert script != null : "Script must be parsed";
320                FqName nameForScript = script.getFqName();
321                return classLoader.loadClass(nameForScript.asString());
322            }
323            catch (Exception e) {
324                throw new RuntimeException("Failed to evaluate script: " + e, e);
325            }
326        }
327    
328        @Nullable
329        public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) {
330            AnalysisResult result = analyze(environment, null);
331    
332            if (result == null) {
333                return null;
334            }
335    
336            if (!result.getShouldGenerateCode()) return null;
337    
338            result.throwIfError();
339    
340            return generate(environment, result, environment.getSourceFiles(), null, null, null);
341        }
342    
343        @Nullable
344        private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment, @Nullable String targetDescription) {
345            MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
346            assert collector != null;
347    
348            long analysisStart = PerformanceCounter.Companion.currentTime();
349            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector);
350            analyzerWithCompilerReport.analyzeAndReport(
351                    environment.getSourceFiles(), new Function0<AnalysisResult>() {
352                        @NotNull
353                        @Override
354                        public AnalysisResult invoke() {
355                            BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
356                            ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject(),
357                                                                                                                    ModuleNameKt
358                                                                                                                            .getModuleName(environment));
359    
360                            return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
361                                    moduleContext,
362                                    environment.getSourceFiles(),
363                                    sharedTrace,
364                                    environment.getConfiguration().get(JVMConfigurationKeys.MODULES),
365                                    environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS),
366                                    new JvmPackagePartProvider(environment)
367                            );
368                        }
369                    }
370            );
371            long analysisNanos = PerformanceCounter.Companion.currentTime() - analysisStart;
372            String message = "ANALYZE: " + environment.getSourceFiles().size() + " files (" +
373                             environment.getSourceLinesOfCode() + " lines) " +
374                             (targetDescription != null ? targetDescription : "") +
375                             "in " + TimeUnit.NANOSECONDS.toMillis(analysisNanos) + " ms";
376            K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message);
377    
378            AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult();
379            assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles();
380    
381            CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(),
382                                                                      environment.getSourceFiles());
383            for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
384                plugin.processFiles(context);
385            }
386    
387            return analyzerWithCompilerReport.hasErrors() ? null : result;
388        }
389    
390        @NotNull
391        private static GenerationState generate(
392                @NotNull KotlinCoreEnvironment environment,
393                @NotNull AnalysisResult result,
394                @NotNull List<KtFile> sourceFiles,
395                @Nullable Module module,
396                File outputDirectory,
397                String moduleName
398        ) {
399            CompilerConfiguration configuration = environment.getConfiguration();
400            IncrementalCompilationComponents incrementalCompilationComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS);
401    
402            Collection<FqName> packagesWithObsoleteParts;
403            List<FqName> obsoleteMultifileClasses;
404            TargetId targetId = null;
405    
406            if (module == null || incrementalCompilationComponents == null) {
407                packagesWithObsoleteParts = Collections.emptySet();
408                obsoleteMultifileClasses = Collections.emptyList();
409            }
410            else {
411                targetId = TargetIdKt.TargetId(module);
412                IncrementalCache incrementalCache = incrementalCompilationComponents.getIncrementalCache(targetId);
413    
414                packagesWithObsoleteParts = new HashSet<FqName>();
415                for (String internalName : incrementalCache.getObsoletePackageParts()) {
416                    packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName());
417                }
418    
419                obsoleteMultifileClasses = new ArrayList<FqName>();
420                for (String obsoleteFacadeInternalName : incrementalCache.getObsoleteMultifileClasses()) {
421                    obsoleteMultifileClasses.add(JvmClassName.byInternalName(obsoleteFacadeInternalName).getFqNameForClassNameWithoutDollars());
422                }
423            }
424            GenerationState generationState = new GenerationState(
425                    environment.getProject(),
426                    ClassBuilderFactories.BINARIES,
427                    result.getModuleDescriptor(),
428                    result.getBindingContext(),
429                    sourceFiles,
430                    configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
431                    configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
432                    GenerationState.GenerateClassFilter.GENERATE_ALL,
433                    configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
434                    configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
435                    /* useTypeTableInSerializer = */ false,
436                    packagesWithObsoleteParts,
437                    obsoleteMultifileClasses,
438                    targetId,
439                    moduleName,
440                    outputDirectory,
441                    incrementalCompilationComponents
442            );
443            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
444    
445            long generationStart = PerformanceCounter.Companion.currentTime();
446    
447            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
448    
449            long generationNanos = PerformanceCounter.Companion.currentTime() - generationStart;
450            String desc = module != null ? "target " + module.getModuleName() + "-" + module.getModuleType() + " " : "";
451            String message = "GENERATE: " + sourceFiles.size() + " files (" +
452                             environment.countLinesOfCode(sourceFiles) + " lines) " + desc + "in " + TimeUnit.NANOSECONDS.toMillis(generationNanos) + " ms";
453            K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message);
454            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
455    
456            AnalyzerWithCompilerReport.reportDiagnostics(
457                    new FilteredJvmDiagnostics(
458                            generationState.getCollectedExtraJvmDiagnostics(),
459                            result.getBindingContext().getDiagnostics()
460                    ),
461                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
462            );
463            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
464            return generationState;
465        }
466    }