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