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