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