DEV Community

Ville M. Vainio
Ville M. Vainio

Posted on

Notes from a local NuGet package development workflow

Note: this is not a production-proven workflow, take it as lab notes I wrote while I still remembered how it works.

When developing a NuGet package, you easily end up with situation where you depend on another package published from the same (mono)repository.

Example:

  • I have FastExpressionKit targeting netstandard2.0
  • I have FastExpressionKit.BulkInsert targeting net472 (because netstandard2.0 is missing System.ComponentModel.DataAnnotations)
  • I have FastExpressionKit.Test that runs tests against both of the libraries.

FastExpressionKit.Test needs to use ProjectReference instead of PackageReference, because you couldn't run the test suite against unpublished packages. You also want a fast edit/run inner development loop.

I solved this dilemma by doing the following for each csproj:

$ dotnet pack -c Release /p:Version=1.1.0 /p:PubVer=1.1.0

Here, PubVer is the toggle that switches on PackageReference. Excerpt from FastExpressionKit.BulkInsert.csproj:

  <ItemGroup Condition="$(PubVer) == ''">
    <ProjectReference Include="..\FastExpressionKit\FastExpressionKit.csproj" />
  </ItemGroup>

  <ItemGroup Condition="$(PubVer) != ''">
    <PackageReference Include="FastExpressionKit" Version="$(PubVer)" />
  </ItemGroup>

When running tests, PubVer is not defined and ProjectReference is activated. That wasn't so bad, especially since you can slap the Condition on ItemGroup instead of reference itself.

Local feeds

In order to create a package for FastExpressionKit.BulkInsert, it needs access to FastExpressionKit package. For a version you don't want to publish yet. Hence we publish it to local feed immediately after dotnet pack:

$ dotnet nuget push -s c:\localfeed .\bin\Release\FastExpressionKit.1.2.0.nupkg

In order to actually use this local repository, you have to create file called NuGet.Config (case sensitive!) with content (adjust relative path accordingly):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="local" value="..\..\localfeed" />
  </packageSources>
</configuration>

When this is in place, PackageReference will find the FastExpressionKit package from that local directory.

Why do it like this???

  • NuGet, unlike pip or npm, doesn't have built-in support for this workflow so tricks are needed
  • Paket.local doesn't support SDK-style project files
  • For convenience, the version of all NuGet packages in the FastExpressionKit "package family" is the same, i.e. one specified by both PubVer and Version
  • I didn't really run those commands, I have publish.py script that does these steps. For the sake of historical correctness, here's the snapshot of the script:
from pathlib import Path

import os,shutil

LOCAL_FEED = Path(r".\dev\localnuget").absolute()

projects = ["FastExpressionKit", "FastExpressionKit.BulkInsert"]
version = "1.2.0.0"
def c(s):
    print(">",s)
    err = os.system(s)
    assert not err

def nuke(pth):
    if os.path.isdir(pth):
        shutil.rmtree(pth)

def nuget_add(pth):
    c(f"dotnet nuget push -s {LOCAL_FEED} {pth}")

startdir = Path(".").absolute()

for prjdir in projects:    
    os.chdir(startdir / prjdir)
    nuke("bin")
    nuke("obj")

    def pack():
        c(f"dotnet pack -c Release /p:Version={version} /p:PubVer={version}")
        pkgs = list(Path("bin/Release").glob("*.nupkg"))
        nuget_add(pkgs[0])

    pack()

Got a better workflow that is not a massive MsBuild XML library? Let us know in the comments!

Top comments (0)