package org.rascalmpl.eclipse.builder; import java.io.IOException; import java.io.PrintStream; import java.net.MalformedURLException; import java.util.Arrays; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.rascalmpl.eclipse.Activator; import org.rascalmpl.eclipse.IRascalResources; import org.rascalmpl.eclipse.editor.IDEServicesModelProvider; import org.rascalmpl.eclipse.editor.MessagesToMarkers; import org.rascalmpl.eclipse.preferences.RascalPreferences; import org.rascalmpl.eclipse.util.ProjectConfig; import org.rascalmpl.eclipse.util.RascalEclipseManifest; import org.rascalmpl.eclipse.util.ResourcesToModules; import org.rascalmpl.interpreter.load.IRascalSearchPathContributor; import org.rascalmpl.interpreter.load.RascalSearchPath; import org.rascalmpl.library.Prelude; import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.java2rascal.Java2Rascal; import org.rascalmpl.library.lang.rascal.boot.IKernel; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.uri.ProjectURIResolver; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValue; import io.usethesource.vallang.IValueFactory; import org.rascalmpl.values.ValueFactoryFactory; import io.usethesource.impulse.builder.MarkerCreator; import io.usethesource.impulse.runtime.RuntimePlugin; /** * This builder manages the execution of the Rascal compiler on all Rascal files which have been changed while editing them in Eclipse. * It also interacts with Project Clean actions to clear up files and markers on request. */ public class IncrementalRascalBuilder extends IncrementalProjectBuilder { // A kernel is 100Mb, so we can't have one for every project; that's why it's static: private static IKernel kernel; private static PrintStream out; private static PrintStream err; private static IValueFactory vf; private static List<String> binaryExtension = Arrays.asList("imps","rvm", "rvmx", "tc","sig","sigs"); private ISourceLocation projectLoc; private PathConfig pathConfig; static { synchronized(IncrementalRascalBuilder.class){ try { out = new PrintStream(RuntimePlugin.getInstance().getConsoleStream()); err = new PrintStream(RuntimePlugin.getInstance().getConsoleStream()); vf = ValueFactoryFactory.getValueFactory(); kernel = Java2Rascal.Builder .bridge(vf, new PathConfig(), IKernel.class) .stderr(err) .stdout(out) .build(); } catch (IOException e) { Activator.log("could not initialize incremental Rascal builder", e); } } } public IncrementalRascalBuilder() { } @Override protected void clean(IProgressMonitor monitor) throws CoreException { cleanBinFiles(monitor); cleanProblemMarkers(monitor); } private void cleanProblemMarkers(IProgressMonitor monitor) throws CoreException { RascalEclipseManifest manifest = new RascalEclipseManifest(); for (String src : manifest.getSourceRoots(getProject())) { getProject().findMember(src).accept(new IResourceVisitor() { @Override public boolean visit(IResource resource) throws CoreException { if (IRascalResources.RASCAL_EXT.equals(resource.getFileExtension())) { resource.deleteMarkers(IRascalResources.ID_RASCAL_MARKER, true, IResource.DEPTH_ONE); return false; } return true; } }); } } private void cleanBinFiles(IProgressMonitor monitor) throws CoreException { getProject().findMember(ProjectConfig.BIN_FOLDER).accept(new IResourceVisitor() { @Override public boolean visit(IResource resource) throws CoreException { if (binaryExtension.contains(resource.getFileExtension())) { resource.delete(true, monitor); return false; } return true; } }); } @Override protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { switch (kind) { case INCREMENTAL_BUILD: case AUTO_BUILD: buildIncremental(getDelta(getProject()), monitor); break; case FULL_BUILD: buildMain(monitor); break; } // TODO: return project this project depends on? return new IProject[0]; } private void buildMain(IProgressMonitor monitor) throws CoreException { IDEServicesModelProvider.getInstance().invalidateEverything(); IFile mfFile = getProject().getFile(RascalEclipseManifest.META_INF_RASCAL_MF); if (mfFile == null || !mfFile.exists()) { return; // fine; no meta file so we don't know what to compile. } else { // remove previous markers mfFile.deleteMarkers(IMarker.PROBLEM, true, 1); } RascalEclipseManifest mf = new RascalEclipseManifest(); String main = mf.getMainModule(getProject()); if (main == null) { // no main defined in the RASCAL.MF file is fine return; } initializeParameters(false); RascalSearchPath p = new RascalSearchPath(); p.addPathContributor(new IRascalSearchPathContributor() { @Override public String getName() { return "config"; } @Override public void contributePaths(List<ISourceLocation> path) { for (IValue val :pathConfig.getSrcs()) { path.add((ISourceLocation) val); } } }); ISourceLocation module = p.resolveModule(main); if (module == null) { // TODO: this should be a marker on RASCAL.MF IMarker marker = mfFile.createMarker(IMarker.PROBLEM); marker.setAttribute(IMarker.MESSAGE, "Main module with name " + main + " does not exist."); marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING); marker.setAttribute(IMarker.LINE_NUMBER, 1); return; } try { IConstructor result = kernel.compileAndLink(vf.string(main), pathConfig.asConstructor(kernel), kernel.kw_compileAndLink()); markErrors(module, result); } catch (Throwable e) { Activator.log("error during compilation of " + main, e); } finally { monitor.done(); } } private void buildIncremental(IResourceDelta delta, IProgressMonitor monitor) { if (!RascalPreferences.isRascalCompilerEnabled()) { return; } try { delta.accept(new IResourceDeltaVisitor() { @Override public boolean visit(IResourceDelta delta) throws CoreException { IPath path = delta.getProjectRelativePath(); if (RascalEclipseManifest.META_INF_RASCAL_MF.equals(path.toPortableString())) { // if the meta information has changed, we need to recompile everything clean(monitor); initializeParameters(true); buildMain(monitor); return false; } else if (IRascalResources.RASCAL_EXT.equals(path.getFileExtension() /* could be null */)) { if ((delta.getFlags() & IResourceDelta.CONTENT) == 0) { // if no content changed, we can bail out now. return false; } ISourceLocation loc = ProjectURIResolver.constructProjectURI(delta.getFullPath()); if (loc == null) { return false; } monitor.beginTask("Compiling " + loc, 100); try { IFile file = (IFile) delta.getResource(); file.deleteMarkers(IMarker.PROBLEM, true, 1); String module = ResourcesToModules.moduleFromFile(file); if (module != null) { initializeParameters(false); synchronized (kernel) { // System.err.println("buildIncremental project: " + getProject()); // System.err.println("buildIncremental file: " + file); // System.err.println("buildIncremental module: " + module); // System.err.println("buildIncremental: " + pathConfig); IConstructor result = kernel.compile(vf.string(module), pathConfig.asConstructor(kernel), kernel.kw_compile()); markErrors(loc, result); IDEServicesModelProvider.getInstance().clearUseDefCache(loc); } } else { // this module is not on the source search path } } catch (Throwable e) { Activator.log("Error during compilation of " + loc, e); } finally { monitor.done(); } return false; } return !ProjectConfig.BIN_FOLDER.equals(path.toPortableString()); } }); } catch (CoreException e) { Activator.log("error during Rascal compilation", e); } } private void markErrors(ISourceLocation loc, IConstructor result) throws MalformedURLException, IOException { if (result.has("main_module")) { result = (IConstructor) result.get("main_module"); } if (!result.has("messages")) { Activator.log("Unexpected Rascal compiler result: " + result, new IllegalArgumentException()); } new MessagesToMarkers().process(loc, (ISet) result.get("messages"), new MarkerCreator(new ProjectURIResolver().resolveFile(loc))); } private void initializeParameters(boolean force) throws CoreException { if (projectLoc != null && !force) { return; } IProject project = getProject(); // TODO: these should not be fields projectLoc = ProjectURIResolver.constructProjectURI(project.getFullPath()); pathConfig = new ProjectConfig(vf).getPathConfig(project); } }