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