Search code examples
c#.netmsbuildnuget.net-standard

Copy files from Nuget package to output directory with MsBuild in .csproj and dotnet pack command


Last time I had to find out how to extract some files from a Nuget package in took me at least 6 months but I finally managed to find the solution.

The thing is that, this solution assumes I have a .nupkg file and manually add a .targets file to perform the extraction process.

Now, things are different:

  1. I don't have any .nupgk file, we generate one automatically on our VSTS server using the dotnet pack command. Then we consume the package from our Nuget server
  2. We can't afford to take another 6 months to find the solution

Here is my ProjectName.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
        <Authors>Jérôme MEVEL</Authors>
        <Version>1.0.3</Version>
        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>

        <!-- This answer got many votes on a Github thread so I tried just in case -->
        <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

        <!-- Just trying that too-->
        <IncludeBuildOutput>true</IncludeBuildOutput>
        <IncludeContentInPack>true</IncludeContentInPack>

        <!-- I wanted to see the full generated Nuget package -->
        <IncludeSource>true</IncludeSource>

        <!-- desperate attempt -->     
        <TargetsForTfmSpecificBuildOutput>GetMyPackageFiles</TargetsForTfmSpecificBuildOutput>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Dapper" Version="1.50.5" />
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
        <PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
        <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
        <PackageReference Include="NLog" Version="4.5.8">

            <!-- Let's try this too just in case-->
            <IncludeAssets>all</IncludeAssets>
        </PackageReference>
        <PackageReference Include="NLog.Extensions.Logging" Version="1.2.1">
            <IncludeAssets>all</IncludeAssets>
        </PackageReference>
        <PackageReference Include="NLog.Web.AspNetCore" Version="4.6.0">
            <IncludeAssets>all</IncludeAssets>
        </PackageReference>
        <PackageReference Include="System.Data.Common" Version="4.3.0" />
        <PackageReference Include="System.Data.SqlClient" Version="4.5.1" />
    </ItemGroup>
    <ItemGroup>

        <!-- Added the file to the root folder in case <IncludeAssets>all</IncludeAssets> is looking there -->
        <Content Include="NLog.config">
            <Pack>true</Pack>
            <PackagePath>NLog;;</PackagePath>
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </Content>

        <!-- My desired path to look for the file -->
        <Content Include="NLog\NLog.config">
          <Pack>true</Pack>
          <PackagePath>NLog;;</PackagePath>
          <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </Content>
    </ItemGroup>

    <!-- This is the default setting, perfectly working when I reference the project instead of installing the Nuget package -->
    <ItemGroup>
        <None Update="NLog\NLog.config">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </None>
    </ItemGroup>

    <!-- desperate attempt -->
    <Target Name="GetMyPackageFiles">
        <ItemGroup>
            <BuildOutputInPackage Include="NLog/Nlog.config">
                <TargetPath>NLog</TargetPath>
            </BuildOutputInPackage>
        </ItemGroup>
    </Target>
</Project>

As you can see I tried several different settings. This MsBuild results in a NLog.config file included in a NLog folder at the root of the Nuget package file.

enter image description here

During my different tries, depending of the configuration I set, I was able to end-up with the NLog.config file at src/ProjectName.Logging/NLog/NLog.config or even at lib/netstandard2.0/Nlog.config.

So my file is definitely included in my Nuget package file but isn't copied in the output directory of the project that consumes the package.

I tried to specify a .nuspec file when generating my package with dotnet pack like described here but I was never able to get a desired result (either only my NLog.config was included in the Nuget package or all the source files). Moreover, this has several downsides like overriding the configuration in the .csproj file or adding useless complexity. I believe what I want to achieve could be done without using a .nuspec file (maybe I'm wrong).

I noticed the build/ProjectName.targets file is missing in my package and this is probably the missing piece. So how to add this .targets file without manually modifying the package?

Is there another way to copy my config file out of the Nuget package to the output directory?

I really hope someone could help me solve this issue. This is the 2nd time I want to perform the same operation but with a slight difference and once again this is hard to do.

Thanks a lot


Solution

  • It is possible to copy files without the .nuspec file, if you create your nuget from the .csproj file as described here.

    And to copy files from nuget to output directory, create a ProjectName.targets file with the following content:

    <Project>
      <ItemGroup>
        <LogFiles Include="$(MSBuildThisFileDirectory)\..\contentFiles\LogFiles\*.config" />
      </ItemGroup>
      <Target Name="CopyLogFiles" BeforeTargets="Build">
        <Copy SourceFiles="@(LogFiles)" DestinationFolder="$(TargetDir)CopiedLogFiles\" />
      </Target>
    </Project>
    

    In your .csproj file add:

    <ItemGroup Label="FilesToCopy">
       <Content Include="ProjectName.targets" PackagePath="build/ProjectName.targets" />
       <Content Include="LogFiles\*.config" Pack="true" PackagePath="contentFiles\LogFiles">
         <PackageCopyToOutput>true</PackageCopyToOutput>
       </Content>
    </ItemGroup>
    

    The paths and names can of course be freely chosen.

    This should copy all .config files to a folder called CopiedLogFiles in the output directory!