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