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 }