Local NuGet Dependencies On Linux Tutorial

I am embarking on making some libraries that have a chance to get large and that I wanted to be independent from the applications that will utilize them. It’s really all about easy and scalable dependency management. In Java we usually do this with Maven or Gradle. Under .NET we have NuGet. Of course there are command differences across the three but the idea is the same: just say which packages your code depends on and we will do the rest for you. They have another neat feature where if you write our own library you can easily bring that into your other project’s dependencies too. For public projects you want to circulate you can push them to the same repositories you download the other dozens (or hundreds) of libraries you use. For local development you can use them too. I thought it’d be as simple as mvn install or gradlew install in NuGet. Would that it were so simple. The long story short is that if I were developing on Windows it’d be slightly more cumbersome but not that difficult. For Linux, and I believe for Mac, however there is a lot more setup that needs to happen. Worse, the documentation for doing local repositories is a bit hidden and there are things you have to do in a few places. I’ve decided to document them all here.

Installing Mono and NuGet on Linux

First and foremost you need to install the Mono project from this link. I already had Mono installed directly from the Ubuntu repositories so thought I could skip this step. That turned out not to be the case. It is disappointing to have to install an entire other .NET environment just to do something as simple as doing this package install but sadly that’s the state of things right now. Once that is installed you now have to install NuGet. Once again there are NuGet executables probably in your package repositories. My Ubuntu 18 repository though had a 2.x version of NuGet. The ability to do local repository management didn’t exist until 3.x and the current version is 5.3.1. However if you go to the NuGet page it says, somewhat correctly, that it only is for Windows x86. That’s partially true. It doesn’t run under .NET Core, why I have no idea, but it will run under Mono. This link here from the Microsoft website provides instructions for how to download and configure your environment. It’s essentially just cURLing the file into a local file and then instructions on how to setup your shell environment to map a nuget command to that. It’s not complicated but again it seems like it’s intentionally obtusely hidden for configuring this. With NuGet setup on your environment though we will be in business.

Creating the Library

NuGet packaging is actually pretty easy and there are some good examples of creating a project and building a package. Let’s try a real simple example. Create a new library project:

dotnet new classlib -n "MyTestNuGetLibrary"

If we look at the standard generated library we’ll have a Class1.cs file that looks like:

using System;

namespace MyTestNuGetLibrary
{
    public class Class1
    {
    }
}

Let’s add a simple method that adds two numbers so we have:

using System;

namespace MyTestNuGetLibrary
{
    public class Class1
    {
        public static int Add(int x, int y) => x + y;
    }
}

Let’s confirm this builds with the dotnet build command. Assuming no compile errors (which you shouldn’t have) we are ready to start making and installing our NuGet package version of this library. First we have to add some additional information to our C# Project file MyTestNuGetLibrary.csproj. By default it will look like:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

Packaging and Installing Our Library

We need to add some minimal fields to our property group to properly build a NuGet package. Those fields are:

  • PackageId: The name of the package that will be created and how it will be referred to by NuGet. This will hopefully be something unique for a real library. That’s especially true if you plan on publishing it to a public repository.
  • Version: A version number of the library so that users can select between multiple versions over time
  • Authors: The name of authors of the library
  • Company: The name of the company of the library

For my example I filled them in as below. Note I added an additional setting to have it constantly generate the package every time I do a build. That will make it easier as I’m developing to keep pushing it to my local repository.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <PackageId>MyTestNuGetLibrary</PackageId>
    <Version>0.0.1</Version>
    <Authors>HankG</Authors>
    <Company>HankG</Company>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
  </PropertyGroup>

</Project>

With these settings complete we can create a package by simply executing dotnet pack in the same directory as our project. This will give you output that looks something like (note I’m doing this in a /tmp directory):

Restore completed in 95.8 ms for /tmp/MyTestNuGetLibrary/MyTestNuGetLibrary.csproj.
Successfully created package '/tmp/MyTestNuGetLibrary/bin/Debug/MyTestNuGetLibrary.0.0.1.nupkg'.

Configuring NuGet Locally and Publishing

We are now almost ready to install our library locally. First we need to create a directory where we want to constantly publish to and pull from. I created a hidden folder in my root folder called .private_nuget:

cd ~/
mkdir .private_nuget

Now to publish our library to this folder we are going to use the nuget add command. If you didn’t follow the steps above in configuring you’ll be dead in the water here. If when you type nuget with no arguments you don’t get a listing of commands and the version at the top doesn’t match the latest build version you were supposed to download, go do the setup again. Assuming you can make it that far lets go on to publish our local package. The simplest version of the command which we will be using takes two arguments: the path to a created NuGet package and the name of the local directory to publish to. For my system it looks like this:

nuget add /tmp/MyTestNuGetLibrary/bin/Debug/MyTestNuGetLibrary.0.0.1.nupkg -Source ~/.private_nuget/

If you go into your local repository directory you should now see a folder named “mytestnugetlibrary” (a hold over to this needing to work on case insensitive file systems). The structure of that directory will look something like this:

mytestnugetlibrary/
└── 0.0.1
    ├── mytestnugetlibrary.0.0.1.nupkg
    ├── mytestnugetlibrary.0.0.1.nupkg.sha512
    └── mytestnugetlibrary.nuspec

The nuget tool took our library, created a spec, created a hash so it can confirm its integrity, and renamed it in a way it could use. You can see it is also nesting this within a version sub-directory too. This is how package managers like NuGet allow you to have multiple concurrent installs of the same directory. While we have properly built a library what we haven’t done is tell NuGet to look there for files. The list of package repositories NuGet uses is maintained in an XML configuration file. These can be found in one of two locations:

  • ~/.nuget/NuGet/NuGet.config
  • ~/.config/NuGet/NuGet.config

Which one do you need to edit if you have both? If you have both you probably need to edit both. On my system the first one is used by the dotnet command line tool while the second one is being used by JetBrains Rider. The default file after installation will look like:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
  </packageSources>
</configuration>

This is a path to the standard main NuGet repository. If you’ve been developing for awhile you may have others in there as well. All we want to do is add our own which points to our local private directory. So for example on my system it looks like:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
    <add key="Local" value="/home/user1/.private_nuget" />
  </packageSources>
</configuration>

The arguments are pretty simple. The key is just the label that will show up for developers to see what it is. The value is simply the full path down to the repository. That’s all that is needed. Note that unlike the command line usage we could not use the tilde to represent the home path. Once you’ve changed that in both locations we are ready to start using our library in another project. Let’s prove it by making a console project to do that.

Testing Our Package With an Application

Let’s make a console application to confirm our package configuration works and our package library is available to other applications:

dotnet new console -n TestMyLibraryConsole

This command created a library with a structure like this:

TestMyLibraryConsole/
├── obj
│   ├── project.assets.json
│   ├── TestMyLibraryConsole.csproj.nuget.cache
│   ├── TestMyLibraryConsole.csproj.nuget.dgspec.json
│   ├── TestMyLibraryConsole.csproj.nuget.g.props
│   └── TestMyLibraryConsole.csproj.nuget.g.targets
├── Program.cs
└── TestMyLibraryConsole.csproj

We will want to edit your Program.cs to use our library and bring in our package reference. Let’s first modify the our program to use it. First confirm the project runs by typing dotnet run from within the directory. You should see a nice Hello World! come up after a few moments. Let’s change this to use our “fancy” math library. We add to our one “hello world” prompt line some variable declarations and usage of our library.

using System;

namespace TestMyLibraryConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            var x = 1;
            var y = 3;
            var result = MyTestNuGetLibrary.Class1.Add(x, y);
            Console.WriteLine($"{x} + {y} = {result}");
        }
    }
}

Now at this point we assume it’s not going to work because our program doesn’t know anything about this library. If we do a dotnet run now we will get a bulid error saying something about the library “MyTestNuGetLibrary” doesn’t exist. We need to configure our project to ask for this library. We do this by adding a package reference to your csproj file. We start with this from our .NET template:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

</Project>

To add a package reference we simply use the dotnet add package command to add a dependency to our new library:

dotnet add package MyTestNuGetLibrary -v 0.0.1

You execute this with the name of the library and the version explicitly. This adds a few more lines to our project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MyTestNuGetLibrary" Version="0.0.1" />
  </ItemGroup>

</Project>

The PackageReference takes two simple parameters Include for the NuGet package name and Version for the version of the library that is requested. This could of course have been hand edited as well. Now if we do dotnet run the dotnet build tool will recognize it doesn’t know about the packages, attempt to download it from all the sources it knows about (including our local folder), and if it does so build the project and run it. Your final output will then look like:

Hello World!
1 + 3 = 4

Other Gotchas

Alright so we now have local repositories for our NuGet packages. We can use them in our projects. So after some of the headaches we should be almost in the same place as we were back under Maven or Gradle? Unfortunately, not quite. With these tools as I develop in one library and it’s ready to be used on another project I literally just have to run mvn install again. The libraries are updated and when it’s time to build the other project again using the new code it auto-refreshes with the latest copy in the local ~/.m2 repository and that’s that. Under NuGet on Linux it’s a little more complicated than that. The first problem is that the nuget add command really means add. In Maven/Gradle install will just overwrite the files. NuGet’s add on the other hand will error out since there is already a library there. The second problem is that even if you pushed a copy to your repository the NuGet Cache won’t update with the new value. Once it is in the cache it’s in the cache. With each build therefore we need to do a little cleanup operation. We need to forcibly delete the files in both the repository and the caches before we push a new package. So a typical re-publishing process would look like the following commands for our TestNuGetLibrary 0.0.1:

rm -rf ~/.private_nuget/mytestnugetlibrary/0.0.1/
rm -rf ~/.nuget/packages/mytestnugetlibrary/0.0.1/
nuget add /tmp/MyTestNuGetLibrary/bin/Debug/MyTestNuGetLibrary.0.0.1.nupkg -Source ~/.private_nuget/

With that you should be able to regularly update libraries, publish those packages locally, and then use the updated versions of the library in your other .NET projects.

Conclusions

The steps of getting this working aren’t particularly onerous but they are definitely far less streamlined than under Maven or Gradle. Even on Windows these steps are a bit more complicated than they need to be but on Linux it smarts a bit more because of the built in nuget for .NET Core being insufficient for doing these tasks and the NuGet executable being unable to run under .NET Core at the very least. I’m actually shocked these deficiencies in NuGet haven’t been solved already.



Picture of Me (Hank)

Categories

Updates (124)
Journal (116)
Software Engineering (97)
Daily Updates (84)
Commentary (66)
Methodology (57)

Archive

2019
2018
2017
2016
2015
2014
2013