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.*;
034    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
035    import org.jetbrains.jet.codegen.*;
036    import org.jetbrains.jet.codegen.state.GenerationState;
037    import org.jetbrains.jet.codegen.state.Progress;
038    import org.jetbrains.jet.config.CommonConfigurationKeys;
039    import org.jetbrains.jet.config.CompilerConfiguration;
040    import org.jetbrains.jet.lang.parsing.JetScriptDefinition;
041    import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider;
042    import org.jetbrains.jet.lang.psi.JetFile;
043    import org.jetbrains.jet.lang.psi.JetPsiUtil;
044    import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter;
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.ExceptionUtils;
052    import org.jetbrains.jet.utils.KotlinPaths;
053    import org.jetbrains.jet.utils.PathUtil;
054    
055    import java.io.File;
056    import java.net.URISyntaxException;
057    import java.net.URL;
058    import java.net.URLClassLoader;
059    import java.util.Collection;
060    import java.util.Collections;
061    import java.util.LinkedList;
062    import java.util.List;
063    
064    public class KotlinToJVMBytecodeCompiler {
065    
066        private static final boolean COMPILE_CHUNK_AS_ONE_MODULE = true;
067    
068        private KotlinToJVMBytecodeCompiler() {
069        }
070    
071        @Nullable
072        public static ClassFileFactory compileModule(CompilerConfiguration configuration, Module module, File directory) {
073            List<String> sourceFiles = module.getSourceFiles();
074            if (sourceFiles.isEmpty()) {
075                throw new CompileEnvironmentException("No source files where defined in module " + module.getModuleName());
076            }
077    
078            CompilerConfiguration compilerConfiguration = configuration.copy();
079            for (String sourceFile : sourceFiles) {
080                File source = new File(sourceFile);
081                if (!source.isAbsolute()) {
082                    source = new File(directory, sourceFile);
083                }
084    
085                if (!source.exists()) {
086                    throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName());
087                }
088    
089                compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, source.getPath());
090            }
091    
092            for (String classpathRoot : module.getClasspathRoots()) {
093                compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
094            }
095    
096            for (String annotationsRoot : module.getAnnotationsRoots()) {
097                compilerConfiguration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
098            }
099    
100            Disposable parentDisposable = CompileEnvironmentUtil.createMockDisposable();
101            JetCoreEnvironment moduleEnvironment = null;
102            try {
103                moduleEnvironment = new JetCoreEnvironment(parentDisposable, compilerConfiguration);
104    
105    
106                GenerationState generationState = analyzeAndGenerate(moduleEnvironment);
107                if (generationState == null) {
108                    return null;
109                }
110                return generationState.getFactory();
111            } finally {
112                if (moduleEnvironment != null) {
113                    Disposer.dispose(parentDisposable);
114                }
115            }
116        }
117    
118        private static void writeOutput(
119                CompilerConfiguration configuration,
120                ClassFileFactory moduleFactory,
121                CompileEnvironmentUtil.OutputDirector outputDir,
122                File jarPath,
123                boolean jarRuntime,
124                FqName mainClass
125        ) {
126            MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
127            CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, moduleFactory, messageCollector);
128        }
129    
130        public static boolean compileModules(
131                CompilerConfiguration configuration,
132                @NotNull final ModuleChunk chunk,
133                @NotNull File directory,
134                @Nullable File jarPath,
135                boolean jarRuntime
136        ) {
137            List<Module> modules = chunk.getModules();
138            if (COMPILE_CHUNK_AS_ONE_MODULE && modules.size() > 1) {
139                modules = Collections.<Module>singletonList(new ChunkAsOneModule(chunk));
140            }
141            for (Module module : modules) {
142                ClassFileFactory moduleFactory = compileModule(configuration, module, directory);
143                if (moduleFactory == null) {
144                    return false;
145                }
146                CompileEnvironmentUtil.OutputDirector outputDir = new CompileEnvironmentUtil.OutputDirector() {
147                    @NotNull
148                    @Override
149                    public File getOutputDirectory(@NotNull Collection<File> sourceFiles) {
150                        for (File sourceFile : sourceFiles) {
151                            // Note that here we track original modules:
152                            Module module = chunk.findModuleBySourceFile(sourceFile);
153                            if (module != null) {
154                                return new File(module.getOutputDirectory());
155                            }
156                        }
157                        throw new IllegalStateException("No module found for source files: " + sourceFiles);
158                    }
159                };
160    
161                writeOutput(configuration, moduleFactory, outputDir, jarPath, jarRuntime, null);
162            }
163            return true;
164        }
165    
166        @Nullable
167        private static FqName findMainClass(@NotNull List<JetFile> files) {
168            FqName mainClass = null;
169            for (JetFile file : files) {
170                if (JetMainDetector.hasMain(file.getDeclarations())) {
171                    if (mainClass != null) {
172                        // more than one main
173                        return null;
174                    }
175                    FqName fqName = JetPsiUtil.getFQName(file);
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            FqName mainClass = findMainClass(environment.getSourceFiles());
190    
191            GenerationState generationState = analyzeAndGenerate(environment);
192            if (generationState == null) {
193                return false;
194            }
195    
196            try {
197                CompileEnvironmentUtil.OutputDirector outputDirector = CompileEnvironmentUtil.singleDirectory(outputDir);
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 boolean compileAndExecuteScript(
207                @NotNull KotlinPaths paths,
208                @NotNull JetCoreEnvironment environment,
209                @NotNull List<String> scriptArgs) {
210            Class<?> scriptClass = compileScript(paths, environment, null);
211            if(scriptClass == null)
212                return false;
213    
214            try {
215                scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])});
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            return true;
224        }
225    
226        private static Class<?> compileScript(
227                @NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment, @Nullable ClassLoader parentLoader) {
228    
229            GenerationState generationState = analyzeAndGenerate(environment);
230            if (generationState == null) {
231                return null;
232            }
233    
234            GeneratedClassLoader classLoader = null;
235            try {
236                ClassFileFactory factory = generationState.getFactory();
237                classLoader = new GeneratedClassLoader(factory,
238                        new URLClassLoader(new URL[] {
239                            // TODO: add all classpath
240                            paths.getRuntimePath().toURI().toURL()
241                        },
242                        parentLoader == null ? AllModules.class.getClassLoader() : parentLoader));
243    
244                JetFile scriptFile = environment.getSourceFiles().get(0);
245                return classLoader.loadClass(ScriptNameUtil.classNameForScript(scriptFile));
246            }
247            catch (Exception e) {
248                throw new RuntimeException("Failed to evaluate script: " + e, e);
249            }
250            finally {
251                if (classLoader != null) {
252                    classLoader.dispose();
253                }
254                generationState.destroy();
255            }
256        }
257    
258        @Nullable
259        public static GenerationState analyzeAndGenerate(
260                JetCoreEnvironment environment
261        ) {
262            AnalyzeExhaust exhaust = analyze(environment);
263    
264            if (exhaust == null) {
265                return null;
266            }
267    
268            exhaust.throwIfError();
269    
270            return generate(environment, exhaust);
271        }
272    
273        @Nullable
274        private static AnalyzeExhaust analyze(final JetCoreEnvironment environment) {
275            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
276                    environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
277            analyzerWithCompilerReport.analyzeAndReport(
278                    new Function0<AnalyzeExhaust>() {
279                        @NotNull
280                        @Override
281                        public AnalyzeExhaust invoke() {
282                            BindingTrace sharedTrace = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()).getTrace();
283                            return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
284                                    environment.getProject(),
285                                    environment.getSourceFiles(),
286                                    sharedTrace,
287                                    environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS),
288                                    Predicates.<PsiFile>alwaysTrue(),
289                                    false
290                            );
291                        }
292                    }, environment.getSourceFiles()
293            );
294    
295            return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust();
296        }
297    
298        @NotNull
299        private static GenerationState generate(
300                JetCoreEnvironment environment,
301                AnalyzeExhaust exhaust
302        ) {
303            Project project = environment.getProject();
304            CompilerConfiguration configuration = environment.getConfiguration();
305            GenerationState generationState = new GenerationState(
306                    project, ClassBuilderFactories.BINARIES, Progress.DEAF, exhaust.getBindingContext(), environment.getSourceFiles(),
307                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
308                    configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
309                    /*generateDeclaredClasses = */true
310            );
311            KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
312    
313            CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles());
314            for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
315                plugin.processFiles(context);
316            }
317            return generationState;
318        }
319    
320        public static Class compileScript(
321                @NotNull ClassLoader parentLoader,
322                @NotNull KotlinPaths paths,
323                @NotNull String scriptPath,
324                @Nullable List<AnalyzerScriptParameter> scriptParameters,
325                @Nullable List<JetScriptDefinition> scriptDefinitions) {
326            MessageRenderer messageRenderer = MessageRenderer.PLAIN;
327            GroupingMessageCollector messageCollector = new GroupingMessageCollector(new PrintingMessageCollector(System.err, messageRenderer, false));
328            Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable();
329            try {
330                CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
331                compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector);
332                compilerConfiguration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, getClasspath(parentLoader));
333                compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar());
334                compilerConfiguration.addAll(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, Collections.singletonList(
335                        paths.getJdkAnnotationsPath()));
336                compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, scriptPath);
337                compilerConfiguration.addAll(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY,
338                                             scriptDefinitions != null ? scriptDefinitions : Collections.<JetScriptDefinition>emptyList());
339                compilerConfiguration.put(JVMConfigurationKeys.SCRIPT_PARAMETERS, scriptParameters);
340    
341                JetCoreEnvironment environment = new JetCoreEnvironment(rootDisposable, compilerConfiguration);
342    
343                try {
344                    JetScriptDefinitionProvider.getInstance(environment.getProject()).markFileAsScript(environment.getSourceFiles().get(0));
345                    return compileScript(paths, environment, parentLoader);
346                }
347                catch (CompilationException e) {
348                    messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(e),
349                                            MessageUtil.psiElementToMessageLocation(e.getElement()));
350                    return null;
351                }
352                catch (Throwable t) {
353                    MessageCollectorUtil.reportException(messageCollector, t);
354                    return null;
355                }
356    
357            }
358            finally {
359                messageCollector.flush();
360                Disposer.dispose(rootDisposable);
361            }
362        }
363    
364        private static Collection<File> getClasspath(ClassLoader loader) {
365            return getClasspath(loader, new LinkedList<File>());
366        }
367    
368        private static Collection<File> getClasspath(ClassLoader loader, LinkedList<File> files) {
369            ClassLoader parent = loader.getParent();
370            if(parent != null)
371                getClasspath(parent, files);
372    
373            if(loader instanceof URLClassLoader) {
374                for (URL url : ((URLClassLoader) loader).getURLs()) {
375                    String urlFile = url.getFile();
376    
377                    if (urlFile.contains("%")) {
378                        try {
379                            urlFile = url.toURI().getPath();
380                        }
381                        catch (URISyntaxException e) {
382                            throw ExceptionUtils.rethrow(e);
383                        }
384                    }
385    
386                    File file = new File(urlFile);
387                    if(file.exists() && (file.isDirectory() || file.getName().endsWith(".jar"))) {
388                        files.add(file);
389                    }
390                }
391            }
392            return files;
393        }
394    }