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