Tag: nuget

  • Distributing C# Code Standards with a Nuget Package

    My deep dive into Nuget packages made me ask a new question: Can I use Nuget packages to deploy code standards for C#?

    It’s Good To Have Standards

    Generally, the goal of coding standards is to produce a uniform code base. My go-to explanation of what coding standards are is this: “If you look at the code, you should not be able to tell who wrote it.”

    Linting is, to some degree, an extension of formatting. Linting applies a level of analysis to your code, identifying potential runtime problems. Linting rules typically derive from various best practices which can (and do) change over time, especially for languages in active development. Thing of it this way: C++ has not changed much in the last 20 years, but C#, JavaScript, and, by association, TypeScript, have all seen active changes in recent history.

    There are many tools for each language that offer linting and formatting. I have developed some favorites:

    • Formatting
      • dotnet format – C#
      • prettier – Javascript/TypeScript
      • black – Python
    • Linting
      • Sonar – support for various languages
      • eslint – Javascript/TypeScript
      • flake8 – Python

    Regardless of your tool of choice, being able to deploy standards across multiple projects has been difficult. If your projects live in the same repository, you can store settings files in a common location, but for projects across multiple repositories, the task becomes harder.

    Nuget to the Rescue!

    I spent a good amount of time in the last week relearning Nuget. In this journey, it occurred to me that I could create a Nuget package that only contained references and settings for our C# standards. So I figured I’d give it a shot. Turns out, it was easier than I anticipated.

    There are 3 files in the project:

    • The csproj file – MyStandards.CSharp.csproj
    • main.editorconfig
    • MyStandards.CSharp.props

    I put the content of the CSProj and Props files below. main.editorconfig will get copied into the project where this package is referenced as .editorconfig. You can read all about customizing .editorconfig here.

    MyStandards.CSharp.csproj

    <Project Sdk="Microsoft.NET.Sdk">
    
    	<PropertyGroup>
    		<TargetFramework>netstandard2.1</TargetFramework>
    		<Nullable>enable</Nullable>
    		<IncludeBuildOutput>false</IncludeBuildOutput>
    		<DevelopmentDependency>true</DevelopmentDependency>
    	</PropertyGroup>
    
    	<ItemGroup>
    		<Content Include="main.editorconfig">
    			<CopyToOutputDirectory>Always</CopyToOutputDirectory>
    			<PackagePath>build</PackagePath>
    		</Content>
    		<Content Include="MyStandards.CSharp.props">
    			<CopyToOutputDirectory>Always</CopyToOutputDirectory>
    			<PackagePath>build</PackagePath>
    		</Content>
    	</ItemGroup>
    
    	<ItemGroup>
    		<PackageReference Include="SonarAnalyzer.CSharp" Version="9.15.0.81779" PrivateAssets="None" />
    	</ItemGroup>
    
    </Project>
    

    MyStandards.CSharp.props

    <Project>
    	<Target Name="CopyFiles" BeforeTargets="Build">
    		<ItemGroup>
    			<SourceFile Include="$(MSBuildThisFileDirectory)main.editorconfig"></SourceFile>
    			<DestFile Include="$(ProjectDir)\.editorconfig"></DestFile>
    		</ItemGroup>
    		<Copy SourceFiles="@(SourceFile)" DestinationFiles="@(DestFile)"></Copy>
    	</Target>
    </Project>
    

    What does it do?

    When this project is built as a Nuget package, it contains the .props and main.editorconfig files in the build folder of the package. Additionally, it has a dependency on SonarAnalyzer.CSharp.

    Make it a Development Dependency

    Note that IncludeBuildOutput is set to false, and DevelopmentDependency is set to true. This tells Nuget to not include the empty MyStandards.CSharp.dll file that gets built, and to package this Nuget file as a development only dependency.

    When referenced by another project, the reference will be added with the following defaults:

    <PackageReference Include="MyStandards.CSharp" Version="1.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    So, the consuming package won’t include a dependency for my standards, meaning no “chain” of references.

    Set the Settings

    The .props file does the yeoman’s work: it copies main.editorconfig into the target project’s folder as .editorconfig. Notice that this happens before every build, which prevents individual projects from overriding the .editorconfig file.

    Referencing Additional Development Tools

    As I mentioned, I’m a fan of the Sonar toolset for linting. As such, I like to add their built-in analyzers wherever I can. This includes adding a reference to SonarAnalyzer.CSharp to get some built-in analysis.

    Since SonarAnalyzer.CSharp is a development dependency, when installed, it is not included in the package. It would be nice if my standards package would carry this over to projects which use it. To do this, I set PrivateAssets="None" in the PackageReference, and removed the default settings.

    The Road Ahead

    With my C# standards wrapped nicely in a Nuget package, my next steps will be to automate the build pipeline for the packages so that changes can be tracked.

    For my next trick, I would like to dig in to eslint and prettier, to see if there is a way to create my own extensions for those tools to distribute our standards for JavaScript/TypeScript. However, that task may have to wait a bit, as I have some build pipelines to create.

  • Pack it up, Pack it In…

    I dove very deeply into Nuget packages this week, and found some pretty useful steps in controlling how my package can control properties in the consuming project.

    Problem at Hand

    It’s been a long time since I built anything more than a trivial Nuget package. By “trivial” I mean telling the .Net project to package the version, and letting it take care of generating the Nuget package based on the project. If I added a .nuspec file, it was for things like description or copyright.

    I am working on building a package that contains some of our common settings for Unit Test execution, including Coverlet settings and XUnit runner settings. Additionally, I wanted the references in my package to be used in the consuming package, so that we did not need to update xunit or coverlet packages across all of our Unit Test projects.

    With these requirements, I needed to build a package that does two things:

    1. Copy settings files to the appropriate location in the consuming project.
    2. Make sure references in the package were added to the consuming project.

    Content Files… What happened??

    It would seem that the last time I used content files, packages.config was the way to include Nuget packages in a project. However, with the migration to using the PackageReference attribute, some things changed, including the way content files are handled.

    The long and short of it is, packages are no longer “copied on install,” so additional steps are required to move files around. However, I wanted those additional steps to be part of my package, rather than added to every referencing project.

    Out comes “props and targets.” You can add .props and/or .targets files to your package, using the same name as the package, and those props will be included in the consuming project. I have to admit, I spent a great deal of time researching MSBuild and how to actually make this do what I wanted. But, I will give you a quick summary and solution.

    My Solution

    myCompany.Test.UnitTest.Core.props

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    	<PropertyGroup>
    		<RunSettingsFilePath>$(MSBuildThisFileDirectory)\coverlet.runsettings</RunSettingsFilePath>
    	</PropertyGroup>
    	<ItemGroup>
    		<Content Include="$(MSBuildThisFileDirectory)xunit.runner.json">
    			<Link>xunit.runner.json</Link>
    			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    			<Visible>False</Visible>
    		</Content>
    	</ItemGroup>
    </Project>

    myCompany.Test.UnitTest.Core.csproj

    <Project Sdk="Microsoft.NET.Sdk">
    
    	<PropertyGroup>
    		<TargetFramework>net6.0</TargetFramework>
    		<OutputType>Library</OutputType>
    		<IsPackable>true</IsPackable>
    	</PropertyGroup>
    
    	<ItemGroup>
    		<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" PrivateAssets="None" />
    		<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="None" />
    		<PackageReference Include="xunit" Version="2.6.3" PrivateAssets="None" />
    		<PackageReference Include="xunit.runner.utility" Version="2.6.3" PrivateAssets="None" />
    		<PackageReference Include="JustMock" Version="2023.3.1122.188" PrivateAssets="None" />
    		<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" PrivateAssets="None" />
    		<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="None" />
    		<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5" PrivateAssets="None" />
    	</ItemGroup>
    
    	<ItemGroup>
    		<Content Include="xunit.runner.json" CopyToOutputDirectory="Always">
    			<PackagePath>build</PackagePath>
    		</Content>
    		<Content Include="coverlet.runsettings" CopyToOutputDirectory="Always">
    			<PackagePath>build</PackagePath>
    		</Content>
    		<Content Include="myCompany.Test.UnitTest.Core.props" CopyToOutputDirectory="Always">
    			<PackagePath>build</PackagePath>
    		</Content>
    	</ItemGroup>
    
    </Project>

    Using Content Files

    We have a common settings file (xunit.runner.json) that needs to be copied to the output directory of the Unit Tests, so that XUnit runs with common settings. To accomplish this, I added the file to my package’s .csproj file as a content file (lines 21-23 in the .csproj file). Then, in the .props file (lines 7-11), I added a step to ensure that the file is copied to the output directory.

    For the coverlet.runsettings file, all I need is to make sure that the consuming project uses that file as for its run settings. I set the RunSettingsFilePath in the .props file, and pointed it to the file in the package.

    Using References

    In the .csproj file, you’ll notice that I have a number of references to packages with PrivateAssets="None" configured. These packages have some assets that are “hidden” from the consuming project, meaning when these projects are installed as transitive packages, those assets are hidden.

    In this case, I do not want to hide any assets from these packages, I want them to flow through to the consuming project. Setting PrivateAssets="None" tells the consuming project that these packages should be used in full. This post and the MSDN documentation shed most of the light on this particular topic for me.

    Miscellaneous Notes

    You may have noticed that, for the settings files, I sent them to the build folder alongside the .props file, rather than in content or contentFiles. Since these things are only used during build or execution of unit tests, this seemed the appropriate place.

    It’s also worth noting that I went through SEVERAL iterations of solutions on this. There is a ton of flexibility in MSBuild and its interactions with the packages installed, but I ended up with a solution which requires minimal effort on the package consumer side. And, for me, this is the intent: keep it DRY (Don’t Repeat Yourself).

  • Packages, pipelines, and environment variables…. oh my!

    I was clearly tempting the fates of package management when I called out NPM package management. Nuget was not to be outdone, and threw us a curveball in one of our Azure DevOps builds that is too good not to share.

    The Problem

    Our Azure Pipeline build was erroring out, however, it was erroring in different steps, and steps that seemingly had no connection to the changes that were made.

    Error 1 – Unable to install GitVersion.Tool

    We use GitVersion to version our builds and packages. We utilize the GitTools Azure DevOps extension to provide us with pipeline tasks to install and execute GitVersion. Most of our builds use step templates to centralize common steps, so these steps are executed every time, usually without error.

    However, in this particular branch, we were getting the following error:

    --------------------------
    Installing GitVersion.Tool version 5.x
    --------------------------
    Command: dotnet tool install GitVersion.Tool --tool-path /agent/_work/_temp --version 5.7.0
    /usr/bin/dotnet tool install GitVersion.Tool --tool-path /agent/_work/_temp --version 5.7.0
    The tool package could not be restored.
    /usr/share/dotnet/sdk/5.0.402/NuGet.targets(131,5): error : 'feature' is not a valid version string. (Parameter 'value') [/tmp/t31raumn.tj5/restore.csproj]

    Now, I would have concentrated more on the “‘feature’ is not a valid version string” error initially, however, I was distracted because, sometimes, we got past this error but into another error.

    Error 2 – Dotnet restore failed

    So, sometimes (not always), the pipeline would get past the installation of GitVersion and make it about three steps forward, into the dotnet restore step. Buried in that restore log was a similar error to the first one:

    error : 'feature' is not a valid version string. (Parameter 'value')

    Same error, different steps?

    So, we were seeing the same error, but in two distinct places: one in a step which installs the tool, and presumably has nothing to do with the code in the repository, and the other which is squarely around the dotnet restore command.

    A quick Google search yielded an issue in the dotnet core Github repository. At the tail end of that thread is this little tidbit from user fabioduque:

    The same thing happened to me, and it was due to me setting up the Environment Variable “VERSION”.
    Solved with with:
    set VERSION=

    Given that, I put a quick step in to print out the environment variables and determined the problem.

    Better the devil you know…

    As the saying goes: Better the devil you know than the devil you don’t. In this case, the devil we didn’t know about was an environment variable named VERSIONPREFIX. Now, we were not expressly setting that version in a script, but one of our variables was named versionPrefix. Azure DevOps makes pipeline variables available as environment variables, and it standardizes them into all caps, so we ended up with VERSIONPREFIX. Nuget, in versions after 3.4, provides for applying settings at runtime via environment variables. And since VERSIONPREFIX is applicable in dotnet / nuget, our variable was causing these random errors. I renamed the variable and viola, no more random errors.

    The Short, Short Version

    Be careful when naming variables in Azure Pipelines. They are made available as-named via environment variables. If some of the steps or commands that you use accept those environment variables as settings, you may be inadvertently affecting your builds.