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