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.Function0;
025    import kotlin.Function1;
026    import kotlin.Unit;
027    import kotlin.modules.AllModules;
028    import kotlin.modules.Module;
029    import org.jetbrains.annotations.NotNull;
030    import org.jetbrains.annotations.Nullable;
031    import org.jetbrains.kotlin.analyzer.AnalysisResult;
032    import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics;
033    import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
034    import org.jetbrains.kotlin.cli.common.CompilerPlugin;
035    import org.jetbrains.kotlin.cli.common.CompilerPluginContext;
036    import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
037    import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
038    import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsPackage;
039    import org.jetbrains.kotlin.cli.jvm.JVMConfigurationKeys;
040    import org.jetbrains.kotlin.codegen.*;
041    import org.jetbrains.kotlin.codegen.state.GenerationState;
042    import org.jetbrains.kotlin.codegen.state.Progress;
043    import org.jetbrains.kotlin.config.CommonConfigurationKeys;
044    import org.jetbrains.kotlin.config.CompilerConfiguration;
045    import org.jetbrains.kotlin.context.ContextPackage;
046    import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl;
047    import org.jetbrains.kotlin.idea.MainFunctionDetector;
048    import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
049    import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCache;
050    import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCacheProvider;
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.psi.JetFile;
055    import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter;
056    import org.jetbrains.kotlin.resolve.BindingTrace;
057    import org.jetbrains.kotlin.resolve.BindingTraceContext;
058    import org.jetbrains.kotlin.resolve.ScriptNameUtil;
059    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
060    import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
061    import org.jetbrains.kotlin.utils.KotlinPaths;
062    
063    import java.io.File;
064    import java.net.URL;
065    import java.net.URLClassLoader;
066    import java.util.*;
067    
068    public class KotlinToJVMBytecodeCompiler {
069    
070        private KotlinToJVMBytecodeCompiler() {
071        }
072    
073        @NotNull
074        private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) {
075            List<String> result = Lists.newArrayList();
076    
077            for (String sourceFile : module.getSourceFiles()) {
078                File source = new File(sourceFile);
079                if (!source.isAbsolute()) {
080                    source = new File(directory, sourceFile);
081                }
082    
083                if (!source.exists()) {
084                    throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName());
085                }
086    
087                result.add(source.getAbsolutePath());
088            }
089            return result;
090        }
091    
092        private static void writeOutput(
093                @NotNull CompilerConfiguration configuration,
094                @NotNull ClassFileFactory outputFiles,
095                @Nullable File outputDir,
096                @Nullable File jarPath,
097                boolean jarRuntime,
098                @Nullable FqName mainClass
099        ) {
100            if (jarPath != null) {
101                CompileEnvironmentUtil.writeToJar(jarPath, jarRuntime, mainClass, outputFiles);
102            }
103            else {
104                MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
105                OutputUtilsPackage.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector);
106            }
107        }
108    
109        public static boolean compileModules(
110                @NotNull CompilerConfiguration configuration,
111                @NotNull List<Module> chunk,
112                @NotNull File directory,
113                @Nullable File jarPath,
114                boolean jarRuntime
115        ) {
116            Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap();
117    
118            CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory);
119    
120            Disposable parentDisposable = Disposer.newDisposable();
121            JetCoreEnvironment environment = null;
122            try {
123                environment = JetCoreEnvironment
124                        .createForProduction(parentDisposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
125    
126                AnalysisResult result = analyze(environment);
127                if (result == null) {
128                    return false;
129                }
130    
131                result.throwIfError();
132    
133                for (Module module : chunk) {
134                    List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles(
135                            environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() {
136                                @Override
137                                public Unit invoke(String s) {
138                                    throw new IllegalStateException("Should have been checked before: " + s);
139                                }
140                            }
141                    );
142                    GenerationState generationState =
143                            generate(environment, result, jetFiles, module.getModuleName(), new File(module.getOutputDirectory()));
144                    outputFiles.put(module, generationState.getFactory());
145                }
146            }
147            finally {
148                if (environment != null) {
149                    Disposer.dispose(parentDisposable);
150                }
151            }
152    
153            for (Module module : chunk) {
154                writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null);
155            }
156            return true;
157        }
158    
159        @NotNull
160        private static CompilerConfiguration createCompilerConfiguration(
161                @NotNull CompilerConfiguration base,
162                @NotNull List<Module> chunk,
163                @NotNull File directory
164        ) {
165            CompilerConfiguration configuration = base.copy();
166    
167            for (Module module : chunk) {
168                configuration.addAll(CommonConfigurationKeys.SOURCE_ROOTS_KEY, getAbsolutePaths(directory, module));
169    
170                for (String classpathRoot : module.getClasspathRoots()) {
171                    configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
172                }
173    
174                for (String annotationsRoot : module.getAnnotationsRoots()) {
175                    configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
176                }
177    
178                configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName());
179            }
180    
181            return configuration;
182        }
183    
184        @Nullable
185        private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
186            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
187            FqName mainClass = null;
188            for (JetFile file : files) {
189                if (mainFunctionDetector.hasMain(file.getDeclarations())) {
190                    if (mainClass != null) {
191                        // more than one main
192                        return null;
193                    }
194                    FqName fqName = file.getPackageFqName();
195                    mainClass = PackageClassUtils.getPackageClassFqName(fqName);
196                }
197            }
198            return mainClass;
199        }
200    
201        public static boolean compileBunchOfSources(
202                @NotNull JetCoreEnvironment environment,
203                @Nullable File jar,
204                @Nullable File outputDir,
205                boolean includeRuntime
206        ) {
207    
208            GenerationState generationState = analyzeAndGenerate(environment);
209            if (generationState == null) {
210                return false;
211            }
212    
213            FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
214    
215            try {
216                writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass);
217                return true;
218            }
219            finally {
220                generationState.destroy();
221            }
222        }
223    
224        public static void compileAndExecuteScript(
225                @NotNull CompilerConfiguration configuration,
226                @NotNull KotlinPaths paths,
227                @NotNull JetCoreEnvironment environment,
228                @NotNull List<String> scriptArgs
229        ) {
230            Class<?> scriptClass = compileScript(configuration, paths, environment);
231            if (scriptClass == null) return;
232    
233            try {
234                scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)});
235            }
236            catch (RuntimeException e) {
237                throw e;
238            }
239            catch (Exception e) {
240                throw new RuntimeException("Failed to evaluate script: " + e, e);
241            }
242        }
243    
244        @Nullable
245        public static Class<?> compileScript(
246                @NotNull CompilerConfiguration configuration,
247                @NotNull KotlinPaths paths,
248                @NotNull JetCoreEnvironment environment
249        ) {
250            List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS);
251            if (!scriptParameters.isEmpty()) {
252                JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition(
253                        new JetScriptDefinition(".kts", scriptParameters)
254                );
255            }
256            GenerationState state = analyzeAndGenerate(environment);
257            if (state == null) {
258                return null;
259            }
260    
261            GeneratedClassLoader classLoader;
262            try {
263                List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL());
264                for (File file : configuration.get(JVMConfigurationKeys.CLASSPATH_KEY, Collections.<File>emptyList())) {
265                    classPaths.add(file.toURI().toURL());
266                }
267                classLoader = new GeneratedClassLoader(state.getFactory(),
268                                                       new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]),
269                                                                          AllModules.class.getClassLoader())
270                );
271    
272                FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript());
273                return classLoader.loadClass(nameForScript.asString());
274            }
275            catch (Exception e) {
276                throw new RuntimeException("Failed to evaluate script: " + e, e);
277            }
278        }
279    
280        @Nullable
281        public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) {
282            AnalysisResult result = analyze(environment);
283    
284            if (result == null) {
285                return null;
286            }
287    
288            result.throwIfError();
289    
290            return generate(environment, result, environment.getSourceFiles(), null, null);
291        }
292    
293        @Nullable
294        private static AnalysisResult analyze(@NotNull final JetCoreEnvironment environment) {
295            MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
296            assert collector != null;
297    
298            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector);
299            analyzerWithCompilerReport.analyzeAndReport(
300                    environment.getSourceFiles(), new Function0<AnalysisResult>() {
301                        @NotNull
302                        @Override
303                        public AnalysisResult invoke() {
304                            BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
305                            ModuleDescriptorImpl analyzeModule = TopDownAnalyzerFacadeForJVM.createSealedJavaModule();
306    
307                            return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
308                                    environment.getProject(),
309                                    ContextPackage.GlobalContext(),
310                                    environment.getSourceFiles(),
311                                    sharedTrace,
312                                    analyzeModule,
313                                    environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS),
314                                    environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER)
315                            );
316                        }
317                    }
318            );
319    
320            AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult();
321            assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles();
322    
323            CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(),
324                                                                      environment.getSourceFiles());
325            for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
326                plugin.processFiles(context);
327            }
328    
329            return analyzerWithCompilerReport.hasErrors() ? null : result;
330        }
331    
332        @NotNull
333        private static GenerationState generate(
334                @NotNull JetCoreEnvironment environment,
335                @NotNull AnalysisResult result,
336                @NotNull List<JetFile> sourceFiles,
337                @Nullable String moduleId,
338                File outputDirectory
339        ) {
340            CompilerConfiguration configuration = environment.getConfiguration();
341            IncrementalCacheProvider incrementalCacheProvider = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER);
342    
343            Collection<FqName> packagesWithObsoleteParts;
344            if (moduleId == null || incrementalCacheProvider == null) {
345                packagesWithObsoleteParts = null;
346            }
347            else {
348                IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(moduleId);
349                packagesWithObsoleteParts = new HashSet<FqName>();
350                for (String internalName : incrementalCache.getObsoletePackageParts()) {
351                    packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName());
352                }
353            }
354            BindingTraceContext diagnosticHolder = new BindingTraceContext();
355            GenerationState generationState = new GenerationState(
356                    environment.getProject(),
357                    ClassBuilderFactories.BINARIES,
358                    Progress.DEAF,
359                    result.getModuleDescriptor(),
360                    result.getBindingContext(),
361                    sourceFiles,
362                    configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
363                    configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
364                    GenerationState.GenerateClassFilter.GENERATE_ALL,
365                    configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
366                    configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
367                    packagesWithObsoleteParts,
368                    moduleId,
369                    diagnosticHolder,
370                    outputDirectory
371            );
372            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
373            AnalyzerWithCompilerReport.reportDiagnostics(
374                    new FilteredJvmDiagnostics(
375                            diagnosticHolder.getBindingContext().getDiagnostics(),
376                            result.getBindingContext().getDiagnostics()
377                    ),
378                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
379            );
380            return generationState;
381        }
382    }