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