/*******************************************************************************
* Copyright (c) 2015 Bruno Medeiros and other Contributors.
* 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
*
* Contributors:
* Bruno Medeiros - initial API and implementation
*******************************************************************************/
package melnorme.lang.ide.core.operations.build;
import static melnorme.lang.ide.core.LangCore_Actual.VAR_NAME_SdkToolPath;
import static melnorme.lang.ide.core.operations.build.BuildManagerMessages.MSG_Starting_LANG_Build;
import static melnorme.lang.ide.core.utils.TextMessageUtils.headerVeryBig;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import static melnorme.utilbox.core.CoreUtil.areEqual;
import static melnorme.utilbox.core.CoreUtil.option;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Platform;
import melnorme.lang.ide.core.LangCore;
import melnorme.lang.ide.core.LangCore_Actual;
import melnorme.lang.ide.core.launch.LaunchMessages;
import melnorme.lang.ide.core.operations.ILangOperationsListener_Default.IToolOperationMonitor;
import melnorme.lang.ide.core.operations.ToolManager;
import melnorme.lang.ide.core.operations.build.BuildOperationCreator.ProjectBuildOperation;
import melnorme.lang.ide.core.operations.build.BuildTargetOperation.BuildOperationParameters;
import melnorme.lang.ide.core.project_model.IProjectModelListener;
import melnorme.lang.ide.core.project_model.LangBundleModel;
import melnorme.lang.ide.core.project_model.ProjectBasedModel;
import melnorme.lang.ide.core.project_model.UpdateEvent;
import melnorme.lang.ide.core.utils.ProjectValidator;
import melnorme.lang.ide.core.utils.ResourceUtils;
import melnorme.lang.ide.core.utils.operation.EclipseJobOperation;
import melnorme.lang.ide.core.utils.prefs.IProjectPreference;
import melnorme.lang.ide.core.utils.prefs.StringPreference;
import melnorme.lang.tooling.bundle.BuildConfiguration;
import melnorme.lang.tooling.bundle.BuildTargetNameParser;
import melnorme.lang.tooling.bundle.BundleInfo;
import melnorme.lang.tooling.bundle.LaunchArtifact;
import melnorme.lang.tooling.common.ops.IOperationMonitor;
import melnorme.lang.tooling.common.ops.Operation;
import melnorme.lang.utils.EnablementCounter;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.collections.HashMap2;
import melnorme.utilbox.collections.Indexable;
import melnorme.utilbox.concurrency.OperationCancellation;
import melnorme.utilbox.core.CommonException;
import melnorme.utilbox.misc.CollectionUtil;
import melnorme.utilbox.misc.Location;
import melnorme.utilbox.misc.SimpleLogger;
import melnorme.utilbox.misc.StringUtil;
import melnorme.utilbox.status.StatusException;
public abstract class BuildManager {
public static final SimpleLogger log = new SimpleLogger(Platform.inDebugMode());
public static BuildManager getInstance() {
return LangCore.getBuildManager();
}
/* ----------------- ----------------- */
protected final BuildModel buildModel;
protected final LangBundleModel bundleModel;
protected final ToolManager toolManager;
public BuildManager(LangBundleModel bundleModel, ToolManager toolManager) {
this(new BuildModel(), bundleModel, toolManager);
}
public BuildManager(BuildModel buildModel, LangBundleModel bundleModel, ToolManager toolManager) {
this(buildModel, bundleModel, toolManager, true);
}
public BuildManager(BuildModel buildModel, LangBundleModel bundleModel, ToolManager toolManager,
boolean initialize) {
this.buildModel = assertNotNull(buildModel);
this.bundleModel = assertNotNull(bundleModel);
this.toolManager = assertNotNull(toolManager);
if(initialize) {
initialize(bundleModel);
}
}
public ToolManager getToolManager() {
return toolManager;
}
public void initialize(LangBundleModel bundleModel) {
synchronized (init_Lock) {
HashMap<String, BundleInfo> projectInfos = bundleModel.connectListener(listener);
for(Entry<String, BundleInfo> entry : projectInfos.entrySet()) {
IProject project = ResourceUtils.getProject(entry.getKey());
BundleInfo bundleInfo = entry.getValue();
bundleProjectAddedOrModified(project, bundleInfo);
}
}
}
protected Object init_Lock = new Object();
protected final IProjectModelListener<BundleInfo> listener = new IProjectModelListener<BundleInfo>() {
@Override
public void notifyUpdateEvent(UpdateEvent<BundleInfo> updateEvent) {
synchronized (init_Lock) {
if(updateEvent.newProjectInfo2 != null) {
bundleProjectAddedOrModified(updateEvent.project, updateEvent.newProjectInfo2);
} else {
bundleProjectRemoved(updateEvent.project);
}
}
}
};
public void dispose() {
bundleModel.removeListener(listener);
}
public BuildModel getBuildModel() {
return buildModel;
}
public static class BuildModel extends ProjectBasedModel<ProjectBuildInfo> {
public BuildModel() {
}
@Override
protected SimpleLogger getLog() {
return BuildManager.log;
}
}
/* ----------------- ----------------- */
public ProjectBuildInfo getBuildInfo(IProject project) {
return buildModel.getProjectInfo(assertNotNull(project));
}
public ProjectBuildInfo getValidBuildInfo(IProject project) throws CommonException {
return getValidBuildInfo(project, true);
}
public ProjectBuildInfo getValidBuildInfo(IProject project, boolean requireNonEmtpyTargets)
throws CommonException {
new ProjectValidator().checkProjectNotNull(project);
ProjectBuildInfo buildInfo = getBuildInfo(project);
if(buildInfo == null || (requireNonEmtpyTargets && buildInfo.getBuildTargets().isEmpty())) {
throw new CommonException("No build targets available for project.");
}
return buildInfo;
}
/* ----------------- Persistence preference ----------------- */
protected static final IProjectPreference<String> BUILD_TARGETS_DATA =
new StringPreference("build_targets", "").getProjectPreference();
protected BuildTargetsSerializer createSerializer() {
return new BuildTargetsSerializer();
}
protected String getBuildTargetsPref(IProject project) {
return StringUtil.emptyAsNull(BUILD_TARGETS_DATA.getStoredValue(option(project)));
}
/* ----------------- ProjectBuildInfo ----------------- */
protected void bundleProjectAddedOrModified(IProject project, BundleInfo newBundleInfo) {
loadProjectBuildInfo(project, newBundleInfo);
}
protected void bundleProjectRemoved(IProject project) {
buildModel.removeProjectInfo(project);
}
public ProjectBuildInfo setProjectBuildInfo(IProject project, ProjectBuildInfo newProjectBuildInfo) {
return buildModel.setProjectInfo(project, newProjectBuildInfo);
}
public void tryUpdateProjectBuildInfo(IProject project, ProjectBuildInfo oldInfo, ProjectBuildInfo newInfo)
throws CommonException {
boolean success = buildModel.updateProjectInfo(project, oldInfo, newInfo);
if(!success) {
throw new CommonException(BuildManagerMessages.ERROR_ProjectBuildSettingsOutOfDate);
}
}
protected void loadProjectBuildInfo(IProject project, BundleInfo newBundleInfo) {
assertNotNull(project);
assertNotNull(newBundleInfo);
final ProjectBuildInfo currentBuildInfo = buildModel.getProjectInfo(project);
Map<String, BuildTarget> existingBuildTargets;
if(currentBuildInfo != null) {
existingBuildTargets = currentBuildInfo.getBuildTargetsMap();
} else {
existingBuildTargets = getStoredTargetSettings(project);
}
// Create new build info
ArrayList2<BuildTarget> buildTargets = createBuildTargetsForNewBundleInfo(
project, newBundleInfo, existingBuildTargets);
ProjectBuildInfo newBuildInfo = new ProjectBuildInfo(this, project, newBundleInfo, buildTargets);
setProjectBuildInfo(project, newBuildInfo);
}
protected Map<String, BuildTarget> getStoredTargetSettings(IProject project) {
Map<String, BuildTarget> existingBuildTargets = new HashMap<>();
String targetsPrefValue = getBuildTargetsPref(project);
if(targetsPrefValue != null) {
ArrayList2<BuildTargetData> buildTargetsData;
try {
buildTargetsData = createSerializer().readFromString(targetsPrefValue);
} catch(CommonException ce) {
LangCore.logError("Error reading project build-info.", ce);
return existingBuildTargets;
}
for (BuildTargetData buildTargetData : buildTargetsData) {
BuildTarget buildTarget;
try {
buildTarget = createBuildTarget(project, buildTargetData);
} catch(CommonException ce) {
LangCore.logWarning("Invalid build target.", ce);
continue;
}
existingBuildTargets.put(buildTargetData.getTargetName(), buildTarget);
}
}
return existingBuildTargets;
}
protected final ArrayList2<BuildTarget> createBuildTargetsForNewBundleInfo(IProject project,
BundleInfo newBundleInfo, Map<String, BuildTarget> currentBuildTargets) {
ArrayList2<BuildTarget> buildTargets = getDefaultBuildTargets(project, newBundleInfo);
for(int ix = 0; ix < buildTargets.size(); ix++) {
BuildTarget buildTarget = buildTargets.get(ix);
BuildTarget currentBuildTarget = currentBuildTargets.get(buildTarget.getTargetName());
if(currentBuildTarget != null) {
buildTargets.set(ix, createBuildTargetForNewBundleInfo(
newBundleInfo, buildTarget, currentBuildTarget.getDataCopy()));
}
}
return buildTargets;
}
protected BuildTarget createBuildTargetForNewBundleInfo(BundleInfo newBundleInfo, BuildTarget defaultBuildTarget,
BuildTargetData btd) {
return new BuildTarget(defaultBuildTarget.getProject(), newBundleInfo, btd,
defaultBuildTarget.getBuildType(), defaultBuildTarget.getBuildConfiguration());
}
protected ArrayList2<BuildTarget> getDefaultBuildTargets(IProject project, BundleInfo newBundleInfo) {
ArrayList2<BuildTarget> buildTargets = new ArrayList2<>();
boolean isFirstConfig = true;
Indexable<BuildConfiguration> buildConfigs = newBundleInfo.getBuildConfigurations();
for(BuildConfiguration buildConfig : buildConfigs) {
for(BuildType buildType : getBuildTypes()) {
String targetName = getBuildTargetName2(buildConfig.getName(), buildType.getName());
BuildTargetData newBuildTargetData = new BuildTargetData(targetName, isFirstConfig, false);
addDefaultBuildTarget(buildTargets, project, newBundleInfo, buildConfig, buildType, newBuildTargetData);
isFirstConfig = false;
}
}
return buildTargets;
}
protected void addDefaultBuildTarget(ArrayList2<BuildTarget> buildTargets, IProject project, BundleInfo bundleInfo,
BuildConfiguration buildConfig, BuildType buildType, BuildTargetData btd) {
buildTargets.add(new BuildTarget(project, bundleInfo, btd, buildType, buildConfig));
}
public void saveProjectInfo(IProject project) {
ProjectBuildInfo projectInfo = buildModel.getProjectInfo(project);
try {
String data = createSerializer().writeProjectBuildInfo(projectInfo);
BUILD_TARGETS_DATA.setValue(project, data);
} catch(CommonException e) {
LangCore.logError("Error persisting project build info: ", e);
}
}
/* ----------------- Build Types ----------------- */
public static abstract class BuildType {
protected final String name;
public BuildType(String name) {
this.name = assertNotNull(name);
}
/* ----------------- ----------------- */
public String getName() {
return name;
}
protected BuildConfiguration getValidBuildconfiguration(String buildConfigName, BundleInfo bundleInfo)
throws CommonException {
return bundleInfo.getBuildConfiguration_nonNull(buildConfigName);
}
public String getDefaultCommandLine(BuildTarget bt) throws CommonException {
return VariablesResolver.variableRefString(VAR_NAME_SdkToolPath) + " " + getDefaultCommandArguments(bt);
}
public abstract String getDefaultCommandArguments(BuildTarget bt) throws CommonException;
public LaunchArtifact getMainLaunchArtifact(BuildTarget bt) throws CommonException {
BuildConfiguration buildConfig = bt.getBuildConfiguration();
if(buildConfig.getArtifactPath() == null) {
return null;
}
return new LaunchArtifact(buildConfig.getName(), buildConfig.getArtifactPath());
}
@SuppressWarnings("unused")
public Indexable<LaunchArtifact> getSubTargetLaunchArtifacts(BuildTarget bt) throws CommonException {
return null;
}
public abstract BuildTargetOperation getBuildOperation(BuildOperationParameters buildOpParams)
throws CommonException;
}
protected final Indexable<BuildType> buildTypes = initBuildTypes();
protected final Indexable<BuildType> initBuildTypes() {
Indexable<BuildType> buildTypes = getBuildTypes_do();
assertTrue(buildTypes.size() > 0);
assertTrue(buildTypes.contains(null) == false);
return buildTypes;
}
protected abstract Indexable<BuildType> getBuildTypes_do();
public Indexable<BuildType> getBuildTypes() {
return buildTypes;
}
public BuildType getBuildType_NonNull(String buildTypeName) throws CommonException {
if(buildTypeName == null || buildTypeName.isEmpty()) {
return getDefaultBuildType();
}
for(BuildType buildType : buildTypes) {
if(areEqual(buildType.getName(), buildTypeName)) {
return buildType;
}
}
throw new CommonException(BuildManagerMessages.BuildType_NotFound(buildTypeName));
}
public BuildType getDefaultBuildType() {
return getBuildTypes().get(0);
}
/* ----------------- Build Target name ----------------- */
public BuildTargetNameParser getBuildTargetNameParser() {
return new BuildTargetNameParser();
}
public final String getDefaultBuildTypeName() {
return getDefaultBuildType().getName();
}
public String getBuildTargetName2(String buildConfigName, String buildTypeName) {
assertNotNull(buildConfigName);
return getBuildTargetNameParser().getFullName(buildConfigName, buildTypeName);
}
public String getResolvedBuildTargetName(String buildTargetName) {
BuildTargetNameParser nameParser = getBuildTargetNameParser();
String buildConfig = nameParser.getBuildConfig(buildTargetName);
String buildType = nameParser.getBuildType(buildTargetName);
if(buildType == null) {
buildType = getDefaultBuildTypeName();
}
return nameParser.getFullName(buildConfig, buildType);
}
/* ----------------- Build Target ----------------- */
public BuildTarget createBuildTarget(IProject project, BuildTargetDataView buildTargetData)
throws CommonException {
assertNotNull(buildTargetData.getTargetName());
String targetName = buildTargetData.getTargetName();
assertNotNull(targetName);
BuildTargetNameParser nameParser = getBuildTargetNameParser();
String buildConfigName = nameParser.getBuildConfig(targetName);
BuildType buildType = getBuildType_NonNull(nameParser.getBuildType(targetName));
BundleInfo bundleInfo = bundleModel.getBundleInfo(project);
return BuildTarget.create(project, bundleInfo, buildTargetData, buildType, buildConfigName);
}
public BuildTarget getDefinedBuildTarget(IProject project, String buildTargetName)
throws CommonException {
return getBuildTarget(project, buildTargetName, true, true);
}
public BuildTarget getBuildTarget(IProject project, String buildTargetName, boolean definedTargetsOnly)
throws CommonException, StatusException {
return getBuildTarget(project, buildTargetName, definedTargetsOnly, true);
}
public final BuildTarget getBuildTarget(IProject project, String buildTargetName, boolean definedTargetsOnly,
boolean requireNonNull) throws CommonException {
ProjectBuildInfo buildInfo = getValidBuildInfo(project, false);
return getBuildTarget_x(buildInfo, buildTargetName, definedTargetsOnly, requireNonNull);
}
protected void validateBuildTargetName(String buildTargetName) throws CommonException {
if(buildTargetName == null || buildTargetName.isEmpty()) {
throw new CommonException(LaunchMessages.BuildTarget_NotSpecified);
}
}
public BuildTarget getBuildTarget_x(ProjectBuildInfo buildInfo, String buildTargetName, boolean definedTargetsOnly,
boolean requireNonNull) throws CommonException {
// validate name after validation project buildInfo
validateBuildTargetName(buildTargetName);
BuildTarget buildTarget = buildInfo.getDefinedBuildTarget(buildTargetName);
if(buildTarget == null) {
if(!definedTargetsOnly) {
buildTarget = createBuildTarget(buildInfo.getProject(),
new BuildTargetData(buildTargetName, false, false));
}
else if(requireNonNull) {
throw new CommonException(LaunchMessages.BuildTarget_NotFound);
}
}
return buildTarget;
}
public BuildTarget getFirstDefinedBuildTarget(IProject project, BuildType buildType) throws CommonException {
ProjectBuildInfo buildInfo = getBuildInfo(project);
assertNotNull(buildType);
BuildTarget foundBT = buildInfo.getBuildTargets().findElement((bt) -> bt.getBuildType() == buildType);
if(foundBT == null) {
throw CommonException.fromMsgFormat(
BuildManagerMessages.NO_BUILD_TARGET_FOUND_FOR_BUILD_TYPE_0, buildType.getName());
}
return foundBT;
}
/* ----------------- Build operations ----------------- */
protected final EnablementCounter autoBuildsEnablement = new EnablementCounter();
public EnablementCounter autoBuildsEnablement() {
return autoBuildsEnablement;
}
/* ----------------- ----------------- */
public final void executeBuildTargetOperation(
IOperationMonitor om, IProject project, BuildTarget buildTarget
) throws CommonException, OperationCancellation {
executeBuildTargetsOperation(om, project, ArrayList2.create(buildTarget));
}
public final void executeBuildTargetsOperation(
IOperationMonitor om, IProject project, Iterable<BuildTarget> targetsToBuild
) throws CommonException, OperationCancellation {
IToolOperationMonitor toolMonitor = getToolManager().startNewBuildOperation();
requestBuildOperation(toolMonitor, project, true, targetsToBuild).execute(om);
}
public final EclipseJobOperation requestMultiBuild(
IOperationMonitor parentOM,
Iterable<IProject> projects,
boolean clearMarkers
) throws CommonException, OperationCancellation {
IToolOperationMonitor toolMonitor = getToolManager().startNewBuildOperation();
toolMonitor.writeInfoMessage(
headerVeryBig(MessageFormat.format(MSG_Starting_LANG_Build, LangCore_Actual.NAME_OF_LANGUAGE))
);
ArrayList2<ProjectBuildOperation> projectOps = ArrayList2.create();
for (IProject project : projects) {
// Note: this will immediately cancel previous operations
ProjectBuildOperation newBuildOp =
requestProjectBuildOperation(toolMonitor, project, clearMarkers, false);
projectOps.add(newBuildOp);
}
// Clear markers for all projects first
// This is because building for a project/bundle can actually create markers in other projects
for (IProject project : projects) {
newProjectClearMarkersOperation(toolMonitor, project).execute(parentOM);
}
Operation op = (om) -> {
for (ProjectBuildOperation projectOperation : projectOps) {
projectOperation.execute(om);
}
};
String opName = MessageFormat.format("Running {0} build", LangCore_Actual.NAME_OF_LANGUAGE);
EclipseJobOperation job = new EclipseJobOperation(opName, getToolManager(), op);
job.schedule();
return job;
}
public Operation newProjectClearMarkersOperation(
IToolOperationMonitor toolMonitor, IProject project
) throws CommonException {
return new ClearMarkersOperation(project, toolMonitor);
}
/* ----------------- ----------------- */
public final ProjectBuildOperation requestProjectBuildOperation(
IToolOperationMonitor toolMonitor,
IProject project,
boolean clearMarkers,
boolean isAuto
) throws CommonException, OperationCancellation {
ArrayList2<BuildTarget> enabledTargets = getValidBuildInfo(project).getEnabledTargets(!isAuto);
return requestBuildOperation(toolMonitor, project, clearMarkers, enabledTargets);
}
public ProjectBuildOperation requestBuildOperation(
IToolOperationMonitor toolMonitor,
IProject project,
boolean clearMarkers,
Iterable<BuildTarget> targetsToBuild
) throws CommonException {
assertNotNull(toolMonitor);
ArrayList2<Operation> buildCommands = CollectionUtil.mapx(targetsToBuild,
(buildTarget) -> buildTarget.getBuildOperation(toolManager, toolMonitor));
BuildOperationCreator opCreator = createBuildOperationCreator(toolMonitor, project);
ProjectBuildOperation newBuildOp = opCreator.newProjectBuildOperation2(clearMarkers, buildCommands);
setNewBuildOperation(newBuildOp);
return newBuildOp;
}
protected BuildOperationCreator createBuildOperationCreator(
IToolOperationMonitor opMonitor, IProject project
) throws CommonException {
return new BuildOperationCreator(project, opMonitor);
}
/* ----------------- ----------------- */
protected HashMap2<Location, ProjectBuildOperation> buildOps = new HashMap2<>();
protected final Object buildOps_mutex = new Object();
public ProjectBuildOperation getBuildOperation(Location location) {
synchronized (buildOps_mutex) {
return buildOps.get0(location);
}
}
public ProjectBuildOperation setNewBuildOperation(ProjectBuildOperation newOperation) {
Location location = newOperation.getLocation();
ProjectBuildOperation oldOp;
synchronized (buildOps_mutex) {
oldOp = buildOps.put(location, newOperation);
}
if(oldOp != null) {
oldOp.tryCancel();
}
return oldOp;
}
public Collection<ProjectBuildOperation> cancelAllBuilds() {
HashMap2<Location, ProjectBuildOperation> oldBuildOps;
synchronized (buildOps_mutex) {
oldBuildOps = buildOps;
buildOps = new HashMap2<>();
}
for (ProjectBuildOperation buildOp : oldBuildOps.values()) {
buildOp.tryCancel();
}
return oldBuildOps.values();
}
}