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.OutputUtilsPackage;
038    import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
039    import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
040    import org.jetbrains.kotlin.codegen.*;
041    import org.jetbrains.kotlin.codegen.state.GenerationState;
042    import org.jetbrains.kotlin.config.CompilerConfiguration;
043    import org.jetbrains.kotlin.context.ModuleContext;
044    import org.jetbrains.kotlin.idea.MainFunctionDetector;
045    import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager;
046    import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
047    import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache;
048    import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents;
049    import org.jetbrains.kotlin.modules.Module;
050    import org.jetbrains.kotlin.modules.ModulesPackage;
051    import org.jetbrains.kotlin.modules.TargetId;
052    import org.jetbrains.kotlin.name.FqName;
053    import org.jetbrains.kotlin.parsing.JetScriptDefinition;
054    import org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider;
055    import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
056    import org.jetbrains.kotlin.psi.JetFile;
057    import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter;
058    import org.jetbrains.kotlin.resolve.BindingTrace;
059    import org.jetbrains.kotlin.resolve.BindingTraceContext;
060    import org.jetbrains.kotlin.resolve.ScriptNameUtil;
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.KotlinPaths;
065    
066    import java.io.File;
067    import java.net.URL;
068    import java.net.URLClassLoader;
069    import java.util.*;
070    import java.util.concurrent.TimeUnit;
071    
072    import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.*;
073    import static org.jetbrains.kotlin.config.ConfigPackage.addKotlinSourceRoots;
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                OutputUtilsPackage.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<JetFile> 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                addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module));
176            }
177    
178            for (Module module : chunk) {
179                for (String javaSourceRoot : module.getJavaSourceRoots()) {
180                    addJavaSourceRoot(configuration, new File(javaSourceRoot));
181                }
182            }
183    
184            for (Module module : chunk) {
185                for (String classpathRoot : module.getClasspathRoots()) {
186                    addJvmClasspathRoot(configuration, new File(classpathRoot));
187                }
188            }
189    
190            for (Module module : chunk) {
191                for (String annotationsRoot : module.getAnnotationsRoots()) {
192                    configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
193                }
194    
195                configuration.add(JVMConfigurationKeys.MODULES, module);
196            }
197    
198            return configuration;
199        }
200    
201        @Nullable
202        private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
203            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
204            FqName mainClass = null;
205            for (JetFile file : files) {
206                if (mainFunctionDetector.hasMain(file.getDeclarations())) {
207                    if (mainClass != null) {
208                        // more than one main
209                        return null;
210                    }
211                    FqName fqName = file.getPackageFqName();
212                    mainClass = PackageClassUtils.getPackageClassFqName(fqName);
213                }
214            }
215            return mainClass;
216        }
217    
218        public static boolean compileBunchOfSources(
219                @NotNull KotlinCoreEnvironment environment,
220                @Nullable File jar,
221                @Nullable File outputDir,
222                boolean includeRuntime
223        ) {
224    
225            GenerationState generationState = analyzeAndGenerate(environment);
226            if (generationState == null) {
227                return false;
228            }
229    
230            FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
231    
232            try {
233                writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass);
234                return true;
235            }
236            finally {
237                generationState.destroy();
238            }
239        }
240    
241        public static void compileAndExecuteScript(
242                @NotNull CompilerConfiguration configuration,
243                @NotNull KotlinPaths paths,
244                @NotNull KotlinCoreEnvironment environment,
245                @NotNull List<String> scriptArgs
246        ) {
247            Class<?> scriptClass = compileScript(configuration, paths, environment);
248            if (scriptClass == null) return;
249    
250            try {
251                scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)});
252            }
253            catch (RuntimeException e) {
254                throw e;
255            }
256            catch (Exception e) {
257                throw new RuntimeException("Failed to evaluate script: " + e, e);
258            }
259        }
260    
261        @Nullable
262        public static Class<?> compileScript(
263                @NotNull CompilerConfiguration configuration,
264                @NotNull KotlinPaths paths,
265                @NotNull KotlinCoreEnvironment environment
266        ) {
267            List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS);
268            if (!scriptParameters.isEmpty()) {
269                JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition(
270                        new JetScriptDefinition(".kts", scriptParameters)
271                );
272            }
273            GenerationState state = analyzeAndGenerate(environment);
274            if (state == null) {
275                return null;
276            }
277    
278            GeneratedClassLoader classLoader;
279            try {
280                List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL());
281                for (File file : getJvmClasspathRoots(configuration)) {
282                    classPaths.add(file.toURI().toURL());
283                }
284                //noinspection UnnecessaryFullyQualifiedName
285                classLoader = new GeneratedClassLoader(state.getFactory(),
286                                                       new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]), null)
287                );
288    
289                FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript());
290                return classLoader.loadClass(nameForScript.asString());
291            }
292            catch (Exception e) {
293                throw new RuntimeException("Failed to evaluate script: " + e, e);
294            }
295        }
296    
297        @Nullable
298        public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) {
299            AnalysisResult result = analyze(environment, null);
300    
301            if (result == null) {
302                return null;
303            }
304    
305            if (!result.getShouldGenerateCode()) return null;
306    
307            result.throwIfError();
308    
309            return generate(environment, result, environment.getSourceFiles(), null, null, null);
310        }
311    
312        @Nullable
313        private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment, @Nullable String targetDescription) {
314            MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
315            assert collector != null;
316    
317            long analysisStart = PerformanceCounter.Companion.currentTime();
318            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector);
319            analyzerWithCompilerReport.analyzeAndReport(
320                    environment.getSourceFiles(), new Function0<AnalysisResult>() {
321                        @NotNull
322                        @Override
323                        public AnalysisResult invoke() {
324                            BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
325                            ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject(),
326                                                                                                                    getModuleName(environment));
327    
328                            return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
329                                    moduleContext,
330                                    environment.getSourceFiles(),
331                                    sharedTrace,
332                                    environment.getConfiguration().get(JVMConfigurationKeys.MODULES),
333                                    environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS),
334                                    new JvmPackagePartProvider(environment)
335                            );
336                        }
337                    }
338            );
339            long analysisNanos = PerformanceCounter.Companion.currentTime() - analysisStart;
340            String message = "ANALYZE: " + environment.getSourceFiles().size() + " files (" +
341                             environment.getSourceLinesOfCode() + " lines) " +
342                             (targetDescription != null ? targetDescription : "") +
343                             "in " + TimeUnit.NANOSECONDS.toMillis(analysisNanos) + " ms";
344            K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message);
345    
346            AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult();
347            assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles();
348    
349            CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(),
350                                                                      environment.getSourceFiles());
351            for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
352                plugin.processFiles(context);
353            }
354    
355            return analyzerWithCompilerReport.hasErrors() ? null : result;
356        }
357    
358        @NotNull
359        private static GenerationState generate(
360                @NotNull KotlinCoreEnvironment environment,
361                @NotNull AnalysisResult result,
362                @NotNull List<JetFile> sourceFiles,
363                @Nullable Module module,
364                File outputDirectory,
365                String moduleName
366        ) {
367            CompilerConfiguration configuration = environment.getConfiguration();
368            IncrementalCompilationComponents incrementalCompilationComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS);
369    
370            Collection<FqName> packagesWithObsoleteParts;
371            TargetId targetId = null;
372    
373            if (module == null || incrementalCompilationComponents == null) {
374                packagesWithObsoleteParts = Collections.emptySet();
375            }
376            else {
377                targetId = ModulesPackage.TargetId(module);
378                IncrementalCache incrementalCache = incrementalCompilationComponents.getIncrementalCache(targetId);
379                packagesWithObsoleteParts = new HashSet<FqName>();
380                for (String internalName : incrementalCache.getObsoletePackageParts()) {
381                    packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName());
382                }
383            }
384            BindingTraceContext diagnosticHolder = new BindingTraceContext();
385            GenerationState generationState = new GenerationState(
386                    environment.getProject(),
387                    ClassBuilderFactories.BINARIES,
388                    result.getModuleDescriptor(),
389                    result.getBindingContext(),
390                    sourceFiles,
391                    configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
392                    configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
393                    GenerationState.GenerateClassFilter.GENERATE_ALL,
394                    configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
395                    configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
396                    configuration.get(JVMConfigurationKeys.PACKAGE_FACADES_AS_MULTIFILE_CLASSES, false),
397                    diagnosticHolder,
398                    packagesWithObsoleteParts,
399                    targetId,
400                    moduleName,
401                    outputDirectory,
402                    incrementalCompilationComponents
403            );
404            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
405    
406            long generationStart = PerformanceCounter.Companion.currentTime();
407    
408            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
409    
410            long generationNanos = PerformanceCounter.Companion.currentTime() - generationStart;
411            String desc = module != null ? "target " + module.getModuleName() + "-" + module.getModuleType() + " " : "";
412            String message = "GENERATE: " + sourceFiles.size() + " files (" +
413                             environment.countLinesOfCode(sourceFiles) + " lines) " + desc + "in " + TimeUnit.NANOSECONDS.toMillis(generationNanos) + " ms";
414            K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message);
415            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
416    
417            AnalyzerWithCompilerReport.reportDiagnostics(
418                    new FilteredJvmDiagnostics(
419                            diagnosticHolder.getBindingContext().getDiagnostics(),
420                            result.getBindingContext().getDiagnostics()
421                    ),
422                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
423            );
424            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
425            return generationState;
426        }
427    }