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