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