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 com.intellij.util.containers.HashMap;
028 import org.jetbrains.annotations.NotNull;
029 import org.jetbrains.annotations.Nullable;
030 import org.jetbrains.kotlin.analyzer.AnalysisResult;
031 import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
032 import org.jetbrains.kotlin.cli.common.CLICompiler;
033 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
034 import org.jetbrains.kotlin.cli.common.ExitCode;
035 import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
036 import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants;
037 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
038 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation;
039 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
040 import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
041 import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
042 import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
043 import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
044 import org.jetbrains.kotlin.config.CommonConfigurationKeys;
045 import org.jetbrains.kotlin.config.CompilerConfiguration;
046 import org.jetbrains.kotlin.config.ContentRootsKt;
047 import org.jetbrains.kotlin.config.Services;
048 import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS;
049 import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult;
050 import org.jetbrains.kotlin.js.config.EcmaVersion;
051 import org.jetbrains.kotlin.js.config.JSConfigurationKeys;
052 import org.jetbrains.kotlin.js.config.JsConfig;
053 import org.jetbrains.kotlin.js.config.LibrarySourcesConfig;
054 import org.jetbrains.kotlin.js.facade.K2JSTranslator;
055 import org.jetbrains.kotlin.js.facade.MainCallParameters;
056 import org.jetbrains.kotlin.js.facade.TranslationResult;
057 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
058 import org.jetbrains.kotlin.psi.KtFile;
059 import org.jetbrains.kotlin.serialization.js.ModuleKind;
060 import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
061 import org.jetbrains.kotlin.utils.PathUtil;
062
063 import java.io.File;
064 import java.util.List;
065 import java.util.Map;
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.UtilsKt.checkKotlinPackageUsage;
070 import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
071
072 public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
073 private static final Map<String, ModuleKind> moduleKindMap = new HashMap<String, ModuleKind>();
074
075 static {
076 moduleKindMap.put(K2JsArgumentConstants.MODULE_PLAIN, ModuleKind.PLAIN);
077 moduleKindMap.put(K2JsArgumentConstants.MODULE_COMMONJS, ModuleKind.COMMON_JS);
078 moduleKindMap.put(K2JsArgumentConstants.MODULE_AMD, ModuleKind.AMD);
079 moduleKindMap.put(K2JsArgumentConstants.MODULE_UMD, ModuleKind.UMD);
080 }
081
082 public static void main(String... args) {
083 doMain(new K2JSCompiler(), args);
084 }
085
086 @NotNull
087 @Override
088 protected K2JSCompilerArguments createArguments() {
089 return new K2JSCompilerArguments();
090 }
091
092 @NotNull
093 @Override
094 protected ExitCode doExecute(
095 @NotNull K2JSCompilerArguments arguments, @NotNull CompilerConfiguration configuration, @NotNull Disposable rootDisposable
096 ) {
097 final MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
098
099 if (arguments.freeArgs.isEmpty()) {
100 if (arguments.version) {
101 return OK;
102 }
103 messageCollector.report(CompilerMessageSeverity.ERROR, "Specify at least one source file or directory", NO_LOCATION);
104 return COMPILATION_ERROR;
105 }
106
107 ContentRootsKt.addKotlinSourceRoots(configuration, arguments.freeArgs);
108 KotlinCoreEnvironment environmentForJS =
109 KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JS_CONFIG_FILES);
110
111 Project project = environmentForJS.getProject();
112 List<KtFile> sourcesFiles = environmentForJS.getSourceFiles();
113
114 environmentForJS.getConfiguration().put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, arguments.allowKotlinPackage);
115
116 if (!checkKotlinPackageUsage(environmentForJS, sourcesFiles)) return ExitCode.COMPILATION_ERROR;
117
118 if (arguments.outputFile == null) {
119 messageCollector.report(CompilerMessageSeverity.ERROR, "Specify output file via -output", CompilerMessageLocation.NO_LOCATION);
120 return ExitCode.COMPILATION_ERROR;
121 }
122
123 if (messageCollector.hasErrors()) {
124 return ExitCode.COMPILATION_ERROR;
125 }
126
127 if (sourcesFiles.isEmpty()) {
128 messageCollector.report(CompilerMessageSeverity.ERROR, "No source files", CompilerMessageLocation.NO_LOCATION);
129 return COMPILATION_ERROR;
130 }
131
132 if (arguments.verbose) {
133 reportCompiledSourcesList(messageCollector, sourcesFiles);
134 }
135
136 File outputFile = new File(arguments.outputFile);
137
138 configuration.put(CommonConfigurationKeys.MODULE_NAME, FileUtil.getNameWithoutExtension(outputFile));
139
140 JsConfig config = new LibrarySourcesConfig(project, configuration);
141 if (config.checkLibFilesAndReportErrors(new JsConfig.Reporter() {
142 @Override
143 public void error(@NotNull String message) {
144 messageCollector.report(CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION);
145 }
146
147 @Override
148 public void warning(@NotNull String message) {
149 messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, message, CompilerMessageLocation.NO_LOCATION);
150 }
151 })) {
152 return COMPILATION_ERROR;
153 }
154
155 AnalyzerWithCompilerReport analyzerWithCompilerReport = analyzeAndReportErrors(messageCollector, sourcesFiles, config);
156 if (analyzerWithCompilerReport.hasErrors()) {
157 return COMPILATION_ERROR;
158 }
159
160 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
161
162 AnalysisResult analysisResult = analyzerWithCompilerReport.getAnalysisResult();
163 assert analysisResult instanceof JsAnalysisResult : "analysisResult should be instance of JsAnalysisResult, but " + analysisResult;
164 JsAnalysisResult jsAnalysisResult = (JsAnalysisResult) analysisResult;
165
166 File outputPrefixFile = null;
167 if (arguments.outputPrefix != null) {
168 outputPrefixFile = new File(arguments.outputPrefix);
169 if (!outputPrefixFile.exists()) {
170 messageCollector.report(CompilerMessageSeverity.ERROR,
171 "Output prefix file '" + arguments.outputPrefix + "' not found",
172 CompilerMessageLocation.NO_LOCATION);
173 return ExitCode.COMPILATION_ERROR;
174 }
175 }
176
177 File outputPostfixFile = null;
178 if (arguments.outputPostfix != null) {
179 outputPostfixFile = new File(arguments.outputPostfix);
180 if (!outputPostfixFile.exists()) {
181 messageCollector.report(CompilerMessageSeverity.ERROR,
182 "Output postfix file '" + arguments.outputPostfix + "' not found",
183 CompilerMessageLocation.NO_LOCATION);
184 return ExitCode.COMPILATION_ERROR;
185 }
186 }
187
188 MainCallParameters mainCallParameters = createMainCallParameters(arguments.main);
189 TranslationResult translationResult;
190
191 K2JSTranslator translator = new K2JSTranslator(config);
192 try {
193 //noinspection unchecked
194 translationResult = translator.translate(sourcesFiles, mainCallParameters, jsAnalysisResult);
195 }
196 catch (Exception e) {
197 throw ExceptionUtilsKt.rethrow(e);
198 }
199
200 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
201
202 AnalyzerWithCompilerReport.Companion.reportDiagnostics(translationResult.getDiagnostics(), messageCollector);
203
204 if (!(translationResult instanceof TranslationResult.Success)) return ExitCode.COMPILATION_ERROR;
205
206 TranslationResult.Success successResult = (TranslationResult.Success) translationResult;
207 OutputFileCollection outputFiles = successResult.getOutputFiles(outputFile, outputPrefixFile, outputPostfixFile);
208
209 if (outputFile.isDirectory()) {
210 messageCollector.report(CompilerMessageSeverity.ERROR,
211 "Cannot open output file '" + outputFile.getPath() + "': is a directory",
212 CompilerMessageLocation.NO_LOCATION);
213 return ExitCode.COMPILATION_ERROR;
214 }
215
216 File outputDir = outputFile.getParentFile();
217 if (outputDir == null) {
218 outputDir = outputFile.getAbsoluteFile().getParentFile();
219 }
220
221 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
222
223 OutputUtilsKt.writeAll(outputFiles, outputDir, messageCollector);
224
225 return OK;
226 }
227
228 private static void reportCompiledSourcesList(@NotNull MessageCollector messageCollector, @NotNull List<KtFile> sourceFiles) {
229 Iterable<String> fileNames = ContainerUtil.map(sourceFiles, new Function<KtFile, String>() {
230 @Override
231 public String fun(@Nullable KtFile file) {
232 assert file != null;
233 VirtualFile virtualFile = file.getVirtualFile();
234 if (virtualFile != null) {
235 return FileUtil.toSystemDependentName(virtualFile.getPath());
236 }
237 return file.getName() + "(no virtual file)";
238 }
239 });
240 messageCollector.report(CompilerMessageSeverity.LOGGING, "Compiling source files: " + Joiner.on(", ").join(fileNames),
241 CompilerMessageLocation.NO_LOCATION);
242 }
243
244 private static AnalyzerWithCompilerReport analyzeAndReportErrors(
245 @NotNull MessageCollector messageCollector, @NotNull final List<KtFile> sources, @NotNull final JsConfig config
246 ) {
247 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(messageCollector);
248 analyzerWithCompilerReport.analyzeAndReport(sources, new AnalyzerWithCompilerReport.Analyzer() {
249 @NotNull
250 @Override
251 public AnalysisResult analyze() {
252 return TopDownAnalyzerFacadeForJS.analyzeFiles(sources, config);
253 }
254
255 @Override
256 public void reportEnvironmentErrors() {
257 }
258 });
259 return analyzerWithCompilerReport;
260 }
261
262 @Override
263 protected void setupPlatformSpecificArgumentsAndServices(
264 @NotNull CompilerConfiguration configuration, @NotNull K2JSCompilerArguments arguments,
265 @NotNull Services services
266 ) {
267 MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
268
269 if (arguments.target != null) {
270 assert arguments.target == "v5" : "Unsupported ECMA version: " + arguments.target;
271 }
272 configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.defaultVersion());
273
274 if (arguments.sourceMap) {
275 configuration.put(JSConfigurationKeys.SOURCE_MAP, true);
276 }
277 if (arguments.metaInfo) {
278 configuration.put(JSConfigurationKeys.META_INFO, true);
279 }
280
281 List<String> libraries = new SmartList<String>();
282 if (!arguments.noStdlib) {
283 libraries.add(0, PathUtil.getKotlinPathsForCompiler().getJsStdLibJarPath().getAbsolutePath());
284 }
285
286 if (arguments.libraries != null) {
287 ContainerUtil.addAllNotNull(libraries, arguments.libraries.split(File.pathSeparator));
288 }
289
290 configuration.put(JSConfigurationKeys.LIBRARIES, libraries);
291
292 String moduleKindName = arguments.moduleKind;
293 ModuleKind moduleKind = moduleKindName != null ? moduleKindMap.get(moduleKindName) : ModuleKind.PLAIN;
294 if (moduleKind == null) {
295 messageCollector.report(CompilerMessageSeverity.ERROR, "Unknown module kind: " + moduleKindName + ". " +
296 "Valid values are: plain, amd, commonjs, umd",
297 CompilerMessageLocation.NO_LOCATION);
298 }
299 configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind);
300 }
301
302 private static MainCallParameters createMainCallParameters(String main) {
303 if (K2JsArgumentConstants.NO_CALL.equals(main)) {
304 return MainCallParameters.noCall();
305 }
306 else {
307 return MainCallParameters.mainWithoutArguments();
308 }
309 }
310 }