001 /*
002 * Copyright 2010-2013 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.Predicate;
020 import com.google.common.base.Predicates;
021 import com.intellij.openapi.Disposable;
022 import com.intellij.openapi.project.Project;
023 import com.intellij.openapi.util.Disposer;
024 import com.intellij.psi.PsiFile;
025 import jet.Function0;
026 import jet.modules.AllModules;
027 import jet.modules.Module;
028 import org.jetbrains.annotations.NotNull;
029 import org.jetbrains.annotations.Nullable;
030 import org.jetbrains.jet.analyzer.AnalyzeExhaust;
031 import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
032 import org.jetbrains.jet.cli.common.CompilerPlugin;
033 import org.jetbrains.jet.cli.common.CompilerPluginContext;
034 import org.jetbrains.jet.cli.common.messages.*;
035 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
036 import org.jetbrains.jet.codegen.*;
037 import org.jetbrains.jet.codegen.state.GenerationState;
038 import org.jetbrains.jet.codegen.state.Progress;
039 import org.jetbrains.jet.config.CommonConfigurationKeys;
040 import org.jetbrains.jet.config.CompilerConfiguration;
041 import org.jetbrains.jet.lang.parsing.JetScriptDefinition;
042 import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider;
043 import org.jetbrains.jet.lang.psi.JetFile;
044 import org.jetbrains.jet.lang.psi.JetPsiUtil;
045 import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter;
046 import org.jetbrains.jet.lang.resolve.BindingTrace;
047 import org.jetbrains.jet.lang.resolve.ScriptNameUtil;
048 import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
049 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
050 import org.jetbrains.jet.lang.resolve.name.FqName;
051 import org.jetbrains.jet.plugin.JetMainDetector;
052 import org.jetbrains.jet.utils.ExceptionUtils;
053 import org.jetbrains.jet.utils.KotlinPaths;
054 import org.jetbrains.jet.utils.PathUtil;
055
056 import java.io.File;
057 import java.io.FileNotFoundException;
058 import java.io.FileOutputStream;
059 import java.io.IOException;
060 import java.net.URISyntaxException;
061 import java.net.URL;
062 import java.net.URLClassLoader;
063 import java.util.Collection;
064 import java.util.Collections;
065 import java.util.LinkedList;
066 import java.util.List;
067
068 public class KotlinToJVMBytecodeCompiler {
069
070 private KotlinToJVMBytecodeCompiler() {
071 }
072
073 @Nullable
074 public static ClassFileFactory compileModule(CompilerConfiguration configuration, Module moduleBuilder, File directory) {
075 if (moduleBuilder.getSourceFiles().isEmpty()) {
076 throw new CompileEnvironmentException("No source files where defined in module " + moduleBuilder.getModuleName());
077 }
078
079 CompilerConfiguration compilerConfiguration = configuration.copy();
080 for (String sourceFile : moduleBuilder.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 " + moduleBuilder.getModuleName());
088 }
089
090 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, source.getPath());
091 }
092
093 for (String classpathRoot : moduleBuilder.getClasspathRoots()) {
094 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
095 }
096
097 for (String annotationsRoot : moduleBuilder.getAnnotationsRoots()) {
098 compilerConfiguration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
099 }
100
101 Disposable parentDisposable = CompileEnvironmentUtil.createMockDisposable();
102 JetCoreEnvironment moduleEnvironment = null;
103 try {
104 moduleEnvironment = new JetCoreEnvironment(parentDisposable, compilerConfiguration);
105
106
107 GenerationState generationState = analyzeAndGenerate(moduleEnvironment);
108 if (generationState == null) {
109 return null;
110 }
111 return generationState.getFactory();
112 } finally {
113 if (moduleEnvironment != null) {
114 Disposer.dispose(parentDisposable);
115 }
116 }
117 }
118
119 public static boolean compileModules(
120 CompilerConfiguration configuration,
121 @NotNull List<Module> modules,
122 @NotNull File directory,
123 @Nullable File jarPath,
124 @Nullable File outputDir,
125 boolean jarRuntime) {
126
127 for (Module moduleBuilder : modules) {
128 ClassFileFactory moduleFactory = compileModule(configuration, moduleBuilder, directory);
129 if (moduleFactory == null) {
130 return false;
131 }
132 if (outputDir != null) {
133 CompileEnvironmentUtil.writeToOutputDirectory(moduleFactory, outputDir);
134 }
135 else {
136 File path = jarPath != null ? jarPath : new File(directory, moduleBuilder.getModuleName() + ".jar");
137 FileOutputStream outputStream = null;
138 try {
139 outputStream = new FileOutputStream(path);
140 CompileEnvironmentUtil.writeToJar(moduleFactory, outputStream, null, jarRuntime);
141 outputStream.close();
142 }
143 catch (FileNotFoundException e) {
144 throw new CompileEnvironmentException("Invalid jar path " + path, e);
145 }
146 catch (IOException e) {
147 throw ExceptionUtils.rethrow(e);
148 }
149 finally {
150 ExceptionUtils.closeQuietly(outputStream);
151 }
152 }
153 }
154 return true;
155 }
156
157 @Nullable
158 private static FqName findMainClass(@NotNull List<JetFile> files) {
159 FqName mainClass = null;
160 for (JetFile file : files) {
161 if (JetMainDetector.hasMain(file.getDeclarations())) {
162 if (mainClass != null) {
163 // more than one main
164 return null;
165 }
166 FqName fqName = JetPsiUtil.getFQName(file);
167 mainClass = PackageClassUtils.getPackageClassFqName(fqName);
168 }
169 }
170 return mainClass;
171 }
172
173 public static boolean compileBunchOfSources(
174 JetCoreEnvironment environment,
175 @Nullable File jar,
176 @Nullable File outputDir,
177 boolean includeRuntime
178 ) {
179
180 FqName mainClass = findMainClass(environment.getSourceFiles());
181
182 GenerationState generationState = analyzeAndGenerate(environment);
183 if (generationState == null) {
184 return false;
185 }
186
187 try {
188 ClassFileFactory factory = generationState.getFactory();
189 if (jar != null) {
190 FileOutputStream os = null;
191 try {
192 os = new FileOutputStream(jar);
193 CompileEnvironmentUtil.writeToJar(factory, new FileOutputStream(jar), mainClass, includeRuntime);
194 os.close();
195 }
196 catch (FileNotFoundException e) {
197 throw new CompileEnvironmentException("Invalid jar path " + jar, e);
198 }
199 catch (IOException e) {
200 throw ExceptionUtils.rethrow(e);
201 }
202 finally {
203 ExceptionUtils.closeQuietly(os);
204 }
205 }
206 else if (outputDir != null) {
207 CompileEnvironmentUtil.writeToOutputDirectory(factory, outputDir);
208 }
209 else {
210 throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk");
211 }
212 return true;
213 }
214 finally {
215 generationState.destroy();
216 }
217 }
218
219 public static boolean compileAndExecuteScript(
220 @NotNull KotlinPaths paths,
221 @NotNull JetCoreEnvironment environment,
222 @NotNull List<String> scriptArgs) {
223 Class<?> scriptClass = compileScript(paths, environment, null);
224 if(scriptClass == null)
225 return false;
226
227 try {
228 scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])});
229 }
230 catch (RuntimeException e) {
231 throw e;
232 }
233 catch (Exception e) {
234 throw new RuntimeException("Failed to evaluate script: " + e, e);
235 }
236 return true;
237 }
238
239 private static Class<?> compileScript(
240 @NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment, @Nullable ClassLoader parentLoader) {
241
242 GenerationState generationState = analyzeAndGenerate(environment);
243 if (generationState == null) {
244 return null;
245 }
246
247 GeneratedClassLoader classLoader = null;
248 try {
249 ClassFileFactory factory = generationState.getFactory();
250 classLoader = new GeneratedClassLoader(factory,
251 new URLClassLoader(new URL[] {
252 // TODO: add all classpath
253 paths.getRuntimePath().toURI().toURL()
254 },
255 parentLoader == null ? AllModules.class.getClassLoader() : parentLoader));
256
257 JetFile scriptFile = environment.getSourceFiles().get(0);
258 return classLoader.loadClass(ScriptNameUtil.classNameForScript(scriptFile));
259 }
260 catch (Exception e) {
261 throw new RuntimeException("Failed to evaluate script: " + e, e);
262 }
263 finally {
264 if (classLoader != null) {
265 classLoader.dispose();
266 }
267 generationState.destroy();
268 }
269 }
270
271 @Nullable
272 public static GenerationState analyzeAndGenerate(JetCoreEnvironment environment) {
273 return analyzeAndGenerate(environment, environment.getConfiguration().get(JVMConfigurationKeys.STUBS, false),
274 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS));
275 }
276
277 @Nullable
278 public static GenerationState analyzeAndGenerate(
279 JetCoreEnvironment environment,
280 boolean stubs
281 ) {
282 return analyzeAndGenerate(environment, stubs,
283 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS));
284 }
285
286 @Nullable
287 public static GenerationState analyzeAndGenerate(
288 JetCoreEnvironment environment,
289 boolean stubs,
290 List<AnalyzerScriptParameter> scriptParameters
291 ) {
292 AnalyzeExhaust exhaust = analyze(environment, scriptParameters, stubs);
293
294 if (exhaust == null) {
295 return null;
296 }
297
298 exhaust.throwIfError();
299
300 return generate(environment, exhaust, stubs);
301 }
302
303 @Nullable
304 private static AnalyzeExhaust analyze(
305 final JetCoreEnvironment environment,
306 final List<AnalyzerScriptParameter> scriptParameters,
307 boolean stubs) {
308 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
309 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
310 final Predicate<PsiFile> filesToAnalyzeCompletely =
311 stubs ? Predicates.<PsiFile>alwaysFalse() : Predicates.<PsiFile>alwaysTrue();
312 analyzerWithCompilerReport.analyzeAndReport(
313 new Function0<AnalyzeExhaust>() {
314 @NotNull
315 @Override
316 public AnalyzeExhaust invoke() {
317 BindingTrace sharedTrace = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject()).getTrace();
318 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
319 environment.getProject(),
320 environment.getSourceFiles(),
321 sharedTrace,
322 scriptParameters,
323 filesToAnalyzeCompletely,
324 false
325 );
326 }
327 }, environment.getSourceFiles()
328 );
329
330 return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust();
331 }
332
333 @NotNull
334 private static GenerationState generate(
335 JetCoreEnvironment environment,
336 AnalyzeExhaust exhaust,
337 boolean stubs) {
338 Project project = environment.getProject();
339 final CompilerConfiguration configuration = environment.getConfiguration();
340 Progress backendProgress = new Progress() {
341 @Override
342 public void reportOutput(@NotNull Collection<File> sourceFiles, @Nullable File outputFile) {
343 if (outputFile == null) return;
344
345 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
346 if (messageCollector == null) return;
347
348 messageCollector.report(
349 CompilerMessageSeverity.OUTPUT,
350 OutputMessageUtil.formatOutputMessage(sourceFiles, outputFile),
351 CompilerMessageLocation.NO_LOCATION);
352 }
353 };
354 GenerationState generationState = new GenerationState(
355 project, ClassBuilderFactories.binaries(stubs), backendProgress, exhaust.getBindingContext(), environment.getSourceFiles(),
356 configuration.get(JVMConfigurationKeys.BUILTIN_TO_JAVA_TYPES_MAPPING_KEY, BuiltinToJavaTypesMapping.ENABLED),
357 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
358 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
359 /*generateDeclaredClasses = */true
360 );
361 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
362
363 CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles());
364 for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
365 plugin.processFiles(context);
366 }
367 return generationState;
368 }
369
370 public static Class compileScript(
371 @NotNull ClassLoader parentLoader,
372 @NotNull KotlinPaths paths,
373 @NotNull String scriptPath,
374 @Nullable List<AnalyzerScriptParameter> scriptParameters,
375 @Nullable List<JetScriptDefinition> scriptDefinitions) {
376 MessageRenderer messageRenderer = MessageRenderer.PLAIN;
377 GroupingMessageCollector messageCollector = new GroupingMessageCollector(new PrintingMessageCollector(System.err, messageRenderer, false));
378 Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable();
379 try {
380 CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
381 compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector);
382 compilerConfiguration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, getClasspath(parentLoader));
383 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar());
384 compilerConfiguration.addAll(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, Collections.singletonList(
385 paths.getJdkAnnotationsPath()));
386 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, scriptPath);
387 compilerConfiguration.addAll(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY,
388 scriptDefinitions != null ? scriptDefinitions : Collections.<JetScriptDefinition>emptyList());
389 compilerConfiguration.put(JVMConfigurationKeys.SCRIPT_PARAMETERS, scriptParameters);
390
391 JetCoreEnvironment environment = new JetCoreEnvironment(rootDisposable, compilerConfiguration);
392
393 try {
394 JetScriptDefinitionProvider.getInstance(environment.getProject()).markFileAsScript(environment.getSourceFiles().get(0));
395 return compileScript(paths, environment, parentLoader);
396 }
397 catch (CompilationException e) {
398 messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(e),
399 MessageUtil.psiElementToMessageLocation(e.getElement()));
400 return null;
401 }
402 catch (Throwable t) {
403 MessageCollectorUtil.reportException(messageCollector, t);
404 return null;
405 }
406
407 }
408 finally {
409 messageCollector.flush();
410 Disposer.dispose(rootDisposable);
411 }
412 }
413
414 private static Collection<File> getClasspath(ClassLoader loader) {
415 return getClasspath(loader, new LinkedList<File>());
416 }
417
418 private static Collection<File> getClasspath(ClassLoader loader, LinkedList<File> files) {
419 ClassLoader parent = loader.getParent();
420 if(parent != null)
421 getClasspath(parent, files);
422
423 if(loader instanceof URLClassLoader) {
424 for (URL url : ((URLClassLoader) loader).getURLs()) {
425 String urlFile = url.getFile();
426
427 if (urlFile.contains("%")) {
428 try {
429 urlFile = url.toURI().getPath();
430 }
431 catch (URISyntaxException e) {
432 throw ExceptionUtils.rethrow(e);
433 }
434 }
435
436 File file = new File(urlFile);
437 if(file.exists() && (file.isDirectory() || file.getName().endsWith(".jar"))) {
438 files.add(file);
439 }
440 }
441 }
442 return files;
443 }
444 }