4

I've spent a few days on this, and finally found a solution that seems to work. However, it seems a bit brittle or perhaps ill-advised.

I want all non-system referenced assemblies (current project libraries used are NuGet managed) included in a "libs" subfolder of the build output.

The following snippet included in the bottom of my csproj file does this (built from googling and a couple of questions I've found here.)

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets. -->
  <Target Name="BeforeBuild">
    <Message Text="### Removing existing 'lib' folder" Importance="high" />
    <RemoveDir Directories="$(TargetDir)libs" />
  </Target>
  <Target Name="MoveLibrariesOnBuild" AfterTargets="Build">
    <ItemGroup>
        <Libraries Include="%(Reference.HintPath)" >
           <MissingHintPath>$([MSBuild]::ValueOrDefault('%(Reference.HintPath)', '').Equals(''))</MissingHintPath>         
        </Libraries>
    </ItemGroup>
    <Message Text="### Moving libraries to 'libs' ###" Importance="high" />
    <Copy SourceFiles="@(Libraries)" 
            DestinationFiles="$(TargetDir)libs\%(Filename).dll" 
            Condition="%(Libraries.MissingHintPath) == False" 
            SkipUnchangedFiles="false" 
            OverwriteReadOnlyFiles="true" />
  </Target>
</Project>

I tried using Reference.Identity and Reference.Filename but Filename included the version, culture, etc info as well (so it wouldn't be properly renamed, unless I tried messing around with an inline string replace.)

Your thoughts? Is there an easier way, am I over thinking this?

I build a list based on the References, and use the HintPath (only exists for non-standard assemblies) as a flag as to which I should include. Then, in my MoveLibrariesOnBuild target the MissingHintPath is used as a condition flag.

I've avoided using CopyLocal (which generates a True property for references) as per recommendations I've found. For example, What is the best practice for "Copy Local" and with project references?

I saw other people asking similar questions, but none seemed answered or complete. Perhaps, with some critique from the community, this can become a useful resource.

PS: I also tried using project Build Events using xcopy and robocopy but those proved even less reliable (and difficult to debug.)

Additionally, this relies upon setting the probing path as described here, C#: Custom assembly directory

PPS: If you have "file in use" problems when removing/overwritting the files, you may need to uncheck "Enable the Visual Studio hosting process", as described here

Community
  • 1
  • 1
t.j.
  • 1,227
  • 3
  • 16
  • 30
  • 1
    Programmers can be obsessive about organizing their projects. This is actively harmful, the CLR does *not* like you to do this. Having to rely on a .config file just adds a couple of extra failure modes and cold start delay with no conceivable benefit. – Hans Passant Jun 21 '14 at 19:06
  • The benefit is to the stakeholders as required, there is concern that users will find it too troublesome to wade through a mess of files and would rather just as few as possible for them to deal with. I just couldnt find a good way of doing this documented anywhere, and thought others may find it useful too (perhaps once corrected if there is a better way.) But, perhaps to your point, the reason there wasn't much in the way of documentation around how to do this because it isn't advised. – t.j. Jun 23 '14 at 19:47

1 Answers1

13

This is an old question, but somewhere along the way MSBuild introduced a seemingly-undocumented "DestinationSubDirectory" metadata option for project references. I only know this because I've spent a lot of time picking apart Microsoft.Common.targets itself to see how it works.

Use as such:

  <ItemGroup>
    <ProjectReference Include="..\Utilities\Company.Project.Utilities.csproj">
      <Project>{ea7de0b6-aaba-42e7-97ae-bb33877db2a4}</Project>
      <Name>Company.Project.Utilities</Name>
      <DestinationSubDirectory>libs\</DestinationSubDirectory>
    </ProjectReference>
  </ItemGroup>

Your dll will be copied to a "libs" subfolder located within your project's output directory.

bwerks
  • 8,651
  • 14
  • 68
  • 100
  • Does this work with `PackageReference`? Or only with `ProjectReference`? – Zev Spitz May 25 '20 at 11:32
  • @ZevSpitz Not sure! My projects are not supported for use with PackageReference so I can't try this out, but if you're using them already it's a pretty straightforward to edit the MSBuild to add the DestinationSubDirectory child element. I'd be curious to hear if it works! – bwerks May 26 '20 at 22:45
  • @bwerks - I've tried this and did not work for me. Please see my question here: https://stackoverflow.com/questions/63986659/dotnet-core-copy-project-reference-output-to-separate-folder-in-the-output-dir – nari447 Sep 21 '20 at 05:50
  • Is there a way to also include the resource files (like appsettings.json) in the DestinationSubDirectory? – Stijn Van Antwerpen May 11 '22 at 08:05