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 kotlin.Function0;
026    import kotlin.Function1;
027    import kotlin.Unit;
028    import kotlin.modules.AllModules;
029    import kotlin.modules.Module;
030    import org.jetbrains.annotations.NotNull;
031    import org.jetbrains.annotations.Nullable;
032    import org.jetbrains.jet.analyzer.AnalyzeExhaust;
033    import org.jetbrains.jet.asJava.FilteredJvmDiagnostics;
034    import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
035    import org.jetbrains.jet.cli.common.CompilerPlugin;
036    import org.jetbrains.jet.cli.common.CompilerPluginContext;
037    import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
038    import org.jetbrains.jet.cli.common.messages.MessageCollector;
039    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
040    import org.jetbrains.jet.codegen.*;
041    import org.jetbrains.jet.codegen.inline.InlineCodegenUtil;
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.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.IncrementalCacheProvider;
057    import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalPackage;
058    import org.jetbrains.jet.lang.resolve.name.FqName;
059    import org.jetbrains.jet.plugin.MainFunctionDetector;
060    import org.jetbrains.jet.utils.KotlinPaths;
061    
062    import java.io.File;
063    import java.net.URL;
064    import java.net.URLClassLoader;
065    import java.util.Collection;
066    import java.util.List;
067    import java.util.Map;
068    
069    public class KotlinToJVMBytecodeCompiler {
070    
071        private KotlinToJVMBytecodeCompiler() {
072        }
073    
074        @NotNull
075        private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) {
076            List<String> result = Lists.newArrayList();
077    
078            for (String sourceFile : module.getSourceFiles()) {
079                File source = new File(sourceFile);
080                if (!source.isAbsolute()) {
081                    source = new File(directory, sourceFile);
082                }
083    
084                if (!source.exists()) {
085                    throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName());
086                }
087    
088                result.add(source.getAbsolutePath());
089            }
090            return result;
091        }
092    
093        private static void writeOutput(
094                CompilerConfiguration configuration,
095                ClassFileFactory outputFiles,
096                File outputDir,
097                File jarPath,
098                boolean jarRuntime,
099                FqName mainClass
100        ) {
101            MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
102            CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, outputFiles, messageCollector);
103        }
104    
105        public static boolean compileModules(
106                @NotNull CompilerConfiguration configuration,
107                @NotNull List<Module> chunk,
108                @NotNull File directory,
109                @Nullable File jarPath,
110                boolean jarRuntime
111        ) {
112            Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap();
113    
114            CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory);
115    
116            Disposable parentDisposable = Disposer.newDisposable();
117            JetCoreEnvironment environment = null;
118            try {
119                environment = JetCoreEnvironment.createForProduction(parentDisposable, compilerConfiguration);
120    
121                AnalyzeExhaust exhaust = analyze(environment);
122                if (exhaust == null) {
123                    return false;
124                }
125    
126                exhaust.throwIfError();
127    
128                for (Module module : chunk) {
129                    List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles(
130                            environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() {
131                                @Override
132                                public Unit invoke(String s) {
133                                    throw new IllegalStateException("Should have been checked before: " + s);
134                                }
135                            }
136                    );
137                    GenerationState generationState = generate(environment, exhaust, jetFiles, module.getModuleName());
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            for (Module module : chunk) {
161                configuration.addAll(CommonConfigurationKeys.SOURCE_ROOTS_KEY, getAbsolutePaths(directory, module));
162    
163                for (String classpathRoot : module.getClasspathRoots()) {
164                    configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
165                }
166    
167                for (String annotationsRoot : module.getAnnotationsRoots()) {
168                    configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
169                }
170    
171                configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName());
172            }
173    
174            return configuration;
175        }
176    
177        @Nullable
178        private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
179            MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
180            FqName mainClass = null;
181            for (JetFile file : files) {
182                if (mainFunctionDetector.hasMain(file.getDeclarations())) {
183                    if (mainClass != null) {
184                        // more than one main
185                        return null;
186                    }
187                    FqName fqName = file.getPackageFqName();
188                    mainClass = PackageClassUtils.getPackageClassFqName(fqName);
189                }
190            }
191            return mainClass;
192        }
193    
194        public static boolean compileBunchOfSources(
195                JetCoreEnvironment environment,
196                @Nullable File jar,
197                @Nullable File outputDir,
198                boolean includeRuntime
199        ) {
200    
201            GenerationState generationState = analyzeAndGenerate(environment);
202            if (generationState == null) {
203                return false;
204            }
205    
206            FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
207    
208            try {
209                writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass);
210                return true;
211            }
212            finally {
213                generationState.destroy();
214            }
215        }
216    
217        public static void compileAndExecuteScript(
218                @NotNull KotlinPaths paths,
219                @NotNull JetCoreEnvironment environment,
220                @NotNull List<String> scriptArgs
221        ) {
222            Class<?> scriptClass = compileScript(paths, environment);
223            if (scriptClass == null) return;
224    
225            try {
226                scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[scriptArgs.size()])});
227            }
228            catch (RuntimeException e) {
229                throw e;
230            }
231            catch (Exception e) {
232                throw new RuntimeException("Failed to evaluate script: " + e, e);
233            }
234        }
235    
236        @Nullable
237        public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) {
238            List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS);
239            if (!scriptParameters.isEmpty()) {
240                JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition(
241                        new JetScriptDefinition(".kts", scriptParameters)
242                );
243            }
244            GenerationState state = analyzeAndGenerate(environment);
245            if (state == null) {
246                return null;
247            }
248    
249            GeneratedClassLoader classLoader;
250            try {
251                classLoader = new GeneratedClassLoader(state.getFactory(),
252                                                       new URLClassLoader(new URL[] {
253                                                               // TODO: add all classpath
254                                                               paths.getRuntimePath().toURI().toURL()
255                                                       }, AllModules.class.getClassLoader())
256                );
257    
258                FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript());
259                return classLoader.loadClass(nameForScript.asString());
260            }
261            catch (Exception e) {
262                throw new RuntimeException("Failed to evaluate script: " + e, e);
263            }
264        }
265    
266        @Nullable
267        public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) {
268            AnalyzeExhaust exhaust = analyze(environment);
269    
270            if (exhaust == null) {
271                return null;
272            }
273    
274            exhaust.throwIfError();
275    
276            return generate(environment, exhaust, environment.getSourceFiles(), null);
277        }
278    
279        @Nullable
280        private static AnalyzeExhaust analyze(@NotNull final JetCoreEnvironment environment) {
281            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
282                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
283            analyzerWithCompilerReport.analyzeAndReport(
284                    new Function0<AnalyzeExhaust>() {
285                        @NotNull
286                        @Override
287                        public AnalyzeExhaust invoke() {
288                            CliLightClassGenerationSupport support = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject());
289                            BindingTrace sharedTrace = support.getTrace();
290                            ModuleDescriptorImpl sharedModule = support.getModule();
291                            return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
292                                    environment.getProject(),
293                                    environment.getSourceFiles(),
294                                    sharedTrace,
295                                    Predicates.<PsiFile>alwaysTrue(),
296                                    sharedModule,
297                                    environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS),
298                                    environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR)
299                            );
300                        }
301                    }, environment.getSourceFiles()
302            );
303    
304            AnalyzeExhaust exhaust = analyzerWithCompilerReport.getAnalyzeExhaust();
305            assert exhaust != null : "AnalyzeExhaust should be non-null, compiling: " + environment.getSourceFiles();
306    
307            CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), exhaust.getBindingContext(),
308                                                                      environment.getSourceFiles());
309            for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
310                plugin.processFiles(context);
311            }
312    
313            return analyzerWithCompilerReport.hasErrors() ? null : exhaust;
314        }
315    
316        @NotNull
317        private static GenerationState generate(
318                @NotNull JetCoreEnvironment environment,
319                @NotNull AnalyzeExhaust exhaust,
320                @NotNull List<JetFile> sourceFiles,
321                @Nullable String moduleId
322        ) {
323            CompilerConfiguration configuration = environment.getConfiguration();
324            File incrementalCacheDir = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR);
325            IncrementalCacheProvider incrementalCacheProvider = IncrementalCacheProvider.object$.getInstance();
326    
327            Collection<FqName> packagesWithRemovedFiles =
328                    incrementalCacheDir == null || moduleId == null || incrementalCacheProvider == null
329                    ? null
330                    : IncrementalPackage.getPackagesWithRemovedFiles(
331                            incrementalCacheProvider.getIncrementalCache(incrementalCacheDir), moduleId, environment.getSourceFiles());
332            BindingTraceContext diagnosticHolder = new BindingTraceContext();
333            GenerationState generationState = new GenerationState(
334                    environment.getProject(),
335                    ClassBuilderFactories.BINARIES,
336                    Progress.DEAF,
337                    exhaust.getModuleDescriptor(),
338                    exhaust.getBindingContext(),
339                    sourceFiles,
340                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
341                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
342                    GenerationState.GenerateClassFilter.GENERATE_ALL,
343                    configuration.get(JVMConfigurationKeys.ENABLE_INLINE, InlineCodegenUtil.DEFAULT_INLINE_FLAG),
344                    packagesWithRemovedFiles,
345                    moduleId,
346                    diagnosticHolder
347            );
348            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
349            AnalyzerWithCompilerReport.reportDiagnostics(
350                    new FilteredJvmDiagnostics(
351                            diagnosticHolder.getBindingContext().getDiagnostics(),
352                            exhaust.getBindingContext().getDiagnostics()
353                    ),
354                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
355            );
356            return generationState;
357        }
358    }