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 }