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