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.intellij.openapi.Disposable;
021 import com.intellij.openapi.project.Project;
022 import com.intellij.openapi.util.Disposer;
023 import com.intellij.psi.PsiFile;
024 import kotlin.Function0;
025 import kotlin.modules.AllModules;
026 import kotlin.modules.Module;
027 import org.jetbrains.annotations.NotNull;
028 import org.jetbrains.annotations.Nullable;
029 import org.jetbrains.jet.analyzer.AnalyzeExhaust;
030 import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
031 import org.jetbrains.jet.cli.common.CompilerPlugin;
032 import org.jetbrains.jet.cli.common.CompilerPluginContext;
033 import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport;
034 import org.jetbrains.jet.cli.common.messages.MessageCollector;
035 import org.jetbrains.jet.cli.common.output.OutputDirector;
036 import org.jetbrains.jet.cli.common.output.SingleDirectoryDirector;
037 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
038 import org.jetbrains.jet.codegen.*;
039 import org.jetbrains.jet.codegen.state.GenerationState;
040 import org.jetbrains.jet.codegen.state.Progress;
041 import org.jetbrains.jet.config.CommonConfigurationKeys;
042 import org.jetbrains.jet.config.CompilerConfiguration;
043 import org.jetbrains.jet.lang.descriptors.ModuleDescriptorImpl;
044 import org.jetbrains.jet.lang.psi.JetFile;
045 import org.jetbrains.jet.lang.psi.JetPsiUtil;
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.lang.types.lang.InlineUtil;
052 import org.jetbrains.jet.plugin.MainFunctionDetector;
053 import org.jetbrains.jet.utils.KotlinPaths;
054
055 import java.io.File;
056 import java.net.URL;
057 import java.net.URLClassLoader;
058 import java.util.Collection;
059 import java.util.Collections;
060 import java.util.List;
061
062 public class KotlinToJVMBytecodeCompiler {
063
064 private static final boolean COMPILE_CHUNK_AS_ONE_MODULE = true;
065
066 private KotlinToJVMBytecodeCompiler() {
067 }
068
069 @Nullable
070 public static ClassFileFactory compileModule(CompilerConfiguration configuration, Module module, File directory) {
071 List<String> sourceFiles = module.getSourceFiles();
072 if (sourceFiles.isEmpty()) {
073 throw new CompileEnvironmentException("No source files where defined in module " + module.getModuleName());
074 }
075
076 CompilerConfiguration compilerConfiguration = configuration.copy();
077 for (String sourceFile : sourceFiles) {
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 compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, source.getPath());
088 }
089
090 for (String classpathRoot : module.getClasspathRoots()) {
091 compilerConfiguration.add(JVMConfigurationKeys.CLASSPATH_KEY, new File(classpathRoot));
092 }
093
094 for (String annotationsRoot : module.getAnnotationsRoots()) {
095 compilerConfiguration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, new File(annotationsRoot));
096 }
097
098 Disposable parentDisposable = Disposer.newDisposable();
099 JetCoreEnvironment moduleEnvironment = null;
100 try {
101 moduleEnvironment = JetCoreEnvironment.createForProduction(parentDisposable, compilerConfiguration);
102
103
104 GenerationState generationState = analyzeAndGenerate(moduleEnvironment);
105 if (generationState == null) {
106 return null;
107 }
108 return generationState.getFactory();
109 } finally {
110 if (moduleEnvironment != null) {
111 Disposer.dispose(parentDisposable);
112 }
113 }
114 }
115
116 private static void writeOutput(
117 CompilerConfiguration configuration,
118 ClassFileFactory outputFiles,
119 OutputDirector outputDir,
120 File jarPath,
121 boolean jarRuntime,
122 FqName mainClass
123 ) {
124 MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE);
125 CompileEnvironmentUtil.writeOutputToDirOrJar(jarPath, outputDir, jarRuntime, mainClass, outputFiles, messageCollector);
126 }
127
128 public static boolean compileModules(
129 CompilerConfiguration configuration,
130 @NotNull final ModuleChunk chunk,
131 @NotNull File directory,
132 @Nullable File jarPath,
133 boolean jarRuntime
134 ) {
135 List<Module> modules = chunk.getModules();
136 if (COMPILE_CHUNK_AS_ONE_MODULE && modules.size() > 1) {
137 modules = Collections.<Module>singletonList(new ChunkAsOneModule(chunk));
138 }
139 for (Module module : modules) {
140 ClassFileFactory outputFiles = compileModule(configuration, module, directory);
141 if (outputFiles == null) {
142 return false;
143 }
144 OutputDirector outputDir = new OutputDirector() {
145 @NotNull
146 @Override
147 public File getOutputDirectory(@NotNull Collection<? extends File> sourceFiles) {
148 for (File sourceFile : sourceFiles) {
149 // Note that here we track original modules:
150 Module module = chunk.findModuleBySourceFile(sourceFile);
151 if (module != null) {
152 return new File(module.getOutputDirectory());
153 }
154 }
155 throw new IllegalStateException("No module found for source files: " + sourceFiles);
156 }
157 };
158
159 writeOutput(configuration, outputFiles, outputDir, jarPath, jarRuntime, null);
160 }
161 return true;
162 }
163
164 @Nullable
165 private static FqName findMainClass(@NotNull GenerationState generationState, @NotNull List<JetFile> files) {
166 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(generationState.getBindingContext());
167 FqName mainClass = null;
168 for (JetFile file : files) {
169 if (mainFunctionDetector.hasMain(file.getDeclarations())) {
170 if (mainClass != null) {
171 // more than one main
172 return null;
173 }
174 FqName fqName = JetPsiUtil.getFQName(file);
175 mainClass = PackageClassUtils.getPackageClassFqName(fqName);
176 }
177 }
178 return mainClass;
179 }
180
181 public static boolean compileBunchOfSources(
182 JetCoreEnvironment environment,
183 @Nullable File jar,
184 @Nullable File outputDir,
185 boolean includeRuntime
186 ) {
187
188 GenerationState generationState = analyzeAndGenerate(environment);
189 if (generationState == null) {
190 return false;
191 }
192
193 FqName mainClass = findMainClass(generationState, environment.getSourceFiles());
194
195 try {
196 OutputDirector outputDirector = outputDir != null ? new SingleDirectoryDirector(outputDir) : null;
197 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDirector, jar, includeRuntime, mainClass);
198 return true;
199 }
200 finally {
201 generationState.destroy();
202 }
203 }
204
205 public static void compileAndExecuteScript(
206 @NotNull KotlinPaths paths,
207 @NotNull JetCoreEnvironment environment,
208 @NotNull List<String> scriptArgs
209 ) {
210 Class<?> scriptClass = compileScript(paths, environment);
211 if (scriptClass == null) return;
212
213 try {
214 scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[scriptArgs.size()])});
215 }
216 catch (RuntimeException e) {
217 throw e;
218 }
219 catch (Exception e) {
220 throw new RuntimeException("Failed to evaluate script: " + e, e);
221 }
222 }
223
224 @Nullable
225 public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) {
226 GenerationState state = analyzeAndGenerate(environment);
227 if (state == null) {
228 return null;
229 }
230
231 GeneratedClassLoader classLoader = null;
232 try {
233 classLoader = new GeneratedClassLoader(state.getFactory(),
234 new URLClassLoader(new URL[] {
235 // TODO: add all classpath
236 paths.getRuntimePath().toURI().toURL()
237 }, AllModules.class.getClassLoader())
238 );
239
240 return classLoader.loadClass(ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0)));
241 }
242 catch (Exception e) {
243 throw new RuntimeException("Failed to evaluate script: " + e, e);
244 }
245 finally {
246 if (classLoader != null) {
247 classLoader.dispose();
248 }
249 state.destroy();
250 }
251 }
252
253 @Nullable
254 public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) {
255 AnalyzeExhaust exhaust = analyze(environment);
256
257 if (exhaust == null) {
258 return null;
259 }
260
261 exhaust.throwIfError();
262
263 return generate(environment, exhaust);
264 }
265
266 @Nullable
267 private static AnalyzeExhaust analyze(@NotNull final JetCoreEnvironment environment) {
268 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
269 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
270 analyzerWithCompilerReport.analyzeAndReport(
271 new Function0<AnalyzeExhaust>() {
272 @NotNull
273 @Override
274 public AnalyzeExhaust invoke() {
275 CliLightClassGenerationSupport support = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject());
276 BindingTrace sharedTrace = support.getTrace();
277 ModuleDescriptorImpl sharedModule = support.getModule();
278 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
279 environment.getProject(),
280 environment.getSourceFiles(),
281 sharedTrace,
282 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS),
283 Predicates.<PsiFile>alwaysTrue(),
284 false,
285 sharedModule
286 );
287 }
288 }, environment.getSourceFiles()
289 );
290
291 AnalyzeExhaust exhaust = analyzerWithCompilerReport.getAnalyzeExhaust();
292 assert exhaust != null : "AnalyzeExhaust should be non-null, compiling: " + environment.getSourceFiles();
293
294 CompilerPluginContext context = new CompilerPluginContext(environment.getProject(), exhaust.getBindingContext(),
295 environment.getSourceFiles());
296 for (CompilerPlugin plugin : environment.getConfiguration().getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
297 plugin.processFiles(context);
298 }
299
300 return analyzerWithCompilerReport.hasErrors() ? null : exhaust;
301 }
302
303 @NotNull
304 private static GenerationState generate(@NotNull JetCoreEnvironment environment, @NotNull AnalyzeExhaust exhaust) {
305 CompilerConfiguration configuration = environment.getConfiguration();
306 GenerationState generationState = new GenerationState(
307 environment.getProject(), ClassBuilderFactories.BINARIES, Progress.DEAF, exhaust.getBindingContext(), environment.getSourceFiles(),
308 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
309 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
310 GenerationState.GenerateClassFilter.GENERATE_ALL,
311 configuration.get(JVMConfigurationKeys.ENABLE_INLINE, InlineUtil.DEFAULT_INLINE_FLAG)
312 );
313 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
314 return generationState;
315 }
316 }