New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
make pre build events post build events work using targets #2367
Changes from 5 commits
f239054
62c1be3
033dff3
7fdcd3d
2d1c39b
6be6230
7e7f558
27f6105
153579a
7d804b0
efe78bd
031b78e
4571f72
4dc3de9
f49f152
feddd05
f5e4e00
6b75f26
1df58fa
3a59393
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.Xml; | ||
using Microsoft.Build.Construction; | ||
|
||
namespace Microsoft.VisualStudio.ProjectSystem.VS.Utilities | ||
{ | ||
public static class StringExtensions | ||
{ | ||
public static ProjectRootElement AsProjectRootElement(this string @string) | ||
{ | ||
var stringReader = new System.IO.StringReader(@string); | ||
var xmlReader = new XmlTextReader(stringReader); | ||
var root = ProjectRootElement.Create(xmlReader); | ||
return root; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
|
||
using System; | ||
using System.Linq; | ||
using Microsoft.Build.Construction; | ||
|
||
namespace Microsoft.VisualStudio.ProjectSystem.VS.Properties.InterceptedProjectProperties | ||
{ | ||
internal abstract partial class AbstractBuildEventValueProvider | ||
{ | ||
public abstract class Helper | ||
{ | ||
private const string _execTaskName = "Exec"; | ||
private const string _commandString = "Command"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
|
||
protected Helper(string buildEventString, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
string targetNameString, | ||
Func<ProjectTargetElement, string> getTargetString, | ||
Action<ProjectTargetElement> setTargetDependencies) | ||
{ | ||
BuildEventString = buildEventString; | ||
TargetNameString = targetNameString; | ||
GetTargetString = getTargetString; | ||
SetTargetDependencies = setTargetDependencies; | ||
} | ||
|
||
private Func<ProjectTargetElement, string> GetTargetString { get; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I am not too bothered about the redundant naming here. But just wanted to bring it to your notice . |
||
private Action<ProjectTargetElement> SetTargetDependencies { get; } | ||
private string BuildEventString { get; } | ||
private string TargetNameString { get; } | ||
|
||
public string GetProperty(ProjectRootElement projectXml) | ||
{ | ||
var result = FindExecTaskInTargets(projectXml); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why return a tuple here? Instead, cant we do a null check to determine success? |
||
|
||
if (result.success == false) | ||
{ | ||
return null; | ||
} | ||
|
||
if (result.execTask.Parameters.TryGetValue(_commandString, out var commandText)) | ||
{ | ||
return commandText; | ||
} | ||
|
||
return null; //unreachable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to add a Assert fail so that we get notified when this happens rather than silently passing |
||
} | ||
|
||
public void SetProperty(string unevaluatedPropertyValue, ProjectRootElement projectXml) | ||
{ | ||
if (string.IsNullOrWhiteSpace(unevaluatedPropertyValue) && | ||
!unevaluatedPropertyValue.Contains("\n")) | ||
{ | ||
var result = FindTargetToRemove(projectXml); | ||
if (result.success) | ||
{ | ||
projectXml.RemoveChild(result.target); | ||
} | ||
} | ||
else | ||
{ | ||
SetParameter(projectXml, unevaluatedPropertyValue); | ||
} | ||
} | ||
|
||
private (bool success, ProjectTaskElement execTask) FindExecTaskInTargets(ProjectRootElement projectXml) | ||
{ | ||
var execTask = projectXml.Targets | ||
.Where(target => GetTargetString(target) == BuildEventString) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. case-insensitive comparison required |
||
.SelectMany(target => target.Tasks) | ||
.Where(task => task.Name == _execTaskName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Msbuild syntax itself is case-insensitive. We should account for it. |
||
.FirstOrDefault(); | ||
return (success: execTask != null, execTask: execTask); | ||
} | ||
|
||
private (bool success, ProjectTargetElement target) FindTargetToRemove(ProjectRootElement projectXml) | ||
{ | ||
var targetArray = projectXml.Targets | ||
.Where(target => | ||
GetTargetString(target) == BuildEventString && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. case insensitive comparison here as well |
||
target.Children.Count == 1 && | ||
target.Tasks.Count == 1 && | ||
target.Tasks.First().Name == _execTaskName); | ||
return (success: targetArray.Count() == 1, target: targetArray.SingleOrDefault()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does not make sense to have the same target twice but nevertheless we do allow it. If there is more than one, then we will crash since we are using |
||
} | ||
|
||
private void SetParameter(ProjectRootElement projectXml, string unevaluatedPropertyValue) | ||
{ | ||
var result = FindExecTaskInTargets(projectXml); | ||
|
||
if (result.success == true) | ||
{ | ||
SetExecParameter(result.execTask, unevaluatedPropertyValue); | ||
} | ||
else | ||
{ | ||
var targetName = GetTargetName(projectXml); | ||
var target = projectXml.AddTarget(targetName); | ||
SetTargetDependencies(target); | ||
var execTask = target.AddTask(_execTaskName); | ||
SetExecParameter(execTask, unevaluatedPropertyValue); | ||
} | ||
} | ||
|
||
private void SetExecParameter(ProjectTaskElement execTask, string unevaluatedPropertyValue) | ||
=> execTask.SetParameter(_commandString, unevaluatedPropertyValue); | ||
|
||
private string GetTargetName(ProjectRootElement projectXml) | ||
{ | ||
var targetNames = projectXml.Targets.Select(t => t.Name).ToArray(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are going to allocate a string for all targets. |
||
var targetName = TargetNameString; | ||
if (targetNames.Contains(targetName)) | ||
{ | ||
targetName = FindNonCollidingName(targetName, targetNames); | ||
} | ||
|
||
return targetName; | ||
|
||
} | ||
|
||
private string FindNonCollidingName(string buildEventString, string[] targetNames) | ||
{ | ||
var initialValue = 1; | ||
var newName = buildEventString + initialValue.ToString(); | ||
while (targetNames.Contains(newName)) | ||
{ | ||
initialValue++; | ||
newName = buildEventString + initialValue.ToString(); | ||
} | ||
|
||
return newName; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.ProjectSystem.Properties; | ||
|
||
namespace Microsoft.VisualStudio.ProjectSystem.VS.Properties.InterceptedProjectProperties | ||
{ | ||
internal abstract partial class AbstractBuildEventValueProvider : InterceptingPropertyValueProviderBase | ||
{ | ||
protected readonly IProjectLockService _projectLockService; | ||
protected readonly UnconfiguredProject _unconfiguredProject; | ||
private readonly Helper _helper; | ||
|
||
protected AbstractBuildEventValueProvider( | ||
IProjectLockService projectLockService, | ||
UnconfiguredProject unconfiguredProject, | ||
Helper helper) | ||
{ | ||
_projectLockService = projectLockService; | ||
_unconfiguredProject = unconfiguredProject; | ||
_helper = helper; | ||
} | ||
|
||
public override async Task<string> OnGetEvaluatedPropertyValueAsync( | ||
string evaluatedPropertyValue, | ||
IProjectProperties defaultProperties) | ||
{ | ||
using (var access = await _projectLockService.ReadLockAsync()) | ||
{ | ||
var projectXml = await access.GetProjectXmlAsync(_unconfiguredProject.FullPath).ConfigureAwait(true); | ||
return _helper.GetProperty(projectXml); | ||
} | ||
} | ||
|
||
public override async Task<string> OnSetPropertyValueAsync( | ||
string unevaluatedPropertyValue, | ||
IProjectProperties defaultProperties, | ||
IReadOnlyDictionary<string, string> dimensionalConditions = null) | ||
{ | ||
using (var access = await _projectLockService.WriteLockAsync()) | ||
{ | ||
var projectXml = await access.GetProjectXmlAsync(_unconfiguredProject.FullPath).ConfigureAwait(true); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove Linebreak |
||
_helper.SetProperty(unevaluatedPropertyValue, projectXml); | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.ComponentModel.Composition; | ||
using Microsoft.VisualStudio.ProjectSystem.Properties; | ||
|
||
namespace Microsoft.VisualStudio.ProjectSystem.VS.Properties.InterceptedProjectProperties | ||
{ | ||
[ExportInterceptingPropertyValueProvider(_postBuildEventString, ExportInterceptingPropertyValueProviderFile.ProjectFile)] | ||
internal class PostBuildEventValueProvider : AbstractBuildEventValueProvider | ||
{ | ||
private const string _postBuildEventString = "PostBuildEvent"; | ||
private const string _targetNameString = "PostBuild"; | ||
|
||
[ImportingConstructor] | ||
public PostBuildEventValueProvider( | ||
IProjectLockService projectLockService, | ||
UnconfiguredProject unconfiguredProject) | ||
: base(projectLockService, | ||
unconfiguredProject, | ||
new PostBuildEventHelper()) | ||
{} | ||
|
||
internal class PostBuildEventHelper : Helper | ||
{ | ||
internal PostBuildEventHelper() | ||
: base(_postBuildEventString, | ||
_targetNameString, | ||
target => target.AfterTargets, | ||
target => { target.AfterTargets = _postBuildEventString; }) | ||
{ } | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.ComponentModel.Composition; | ||
using Microsoft.VisualStudio.ProjectSystem.Properties; | ||
|
||
namespace Microsoft.VisualStudio.ProjectSystem.VS.Properties.InterceptedProjectProperties | ||
{ | ||
[ExportInterceptingPropertyValueProvider(_preBuildEventString, ExportInterceptingPropertyValueProviderFile.ProjectFile)] | ||
internal class PreBuildEventValueProvider : AbstractBuildEventValueProvider | ||
{ | ||
private const string _preBuildEventString = "PreBuildEvent"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
private const string _targetNameString = "PreBuild"; | ||
|
||
[ImportingConstructor] | ||
public PreBuildEventValueProvider( | ||
IProjectLockService projectLockService, | ||
UnconfiguredProject unconfiguredProject) | ||
: base(projectLockService, | ||
unconfiguredProject, | ||
new PreBuildEventHelper()) | ||
{ } | ||
|
||
internal class PreBuildEventHelper : Helper | ||
{ | ||
internal PreBuildEventHelper() | ||
: base(_preBuildEventString, | ||
_targetNameString, | ||
target => target.BeforeTargets, | ||
target => { target.BeforeTargets = _preBuildEventString; }) | ||
{ } | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: just
ExecTask
maybe?