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