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