dotnet / msbuild

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.
https://docs.microsoft.com/visualstudio/msbuild/msbuild
MIT License
5.22k stars 1.35k forks source link

Wildcards do not match paths with .. in Remove at execution time #1983

Closed jaredpar closed 7 years ago

jaredpar commented 7 years ago

An xunit NuGet props file is injecting a Content item into our build via this mechanism:

<ItemGroup>
  <Content Include="$(MSBuildThisFileDirectory)..\_common\xunit.abstractions.dll">
    <Link>xunit.abstractions.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <Visible>False</Visible>
  </Content>
</ItemGroup>

This is causing our build to fail because the xunit.abstractions dll is getting written to the output directory twice: once from an explicit NuGet reference, the other through this Content item. As such I'd like to remove this Content item to unblock our build.

I feel like this should be easy to do using a Remove ItemGroup operation:

<Target Name="RemoveDuplicateXUnitContent">
  <ItemGroup>
    <Content Remove="**\xunit.abstractions.dll" />
  </ItemGroup>
</Target>

Unfortunately this isn't working. The Target is executing and at a point in which the content is present yet it is not removed. I've verified by digging through diagnostic logs and adding Message tasks before / after.

The problem appears to be the wild card. If I substitute in the actual full path then the Remove works:

  <Content Remove="e:\nuget\xunit.runner.visualstudio\2.2.0-beta4-build1194\build\net20\..\_common\xunit.abstractions.dll" />

What is going on here? The wild card seems pretty straight forward yet I can't get it to match the injected Content item. Tried a number of variations here and none seem to work:

  <Content Remove="e:\nuget\**\xunit.abstractions.dll" />
  <Content Remove="e:\nuget\xunit.runner.visualstudio\2.2.0-beta4-build1194\**\xunit.abstractions.dll" />
jaredpar commented 7 years ago

Ping

rainersigwald commented 7 years ago

Inside a target, the Remove operation expands its wildcard list and removes the individual found items from the list. Since the Identity of the included item includes ..\, it will never match a filesystem expansion, which will have bare identities.

You can remove this item by filtering the list on filename:

<Content Remove="@(Content)" Condition="'%(Filename)%(Extension)' == 'xunit.abstractions.dll'" />

@cdmihai I remember a bunch of . and .. stuff around glob matching coming up when working on lazy item expansion, but I can't dig it up. Is this handled differently in an eval-time remove?

cdmihai commented 7 years ago

Yes, eval time removes behave differently (:cry:) and try to do path matching. However, we also made them project cone aware to satisfy CPS, so <Content Remove="**\xunit.abstractions.dll" /> would still not catch $(MSBuildThisFileDirectory)..\_common\xunit.abstractions.dll because it's outside the project file cone. The specified remove glob is relative to the project file, so it will only match xunit.abstractions.dll files under $(MSBuildThisFileDirectory)

Patterns that should work on eval time removes:

<Content Remove="e:\nuget\**\xunit.abstractions.dll" /> <Content Remove="..\**\xunit.abstractions.dll" />

rainersigwald commented 7 years ago

Drat, if it works at eval time it should also work at exec time. Changed the title here to track that.

cdmihai commented 7 years ago

1122 tracks it as part of #1124

jaredpar commented 7 years ago

Thanks @rainersigwald. That appears to be the solution I was looking for. I'm fine with this being closed out unless you all need this to track something else.

rainersigwald commented 7 years ago

Closing this as a dupe of #1122.

jaredpar commented 7 years ago

As we've played with this as a solution we've found a particular problem. When re-building from Visual Studio the solution suggested here will actually cause the DLL to be deleted from the output directory. This doesn't reproduce 100% of the time but does repro fairly regularly. The setup seems to be:

  1. Build from a clean state
  2. Build a specific project, which would execute the <Content Remove... /> target

The second or third time we do this, the xunit.abstractions.dll file will dissapear from our output directory. No other output is affected by this.

Hard to get logs for this because it's from Visual Studio, not the command line. Can take steps to grab the logs. But before I embarked down that road I wanted to see if this behavior was somehow by unfortunate design.

cdmihai commented 7 years ago

Doesn't sound like unfortunate design, more like unfortunate accident. Does this repro from the command line as well?

jaredpar commented 7 years ago

Have not seen it repro from the command line. But we also haven't tried that at all. It most often gets hit when we're in VS and

  1. fix a bug
  2. right click to run tests. That in turn builds the project and then fires up xunit

I will try a bit from the command line today and see if I can get it to repro

jeffkl commented 7 years ago

Sounds like https://github.com/Microsoft/msbuild/issues/1054 to me.

jaredpar commented 7 years ago

Good call @jeffkl. The discussion in that bug are exactly what we are seeing.

Looks like I could possibly work around this by adding the following condition to my target: $(BuildProjectReferences)' != 'false'. Sound right?

jeffkl commented 7 years ago

That might work. When I investigated, the issue appears to be that we don't get all outputs when building inside Visual Studio.

https://github.com/Microsoft/msbuild/blob/c6e1a3619db2cebc4ab268df1c093e631ae6e391/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1674 vs https://github.com/Microsoft/msbuild/blob/c6e1a3619db2cebc4ab268df1c093e631ae6e391/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1718

Since different targets run, different outputs are collected, and incremental clean deletes Content items.