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