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.collect.Lists;
020 import com.google.common.collect.Maps;
021 import com.intellij.openapi.Disposable;
022 import com.intellij.openapi.util.Disposer;
023 import com.intellij.util.ArrayUtil;
024 import kotlin.Unit;
025 import kotlin.jvm.functions.Function0;
026 import kotlin.jvm.functions.Function1;
027 import org.jetbrains.annotations.NotNull;
028 import org.jetbrains.annotations.Nullable;
029 import org.jetbrains.kotlin.analyzer.AnalysisResult;
030 import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics;
031 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
032 import org.jetbrains.kotlin.cli.common.CompilerPlugin;
033 import org.jetbrains.kotlin.cli.common.CompilerPluginContext;
034 import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
035 import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
036 import org.jetbrains.kotlin.cli.common.modules.Module;
037 import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsPackage;
038 import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
039 import org.jetbrains.kotlin.codegen.*;
040 import org.jetbrains.kotlin.codegen.state.GenerationState;
041 import org.jetbrains.kotlin.codegen.state.Progress;
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.cache.IncrementalCache;
047 import org.jetbrains.kotlin.load.kotlin.incremental.cache.IncrementalCacheProvider;
048 import org.jetbrains.kotlin.name.FqName;
049 import org.jetbrains.kotlin.parsing.JetScriptDefinition;
050 import org.jetbrains.kotlin.parsing.JetScriptDefinitionProvider;
051 import org.jetbrains.kotlin.psi.JetFile;
052 import org.jetbrains.kotlin.resolve.AnalyzerScriptParameter;
053 import org.jetbrains.kotlin.resolve.BindingTrace;
054 import org.jetbrains.kotlin.resolve.BindingTraceContext;
055 import org.jetbrains.kotlin.resolve.ScriptNameUtil;
056 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
057 import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
058 import org.jetbrains.kotlin.utils.KotlinPaths;
059
060 import java.io.File;
061 import java.net.URL;
062 import java.net.URLClassLoader;
063 import java.util.Collection;
064 import java.util.HashSet;
065 import java.util.List;
066 import java.util.Map;
067
068 import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.*;
069 import static org.jetbrains.kotlin.config.ConfigPackage.addKotlinSourceRoots;
070
071 public class KotlinToJVMBytecodeCompiler {
072
073 private KotlinToJVMBytecodeCompiler() {
074 }
075
076 @NotNull
077 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) {
078 List<String> result = Lists.newArrayList();
079
080 for (String sourceFile : module.getSourceFiles()) {
081 File source = new File(sourceFile);
082 if (!source.isAbsolute()) {
083 source = new File(directory, sourceFile);
084 }
085
086 if (!source.exists()) {
087 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName());
088 }
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 OutputUtilsPackage.writeAll(outputFiles, outputDir == null ? new File(".") : outputDir, messageCollector);
109 }
110 }
111
112 public static boolean compileModules(
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 CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory);
122
123 Disposable parentDisposable = Disposer.newDisposable();
124 KotlinCoreEnvironment environment = null;
125 try {
126 environment = KotlinCoreEnvironment
127 .createForProduction(parentDisposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
128
129 AnalysisResult result = analyze(environment);
130 if (result == null) {
131 return false;
132 }
133
134 result.throwIfError();
135
136 for (Module module : chunk) {
137 List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles(
138 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() {
139 @Override
140 public Unit invoke(String s) {
141 throw new IllegalStateException("Should have been checked before: " + s);
142 }
143 }
144 );
145 GenerationState generationState =
146 generate(environment, result, jetFiles, module.getModuleName(), new File(module.getOutputDirectory()));
147 outputFiles.put(module, generationState.getFactory());
148 }
149 }
150 finally {
151 if (environment != null) {
152 Disposer.dispose(parentDisposable);
153 }
154 }
155
156 for (Module module : chunk) {
157 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null);
158 }
159 return true;
160 }
161
162 @NotNull
163 private static CompilerConfiguration createCompilerConfiguration(
164 @NotNull CompilerConfiguration base,
165 @NotNull List<Module> chunk,
166 @NotNull File directory
167 ) {
168 CompilerConfiguration configuration = base.copy();
169
170 for (Module module : chunk) {
171 addKotlinSourceRoots(configuration, getAbsolutePaths(directory, module));
172 }
173
174 for (Module module : chunk) {
175 for (String javaSourceRoot : module.getJavaSourceRoots()) {
176 addJavaSourceRoot(configuration, new File(javaSourceRoot));
177 }
178 }
179
180 for (Module module : chunk) {
181 for (String classpathRoot : module.getClasspathRoots()) {
182 addJvmClasspathRoot(configuration, new File(classpathRoot));
183 }
184 }
185
186 for (Module module : chunk) {
187 for (String annotationsRoot : module.getAnnotationsRoots()) {
188 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
189 }
190
191 configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName());
192 }
193
194 return configuration;
195 }
196
197 @Nullable
198 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
199 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
200 FqName mainClass = null;
201 for (JetFile 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 = PackageClassUtils.getPackageClassFqName(fqName);
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 : 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()]),
283 kotlin.KotlinPackage.class.getClassLoader())
284 );
285
286 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript());
287 return classLoader.loadClass(nameForScript.asString());
288 }
289 catch (Exception e) {
290 throw new RuntimeException("Failed to evaluate script: " + e, e);
291 }
292 }
293
294 @Nullable
295 public static GenerationState analyzeAndGenerate(@NotNull KotlinCoreEnvironment environment) {
296 AnalysisResult result = analyze(environment);
297
298 if (result == null) {
299 return null;
300 }
301
302 result.throwIfError();
303
304 return generate(environment, result, environment.getSourceFiles(), null, null);
305 }
306
307 @Nullable
308 private static AnalysisResult analyze(@NotNull final KotlinCoreEnvironment environment) {
309 MessageCollector collector = environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
310 assert collector != null;
311
312 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(collector);
313 analyzerWithCompilerReport.analyzeAndReport(
314 environment.getSourceFiles(), new Function0<AnalysisResult>() {
315 @NotNull
316 @Override
317 public AnalysisResult invoke() {
318 BindingTrace sharedTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
319 ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject());
320
321 return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
322 moduleContext,
323 environment.getSourceFiles(),
324 sharedTrace,
325 environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS),
326 environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER)
327 );
328 }
329 }
330 );
331
332 AnalysisResult result = analyzerWithCompilerReport.getAnalysisResult();
333 assert result != null : "AnalysisResult should be non-null, compiling: " + environment.getSourceFiles();
334
335 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), result.getBindingContext(),
336 environment.getSourceFiles());
337 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
338 plugin.processFiles(context);
339 }
340
341 return analyzerWithCompilerReport.hasErrors() ? null : result;
342 }
343
344 @NotNull
345 private static GenerationState generate(
346 @NotNull KotlinCoreEnvironment environment,
347 @NotNull AnalysisResult result,
348 @NotNull List<JetFile> sourceFiles,
349 @Nullable String moduleId,
350 File outputDirectory
351 ) {
352 CompilerConfiguration configuration = environment.getConfiguration();
353 IncrementalCacheProvider incrementalCacheProvider = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_PROVIDER);
354
355 Collection<FqName> packagesWithObsoleteParts;
356 if (moduleId == null || incrementalCacheProvider == null) {
357 packagesWithObsoleteParts = null;
358 }
359 else {
360 IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(moduleId);
361 packagesWithObsoleteParts = new HashSet<FqName>();
362 for (String internalName : incrementalCache.getObsoletePackageParts()) {
363 packagesWithObsoleteParts.add(JvmClassName.byInternalName(internalName).getPackageFqName());
364 }
365 }
366 BindingTraceContext diagnosticHolder = new BindingTraceContext();
367 GenerationState generationState = new GenerationState(
368 environment.getProject(),
369 ClassBuilderFactories.BINARIES,
370 Progress.DEAF,
371 result.getModuleDescriptor(),
372 result.getBindingContext(),
373 sourceFiles,
374 configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
375 configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
376 GenerationState.GenerateClassFilter.GENERATE_ALL,
377 configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
378 configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
379 packagesWithObsoleteParts,
380 moduleId,
381 diagnosticHolder,
382 outputDirectory
383 );
384 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
385 AnalyzerWithCompilerReport.reportDiagnostics(
386 new FilteredJvmDiagnostics(
387 diagnosticHolder.getBindingContext().getDiagnostics(),
388 result.getBindingContext().getDiagnostics()
389 ),
390 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
391 );
392 return generationState;
393 }
394 }