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