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

Merged
merged 20 commits into from Jun 20, 2017

Conversation

Projects
None yet
9 participants
@jmarolf
Member

jmarolf commented Jun 2, 2017

Customer scenario

User wants to have their pre-build and post build events use MSBuild variables but they are evaluated as empty strings without this fix

Bugs this fixes:

#1569
#1879

Workarounds, if any
User can manually modify their project file.

Risk

this only affects the project file if the user writes pre or post build events from the properties pages. risk is low.

Performance impact

Code is only called when the user changes the value in the property page. Risk is low.

Is this a regression from a previous update?

It is a regression from the native project system

Root cause analysis:

Targets are now implicitly imported to MSBuild variables do not exist. The fix is to add a target to the project file so that when the user add pre and post build events, by the time they are evaluated the MeBuild variables exist.

How was the bug found?

customer reported - This is one of the top VS Feedback reports with 20+ votes.

@jmarolf jmarolf requested review from srivatsn and basoundr Jun 2, 2017

@jmarolf

This comment has been minimized.

Show comment
Hide comment
@jmarolf

jmarolf Jun 2, 2017

Member

@dotnet/project-system please review. Let me know if you have a better idea for project file format.

Member

jmarolf commented Jun 2, 2017

@dotnet/project-system please review. Let me know if you have a better idea for project file format.

@jmarolf jmarolf changed the title from Bugfix/make pre build events post build events work using targets to make pre build events post build events work using targets Jun 2, 2017

@srivatsn

This comment has been minimized.

Show comment
Hide comment
@srivatsn

srivatsn Jun 2, 2017

Contributor

Tagging @rainersigwald @AndyGerlicher @cdmihai for ideas as well. The problem here is that prebuildevent and postbuildevent used to be specificed after import of csharp.targets so that variables like $(OutDir) can be used in the events. with the SDK attribute there's no easy way to do that.

@jmarolf here is making the property pages spit out a

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
   <Exec Command="echo $(OutDir)" />
</Target>

to the project file but that's not the best experience. Any better ideas?

@davkean suggested that atleast it should be

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
      <PreBuildEvent>echo $(OutDir)</PreBuildEvent>
</Target>

This will require changes to msbuild though because the PreBuildEvent target is conditioned on the existence of the PreBuildEvent property.

Contributor

srivatsn commented Jun 2, 2017

Tagging @rainersigwald @AndyGerlicher @cdmihai for ideas as well. The problem here is that prebuildevent and postbuildevent used to be specificed after import of csharp.targets so that variables like $(OutDir) can be used in the events. with the SDK attribute there's no easy way to do that.

@jmarolf here is making the property pages spit out a

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
   <Exec Command="echo $(OutDir)" />
</Target>

to the project file but that's not the best experience. Any better ideas?

@davkean suggested that atleast it should be

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
      <PreBuildEvent>echo $(OutDir)</PreBuildEvent>
</Target>

This will require changes to msbuild though because the PreBuildEvent target is conditioned on the existence of the PreBuildEvent property.

@rainersigwald

This comment has been minimized.

Show comment
Hide comment
@rainersigwald

rainersigwald Jun 2, 2017

but that's not the best experience

Can you elaborate, @srivatsn? I like it a lot better. As the original reasoning here reveals, defining properties that get fed into an exec task later has . . . confusing behavior.

If you really love the current behavior, you could rewrite the project to have an explicit imports instead of implicit, and add the property definition in the old place.

rainersigwald commented Jun 2, 2017

but that's not the best experience

Can you elaborate, @srivatsn? I like it a lot better. As the original reasoning here reveals, defining properties that get fed into an exec task later has . . . confusing behavior.

If you really love the current behavior, you could rewrite the project to have an explicit imports instead of implicit, and add the property definition in the old place.

@jmarolf

This comment has been minimized.

Show comment
Hide comment
@jmarolf

jmarolf Jun 5, 2017

Member

We can do this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <RunPostBuildEvent>Always</RunPostBuildEvent>
  </PropertyGroup>

  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>

  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>

</Project>

or this:

<Project>

  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />
  
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <RunPostBuildEvent>Always</RunPostBuildEvent>
  </PropertyGroup>

  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />

  <PropertyGroup>
    <PreBuildEvent>echo $(ProjectDir)</PreBuildEvent>
    <PostBuildEvent>echo $(ProjectDir)</PostBuildEvent>
  </PropertyGroup>
  
</Project>

Both of which add six lines. I chose the first because the complexities of deciding when to change implicit/explicit target and props imports seemed like a drawback. Any other option would need to add special target or msbuild logic. Is that what we want to do?

Member

jmarolf commented Jun 5, 2017

We can do this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <RunPostBuildEvent>Always</RunPostBuildEvent>
  </PropertyGroup>

  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>

  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>

</Project>

or this:

<Project>

  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />
  
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <RunPostBuildEvent>Always</RunPostBuildEvent>
  </PropertyGroup>

  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />

  <PropertyGroup>
    <PreBuildEvent>echo $(ProjectDir)</PreBuildEvent>
    <PostBuildEvent>echo $(ProjectDir)</PostBuildEvent>
  </PropertyGroup>
  
</Project>

Both of which add six lines. I chose the first because the complexities of deciding when to change implicit/explicit target and props imports seemed like a drawback. Any other option would need to add special target or msbuild logic. Is that what we want to do?

@jmarolf

This comment has been minimized.

Show comment
Hide comment
@jmarolf

jmarolf Jun 5, 2017

Member

@srivatsn I think this PR needs to handle

  1. Converting the old property syntax to the new one
  2. Persisting the explicit imports style is a user enters that into the project file manually

Anything else?

Member

jmarolf commented Jun 5, 2017

@srivatsn I think this PR needs to handle

  1. Converting the old property syntax to the new one
  2. Persisting the explicit imports style is a user enters that into the project file manually

Anything else?

@rainersigwald

This comment has been minimized.

Show comment
Hide comment
@rainersigwald

rainersigwald Jun 5, 2017

@jmarolf Does MSBuild not do the latter already? Seems like we should.

rainersigwald commented Jun 5, 2017

@jmarolf Does MSBuild not do the latter already? Seems like we should.

@jmarolf

This comment has been minimized.

Show comment
Hide comment
@jmarolf

jmarolf Jun 5, 2017

Member

@rainersigwald MSBuild does, I am talking about when the user navigates to the property page to set this value. The property page should know this alternate format and not change it.

Member

jmarolf commented Jun 5, 2017

@rainersigwald MSBuild does, I am talking about when the user navigates to the property page to set this value. The property page should know this alternate format and not change it.

@srivatsn

This comment has been minimized.

Show comment
Hide comment
@srivatsn

srivatsn Jun 5, 2017

Contributor

I don't like unrolling the SDK attribute for the questions mentioned above - someone adds a prebuilevent and removes it, now we have to move it back to a Project SDK attribute but only if users haven't edit the csproj and add more stuff above\below the imports. The targets solution is better than the unrolling but it's more verbose and so I was just wondering if there was something better here (assuming we can change whatever we want in the core targets)

Contributor

srivatsn commented Jun 5, 2017

I don't like unrolling the SDK attribute for the questions mentioned above - someone adds a prebuilevent and removes it, now we have to move it back to a Project SDK attribute but only if users haven't edit the csproj and add more stuff above\below the imports. The targets solution is better than the unrolling but it's more verbose and so I was just wondering if there was something better here (assuming we can change whatever we want in the core targets)

jmarolf added some commits Jun 10, 2017

@rainersigwald

I'm unreasonably excited about this!

Show outdated Hide outdated ...ctProperties/AbstractBuildEventValueProvider.AbstractBuildEventHelper.cs
Show outdated Hide outdated ...ctProperties/AbstractBuildEventValueProvider.AbstractBuildEventHelper.cs
Show outdated Hide outdated ...ctProperties/AbstractBuildEventValueProvider.AbstractBuildEventHelper.cs
Show outdated Hide outdated ...ctProperties/AbstractBuildEventValueProvider.AbstractBuildEventHelper.cs
Show outdated Hide outdated ...operties/InterceptedProjectProperties/AbstractBuildEventValueProvider.cs
namespace Microsoft.VisualStudio.ProjectSystem.VS.Properties
{
[ProjectSystemTrait]
public class PostBuildEventValueProviderTests

This comment has been minimized.

@basoundr

basoundr Jun 12, 2017

Contributor

These are the tests for the helper, which we can keep them in a separate file and add some basic tests for the Provider itself. Constructor validations, Get & Set calling the helpers properly

@basoundr

basoundr Jun 12, 2017

Contributor

These are the tests for the helper, which we can keep them in a separate file and add some basic tests for the Provider itself. Constructor validations, Get & Set calling the helpers properly

This comment has been minimized.

@jmarolf

jmarolf Jun 12, 2017

Member

Unless I am missing something ProjectLockAwaitable is un-mockable being a struct. Can you point me to other classes that use ReadLockAsync() and GetProjectXmlAsync()?

@jmarolf

jmarolf Jun 12, 2017

Member

Unless I am missing something ProjectLockAwaitable is un-mockable being a struct. Can you point me to other classes that use ReadLockAsync() and GetProjectXmlAsync()?

Show outdated Hide outdated ...nitTests/ProjectSystem/VS/Properties/PostBuildEventValueProviderTests.cs
Show outdated Hide outdated ...nitTests/ProjectSystem/VS/Properties/PostBuildEventValueProviderTests.cs
namespace Microsoft.VisualStudio.ProjectSystem.VS.Properties
{
[ProjectSystemTrait]
public class PostBuildEventValueProviderTests

This comment has been minimized.

@basoundr

basoundr Jun 12, 2017

Contributor

We need some tests to show that we handle case insensitivity as well

@basoundr

basoundr Jun 12, 2017

Contributor

We need some tests to show that we handle case insensitivity as well

This comment has been minimized.

@jmarolf

jmarolf Jun 13, 2017

Member

done

@jmarolf
namespace Microsoft.VisualStudio.ProjectSystem.VS.Properties
{
[ProjectSystemTrait]
public class PreBuildEventValueProviderTests

This comment has been minimized.

@basoundr

basoundr Jun 12, 2017

Contributor

Since the tests are a mirror of the PostBuildEvent, all the comments, above, apply here as well

@basoundr

basoundr Jun 12, 2017

Contributor

Since the tests are a mirror of the PostBuildEvent, all the comments, above, apply here as well

jmarolf added some commits Jun 12, 2017

Merge remote-tracking branch 'origin/bugfix/Make-PreBuildEvents-PostB…
…uildEvents-work-using-targets' into bugfix/Make-PreBuildEvents-PostBuildEvents-work-using-targets
@jmarolf

This comment has been minimized.

Show comment
Hide comment
@jmarolf

jmarolf Jun 13, 2017

Member

@balajikris @basoundr can you do another review pass? thanks!

Member

jmarolf commented Jun 13, 2017

@balajikris @basoundr can you do another review pass? thanks!

@davkean

This comment has been minimized.

Show comment
Hide comment
@davkean

davkean Jun 13, 2017

Member

@jmarolf Can you summarize the new behavior?

Member

davkean commented Jun 13, 2017

@jmarolf Can you summarize the new behavior?

@jmarolf

This comment has been minimized.

Show comment
Hide comment
@jmarolf

jmarolf Jun 13, 2017

Member

@davkean here are the basics. I can write a more formal doc.

  1. If the user has PreBuildEvent or PostBuildEvent properties defined already in their project we use those properties and do not attempt to convert them. So this proj file is not modified even though the msbuild variable ProjectDir will not be correctly evaluated
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <PreBuildEvent>echo $(ProjectDir)</PreBuildEvent>
    <PostBuildEvent>echo $(ProjectDir)</PostBuildEvent>
  </PropertyGroup>
</Project>

image

  1. If the user has no PreBuildEvent or PostBuildEvent properties defined we will add a target to their project with an exec task when they modify the build events tab.

So this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
</Project>

Becomes this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>
  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>
</Project>

When the build events tab looks like this:
image

On a new project

  1. In the event that the user has a target defined in their project that does not match

This for prebuild

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
  <Exec Command="echo $(ProjectDir)" />
</Target>

Or this for postbuild:

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
  <Exec Command="echo $(ProjectDir)" />
</Target>

We will add a new target with a unique name (Post/Pre Build + N) whenever they edit the build events tab

  1. If the developer deletes their text from the build events tab we will remove the targets iff they match the rules for 3
Member

jmarolf commented Jun 13, 2017

@davkean here are the basics. I can write a more formal doc.

  1. If the user has PreBuildEvent or PostBuildEvent properties defined already in their project we use those properties and do not attempt to convert them. So this proj file is not modified even though the msbuild variable ProjectDir will not be correctly evaluated
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <PreBuildEvent>echo $(ProjectDir)</PreBuildEvent>
    <PostBuildEvent>echo $(ProjectDir)</PostBuildEvent>
  </PropertyGroup>
</Project>

image

  1. If the user has no PreBuildEvent or PostBuildEvent properties defined we will add a target to their project with an exec task when they modify the build events tab.

So this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
</Project>

Becomes this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>
  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <Exec Command="echo $(ProjectDir)" />
  </Target>
</Project>

When the build events tab looks like this:
image

On a new project

  1. In the event that the user has a target defined in their project that does not match

This for prebuild

<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
  <Exec Command="echo $(ProjectDir)" />
</Target>

Or this for postbuild:

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
  <Exec Command="echo $(ProjectDir)" />
</Target>

We will add a new target with a unique name (Post/Pre Build + N) whenever they edit the build events tab

  1. If the developer deletes their text from the build events tab we will remove the targets iff they match the rules for 3
@tannergooding

This comment has been minimized.

Show comment
Hide comment
@tannergooding

tannergooding Jun 13, 2017

Member

So, if you have two targets with the same 'BeforeTargets':

  <Target Name="PreBuild1" BeforeTargets="PreBuildEvent">
    <Exec Command="echo Hello" />
  </Target>

  <Target Name="PreBuild2" BeforeTargets="PreBuildEvent">
    <Exec Command="echo World" />
  </Target>

We will only display the first:
image

Modifying it (and saving) updates the first target:

  <Target Name="PreBuild1" BeforeTargets="PreBuildEvent">
    <Exec Command="echo Hello, " />
  </Target>

  <Target Name="PreBuild2" BeforeTargets="PreBuildEvent">
    <Exec Command="echo World" />
  </Target>

Fully deleting the text (and saving) causes the first target (PreBuild1) to be deleted from the csproj file.

  <Target Name="PreBuild2" BeforeTargets="PreBuildEvent">
    <Exec Command="echo World" />
  </Target>

Then, if you modify the text box again (and save), we now end up modifying the second target:

This results in us overwriting user targets (without them being aware that it switched the target is was matched against).

Member

tannergooding commented Jun 13, 2017

So, if you have two targets with the same 'BeforeTargets':

  <Target Name="PreBuild1" BeforeTargets="PreBuildEvent">
    <Exec Command="echo Hello" />
  </Target>

  <Target Name="PreBuild2" BeforeTargets="PreBuildEvent">
    <Exec Command="echo World" />
  </Target>

We will only display the first:
image

Modifying it (and saving) updates the first target:

  <Target Name="PreBuild1" BeforeTargets="PreBuildEvent">
    <Exec Command="echo Hello, " />
  </Target>

  <Target Name="PreBuild2" BeforeTargets="PreBuildEvent">
    <Exec Command="echo World" />
  </Target>

Fully deleting the text (and saving) causes the first target (PreBuild1) to be deleted from the csproj file.

  <Target Name="PreBuild2" BeforeTargets="PreBuildEvent">
    <Exec Command="echo World" />
  </Target>

Then, if you modify the text box again (and save), we now end up modifying the second target:

This results in us overwriting user targets (without them being aware that it switched the target is was matched against).

jmarolf added some commits Jun 16, 2017

responding to Sri's feedback
- removing target duplicate detection
- restricting target build name
@srivatsn

This comment has been minimized.

Show comment
Hide comment
@srivatsn

srivatsn Jun 19, 2017

Contributor

Tagging @MattGertz for Preview4

Contributor

srivatsn commented Jun 19, 2017

Tagging @MattGertz for Preview4

public static string SaveAndGetChanges(this ProjectRootElement root)
{
var tempFile = Path.GetTempFileName();

This comment has been minimized.

@vshapenko

vshapenko Jun 20, 2017

Why cant we simply use root.Save(TextWriter) here?

@vshapenko

vshapenko Jun 20, 2017

Why cant we simply use root.Save(TextWriter) here?

This comment has been minimized.

@jmarolf

jmarolf Jun 20, 2017

Member

@vshapenko saving to a string will cause a different code path to be used that writes out the file differently. I am writing to disk here because I want the same behavior in the unit tests and what MSBuild actually does. I can look for other ways around this and revisit this is a subsequent PR

@jmarolf

jmarolf Jun 20, 2017

Member

@vshapenko saving to a string will cause a different code path to be used that writes out the file differently. I am writing to disk here because I want the same behavior in the unit tests and what MSBuild actually does. I can look for other ways around this and revisit this is a subsequent PR

@srivatsn

This comment has been minimized.

Show comment
Hide comment
@srivatsn

srivatsn Jun 20, 2017

Contributor

@MattGertz tagging again in case you missed this - this is one of the top vsfeedback issues we've had.

Contributor

srivatsn commented Jun 20, 2017

@MattGertz tagging again in case you missed this - this is one of the top vsfeedback issues we've had.

@jmarolf jmarolf merged commit 682e420 into dotnet:master Jun 20, 2017

2 checks passed

Windows Debug Build finished.
Details
Windows Release Build finished.
Details

@jotatsu jotatsu referenced this pull request Dec 24, 2017

Closed

CONTRIBUTION: How to? #691

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment