001    /*
002     * Copyright 2010-2015 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.kotlin.cli.js;
018    
019    import com.google.common.base.Joiner;
020    import com.intellij.openapi.Disposable;
021    import com.intellij.openapi.project.Project;
022    import com.intellij.openapi.util.io.FileUtil;
023    import com.intellij.openapi.vfs.VirtualFile;
024    import com.intellij.util.Function;
025    import com.intellij.util.SmartList;
026    import com.intellij.util.containers.ContainerUtil;
027    import kotlin.Unit;
028    import kotlin.jvm.functions.Function1;
029    import org.jetbrains.annotations.NotNull;
030    import org.jetbrains.annotations.Nullable;
031    import org.jetbrains.kotlin.analyzer.AnalysisResult;
032    import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
033    import org.jetbrains.kotlin.cli.common.CLICompiler;
034    import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
035    import org.jetbrains.kotlin.cli.common.ExitCode;
036    import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
037    import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants;
038    import org.jetbrains.kotlin.cli.common.messages.*;
039    import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
040    import org.jetbrains.kotlin.cli.jvm.compiler.CompilerJarLocator;
041    import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
042    import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
043    import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
044    import org.jetbrains.kotlin.config.CompilerConfiguration;
045    import org.jetbrains.kotlin.config.ContentRootsKt;
046    import org.jetbrains.kotlin.config.Services;
047    import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS;
048    import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult;
049    import org.jetbrains.kotlin.js.config.Config;
050    import org.jetbrains.kotlin.js.config.EcmaVersion;
051    import org.jetbrains.kotlin.js.config.LibrarySourcesConfig;
052    import org.jetbrains.kotlin.js.facade.K2JSTranslator;
053    import org.jetbrains.kotlin.js.facade.MainCallParameters;
054    import org.jetbrains.kotlin.js.facade.TranslationResult;
055    import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
056    import org.jetbrains.kotlin.psi.KtFile;
057    import org.jetbrains.kotlin.utils.PathUtil;
058    
059    import java.io.File;
060    import java.util.List;
061    
062    import static org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR;
063    import static org.jetbrains.kotlin.cli.common.ExitCode.OK;
064    import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
065    
066    public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
067    
068        public static void main(String... args) {
069            doMain(new K2JSCompiler(), args);
070        }
071    
072        @NotNull
073        @Override
074        protected K2JSCompilerArguments createArguments() {
075            return new K2JSCompilerArguments();
076        }
077    
078    
079        @NotNull
080        @Override
081        protected ExitCode doExecute(
082                @NotNull K2JSCompilerArguments arguments,
083                @NotNull Services services,
084                @NotNull MessageCollector messageCollector,
085                @NotNull Disposable rootDisposable
086        ) {
087            final MessageSeverityCollector messageSeverityCollector = new MessageSeverityCollector(messageCollector);
088    
089            if (arguments.freeArgs.isEmpty()) {
090                if (arguments.version) {
091                    return OK;
092                }
093                messageSeverityCollector.report(CompilerMessageSeverity.ERROR, "Specify at least one source file or directory", NO_LOCATION);
094                return COMPILATION_ERROR;
095            }
096    
097            CompilerConfiguration configuration = new CompilerConfiguration();
098            configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageSeverityCollector);
099    
100            CompilerJarLocator locator = services.get(CompilerJarLocator.class);
101            if (locator != null) {
102                configuration.put(JVMConfigurationKeys.COMPILER_JAR_LOCATOR, locator);
103            }
104    
105            ContentRootsKt.addKotlinSourceRoots(configuration, arguments.freeArgs);
106            KotlinCoreEnvironment environmentForJS =
107                    KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JS_CONFIG_FILES);
108    
109            Project project = environmentForJS.getProject();
110            List<KtFile> sourcesFiles = environmentForJS.getSourceFiles();
111    
112            if (arguments.outputFile == null) {
113                messageSeverityCollector.report(CompilerMessageSeverity.ERROR, "Specify output file via -output", CompilerMessageLocation.NO_LOCATION);
114                return ExitCode.COMPILATION_ERROR;
115            }
116    
117            if (messageSeverityCollector.anyReported(CompilerMessageSeverity.ERROR)) {
118                return ExitCode.COMPILATION_ERROR;
119            }
120    
121            if (sourcesFiles.isEmpty()) {
122                messageSeverityCollector.report(CompilerMessageSeverity.ERROR, "No source files", CompilerMessageLocation.NO_LOCATION);
123                return COMPILATION_ERROR;
124            }
125    
126            if (arguments.verbose) {
127                reportCompiledSourcesList(messageSeverityCollector, sourcesFiles);
128            }
129    
130            File outputFile = new File(arguments.outputFile);
131    
132            Config config = getConfig(arguments, project);
133            if (config.checkLibFilesAndReportErrors(new Function1<String, Unit>() {
134                @Override
135                public Unit invoke(String message) {
136                    messageSeverityCollector.report(CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION);
137                    return Unit.INSTANCE;
138                }
139            })) {
140                return COMPILATION_ERROR;
141            }
142    
143            AnalyzerWithCompilerReport analyzerWithCompilerReport = analyzeAndReportErrors(messageSeverityCollector, sourcesFiles, config);
144            if (analyzerWithCompilerReport.hasErrors()) {
145                return COMPILATION_ERROR;
146            }
147    
148            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
149    
150            AnalysisResult analysisResult = analyzerWithCompilerReport.getAnalysisResult();
151            assert analysisResult instanceof JsAnalysisResult : "analysisResult should be instance of JsAnalysisResult, but " + analysisResult;
152            JsAnalysisResult jsAnalysisResult = (JsAnalysisResult) analysisResult;
153    
154            File outputPrefixFile = null;
155            if (arguments.outputPrefix != null) {
156                outputPrefixFile = new File(arguments.outputPrefix);
157                if (!outputPrefixFile.exists()) {
158                    messageSeverityCollector.report(CompilerMessageSeverity.ERROR,
159                                            "Output prefix file '" + arguments.outputPrefix + "' not found",
160                                            CompilerMessageLocation.NO_LOCATION);
161                    return ExitCode.COMPILATION_ERROR;
162                }
163            }
164    
165            File outputPostfixFile = null;
166            if (arguments.outputPostfix != null) {
167                outputPostfixFile = new File(arguments.outputPostfix);
168                if (!outputPostfixFile.exists()) {
169                    messageSeverityCollector.report(CompilerMessageSeverity.ERROR,
170                                            "Output postfix file '" + arguments.outputPostfix + "' not found",
171                                            CompilerMessageLocation.NO_LOCATION);
172                    return ExitCode.COMPILATION_ERROR;
173                }
174            }
175    
176            MainCallParameters mainCallParameters = createMainCallParameters(arguments.main);
177            TranslationResult translationResult;
178    
179            K2JSTranslator translator = new K2JSTranslator(config);
180            try {
181                //noinspection unchecked
182                translationResult = translator.translate(sourcesFiles, mainCallParameters, jsAnalysisResult);
183            } catch (Exception e) {
184                throw new RuntimeException(e);
185            }
186    
187            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
188    
189            AnalyzerWithCompilerReport.Companion.reportDiagnostics(translationResult.getDiagnostics(), messageSeverityCollector);
190    
191            if (!(translationResult instanceof TranslationResult.Success)) return ExitCode.COMPILATION_ERROR;
192    
193            TranslationResult.Success successResult = (TranslationResult.Success) translationResult;
194            OutputFileCollection outputFiles = successResult.getOutputFiles(outputFile, outputPrefixFile, outputPostfixFile);
195    
196            if (outputFile.isDirectory()) {
197                messageSeverityCollector.report(CompilerMessageSeverity.ERROR,
198                                        "Cannot open output file '" + outputFile.getPath() + "': is a directory",
199                                        CompilerMessageLocation.NO_LOCATION);
200                return ExitCode.COMPILATION_ERROR;
201            }
202    
203            File outputDir = outputFile.getParentFile();
204            if (outputDir == null) {
205                outputDir = outputFile.getAbsoluteFile().getParentFile();
206            }
207    
208            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
209    
210            OutputUtilsKt.writeAll(outputFiles, outputDir, messageSeverityCollector);
211    
212            return OK;
213        }
214    
215        private static void reportCompiledSourcesList(@NotNull MessageCollector messageCollector, @NotNull List<KtFile> sourceFiles) {
216            Iterable<String> fileNames = ContainerUtil.map(sourceFiles, new Function<KtFile, String>() {
217                @Override
218                public String fun(@Nullable KtFile file) {
219                    assert file != null;
220                    VirtualFile virtualFile = file.getVirtualFile();
221                    if (virtualFile != null) {
222                        return FileUtil.toSystemDependentName(virtualFile.getPath());
223                    }
224                    return file.getName() + "(no virtual file)";
225                }
226            });
227            messageCollector.report(CompilerMessageSeverity.LOGGING, "Compiling source files: " + Joiner.on(", ").join(fileNames),
228                                    CompilerMessageLocation.NO_LOCATION);
229        }
230    
231        private static AnalyzerWithCompilerReport analyzeAndReportErrors(@NotNull MessageCollector messageCollector,
232                @NotNull final List<KtFile> sources, @NotNull final Config config) {
233            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(messageCollector);
234            analyzerWithCompilerReport.analyzeAndReport(sources, new AnalyzerWithCompilerReport.Analyzer() {
235                @NotNull
236                @Override
237                public AnalysisResult analyze() {
238                    return TopDownAnalyzerFacadeForJS.analyzeFiles(sources, config);
239                }
240    
241                @Override
242                public void reportEnvironmentErrors() {
243                }
244            });
245            return analyzerWithCompilerReport;
246        }
247    
248        @NotNull
249        private static Config getConfig(@NotNull K2JSCompilerArguments arguments, @NotNull Project project) {
250            if (arguments.target != null) {
251                assert arguments.target == "v5" : "Unsupported ECMA version: " + arguments.target;
252            }
253            EcmaVersion ecmaVersion = EcmaVersion.defaultVersion();
254            String moduleId = FileUtil.getNameWithoutExtension(new File(arguments.outputFile));
255            boolean inlineEnabled = !arguments.noInline;
256    
257            List<String> libraryFiles = new SmartList<String>();
258            if (!arguments.noStdlib) {
259                libraryFiles.add(0, PathUtil.getKotlinPathsForCompiler().getJsStdLibJarPath().getAbsolutePath());
260            }
261    
262            if (arguments.libraryFiles != null) {
263                ContainerUtil.addAllNotNull(libraryFiles, arguments.libraryFiles);
264            }
265    
266            return new LibrarySourcesConfig.Builder(project, moduleId, libraryFiles)
267                    .ecmaVersion(ecmaVersion)
268                    .sourceMap(arguments.sourceMap)
269                    .inlineEnabled(inlineEnabled)
270                    .metaInfo(arguments.metaInfo)
271                    .kjsm(arguments.kjsm)
272                    .build();
273        }
274    
275        public static MainCallParameters createMainCallParameters(String main) {
276            if (K2JsArgumentConstants.NO_CALL.equals(main)) {
277                return MainCallParameters.noCall();
278            }
279            else {
280                return MainCallParameters.mainWithoutArguments();
281            }
282        }
283    }