Search code examples
c#msbuildnuget-packagebond

Compile .bond files of projects using my NuGet package


I have a NuGet package with a .bond file. Users of my package can derive their Bond structs from the structs in my package's .bond file.

I want the user's Bond files to be compiled when they include my NuGet package. Today they must include my NuGet and the Bond.CSharp NuGet. But, my NuGet already has a reference to Bond.CSharp.

How can I author my package so that the consumers do not need to have their own <PackageReference Include="Bond.CSharp" ... />?


Solution

  • Bond codegen is run from the Bond.CSharp's build targets.

    By default, the build targets of packages you consume do not flow to your consumers. The default value of a PackageReference's PrivateAssets is "contentfiles;analyzers;build".

    You can override this behavior in your csproj's PackageReference:

    <ItemGroup>
      <PackageReference Include="Bond.CSharp" Version="[9.0.3]">
        <!-- removing "build" from default so that consumers also run codegen -->
        <PrivateAssets>contentfiles;analyzers</PrivateAssets>
      </PackageReference>
    </ItemGroup>
    

    I assume you are compiling the base struct into an assembly in your package. Bond codegen assumes that the generated code and the runtime library exactly match, so I've used an exact match version bound in the PackageReference: [9.0.3]

    You said that you want your consumers to be able to derive from your Bond structs, so you'll probably also want to configure their BondImportPath to include the .bond file inside your package. To do this, you need to

    1. make sure the .bond files are included in the package and
    2. add a package .props file to set the BondImportPath to the package directory with said .bond files.

    The make sure the .bond files are included in the package, add something like this to your package's .csproj file:

    <ItemGroup>
      <None Include="bond/**">
        <Pack>true</Pack>
        <PackagePath>build/bond/</PackagePath>
      </None>
    </ItemGroup>
    

    This assumes that your .bond files live in a bond\ subdirectory.

    To automatically add something to BondImportPath, you need to add a package .props file that will be automatically imported by consumers. Create a file named ExactNameOfPackage.props with the following content:

    <Project>
      <ItemGroup>
        <BondImportDirectory Include="$(MSBuildThisFileDirectory)bond" />
      </ItemGroup>
    </Project>
    

    This .props file also needs to be packed. Add this to your project's .csproj file:

    <ItemGroup>
      <None Include="ExactNameOfPackage.props">
        <Pack>true</Pack>
        <PackagePath>build/</PackagePath>
      </None>
    </ItemGroup>
    

    Now, the consumer can just use a PackageReference. Any .bond files in their project will be compiled automatically, and they can use import "you-file.bond" to refer to a .bond file in your package.

    Build assets do not flow transitively. The NuGet 5+ buildTransitive feature looks like it solves this, but I haven't experimented with it.


    Here are the complete project files I used. The complete code is in my GitHub repository, export-bond-file-nuget.

    lib-with-bond.csproj

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
        <PackageId>LibWithBond</PackageId>
        <Version>1.2.0</Version>
      </PropertyGroup>
    
      <ItemGroup>
        <None Include="LibWithBond.props">
          <Pack>true</Pack>
          <PackagePath>build/</PackagePath>
        </None>
        <None Include="bond/**">
          <Pack>true</Pack>
          <PackagePath>build/bond/</PackagePath>
        </None>
      </ItemGroup>
    
      <ItemGroup>
        <PackageReference Include="Bond.CSharp" Version="[9.0.3]">
          <PrivateAssets>contentfiles;analyzers</PrivateAssets>
        </PackageReference>
      </ItemGroup>
    </Project>
    

    LibWithBond.props

    <Project>
      <ItemGroup>
        <BondImportDirectory Include="$(MSBuildThisFileDirectory)bond" />
      </ItemGroup>
    </Project>
    

    consume-lib.csproj

    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <!-- I had a local NuGet source pointing to the output of running
        `dotnet pack` on lib-with-bond.csproj -->
        <PackageReference Include="LibWithBond" Version="1.2.0" />
      </ItemGroup>
    </Project>
    

    I was able to make this work for a PackageReference. My initial experiments making it work for ProjectReference were not successful, and I ran out of time to work more on this answer.