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