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