abenci
abenci

Reputation: 8651

Can I specify the output path for the MSBuild <Content> tag?

Is it possible to specify a different folder for the output of the following file?

<Content Include="test.stl">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Upvotes: 27

Views: 15026

Answers (3)

laolu
laolu

Reputation: 51

A different output directory for a Content item can be specified with its TargetPath metadata. The value for the metadata should be relative to the configuration and/or platform-specific output directory.

For the following MSBuild snippet, the file will be copied to a directory one up from the project OutputPath.

<Content Include="test.stl">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <TargetPath>..\test.stl</TargetPath>
</Content>

References

Upvotes: 5

Beeeaaar
Beeeaaar

Reputation: 1035

You can, but not with 'Content'. It depends on the item task, but most of the built-in ones you could hack in, arnt worth the trouble or side-effects.

There is a basic well worn path for dealing with this :) This also avoids the nasty PostBuild cmd shell way if you are doing .Net, and uses the actuall build process.

I didnt see any other answers like this, using strieght up MSBuild, where I think the heart of the OPs question is. This is the core concept, and the shortest path, baring finding an item type that has a 'relative to outputpath' path parameter with no side effects.

  1. Post Process Style:

    ... ...

Then at the bottom (using whatever paths you are after):

<PropertyGroup>
  <MyDeployDir>$(SolutionDir)$(Configuration)</MyDeployDir>
  <MyOtherDeployDir>$(SolutionDir)$(Configuration)\Templates</MyDeployDir>
</PropertGroup>

Then your existing MS build includes (dont add this, is here as a marker):

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

Then the 'after build':

<Target Name="AfterBuild">
  <Copy SourceFiles="@(MyTargets)" DestinationFolder="$(MyDeployDir)" SkipUnchangedFiles="true" />
  <Copy SourceFiles="@(MyOtherTargets)" DestinationFolder="$(MyOtherDeployDir)" SkipUnchangedFiles="true" />
  <Copy SourceFiles="@(MyTargets2)" DestinationFolder="$(MyDeployDir)\IHeardYourMomLikesGrapeNuts" SkipUnchangedFiles="true" />
</Target>

The fundemental issue is that project items dont do anything by default, they have a type like 'Content' or 'MyTarget'. Its those types that say what will happen. You might be able to find a task, or type, or build script include that has what you want, but there is nothing intrinsic about a item in an itemgroup as far as what will happen with that file during build. The above is a balance between power of a specially built 'task' but with out all trouble.

Once you add

 <ItemGroup>
    <MyOutFiles Include="xxx.xxx" />

one time to the project file, it will then appear in the BuildAction list for any file, where you can set on any file without having to edit the proj file manually.


  1. In one step

In later versions of MSBuild you can embed an 'ItemGroup' inside the 'AfterBuild' target and do the above or do other fancy things without touching the rest of the file. This allows for instance grabbing the resultof the build using a simple Include and displacing it somewhere else. This is all without doing RoboCopy anything or resorting to the more complicated build target function processing.

<Target Name="AfterBuild">
  <ItemGroup>
    <MyOutFiles Include="$(OutDir)*.*" />
  </ItemGroup>
  <Copy SourceFiles="@(MyOutFiles)" DestinationFolder="$(SolutionDir)\Application" SkipUnchangedFiles="true" />

Edit (per question in the comments):

To disambiguate the possible methods and to reiterate, this method does not use MSBuild 'functions' or alternate tasks like 'RoboCopy' but was meant to show a more pure MSBuild style using core functionality like one would use in making item tasks like 'Content' itself.

The original question was can I specify a 'different folder for the following file' and can I do this for the content tag. You can reroute all of a BuildAction using MSBuild functions, however I don't believe that was the question.

You can do this is one step as shown above, so I don't think this is any more complicated and believe it easier to read. Below is the short form, and lets him create his own BuildAction that can be handled anyway he wants. So no, you cant tell 'Content' to pick another folder for a particular file marked as 'Content', but you can make another build action that does fairly easily. You could also inject meta info into the StlFiles tag that directs the action sothat you could set it on the tag itself or have the StlFiles hook earlier in the process like content would, but that's more complicated.

<StlFiles Include="test.stl" />         
...
<Target Name="AfterBuild">
  <Copy SourceFiles="@(StlFiles)" DestinationFolder="$(SolutionDir)\Release\MyTypes" SkipUnchangedFiles="true" />

Upvotes: 8

Martin Ullrich
Martin Ullrich

Reputation: 100581

A different output folder can be specified by using the Link metadata on None/Content items:

<Content Include="test.stl">
  <Link>some\folder\%(Filename)%(Extension)</Link>
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

When using wildcards in an include statement, this is also the way to preserve the directory hierarchy, even for files coming from outside the project directory:

<Content Include="..\shared\**\*">
  <Link>some\folder\%(RecursiveDir)%(Filename)%(Extension)</Link>
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

In SDK-based projects (default for ASP.NET Core / .NET Core / .NET Standard projects) using a 2.0.0+ SDK, the same can be achieved using the LinkBase metadata:

<Content Include="..\shared\**\*" LinkBase="some\folder" CopyToOutputDirectory="PreserveNewest" />

Upvotes: 60

Related Questions