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