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.collect.Lists;
020    import com.google.common.collect.Maps;
021    import com.intellij.openapi.Disposable;
022    import com.intellij.openapi.util.Disposer;
023    import com.intellij.util.ArrayUtil;
024    import kotlin.Unit;
025    import kotlin.jvm.functions.Function0;
026    import kotlin.jvm.functions.Function1;
027    import org.jetbrains.annotations.NotNull;
028    import org.jetbrains.annotations.Nullable;
029    import org.jetbrains.kotlin.analyzer.AnalysisResult;
030    import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics;
031    import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
032    import org.jetbrains.kotlin.cli.common.CompilerPlugin;
033    import org.jetbrains.kotlin.cli.common.CompilerPluginContext;
034    import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
035    import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
036    import org.jetbrains.kotlin.cli.common.modules.Module;
037    import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsPackage;
038    import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
039    import org.jetbrains.kotlin.codegen.*;
040    import org.jetbrains.kotlin.codegen.state.GenerationState;
041    import org.jetbrains.kotlin.codegen.state.Progress;
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.cache.IncrementalCache;
047    import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCacheProvider;
048    import org.jetbrains.kotlin.name.FqName;
049    import org.jetbrains.kotlin.parsing.JetScriptDefinition;
050    import org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider;
051    import org.jetbrains.kotlin.psi.JetFile;
052    import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter;
053    import org.jetbrains.kotlin.resolve.BindingTrace;
054    import org.jetbrains.kotlin.resolve.BindingTraceContext;
055    import org.jetbrains.kotlin.resolve.ScriptNameUtil;
056    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
057    import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
058    import org.jetbrains.kotlin.utils.KotlinPaths;
059    
060    import java.io.File;
061    import java.net.URL;
062    import java.net.URLClassLoader;
063    import java.util.Collection;
064    import java.util.HashSet;
065    import java.util.List;
066    import java.util.Map;
067    
068    import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.*;
069    import static org.jetbrains.kotlin.config.ConfigPackage.addKotlinSourceRoots;
070    
071    public class KotlinToJVMBytecodeCompiler {
072    
073        private KotlinToJVMBytecodeCompiler() {
074        }
075    
076        @NotNull
077        private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) {
078            List<String> result = Lists.newArrayList();
079    
080            for (String sourceFile : module.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 " + module.getModuleName());
088                }
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                OutputUtilsPackage.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector);
109            }
110        }
111    
112        public static boolean compileModules(
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            CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory);
122    
123            Disposable parentDisposable = Disposer.newDisposable();
124            KotlinCoreEnvironment environment = null;
125            try {
126                environment = KotlinCoreEnvironment
127                        .createForProduction(parentDisposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
128    
129                AnalysisResult result = analyze(environment);
130                if (result == null) {
131                    return false;
132                }
133    
134                result.throwIfError();
135    
136                for (Module module : chunk) {
137                    List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles(
138                            environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() {
139                                @Override
140                                public Unit invoke(String s) {
141                                    throw new IllegalStateException("Should have been checked before: " + s);
142                                }
143                            }
144                    );
145                    GenerationState generationState =
146                            generate(environment, result, jetFiles, module.getModuleName(), new File(module.getOutputDirectory()));
147                    outputFiles.put(module, generationState.getFactory());
148                }
149            }
150            finally {
151                if (environment != null) {
152                    Disposer.dispose(parentDisposable);
153                }
154            }
155    
156            for (Module module : chunk) {
157                writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null);
158            }
159            return true;
160        }
161    
162        @NotNull
163        private static CompilerConfiguration createCompilerConfiguration(
164                @NotNull CompilerConfiguration base,
165                @NotNull List<Module> chunk,
166                @NotNull File directory
167        ) {
168            CompilerConfiguration configuration = base.copy();
169    
170            for (Module module : chunk) {
171                addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module));
172            }
173    
174            for (Module module : chunk) {
175                for (String javaSourceRoot : module.getJavaSourceRoots()) {
176                    addJavaSourceRoot(configuration, new File(javaSourceRoot));
177                }
178            }
179    
180            for (Module module : chunk) {
181                for (String classpathRoot : module.getClasspathRoots()) {
182                    addJvmClasspathRoot(configuration, new File(classpathRoot));
183                }
184            }
185    
186            for (Module module : chunk) {
187                for (String annotationsRoot : module.getAnnotationsRoots()) {
188                    configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
189                }
190    
191                configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName());
192            }
193    
194            return configuration;
195        }
196    
197        @Nullable
198        private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
199            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
200            FqName mainClass = null;
201            for (JetFile 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 = PackageClassUtils.getPackageClassFqName(fqName);
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 : 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()]),
283                                                                          kotlin.KotlinPackage.class.getClassLoader())
284                );
285    
286                FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript());
287                return classLoader.loadClass(nameForScript.asString());
288            }
289            catch (Exception e) {
290                throw new RuntimeException("Failed to evaluate script: " + e, e);
291            }
292        }
293    
294        @Nullable
295        public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) {
296            AnalysisResult result = analyze(environment);
297    
298            if (result == null) {
299                return null;
300            }
301    
302            if (!result.getShouldGenerateCode()) return null;
303    
304            result.throwIfError();
305    
306            return generate(environment, result, environment.getSourceFiles(), null, null);
307        }
308    
309        @Nullable
310        private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment) {
311            MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
312            assert collector != null;
313    
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    
323                            return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
324                                    moduleContext,
325                                    environment.getSourceFiles(),
326                                    sharedTrace,
327                                    environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS),
328                                    environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER)
329                            );
330                        }
331                    }
332            );
333    
334            AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult();
335            assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles();
336    
337            CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(),
338                                                                      environment.getSourceFiles());
339            for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
340                plugin.processFiles(context);
341            }
342    
343            return analyzerWithCompilerReport.hasErrors() ? null : result;
344        }
345    
346        @NotNull
347        private static GenerationState generate(
348                @NotNull KotlinCoreEnvironment environment,
349                @NotNull AnalysisResult result,
350                @NotNull List<JetFile> sourceFiles,
351                @Nullable String moduleId,
352                File outputDirectory
353        ) {
354            CompilerConfiguration configuration = environment.getConfiguration();
355            IncrementalCacheProvider incrementalCacheProvider = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER);
356    
357            Collection<FqName> packagesWithObsoleteParts;
358            if (moduleId == null || incrementalCacheProvider == null) {
359                packagesWithObsoleteParts = null;
360            }
361            else {
362                IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(moduleId);
363                packagesWithObsoleteParts = new HashSet<FqName>();
364                for (String internalName : incrementalCache.getObsoletePackageParts()) {
365                    packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName());
366                }
367            }
368            BindingTraceContext diagnosticHolder = new BindingTraceContext();
369            GenerationState generationState = new GenerationState(
370                    environment.getProject(),
371                    ClassBuilderFactories.BINARIES,
372                    Progress.DEAF,
373                    result.getModuleDescriptor(),
374                    result.getBindingContext(),
375                    sourceFiles,
376                    configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
377                    configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
378                    GenerationState.GenerateClassFilter.GENERATE_ALL,
379                    configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
380                    configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
381                    packagesWithObsoleteParts,
382                    moduleId,
383                    diagnosticHolder,
384                    outputDirectory
385            );
386            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
387            AnalyzerWithCompilerReport.reportDiagnostics(
388                    new FilteredJvmDiagnostics(
389                            diagnosticHolder.getBindingContext().getDiagnostics(),
390                            result.getBindingContext().getDiagnostics()
391                    ),
392                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
393            );
394            return generationState;
395        }
396    }