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