/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Oct 25, 2004 * * @author Fabio Zadrozny */ package org.python.pydev.builder; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.jface.text.IDocument; import org.python.pydev.builder.pycremover.PycHandlerBuilderVisitor; import org.python.pydev.builder.syntaxchecker.PySyntaxChecker; import org.python.pydev.builder.todo.PyTodoVisitor; import org.python.pydev.core.ExtensionHelper; import org.python.pydev.core.FileUtilsFileBuffer; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.IPythonPathNature; import org.python.pydev.core.log.Log; import org.python.pydev.editor.codecompletion.revisited.PyCodeCompletionVisitor; import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.shared_core.callbacks.ICallback0; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.utils.PyFileListing; /** * This builder only passes through python files * * @author Fabio Zadrozny */ public class PyDevBuilder extends IncrementalProjectBuilder { private static final boolean DEBUG = false; /** * * @return a list of visitors for building the application. */ public List<PyDevBuilderVisitor> getVisitors() { List<PyDevBuilderVisitor> list = new ArrayList<PyDevBuilderVisitor>(); list.add(new PyTodoVisitor()); list.add(new PyCodeCompletionVisitor()); list.add(new PycHandlerBuilderVisitor()); list.add(new PySyntaxChecker()); list.addAll(ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_BUILDER)); return list; } /** * Marking that no locking should be done during the build. */ public ISchedulingRule getRule() { return null; } @Override public ISchedulingRule getRule(int kind, Map<String, String> args) { return null; } /** * Builds the project. * * @see org.eclipse.core.internal.events InternalBuilder#build(int, java.util.Map, org.eclipse.core.runtime.IProgressMonitor) */ @Override protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { if (PyDevBuilderPrefPage.usePydevBuilders() == false) { return null; } if (kind == IncrementalProjectBuilder.FULL_BUILD || kind == IncrementalProjectBuilder.CLEAN_BUILD) { // Do a Full Build: Use a ResourceVisitor to process the tree. //Timer timer = new Timer(); performFullBuild(monitor); //timer.printDiff("Total time for analysis of: " + getProject()); } else { // Build it with a delta IResourceDelta delta = getDelta(getProject()); if (delta == null) { //no delta (unspecified changes?... let's do a full build...) performFullBuild(monitor); } else { VisitorMemo memo = new VisitorMemo(); memo.put(PyDevBuilderVisitor.IS_FULL_BUILD, false); //mark it as delta build // ok, we have a delta // first step is just counting them PyDevDeltaCounter counterVisitor = new PyDevDeltaCounter(); counterVisitor.memo = memo; delta.accept(counterVisitor); List<PyDevBuilderVisitor> visitors = getVisitors(); //sort by priority Collections.sort(visitors); PydevGrouperVisitor grouperVisitor = new PydevGrouperVisitor(visitors, monitor, counterVisitor.getNVisited()); grouperVisitor.memo = memo; try (AutoCloseable closeable = withStartEndVisitingNotifications(visitors, monitor, false, null)) { try { delta.accept(grouperVisitor); } catch (Exception e) { Log.log(e); } try { grouperVisitor.finishDelayedVisits(); } catch (Exception e) { Log.log(e); } } catch (Exception e1) { Log.log(e1); } } } return null; } /** * Processes all python files. * * @param monitor */ private void performFullBuild(IProgressMonitor monitor) throws CoreException { IProject project = getProject(); //we need the project... if (project != null) { IPythonNature nature = PythonNature.getPythonNature(project); //and the nature... if (nature != null && nature.startRequests()) { nature.updateMtime(); try { IPythonPathNature pythonPathNature = nature.getPythonPathNature(); pythonPathNature.getProjectSourcePath(false); //this is just to update the paths (in case the project name has just changed) List<IFile> resourcesToParse = new ArrayList<IFile>(); List<PyDevBuilderVisitor> visitors = getVisitors(); try (AutoCloseable closable = withStartEndVisitingNotifications(visitors, monitor, true, nature)) { monitor.beginTask("Building...", (visitors.size() * 100) + 30); IResource[] members = project.members(); if (members != null) { // get all the python files to get information. int len = members.length; for (int i = 0; i < len; i++) { try { IResource member = members[i]; if (member == null) { continue; } if (member.getType() == IResource.FILE) { addToResourcesToParse(resourcesToParse, (IFile) member, nature); } else if (member.getType() == IResource.FOLDER) { //if it is a folder, let's get all python files that are beneath it //the heuristics to know if we have to analyze them are the same we have //for a single file List<IFile> files = PyFileListing.getAllIFilesBelow((IFolder) member); for (IFile file : files) { if (file != null) { addToResourcesToParse(resourcesToParse, file, nature); } } } else { if (DEBUG) { System.out.println("Unknown type: " + member.getType()); } } } catch (Exception e) { // that's ok... } } monitor.worked(30); buildResources(resourcesToParse, monitor, visitors); } } catch (Exception e1) { Log.log(e1); } } finally { nature.endRequests(); } } } monitor.done(); } private AutoCloseable withStartEndVisitingNotifications(final List<PyDevBuilderVisitor> visitors, final IProgressMonitor monitor, boolean isFullBuild, IPythonNature nature) { for (PyDevBuilderVisitor visitor : visitors) { try { visitor.visitingWillStart(monitor, isFullBuild, nature); } catch (Throwable e) { Log.log(e); } } return new AutoCloseable() { @Override public void close() throws Exception { for (PyDevBuilderVisitor visitor : visitors) { try { visitor.visitingEnded(monitor); } catch (Throwable e) { Log.log(e); } } } }; } /** * @param resourcesToParse the list where the resource may be added * @param member the resource we are adding * @param nature the nature associated to the resource */ private void addToResourcesToParse(List<IFile> resourcesToParse, IFile member, IPythonNature nature) { //analyze it only if it is a valid source file String fileExtension = member.getFileExtension(); if (DEBUG) { System.out.println("Checking name:'" + member.getName() + "' projPath:'" + member.getProjectRelativePath() + "' ext:'" + fileExtension + "'"); System.out.println("loc:'" + member.getLocation() + "' rawLoc:'" + member.getRawLocation() + "'"); } if (fileExtension != null && PythonPathHelper.isValidSourceFile("." + fileExtension)) { if (DEBUG) { System.out.println("Adding resource to parse:" + member.getProjectRelativePath()); } resourcesToParse.add(member); } } /** * Default implementation. Visits each resource once at a time. May be overridden if a better implementation is needed. * * @param resourcesToParse list of resources from project that are python files. * @param monitor * @param visitors */ public void buildResources(List<IFile> resourcesToParse, IProgressMonitor monitor, List<PyDevBuilderVisitor> visitors) { // we have 100 units here double inc = (visitors.size() * 100) / (double) resourcesToParse.size(); double total = 0; int totalResources = resourcesToParse.size(); int i = 0; FastStringBuffer bufferToCreateString = new FastStringBuffer(); boolean loggedMisconfiguration = false; long lastProgressTime = 0; Object memoSharedProjectState = null; for (Iterator<IFile> iter = resourcesToParse.iterator(); iter.hasNext() && monitor.isCanceled() == false;) { i += 1; total += inc; IFile r = iter.next(); PythonPathHelper.markAsPyDevFileIfDetected(r); IPythonNature nature = PythonNature.getPythonNature(r); if (nature == null) { continue; } if (!nature.startRequests()) { continue; } try { String moduleName; try { //we visit external because we must index them moduleName = nature.resolveModuleOnlyInProjectSources(r, true); if (moduleName == null) { continue; // we only analyze resources that are in the pythonpath } } catch (Exception e1) { if (!loggedMisconfiguration) { loggedMisconfiguration = true; //No point in logging it over and over again. Log.log(e1); } continue; } //create new memo for each resource VisitorMemo memo = new VisitorMemo(); memo.setSharedProjectState(memoSharedProjectState); memo.put(PyDevBuilderVisitor.IS_FULL_BUILD, true); //mark it as full build ICallback0<IDocument> doc = FileUtilsFileBuffer.getDocOnCallbackFromResource(r); memo.put(PyDevBuilderVisitor.DOCUMENT_TIME, System.currentTimeMillis()); PyDevBuilderVisitor.setModuleNameInCache(memo, r, moduleName); for (Iterator<PyDevBuilderVisitor> it = visitors.iterator(); it.hasNext() && monitor.isCanceled() == false;) { try { PyDevBuilderVisitor visitor = it.next(); visitor.memo = memo; //setting the memo must be the first thing. long currentTimeMillis = System.currentTimeMillis(); if (currentTimeMillis - lastProgressTime > 300) { communicateProgress(monitor, totalResources, i, r, visitor, bufferToCreateString); lastProgressTime = currentTimeMillis; } //on a full build, all visits are as some add... visitor.visitAddedResource(r, doc, monitor); } catch (Exception e) { Log.log(e); } } if (total > 1) { monitor.worked((int) total); total -= (int) total; } memoSharedProjectState = memo.getSharedProjectState(); } finally { nature.endRequests(); } } } /** * Used so that we can communicate the progress to the user * * @param bufferToCreateString: this is a buffer that's emptied and used to create the string to be shown to the * user with the progress. */ public static void communicateProgress(IProgressMonitor monitor, int totalResources, int i, IResource r, PyDevBuilderVisitor visitor, FastStringBuffer bufferToCreateString) { if (monitor != null) { bufferToCreateString.clear(); bufferToCreateString.append("PyDev: Analyzing "); bufferToCreateString.append(i); bufferToCreateString.append(" of "); bufferToCreateString.append(totalResources); bufferToCreateString.append(" ("); bufferToCreateString.append(r.getName()); bufferToCreateString.append(")"); //in this case the visitor does not have the progress and therefore does not communicate the progress String name = bufferToCreateString.toString(); monitor.subTask(name); } } }