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