001    /*
002     * Copyright 2010-2013 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.jet.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.containers.ContainerUtil;
029    import kotlin.Function0;
030    import org.jetbrains.annotations.NotNull;
031    import org.jetbrains.annotations.Nullable;
032    import org.jetbrains.jet.OutputFileCollection;
033    import org.jetbrains.jet.analyzer.AnalyzeExhaust;
034    import org.jetbrains.jet.cli.common.CLICompiler;
035    import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
036    import org.jetbrains.jet.cli.common.ExitCode;
037    import org.jetbrains.jet.cli.common.arguments.K2JSCompilerArguments;
038    import org.jetbrains.jet.cli.common.arguments.K2JsArgumentConstants;
039    import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
040    import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation;
041    import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity;
042    import org.jetbrains.jet.cli.common.messages.MessageCollector;
043    import org.jetbrains.jet.cli.common.output.outputUtils.OutputUtilsPackage;
044    import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentUtil;
045    import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
046    import org.jetbrains.jet.config.CommonConfigurationKeys;
047    import org.jetbrains.jet.config.CompilerConfiguration;
048    import org.jetbrains.jet.config.Services;
049    import org.jetbrains.jet.lang.psi.JetFile;
050    import org.jetbrains.k2js.analyze.TopDownAnalyzerFacadeForJS;
051    import org.jetbrains.k2js.config.*;
052    import org.jetbrains.k2js.facade.MainCallParameters;
053    
054    import java.io.File;
055    import java.util.Arrays;
056    import java.util.List;
057    
058    import static org.jetbrains.jet.cli.common.ExitCode.COMPILATION_ERROR;
059    import static org.jetbrains.jet.cli.common.ExitCode.OK;
060    import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
061    import static org.jetbrains.k2js.facade.K2JSTranslator.translateWithMainCallParameters;
062    
063    public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
064    
065        public static void main(String... args) {
066            doMain(new K2JSCompiler(), args);
067        }
068    
069        @NotNull
070        @Override
071        protected K2JSCompilerArguments createArguments() {
072            return new K2JSCompilerArguments();
073        }
074    
075    
076        @NotNull
077        @Override
078        protected ExitCode doExecute(
079                @NotNull K2JSCompilerArguments arguments,
080                @NotNull Services services,
081                @NotNull MessageCollector messageCollector,
082                @NotNull Disposable rootDisposable
083        ) {
084            if (arguments.freeArgs.isEmpty()) {
085                messageCollector.report(CompilerMessageSeverity.ERROR, "Specify at least one source file or directory", NO_LOCATION);
086                return ExitCode.INTERNAL_ERROR;
087            }
088    
089            CompilerConfiguration configuration = new CompilerConfiguration();
090            configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector);
091    
092            CompileEnvironmentUtil.addSourceFilesCheckingForDuplicates(configuration, arguments.freeArgs);
093            JetCoreEnvironment environmentForJS = JetCoreEnvironment.createForProduction(rootDisposable, configuration);
094    
095            Project project = environmentForJS.getProject();
096            List<JetFile> sourcesFiles = environmentForJS.getSourceFiles();
097    
098            ClassPathLibrarySourcesLoader sourceLoader = new ClassPathLibrarySourcesLoader(project);
099            List<JetFile> additionalSourceFiles = sourceLoader.findSourceFiles();
100            sourcesFiles.addAll(additionalSourceFiles);
101    
102            if (arguments.verbose) {
103                reportCompiledSourcesList(messageCollector, sourcesFiles);
104            }
105    
106            if (arguments.outputFile == null) {
107                messageCollector.report(CompilerMessageSeverity.ERROR, "Specify output file via -output", CompilerMessageLocation.NO_LOCATION);
108                return ExitCode.INTERNAL_ERROR;
109            }
110    
111            File outputFile = new File(arguments.outputFile);
112    
113            Config config = getConfig(arguments, project);
114            if (analyzeAndReportErrors(messageCollector, sourcesFiles, config)) {
115                return COMPILATION_ERROR;
116            }
117    
118            File outputPrefixFile = null;
119            if (arguments.outputPrefix != null) {
120                outputPrefixFile = new File(arguments.outputPrefix);
121                if (!outputPrefixFile.exists()) {
122                    messageCollector.report(CompilerMessageSeverity.ERROR,
123                                            "Output prefix file '" + arguments.outputPrefix + "' not found",
124                                            CompilerMessageLocation.NO_LOCATION);
125                    return ExitCode.COMPILATION_ERROR;
126                }
127            }
128    
129            File outputPostfixFile = null;
130            if (arguments.outputPostfix != null) {
131                outputPostfixFile = new File(arguments.outputPostfix);
132                if (!outputPostfixFile.exists()) {
133                    messageCollector.report(CompilerMessageSeverity.ERROR,
134                                            "Output postfix file '" + arguments.outputPostfix + "' not found",
135                                            CompilerMessageLocation.NO_LOCATION);
136                    return ExitCode.COMPILATION_ERROR;
137                }
138            }
139    
140            MainCallParameters mainCallParameters = createMainCallParameters(arguments.main);
141    
142            OutputFileCollection outputFiles = translate(mainCallParameters, config, sourcesFiles, outputFile, outputPrefixFile, outputPostfixFile);
143    
144            OutputUtilsPackage.writeAll(outputFiles, outputFile.getParentFile(), messageCollector);
145    
146            return OK;
147        }
148    
149        private static void reportCompiledSourcesList(@NotNull MessageCollector messageCollector, @NotNull List<JetFile> sourceFiles) {
150            Iterable<String> fileNames = ContainerUtil.map(sourceFiles, new Function<JetFile, String>() {
151                @Override
152                public String fun(@Nullable JetFile file) {
153                    assert file != null;
154                    VirtualFile virtualFile = file.getVirtualFile();
155                    if (virtualFile != null) {
156                        return FileUtil.toSystemIndependentName(virtualFile.getPath());
157                    }
158                    return file.getName() + "(no virtual file)";
159                }
160            });
161            messageCollector.report(CompilerMessageSeverity.LOGGING, "Compiling source files: " + Joiner.on(", ").join(fileNames),
162                                    CompilerMessageLocation.NO_LOCATION);
163        }
164    
165        private static OutputFileCollection translate(
166                @NotNull MainCallParameters mainCall,
167                @NotNull Config config,
168                @NotNull List<JetFile> sourceFiles,
169                @NotNull File outputFile,
170                @Nullable File outputPrefix,
171                @Nullable File outputPostfix
172        ) {
173            try {
174                //noinspection unchecked
175                return translateWithMainCallParameters(mainCall, sourceFiles, outputFile, outputPrefix, outputPostfix, config, Consumer.EMPTY_CONSUMER);
176            }
177            catch (Exception e) {
178                throw new RuntimeException(e);
179            }
180        }
181    
182        private static boolean analyzeAndReportErrors(@NotNull MessageCollector messageCollector,
183                @NotNull final List<JetFile> sources, @NotNull final Config config) {
184            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(messageCollector);
185            analyzerWithCompilerReport.analyzeAndReport(sources, new Function0<AnalyzeExhaust>() {
186                @Override
187                public AnalyzeExhaust invoke() {
188                    return TopDownAnalyzerFacadeForJS.analyzeFiles(sources, Predicates.<PsiFile>alwaysTrue(), config);
189                }
190            });
191            return analyzerWithCompilerReport.hasErrors();
192        }
193    
194        @NotNull
195        private static Config getConfig(@NotNull K2JSCompilerArguments arguments, @NotNull Project project) {
196            if (arguments.target != null) {
197                assert arguments.target == "v5" : "Unsupported ECMA version: " + arguments.target;
198            }
199            EcmaVersion ecmaVersion = EcmaVersion.defaultVersion();
200            String moduleId = FileUtil.getNameWithoutExtension(new File(arguments.outputFile));
201            boolean inlineEnabled = !arguments.noInline;
202    
203            if (arguments.libraryFiles != null) {
204                return new LibrarySourcesConfig(project, moduleId, Arrays.asList(arguments.libraryFiles), ecmaVersion, arguments.sourceMap, inlineEnabled);
205            }
206            else {
207                // lets discover the JS library definitions on the classpath
208                return new ClassPathLibraryDefintionsConfig(project, moduleId, ecmaVersion, arguments.sourceMap, inlineEnabled);
209            }
210        }
211    
212        public static MainCallParameters createMainCallParameters(String main) {
213            if (K2JsArgumentConstants.NO_CALL.equals(main)) {
214                return MainCallParameters.noCall();
215            }
216            else {
217                return MainCallParameters.mainWithoutArguments();
218            }
219        }
220    }