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