Skip to content

Commit

Permalink
Merge pull request #7119 from drewnoakes/fix-5953-build-events
Browse files Browse the repository at this point in the history
Add pre/post build events to the Build page
  • Loading branch information
msftbot[bot] authored Apr 14, 2021
2 parents 034b42b + 9bc3629 commit 9a1ca91
Show file tree
Hide file tree
Showing 18 changed files with 679 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected AbstractBuildEventHelper(string buildEvent,
private string BuildEvent { get; }
private string TargetName { get; }

public async Task<(bool success, string? property)> TryGetPropertyAsync(IProjectProperties defaultProperties)
public async Task<(bool success, string? value)> TryGetUnevaluatedPropertyValueAsync(IProjectProperties defaultProperties)
{
// check if value already exists
string? unevaluatedPropertyValue = await defaultProperties.GetUnevaluatedPropertyValueAsync(BuildEvent);
Expand All @@ -43,9 +43,34 @@ protected AbstractBuildEventHelper(string buildEvent,
return (false, null);
}

public string? GetProperty(ProjectRootElement projectXml)
public async Task<(bool success, string value)> TryGetEvaluatedPropertyValueAsync(IProjectProperties defaultProperties)
{
return GetFromTargets(projectXml);
string? unevaluatedPropertyValue = await defaultProperties.GetUnevaluatedPropertyValueAsync(BuildEvent);

if (unevaluatedPropertyValue is null)
{
return (false, "");
}

string evaluatedPropertyValue = await defaultProperties.GetEvaluatedPropertyValueAsync(BuildEvent);
return (true, evaluatedPropertyValue);
}

public string? TryGetValueFromTarget(ProjectRootElement projectXml)
{
ProjectTaskElement? execTask = FindExecTaskInTargets(projectXml);

if (execTask == null)
{
return null;
}

if (execTask.Parameters.TryGetValue(Command, out string commandText))
{
return commandText.Replace("%25", "%");
}

return null; // exec task as written in the project file is invalid, we should be resilient to this case.
}

public async Task<bool> TrySetPropertyAsync(string unevaluatedPropertyValue, IProjectProperties defaultProperties)
Expand Down Expand Up @@ -82,23 +107,6 @@ public void SetProperty(string unevaluatedPropertyValue, ProjectRootElement proj
SetParameter(projectXml, unevaluatedPropertyValue);
}

private string? GetFromTargets(ProjectRootElement projectXml)
{
ProjectTaskElement? execTask = FindExecTaskInTargets(projectXml);

if (execTask == null)
{
return null;
}

if (execTask.Parameters.TryGetValue(Command, out string commandText))
{
return commandText.Replace("%25", "%");
}

return null; // exec task as written in the project file is invalid, we should be resilient to this case.
}

private static bool OnlyWhitespaceCharacters(string unevaluatedPropertyValue)
=> string.IsNullOrWhiteSpace(unevaluatedPropertyValue) &&
!unevaluatedPropertyValue.Contains("\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,68 @@ protected AbstractBuildEventValueProvider(
_helper = helper;
}

// There are two ways of storing pre/post build events in the project.
//
// 1. As MSBuild properties (PreBuildEvent / PostBuildEvent)
// 2. As MSBuild tasks (PreBuild / PostBuild)
//
// Properties were used in legacy projects.
//
// For SDK style projects, we should use tasks.
//
// In legacy projects, the properties were defined _after_ the import of common targets,
// meaning that the properties had access to a full range of property values for use in their
// bodies.
//
// In SDK projects, it's not possible to define a property in the project after the common
// targets, so an MSBuild task is used instead.
//
// Some projects still define these events using properties, and the below code will work
// with such properties when they exist. However if these properties are absent, then
// tasks are used instead.
//
// Examples of MSBuild properties that are not available to PreBuildEvent/PostBuildEvent
// properties (but which are available to PreBuild/PostBuild targets) are ProjectExt,
// PlatformName, ProjectDir, TargetDir, TargetFileName, TargetExt, ProjectFileName,
// ProjectPath, TargetPath, TargetName, ProjectName, ConfigurationName, and OutDir.
//
// Tasks are defined as:
//
// <Target Name="PreBuild" AfterTargets="PreBuildEvent">
// <Exec Command="echo Hello World" />
// </Target>
// <Target Name="PostBuild" AfterTargets="PostBuildEvent">
// <Exec Command="echo Hello World" />
// </Target>

public override async Task<string> OnGetUnevaluatedPropertyValueAsync(
string propertyName,
string unevaluatedPropertyValue,
IProjectProperties defaultProperties)
{
(bool success, string? property) = await _helper.TryGetUnevaluatedPropertyValueAsync(defaultProperties);

if (success)
{
return property ?? string.Empty;
}

return await _projectAccessor.OpenProjectXmlForReadAsync(_unconfiguredProject, projectXml => _helper.TryGetValueFromTarget(projectXml)) ?? string.Empty;
}

public override async Task<string> OnGetEvaluatedPropertyValueAsync(
string propertyName,
string evaluatedPropertyValue,
IProjectProperties defaultProperties)
{
(bool success, string? property) = await _helper.TryGetPropertyAsync(defaultProperties);
(bool success, string property) = await _helper.TryGetEvaluatedPropertyValueAsync(defaultProperties);

if (success)
{
return property ?? string.Empty;
return property;
}

return await _projectAccessor.OpenProjectXmlForReadAsync(_unconfiguredProject, projectXml => _helper.GetProperty(projectXml)) ?? string.Empty;
return await _projectAccessor.OpenProjectXmlForReadAsync(_unconfiguredProject, projectXml => _helper.TryGetValueFromTarget(projectXml)) ?? string.Empty;
}

public override async Task<string?> OnSetPropertyValueAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
Description="Configures the output options for the build process."
DisplayName="Output" />

<Category Name="Events"
Description="Configures custom events that run before and after build."
DisplayName="Events" />

<Category Name="StrongNaming"
Description="Configures strong name signing of build outputs."
DisplayName="Strong naming" />
Expand Down Expand Up @@ -275,6 +279,63 @@
</StringProperty.Metadata>
</StringProperty>

<!-- TODO create fwlink -->
<StringProperty Name="PreBuildEvent"
DisplayName="Pre-build event"
Description="Specifies commands that run before the build starts. Does not run if the project is up-to-date. A non-zero exit code will fail the build before it runs."
HelpUrl="https://docs.microsoft.com/en-us/visualstudio/ide/how-to-specify-build-events-csharp?view=vs-2019"
Category="Events">
<StringProperty.DataSource>
<DataSource HasConfigurationCondition="False"
Persistence="ProjectFileWithInterception"
SourceOfDefaultValue="AfterContext" />
</StringProperty.DataSource>
<StringProperty.ValueEditors>
<ValueEditor EditorType="MultiLineString">
<ValueEditor.Metadata>
<NameValuePair Name="UseMonospaceFont" Value="True" />
</ValueEditor.Metadata>
</ValueEditor>
</StringProperty.ValueEditors>
</StringProperty>

<!-- TODO create fwlink -->
<StringProperty Name="PostBuildEvent"
DisplayName="Post-build event"
Description="Specifies commands that run after the build completes. Does not run if the build failed. Use 'call' to invoke .bat files. A non-zero exit code will fail the build."
HelpUrl="https://docs.microsoft.com/en-us/visualstudio/ide/how-to-specify-build-events-csharp?view=vs-2019"
Category="Events">
<StringProperty.DataSource>
<DataSource HasConfigurationCondition="False"
Persistence="ProjectFileWithInterception"
SourceOfDefaultValue="AfterContext" />
</StringProperty.DataSource>
<StringProperty.ValueEditors>
<ValueEditor EditorType="MultiLineString">
<ValueEditor.Metadata>
<NameValuePair Name="UseMonospaceFont" Value="True" />
</ValueEditor.Metadata>
</ValueEditor>
</StringProperty.ValueEditors>
</StringProperty>

<!-- TODO create fwlink -->
<EnumProperty Name="RunPostBuildEvent"
DisplayName="When to run the post-build event"
Description="Specifies under which condition the post-build event will be executed."
HelpUrl="https://docs.microsoft.com/en-us/visualstudio/ide/how-to-specify-build-events-csharp?view=vs-2019"
Category="Events">
<EnumProperty.DataSource>
<DataSource HasConfigurationCondition="False"
PersistedName="RunPostBuildEvent"
Persistence="ProjectFile"
SourceOfDefaultValue="AfterContext" />
</EnumProperty.DataSource>
<EnumValue Name="Always" />
<EnumValue Name="OnBuildSuccess" IsDefault="True" />
<EnumValue Name="OnOutputUpdated" />
</EnumProperty>

<BoolProperty Name="SignAssembly"
Description="Sign the output assembly to give it a strong name."
HelpUrl="https://go.microsoft.com/fwlink/?linkid=2147136"
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9a1ca91

Please sign in to comment.