Notes from a local NuGet package development workflow
Tags: dotnet
Published: 2020-03-08 10:14:44

*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:

```xml
  <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
﻿<?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](https://github.com/vivainio/FastExpressionKit/blob/master/publish.py) that does these steps. For the sake of historical correctness, here's the snapshot of the script:

```python
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!