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