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.JetMainDetector;
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 List<JetFile> files) {
166 FqName mainClass = null;
167 for (JetFile file : files) {
168 if (JetMainDetector.hasMain(file.getDeclarations())) {
169 if (mainClass != null) {
170 // more than one main
171 return null;
172 }
173 FqName fqName = JetPsiUtil.getFQName(file);
174 mainClass = PackageClassUtils.getPackageClassFqName(fqName);
175 }
176 }
177 return mainClass;
178 }
179
180 public static boolean compileBunchOfSources(
181 JetCoreEnvironment environment,
182 @Nullable File jar,
183 @Nullable File outputDir,
184 boolean includeRuntime
185 ) {
186
187 FqName mainClass = findMainClass(environment.getSourceFiles());
188
189 GenerationState generationState = analyzeAndGenerate(environment);
190 if (generationState == null) {
191 return false;
192 }
193
194 try {
195 OutputDirector outputDirector = outputDir != null ? new SingleDirectoryDirector(outputDir) : null;
196 writeOutput(environment.getConfiguration(), generationState.getFactory(), outputDirector, jar, includeRuntime, mainClass);
197 return true;
198 }
199 finally {
200 generationState.destroy();
201 }
202 }
203
204 public static void compileAndExecuteScript(
205 @NotNull KotlinPaths paths,
206 @NotNull JetCoreEnvironment environment,
207 @NotNull List<String> scriptArgs
208 ) {
209 Class<?> scriptClass = compileScript(paths, environment);
210 if (scriptClass == null) return;
211
212 try {
213 scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[scriptArgs.size()])});
214 }
215 catch (RuntimeException e) {
216 throw e;
217 }
218 catch (Exception e) {
219 throw new RuntimeException("Failed to evaluate script: " + e, e);
220 }
221 }
222
223 @Nullable
224 public static Class<?> compileScript(@NotNull KotlinPaths paths, @NotNull JetCoreEnvironment environment) {
225 GenerationState state = analyzeAndGenerate(environment);
226 if (state == null) {
227 return null;
228 }
229
230 GeneratedClassLoader classLoader = null;
231 try {
232 classLoader = new GeneratedClassLoader(state.getFactory(),
233 new URLClassLoader(new URL[] {
234 // TODO: add all classpath
235 paths.getRuntimePath().toURI().toURL()
236 }, AllModules.class.getClassLoader())
237 );
238
239 return classLoader.loadClass(ScriptNameUtil.classNameForScript(environment.getSourceFiles().get(0)));
240 }
241 catch (Exception e) {
242 throw new RuntimeException("Failed to evaluate script: " + e, e);
243 }
244 finally {
245 if (classLoader != null) {
246 classLoader.dispose();
247 }
248 state.destroy();
249 }
250 }
251
252 @Nullable
253 public static GenerationState analyzeAndGenerate(@NotNull JetCoreEnvironment environment) {
254 AnalyzeExhaust exhaust = analyze(environment);
255
256 if (exhaust == null) {
257 return null;
258 }
259
260 exhaust.throwIfError();
261
262 return generate(environment, exhaust);
263 }
264
265 @Nullable
266 private static AnalyzeExhaust analyze(@NotNull final JetCoreEnvironment environment) {
267 AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(
268 environment.getConfiguration().get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY));
269 analyzerWithCompilerReport.analyzeAndReport(
270 new Function0<AnalyzeExhaust>() {
271 @NotNull
272 @Override
273 public AnalyzeExhaust invoke() {
274 CliLightClassGenerationSupport support = CliLightClassGenerationSupport.getInstanceForCli(environment.getProject());
275 BindingTrace sharedTrace = support.getTrace();
276 ModuleDescriptorImpl sharedModule = support.getModule();
277 return AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
278 environment.getProject(),
279 environment.getSourceFiles(),
280 sharedTrace,
281 environment.getConfiguration().getList(JVMConfigurationKeys.SCRIPT_PARAMETERS),
282 Predicates.<PsiFile>alwaysTrue(),
283 false,
284 sharedModule
285 );
286 }
287 }, environment.getSourceFiles()
288 );
289
290 return analyzerWithCompilerReport.hasErrors() ? null : analyzerWithCompilerReport.getAnalyzeExhaust();
291 }
292
293 @NotNull
294 private static GenerationState generate(@NotNull JetCoreEnvironment environment, @NotNull AnalyzeExhaust exhaust) {
295 Project project = environment.getProject();
296 CompilerConfiguration configuration = environment.getConfiguration();
297 GenerationState generationState = new GenerationState(
298 project, ClassBuilderFactories.BINARIES, Progress.DEAF, exhaust.getBindingContext(), environment.getSourceFiles(),
299 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, false),
300 configuration.get(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, false),
301 GenerationState.GenerateClassFilter.GENERATE_ALL,
302 configuration.get(JVMConfigurationKeys.ENABLE_INLINE, InlineUtil.DEFAULT_INLINE_FLAG)
303 );
304 KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION);
305
306 CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles());
307 for (CompilerPlugin plugin : configuration.getList(CLIConfigurationKeys.COMPILER_PLUGINS)) {
308 plugin.processFiles(context);
309 }
310 return generationState;
311 }
312 }