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(
255 ClassFileFactory factory,
256 @NotNull OutputDirector outputDirector,
257 @NotNull MessageCollector messageCollector
258 ) {
259 List<String> files = factory.files();
260 for (String file : files) {
261 List<File> sourceFiles = factory.getSourceFiles(file);
262 File target = new File(outputDirector.getOutputDirectory(sourceFiles), file);
263 messageCollector.report(
264 CompilerMessageSeverity.OUTPUT,
265 OutputMessageUtil.formatOutputMessage(sourceFiles, target),
266 CompilerMessageLocation.NO_LOCATION);
267 try {
268 FileUtil.writeToFile(target, factory.asBytes(file));
269 }
270 catch (IOException e) {
271 throw new CompileEnvironmentException(e);
272 }
273 }
274 }
275
276 public static void writeToOutputDirectory(ClassFileFactory factory, @NotNull File outputDir) {
277 writeToOutputWithDirector(factory, singleDirectory(outputDir), MessageCollector.NONE);
278 }
279
280 // Used for debug output only
281 private static String loadModuleScriptText(String moduleScriptFile) {
282 String moduleScriptText;
283 try {
284 moduleScriptText = FileUtil.loadFile(new File(moduleScriptFile));
285 }
286 catch (IOException e) {
287 moduleScriptText = "Can't load module script text:\n" + MessageRenderer.PLAIN.renderException(e);
288 }
289 return moduleScriptText;
290 }
291
292 static void writeOutputToDirOrJar(
293 @Nullable File jar,
294 @Nullable OutputDirector outputDir,
295 boolean includeRuntime,
296 @Nullable FqName mainClass,
297 @NotNull ClassFileFactory factory,
298 @NotNull MessageCollector messageCollector
299 ) {
300 if (jar != null) {
301 writeToJar(jar, includeRuntime, mainClass, factory);
302 }
303 else if (outputDir != null) {
304 writeToOutputWithDirector(factory, outputDir, messageCollector);
305 }
306 else {
307 throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk");
308 }
309 }
310
311 private static class DescriptionToModuleAdapter implements Module {
312 private final ModuleDescription description;
313
314 public DescriptionToModuleAdapter(ModuleDescription description) {
315 this.description = description;
316 }
317
318 @Override
319 public String getModuleName() {
320 return description.getModuleName();
321 }
322
323 @Override
324 public String getOutputDirectory() {
325 return description.getOutputDir();
326 }
327
328 @Override
329 public List<String> getSourceFiles() {
330 return description.getSourceFiles();
331 }
332
333 @Override
334 public List<String> getClasspathRoots() {
335 return description.getClasspathRoots();
336 }
337
338 @Override
339 public List<String> getAnnotationsRoots() {
340 return description.getAnnotationsRoots();
341 }
342 }
343 }