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