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