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 kotlin.modules.AllModules;
026 import kotlin.modules.Module;
027 import org.jetbrains.annotations.NotNull;
028 import org.jetbrains.annotations.Nullable;
029 import org.jetbrains.jet.OutputFile;
030 import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
031 import org.jetbrains.jet.cli.common.messages.MessageCollector;
032 import org.jetbrains.jet.cli.common.messages.MessageRenderer;
033 import org.jetbrains.jet.cli.common.modules.ModuleDescription;
034 import org.jetbrains.jet.cli.common.modules.ModuleXmlParser;
035 import org.jetbrains.jet.cli.common.output.OutputDirector;
036 import org.jetbrains.jet.cli.common.output.outputUtils.OutputUtilsPackage;
037 import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
038 import org.jetbrains.jet.codegen.ClassFileFactory;
039 import org.jetbrains.jet.codegen.GeneratedClassLoader;
040 import org.jetbrains.jet.codegen.state.GenerationState;
041 import org.jetbrains.jet.config.CommonConfigurationKeys;
042 import org.jetbrains.jet.config.CompilerConfiguration;
043 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
044 import org.jetbrains.jet.lang.resolve.name.FqName;
045 import org.jetbrains.jet.utils.KotlinPaths;
046 import org.jetbrains.jet.utils.PathUtil;
047 import org.jetbrains.jet.utils.UtilsPackage;
048
049 import java.io.*;
050 import java.lang.reflect.Method;
051 import java.net.MalformedURLException;
052 import java.net.URL;
053 import java.net.URLClassLoader;
054 import java.util.ArrayList;
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
063 @Nullable
064 private static File getRuntimeJarPath() {
065 File runtimePath = PathUtil.getKotlinPathsForCompiler().getRuntimePath();
066 return runtimePath.exists() ? runtimePath : null;
067 }
068
069 @NotNull
070 public static ModuleChunk loadModuleDescriptions(KotlinPaths paths, String moduleDefinitionFile, MessageCollector messageCollector) {
071 File file = new File(moduleDefinitionFile);
072 if (!file.exists()) {
073 messageCollector.report(ERROR, "Module definition file does not exist: " + moduleDefinitionFile, NO_LOCATION);
074 return ModuleChunk.EMPTY;
075 }
076 String extension = FileUtilRt.getExtension(moduleDefinitionFile);
077 if ("kts".equalsIgnoreCase(extension)) {
078 return new ModuleChunk(loadModuleScript(paths, moduleDefinitionFile, messageCollector));
079 }
080 if ("xml".equalsIgnoreCase(extension)) {
081 return new ModuleChunk(ContainerUtil.map(
082 ModuleXmlParser.parse(moduleDefinitionFile, messageCollector),
083 new Function<ModuleDescription, Module>() {
084 @Override
085 public Module fun(ModuleDescription description) {
086 return new DescriptionToModuleAdapter(description);
087 }
088 }));
089 }
090 messageCollector.report(ERROR, "Unknown module definition type: " + moduleDefinitionFile, NO_LOCATION);
091 return ModuleChunk.EMPTY;
092 }
093
094 @NotNull
095 private static List<Module> loadModuleScript(KotlinPaths paths, String moduleScriptFile, MessageCollector messageCollector) {
096 CompilerConfiguration configuration = new CompilerConfiguration();
097 File runtimePath = paths.getRuntimePath();
098 if (runtimePath.exists()) {
099 configuration.add(JVMConfigurationKeys.CLASSPATH_KEY, runtimePath);
100 }
101 configuration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.getJdkClassesRoots());
102 File jdkAnnotationsPath = paths.getJdkAnnotationsPath();
103 if (jdkAnnotationsPath.exists()) {
104 configuration.add(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, jdkAnnotationsPath);
105 }
106 configuration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, moduleScriptFile);
107 configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector);
108
109 List<Module> modules;
110
111 Disposable disposable = Disposer.newDisposable();
112 try {
113 JetCoreEnvironment scriptEnvironment = JetCoreEnvironment.createForProduction(disposable, configuration);
114 GenerationState generationState = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(scriptEnvironment);
115 if (generationState == null) {
116 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " analyze failed:\n" +
117 loadModuleScriptText(moduleScriptFile));
118 }
119
120 modules = runDefineModules(paths, generationState.getFactory());
121 }
122 finally {
123 Disposer.dispose(disposable);
124 }
125
126 if (modules == null) {
127 throw new CompileEnvironmentException("Module script " + moduleScriptFile + " compilation failed");
128 }
129
130 if (modules.isEmpty()) {
131 throw new CompileEnvironmentException("No modules where defined by " + moduleScriptFile);
132 }
133 return modules;
134 }
135
136 private static List<Module> runDefineModules(KotlinPaths paths, ClassFileFactory factory) {
137 File stdlibJar = paths.getRuntimePath();
138 GeneratedClassLoader loader;
139 if (stdlibJar.exists()) {
140 try {
141 loader = new GeneratedClassLoader(factory, new URLClassLoader(new URL[]{stdlibJar.toURI().toURL()},
142 AllModules.class.getClassLoader()));
143 }
144 catch (MalformedURLException e) {
145 throw new RuntimeException(e);
146 }
147 }
148 else {
149 loader = new GeneratedClassLoader(factory, KotlinToJVMBytecodeCompiler.class.getClassLoader());
150 }
151 try {
152 Class<?> packageClass = loader.loadClass(PackageClassUtils.getPackageClassName(FqName.ROOT));
153 Method method = packageClass.getDeclaredMethod("project");
154
155 method.setAccessible(true);
156 method.invoke(null);
157
158 ArrayList<Module> answer = new ArrayList<Module>(AllModules.instance$.get());
159 AllModules.instance$.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 outputFiles, 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 (OutputFile outputFile : outputFiles.asList()) {
182 stream.putNextEntry(new JarEntry(outputFile.getRelativePath()));
183 stream.write(outputFile.asByteArray());
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 outputFiles) {
196 FileOutputStream outputStream = null;
197 try {
198 outputStream = new FileOutputStream(jarPath);
199 doWriteToJar(outputFiles, 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 UtilsPackage.rethrow(e);
207 }
208 finally {
209 UtilsPackage.closeQuietly(outputStream);
210 }
211 }
212
213 private static void writeRuntimeToJar(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 // Used for debug output only
239 private static String loadModuleScriptText(String moduleScriptFile) {
240 String moduleScriptText;
241 try {
242 moduleScriptText = FileUtil.loadFile(new File(moduleScriptFile));
243 }
244 catch (IOException e) {
245 moduleScriptText = "Can't load module script text:\n" + MessageRenderer.PLAIN.renderException(e);
246 }
247 return moduleScriptText;
248 }
249
250 static void writeOutputToDirOrJar(
251 @Nullable File jar,
252 @Nullable OutputDirector outputDir,
253 boolean includeRuntime,
254 @Nullable FqName mainClass,
255 @NotNull ClassFileFactory outputFiles,
256 @NotNull MessageCollector messageCollector
257 ) {
258 if (jar != null) {
259 writeToJar(jar, includeRuntime, mainClass, outputFiles);
260 }
261 else if (outputDir != null) {
262 OutputUtilsPackage.writeAll(outputFiles, outputDir, messageCollector);
263 }
264 else {
265 throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk");
266 }
267 }
268
269 private static class DescriptionToModuleAdapter implements Module {
270 private final ModuleDescription description;
271
272 public DescriptionToModuleAdapter(ModuleDescription description) {
273 this.description = description;
274 }
275
276 @NotNull
277 @Override
278 public String getModuleName() {
279 return description.getModuleName();
280 }
281
282 @NotNull
283 @Override
284 public String getOutputDirectory() {
285 return description.getOutputDir();
286 }
287
288 @NotNull
289 @Override
290 public List<String> getSourceFiles() {
291 return description.getSourceFiles();
292 }
293
294 @NotNull
295 @Override
296 public List<String> getClasspathRoots() {
297 return description.getClasspathRoots();
298 }
299
300 @NotNull
301 @Override
302 public List<String> getAnnotationsRoots() {
303 return description.getAnnotationsRoots();
304 }
305 }
306 }