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.jvm.compiler;
018
019 import com.google.common.base.Function;
020 import com.google.common.base.Joiner;
021 import com.google.common.collect.Collections2;
022 import com.google.common.collect.Lists;
023 import com.google.common.collect.Maps;
024 import com.intellij.util.ArrayUtil;
025 import kotlin.Unit;
026 import kotlin.jvm.functions.Function0;
027 import kotlin.jvm.functions.Function1;
028 import org.jetbrains.annotations.NotNull;
029 import org.jetbrains.annotations.Nullable;
030 import org.jetbrains.kotlin.analyzer.AnalysisResult;
031 import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics;
032 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
033 import org.jetbrains.kotlin.cli.common.CompilerPlugin;
034 import org.jetbrains.kotlin.cli.common.CompilerPluginContext;
035 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
036 import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
037 import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
038 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
039 import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
040 import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
041 import org.jetbrains.kotlin.cli.jvm.config.ModuleNameKt;
042 import org.jetbrains.kotlin.codegen.*;
043 import org.jetbrains.kotlin.codegen.state.GenerationState;
044 import org.jetbrains.kotlin.config.CompilerConfiguration;
045 import org.jetbrains.kotlin.config.ContentRootsKt;
046 import org.jetbrains.kotlin.context.ModuleContext;
047 import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
048 import org.jetbrains.kotlin.idea.MainFunctionDetector;
049 import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager;
050 import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache;
051 import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents;
052 import org.jetbrains.kotlin.modules.JavaRootPath;
053 import org.jetbrains.kotlin.modules.Module;
054 import org.jetbrains.kotlin.modules.TargetId;
055 import org.jetbrains.kotlin.modules.TargetIdKt;
056 import org.jetbrains.kotlin.name.FqName;
057 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
058 import org.jetbrains.kotlin.psi.KtFile;
059 import org.jetbrains.kotlin.psi.KtScript;
060 import org.jetbrains.kotlin.resolve.BindingTrace;
061 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
062 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
063 import org.jetbrains.kotlin.util.PerformanceCounter;
064 import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
065 import org.jetbrains.kotlin.utils.KotlinPaths;
066
067 import java.io.File;
068 import java.io.PrintStream;
069 import java.lang.reflect.Constructor;
070 import java.lang.reflect.InvocationTargetException;
071 import java.net.URL;
072 import java.net.URLClassLoader;
073 import java.util.*;
074 import java.util.concurrent.TimeUnit;
075
076 public class KotlinToJVMBytecodeCompiler {
077
078 private KotlinToJVMBytecodeCompiler() {
079 }
080
081 @NotNull
082 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) {
083 List<String> result = Lists.newArrayList();
084
085 for (String sourceFile : module.getSourceFiles()) {
086 File source = new File(sourceFile);
087 if (!source.isAbsolute()) {
088 source = new File(directory, sourceFile);
089 }
090 result.add(source.getAbsolutePath());
091 }
092 return result;
093 }
094
095 private static void writeOutput(
096 @NotNull CompilerConfiguration configuration,
097 @NotNull ClassFileFactory outputFiles,
098 @Nullable File outputDir,
099 @Nullable File jarPath,
100 boolean jarRuntime,
101 @Nullable FqName mainClass
102 ) {
103 if (jarPath != null) {
104 CompileEnvironmentUtil.writeToJar(jarPath, jarRuntime, mainClass, outputFiles);
105 }
106 else {
107 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
108 OutputUtilsKt.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector);
109 }
110 }
111
112 public static boolean compileModules(
113 @NotNull KotlinCoreEnvironment environment,
114 @NotNull CompilerConfiguration configuration,
115 @NotNull List<Module> chunk,
116 @NotNull File directory,
117 @Nullable File jarPath,
118 @NotNull List<String> friendPaths,
119 boolean jarRuntime
120 ) {
121 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap();
122
123 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
124
125 ModuleVisibilityManager moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.getProject());
126
127 for (Module module: chunk) {
128 moduleVisibilityManager.addModule(module);
129 }
130
131 for (String path : friendPaths) {
132 moduleVisibilityManager.addFriendPath(path);
133 }
134
135 String targetDescription = "in targets [" + Joiner.on(", ").join(Collections2.transform(chunk, new Function<Module, String>() {
136 @Override
137 public String apply(@Nullable Module input) {
138 return input != null ? input.getModuleName() + "-" + input.getModuleType() : "<null>";
139 }
140 })) + "] ";
141 AnalysisResult result = analyze(environment, targetDescription);
142 if (result == null) {
143 return false;
144 }
145
146 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
147
148 result.throwIfError();
149
150 for (Module module : chunk) {
151 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
152 List<KtFile> jetFiles = CompileEnvironmentUtil.getKtFiles(
153 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() {
154 @Override
155 public Unit invoke(String s) {
156 throw new IllegalStateException("Should have been checked before: " + s);
157 }
158 }
159 );
160 File moduleOutputDirectory = new File(module.getOutputDirectory());
161 GenerationState generationState =
162 generate(environment, result, jetFiles, module, moduleOutputDirectory,
163 module.getModuleName());
164 outputFiles.put(module, generationState.getFactory());
165 }
166
167 for (Module module : chunk) {
168 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
169 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null);
170 }
171 return true;
172 }
173
174 @NotNull
175 public static CompilerConfiguration createCompilerConfiguration(
176 @NotNull CompilerConfiguration base,
177 @NotNull List<Module> chunk,
178 @NotNull File directory
179 ) {
180 CompilerConfiguration configuration = base.copy();
181
182 for (Module module : chunk) {
183 ContentRootsKt.addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module));
184 }
185
186 for (Module module : chunk) {
187 for (JavaRootPath javaRootPath : module.getJavaSourceRoots()) {
188 JvmContentRootsKt.addJavaSourceRoot(configuration, new File(javaRootPath.getPath()), javaRootPath.getPackagePrefix());
189 }
190 }
191
192 for (Module module : chunk) {
193 for (String classpathRoot : module.getClasspathRoots()) {
194 JvmContentRootsKt.addJvmClasspathRoot(configuration, new File(classpathRoot));
195 }
196 }
197
198 for (Module module : chunk) {
199 configuration.add(JVMConfigurationKeys.MODULES, module);
200 }
201
202 return configuration;
203 }
204
205 @Nullable
206 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<KtFile> files) {
207 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
208 FqName mainClass = null;
209 for (KtFile file : files) {
210 if (mainFunctionDetector.hasMain(file.getDeclarations())) {
211 if (mainClass != null) {
212 // more than one main
213 return null;
214 }
215 FqName fqName = file.getPackageFqName();
216 mainClass = JvmFileClassUtil.getFileClassInfoNoResolve(file).getFacadeClassFqName();
217 }
218 }
219 return mainClass;
220 }
221
222 public static boolean compileBunchOfSources(
223 @NotNull KotlinCoreEnvironment environment,
224 @Nullable File jar,
225 @Nullable File outputDir,
226 @NotNull List<String> friendPaths,
227 boolean includeRuntime
228 ) {
229
230 ModuleVisibilityManager moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.getProject());
231
232 for (String path : friendPaths) {
233 moduleVisibilityManager.addFriendPath(path);
234 }
235
236 GenerationState generationState = analyzeAndGenerate(environment);
237 if (generationState == null) {
238 return false;
239 }
240
241 FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
242
243 try {
244 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass);
245 return true;
246 }
247 finally {
248 generationState.destroy();
249 }
250 }
251
252 public static void compileAndExecuteScript(
253 @NotNull CompilerConfiguration configuration,
254 @NotNull KotlinPaths paths,
255 @NotNull KotlinCoreEnvironment environment,
256 @NotNull List<String> scriptArgs
257 ) {
258 Class<?> scriptClass = compileScript(configuration, paths, environment);
259 if (scriptClass == null) return;
260 Constructor<?> scriptConstructor = getScriptConstructor(scriptClass);
261
262 try {
263 scriptConstructor.newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)});
264 }
265 catch (Throwable e) {
266 reportExceptionFromScript(e);
267 }
268 }
269
270 private static void reportExceptionFromScript(@NotNull Throwable exception) {
271 // expecting InvocationTargetException from constructor invocation with cause that describes the actual cause
272 PrintStream stream = System.err;
273 Throwable cause = exception.getCause();
274 if (!(exception instanceof InvocationTargetException) || cause == null) {
275 exception.printStackTrace(stream);
276 return;
277 }
278 stream.println(cause);
279 StackTraceElement[] fullTrace = cause.getStackTrace();
280 int relevantEntries = fullTrace.length - exception.getStackTrace().length;
281 for (int i = 0; i < relevantEntries; i++) {
282 stream.println("\tat " + fullTrace[i]);
283 }
284 }
285
286 @NotNull
287 private static Constructor<?> getScriptConstructor(Class<?> scriptClass) {
288 try {
289 return scriptClass.getConstructor(String[].class);
290 }
291 catch (NoSuchMethodException e) {
292 throw ExceptionUtilsKt.rethrow(e);
293 }
294 }
295
296 @Nullable
297 public static Class<?> compileScript(
298 @NotNull CompilerConfiguration configuration,
299 @NotNull KotlinPaths paths,
300 @NotNull KotlinCoreEnvironment environment
301 ) {
302 GenerationState state = analyzeAndGenerate(environment);
303 if (state == null) {
304 return null;
305 }
306
307 GeneratedClassLoader classLoader;
308 try {
309 List<URL> classPaths = Lists.newArrayList(paths.getRuntimePath().toURI().toURL());
310 for (File file : JvmContentRootsKt.getJvmClasspathRoots(configuration)) {
311 classPaths.add(file.toURI().toURL());
312 }
313 //noinspection UnnecessaryFullyQualifiedName
314 classLoader = new GeneratedClassLoader(state.getFactory(),
315 new URLClassLoader(classPaths.toArray(new URL[classPaths.size()]), null)
316 );
317
318 KtScript script = environment.getSourceFiles().get(0).getScript();
319 assert script != null : "Script must be parsed";
320 FqName nameForScript = script.getFqName();
321 return classLoader.loadClass(nameForScript.asString());
322 }
323 catch (Exception e) {
324 throw new RuntimeException("Failed to evaluate script: " + e, e);
325 }
326 }
327
328 @Nullable
329 public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) {
330 AnalysisResult result = analyze(environment, null);
331
332 if (result == null) {
333 return null;
334 }
335
336 if (!result.getShouldGenerateCode()) return null;
337
338 result.throwIfError();
339
340 return generate(environment, result, environment.getSourceFiles(), null, null, null);
341 }
342
343 @Nullable
344 private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment, @Nullable String targetDescription) {
345 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
346 assert collector != null;
347
348 long analysisStart = PerformanceCounter.Companion.currentTime();
349 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector);
350 analyzerWithCompilerReport.analyzeAndReport(
351 environment.getSourceFiles(), new Function0<AnalysisResult>() {
352 @NotNull
353 @Override
354 public AnalysisResult invoke() {
355 BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
356 ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject(),
357 ModuleNameKt
358 .getModuleName(environment));
359
360 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
361 moduleContext,
362 environment.getSourceFiles(),
363 sharedTrace,
364 environment.getConfiguration().get(JVMConfigurationKeys.MODULES),
365 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS),
366 new JvmPackagePartProvider(environment)
367 );
368 }
369 }
370 );
371 long analysisNanos = PerformanceCounter.Companion.currentTime() - analysisStart;
372 String message = "ANALYZE: " + environment.getSourceFiles().size() + " files (" +
373 environment.getSourceLinesOfCode() + " lines) " +
374 (targetDescription != null ? targetDescription : "") +
375 "in " + TimeUnit.NANOSECONDS.toMillis(analysisNanos) + " ms";
376 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message);
377
378 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult();
379 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles();
380
381 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(),
382 environment.getSourceFiles());
383 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
384 plugin.processFiles(context);
385 }
386
387 return analyzerWithCompilerReport.hasErrors() ? null : result;
388 }
389
390 @NotNull
391 private static GenerationState generate(
392 @NotNull KotlinCoreEnvironment environment,
393 @NotNull AnalysisResult result,
394 @NotNull List<KtFile> sourceFiles,
395 @Nullable Module module,
396 File outputDirectory,
397 String moduleName
398 ) {
399 CompilerConfiguration configuration = environment.getConfiguration();
400 IncrementalCompilationComponents incrementalCompilationComponents = configuration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS);
401
402 Collection<FqName> packagesWithObsoleteParts;
403 List<FqName> obsoleteMultifileClasses;
404 TargetId targetId = null;
405
406 if (module == null || incrementalCompilationComponents == null) {
407 packagesWithObsoleteParts = Collections.emptySet();
408 obsoleteMultifileClasses = Collections.emptyList();
409 }
410 else {
411 targetId = TargetIdKt.TargetId(module);
412 IncrementalCache incrementalCache = incrementalCompilationComponents.getIncrementalCache(targetId);
413
414 packagesWithObsoleteParts = new HashSet<FqName>();
415 for (String internalName : incrementalCache.getObsoletePackageParts()) {
416 packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName());
417 }
418
419 obsoleteMultifileClasses = new ArrayList<FqName>();
420 for (String obsoleteFacadeInternalName : incrementalCache.getObsoleteMultifileClasses()) {
421 obsoleteMultifileClasses.add(JvmClassName.byInternalName(obsoleteFacadeInternalName).getFqNameForClassNameWithoutDollars());
422 }
423 }
424 GenerationState generationState = new GenerationState(
425 environment.getProject(),
426 ClassBuilderFactories.BINARIES,
427 result.getModuleDescriptor(),
428 result.getBindingContext(),
429 sourceFiles,
430 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
431 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
432 GenerationState.GenerateClassFilter.GENERATE_ALL,
433 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
434 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
435 /* useTypeTableInSerializer = */ false,
436 packagesWithObsoleteParts,
437 obsoleteMultifileClasses,
438 targetId,
439 moduleName,
440 outputDirectory,
441 incrementalCompilationComponents,
442 configuration.get(JVMConfigurationKeys.MULTIFILE_FACADES_OPEN, false)
443 );
444 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
445
446 long generationStart = PerformanceCounter.Companion.currentTime();
447
448 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
449
450 long generationNanos = PerformanceCounter.Companion.currentTime() - generationStart;
451 String desc = module != null ? "target " + module.getModuleName() + "-" + module.getModuleType() + " " : "";
452 String message = "GENERATE: " + sourceFiles.size() + " files (" +
453 environment.countLinesOfCode(sourceFiles) + " lines) " + desc + "in " + TimeUnit.NANOSECONDS.toMillis(generationNanos) + " ms";
454 K2JVMCompiler.Companion.reportPerf(environment.getConfiguration(), message);
455 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
456
457 AnalyzerWithCompilerReport.reportDiagnostics(
458 new FilteredJvmDiagnostics(
459 generationState.getCollectedExtraJvmDiagnostics(),
460 result.getBindingContext().getDiagnostics()
461 ),
462 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
463 );
464 ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
465 return generationState;
466 }
467 }