001    /*
002     * Copyright 2010-2013 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.project.Project;
022    import com.intellij.openapi.util.Disposer;
023    import com.intellij.psi.PsiFile;
024    import jet.Function0;
025    import jet.modules.AllModules;
026    import jet.modules.Module;
027    import org.jetbrains.annotations.NotNull;
028    import org.jetbrains.annotations.Nullable;
029    import org.jetbrains.jet.analyzer.AnalyzeExhaust;
030    import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
031    import org.jetbrains.jet.cli.common.CompilerPlugin;
032    import org.jetbrains.jet.cli.common.CompilerPluginContext;
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.psi.JetFile;
044    import org.jetbrains.jet.lang.psi.JetPsiUtil;
045    import org.jetbrains.jet.lang.resolve.BindingTrace;
046    import org.jetbrains.jet.lang.resolve.ScriptNameUtil;
047    import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
048    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
049    import org.jetbrains.jet.lang.resolve.name.FqName;
050    import org.jetbrains.jet.lang.types.lang.InlineUtil;
051    import org.jetbrains.jet.plugin.JetMainDetector;
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 List<JetFile> files) {
165            FqName mainClass = null;
166            for (JetFile file : files) {
167                if (JetMainDetector.hasMain(file.getDeclarations())) {
168                    if (mainClass != null) {
169                        // more than one main
170                        return null;
171                    }
172                    FqName fqName = JetPsiUtil.getFQName(file);
173                    mainClass = PackageClassUtils.getPackageClassFqName(fqName);
174                }
175            }
176            return mainClass;
177        }
178    
179        public static boolean compileBunchOfSources(
180                JetCoreEnvironment environment,
181                @Nullable File jar,
182                @Nullable File outputDir,
183                boolean includeRuntime
184        ) {
185    
186            FqName mainClass = findMainClass(environment.getSourceFiles());
187    
188            GenerationState generationState = analyzeAndGenerate(environment);
189            if (generationState == null) {
190                return false;
191            }
192    
193            try {
194                OutputDirector outputDirector = outputDir != null ? new SingleDirectoryDirector(outputDir) : null;
195                writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDirector, jar, includeRuntime, mainClass);
196                return true;
197            }
198            finally {
199                generationState.destroy();
200            }
201        }
202    
203        public static void compileAndExecuteScript(
204                @NotNull KotlinPaths paths,
205                @NotNull JetCoreEnvironment environment,
206                @NotNull List<String> scriptArgs
207        ) {
208            Class<?> scriptClass = compileScript(paths, environment);
209            if (scriptClass == null) return;
210    
211            try {
212                scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[scriptArgs.size()])});
213            }
214            catch (RuntimeException e) {
215                throw e;
216            }
217            catch (Exception e) {
218                throw new RuntimeException("Failed to evaluate script: " + e, e);
219            }
220        }
221    
222        @Nullable
223        public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) {
224            GenerationState state = analyzeAndGenerate(environment);
225            if (state == null) {
226                return null;
227            }
228    
229            GeneratedClassLoader classLoader = null;
230            try {
231                classLoader = new GeneratedClassLoader(state.getFactory(),
232                                                       new URLClassLoader(new URL[] {
233                                                               // TODO: add all classpath
234                                                               paths.getRuntimePath().toURI().toURL()
235                                                       }, AllModules.class.getClassLoader())
236                );
237    
238                return classLoader.loadClass(ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0)));
239            }
240            catch (Exception e) {
241                throw new RuntimeException("Failed to evaluate script: " + e, e);
242            }
243            finally {
244                if (classLoader != null) {
245                    classLoader.dispose();
246                }
247                state.destroy();
248            }
249        }
250    
251        @Nullable
252        public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) {
253            AnalyzeExhaust exhaust = analyze(environment);
254    
255            if (exhaust == null) {
256                return null;
257            }
258    
259            exhaust.throwIfError();
260    
261            return generate(environment, exhaust);
262        }
263    
264        @Nullable
265        private static AnalyzeExhaust analyze(@NotNull final JetCoreEnvironment environment) {
266            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
267                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
268            analyzerWithCompilerReport.analyzeAndReport(
269                    new Function0<AnalyzeExhaust>() {
270                        @NotNull
271                        @Override
272                        public AnalyzeExhaust invoke() {
273                            BindingTrace sharedTrace = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()).getTrace();
274                            return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
275                                    environment.getProject(),
276                                    environment.getSourceFiles(),
277                                    sharedTrace,
278                                    environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS),
279                                    Predicates.<PsiFile>alwaysTrue(),
280                                    false
281                            );
282                        }
283                    }, environment.getSourceFiles()
284            );
285    
286            return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust();
287        }
288    
289        @NotNull
290        private static GenerationState generate(@NotNull JetCoreEnvironment environment, @NotNull AnalyzeExhaust exhaust) {
291            Project project = environment.getProject();
292            CompilerConfiguration configuration = environment.getConfiguration();
293            GenerationState generationState = new GenerationState(
294                    project, ClassBuilderFactories.BINARIES, Progress.DEAF, exhaust.getBindingContext(), environment.getSourceFiles(),
295                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
296                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
297                    /*generateDeclaredClasses = */true,
298                    configuration.get(JVMConfigurationKeys.ENABLE_INLINE, InlineUtil.DEFAULT_INLINE_FLAG)
299            );
300            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
301    
302            CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles());
303            for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
304                plugin.processFiles(context);
305            }
306            return generationState;
307        }
308    }