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