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.AnalysisResult;
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.CompilerConfiguration;
045    import org.jetbrains.jet.context.ContextPackage;
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.Collection;
067    import java.util.List;
068    import java.util.Map;
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            MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
103            CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, outputFiles, messageCollector);
104        }
105    
106        public static boolean compileModules(
107                @NotNull CompilerConfiguration configuration,
108                @NotNull List<Module> chunk,
109                @NotNull File directory,
110                @Nullable File jarPath,
111                boolean jarRuntime
112        ) {
113            Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap();
114    
115            CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory);
116    
117            Disposable parentDisposable = Disposer.newDisposable();
118            JetCoreEnvironment environment = null;
119            try {
120                environment = JetCoreEnvironment
121                        .createForProduction(parentDisposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
122    
123                AnalysisResult result = analyze(environment);
124                if (result == null) {
125                    return false;
126                }
127    
128                result.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, result, 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    
164            List<String> sourceRoots = Lists.newArrayList();
165    
166            for (Module module : chunk) {
167                sourceRoots.addAll(getAbsolutePaths(directory, module));
168    
169                for (String classpathRoot : module.getClasspathRoots()) {
170                    configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
171                }
172    
173                for (String annotationsRoot : module.getAnnotationsRoots()) {
174                    configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
175                }
176    
177                configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName());
178            }
179    
180            CompileEnvironmentUtil.addSourceFilesCheckingForDuplicates(configuration, sourceRoots);
181    
182            return configuration;
183        }
184    
185        @Nullable
186        private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
187            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
188            FqName mainClass = null;
189            for (JetFile file : files) {
190                if (mainFunctionDetector.hasMain(file.getDeclarations())) {
191                    if (mainClass != null) {
192                        // more than one main
193                        return null;
194                    }
195                    FqName fqName = file.getPackageFqName();
196                    mainClass = PackageClassUtils.getPackageClassFqName(fqName);
197                }
198            }
199            return mainClass;
200        }
201    
202        public static boolean compileBunchOfSources(
203                @NotNull JetCoreEnvironment environment,
204                @Nullable File jar,
205                @Nullable File outputDir,
206                boolean includeRuntime
207        ) {
208    
209            GenerationState generationState = analyzeAndGenerate(environment);
210            if (generationState == null) {
211                return false;
212            }
213    
214            FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
215    
216            try {
217                writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass);
218                return true;
219            }
220            finally {
221                generationState.destroy();
222            }
223        }
224    
225        public static void compileAndExecuteScript(
226                @NotNull KotlinPaths paths,
227                @NotNull JetCoreEnvironment environment,
228                @NotNull List<String> scriptArgs
229        ) {
230            Class<?> scriptClass = compileScript(paths, environment);
231            if (scriptClass == null) return;
232    
233            try {
234                scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)});
235            }
236            catch (RuntimeException e) {
237                throw e;
238            }
239            catch (Exception e) {
240                throw new RuntimeException("Failed to evaluate script: " + e, e);
241            }
242        }
243    
244        @Nullable
245        public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) {
246            List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS);
247            if (!scriptParameters.isEmpty()) {
248                JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition(
249                        new JetScriptDefinition(".kts", scriptParameters)
250                );
251            }
252            GenerationState state = analyzeAndGenerate(environment);
253            if (state == null) {
254                return null;
255            }
256    
257            GeneratedClassLoader classLoader;
258            try {
259                classLoader = new GeneratedClassLoader(state.getFactory(),
260                                                       new URLClassLoader(new URL[] {
261                                                               // TODO: add all classpath
262                                                               paths.getRuntimePath().toURI().toURL()
263                                                       }, AllModules.class.getClassLoader())
264                );
265    
266                FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript());
267                return classLoader.loadClass(nameForScript.asString());
268            }
269            catch (Exception e) {
270                throw new RuntimeException("Failed to evaluate script: " + e, e);
271            }
272        }
273    
274        @Nullable
275        public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) {
276            AnalysisResult result = analyze(environment);
277    
278            if (result == null) {
279                return null;
280            }
281    
282            result.throwIfError();
283    
284            return generate(environment, result, environment.getSourceFiles(), null, null);
285        }
286    
287        @Nullable
288        private static AnalysisResult analyze(@NotNull final JetCoreEnvironment environment) {
289            MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
290            assert collector != null;
291    
292            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector);
293            analyzerWithCompilerReport.analyzeAndReport(
294                    environment.getSourceFiles(), new Function0<AnalysisResult>() {
295                        @NotNull
296                        @Override
297                        public AnalysisResult invoke() {
298                            BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
299                            ModuleDescriptorImpl analyzeModule = TopDownAnalyzerFacadeForJVM.createSealedJavaModule();
300    
301                            return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
302                                    environment.getProject(),
303                                    ContextPackage.GlobalContext(),
304                                    environment.getSourceFiles(),
305                                    sharedTrace,
306                                    Predicates.<PsiFile>alwaysTrue(),
307                                    analyzeModule,
308                                    environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS),
309                                    environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER)
310                            );
311                        }
312                    }
313            );
314    
315            AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult();
316            assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles();
317    
318            CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(),
319                                                                      environment.getSourceFiles());
320            for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
321                plugin.processFiles(context);
322            }
323    
324            return analyzerWithCompilerReport.hasErrors() ? null : result;
325        }
326    
327        @NotNull
328        private static GenerationState generate(
329                @NotNull JetCoreEnvironment environment,
330                @NotNull AnalysisResult result,
331                @NotNull List<JetFile> sourceFiles,
332                @Nullable String moduleId,
333                File outputDirectory
334        ) {
335            CompilerConfiguration configuration = environment.getConfiguration();
336            IncrementalCacheProvider incrementalCacheProvider = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER);
337    
338            Collection<FqName> packagesWithRemovedFiles;
339            if (moduleId == null || incrementalCacheProvider == null) {
340                packagesWithRemovedFiles = null;
341            }
342            else {
343                IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(moduleId);
344                packagesWithRemovedFiles = IncrementalPackage.getPackagesWithRemovedFiles(incrementalCache, environment.getSourceFiles());
345            }
346            BindingTraceContext diagnosticHolder = new BindingTraceContext();
347            GenerationState generationState = new GenerationState(
348                    environment.getProject(),
349                    ClassBuilderFactories.BINARIES,
350                    Progress.DEAF,
351                    result.getModuleDescriptor(),
352                    result.getBindingContext(),
353                    sourceFiles,
354                    configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
355                    configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
356                    GenerationState.GenerateClassFilter.GENERATE_ALL,
357                    configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
358                    configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
359                    packagesWithRemovedFiles,
360                    moduleId,
361                    diagnosticHolder,
362                    outputDirectory
363            );
364            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
365            AnalyzerWithCompilerReport.reportDiagnostics(
366                    new FilteredJvmDiagnostics(
367                            diagnosticHolder.getBindingContext().getDiagnostics(),
368                            result.getBindingContext().getDiagnostics()
369                    ),
370                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
371            );
372            return generationState;
373        }
374    }