001 /*
002 * Copyright 2010-2014 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.jet.cli.jvm.compiler;
018
019 import com.google.common.base.Predicates;
020 import com.google.common.collect.Lists;
021 import com.google.common.collect.Maps;
022 import com.intellij.openapi.Disposable;
023 import com.intellij.openapi.util.Disposer;
024 import com.intellij.psi.PsiFile;
025 import com.intellij.util.ArrayUtil;
026 import kotlin.Function0;
027 import kotlin.Function1;
028 import kotlin.Unit;
029 import kotlin.modules.AllModules;
030 import kotlin.modules.Module;
031 import org.jetbrains.annotations.NotNull;
032 import org.jetbrains.annotations.Nullable;
033 import org.jetbrains.jet.analyzer.AnalyzeExhaust;
034 import org.jetbrains.jet.asJava.FilteredJvmDiagnostics;
035 import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
036 import org.jetbrains.jet.cli.common.CompilerPlugin;
037 import org.jetbrains.jet.cli.common.CompilerPluginContext;
038 import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
039 import org.jetbrains.jet.cli.common.messages.MessageCollector;
040 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
041 import org.jetbrains.jet.codegen.*;
042 import org.jetbrains.jet.codegen.inline.InlineCodegenUtil;
043 import org.jetbrains.jet.codegen.optimization.OptimizationUtils;
044 import org.jetbrains.jet.codegen.state.GenerationState;
045 import org.jetbrains.jet.codegen.state.Progress;
046 import org.jetbrains.jet.config.CommonConfigurationKeys;
047 import org.jetbrains.jet.config.CompilerConfiguration;
048 import org.jetbrains.jet.lang.descriptors.impl.ModuleDescriptorImpl;
049 import org.jetbrains.jet.lang.parsing.JetScriptDefinition;
050 import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider;
051 import org.jetbrains.jet.lang.psi.JetFile;
052 import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter;
053 import org.jetbrains.jet.lang.resolve.BindingTrace;
054 import org.jetbrains.jet.lang.resolve.BindingTraceContext;
055 import org.jetbrains.jet.lang.resolve.ScriptNameUtil;
056 import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
057 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
058 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalCache;
059 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalCacheProvider;
060 import org.jetbrains.jet.lang.resolve.kotlin.incremental.IncrementalPackage;
061 import org.jetbrains.jet.lang.resolve.name.FqName;
062 import org.jetbrains.jet.plugin.MainFunctionDetector;
063 import org.jetbrains.jet.utils.KotlinPaths;
064
065 import java.io.File;
066 import java.net.URL;
067 import java.net.URLClassLoader;
068 import java.util.Collection;
069 import java.util.List;
070 import java.util.Map;
071
072 public class KotlinToJVMBytecodeCompiler {
073
074 private KotlinToJVMBytecodeCompiler() {
075 }
076
077 @NotNull
078 private static List<String> getAbsolutePaths(@NotNull File directory, @NotNull Module module) {
079 List<String> result = Lists.newArrayList();
080
081 for (String sourceFile : module.getSourceFiles()) {
082 File source = new File(sourceFile);
083 if (!source.isAbsolute()) {
084 source = new File(directory, sourceFile);
085 }
086
087 if (!source.exists()) {
088 throw new CompileEnvironmentException("'" + source + "' does not exist in module " + module.getModuleName());
089 }
090
091 result.add(source.getAbsolutePath());
092 }
093 return result;
094 }
095
096 private static void writeOutput(
097 @NotNull CompilerConfiguration configuration,
098 @NotNull ClassFileFactory outputFiles,
099 @Nullable File outputDir,
100 @Nullable File jarPath,
101 boolean jarRuntime,
102 @Nullable FqName mainClass
103 ) {
104 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
105 CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, outputFiles, messageCollector);
106 }
107
108 public static boolean compileModules(
109 @NotNull CompilerConfiguration configuration,
110 @NotNull List<Module> chunk,
111 @NotNull File directory,
112 @Nullable File jarPath,
113 boolean jarRuntime
114 ) {
115 Map<Module, ClassFileFactory> outputFiles = Maps.newHashMap();
116
117 CompilerConfiguration compilerConfiguration = createCompilerConfiguration(configuration, chunk, directory);
118
119 Disposable parentDisposable = Disposer.newDisposable();
120 JetCoreEnvironment environment = null;
121 try {
122 environment = JetCoreEnvironment.createForProduction(parentDisposable, compilerConfiguration);
123
124 AnalyzeExhaust exhaust = analyze(environment);
125 if (exhaust == null) {
126 return false;
127 }
128
129 exhaust.throwIfError();
130
131 for (Module module : chunk) {
132 List<JetFile> jetFiles = CompileEnvironmentUtil.getJetFiles(
133 environment.getProject(), getAbsolutePaths(directory, module), new Function1<String, Unit>() {
134 @Override
135 public Unit invoke(String s) {
136 throw new IllegalStateException("Should have been checked before: " + s);
137 }
138 }
139 );
140 GenerationState generationState =
141 generate(environment, exhaust, jetFiles, module.getModuleName(), new File(module.getOutputDirectory()));
142 outputFiles.put(module, generationState.getFactory());
143 }
144 }
145 finally {
146 if (environment != null) {
147 Disposer.dispose(parentDisposable);
148 }
149 }
150
151 for (Module module : chunk) {
152 writeOutput(configuration, outputFiles.get(module), new File(module.getOutputDirectory()), jarPath, jarRuntime, null);
153 }
154 return true;
155 }
156
157 @NotNull
158 private static CompilerConfiguration createCompilerConfiguration(
159 @NotNull CompilerConfiguration base,
160 @NotNull List<Module> chunk,
161 @NotNull File directory
162 ) {
163 CompilerConfiguration configuration = base.copy();
164 for (Module module : chunk) {
165 configuration.addAll(CommonConfigurationKeys.SOURCE_ROOTS_KEY, getAbsolutePaths(directory, module));
166
167 for (String classpathRoot : module.getClasspathRoots()) {
168 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
169 }
170
171 for (String annotationsRoot : module.getAnnotationsRoots()) {
172 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
173 }
174
175 configuration.add(JVMConfigurationKeys.MODULE_IDS, module.getModuleName());
176 }
177
178 return configuration;
179 }
180
181 @Nullable
182 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
183 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
184 FqName mainClass = null;
185 for (JetFile file : files) {
186 if (mainFunctionDetector.hasMain(file.getDeclarations())) {
187 if (mainClass != null) {
188 // more than one main
189 return null;
190 }
191 FqName fqName = file.getPackageFqName();
192 mainClass = PackageClassUtils.getPackageClassFqName(fqName);
193 }
194 }
195 return mainClass;
196 }
197
198 public static boolean compileBunchOfSources(
199 @NotNull JetCoreEnvironment environment,
200 @Nullable File jar,
201 @Nullable File outputDir,
202 boolean includeRuntime
203 ) {
204
205 GenerationState generationState = analyzeAndGenerate(environment);
206 if (generationState == null) {
207 return false;
208 }
209
210 FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
211
212 try {
213 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDir, jar, includeRuntime, mainClass);
214 return true;
215 }
216 finally {
217 generationState.destroy();
218 }
219 }
220
221 public static void compileAndExecuteScript(
222 @NotNull KotlinPaths paths,
223 @NotNull JetCoreEnvironment environment,
224 @NotNull List<String> scriptArgs
225 ) {
226 Class<?> scriptClass = compileScript(paths, environment);
227 if (scriptClass == null) return;
228
229 try {
230 scriptClass.getConstructor(String[].class).newInstance(new Object[] {ArrayUtil.toStringArray(scriptArgs)});
231 }
232 catch (RuntimeException e) {
233 throw e;
234 }
235 catch (Exception e) {
236 throw new RuntimeException("Failed to evaluate script: " + e, e);
237 }
238 }
239
240 @Nullable
241 public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) {
242 List<AnalyzerScriptParameter> scriptParameters = environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS);
243 if (!scriptParameters.isEmpty()) {
244 JetScriptDefinitionProvider.getInstance(environment.getProject()).addScriptDefinition(
245 new JetScriptDefinition(".kts", scriptParameters)
246 );
247 }
248 GenerationState state = analyzeAndGenerate(environment);
249 if (state == null) {
250 return null;
251 }
252
253 GeneratedClassLoader classLoader;
254 try {
255 classLoader = new GeneratedClassLoader(state.getFactory(),
256 new URLClassLoader(new URL[] {
257 // TODO: add all classpath
258 paths.getRuntimePath().toURI().toURL()
259 }, AllModules.class.getClassLoader())
260 );
261
262 FqName nameForScript = ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0).getScript());
263 return classLoader.loadClass(nameForScript.asString());
264 }
265 catch (Exception e) {
266 throw new RuntimeException("Failed to evaluate script: " + e, e);
267 }
268 }
269
270 @Nullable
271 public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) {
272 AnalyzeExhaust exhaust = analyze(environment);
273
274 if (exhaust == null) {
275 return null;
276 }
277
278 exhaust.throwIfError();
279
280 return generate(environment, exhaust, environment.getSourceFiles(), null, null);
281 }
282
283 @Nullable
284 private static AnalyzeExhaust analyze(@NotNull final JetCoreEnvironment environment) {
285 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
286 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
287 analyzerWithCompilerReport.analyzeAndReport(
288 environment.getSourceFiles(), new Function0<AnalyzeExhaust>() {
289 @NotNull
290 @Override
291 public AnalyzeExhaust invoke() {
292 CliLightClassGenerationSupport support = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject());
293 BindingTrace sharedTrace = support.getTrace();
294 ModuleDescriptorImpl sharedModule = support.getModule();
295
296 IncrementalCacheProvider incrementalCacheProvider = IncrementalCacheProvider.OBJECT$.getInstance();
297 File incrementalCacheBaseDir = environment.getConfiguration().get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR);
298 final IncrementalCache incrementalCache;
299 if (incrementalCacheProvider != null && incrementalCacheBaseDir != null) {
300 incrementalCache = incrementalCacheProvider.getIncrementalCache(incrementalCacheBaseDir);
301 Disposer.register(environment.getApplication(), new Disposable() {
302 @Override
303 public void dispose() {
304 incrementalCache.close();
305 }
306 });
307 }
308 else {
309 incrementalCache = null;
310 }
311
312
313 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
314 environment.getProject(),
315 environment.getSourceFiles(),
316 sharedTrace,
317 Predicates.<PsiFile>alwaysTrue(),
318 sharedModule,
319 environment.getConfiguration().get(JVMConfigurationKeys.MODULE_IDS),
320 incrementalCache
321 );
322 }
323 }
324 );
325
326 AnalyzeExhaust exhaust = analyzerWithCompilerReport.getAnalyzeExhaust();
327 assert exhaust != null : "AnalyzeExhaust should be non-null, compiling: " + environment.getSourceFiles();
328
329 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), exhaust.getBindingContext(),
330 environment.getSourceFiles());
331 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
332 plugin.processFiles(context);
333 }
334
335 return analyzerWithCompilerReport.hasErrors() ? null : exhaust;
336 }
337
338 @NotNull
339 private static GenerationState generate(
340 @NotNull JetCoreEnvironment environment,
341 @NotNull AnalyzeExhaust exhaust,
342 @NotNull List<JetFile> sourceFiles,
343 @Nullable String moduleId,
344 File outputDirectory
345 ) {
346 CompilerConfiguration configuration = environment.getConfiguration();
347 File incrementalCacheDir = configuration.get(JVMConfigurationKeys.INCREMENTAL_CACHE_BASE_DIR);
348 IncrementalCacheProvider incrementalCacheProvider = IncrementalCacheProvider.OBJECT$.getInstance();
349
350 Collection<FqName> packagesWithRemovedFiles;
351 if (incrementalCacheDir == null || moduleId == null || incrementalCacheProvider == null) {
352 packagesWithRemovedFiles = null;
353 }
354 else {
355 IncrementalCache incrementalCache = incrementalCacheProvider.getIncrementalCache(incrementalCacheDir);
356 try {
357 packagesWithRemovedFiles = IncrementalPackage.getPackagesWithRemovedFiles(
358 incrementalCache, moduleId, environment.getSourceFiles());
359 }
360 finally {
361 incrementalCache.close();
362 }
363 }
364 BindingTraceContext diagnosticHolder = new BindingTraceContext();
365 GenerationState generationState = new GenerationState(
366 environment.getProject(),
367 ClassBuilderFactories.BINARIES,
368 Progress.DEAF,
369 exhaust.getModuleDescriptor(),
370 exhaust.getBindingContext(),
371 sourceFiles,
372 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
373 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
374 GenerationState.GenerateClassFilter.GENERATE_ALL,
375 configuration.get(JVMConfigurationKeys.ENABLE_INLINE, InlineCodegenUtil.DEFAULT_INLINE_FLAG),
376 configuration.get(JVMConfigurationKeys.ENABLE_OPTIMIZATION, OptimizationUtils.DEFAULT_OPTIMIZATION_FLAG),
377 packagesWithRemovedFiles,
378 moduleId,
379 diagnosticHolder,
380 outputDirectory);
381 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
382 AnalyzerWithCompilerReport.reportDiagnostics(
383 new FilteredJvmDiagnostics(
384 diagnosticHolder.getBindingContext().getDiagnostics(),
385 exhaust.getBindingContext().getDiagnostics()
386 ),
387 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
388 );
389 return generationState;
390 }
391 }