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