Baguji
Baguji

Reputation: 45

MSBuild in Visual Studio - Moving files before including them as content (C#)

I have a C# project which has two C++ .dll versions with the same name but differ based on their architecture (32-bit or 64-bit), they are stored in separate folders and must have the same name, I want to include the right file with that name.

So the plan is to first check the current platform when building, copy the right file from the right folder (based on the platform) into the project directory and then include that file as content to the project so it can be used.

<ItemGroup>
    <Source32Bit Include="File_32bit\File.dll" />
    <Source64Bit Include="File_64bit\File.dll" />
</ItemGroup>

<Target Name="CopyFiles" BeforeTargets="Build" >
    <Copy SourceFiles="@(Source32Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x86' " />
    <Copy SourceFiles="@(Source64Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x64'" />     
</Target>

<ItemGroup>
    <Content Include="File.dll">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
</ItemGroup>

But if I run this then it tries to perform Content Include before the Target "CopyFiles" has run and so it cannot find the File.dll in that directory. If I make a target for this content include and try to do AfterTarget="CopyFiles" it complains about the CopyToOutputDirectory.

How should I handle this? Any ideas? Thanks!

Upvotes: 4

Views: 960

Answers (3)

Nathan Schubkegel
Nathan Schubkegel

Reputation: 905

This answer works with Visual Studio 2022.

If you want to use globs (**/*) to pick up generated files as <Content>, then define the fileset and <Content> twice - once at the project level, and once again inside the target that generates the files.

  • The first time the project builds, the project-level <Content> picks up zero files, but the target-level <Content> picks up produced files.
  • For later builds, both <Content> elements pick up the files, and that's harmless.
  <ItemGroup>
    <MyOutputFiles Include="where\output\was\generated\**"/>
    <Content Include="@(MyOutputFiles)" CopyToOutputDirectory="PreserveNewest" Link="specific\out\dir\%(RecursiveDir)%(Filename)%(Extension)"/>
  </ItemGroup>

  <Target Name="GenerateMyFilesPlz" BeforeTargets="BeforeBuild">
    <Exec Command="&quot;$(ProjectDir)..\TheTool\TheTool.exe&quot; some args" />
    <ItemGroup>
      <MyOutputFiles Include="where\output\was\generated\**"/>
      <Content Include="@(MyOutputFiles)" CopyToOutputDirectory="PreserveNewest" Link="specific\out\dir\%(RecursiveDir)%(Filename)%(Extension)"/>
    </ItemGroup>
  </Target>

This is particularly more desirable than FindInvalidProjectReferences because

  • it runs only when you build. (FindInvalidProjectReferences tends to be invoked by Visual Studio whenever it feels like it)
  • it makes reasonable error messages when it fails. (when BeforeTargets="FindInvalidProjectReferences" fails, you get a thousand errors like "I can't find Newtonsoft.Json!" because no project references were resolved.)
  • it works the same whether building via Visual Studio or via dotnet build on the command line. (I've had mixed results with FindInvalidProjectReferences)
  • it allows the use of globbing (**/*) to define the fileset

Upvotes: 1

Mr Qian
Mr Qian

Reputation: 23760

But if I run this then it tries to perform Content Include before the Target "CopyFiles" has run and so it cannot find the File.dll in that directory. If I make a target for this content include and try to do AfterTarget="CopyFiles" it complains about the CopyToOutputDirectory.

The main reason is that Build target executes later than MSBuild to read the Item elements, so when you perform the copy operation, it is already later than reading the Item elements, so the project cannot find the copied file. Therefore, you only need to execute the CopyFiles target before reading the Item elements.

So I found a system target called FindInvalidProjectReferences which executes earlier than reading Item elements.

Solution

Try these:

<ItemGroup>
    <Source32Bit Include="File_32bit\File.dll" />
    <Source64Bit Include="File_64bit\File.dll" />
  </ItemGroup>
  <Target Name="CopyFiles" BeforeTargets="FindInvalidProjectReferences">
    <Copy SourceFiles="@(Source32Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x86' " />
    <Copy SourceFiles="@(Source64Bit)" DestinationFolder="$(ProjectDir)" Condition=" '$(Platform)' == 'x64'" />

  </Target>

    <ItemGroup>
        <Content Include="File.dll">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </Content>
    </ItemGroup>

Note: when you first load the project, the test.dll has a yellow triangle which is a normal behavior because you did not build your project. And it needs the build process first.

You should build your project first and it will not turn out any errors and when the test.dll in Solution Explorer will not have the warning symbol.

Upvotes: 1

Sim&#243;n
Sim&#243;n

Reputation: 455

Can you rename the path in which the DLL files reside? If instead of naming those folders File_32bit and File_64bit, you can name them File_x86 and File_x64. Then you could have ItemGroup:

<ItemGroup>
  <Source32Bit Include="File_$(Platform)\File.dll" />
  <Source64Bit Include="File_$(Platform)\File.dll" />
</ItemGroup>

Note: I don't really know if this works.

Upvotes: 0

Related Questions