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