| /******************************************************************************* |
| * Copyright (c) 2005, 2017 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.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Queue; |
| import java.util.concurrent.ArrayBlockingQueue; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.dltk.compiler.problem.DefaultProblemFactory; |
| import org.eclipse.dltk.compiler.problem.IProblemFactory; |
| import org.eclipse.dltk.compiler.problem.IProblemReporter; |
| import org.eclipse.dltk.compiler.problem.IProblemSeverityTranslator; |
| import org.eclipse.dltk.compiler.problem.ProblemCategory; |
| import org.eclipse.dltk.compiler.util.Util; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.DLTKLanguageManager; |
| import org.eclipse.dltk.core.IDLTKLanguageToolkit; |
| import org.eclipse.dltk.core.IScriptProject; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.builder.IBuildChange; |
| import org.eclipse.dltk.core.builder.IBuildContext; |
| import org.eclipse.dltk.core.builder.IBuildParticipant; |
| import org.eclipse.dltk.core.builder.IBuildParticipantExtension; |
| import org.eclipse.dltk.core.builder.IBuildParticipantExtension2; |
| import org.eclipse.dltk.core.builder.IBuildParticipantExtension3; |
| import org.eclipse.dltk.core.builder.IBuildParticipantExtension4; |
| import org.eclipse.dltk.core.builder.IBuildParticipantFilter; |
| 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.internal.core.builder.BuildParticipantManager.BuildParticipantResult; |
| import org.eclipse.osgi.util.NLS; |
| |
| public class StandardScriptBuilder implements IScriptBuilder { |
| private static final boolean DEBUG = false; |
| |
| private static final int WORK_BUILD = 100; |
| private static final int PARALLEL_THRESHOLD = 10; |
| |
| private class BuildModulesJob extends Job { |
| |
| private Queue<ISourceModule> modules; |
| private int buildType; |
| private IBuildState state; |
| |
| public BuildModulesJob(Queue<ISourceModule> modules, int buildType, |
| IBuildState state) { |
| super("Build Modules"); //$NON-NLS-1$ |
| this.setSystem(true); |
| this.modules = modules; |
| this.buildType = buildType; |
| this.state = state; |
| } |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| for (ISourceModule module; (module = modules.poll()) != null;) { |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| processModule(module, buildType, state); |
| } |
| return Status.OK_STATUS; |
| } |
| } |
| |
| private List<IProblemReporter> fReporters = null; |
| |
| @Override |
| public void prepare(IBuildChange change, IBuildState state, |
| IProgressMonitor monitor) throws CoreException { |
| if (participants != null) { |
| for (int i = 0; i < participants.length; ++i) { |
| final IBuildParticipant participant = participants[i]; |
| if (participant instanceof IBuildParticipantExtension2) { |
| ((IBuildParticipantExtension2) participant).prepare(change, |
| state); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void build(IBuildChange change, IBuildState state, |
| IProgressMonitor monitor) throws CoreException { |
| // TODO progress reporting |
| buildExternalElements(change, monitor); |
| if (toolkit != null) { |
| buildNatureModules(change.getScriptProject(), change.getBuildType(), |
| change.getSourceModules(IProjectChange.DEFAULT), state, |
| SubMonitor.convert(monitor, WORK_BUILD)); |
| } |
| final List<IFile> resourceChanges = change |
| .getResources(IProjectChange.DEFAULT); |
| if (!resourceChanges.isEmpty()) { |
| buildResources(resourceChanges, |
| new SubProgressMonitor(monitor, 10)); |
| } |
| } |
| |
| private void buildExternalElements(IBuildChange change, |
| IProgressMonitor monitor) throws CoreException { |
| final int buildType = change.getBuildType(); |
| beginBuild(buildType, monitor); |
| final List<IBuildParticipantExtension2> extensions = selectExtension( |
| IBuildParticipantExtension2.class); |
| |
| if (extensions != null) { |
| final List<ISourceModule> externalElements = change |
| .getExternalModules(IProjectChange.DEFAULT); |
| int remainingWork = externalElements.size(); |
| for (final ISourceModule module : externalElements) { |
| if (monitor.isCanceled()) |
| return; |
| monitor.subTask(NLS.bind( |
| Messages.ValidatorBuilder_buildExternalModuleSubTask, |
| String.valueOf(remainingWork), |
| module.getElementName())); |
| final ExternalModuleBuildContext context = new ExternalModuleBuildContext( |
| module, buildType); |
| try { |
| for (int i = 0; i < extensions.size(); ++i) { |
| if (monitor.isCanceled()) { |
| return; |
| } |
| extensions.get(i).buildExternalModule(context); |
| } |
| } catch (CoreException e) { |
| DLTKCore.error(NLS.bind( |
| Messages.StandardScriptBuilder_errorBuildingExternalModule, |
| module.getElementName()), e); |
| } |
| --remainingWork; |
| } |
| } |
| } |
| |
| /** |
| * @return |
| */ |
| private <T> List<T> selectExtension(Class<T> clazz) { |
| if (participants != null) { |
| int count = 0; |
| for (int i = 0; i < participants.length; ++i) { |
| final IBuildParticipant participant = participants[i]; |
| if (clazz.isInstance(participant)) { |
| ++count; |
| } |
| } |
| if (count != 0) { |
| final List<T> result = new ArrayList<>(count); |
| for (int i = 0; i < participants.length; ++i) { |
| final IBuildParticipant participant = participants[i]; |
| if (clazz.isInstance(participant)) { |
| @SuppressWarnings("unchecked") |
| final T casted = (T) participant; |
| result.add(casted); |
| } |
| } |
| return result; |
| } |
| } |
| return null; |
| } |
| |
| private void buildNatureModules(final IScriptProject project, int buildType, |
| final List<ISourceModule> modules, IBuildState state, |
| final IProgressMonitor monitor) { |
| final long startTime = DEBUG ? System.currentTimeMillis() : 0; |
| beginBuild(buildType, monitor); |
| monitor.beginTask(Messages.ValidatorBuilder_buildingModules, |
| modules.size()); |
| try { |
| if (participants.length == 0 || modules.isEmpty()) { |
| return; |
| } |
| |
| if (fReporters == null) { |
| fReporters = Collections.synchronizedList( |
| new ArrayList<IProblemReporter>(modules.size())); |
| } |
| |
| int availableProcessors = Runtime.getRuntime() |
| .availableProcessors(); |
| if (modules.size() >= PARALLEL_THRESHOLD |
| && availableProcessors > 2) { |
| processInParallel(modules, buildType, state, project, monitor); |
| } else { |
| processInSingleThread(modules, buildType, state, monitor); |
| } |
| } finally { |
| monitor.done(); |
| if (DEBUG) { |
| System.out.println("Build " + project.getElementName() + "(" //$NON-NLS-1$ //$NON-NLS-2$ |
| + modules.size() + ") in " //$NON-NLS-1$ |
| + (System.currentTimeMillis() - startTime) + " ms"); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private void processInSingleThread(List<ISourceModule> modules, |
| int buildType, IBuildState state, IProgressMonitor monitor) { |
| int numberOfScannedFiles = 0; |
| for (Iterator<ISourceModule> j = modules.iterator(); j.hasNext();) { |
| if (monitor.isCanceled()) { |
| return; |
| } |
| final ISourceModule module = j.next(); |
| monitor.subTask(NLS.bind( |
| Messages.ValidatorBuilder_buildModuleSubTask, |
| (int) ((numberOfScannedFiles * 100f) / modules.size()), |
| module.getElementName())); |
| processModule(module, buildType, state); |
| monitor.worked(1); |
| ++numberOfScannedFiles; |
| } |
| } |
| |
| private void processInParallel(final List<ISourceModule> modules, |
| int buildType, IBuildState state, final IScriptProject project, |
| final IProgressMonitor monitor) { |
| final Queue<ISourceModule> queue = new ArrayBlockingQueue<>( |
| modules.size(), true, modules); |
| |
| int maxThreads = Math.min(modules.size() / 2, |
| Runtime.getRuntime().availableProcessors()); |
| |
| List<Job> jobs = new ArrayList<>(maxThreads); |
| try { |
| for (int i = 0; i < maxThreads; i++) { |
| Job job = new BuildModulesJob(queue, buildType, state); |
| job.schedule(); |
| jobs.add(job); |
| } |
| int lastNumberOfScannedFiles = 0; |
| for (Job job : jobs) { |
| while (!job.join(100, null)) { |
| if (monitor.isCanceled()) { |
| for (Job tmpJob : jobs) { |
| tmpJob.cancel(); |
| } |
| continue; |
| } |
| int numberOfScannedFiles = modules.size() - queue.size(); |
| monitor.subTask(NLS.bind( |
| Messages.ValidatorBuilder_buildModuleSubTask, |
| (int) ((numberOfScannedFiles * 100f) |
| / modules.size()), |
| project.getElementName())); |
| int steps = numberOfScannedFiles - lastNumberOfScannedFiles; |
| monitor.worked(steps); |
| lastNumberOfScannedFiles += steps; |
| } |
| } |
| } catch (OperationCanceledException e) { |
| DLTKCore.error(e); |
| } catch (InterruptedException e) { |
| DLTKCore.error(e); |
| } |
| } |
| |
| private void processModule(ISourceModule module, int buildType, |
| IBuildState state) { |
| final SourceModuleBuildContext context = new SourceModuleBuildContext( |
| problemFactory, module, buildType, state); |
| if (context.reporter != null) { |
| buildModule(context); |
| fReporters.add(context.reporter); |
| } |
| } |
| |
| /** |
| * Calls {@link IBuildParticipantExtension#beginBuild(int)} for all |
| * {@link #participants}. Returns <code>true</code> if it was called for |
| * some of them so it is required to call |
| * {@link IBuildParticipantExtension#endBuild()}. If called multiple times |
| * only first time actual work are performed, so subsequent calls returns |
| * result of the first call. |
| * |
| * @param buildType |
| * @param monitor |
| * @return |
| */ |
| private void beginBuild(int buildType, IProgressMonitor monitor) { |
| if (!beginBuildDone) { |
| monitor.subTask(Messages.ValidatorBuilder_InitializeBuilders); |
| endBuildNeeded = false; |
| int count = 0; |
| for (int j = 0; j < participants.length; ++j) { |
| final IBuildParticipant participant = participants[j]; |
| final boolean useParticipant; |
| if (participant instanceof IBuildParticipantExtension) { |
| useParticipant = ((IBuildParticipantExtension) participant) |
| .beginBuild(buildType); |
| endBuildNeeded = true; |
| } else { |
| useParticipant = true; |
| } |
| if (useParticipant) { |
| if (count < j) { |
| participants[count] = participants[j]; |
| } |
| ++count; |
| } |
| } |
| participants = BuildParticipantManager.copyFirst(participants, |
| count); |
| BuildParticipantManager.notifyDependents(participants, |
| participantDependencies); |
| beginBuildDone = true; |
| } |
| } |
| |
| private void buildModule(IBuildContext context) { |
| IBuildParticipant[] selected = participants; |
| for (IBuildParticipantFilter filter : filters) { |
| selected = filter.filter(selected, context); |
| if (selected == null || selected.length == 0) { |
| return; |
| } |
| } |
| try { |
| for (int k = 0; k < selected.length; ++k) { |
| selected[k].build(context); |
| } |
| } catch (CoreException e) { |
| DLTKCore.error(Messages.StandardScriptBuilder_errorBuildingModule, |
| e); |
| } finally { |
| for (IBuildParticipant participant : participants) { |
| if (participant instanceof IBuildParticipantExtension4) { |
| ((IBuildParticipantExtension4) participant) |
| .afterBuild(context); |
| } |
| } |
| } |
| } |
| |
| protected IStatus buildResources(List<IFile> resources, |
| IProgressMonitor monitor) { |
| try { |
| monitor.beginTask(Util.EMPTY_STRING, resources.size()); |
| try { |
| for (final IFile resource : resources) { |
| monitor.subTask(NLS.bind( |
| Messages.ValidatorBuilder_clearingResourceMarkers, |
| resource.getName())); |
| problemFactory.deleteMarkers(resource); |
| monitor.worked(1); |
| } |
| } catch (CoreException e) { |
| final String msg = Messages.ValidatorBuilder_errorDeleteResourceMarkers; |
| DLTKCore.error(msg, e); |
| } |
| } finally { |
| monitor.done(); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| @Override |
| public void clean(IScriptProject project, IProgressMonitor monitor) { |
| final List<IBuildParticipantExtension3> extensions = selectExtension( |
| IBuildParticipantExtension3.class); |
| if (extensions != null) { |
| for (IBuildParticipantExtension3 extension : extensions) { |
| extension.clean(); |
| } |
| } |
| final IProject p = project.getProject(); |
| try { |
| problemFactory.deleteMarkers(p); |
| } catch (CoreException e) { |
| DLTKCore.error( |
| NLS.bind(Messages.StandardScriptBuilder_errorCleaning, |
| p.getName()), |
| e); |
| } |
| } |
| |
| private boolean beginBuildDone = false; |
| private boolean endBuildNeeded = false; |
| private IBuildParticipant[] participants = null; |
| private Map<IBuildParticipant, List<IBuildParticipant>> participantDependencies = null; |
| private IBuildParticipantFilter[] filters = null; |
| private IDLTKLanguageToolkit toolkit = null; |
| private IProblemFactory problemFactory = null; |
| |
| protected IDLTKLanguageToolkit getLanguageToolkit() { |
| return toolkit; |
| } |
| |
| @Override |
| public boolean initialize(IScriptProject project) { |
| toolkit = project.getLanguageToolkit(); |
| if (toolkit != null) { |
| final BuildParticipantResult result = BuildParticipantManager |
| .getBuildParticipants(project, toolkit.getNatureId()); |
| participants = result.participants; |
| participantDependencies = result.dependencies; |
| } |
| if (participants == null || participants.length == 0) { |
| return false; |
| } |
| filters = BuildParticipantManager.getFilters(project, |
| toolkit.getNatureId(), this); |
| problemFactory = createProblemFactory(); |
| beginBuildDone = false; |
| endBuildNeeded = false; |
| return true; |
| } |
| |
| protected IProblemFactory createProblemFactory() { |
| if (toolkit != null) { |
| return DLTKLanguageManager.getProblemFactory(toolkit.getNatureId()); |
| } else { |
| return new DefaultProblemFactory(); |
| } |
| } |
| |
| @Override |
| public void endBuild(IScriptProject project, IBuildState state, |
| IProgressMonitor monitor) { |
| if (endBuildNeeded) { |
| monitor.subTask(Messages.ValidatorBuilder_finalizeBuild); |
| final IProgressMonitor finalizeMonitor = new SubTaskProgressMonitor( |
| monitor, Messages.ValidatorBuilder_finalizeBuild); |
| for (int j = 0; j < participants.length; ++j) { |
| final IBuildParticipant participant = participants[j]; |
| if (participant instanceof IBuildParticipantExtension) { |
| ((IBuildParticipantExtension) participant) |
| .endBuild(finalizeMonitor); |
| } |
| } |
| endBuildNeeded = false; |
| } |
| if (fReporters != null) { |
| final IProblemSeverityTranslator severityTranslator = problemFactory |
| .createSeverityTranslator(project); |
| for (IProblemReporter reporter : fReporters) { |
| final BuildProblemReporter buildReporter = (BuildProblemReporter) reporter; |
| if (buildReporter.hasCategory(ProblemCategory.IMPORT)) { |
| state.recordImportProblem( |
| buildReporter.resource.getFullPath()); |
| } |
| buildReporter.flush(severityTranslator); |
| } |
| fReporters = null; |
| } |
| participants = null; |
| participantDependencies = null; |
| filters = null; |
| toolkit = null; |
| problemFactory = null; |
| beginBuildDone = false; |
| } |
| |
| } |