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