package org.fandev.compiler; import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessListener; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompileScope; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.compiler.TranslatingCompiler; import com.intellij.openapi.module.JavaModuleType; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.util.Chunk; import com.intellij.util.io.ZipUtil; import org.fandev.lang.fan.FanBundle; import org.fandev.lang.fan.FanFileType; import org.fandev.module.FanModuleSettings; import org.fandev.sdk.FanSdkType; import org.fandev.utils.FanUtil; import org.fandev.utils.VirtualFileUtil; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.util.*; /** * @author Dror Bereznitsky * @date Jan 25, 2009 12:08:47 AM */ public class FanCompiler implements TranslatingCompiler { private static final String FAN_COMPILER = "Fantom compiler"; private Project myProject; public FanCompiler(final Project project) { myProject = project; } public boolean isCompilableFile(final VirtualFile file, final CompileContext context) { return FanFileType.FAN_FILE_TYPE.equals(file.getFileType()); } public void compile(final CompileContext compileContext, final Chunk<Module> moduleChunk, final VirtualFile[] virtualFiles, final OutputSink outputSink) { System.out.println("In compile(CompileContext compileContext, Chunk<Module> moduleChunk, VirtualFile[] virtualFiles, OutputSink outputSink)"); } public void compile(final CompileContext context, final VirtualFile[] files, final OutputSink sink) { Set<VirtualFile> toRecompile = new HashSet<VirtualFile>(); Set<TranslatingCompiler.OutputItem> successfullyCompiled = new HashSet<TranslatingCompiler.OutputItem>(); final Set<Module> modulesToBuild = new HashSet<Module>(); final Map<Module, List<VirtualFile>> moduleToFile = new HashMap<Module, List<VirtualFile>>(); for (final VirtualFile fileToCompile : files) { final Module moduleToBuild = context.getModuleByFile(fileToCompile); modulesToBuild.add(moduleToBuild); if (!moduleToFile.containsKey(moduleToBuild)) { moduleToFile.put(moduleToBuild, new ArrayList<VirtualFile>()); } moduleToFile.get(moduleToBuild).add(fileToCompile); } for (final Module moduleToBuild : modulesToBuild) { final Sdk moduleSdk = FanUtil.getSdk(moduleToBuild); if (moduleSdk == null) { // Not a fan module continue; } FanUtil.setFanHome(moduleSdk); final String fanLauncherPath = moduleSdk.getHomePath() + File.separator + FanSdkType.getFanLauncherPath(); final VirtualFile buildVirtualFile = FanModuleSettings.getInstance(moduleToBuild).getBuildScript(); if (VirtualFileUtil.fileExists(buildVirtualFile)) { // Build Fan bytecode (.pod archive) Process process; try { //TODO [Dror] reuese from FanScriptRunConfiguration process = Runtime.getRuntime().exec(new String[]{fanLauncherPath, buildVirtualFile.getPath(), "-v", "compile"}); final OSProcessHandler handler = new OSProcessHandler(process, ""); handler.addProcessListener(new FanCompileProcessListener(context, moduleToBuild, toRecompile)); handler.startNotify(); } catch (IOException e) { e.printStackTrace(); // Unable to build this module context.addMessage(CompilerMessageCategory.ERROR, "Failed to compile module", null, -1, -1); toRecompile.addAll(moduleToFile.get(moduleToBuild)); } } else { // Unable to build this module context.addMessage(CompilerMessageCategory.ERROR, "Failed to compile module, could not find build file: " + FanModuleSettings.getInstance(moduleToBuild).getBuildScriptPath() , null, -1, -1); toRecompile.addAll(moduleToFile.get(moduleToBuild)); } sink.add(moduleSdk.getHomePath() + "/lib", successfullyCompiled, toRecompile.toArray(new VirtualFile[0])); toRecompile = new HashSet<VirtualFile>(); successfullyCompiled = new HashSet<TranslatingCompiler.OutputItem>(); } } @NotNull public String getDescription() { return FAN_COMPILER; } public boolean validateConfiguration(final CompileScope compileScope) { final VirtualFile[] files = compileScope.getFiles(FanFileType.FAN_FILE_TYPE, true); if (files.length == 0) { return true; } final Set<Module> modules = new HashSet<Module>(); for (final VirtualFile file : files) { final ProjectRootManager rootManager = ProjectRootManager.getInstance(myProject); final Module module = rootManager.getFileIndex().getModuleForFile(file); if (module != null) { modules.add(module); } } final Set<Module> nojdkModules = new HashSet<Module>(); for (final Module module : compileScope.getAffectedModules()) { if (!(module.getModuleType() instanceof JavaModuleType)) { continue; } final Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); if (sdk == null || !(sdk.getSdkType() instanceof FanSdkType)) { nojdkModules.add(module); } } if (!nojdkModules.isEmpty()) { final Module[] noJdkArray = nojdkModules.toArray(new Module[0]); if (noJdkArray.length == 1) { Messages.showErrorDialog(myProject, FanBundle.message("cannot.compile.fan.files.no.sdk", noJdkArray[0].getName()), FanBundle.message("cannot.compile")); } else { final StringBuffer modulesList = new StringBuffer(); for (int i = 0; i < noJdkArray.length; i++) { if (i > 0) { modulesList.append(", "); } modulesList.append(noJdkArray[i].getName()); } Messages.showErrorDialog(myProject, FanBundle.message("cannot.compile.fan.files.no.sdk.mult", modulesList.toString()), FanBundle.message("cannot.compile")); } return false; } return true; } class FanCompileProcessListener implements ProcessListener{ private boolean finishedSuccessfuly = false; private String podName; private final Module module; private final CompileContext context; private final String moduleRootPath; private final String projectRoot; private final Set<VirtualFile> toRecompile; private final List<String> jstubParams; FanCompileProcessListener(final CompileContext context, final Module module, final Set<VirtualFile> toRecompile) { this.context = context; this.module = module; this.jstubParams = new ArrayList<String>(); this.jstubParams.add(VirtualFileUtil.buildUrl(FanUtil.getSdk(module).getHomePath(), FanSdkType.getJStubPath())); if (SystemInfo.isUnix) { jstubParams.add("jstub"); } this.moduleRootPath = context.getModuleOutputDirectory(module).getPath(); jstubParams.add("-v"); jstubParams.add("-d"); jstubParams.add(moduleRootPath); this.projectRoot = new File(context.getProject().getBaseDir().getPath()).getAbsolutePath(); this.toRecompile = toRecompile; } public void startNotified(final ProcessEvent event) {} //TODO - replace this with a more elegant solution public void processTerminated(final ProcessEvent event) { if (event.getExitCode() == 0 && finishedSuccessfuly) { Process process; try { final List<String> params = new ArrayList<String>(jstubParams); params.add(podName); process = Runtime.getRuntime().exec(params.toArray(new String[0])); final OSProcessHandler handler = new OSProcessHandler(process, ""); handler.addProcessListener(new ProcessAdapter() { private int classesCreated = 0; @Override public void onTextAvailable(final ProcessEvent event, final Key outputType) { if(event.getText().contains(".class")) { classesCreated++; } } @Override public void processTerminated(final ProcessEvent event) { if (event.getExitCode() != 0) { context.addMessage(CompilerMessageCategory.ERROR, "Failed to create Java stubs for pod " + podName, null, -1, -1); } else { final File generatedJarFile = new File(VirtualFileUtil.buildUrl(moduleRootPath, podName + ".jar")); final File outFolder = new File(VirtualFileUtil.buildUrl(moduleRootPath, "classes")); if (generatedJarFile.exists()) { outFolder.mkdirs(); try { ZipUtil.extract(generatedJarFile, outFolder, null); } catch (IOException e) { context.addMessage(CompilerMessageCategory.ERROR, "Failed to extract classes from " + generatedJarFile.getAbsolutePath(), null, -1, -1); } } context.addMessage(CompilerMessageCategory.INFORMATION, "Java stubs for pod '" + podName + "' created successfully", null, -1, -1); context.addMessage(CompilerMessageCategory.STATISTICS, classesCreated + " classes created", null, -1, -1); } } }); handler.startNotify(); } catch (IOException e) { context.addMessage(CompilerMessageCategory.ERROR, "Failed to create Java stubs for pod '" + podName + "' : " + e.getMessage(), null, -1, -1); } } } public void processWillTerminate(final ProcessEvent event, final boolean willBeDestroyed) {} public void onTextAvailable(final ProcessEvent event, final Key outputType) { final String eventText = event.getText(); if (eventText.contains("BUILD SUCCESS")) { finishedSuccessfuly = true; context.addMessage(CompilerMessageCategory.INFORMATION, "Compilation of Pod '" + podName + "' finished successfuly", null, -1, -1); } else if (eventText.contains("BUILD FAILED")) { finishedSuccessfuly = false; context.addMessage(CompilerMessageCategory.ERROR, "Compilation of Pod '" + podName + "' failed", null, -1, -1); } else if (eventText.contains("Compile")) { //TODO this is a really ugly way to get the pod name. need something generic final int indexOfLefttBr = eventText.indexOf("["); final int indexOfRightBr = eventText.indexOf("]"); podName = indexOfLefttBr > -1 && indexOfRightBr > indexOfLefttBr ? eventText.substring(indexOfLefttBr + 1, indexOfRightBr) : "Unknown"; context.addMessage(CompilerMessageCategory.INFORMATION, "Compiling Pod '" + podName + "'", null, -1, -1); } else if(eventText.startsWith(projectRoot)) { // <FILE>(<ROW>,<COLUMN>): <ERROR> final String errorFile = eventText.substring(0, eventText.indexOf("(")); final String errorLocation = eventText.substring(eventText.indexOf("(") + 1, eventText.indexOf(")")); final String[] rowAndColumn = errorLocation.split(","); final VirtualFile errorVirtualFile = VirtualFileUtil.findFileByLocalPath(errorFile); toRecompile.add(errorVirtualFile); context.addMessage(CompilerMessageCategory.ERROR, eventText, errorVirtualFile.getUrl(), Integer.valueOf(rowAndColumn[0]), Integer.valueOf(rowAndColumn[1])); } } public Set<VirtualFile> getFilesToRecompile() { return toRecompile; } } }