package jetbrains.mps.testbench; /*Generated by MPS */ import java.util.Set; import jetbrains.mps.internal.collections.runtime.SetSequence; import java.util.HashSet; import jetbrains.mps.project.Project; import org.jetbrains.mps.openapi.module.SModule; import java.util.Map; import jetbrains.mps.internal.collections.runtime.MapSequence; import java.util.HashMap; import java.io.File; import java.io.IOException; import jetbrains.mps.generator.GenerationOptions; import jetbrains.mps.make.script.IResult; import jetbrains.mps.make.script.IScript; import jetbrains.mps.make.MakeSession; import jetbrains.mps.internal.make.cfg.TextGenFacetInitializer; import jetbrains.mps.internal.make.cfg.MakeFacetInitializer; import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes; import jetbrains.mps.vfs.IFile; import jetbrains.mps.internal.make.cfg.GenerateFacetInitializer; import jetbrains.mps.make.script.IScriptController; import jetbrains.mps.progress.EmptyProgressMonitor; import java.util.List; import jetbrains.mps.internal.collections.runtime.ListSequence; import java.util.ArrayList; import jetbrains.mps.internal.collections.runtime.IMapping; import jetbrains.mps.vfs.FileSystem; import jetbrains.mps.internal.collections.runtime.Sequence; import jetbrains.mps.internal.collections.runtime.IVisitor; import difflib.Patch; import difflib.DiffUtils; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.FileInputStream; import jetbrains.mps.util.FileUtil; import java.util.Queue; import jetbrains.mps.internal.collections.runtime.QueueSequence; import java.util.LinkedList; import jetbrains.mps.smodel.ModelAccess; import jetbrains.mps.util.Computable; import org.jetbrains.mps.openapi.model.SModel; import jetbrains.mps.util.SNodeOperations; import jetbrains.mps.make.script.ScriptBuilder; import jetbrains.mps.make.facet.IFacet; import jetbrains.mps.make.facet.ITarget; import jetbrains.mps.internal.collections.runtime.IWhereFilter; import jetbrains.mps.smodel.Language; import jetbrains.mps.internal.collections.runtime.ITranslator2; import jetbrains.mps.make.resources.IResource; import jetbrains.mps.smodel.ModelAccessHelper; import java.util.Collections; import jetbrains.mps.generator.GenerationFacade; import jetbrains.mps.smodel.resources.ModelsToResources; import jetbrains.mps.messages.IMessageHandler; import jetbrains.mps.messages.IMessage; import java.io.StringWriter; import java.io.PrintWriter; public class ModuleGenerationHolder { private static String[] BINARY_FILE_EXTENSIONS = new String[]{".png", ".gif"}; private Set<String> ignoredFiles = SetSequence.fromSetAndArray(new HashSet<String>(), "generated", "dependencies", "exports"); private final Project project; private final SModule module; private String tmpPath; private Map<String, String> path2tmp = MapSequence.fromMap(new HashMap<String, String>()); private final ModuleGenerationHolder.MyMessageHandler myMessageHandler = new ModuleGenerationHolder.MyMessageHandler(); private boolean isSuccessful; public ModuleGenerationHolder(SModule module, Project project) { this.module = module; this.project = project; File tmpDir; try { tmpDir = File.createTempFile("projecttest", "tmp"); tmpDir.delete(); tmpDir.mkdir(); } catch (IOException ex) { throw new RuntimeException(ex); } this.tmpPath = tmpDir.getAbsolutePath(); } public void build() throws Exception { if (!(needsGeneration())) { isSuccessful = true; return; } // sanity check build() doesn't come after diff() (due to broken test method ordering) assert tmpPath != null; final GenerationOptions.OptionsBuilder optBuilder = GenerationOptions.getDefaults(); boolean isParallel = "true".equalsIgnoreCase(System.getProperty("parallel.generation")); if (isParallel) { optBuilder.strictMode(true).generateInParallel(isParallel, 8); } IResult result; IScript scr = ModuleGenerationHolder.defaultScriptBuilder().toScript(); final MakeSession session = new MakeSession(project, myMessageHandler, true); // trace.info is useless for tests, however we do keep these files in repo, and diffModule test // fails if we don't generate one here TextGenFacetInitializer tgfi = new TextGenFacetInitializer().generateDebugInfo(true); MakeFacetInitializer mfi = new MakeFacetInitializer().setPathToFile(new _FunctionTypes._return_P1_E0<IFile, String>() { public IFile invoke(String path) { return tmpFile(path); } }); GenerateFacetInitializer gfi = new GenerateFacetInitializer().setGenerationOptions(optBuilder); IScriptController ctl = new IScriptController.Stub2(session, tgfi, mfi, gfi); result = new TestMakeService().make(session, ModuleGenerationHolder.collectResources(project, module), scr, ctl, new EmptyProgressMonitor()).get(); isSuccessful = result != null && result.isSucessful(); } public boolean isBuildSuccessful() { return isSuccessful; } public List<String> buildErrors() { return myMessageHandler.getGenerationErrors(); } public List<String> buildWarns() { return myMessageHandler.getGenerationWarnings(); } /*package*/ boolean hasFilesGenerated() { return !(MapSequence.fromMap(path2tmp).isEmpty()); } public List<String> diff() { List<String> diffs = ListSequence.fromList(new ArrayList<String>()); for (IMapping<String, String> p2t : MapSequence.fromMap(path2tmp).mappingsSet()) { File orig = new File(p2t.key()); File revd = new File(p2t.value()); if (orig.exists() && revd.exists() && orig.isDirectory() && revd.isDirectory()) { diffDirs(orig, revd, diffs); } else if (!(orig.exists()) && !(revd.exists())) { ListSequence.fromList(diffs).addElement("None exists: " + orig + " or " + revd); } else if (!(orig.exists())) { ListSequence.fromList(diffs).addElement("Created: " + revd); } else if (!(revd.exists())) { ListSequence.fromList(diffs).addElement("Removed: " + orig); } else { ListSequence.fromList(diffs).addElement("Something weird here: " + orig + " or here " + revd); } } return diffs; } private IFile tmpFile(String path) { if (MapSequence.fromMap(path2tmp).containsKey(path)) { return FileSystem.getInstance().getFileByPath(MapSequence.fromMap(path2tmp).get(path)); } int idx = path.indexOf("/"); idx = (idx < 0 ? path.indexOf(File.separator) : idx); String tmp = tmpPath + "/" + ((idx < 0 ? path.replace(':', '_') : path.substring(idx + 1))); MapSequence.fromMap(path2tmp).put(path, tmp); return FileSystem.getInstance().getFileByPath(tmp); } private void diffDirs(final File orig, File revd, final List<String> diffs) { Iterable<String> onames = Sequence.fromArray(orig.list()); Iterable<String> rnames = Sequence.fromArray(revd.list()); if (Sequence.fromIterable(onames).disjunction(Sequence.fromIterable(rnames)).isNotEmpty()) { Sequence.fromIterable(onames).subtract(Sequence.fromIterable(rnames)).visitAll(new IVisitor<String>() { public void visit(String it) { if (ignoredFile(it)) { return; } ListSequence.fromList(diffs).addElement("Removed: " + new File(orig, it)); } }); Sequence.fromIterable(rnames).subtract(Sequence.fromIterable(onames)).visitAll(new IVisitor<String>() { public void visit(String it) { if (ignoredFile(it)) { return; } ListSequence.fromList(diffs).addElement("Created: " + new File(orig, it)); } }); } for (String name : Sequence.fromIterable(onames).intersect(Sequence.fromIterable(rnames))) { if (ignoredFile(name)) { continue; } File onext = new File(orig, name); File rnext = new File(revd, name); if (onext.isDirectory() == rnext.isDirectory()) { if (!(onext.isDirectory())) { if (isBinary(name)) { continue; } List<String> olines = fileToStrings(onext); Patch patch = DiffUtils.diff(olines, fileToStrings(rnext)); if (!(patch.getDeltas().isEmpty())) { ListSequence.fromList(diffs).addSequence(ListSequence.fromList(DiffUtils.generateUnifiedDiff(onext.getPath(), rnext.getPath(), olines, patch, 5))); } } else { diffDirs(onext, rnext, diffs); } } else { ListSequence.fromList(diffs).addElement("Something weird here: " + onext + " or here " + rnext); } } } private boolean ignoredFile(String fileName) { return SetSequence.fromSet(ignoredFiles).contains(fileName) || (fileName != null && fileName.startsWith(".hash")); } private boolean isBinary(String filename) { for (String nextExt : BINARY_FILE_EXTENSIONS) { if (filename.endsWith(nextExt)) { return true; } } return false; } private List<String> fileToStrings(File f) { List<String> result = ListSequence.fromList(new ArrayList<String>()); BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(new FileInputStream(f), FileUtil.DEFAULT_CHARSET)); String line; while ((line = in.readLine()) != null) { ListSequence.fromList(result).addElement(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException ignore) { } } } return result; } public void cleanUp() { for (Queue<File> dirs = QueueSequence.fromQueueAndArray(new LinkedList<File>(), new File(tmpPath)); QueueSequence.fromQueue(dirs).isNotEmpty();) { File dir = QueueSequence.fromQueue(dirs).removeFirstElement(); dir.deleteOnExit(); for (File f : dir.listFiles()) { if (f.isDirectory()) { QueueSequence.fromQueue(dirs).addLastElement(f); } else { f.deleteOnExit(); } } } this.tmpPath = null; MapSequence.fromMap(path2tmp).clear(); myMessageHandler.cleanUp(); } /*package*/ boolean needsGeneration() { return ModelAccess.instance().runReadAction(new Computable<Boolean>() { public Boolean compute() { for (SModel descriptor : module.getModels()) { if (SNodeOperations.isGeneratable(descriptor)) { return true; } } return false; } }); } private static ScriptBuilder defaultScriptBuilder() { return new ScriptBuilder().withFacetNames(new IFacet.Name("jetbrains.mps.lang.resources.Binaries"), new IFacet.Name("jetbrains.mps.lang.core.Generate"), new IFacet.Name("jetbrains.mps.lang.core.TextGen"), new IFacet.Name("jetbrains.mps.make.facets.Make"), new IFacet.Name("jetbrains.mps.lang.editor.imageGen.GenerateImages")).withFinalTarget(new ITarget.Name("jetbrains.mps.make.facets.Make.make")); } private static Iterable<SModule> withGenerators(Iterable<SModule> modules) { return Sequence.fromIterable(modules).concat(Sequence.fromIterable(modules).where(new IWhereFilter<SModule>() { public boolean accept(SModule it) { return it instanceof Language; } }).translate(new ITranslator2<SModule, SModule>() { public Iterable<SModule> translate(SModule it) { return (List<SModule>) (List) ((Language) it).getGenerators(); } })); } private static Iterable<IResource> collectResources(Project project, final SModule module) { return new ModelAccessHelper(project.getModelAccess()).runReadAction(new Computable<Iterable<IResource>>() { public Iterable<IResource> compute() { Iterable<SModel> models = Sequence.fromIterable(withGenerators(Collections.singletonList(module))).translate(new ITranslator2<SModule, SModel>() { public Iterable<SModel> translate(SModule mod) { return mod.getModels(); } }).where(new IWhereFilter<SModel>() { public boolean accept(SModel it) { return GenerationFacade.canGenerate(it); } }); return new ModelsToResources(models).resources(false); } }); } private static class MyMessageHandler implements IMessageHandler { private final List<String> myGenerationErrors = new ArrayList<String>(); private final List<String> myGenerationWarnings = new ArrayList<String>(); private MyMessageHandler() { } @Override public void handle(IMessage msg) { switch (msg.getKind()) { case ERROR: if (msg.getException() != null) { StringWriter writer = new StringWriter(); msg.getException().printStackTrace(new PrintWriter(writer)); myGenerationErrors.add(msg.getText() + ": " + writer.getBuffer().toString()); } else { myGenerationErrors.add(msg.getText()); } break; case WARNING: myGenerationWarnings.add(msg.getText()); break; case INFORMATION: break; default: } } public List<String> getGenerationErrors() { return myGenerationErrors; } public List<String> getGenerationWarnings() { return myGenerationWarnings; } public void cleanUp() { myGenerationErrors.clear(); myGenerationWarnings.clear(); } } }