Home: Make it easier to create tool packages that need publish

Created on 15 Dec 2017  Â·  16Comments  Â·  Source: NuGet/Home

If you're writing a tool package that has a .NET Core App tool, the build output's aren't what goes in to the tools\netcoreapp2.0 directory. The output of the publish step is needed instead.

Please make it easier for people to "do the right thing" and have pack call publish on each TFM that needs it and include the correct output files in the package.

/cc @rohit21agrawal @natemcmaster

Pack Icebox Feature

Most helpful comment

@onovotny

I have some luck call publish. Import the following at the end of the csproj

<Project>
  <PropertyGroup>
    <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificBuildOutput);GetMyPackageFiles</TargetsForTfmSpecificContentInPackage>
  </PropertyGroup>
  <Target Name="GetMyPackageFiles" DependsOnTargets="Publish">
    <ItemGroup>
      <TfmSpecificPackageFiles Include="$(PublishDir)/**/*.*"/>
    </ItemGroup>
    <!--All published file in different tools/tfm-->
    <ItemGroup>
      <TfmSpecificPackageFile Include="@(TfmSpecificPackageFiles)">
        <PackagePath>tools/$(targetframework)/%(TfmSpecificPackageFiles.RecursiveDir)%(TfmSpecificPackageFiles.Filename)%(TfmSpecificPackageFiles.Extension)</PackagePath>
      </TfmSpecificPackageFile>
    </ItemGroup>
  </Target>
</Project>

All 16 comments

@wli3 is working on a similar scenario... any thoughts William?

@onovotny when you say “tool package” are you talking about a DotNetCliToolRef or a “global” cli tool? Or something different? I’m assuming you’re asking about the global cli tool scenario.

I'm talking about any package that needs an exe that could be called from a targets file. Or an MSBuild task, which also needs to be published to have all of its references in the right directory.

A "global" CLI tool fits this criteria too.

Look how nasty it is to get an MSBuild task included in a NuGet package today:

https://github.com/paulcbetts/refit/blob/58a333f694b24eaf282f6d06c415f1b64718dbaf/Refit/Refit.csproj#L28-L50

+1 for how nasty it is, but I think this is basically the same conversation we've had on https://github.com/NuGet/Home/issues/5063.

I gave up fighting MSBuild and now I almost always write the nuspec file explicitly, setting <NuspecProperties> to flow variables from MSBuild to the nuspec. It's a little extra glue code, but far more sane than remembering to extend targets with API like "TargetsForTfmSpecificContentInPackage". The nuspec generation targets aren't flexible enough, and the MSBuild item groups and metadata are obscure/poorly named, but writing nuspec is pretty straightforward IMO.

<!-- *.csproj -->
  <PropertyGroup>
    <NoPackageAnalysis>true</NoPackageAnalysis>
    <NuspecFile>$(MSBuildThisFileDirectory)$(MSbuildProjectName).nuspec</NuspecFile>
    <IntermediatePackDir>$(MSBuildThisFileDirectory)bin\$(Configuration)\publish\</IntermediatePackDir>
    <PublishDir>$(IntermediatePackDir)$(TargetFramework)\</PublishDir>
  </PropertyGroup>

  <Target Name="PublishAll">
    <ItemGroup>
      <_TargetFramework Include="$(TargetFrameworks)" />
    </ItemGroup>
    <MSBuild Projects="$(MSBuildProjectFullPath)" Targets="Publish" Properties="TargetFramework=%(_TargetFramework.Identity)" />
  </Target>

  <Target Name="SetPackageDependencies" BeforeTargets="GenerateNuspec" DependsOnTargets="PublishAll">
    <PropertyGroup>
      <NuspecProperties>$(NuspecProperties);id=$(PackageId)</NuspecProperties>
      <NuspecProperties>$(NuspecProperties);publishDir=$(IntermediatePackDir)</NuspecProperties>
      <NuspecProperties>$(NuspecProperties);description=$(Description)</NuspecProperties>
      <NuspecProperties>$(NuspecProperties);version=$(PackageVersion)</NuspecProperties>
    </PropertyGroup>
  </Target>
<!-- *.nuspec -->
<package>
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <authors>Microsoft</authors>
    <description>$description$</description>
  </metadata>
  <files>
    <file src="$publishDir$\net46\**" target="tools\net46\" />
    <file src="$publishDir$\netcoreapp2.1\**" target="tools\netcoreapp2.1\" />
  </files>
</package>

https://github.com/aspnet/BuildTools/blob/63671122d77fccdc68c02b417cd4e534541ce587/src/ApiCheck.Console/Microsoft.AspNetCore.BuildTools.ApiCheck.nuspec#L14-L15
https://github.com/aspnet/BuildTools/blob/63671122d77fccdc68c02b417cd4e534541ce587/src/ApiCheck.Console/ApiCheck.Console.csproj#L30-L44

@natemcmaster do you think having the nuspec file is easier than simply using None include's like this"

https://github.com/paulcbetts/refit/blob/58a333f694b24eaf282f6d06c415f1b64718dbaf/Refit/Refit.csproj#L28-L33

I may be a matter of taste, but yes, I prefer nuspec to MSBuild. It may also be a matter of how many times I've been burned by the pack tasks generating a package layout I didn't expect.

@nkolev92 Are we tracking this already?

@mishra14
If I understand the scenario correctly, this isn't a really a global tools scenario only, rather a more generic one.
Thanks for pointing it out though.
//cc
@wli3

Well, I don't know about that. I have a utility that needs the output of publish and not build. It multi-targets and thus needs publish called on both netcoreapp1.1 and netcoreapp2.0 and the output of that in the package. It also should not have any dependencies in that scenario as they've been included in the package.

I have to use a nuspec today for this.

@onovotny can you not include Publish in the TargetsForTfmSpecificBuildOutput property and change your BuildOutputTargetFolder to tools?

This will leave the dependencies still being listed in the nuspec, which I am currently working on a plan to turn on/off - both for framework assembly references and package dependencies

@rohit21agrawal Part of the problem is that pack calls build not publish, and there's no way to control that either. I'd like there to be an option to have pack call publish on each inner TFM instead and then use those outputs (presumably there's a publish output group?)

@onovotny

I have some luck call publish. Import the following at the end of the csproj

<Project>
  <PropertyGroup>
    <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificBuildOutput);GetMyPackageFiles</TargetsForTfmSpecificContentInPackage>
  </PropertyGroup>
  <Target Name="GetMyPackageFiles" DependsOnTargets="Publish">
    <ItemGroup>
      <TfmSpecificPackageFiles Include="$(PublishDir)/**/*.*"/>
    </ItemGroup>
    <!--All published file in different tools/tfm-->
    <ItemGroup>
      <TfmSpecificPackageFile Include="@(TfmSpecificPackageFiles)">
        <PackagePath>tools/$(targetframework)/%(TfmSpecificPackageFiles.RecursiveDir)%(TfmSpecificPackageFiles.Filename)%(TfmSpecificPackageFiles.Extension)</PackagePath>
      </TfmSpecificPackageFile>
    </ItemGroup>
  </Target>
</Project>

@wli3 nice! I'll check that out closer tomorrow, but it won't matter until @rohit21agrawal's work of disabling dependencies is ready too. If that works, is that something that can be just part of the built-in targets and controlled via an option (in the spirit of making it easier all around)?

... but it won't matter until @rohit21agrawal's work of disabling dependencies is ready too.

Any pointer to where I can find out what that's about, and how it's progressing?

I'm just about to design the build-integration of a new tool our ours, that we want to make available as a package, and that has the trait that it should be executed on build. I just recently reached the conclusion that standard packaging won't work, and that I must turn to publishing first. Hence, I found my way here (also via http://www.natemcmaster.com/blog/2017/11/11/build-tools-in-nuget/).

Is there some work going on in this area that I should consider, or should I just throw myself into the (probably bumpy) ride of doing it like it's suggested in referenced blog, possibly in some combo with more recent hints in this thread?

Any feedback is highly appreciated!

Edit: And oh, @natemcmaster, thanks for that article - really helped out getting into the topic!

I'm not on the NuGet team, so I can't tell you if they have plans to make it easier to work with the pack targets. As others have pointed out on this thread, getting the most current version of dotnet pack to bundle a published app requires jumping through some non-trivial MSBuild hoops...that is if you want to completely define package layout in the csproj. I recommend using a nuspec file instead. I find it's more straightforward and has fewer gotchas.

Was this page helpful?
0 / 5 - 0 ratings