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.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.ExceptionUtils;
046 import org.jetbrains.jet.utils.KotlinPaths;
047 import org.jetbrains.jet.utils.PathUtil;
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.add(JVMConfigurationKeys.CLASSPATH_KEY, PathUtil.findRtJar());
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, moduleScriptFile, 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, String moduleFile, 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 namespaceClass = loader.loadClass(PackageClassUtils.getPackageClassName(FqName.ROOT));
153 Method method = namespaceClass.getDeclaredMethod("project");
154 if (method == null) {
155 throw new CompileEnvironmentException("Module script " + moduleFile + " must define project() function");
156 }
157
158 method.setAccessible(true);
159 method.invoke(null);
160
161 ArrayList<Module> answer = new ArrayList<Module>(AllModules.modules.get());
162 AllModules.modules.get().clear();
163 return answer;
164 }
165 catch (Exception e) {
166 throw new ModuleExecutionException(e);
167 }
168 finally {
169 loader.dispose();
170 }
171 }
172
173 // TODO: includeRuntime should be not a flag but a path to runtime
174 private static void doWriteToJar(ClassFileFactory outputFiles, OutputStream fos, @Nullable FqName mainClass, boolean includeRuntime) {
175 try {
176 Manifest manifest = new Manifest();
177 Attributes mainAttributes = manifest.getMainAttributes();
178 mainAttributes.putValue("Manifest-Version", "1.0");
179 mainAttributes.putValue("Created-By", "JetBrains Kotlin");
180 if (mainClass != null) {
181 mainAttributes.putValue("Main-Class", mainClass.asString());
182 }
183 JarOutputStream stream = new JarOutputStream(fos, manifest);
184 for (OutputFile outputFile : outputFiles.asList()) {
185 stream.putNextEntry(new JarEntry(outputFile.getRelativePath()));
186 stream.write(outputFile.asByteArray());
187 }
188 if (includeRuntime) {
189 writeRuntimeToJar(stream);
190 }
191 stream.finish();
192 }
193 catch (IOException e) {
194 throw new CompileEnvironmentException("Failed to generate jar file", e);
195 }
196 }
197
198 public static void writeToJar(File jarPath, boolean jarRuntime, FqName mainClass, ClassFileFactory outputFiles) {
199 FileOutputStream outputStream = null;
200 try {
201 outputStream = new FileOutputStream(jarPath);
202 doWriteToJar(outputFiles, outputStream, mainClass, jarRuntime);
203 outputStream.close();
204 }
205 catch (FileNotFoundException e) {
206 throw new CompileEnvironmentException("Invalid jar path " + jarPath, e);
207 }
208 catch (IOException e) {
209 throw ExceptionUtils.rethrow(e);
210 }
211 finally {
212 ExceptionUtils.closeQuietly(outputStream);
213 }
214 }
215
216 private static void writeRuntimeToJar(JarOutputStream stream) throws IOException {
217 File runtimeJarPath = getRuntimeJarPath();
218 if (runtimeJarPath != null) {
219 JarInputStream jis = new JarInputStream(new FileInputStream(runtimeJarPath));
220 try {
221 while (true) {
222 JarEntry e = jis.getNextJarEntry();
223 if (e == null) {
224 break;
225 }
226 if (FileUtilRt.extensionEquals(e.getName(), "class")) {
227 stream.putNextEntry(e);
228 FileUtil.copy(jis, stream);
229 }
230 }
231 }
232 finally {
233 jis.close();
234 }
235 }
236 else {
237 throw new CompileEnvironmentException("Couldn't find runtime library");
238 }
239 }
240
241 // Used for debug output only
242 private static String loadModuleScriptText(String moduleScriptFile) {
243 String moduleScriptText;
244 try {
245 moduleScriptText = FileUtil.loadFile(new File(moduleScriptFile));
246 }
247 catch (IOException e) {
248 moduleScriptText = "Can't load module script text:\n" + MessageRenderer.PLAIN.renderException(e);
249 }
250 return moduleScriptText;
251 }
252
253 static void writeOutputToDirOrJar(
254 @Nullable File jar,
255 @Nullable OutputDirector outputDir,
256 boolean includeRuntime,
257 @Nullable FqName mainClass,
258 @NotNull ClassFileFactory outputFiles,
259 @NotNull MessageCollector messageCollector
260 ) {
261 if (jar != null) {
262 writeToJar(jar, includeRuntime, mainClass, outputFiles);
263 }
264 else if (outputDir != null) {
265 OutputUtilsPackage.writeAll(outputFiles, outputDir, messageCollector);
266 }
267 else {
268 throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk");
269 }
270 }
271
272 private static class DescriptionToModuleAdapter implements Module {
273 private final ModuleDescription description;
274
275 public DescriptionToModuleAdapter(ModuleDescription description) {
276 this.description = description;
277 }
278
279 @Override
280 public String getModuleName() {
281 return description.getModuleName();
282 }
283
284 @Override
285 public String getOutputDirectory() {
286 return description.getOutputDir();
287 }
288
289 @Override
290 public List<String> getSourceFiles() {
291 return description.getSourceFiles();
292 }
293
294 @Override
295 public List<String> getClasspathRoots() {
296 return description.getClasspathRoots();
297 }
298
299 @Override
300 public List<String> getAnnotationsRoots() {
301 return description.getAnnotationsRoots();
302 }
303 }
304 }