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