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