mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-07-23 17:05:26 +02:00
Bug 429488: Fix for deadlock in QMakeProjectInfo.updateActiveConfiguration
To prevent a deadlock between two the workspace and QMakeProjectInfo.sync locks, QMakeProjectInfo class has been rewritten to not call any method under sync-lock except for IQMakeEnv.init/destroy method. All methods should allow calling under workspace lock. Added a new IQMakeEnv2 interface to provide an explicit init method. Original QMakeProjectInfo has been split to QMakeProjectInfo and QMakeProjectInfoManager. This change is fully backward compatible. Change-Id: I880f22dd9bd999af1d3f47e4a3c4c0ab216b4ad2 Signed-off-by: David Kaspar <dkaspar@blackberry.com> Reviewed-on: https://git.eclipse.org/r/23270 Tested-by: Hudson CI Reviewed-by: Andrew Eidsness <eclipse@jfront.com> Reviewed-by: Doug Schaefer <dschaefer@qnx.com> IP-Clean: Doug Schaefer <dschaefer@qnx.com>
This commit is contained in:
parent
e62ed2c8b7
commit
73a912f95b
9 changed files with 430 additions and 328 deletions
|
@ -39,8 +39,6 @@ public final class QMakeEnvProviderDescriptor implements Comparable<QMakeEnvProv
|
|||
private final AtomicReference<Boolean> evaluation = new AtomicReference<Boolean>();
|
||||
private final Expression enablementExpression;
|
||||
|
||||
private IQMakeEnvProvider provider;
|
||||
|
||||
QMakeEnvProviderDescriptor(IConfigurationElement element) {
|
||||
this.element = element;
|
||||
|
||||
|
@ -99,17 +97,12 @@ public final class QMakeEnvProviderDescriptor implements Comparable<QMakeEnvProv
|
|||
if (! matches(controller)) {
|
||||
return null;
|
||||
}
|
||||
if (provider == null) {
|
||||
synchronized (this) {
|
||||
if (provider == null) {
|
||||
try {
|
||||
provider = (IQMakeEnvProvider) element.createExecutableExtension(ATTR_CLASS);
|
||||
} catch (CoreException e) {
|
||||
QtPlugin.log("Error in class attribute of " + id, e); //$NON-NLS-1$
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
IQMakeEnvProvider provider;
|
||||
try {
|
||||
provider = (IQMakeEnvProvider) element.createExecutableExtension(ATTR_CLASS);
|
||||
} catch (CoreException e) {
|
||||
QtPlugin.log("Error in class attribute of " + id, e); //$NON-NLS-1$
|
||||
return null;
|
||||
}
|
||||
return provider.createEnv(controller);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
|
|||
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
|
||||
import org.eclipse.cdt.qt.core.QtPlugin;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeEnv;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeEnv2;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider;
|
||||
import org.eclipse.cdt.qt.core.index.QMakeEnvInfo;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
|
@ -77,7 +78,7 @@ public final class QMakeEnvProviderManager {
|
|||
* Represents a fallback IQMakeEnvProvider that is used for a project that has QtNature
|
||||
* while there is no registered IQMakeEnvProvider that would provide any IQMakeEnv.
|
||||
*/
|
||||
private static class ConfigurationQMakeEnv implements IQMakeEnv {
|
||||
private static class ConfigurationQMakeEnv implements IQMakeEnv2 {
|
||||
|
||||
private static final String PRO_FILE_SUFFIX = ".pro"; //$NON-NLS-1$
|
||||
private static final String ENV_VAR_QMAKE = "QMAKE"; //$NON-NLS-1$
|
||||
|
@ -88,6 +89,10 @@ public final class QMakeEnvProviderManager {
|
|||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ package org.eclipse.cdt.internal.qt.core.index;
|
|||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -19,159 +18,92 @@ import java.util.Set;
|
|||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.eclipse.cdt.core.model.CoreModel;
|
||||
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
|
||||
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
|
||||
import org.eclipse.cdt.core.settings.model.ICDescriptionDelta;
|
||||
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
|
||||
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeEnv;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeEnv2;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider.IController;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeInfo;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeProjectInfo;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeProjectInfoListener;
|
||||
import org.eclipse.cdt.qt.core.index.QMakeEnvInfo;
|
||||
import org.eclipse.cdt.qt.core.index.IQMakeInfo;
|
||||
import org.eclipse.core.filesystem.URIUtil;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IResourceChangeEvent;
|
||||
import org.eclipse.core.resources.IResourceChangeListener;
|
||||
import org.eclipse.core.resources.IResourceDelta;
|
||||
import org.eclipse.core.resources.IResourceDeltaVisitor;
|
||||
import org.eclipse.core.resources.IWorkspaceRoot;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
|
||||
/**
|
||||
* Represents a QMake project information that is based on an activate project configuration of a specified related IProject.
|
||||
* Allows to resolve actual information and listen its change.
|
||||
* Manages life-cycle of all QMakeProjectInfo instances.
|
||||
*/
|
||||
public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescriptionListener {
|
||||
public final class QMakeProjectInfo implements IQMakeProjectInfo {
|
||||
|
||||
private static final RCListener LISTENER = new RCListener();
|
||||
// sync object for CACHE field
|
||||
private static final Object SYNC = new Object();
|
||||
// a map of all QMakeProjectInfo instances
|
||||
private static final Map<IProject,QMakeProjectInfo> CACHE = new HashMap<IProject,QMakeProjectInfo>();
|
||||
private final State STATE_FREEZE = new State();
|
||||
private final State STATE_INVALID = new State();
|
||||
|
||||
// called by QtPlugin activator to setup this class
|
||||
public static final void start() {
|
||||
ResourcesPlugin.getWorkspace().addResourceChangeListener(LISTENER, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE);
|
||||
}
|
||||
|
||||
// called by QtPlugin activator to clean up this class
|
||||
public static final void stop() {
|
||||
ResourcesPlugin.getWorkspace().removeResourceChangeListener(LISTENER);
|
||||
List<QMakeProjectInfo> infos;
|
||||
synchronized (SYNC) {
|
||||
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
|
||||
CACHE.clear();
|
||||
}
|
||||
for (QMakeProjectInfo info : infos) {
|
||||
if (info != null) {
|
||||
info.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a QMakeProjectInfo for an active project configuration of a specified project.
|
||||
*
|
||||
* @param project the project
|
||||
* @return the QMakeProjectInfo; or null if the project does not have QtNature
|
||||
*/
|
||||
public static QMakeProjectInfo getQMakeProjectInfoFor(IProject project) {
|
||||
QMakeProjectInfo info;
|
||||
synchronized (SYNC) {
|
||||
info = CACHE.get(project);
|
||||
if (info != null) {
|
||||
return info;
|
||||
}
|
||||
info = new QMakeProjectInfo(project);
|
||||
CACHE.put(project,info);
|
||||
}
|
||||
info.updateActiveConfiguration();
|
||||
return info;
|
||||
}
|
||||
|
||||
// removes the project from the CACHE
|
||||
private static void removeProjectFromCache(IResource project) {
|
||||
QMakeProjectInfo info;
|
||||
synchronized (SYNC) {
|
||||
info = CACHE.remove(project);
|
||||
}
|
||||
if (info != null) {
|
||||
info.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private final IProject project;
|
||||
|
||||
// sync object for all mutable fields
|
||||
private final Object sync = new Object();
|
||||
// true if this instance still registered in CACHE
|
||||
private boolean live = true;
|
||||
// a set of sensitive files that might affects actual QMake information
|
||||
private final SensitiveSet sensitiveFilePathSet = new SensitiveSet();
|
||||
// an active project configuration
|
||||
private ControllerImpl activeController;
|
||||
// the last calculated QMake info; null if not calculated
|
||||
private IQMakeInfo qmakeInfo = null;
|
||||
// listeners
|
||||
private final List<IQMakeProjectInfoListener> listeners = new CopyOnWriteArrayList<IQMakeProjectInfoListener>();
|
||||
|
||||
private QMakeProjectInfo(IProject project) {
|
||||
private final IProject project;
|
||||
|
||||
private final Object stateSync = new Object();
|
||||
|
||||
// represents a current state of QMakeProjectInfo
|
||||
private State state = STATE_INVALID;
|
||||
|
||||
QMakeProjectInfo(IProject project) {
|
||||
this.project = project;
|
||||
CoreModel.getDefault().addCProjectDescriptionListener(this, ICDescriptionDelta.ACTIVE_CFG);
|
||||
}
|
||||
|
||||
// called from removeProjectFromCache only
|
||||
private void destroy() {
|
||||
synchronized (sync) {
|
||||
if (! live) {
|
||||
void destroy() {
|
||||
setState(STATE_FREEZE);
|
||||
}
|
||||
|
||||
// must not be called under any QMake-related sync-lock, except for workspace lock
|
||||
private void updateStateFrom(State fromState) {
|
||||
synchronized (stateSync) {
|
||||
if (state != fromState) {
|
||||
return;
|
||||
}
|
||||
live = false;
|
||||
CoreModel.getDefault().removeCProjectDescriptionListener(this);
|
||||
setActiveConfiguration(null);
|
||||
qmakeInfo = QMakeInfo.INVALID;
|
||||
}
|
||||
updateState();
|
||||
}
|
||||
|
||||
// must not be called under synchronized (SYNC) or synchronized (sync)
|
||||
private void updateActiveConfiguration() {
|
||||
synchronized (sync) {
|
||||
if (! live) {
|
||||
// must not be called under any QMake-related sync-lock, except for workspace lock
|
||||
// we are running outside of synchronized block to prevent deadlock involving workspace lock
|
||||
// this means that theoretically there might be multiple thread calculating the same results but only the last one wins
|
||||
void updateState() {
|
||||
// note that getProjectDescription might acquire workspace lock
|
||||
ICProjectDescription projectDescription = CoreModel.getDefault().getProjectDescriptionManager().getProjectDescription(project);
|
||||
ICConfigurationDescription configuration = projectDescription != null ? projectDescription.getActiveConfiguration() : null;
|
||||
setState(configuration != null ? new State(configuration) : STATE_INVALID);
|
||||
}
|
||||
|
||||
private void setState(State newState) {
|
||||
State oldState = null;
|
||||
synchronized (stateSync) {
|
||||
if (newState == null || state == newState) {
|
||||
return;
|
||||
}
|
||||
ICProjectDescription projectDescription = CoreModel.getDefault().getProjectDescriptionManager().getProjectDescription(project);
|
||||
setActiveConfiguration(projectDescription != null ? projectDescription.getActiveConfiguration() : null);
|
||||
qmakeInfo = null;
|
||||
if (state == STATE_FREEZE) {
|
||||
newState.destroyBeforeInit();
|
||||
return;
|
||||
}
|
||||
oldState = state;
|
||||
state = newState;
|
||||
if (oldState != null) {
|
||||
oldState.destroy();
|
||||
}
|
||||
newState.init();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// called under synchronized (sync)
|
||||
private void setActiveConfiguration(ICConfigurationDescription configuration) {
|
||||
ControllerImpl previous = activeController;
|
||||
activeController = configuration != null ? new ControllerImpl(configuration) : null;
|
||||
if (previous != null) {
|
||||
previous.destroy();
|
||||
for (IQMakeProjectInfoListener listener : listeners) {
|
||||
listener.qmakeInfoChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// called on active project configuration change
|
||||
@Override
|
||||
public void handleEvent(CProjectDescriptionEvent event) {
|
||||
if (event.getProject() != project) {
|
||||
return;
|
||||
}
|
||||
updateActiveConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(IQMakeProjectInfoListener listener) {
|
||||
listeners.add(listener);
|
||||
|
@ -182,60 +114,14 @@ public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescr
|
|||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
private IProject getProject() {
|
||||
IProject getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
// calculates (if does not exist) and returns actual QMake info
|
||||
@Override
|
||||
public IQMakeInfo getActualInfo() {
|
||||
synchronized (sync) {
|
||||
if (! live) {
|
||||
return QMakeInfo.INVALID;
|
||||
}
|
||||
if (qmakeInfo == null) {
|
||||
fetchQMakeInfo();
|
||||
}
|
||||
return qmakeInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// calculates actual QMake info
|
||||
private void fetchQMakeInfo() {
|
||||
// retrieves IQMakeEnvInfo from IQMakeEnv
|
||||
IQMakeEnv qmakeEnv = activeController != null ? activeController.getQMakeEnv() : null;
|
||||
QMakeEnvInfo qmakeEnvInfo = qmakeEnv != null ? qmakeEnv.getQMakeEnvInfo() : null;
|
||||
|
||||
// retrieves .pro file path
|
||||
String proFilePath = toFilePath(qmakeEnvInfo != null ? qmakeEnvInfo.getProFile() : null);
|
||||
// retrieves qmake executable path
|
||||
String qmakeFilePath = qmakeEnvInfo != null ? qmakeEnvInfo.getQMakeFilePath() : null;
|
||||
// retries environment
|
||||
List<String> envList = new ArrayList<String>();
|
||||
Map<String, String> envMap = qmakeEnvInfo != null ? qmakeEnvInfo.getEnvironment() : Collections.<String,String>emptyMap();
|
||||
for (Map.Entry<String,String> entry : envMap.entrySet()) {
|
||||
envList.add(entry.getKey() + "=" + entry.getValue());
|
||||
}
|
||||
|
||||
// calculates actual QMake info
|
||||
qmakeInfo = QMakeInfo.create(proFilePath, qmakeFilePath, envList.toArray(new String[envList.size()]));
|
||||
|
||||
// calculates a new set of sensitive file paths
|
||||
sensitiveFilePathSet.clear();
|
||||
Set<IFile> envSensFiles = qmakeEnvInfo != null ? qmakeEnvInfo.getSensitiveFiles() : Collections.<IFile>emptySet();
|
||||
for (IFile sensitiveFile : envSensFiles) {
|
||||
if (sensitiveFile != null) {
|
||||
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
|
||||
}
|
||||
}
|
||||
if (proFilePath != null) {
|
||||
sensitiveFilePathSet.addSensitiveFile(proFilePath);
|
||||
}
|
||||
List<String> sensitiveFiles = qmakeInfo.getInvolvedQMakeFiles();
|
||||
if (sensitiveFiles != null) {
|
||||
for (String sensitiveFile : sensitiveFiles) {
|
||||
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
|
||||
}
|
||||
synchronized (stateSync) {
|
||||
return state.getQMakeInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,57 +143,82 @@ public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescr
|
|||
}
|
||||
|
||||
// checks if any of the specified files is a sensitive file
|
||||
private boolean containsAnySensitiveFile(Set<IPath> files) {
|
||||
synchronized (sync) {
|
||||
if (live) {
|
||||
for (Iterator<IPath> iterator = files.iterator(); iterator.hasNext();) {
|
||||
IPath path = iterator.next();
|
||||
if (sensitiveFilePathSet.contains(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// resets actual QMake info and notifies all listeners that QMake information has changes
|
||||
private void scheduleFetchQMakeInfo() {
|
||||
synchronized (sync) {
|
||||
if (! live) {
|
||||
return;
|
||||
}
|
||||
qmakeInfo = null;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void notifyListeners() {
|
||||
for (IQMakeProjectInfoListener listener : listeners) {
|
||||
listener.qmakeInfoChanged();
|
||||
boolean containsAnySensitiveFile(Set<IPath> files) {
|
||||
synchronized (stateSync) {
|
||||
return state.containsAnySensitiveFile(files);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a project configuration.
|
||||
* Represents a state of QMakeInfo for a specific QMakeProjectInfo.
|
||||
*/
|
||||
private final class ControllerImpl implements IQMakeEnvProvider.IController {
|
||||
private final class State implements IController {
|
||||
|
||||
// an active project configuration
|
||||
private final ICConfigurationDescription configuration;
|
||||
// an active project qmake env
|
||||
private final IQMakeEnv qmakeEnv;
|
||||
// the last calculated QMake info; null if not calculated
|
||||
private final IQMakeInfo qmakeInfo;
|
||||
// a set of sensitive files that might affects actual QMake information
|
||||
private final SensitiveSet sensitiveFilePathSet;
|
||||
|
||||
public ControllerImpl(ICConfigurationDescription configuration) {
|
||||
State() {
|
||||
configuration = null;
|
||||
qmakeEnv = null;
|
||||
qmakeInfo = QMakeInfo.INVALID;
|
||||
sensitiveFilePathSet = new SensitiveSet();
|
||||
}
|
||||
|
||||
State(ICConfigurationDescription configuration) {
|
||||
this.configuration = configuration;
|
||||
// qmakeEnv created from registry of qmakeEnvProvider extensions
|
||||
this.qmakeEnv = QMakeEnvProviderManager.getInstance().createEnv(this);
|
||||
|
||||
// retrieves IQMakeEnvInfo from IQMakeEnv
|
||||
QMakeEnvInfo qmakeEnvInfo = qmakeEnv.getQMakeEnvInfo();
|
||||
|
||||
// retrieves .pro file path
|
||||
String proFilePath = toFilePath(qmakeEnvInfo != null ? qmakeEnvInfo.getProFile() : null);
|
||||
// retrieves qmake executable path
|
||||
String qmakeFilePath = qmakeEnvInfo != null ? qmakeEnvInfo.getQMakeFilePath() : null;
|
||||
// retries environment
|
||||
List<String> envList = new ArrayList<String>();
|
||||
Map<String, String> envMap = qmakeEnvInfo != null ? qmakeEnvInfo.getEnvironment() : Collections.<String,String>emptyMap();
|
||||
for (Map.Entry<String,String> entry : envMap.entrySet()) {
|
||||
envList.add(entry.getKey() + "=" + entry.getValue());
|
||||
}
|
||||
|
||||
// calculates actual QMake info
|
||||
qmakeInfo = QMakeInfo.create(proFilePath, qmakeFilePath, envList.toArray(new String[envList.size()]));
|
||||
|
||||
// calculates a new set of sensitive file paths
|
||||
sensitiveFilePathSet = new SensitiveSet();
|
||||
Set<IFile> envSensFiles = qmakeEnvInfo != null ? qmakeEnvInfo.getSensitiveFiles() : Collections.<IFile>emptySet();
|
||||
for (IFile sensitiveFile : envSensFiles) {
|
||||
if (sensitiveFile != null) {
|
||||
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
|
||||
}
|
||||
}
|
||||
if (proFilePath != null) {
|
||||
sensitiveFilePathSet.addSensitiveFile(proFilePath);
|
||||
}
|
||||
List<String> sensitiveFiles = qmakeInfo.getInvolvedQMakeFiles();
|
||||
if (sensitiveFiles != null) {
|
||||
for (String sensitiveFile : sensitiveFiles) {
|
||||
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
qmakeEnv.destroy();
|
||||
}
|
||||
|
||||
public IQMakeEnv getQMakeEnv() {
|
||||
return qmakeEnv;
|
||||
private boolean containsAnySensitiveFile(Set<IPath> files) {
|
||||
for (Iterator<IPath> iterator = files.iterator(); iterator.hasNext();) {
|
||||
IPath path = iterator.next();
|
||||
if (sensitiveFilePathSet.contains(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -315,116 +226,32 @@ public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescr
|
|||
return configuration;
|
||||
}
|
||||
|
||||
public void destroyBeforeInit() {
|
||||
// see IQMakeEnv JavaDoc for details
|
||||
if (qmakeEnv != null && ! (qmakeEnv instanceof IQMakeEnv2)) {
|
||||
qmakeEnv.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public void init() {
|
||||
if (qmakeEnv instanceof IQMakeEnv2) {
|
||||
((IQMakeEnv2) qmakeEnv).init();
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (qmakeEnv != null) {
|
||||
qmakeEnv.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleUpdate() {
|
||||
scheduleFetchQMakeInfo();
|
||||
updateStateFrom(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens on Eclipse file system changes.
|
||||
*/
|
||||
private static final class RCListener implements IResourceChangeListener {
|
||||
|
||||
@Override
|
||||
public void resourceChanged(IResourceChangeEvent event) {
|
||||
RDVisitor visitor = new RDVisitor();
|
||||
|
||||
// collect project to delete and changed files
|
||||
switch (event.getType()) {
|
||||
case IResourceChangeEvent.PRE_CLOSE:
|
||||
case IResourceChangeEvent.PRE_DELETE:
|
||||
IResource project = event.getResource();
|
||||
if (project != null && project.getType() == IResource.PROJECT) {
|
||||
visitor.addProjectToDelete(project);
|
||||
}
|
||||
break;
|
||||
case IResourceChangeEvent.POST_CHANGE:
|
||||
IResourceDelta delta = event.getDelta();
|
||||
if (delta != null) {
|
||||
try {
|
||||
delta.accept(visitor);
|
||||
} catch (CoreException e) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// process collected data
|
||||
visitor.process();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class RDVisitor implements IResourceDeltaVisitor {
|
||||
|
||||
private final Set<IResource> projectsToDelete = new HashSet<IResource>();
|
||||
private final Set<IResource> projectsToUpdate = new HashSet<IResource>();
|
||||
private final Set<IPath> changedFiles = new HashSet<IPath>();
|
||||
|
||||
@Override
|
||||
public boolean visit(IResourceDelta delta) throws CoreException {
|
||||
IResource resource = delta.getResource();
|
||||
if (resource != null) {
|
||||
switch (resource.getType()) {
|
||||
case IResource.FILE:
|
||||
addChangedFile(resource);
|
||||
return false;
|
||||
case IResource.PROJECT:
|
||||
switch (delta.getKind()) {
|
||||
case IResourceDelta.CHANGED:
|
||||
if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
|
||||
addProjectToUpdate(resource);
|
||||
}
|
||||
return true;
|
||||
case IResourceDelta.REMOVED:
|
||||
addProjectToDelete(resource);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addProjectToUpdate(IResource project) {
|
||||
projectsToUpdate.add(project);
|
||||
}
|
||||
|
||||
private void addProjectToDelete(IResource project) {
|
||||
projectsToDelete.add(project);
|
||||
}
|
||||
|
||||
private void addChangedFile(IResource file) {
|
||||
IPath fullPath = file.getFullPath();
|
||||
if (fullPath != null) {
|
||||
changedFiles.add(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void process() {
|
||||
// removing projects from CACHE
|
||||
for(IResource project : projectsToDelete) {
|
||||
removeProjectFromCache(project);
|
||||
}
|
||||
|
||||
List<QMakeProjectInfo> infos;
|
||||
synchronized (SYNC) {
|
||||
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
|
||||
}
|
||||
for (QMakeProjectInfo info : infos) {
|
||||
// checking if any project description change affects QMakeProjectInfo
|
||||
if (projectsToUpdate.contains(info.getProject())) {
|
||||
info.updateActiveConfiguration();
|
||||
}
|
||||
// checking if any of the changed files affects QMakeProjectInfo
|
||||
if (info.containsAnySensitiveFile(changedFiles)) {
|
||||
// if so then scheduling update
|
||||
info.scheduleFetchQMakeInfo();
|
||||
}
|
||||
}
|
||||
IQMakeInfo getQMakeInfo() {
|
||||
return qmakeInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright (c) 2014 QNX Software Systems 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.cdt.internal.qt.core.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.cdt.core.model.CoreModel;
|
||||
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
|
||||
import org.eclipse.cdt.core.settings.model.ICDescriptionDelta;
|
||||
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IResourceChangeEvent;
|
||||
import org.eclipse.core.resources.IResourceChangeListener;
|
||||
import org.eclipse.core.resources.IResourceDelta;
|
||||
import org.eclipse.core.resources.IResourceDeltaVisitor;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
|
||||
/**
|
||||
* Represents a management of QMakeProjectInfo instances and manages life-cycle of all QMakeProjectInfo instances.
|
||||
*/
|
||||
public class QMakeProjectInfoManager {
|
||||
|
||||
private static final PDListener PD_LISTENER = new PDListener();
|
||||
private static final RCListener RC_LISTENER = new RCListener();
|
||||
|
||||
// sync object for CACHE field
|
||||
private static final Object CACHE_SYNC = new Object();
|
||||
// a list of all QMakeProjectInfo instances
|
||||
private static Map<IProject,QMakeProjectInfo> CACHE;
|
||||
|
||||
// called by QtPlugin activator to setup this class
|
||||
public static final void start() {
|
||||
synchronized (CACHE_SYNC) {
|
||||
CACHE = new HashMap<IProject,QMakeProjectInfo>();
|
||||
}
|
||||
CoreModel.getDefault().addCProjectDescriptionListener(PD_LISTENER, ICDescriptionDelta.ACTIVE_CFG);
|
||||
ResourcesPlugin.getWorkspace().addResourceChangeListener(RC_LISTENER, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE);
|
||||
}
|
||||
|
||||
// called by QtPlugin activator to clean up this class
|
||||
public static final void stop() {
|
||||
ResourcesPlugin.getWorkspace().removeResourceChangeListener(RC_LISTENER);
|
||||
CoreModel.getDefault().removeCProjectDescriptionListener(PD_LISTENER);
|
||||
List<QMakeProjectInfo> infos;
|
||||
synchronized (CACHE_SYNC) {
|
||||
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
|
||||
CACHE = null;
|
||||
}
|
||||
for (QMakeProjectInfo info : infos) {
|
||||
if (info != null) {
|
||||
info.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a QMakeProjectInfo for an active project configuration of a specified project.
|
||||
*
|
||||
* @param project the project
|
||||
* @return the QMakeProjectInfo; or null if the project does not have QtNature
|
||||
*/
|
||||
public static QMakeProjectInfo getQMakeProjectInfoFor(IProject project) {
|
||||
return getQMakeProjectInfoFor(project, true);
|
||||
}
|
||||
|
||||
private static QMakeProjectInfo getQMakeProjectInfoFor(IProject project, boolean create) {
|
||||
QMakeProjectInfo info;
|
||||
synchronized (CACHE_SYNC) {
|
||||
info = CACHE.get(project);
|
||||
if (info != null) {
|
||||
return info;
|
||||
}
|
||||
if (! create) {
|
||||
// do not create, just return null
|
||||
return null;
|
||||
}
|
||||
info = new QMakeProjectInfo(project);
|
||||
CACHE.put(project, info);
|
||||
}
|
||||
info.updateState();
|
||||
return info;
|
||||
}
|
||||
|
||||
// removes the project from the CACHE
|
||||
private static void removeProjectFromCache(IResource project) {
|
||||
QMakeProjectInfo info;
|
||||
synchronized (CACHE_SYNC) {
|
||||
info = CACHE.remove(project);
|
||||
}
|
||||
if (info != null) {
|
||||
info.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PDListener implements ICProjectDescriptionListener {
|
||||
|
||||
// called on active project configuration change
|
||||
@Override
|
||||
public void handleEvent(CProjectDescriptionEvent event) {
|
||||
QMakeProjectInfo info = getQMakeProjectInfoFor(event.getProject(), false);
|
||||
if (info != null) {
|
||||
info.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens on Eclipse file system changes.
|
||||
*/
|
||||
private static final class RCListener implements IResourceChangeListener {
|
||||
|
||||
@Override
|
||||
public void resourceChanged(IResourceChangeEvent event) {
|
||||
RDVisitor visitor = new RDVisitor();
|
||||
|
||||
// collect project to delete and changed files
|
||||
switch (event.getType()) {
|
||||
case IResourceChangeEvent.PRE_CLOSE:
|
||||
case IResourceChangeEvent.PRE_DELETE:
|
||||
IResource project = event.getResource();
|
||||
if (project != null && project.getType() == IResource.PROJECT) {
|
||||
visitor.addProjectToDelete(project);
|
||||
}
|
||||
break;
|
||||
case IResourceChangeEvent.POST_CHANGE:
|
||||
IResourceDelta delta = event.getDelta();
|
||||
if (delta != null) {
|
||||
try {
|
||||
delta.accept(visitor);
|
||||
} catch (CoreException e) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// process collected data
|
||||
visitor.process();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class RDVisitor implements IResourceDeltaVisitor {
|
||||
|
||||
private final Set<IResource> projectsToDelete = new HashSet<IResource>();
|
||||
private final Set<IResource> projectsToUpdate = new HashSet<IResource>();
|
||||
private final Set<IPath> changedFiles = new HashSet<IPath>();
|
||||
|
||||
@Override
|
||||
public boolean visit(IResourceDelta delta) throws CoreException {
|
||||
IResource resource = delta.getResource();
|
||||
if (resource != null) {
|
||||
switch (resource.getType()) {
|
||||
case IResource.FILE:
|
||||
addChangedFile(resource);
|
||||
return false;
|
||||
case IResource.PROJECT:
|
||||
switch (delta.getKind()) {
|
||||
case IResourceDelta.CHANGED:
|
||||
if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
|
||||
addProjectToUpdate(resource);
|
||||
}
|
||||
return true;
|
||||
case IResourceDelta.REMOVED:
|
||||
addProjectToDelete(resource);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addProjectToUpdate(IResource project) {
|
||||
projectsToUpdate.add(project);
|
||||
}
|
||||
|
||||
private void addProjectToDelete(IResource project) {
|
||||
projectsToDelete.add(project);
|
||||
}
|
||||
|
||||
private void addChangedFile(IResource file) {
|
||||
IPath fullPath = file.getFullPath();
|
||||
if (fullPath != null) {
|
||||
changedFiles.add(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void process() {
|
||||
// removing projects from CACHE
|
||||
for(IResource project : projectsToDelete) {
|
||||
removeProjectFromCache(project);
|
||||
}
|
||||
|
||||
List<QMakeProjectInfo> infos;
|
||||
synchronized (CACHE_SYNC) {
|
||||
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
|
||||
}
|
||||
for (QMakeProjectInfo info : infos) {
|
||||
// checking if any project description change or any of the changed files affect QMakeProjectInfo
|
||||
if (projectsToUpdate.contains(info.getProject()) || info.containsAnySensitiveFile(changedFiles)) {
|
||||
// if so then updating
|
||||
info.updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -7,14 +7,18 @@
|
|||
*/
|
||||
package org.eclipse.cdt.qt.core;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.cdt.core.model.CModelException;
|
||||
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfo;
|
||||
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfoManager;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Plugin;
|
||||
import org.eclipse.core.runtime.QualifiedName;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.BundleEvent;
|
||||
import org.osgi.framework.BundleListener;
|
||||
|
||||
public class QtPlugin extends Plugin {
|
||||
|
||||
|
@ -47,13 +51,27 @@ public class QtPlugin extends Plugin {
|
|||
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
// have to wait for STARTED event because
|
||||
// cannot access CoreModel.getDefault().addCProjectDescriptionListener()
|
||||
// since the CoreModel is not completely initialized at this time
|
||||
BundleListener bundleListener = new BundleListener() {
|
||||
final AtomicBoolean initStarted = new AtomicBoolean(false);
|
||||
@Override
|
||||
public void bundleChanged(BundleEvent bundleEvent) {
|
||||
if (bundleEvent.getType() == BundleEvent.STARTED) {
|
||||
if (!initStarted.getAndSet(true)) {
|
||||
QMakeProjectInfoManager.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
context.addBundleListener(bundleListener);
|
||||
super.start(context);
|
||||
QMakeProjectInfo.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
QMakeProjectInfo.stop();
|
||||
QMakeProjectInfoManager.stop();
|
||||
super.stop(context);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,18 @@ package org.eclipse.cdt.qt.core.index;
|
|||
|
||||
/**
|
||||
* Represents a QMake environment. It is usually created by IQMakeEnvProvider.createEnv() method for a specific project configuration.
|
||||
*
|
||||
* Note that IQMakeEnv has destroy method only and it is expected that the instance is already initialized in the constructor.
|
||||
* This means that it may happen that IQMakeEnv instance is created and destroyed immediately.
|
||||
*
|
||||
* See IQMakeEnv2 interface if you want to get an explicit notification when IQMakeEnv gets really used. In that case, the instance initialization
|
||||
* needs to be done in init method completely - not in the constructor.
|
||||
*/
|
||||
public interface IQMakeEnv {
|
||||
|
||||
/**
|
||||
* Notifies that this environment is no longer used.
|
||||
* This method should not use any workspace-lock or sync-locks that might call QMake-related structures.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2014 QNX Software Systems 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.cdt.qt.core.index;
|
||||
|
||||
/**
|
||||
* Represents a QMake environment similarly to IQMakeEnv but it has an explicit init method which is called to notify
|
||||
* that the IQMakeEnv is used for active listening on change of QMakeEnvInfo and therefore it should initialize
|
||||
* its listener for such changes.
|
||||
*
|
||||
* Note that it is expected that IQMakeEnv2 does complete initialization in init method - not in the constructor
|
||||
* i.e. the IQMakeEnv2 instance should start listening on possible changes of its IQMakeEnvInfo.
|
||||
*/
|
||||
public interface IQMakeEnv2 extends IQMakeEnv {
|
||||
|
||||
/**
|
||||
* Notifies that this IQMakeEnv is used.
|
||||
* This method should not use any workspace-lock or sync-locks that might call QMake-related structures.
|
||||
*/
|
||||
void init();
|
||||
|
||||
}
|
|
@ -41,6 +41,8 @@ public interface IQMakeEnvProvider {
|
|||
/**
|
||||
* Request the controller to schedule a new qmake run to retrieve new QMake information.
|
||||
* This method should be called when there is any change in IQMakeEnv that might affect resulting IQMakeEnvInfo.
|
||||
*
|
||||
* Note that calculation of new QMakeInfo is done immediately if this controller is still active and used.
|
||||
*/
|
||||
void scheduleUpdate();
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
package org.eclipse.cdt.qt.core.index;
|
||||
|
||||
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfo;
|
||||
import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfoManager;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,7 @@ public final class QMakeProjectInfoFactory {
|
|||
* in the specified project.
|
||||
*/
|
||||
public static IQMakeProjectInfo getForActiveConfigurationIn(IProject project) {
|
||||
return QMakeProjectInfo.getQMakeProjectInfoFor(project);
|
||||
return QMakeProjectInfoManager.getQMakeProjectInfoFor(project);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue