Skip to content
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

Add pre/post build events to the Build page #7119

Merged
5 commits merged into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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