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.intellij.openapi.Disposable; 020 import com.intellij.openapi.util.Disposer; 021 import com.intellij.openapi.util.io.FileUtil; 022 import com.intellij.openapi.util.io.FileUtilRt; 023 import com.intellij.util.Function; 024 import com.intellij.util.containers.ContainerUtil; 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.cli.common.CLIConfigurationKeys; 030 import org.jetbrains.jet.cli.common.messages.*; 031 import org.jetbrains.jet.cli.common.modules.ModuleDescription; 032 import org.jetbrains.jet.cli.common.modules.ModuleXmlParser; 033 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; 034 import org.jetbrains.jet.codegen.ClassFileFactory; 035 import org.jetbrains.jet.codegen.GeneratedClassLoader; 036 import org.jetbrains.jet.codegen.state.GenerationState; 037 import org.jetbrains.jet.config.CommonConfigurationKeys; 038 import org.jetbrains.jet.config.CompilerConfiguration; 039 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 040 import org.jetbrains.jet.lang.resolve.name.FqName; 041 import org.jetbrains.jet.utils.ExceptionUtils; 042 import org.jetbrains.jet.utils.KotlinPaths; 043 import org.jetbrains.jet.utils.PathUtil; 044 045 import java.io.*; 046 import java.lang.reflect.Method; 047 import java.net.MalformedURLException; 048 import java.net.URL; 049 import java.net.URLClassLoader; 050 import java.util.ArrayList; 051 import java.util.Collection; 052 import java.util.List; 053 import java.util.jar.*; 054 055 import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.NO_LOCATION; 056 import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERROR; 057 058 public class CompileEnvironmentUtil { 059 public static Disposable createMockDisposable() { 060 return new Disposable() { 061 @Override 062 public void dispose() { 063 } 064 }; 065 } 066 067 @Nullable 068 private static File getRuntimeJarPath() { 069 File runtimePath = PathUtil.getKotlinPathsForCompiler().getRuntimePath(); 070 return runtimePath.exists() ? runtimePath : null; 071 } 072 073 @NotNull 074 public static ModuleChunk loadModuleDescriptions(KotlinPaths paths, String moduleDefinitionFile, MessageCollector messageCollector) { 075 File file = new File(moduleDefinitionFile); 076 if (!file.exists()) { 077 messageCollector.report(ERROR, "Module definition file does not exist: " + moduleDefinitionFile, NO_LOCATION); 078 return ModuleChunk.EMPTY; 079 } 080 String extension = FileUtilRt.getExtension(moduleDefinitionFile); 081 if ("kts".equalsIgnoreCase(extension)) { 082 return new ModuleChunk(loadModuleScript(paths, moduleDefinitionFile, messageCollector)); 083 } 084 if ("xml".equalsIgnoreCase(extension)) { 085 return new ModuleChunk(ContainerUtil.map( 086 ModuleXmlParser.parse(moduleDefinitionFile, messageCollector), 087 new Function<ModuleDescription, Module>() { 088 @Override 089 public Module fun(ModuleDescription description) { 090 return new DescriptionToModuleAdapter(description); 091 } 092 })); 093 } 094 messageCollector.report(ERROR, "Unknown module definition type: " + moduleDefinitionFile, NO_LOCATION); 095 return ModuleChunk.EMPTY; 096 } 097 098 @NotNull 099 private static List<Module> loadModuleScript(KotlinPaths paths, String moduleScriptFile, MessageCollector messageCollector) { 100 Disposable disposable = new Disposable() { 101 @Override 102 public void dispose() { 103 104 } 105 }; 106 CompilerConfiguration configuration = new CompilerConfiguration(); 107 File runtimePath = paths.getRuntimePath(); 108 if (runtimePath.exists()) { 109 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, runtimePath); 110 } 111 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar()); 112 File jdkAnnotationsPath = paths.getJdkAnnotationsPath(); 113 if (jdkAnnotationsPath.exists()) { 114 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, jdkAnnotationsPath); 115 } 116 configuration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, moduleScriptFile); 117 configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector); 118 119 List<Module> modules; 120 try { 121 JetCoreEnvironment scriptEnvironment = new JetCoreEnvironment(disposable, configuration); 122 GenerationState generationState = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(scriptEnvironment); 123 if (generationState == null) { 124 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " analyze failed:\n" + 125 loadModuleScriptText(moduleScriptFile)); 126 } 127 128 modules = runDefineModules(paths, moduleScriptFile, generationState.getFactory()); 129 } 130 finally { 131 Disposer.dispose(disposable); 132 } 133 134 if (modules == null) { 135 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " compilation failed"); 136 } 137 138 if (modules.isEmpty()) { 139 throw new CompileEnvironmentException("No modules where defined by " + moduleScriptFile); 140 } 141 return modules; 142 } 143 144 private static List<Module> runDefineModules(KotlinPaths paths, String moduleFile, ClassFileFactory factory) { 145 File stdlibJar = paths.getRuntimePath(); 146 GeneratedClassLoader loader; 147 if (stdlibJar.exists()) { 148 try { 149 loader = new GeneratedClassLoader(factory, new URLClassLoader(new URL[]{stdlibJar.toURI().toURL()}, 150 AllModules.class.getClassLoader())); 151 } 152 catch (MalformedURLException e) { 153 throw new RuntimeException(e); 154 } 155 } 156 else { 157 loader = new GeneratedClassLoader(factory, KotlinToJVMBytecodeCompiler.class.getClassLoader()); 158 } 159 try { 160 Class namespaceClass = loader.loadClass(PackageClassUtils.getPackageClassName(FqName.ROOT)); 161 Method method = namespaceClass.getDeclaredMethod("project"); 162 if (method == null) { 163 throw new CompileEnvironmentException("Module script " + moduleFile + " must define project() function"); 164 } 165 166 method.setAccessible(true); 167 method.invoke(null); 168 169 ArrayList<Module> answer = new ArrayList<Module>(AllModules.modules.get()); 170 AllModules.modules.get().clear(); 171 return answer; 172 } 173 catch (Exception e) { 174 throw new ModuleExecutionException(e); 175 } 176 finally { 177 loader.dispose(); 178 } 179 } 180 181 // TODO: includeRuntime should be not a flag but a path to runtime 182 private static void doWriteToJar(ClassFileFactory factory, OutputStream fos, @Nullable FqName mainClass, boolean includeRuntime) { 183 try { 184 Manifest manifest = new Manifest(); 185 Attributes mainAttributes = manifest.getMainAttributes(); 186 mainAttributes.putValue("Manifest-Version", "1.0"); 187 mainAttributes.putValue("Created-By", "JetBrains Kotlin"); 188 if (mainClass != null) { 189 mainAttributes.putValue("Main-Class", mainClass.asString()); 190 } 191 JarOutputStream stream = new JarOutputStream(fos, manifest); 192 for (String file : factory.files()) { 193 stream.putNextEntry(new JarEntry(file)); 194 stream.write(factory.asBytes(file)); 195 } 196 if (includeRuntime) { 197 writeRuntimeToJar(stream); 198 } 199 stream.finish(); 200 } 201 catch (IOException e) { 202 throw new CompileEnvironmentException("Failed to generate jar file", e); 203 } 204 } 205 206 public static void writeToJar(File jarPath, boolean jarRuntime, FqName mainClass, ClassFileFactory moduleFactory) { 207 FileOutputStream outputStream = null; 208 try { 209 outputStream = new FileOutputStream(jarPath); 210 doWriteToJar(moduleFactory, outputStream, mainClass, jarRuntime); 211 outputStream.close(); 212 } 213 catch (FileNotFoundException e) { 214 throw new CompileEnvironmentException("Invalid jar path " + jarPath, e); 215 } 216 catch (IOException e) { 217 throw ExceptionUtils.rethrow(e); 218 } 219 finally { 220 ExceptionUtils.closeQuietly(outputStream); 221 } 222 } 223 224 private static void writeRuntimeToJar(final JarOutputStream stream) throws IOException { 225 File runtimeJarPath = getRuntimeJarPath(); 226 if (runtimeJarPath != null) { 227 JarInputStream jis = new JarInputStream(new FileInputStream(runtimeJarPath)); 228 try { 229 while (true) { 230 JarEntry e = jis.getNextJarEntry(); 231 if (e == null) { 232 break; 233 } 234 if (FileUtilRt.extensionEquals(e.getName(), "class")) { 235 stream.putNextEntry(e); 236 FileUtil.copy(jis, stream); 237 } 238 } 239 } 240 finally { 241 jis.close(); 242 } 243 } 244 else { 245 throw new CompileEnvironmentException("Couldn't find runtime library"); 246 } 247 } 248 249 public interface OutputDirector { 250 @NotNull 251 File getOutputDirectory(@NotNull Collection<File> sourceFiles); 252 } 253 254 public static OutputDirector singleDirectory(@Nullable final File file) { 255 if (file == null) return null; 256 return new OutputDirector() { 257 @NotNull 258 @Override 259 public File getOutputDirectory(@NotNull Collection<File> sourceFiles) { 260 return file; 261 } 262 }; 263 } 264 265 public static void writeToOutputWithDirector(ClassFileFactory factory, @NotNull OutputDirector outputDirector) { 266 List<String> files = factory.files(); 267 for (String file : files) { 268 File target = new File(outputDirector.getOutputDirectory(factory.getSourceFiles(file)), file); 269 try { 270 FileUtil.writeToFile(target, factory.asBytes(file)); 271 } 272 catch (IOException e) { 273 throw new CompileEnvironmentException(e); 274 } 275 } 276 } 277 278 public static void writeToOutputDirectory(ClassFileFactory factory, @NotNull File outputDir) { 279 writeToOutputWithDirector(factory, singleDirectory(outputDir)); 280 } 281 282 // Used for debug output only 283 private static String loadModuleScriptText(String moduleScriptFile) { 284 String moduleScriptText; 285 try { 286 moduleScriptText = FileUtil.loadFile(new File(moduleScriptFile)); 287 } 288 catch (IOException e) { 289 moduleScriptText = "Can't load module script text:\n" + MessageRenderer.PLAIN.renderException(e); 290 } 291 return moduleScriptText; 292 } 293 294 static void writeOutputToDirOrJar( 295 @Nullable File jar, 296 @Nullable OutputDirector outputDir, 297 boolean includeRuntime, 298 @Nullable FqName mainClass, 299 @NotNull ClassFileFactory factory, 300 @NotNull MessageCollector messageCollector 301 ) { 302 if (jar != null) { 303 writeToJar(jar, includeRuntime, mainClass, factory); 304 } 305 else if (outputDir != null) { 306 reportOutputs(factory, messageCollector); 307 writeToOutputWithDirector(factory, outputDir); 308 } 309 else { 310 throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk"); 311 } 312 } 313 314 private static void reportOutputs(ClassFileFactory factory, MessageCollector messageCollector) { 315 for (String outputFile : factory.files()) { 316 List<File> sourceFiles = factory.getSourceFiles(outputFile); 317 messageCollector.report( 318 CompilerMessageSeverity.OUTPUT, 319 OutputMessageUtil.formatOutputMessage(sourceFiles, new File(outputFile)), 320 CompilerMessageLocation.NO_LOCATION); 321 322 } 323 } 324 325 private static class DescriptionToModuleAdapter implements Module { 326 private final ModuleDescription description; 327 328 public DescriptionToModuleAdapter(ModuleDescription description) { 329 this.description = description; 330 } 331 332 @Override 333 public String getModuleName() { 334 return description.getModuleName(); 335 } 336 337 @Override 338 public String getOutputDirectory() { 339 return description.getOutputDir(); 340 } 341 342 @Override 343 public List<String> getSourceFiles() { 344 return description.getSourceFiles(); 345 } 346 347 @Override 348 public List<String> getClasspathRoots() { 349 return description.getClasspathRoots(); 350 } 351 352 @Override 353 public List<String> getAnnotationsRoots() { 354 return description.getAnnotationsRoots(); 355 } 356 } 357 }