builder/com.nokia.carbide.cdt.builder/src/com/nokia/carbide/cdt/internal/builder/CarbideBuildManager.java
/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
package com.nokia.carbide.cdt.internal.builder;
import java.util.*;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.settings.model.extension.CFolderData;
import org.eclipse.cdt.core.settings.model.extension.CLanguageData;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import com.nokia.carbide.cdt.builder.*;
import com.nokia.carbide.cdt.builder.project.*;
import com.nokia.carbide.cpp.sdk.core.ICarbideInstalledSDKChangeListener;
import com.nokia.carbide.cpp.sdk.core.SDKCorePlugin;
import com.nokia.cpp.internal.api.utils.core.*;
import com.nokia.cpp.internal.api.utils.core.MultiResourceChangeListenerDispatcher.IResourceChangeHandler;
/**
* This is a singleton class that allows you to get any Carbide.c++ project. You can get this instance by calling
* CarbideBuildManager BuilderPlugin.getBuildManager().
*
* @see BuilderPlugin, ICarbideProjectInfo
*
*/
public class CarbideBuildManager implements ICarbideBuildManager, IResourceChangeListener, ICarbideInstalledSDKChangeListener {
private Map<IProject, ICarbideProjectInfo> projectInfoMap = new HashMap<IProject, ICarbideProjectInfo>();
private MultiResourceChangeListenerDispatcher resourceChangedListener = new MultiResourceChangeListenerDispatcher();
public CarbideBuildManager() {
SDKCorePlugin.getSDKManager().addInstalledSdkChangeListener(this);
}
public boolean isCarbideProject(IProject project) {
boolean carbideProject = false;
try {
if (project != null && project.isOpen() && project.hasNature(CarbideBuilderPlugin.CARBIDE_PROJECT_NATURE_ID)) {
carbideProject = true;
}
} catch (CoreException e) {
e.printStackTrace();
}
return carbideProject;
}
public boolean isCoronaProject(IProject project) {
boolean coronaProject = false;
try {
if (project.isOpen() && project.hasNature(CarbideBuilderPlugin.CORONA_PROJECT_NATURE_ID)) {
coronaProject = true;
}
} catch (CoreException e) {
e.printStackTrace();
}
return coronaProject;
}
public boolean isCarbideSBSv2Project(IProject project) {
boolean carbideSBSv2Project = false;
try {
if (project != null && project.isOpen() && project.hasNature(CarbideBuilderPlugin.CARBIDE_SBSV2_PROJECT_NATURE_ID)) {
carbideSBSv2Project = true;
}
} catch (CoreException e) {
e.printStackTrace();
}
return carbideSBSv2Project;
}
public ICarbideProjectInfo getProjectInfo(IProject project) {
synchronized (projectInfoMap) {
ICarbideProjectInfo info = projectInfoMap.get(project);
if (info != null) {
return info;
}
}
// we haven't loaded info for this project yet. load and it if it's
// valid, add it to the map and return it. otherwise return null.
// The lock on projectInfoMap must be released and reacquired around the
// creation of CarbideProjectInfo because workspace resources will be
// accessed. Those resources may be locked by another thread, so deadlock
// may occur.
if (isCarbideProject(project)) {
CarbideProjectInfo newInfo = new CarbideProjectInfo(project);
ICarbideProjectInfo info = null;
synchronized (projectInfoMap) {
// Since we released and reacquired the lock we need to
// check if someone else added the project already.
info = projectInfoMap.get(project);
if (info == null) {
// projectRelativeBldInfPath will not be null if the project info is valid
if (newInfo.getProjectRelativeBldInfPath() != null) {
projectInfoMap.put(project, newInfo);
addProjectListener(newInfo);
info = newInfo;
}
}
}
if (info != null && info.getBuildConfigurations().size() == 0){
CarbideBuilderPlugin.createCarbideProjectMarker(project, IMarker.SEVERITY_ERROR, "No configurations can be found for this project. Please add a configuration by choosing Project > Properties > Carbide Build Configurations > Manage...", IMarker.PRIORITY_HIGH);
}
validateProjectAndSetProjectMarker(info);
return info;
}
return null;
}
public ICarbideProjectModifier createProjectInfo(ICProjectDescription projDes) {
synchronized (projectInfoMap) {
IProject project = projDes.getProject();
assert(projectInfoMap.get(project) == null);
return new CarbideProjectModifier(projDes);
}
}
public ICarbideProjectModifier getProjectModifier(IProject project) {
ICarbideProjectInfo info = getProjectInfo(project);
if (info != null) {
return new CarbideProjectModifier((CarbideProjectInfo)info);
}
return null;
}
public void setProjectInfo(ICarbideProjectInfo newInfo) {
synchronized (projectInfoMap) {
IProject project = newInfo.getProject();
// ICarbideProjectInfo oldInfo = projectInfoMap.get(project);
// assert(oldInfo != null);
projectInfoMap.put(project, newInfo);
addProjectListener(newInfo);
}
}
public void resourceChanged(IResourceChangeEvent event) {
// remove projects from map when they are closed or deleted
if (event.getType() == IResourceChangeEvent.PRE_CLOSE || event.getType() == IResourceChangeEvent.PRE_DELETE) {
projectInfoMap.remove(event.getResource());
}
}
public void validateProjectAndSetProjectMarker(ICarbideProjectInfo projectInfo) {
if (projectInfo != null) {
List<ICarbideBuildConfiguration> configs = projectInfo.getBuildConfigurations();
for (ICarbideBuildConfiguration config : configs) {
if (config instanceof CarbideBuildConfiguration) {
((CarbideBuildConfiguration)config).validateAndSetProjectMarker();
}
}
}
}
private class ResourceChangeHandler implements IResourceChangeHandler {
private ICarbideProjectInfo cpi;
// lock for the next two booleans
private Object refreshingLock;
// if true, currently running a refresh job
private boolean refreshingCondition;
// if true, re-refreshing was delayed due to a job running
private boolean refreshingDelayed;
public ResourceChangeHandler(ICarbideProjectInfo cpi) {
this.cpi = cpi;
refreshingLock = new Object();
refreshingCondition = false;
refreshingDelayed = false;
}
public void resourceChanged(final IPath workspacePath) {
//System.out.println("Queueing refresh job due to change on " + workspacePath);
queueRefreshingJob();
}
/**
*
*/
private void queueRefreshingJob() {
final IProject project = cpi.getProject();
if (!project.isAccessible()) {
return;
}
// avoid queueing multiple jobs on the same project at the same time
synchronized (refreshingLock) {
if (refreshingCondition) {
refreshingDelayed = true;
return;
}
refreshingDelayed = false;
refreshingCondition = true;
}
// when the bld.inf or an mmp or any of their includes changes we want CDT to re-read the
// paths and symbols info. setting the project description is the only way to get CDT to
// call BuildConfigurationData#getSourceEntries and CarbideLanguageData#getEntries. that's
// where the cache is stored and will be reset for each build config if necessary
WorkspaceJob refreshDataJob = new WorkspaceJob("Refreshing configuration data") {
public IStatus runInWorkspace(IProgressMonitor monitor) {
try {
try {
ICProjectDescription projDes = CoreModel.getDefault().getProjectDescription(project);
if (projDes != null) {
CCorePlugin.getDefault().setProjectDescription(project, projDes, true, null);
}
} catch (CoreException e) {
e.printStackTrace();
CarbideBuilderPlugin.log(e);
}
} finally {
// now we can refresh again; we need to re-refresh if another
// resource changed while we were refreshing since we have no
// idea if its change happened before or after the refresh job
// read it
synchronized (refreshingLock) {
refreshingCondition = false;
if (refreshingDelayed) {
queueRefreshingJob();
}
}
}
return new Status(IStatus.OK, CarbideBuilderPlugin.PLUGIN_ID, IStatus.OK, null, null);
}
};
// schedule on workspace: (1) this job triggers EPOC engine model reading, which
// must be scheduled if models are also being written at the same time -- which is
// highly likely given that this job is in response to a resource change!
// (Actual test case: importing images in UIQ UI Designer projects can touch
// bld.inf and then PKG models in succession.)
// (2) CDT needs the workspace to be scheduled, versus the project, for some reason.
refreshDataJob.setRule(project.getWorkspace().getRoot());
refreshDataJob.setPriority(Job.SHORT);
refreshDataJob.schedule();
// reset the list of files we're listening to as whatever changed could have affected
// the list of files.
listenForReferencedFileChanges(cpi, this);
}
}
private void addProjectListener(ICarbideProjectInfo cpi) {
final IProject project = cpi.getProject();
// get the list of files the engine references when parsing the bld.inf file
listenForReferencedFileChanges(cpi, new ResourceChangeHandler(cpi));
// remove all the resource listeners once the project is closed or deleted
IResourceChangeListener projectDeleteListener = new IResourceChangeListener() {
public void resourceChanged(IResourceChangeEvent event) {
// see if a project was closed or deleted
if (event.getType() == IResourceChangeEvent.PRE_CLOSE || event.getType() == IResourceChangeEvent.PRE_DELETE) {
if (project.equals(event.getResource())) {
resourceChangedListener.removeAllForPrefix(new Path(project.getName()));
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
}
}
}
};
ResourcesPlugin.getWorkspace().addResourceChangeListener(projectDeleteListener);
}
private void listenForReferencedFileChanges(ICarbideProjectInfo cpi, ResourceChangeHandler handler) {
// reset the listener
resourceChangedListener.removeAllForPrefix(new Path(cpi.getProject().getName()));
// calculate the list for each build config so the engine has a context with which to
// expand macros, locate includes, etc. we're using a set to avoid duplicates.
Set<IPath> pathList = new HashSet<IPath>();
for (ICarbideBuildConfiguration config : cpi.getBuildConfigurations()) {
// force a rebuild of the CarbideLanguageData cache
CLanguageData languageData = null;
ICProjectDescription projDes = CoreModel.getDefault().getProjectDescription(config.getCarbideProject().getProject());
if (projDes != null) {
ICConfigurationDescription configDes = projDes.getConfigurationById(config.getDisplayString());
if (configDes != null) {
CConfigurationData configData = configDes.getConfigurationData();
if (configData != null && configData instanceof BuildConfigurationData) {
CFolderData rootFolderData = ((BuildConfigurationData)configData).getRootFolderData();
if (rootFolderData != null) {
CLanguageData[] languageDatas = rootFolderData.getLanguageDatas();
if (languageDatas != null && languageDatas.length > 0) {
// there's only one for Carbide anyway
languageData = languageDatas[0];
if (languageData instanceof CarbideLanguageData)
((CarbideLanguageData)languageData).forceRebuildCache();
}
}
}
}
}
EpocEngineHelper.addIncludedFilesFromBldInf(cpi, config, cpi.getAbsoluteBldInfPath(), pathList);
// get the list of all mmp files selected for the build configuration
// a null buildComponents list means all MMPs are included - so leave it null when indexing all files
List<String> buildComponents = null;
if (!EpocEngineHelper.getIndexAllPreference())
buildComponents = config.getCarbideProject().isBuildingFromInf() ? null : config.getCarbideProject().getInfBuildComponents();
for (IPath mmpPath : EpocEngineHelper.getMMPFilesForBuildConfiguration(config)) {
if (buildComponents == null || TextUtils.listContainsIgnoreCase(buildComponents, mmpPath.lastSegment()))
EpocEngineHelper.addIncludedFilesFromMMP(cpi, config, mmpPath, pathList);
}
}
// now listen for changes to these files (the ones in the workspace at least)
for (IPath path : pathList) {
IPath wsPath = FileUtils.convertToWorkspacePath(path);
if (wsPath != null) {
resourceChangedListener.addResource(wsPath, handler);
}
}
}
public void installedSdkChanged(SDKChangeEventType eventType) {
if (eventType == SDKChangeEventType.eSDKScanned){
synchronized(projectInfoMap){
for (IProject currPrj : projectInfoMap.keySet()){
try {
ICProjectDescription projDes = CoreModel.getDefault().getProjectDescription(currPrj);
if (projDes != null) {
CCorePlugin.getDefault().setProjectDescription(currPrj, projDes, true, null);
}
} catch (CoreException e) {
e.printStackTrace();
CarbideBuilderPlugin.log(e);
}
}
}
}
}
}