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