/******************************************************************************* * Copyright (c) 2005, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.dltk.internal.core.builder; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.IWorkspaceRoot; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.dltk.core.DLTKContentTypeManager; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.IBuildpathEntry; import org.eclipse.dltk.core.IDLTKLanguageToolkit; import org.eclipse.dltk.core.IModelMarker; import org.eclipse.dltk.core.IScriptProjectFilenames; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.ScriptProjectUtil; import org.eclipse.dltk.core.builder.IBuildChange; import org.eclipse.dltk.core.builder.IBuildState; import org.eclipse.dltk.core.builder.IProjectChange; import org.eclipse.dltk.core.builder.IScriptBuilder; import org.eclipse.dltk.core.builder.IScriptBuilderVersionInfo; import org.eclipse.dltk.core.environment.EnvironmentManager; import org.eclipse.dltk.core.environment.IEnvironment; import org.eclipse.dltk.internal.core.BuildpathEntry; import org.eclipse.dltk.internal.core.ModelManager; import org.eclipse.dltk.internal.core.ScriptProject; import org.eclipse.osgi.util.NLS; public class ScriptBuilder extends IncrementalProjectBuilder { public static final boolean DEBUG = DLTKCore.DEBUG_SCRIPT_BUILDER; public static final boolean TRACE = DLTKCore.TRACE_SCRIPT_BUILDER; private static final int TRACE_BUILDER_MIN_ELAPSED_TIME = 10; public IProject currentProject = null; protected ScriptProject scriptProject = null; State lastState; /** * Hook allowing to initialize some static state before a complete build * iteration. This hook is invoked during PRE_AUTO_BUILD notification */ public static void buildStarting() { // build is about to start } /** * Hook allowing to reset some static state after a complete build * iteration. This hook is invoked during POST_AUTO_BUILD notification */ public static void buildFinished() { if (TRACE) System.out.println("build finished"); //$NON-NLS-1$ } private static void log(String message) { System.out.println(message); } private static final QualifiedName PROPERTY_BUILDER_VERSION = new QualifiedName( DLTKCore.PLUGIN_ID, "builderVersion"); //$NON-NLS-1$ private static final String CURRENT_VERSION = "200810012003-2123"; //$NON-NLS-1$ @Override protected IProject[] build(int kind, @SuppressWarnings("rawtypes") Map args, IProgressMonitor monitor) throws CoreException { this.currentProject = getProject(); if (currentProject == null || !currentProject.isAccessible()) return new IProject[0]; if (!DLTKLanguageManager.hasScriptNature(this.currentProject)) { return null; } long startTime = 0; if (DEBUG || TRACE) { startTime = System.currentTimeMillis(); log("\nStarting build of " + this.currentProject.getName() //$NON-NLS-1$ + " @ " + new Date(startTime)); //$NON-NLS-1$ } IProject[] requiredProjects = null; try { this.scriptProject = (ScriptProject) DLTKCore .create(currentProject); if (!ScriptProjectUtil.isBuilderEnabled(scriptProject)) { if (monitor != null) { monitor.done(); } return null; } IEnvironment environment = EnvironmentManager .getEnvironment(scriptProject); if (environment == null || !environment.isConnected()) { // Do not build if environment is not available. // TODO: Store build requests and call builds when connection // will // be established. if (monitor != null) { monitor.done(); } return null; } final String version = currentProject .getPersistentProperty(PROPERTY_BUILDER_VERSION); if (version == null) { removeWrongTaskMarkers(); currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION, CURRENT_VERSION); kind = FULL_BUILD; } else if (!CURRENT_VERSION.equals(version)) { if ("200810012003".equals(version)) { //$NON-NLS-1$ removeWrongTaskMarkers(); } currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION, CURRENT_VERSION); kind = FULL_BUILD; } if (monitor == null) { monitor = new NullProgressMonitor(); } if (kind == FULL_BUILD) { if (DEBUG) log("Performing full build as requested by user"); //$NON-NLS-1$ fullBuild(monitor); } else { if ((this.lastState = getLastState(currentProject, monitor)) == null) { if (DEBUG) log("Performing full build since last saved state was not found"); //$NON-NLS-1$ fullBuild(monitor); } else { IResourceDelta delta = getDelta(getProject()); if (delta == null) { if (DEBUG) log("Performing full build since deltas are missing after incremental request"); //$NON-NLS-1$ fullBuild(monitor); } else if (isProjectConfigChange(delta)) { if (DEBUG) log("Performing full build since .project/.buildpath change"); //$NON-NLS-1$ fullBuild(monitor); } else { if (DEBUG) log("Performing incremental build"); //$NON-NLS-1$ requiredProjects = getRequiredProjects(true); incrementalBuild(delta, requiredProjects, monitor); } } } } catch (OperationCanceledException e) { // TODO what? } finally { cleanup(); } if (DEBUG || TRACE) { final long endTime = System.currentTimeMillis(); if (DEBUG) { log("Finished build of " + currentProject.getName() //$NON-NLS-1$ + " @ " + new Date(endTime) + ", elapsed " + (endTime - startTime) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } if (TRACE) { System.out .println("-----SCRIPT-BUILDER-INFORMATION-TRACE----------------------------"); //$NON-NLS-1$ System.out.println("Finished build of project:" //$NON-NLS-1$ + currentProject.getName() + "\n" //$NON-NLS-1$ + "Building time:" //$NON-NLS-1$ + Long.toString(endTime - startTime) + "\n" //$NON-NLS-1$ + "Build type:" //$NON-NLS-1$ + (kind == FULL_BUILD ? "Full build" //$NON-NLS-1$ : "Incremental build")); //$NON-NLS-1$ System.out .println("-----------------------------------------------------------------"); //$NON-NLS-1$ } } monitor.done(); if (requiredProjects == null) { requiredProjects = getRequiredProjects(true); } return requiredProjects; } private void cleanup() { lastState = null; } private static boolean isProjectConfigChange(IResourceDelta projectDelta) { final String[] filenames = { IScriptProjectFilenames.BUILDPATH_FILENAME, IScriptProjectFilenames.PROJECT_FILENAME }; for (String filename : filenames) { final IResourceDelta delta = projectDelta.findMember(new Path( filename)); if (delta != null) { switch (delta.getKind()) { case IResourceDelta.ADDED: case IResourceDelta.REMOVED: return true; case IResourceDelta.CHANGED: return (delta.getFlags() & (IResourceDelta.CONTENT | IResourceDelta.ENCODING)) != 0; } } } return false; } /** * Remove incorrect task markers. * * DLTK 0.95 were creating wrong task markers, so this function is here to * remove them. New markers will be created by the builder later. * * @param project * @throws CoreException */ private void removeWrongTaskMarkers() throws CoreException { final IMarker[] markers = currentProject.findMarkers(IMarker.TASK, false, IResource.DEPTH_INFINITE); for (int i = 0; i < markers.length; ++i) { final IMarker marker = markers[i]; final IResource resource = marker.getResource(); if (resource.getType() != IResource.FILE) { continue; } if (!DLTKContentTypeManager.isValidResourceForContentType( scriptProject.getLanguageToolkit(), resource)) { continue; } final Map<?, ?> attributes = marker.getAttributes(); if (attributes == null) { continue; } if (!Boolean.FALSE.equals(attributes.get(IMarker.USER_EDITABLE))) { continue; } if (attributes.containsKey(IMarker.LINE_NUMBER) && attributes.containsKey(IMarker.MESSAGE) && attributes.containsKey(IMarker.PRIORITY) && attributes.containsKey(IMarker.CHAR_START) && attributes.containsKey(IMarker.CHAR_END)) { marker.delete(); } } } private static class BuildState extends AbstractBuildState { final State state; public BuildState(State state) { super(state.scriptProjectName); this.state = state; } public void recordImportProblem(IPath path) { this.state.recordImportProblem(path); } public void recordDependency(IPath path, IPath dependency, int flags) { Assert.isTrue(flags != 0); this.state.recordDependency(path, dependency, flags); } } @Override protected void clean(IProgressMonitor monitor) throws CoreException { long start = 0; if (TRACE) { start = System.currentTimeMillis(); } this.currentProject = getProject(); if (!DLTKLanguageManager.hasScriptNature(this.currentProject)) { return; } this.scriptProject = (ScriptProject) DLTKCore.create(currentProject); if (currentProject == null || !currentProject.isAccessible()) return; try { monitor.beginTask(NLS.bind( Messages.ScriptBuilder_cleaningScriptsIn, currentProject.getName()), 66); if (monitor.isCanceled()) { return; } ModelManager.getModelManager().setLastBuiltState(currentProject, null); IScriptBuilder[] builders = getScriptBuilders(); if (builders != null) { for (int k = 0; k < builders.length; k++) { IProgressMonitor sub = new SubProgressMonitor(monitor, 1); builders[k].clean(scriptProject, sub); if (monitor.isCanceled()) { break; } } } resetBuilders(builders, new BuildStateStub(currentProject.getName()), monitor); } catch (CoreException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } if (TRACE) { System.out .println("-----SCRIPT-BUILDER-INFORMATION-TRACE----------------------------"); //$NON-NLS-1$ System.out.println("Finished clean of project:" //$NON-NLS-1$ + currentProject.getName() + "\n" //$NON-NLS-1$ + "Building time:" //$NON-NLS-1$ + Long.toString(System.currentTimeMillis() - start)); System.out .println("-----------------------------------------------------------------"); //$NON-NLS-1$ } monitor.done(); } private IProject[] getRequiredProjects(boolean includeBinaryPrerequisites) { if (scriptProject == null) return new IProject[0]; IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); ArrayList<IProject> projects = new ArrayList<IProject>(); try { IBuildpathEntry[] entries = scriptProject.getExpandedBuildpath(); for (int i = 0, l = entries.length; i < l; i++) { IBuildpathEntry entry = entries[i]; IPath path = entry.getPath(); IProject p = null; switch (entry.getEntryKind()) { case IBuildpathEntry.BPE_PROJECT: p = workspaceRoot.getProject(path.lastSegment()); // missing projects are considered too if (((BuildpathEntry) entry).isOptional() && !ScriptProject.hasScriptNature(p)) // except if entry is optional p = null; break; case IBuildpathEntry.BPE_LIBRARY: if (includeBinaryPrerequisites && path.segmentCount() > 1) { /* * some binary resources on the class path can come from * projects that are not included in the project * references */ IResource resource = workspaceRoot.findMember(path .segment(0)); if (resource instanceof IProject) p = (IProject) resource; } } if (p != null && !projects.contains(p)) projects.add(p); } } catch (ModelException e) { return new IProject[0]; } return projects.toArray(new IProject[projects.size()]); } public State getLastState(IProject project, IProgressMonitor monitor) { return (State) ModelManager.getModelManager().getLastBuiltState( project, monitor); } private State clearLastState() { State state = new State(this); State prevState = (State) ModelManager.getModelManager() .getLastBuiltState(currentProject, null); if (prevState != null) { if (prevState.noCleanExternalFolders) { state.externalFolderLocations = prevState.externalFolderLocations; return state; } } ModelManager.getModelManager().setLastBuiltState(currentProject, null); return state; } private static final int WORK_RESOURCES = 50; private static final int WORK_EXTERNAL = 100; private static final int WORK_SOURCES = 100; private static final int WORK_BUILD = 750; protected static final String NONAME = ""; //$NON-NLS-1$ protected void fullBuild(final IProgressMonitor monitor) { final State newState = clearLastState(); final IBuildState buildState = new BuildState(newState); IScriptBuilder[] builders = null; try { monitor.setTaskName(NLS.bind( Messages.ScriptBuilder_buildingScriptsIn, currentProject.getName())); monitor.beginTask(NONAME, WORK_RESOURCES + WORK_EXTERNAL + WORK_SOURCES + WORK_BUILD); builders = getScriptBuilders(); if (builders == null || builders.length == 0) { return; } final IBuildChange buildChange = new FullBuildChange( currentProject, monitor); for (IScriptBuilder builder : builders) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } builder.prepare(buildChange, buildState, monitor); } for (IScriptBuilder builder : builders) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } final long start = TRACE ? System.currentTimeMillis() : 0; builder.build(buildChange, buildState, monitor); if (TRACE) { final long elapsed = System.currentTimeMillis() - start; if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) { System.out.println(builder.getClass().getName() + " " + elapsed + "ms"); } } } saveBuilderVersions(builders); updateExternalFolderLocations(newState, buildChange); } catch (CoreException e) { DLTKCore.error(e); } finally { resetBuilders(builders, buildState, monitor); ModelManager.getModelManager().setLastBuiltState(currentProject, newState); monitor.done(); this.lastState = null; } } protected void resetBuilders(IScriptBuilder[] builders, IBuildState state, IProgressMonitor monitor) { if (builders != null) { for (int k = 0; k < builders.length; k++) { builders[k].endBuild(scriptProject, state, monitor); } } } private static class DependencyBuildChange extends BuildChange { public DependencyBuildChange(IProject project, IResourceDelta delta, List<IFile> files, IProgressMonitor monitor) { super(project, delta, files, monitor); } @Override public boolean isDependencyBuild() { return true; } } protected void incrementalBuild(IResourceDelta delta, IProject[] requiredProjects, IProgressMonitor monitor) throws CoreException { final State newState = new State(this); newState.copyFrom(this.lastState); final Set<IPath> externalFoldersBefore = new HashSet<IPath>( newState.getExternalFolders()); final BuildState buildState = new BuildState(newState); IScriptBuilder[] builders = null; try { monitor.setTaskName(NLS.bind( Messages.ScriptBuilder_buildingScriptsIn, currentProject.getName())); monitor.beginTask(NONAME, WORK_RESOURCES + WORK_EXTERNAL + WORK_SOURCES + WORK_BUILD); builders = getScriptBuilders(); if (builders == null || builders.length == 0) { return; } IBuildChange buildChange = null; if (isBuilderVersionChange(builders)) { buildChange = new FullBuildChange(currentProject, monitor); newState.resetDependencies(); } if (buildChange == null) { buildChange = createBuildChange(delta, requiredProjects, externalFoldersBefore, monitor); } for (IScriptBuilder builder : builders) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } builder.prepare(buildChange, buildState, monitor); if (buildChange.getBuildType() == IScriptBuilder.FULL_BUILD && buildChange instanceof IncrementalBuildChange) { if (TRACE) { System.out.println("Full build requested by " + builder.getClass().getName()); } buildChange = new FullBuildChange(currentProject, monitor); newState.resetDependencies(); } } if (buildChange instanceof IncrementalBuildChange) { final Set<IPath> changes = ((IncrementalBuildChange) buildChange) .getChangedPaths(); if (TRACE) { System.out.println(" Changes: " + changes); } newState.removeDependenciesFor(changes); } for (IScriptBuilder builder : builders) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } final long start = TRACE ? System.currentTimeMillis() : 0; builder.build(buildChange, buildState, monitor); if (TRACE) { final long elapsed = System.currentTimeMillis() - start; if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) { System.out.println(builder.getClass().getName() + " " + elapsed + "ms"); } } } newState.recordStructuralChanges(buildState.getStructuralChanges()); if (buildChange instanceof IncrementalBuildChange) { final Set<IPath> processed = new HashSet<IPath>(); final Set<IPath> changes = ((IncrementalBuildChange) buildChange) .getChangedPaths(); processed.addAll(changes); final Set<IPath> queue = new HashSet<IPath>(); final Set<IPath> newStructuralChanges = new HashSet<IPath>(); // TODO review cross-project dependency handling for (IProjectChange projectChange : buildChange .getRequiredProjectChanges()) { final Collection<IPath> projectChanges = ((IncrementalProjectChange) projectChange) .getChangedPaths(); final State projectState = getLastState( projectChange.getProject(), monitor); if (projectState != null) { projectChanges.addAll(projectState .getAllStructuralDependencies(projectChanges)); } this.lastState.findDependenciesOf(projectChanges, buildState.getStructuralChanges(), false, queue, newStructuralChanges); } final IWorkspaceRoot root = ResourcesPlugin.getWorkspace() .getRoot(); buildState .recordStructuralChanges(((IncrementalProjectChange) buildChange) .getAddedPaths()); buildState .recordStructuralChanges(((IncrementalProjectChange) buildChange) .getDeletedPaths()); for (int iterationNumber = 0;; ++iterationNumber) { this.lastState.findDependenciesOf(changes, buildState.getStructuralChanges(), iterationNumber == 0, queue, newStructuralChanges); queue.removeAll(processed); if (queue.isEmpty()) { break; } if (TRACE) { System.out.println(" Queue: " + queue); } newState.removeDependenciesFor(queue); final List<IFile> files = new ArrayList<IFile>(); for (IPath path : queue) { files.add(root.getFile(path)); } buildState.resetStructuralChanges(); buildState.recordStructuralChanges(newStructuralChanges); newStructuralChanges.clear(); final DependencyBuildChange qChange = new DependencyBuildChange( currentProject, delta, files, monitor); for (IScriptBuilder builder : builders) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } final long start = TRACE ? System.currentTimeMillis() : 0; builder.build(qChange, buildState, monitor); if (TRACE) { final long elapsed = System.currentTimeMillis() - start; if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) { System.out.println(builder.getClass().getName() + " " + elapsed + "ms"); } } } changes.clear(); changes.addAll(queue); processed.addAll(queue); queue.clear(); } } saveBuilderVersions(builders); updateExternalFolderLocations(newState, buildChange); } catch (CoreException e) { DLTKCore.error(e); } finally { resetBuilders(builders, buildState, monitor); if (TRACE) { // newState.dumpDependencies(); } ModelManager.getModelManager().setLastBuiltState(currentProject, newState); monitor.done(); this.lastState = null; } } private QualifiedName getQualifiedName(IScriptBuilderVersionInfo vi) { return vi.getVersionKey(); } protected boolean isBuilderVersionChange(IScriptBuilder[] builders) throws CoreException { for (IScriptBuilder builder : builders) { if (builder instanceof IScriptBuilderVersionInfo) { final IScriptBuilderVersionInfo vi = (IScriptBuilderVersionInfo) builder; final String builderVersion = vi.getVersion(); if (builderVersion == null) continue; final String version = currentProject .getPersistentProperty(getQualifiedName(vi)); if (!builderVersion.equals(version)) { return true; } } } return false; } private void saveBuilderVersions(IScriptBuilder[] builders) throws CoreException { for (IScriptBuilder builder : builders) { if (builder instanceof IScriptBuilderVersionInfo) { final IScriptBuilderVersionInfo vi = (IScriptBuilderVersionInfo) builder; final String builderVersion = vi.getVersion(); if (builderVersion == null) continue; currentProject.setPersistentProperty(getQualifiedName(vi), builderVersion); } } } private IBuildChange createBuildChange(IResourceDelta delta, IProject[] requiredProjects, final Set<IPath> externalFoldersBefore, IProgressMonitor monitor) { final List<IProjectChange> projectChanges = new ArrayList<IProjectChange>(); for (IProject project : requiredProjects) { final IResourceDelta projectDelta = getDelta(project); if (projectDelta == null) { return new FullBuildChange(currentProject, monitor); } if (projectDelta.getKind() != IResourceDelta.NO_CHANGE) { projectChanges.add(new IncrementalProjectChange(projectDelta, project, monitor)); } } return new IncrementalBuildChange( delta, projectChanges.toArray(new IProjectChange[projectChanges.size()]), currentProject, monitor, new ArrayList<IPath>( externalFoldersBefore)); } private static void updateExternalFolderLocations(State state, IBuildChange buildChange) throws CoreException { state.externalFolderLocations.clear(); state.externalFolderLocations.addAll(buildChange .getExternalPaths(IProjectChange.DEFAULT)); } /** * Return script builders for the current project. ScriptBuilders are * initialized here so this method should be called only once during build * operation. * * @return * @throws CoreException */ protected IScriptBuilder[] getScriptBuilders() throws CoreException { IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(scriptProject); if (toolkit != null) { IScriptBuilder[] builders = getScriptBuilders(toolkit); if (builders != null) { final List<IScriptBuilder> result = new ArrayList<IScriptBuilder>( builders.length); for (int k = 0; k < builders.length; k++) { if (builders[k].initialize(scriptProject)) { result.add(builders[k]); } } builders = result.toArray(new IScriptBuilder[result.size()]); } return builders; } else { return null; } } protected IScriptBuilder[] getScriptBuilders(IDLTKLanguageToolkit toolkit) { return ScriptBuilderManager.getScriptBuilders(toolkit.getNatureId()); } public static void removeProblemsAndTasksFor(IResource resource) { try { if (resource != null && resource.exists()) { resource.deleteMarkers( IModelMarker.SCRIPT_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(IModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE); // delete managed markers } } catch (CoreException e) { // assume there were no problems } } public static void writeState(Object state, DataOutputStream out) throws IOException { ((State) state).write(out); } public static State readState(IProject project, DataInputStream in) throws IOException { State state = State.read(project, in); return state; } }