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