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