DotNET: Difference between revisions

From NixOS Wiki
imported>Milahu
m fixup NETSDK1013, project will build only on Windows
Lostmsu (talk | contribs)
 
(27 intermediate revisions by 10 users not shown)
Line 1: Line 1:
.NET packages can be built with <code>buildDotnetPackage</code>
.NET packages can be built with <code>buildDotnetModule</code>


More information about <code>buildDotnetModule</code> can be found in the [https://nixos.org/manual/nixpkgs/unstable/#dotnet nixpkgs manual]
Example build file:
Example build file:


<syntaxHighlight lang=nix>
<syntaxhighlight lang="nix">
{ lib
{ fetchFromGitHub
, stdenv
, dotnetCorePackages
, fetchFromGitHub
, buildDotnetModule
, buildDotnetPackage
, dotnetPackages
, pkg-config
}:
}:


buildDotnetPackage rec {
buildDotnetModule rec {
   pname = "some_program";
   pname = "some_program";
   version = "some_version";
   version = "some_version";
Line 20: Line 18:
     repo = pname;
     repo = pname;
     rev = "v${version}";
     rev = "v${version}";
     sha256 = "";
     hash = ""; # use e.g. `nix-prefetch-git`
   };
   };


   xBuildFiles = [
   projectFile = "SomeProject/SomeProject.csproj";
    "SomeProject/SomeProject.csproj"
   dotnet-sdk = dotnetCorePackages.sdk_8_0;
  ];
   dotnet-runtime = dotnetCorePackages.runtime_8_0;
 
   nugetDeps = ./deps.nix; # create a blank file here, then populate it with `nix-build -A fetch-deps && ./result`
   nativeBuildInputs = [
    pkg-config
  ];
 
  buildInputs = [
  ];
 
   propagatedBuildInputs = [
  ];
 
   checkInputs = [
    dotnetPackages.NUnit
    dotnetPackages.NUnitRunners
  ];


   meta = with lib; {
   meta = with lib; {
Line 48: Line 32:
   };
   };
}
}
</syntaxHighlight>


== XML namespace error ==
</syntaxhighlight>
 
If the <code>fetch-deps</code> script isn't working for whatever reason, you can manually run <code>nuget-to-nix</code>:
<syntaxhighlight lang="sh">
dotnet restore --packages=packageDir ./SomeProject.csproj
nuget-to-nix packageDir >deps.nix
rm -r packageDir
</syntaxhighlight>
 
Remember to build and run the <code>fetch-deps</code> script after NuGet packages are updated, or building the derivation will fail.
 
== Building non-.NET Core packages ==
 
Keep in mind that building projects which don't use the .NET SDK (formerly the .NET Core SDK) and its <code>dotnet</code> CLI tool isn't supported.
For those projects, you'll have to heavily customise the <code>buildDotnetModule</code> build steps, or write a custom derivation.
 
Projects which target .NET Standard or .NET Framework (incl. Mono), but still use the new project structure and SDK, work as expected.
Just remember to add `mono` to `buildInputs` and generate a wrapper script in `postInstall`.
 
== Packaging ASP.NET projects ==
 
Currently building ASP.NET project as Nix package produces a website that does not work correctly out of the box because the executable can not find <code>wwwroot</code>, so all the static assets won't load with 404.


<blockquote>
<blockquote>
xbuild tool is deprecated and will be removed in future updates, use msbuild instead
Request finished HTTP/2 GET https://my.app/css/site.css - 404 0
 
<nowiki>
The default XML namespace of the project must be the MSBuild XML namespace. If the project is authored in the MSBuild 2003 format, please add xmlns="http://schemas.microsoft.com/developer/msbuild/2003" to the <Project> element. If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format.
</nowiki>
</blockquote>
</blockquote>


Fix: in <code>buildPhase</code>, replace <code>xbuild</code> with <code>msbuild</code>
The situation can be fixed by setting <code>WEBROOT</code> environment variable to the package path.
 
<syntaxHighlight lang=nix>
{ # ...
, msbuild
}:


let
An example of systemd + ASP.NET 8 service:
  arrayToShell = (a: toString (map (lib.escape (lib.stringToCharacters "\\ ';$`()|<>\t") ) a));
in


buildDotnetPackage rec {
<syntaxhighlight lang="nix">
  # ...
# myapp package needs to be imported; and added to `environment.systemPackages`
# the variable myapp is used below


   msBuildFiles = [
systemd.services.my-app = {
     "SomeProject/SomeProject.csproj"
   enable = true;
   ];
  description = "Runs my.app";
  wantedBy = [ "multi-user.target" ];
  after = [ "network-online.target" ];
  wants = [ "network-online.target" ];
  serviceConfig = {
    # allow binding to privileged ports - when you want to expose Kestrel directly without reverse proxy
    AmbientCapabilities = "CAP_NET_BIND_SERVICE";
     User = "myapp"; # must be created using users.users.myapp = { isSystemUser = true; group = "myapp"; };
    Group = "myapp"; # must be created using users.groups.myapp = {};
    Restart = "always";
    ExecStart = "${myapp}/bin/myapp";
    StateDirectory = "myapp";
    StateDirectoryMode = "0750";
    WorkingDirectory = "/var/lib/myapp";
    # EnvironmentFile = "/var/lib/myapp/env";
  };
   environment = {
    WEBROOT = "${myapp}/lib/myapp/wwwroot"; # IMPORTANT, required to pick up static assets


  nativeBuildInputs = [
    DOTNET_ENVIRONMENT = "Production";
    pkg-config
    msbuild
  ];


  msBuildFlags = [
    # the following are examples
     "/p:Configuration=Release"
     ConnectionStrings__DefaultConnection = "Host=/var/run/postgresql;Database=myapp";
  ];


  # based on https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/dotnet/build-dotnet-package/default.nix
    # Kestrel + HTTPS; must setup https://wiki.nixos.org/wiki/ACME
  buildPhase = ''
    Kestrel__Endpoints__Https__Url = "https://my.app";
    runHook preBuild
    Kestrel__Endpoints__Https__Certificate__Path = "/var/lib/acme/my.app/cert.pem";
    Kestrel__Endpoints__Https__Certificate__KeyPath = "/var/lib/acme/my.app/key.pem";


     echo Building dotNET packages...
     Logging__LogLevel__Default = "Information";
    Logging__LogLevel__Microsoft__AspNetCore = "Warning"; # this does not actually work, not sure how to fix


     # Probably needs to be moved to fsharp
     Authentication__Google__ClientId = "xxxyyyzzz.apps.googleusercontent.com";
    if pkg-config FSharp.Core
     Authentication__Microsoft__ClientId = "aaaaaa-0000-aaaa-0000-aaaaaaaaaa";
     then
     # secrets must be placed in /var/lib/myapp/appsettings.json
      export FSharpTargetsPath="$(dirname $(pkg-config FSharp.Core --variable=Libraries))/Microsoft.FSharp.Targets"
     fi


     ran=""
     # TODO email
    for msBuildFile in ${arrayToShell msBuildFiles} ''${msBuildFilesExtra}
    do
      ran="yes"
      msbuild ${arrayToShell msBuildFlags} ''${msBuildFlagsArray} $msBuildFile
    done


     [ -z "$ran" ] && msbuild ${arrayToShell msBuildFlags} ''${msBuildFlagsArray}
     # TODO Stripe
    Stripe__Currency = "USD";
  };
};


    runHook postBuild
</syntaxhighlight>
  '';
}
</syntaxHighlight>


MSBuild reference: https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference?view=vs-2022
See also: setting up SSL certificates using [[ACME]]


== .NET location: Not found ==
== .NET location: Not found ==


set DOTNET_ROOT Environment Variable
If running a .NET-build executable you get the above error, make sure the DOTNET_ROOT environment variable is set:
 
<syntaxHighlight lang=nix>
<syntaxHighlight lang=nix>
environment.sessionVariables = {
environment.sessionVariables = {
Line 126: Line 127:


See : https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#net-sdk-and-cli-environment-variables
See : https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#net-sdk-and-cli-environment-variables
== No .NET SDKs were found ==
Fix:
<syntaxHighlight lang=nix>
  nativeBuildInputs = [
    # ...
    dotnet-sdk
  ];
</syntaxHighlight>


== TargetFramework value was not recognized ==
== TargetFramework value was not recognized ==
Line 148: Line 138:
Wontfix: The project will build only on Windows.
Wontfix: The project will build only on Windows.


== Missing NuGet packages ==
== Unable to find package ==
 
<blockquote>


Example error:
error NU1101: Unable to find package runtime.any.System.Collections. No packages exist with this id in source(s): nugetSource


<blockquote>
<code>
<nowiki>
error : This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is ../../packages/Microsoft.Bcl.Build.1.0.21/build/Microsoft.Bcl.Build.targets.
</nowiki>
</code>
</blockquote>
</blockquote>


These are upstream bugs
Unsure what specific situations cause this, probably has something to do with .NET Standard libraries.
 
The workaround is modifying the bits that generate nuget-deps.nix:


Fix: patch all <code>*.csproj</code> files, to remove all XML tags that contain the "missing file" paths, for example
<syntaxhighlight lang="sh">
dotnet restore --packages=packageDir --use-current-runtime ./SomeProject.csproj
nuget-to-nix packageDir >deps.nix
rm -r packageDir
</syntaxhighlight>


<blockquote>
The new parameter <code>--use-current-runtime</code> requires .NET SDK 8+. I believe what it does is explicitly adding packages missing in this runtime vs .NET Standard to packageDir.
<code>
&lt;Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /&gt;
</code>
</blockquote>


See also https://stackoverflow.com/questions/32254439/nuget-packages-are-missing
If this still does not work, it might indicate a good time to update target frameworks and dependencies.


== NativeAOT ==
== NativeAOT ==
Line 215: Line 203:
== Global Tools ==
== Global Tools ==


There is currently no mechanism to install them globally, and regular (mutable) installation [https://github.com/dotnet/sdk/issues/30546 does not work].
Local installation of .NET global tools is fully supported and preferred when possible - more info [https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools#install-a-local-tool in the Microsoft docs].
 
For globally installing .NET tools, search if they are available as Nix packages - they are packaged as any other normal
.NET binary, using <code>buildDotnetModule</code>. For .NET tools with no source available, or those hard to build from source, <code>buildDotnetGlobalTool</code> is available. See [https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/dotnet.section.md#dotnet-global-tools-dotnet-global-tools dotnet nixpkgs manual] for more info.
 
Note that Nix-packaged .NET tools use a special wrapper (toggled by <code>useDotnetFromEnv</code> option in <code>buildDotnetModule</code>) that automatically picks up .NET install from the user environment. If you want to use a
different SDK version with a Nix-packaged .NET tools than the default, make sure the <code>dotnet</code> CLI of your wanted SDK version is installed and available.
 


[https://github.com/WhiteBlackGoose/dotfiles/blob/3f7d1b508f75ea87b8f36f3e2be0e2db1f4241c1/envs/dotnet-tool.nix Here] is a proof of concept of how .NET tools could be used declaratively.
== Example: Running Rider with dotnet & PowerShell ==


Here's an example of using that package:
Rider package


<syntaxHighlight lang=nix>
<syntaxHighlight lang=nix>
packages =                                                       
pkgs.jetbrains.rider
  let dotnetPkg =                                               
</syntaxHighlight>
    (with dotnetCorePackages; combinePackages [                 
      sdk_7_0                                                   
      sdk_6_0                                                   
    ]);                                                         
    dotnetTools = (callPackage ./dotnet-tool.nix {});    # dotnet-tool.nix is the file from the link above
  in [                                                           
    vim                                                         
    firefox                                                     
    dotnetPkg                                                   
    dotnetTools.combineTools dotnetPkg (with dotnetTools.tools; [
                          #  ^^^^^^^^^ here we specify the dotnet package
                          # that will be invoked for this tool
                          # Ideally, something like dotnetPkg.withTools
                          # should be there


      fsautocomplete                                      # these are tools from dotnetTools.tools;      
dotnet.nix
       csharp-ls                                          # if a package is missing, it can be declared
 
       dotnet-repl                                        # manually, see the sources of dotnet-tools.nix
<syntaxHighlight lang=nix>
     ])                                                          
with import <nixpkgs> {};
   ];                                                            
 
mkShell {
  name = "dotnet-env";
  packages = [
    (with dotnetCorePackages; combinePackages [
      sdk_6_0
       sdk_7_0
       sdk_8_0
     ])
    powershell
   ];
}
</syntaxHighlight>
</syntaxHighlight>
To execute Rider
<syntaxHighlight lang=bash>
nix-shell ./dotnet.nix --run 'nohup rider &'
</syntaxHighlight>
This can be added as an alias to your shell if you update the reference to an absolute address, such as location within your home directory. e.g. `~/nix/dotnet.nix`
== Example: multi-SDK installation with local workload installation enabled ==
By default, workload installation will fail on NixOS, as dotnet will attempt to save it to $DOTNET_ROOT, which is inside the read-only Nix store.
Please visit the [https://discourse.nixos.org/t/dotnet-maui-workload/20370/10 forum] for an example of a multi-SDK installation with workload changed to install to home directory.


== See also ==
== See also ==


* [https://ryantm.github.io/nixpkgs/languages-frameworks/dotnet/ dotnet in the nixpkgs manual]
* [https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/dotnet.section.md NixOS GitHub dotnet docs]
* [https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/dotnet-packages.nix nixpkgs/pkgs/top-level/dotnet-packages.nix] &rarr; look for "SOURCE PACKAGES"
* [https://nixos.org/manual/nixpkgs/unstable/#dotnet dotnet in the nixpkgs manual]
* [https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/dotnet/build-dotnet-package/default.nix buildDotnetPackage implementation]
* [https://github.com/search?q=repo%3ANixOS%2Fnixpkgs%20buildDotnetModule&type=code buildDotnetModule references in nixpkgs]
* [https://grep.app/search?q=buildDotnetPackage&filter%5Brepo%5D%5B0%5D=NixOS/nixpkgs&filter%5Blang%5D%5B0%5D=Nix&filter%5Bpath%5D%5B0%5D=pkgs/ buildDotnetPackage references in nixpkgs]
* [https://www.reddit.com/r/NixOS_dotnet NixOS.NET community on Reddit]
* [https://discord.gg/pTpq7Qfs NixOS.NET community on Discord]
* [https://sgt.hootr.club/molten-matter/dotnet-on-nix/ The journey of packaging a .NET app on Nix]
* [https://sgt.hootr.club/molten-matter/dotnet-on-nix/ The journey of packaging a .NET app on Nix]
* https://en.wikipedia.org/wiki/.NET_Framework
* https://en.wikipedia.org/wiki/.NET_Framework - The old, windows-only version of .NET. Newer versions (ie. .NET Core) are multiplatform.
** https://en.wikipedia.org/wiki/List_of_CLI_languages: C#, [[Fsharp|F#]], Visual Basic, ...
** https://en.wikipedia.org/wiki/Mono_(software) is the open source reimplementation of .NET Framework. Its runtime/JIT has been merged into .NET Core, and now it only receives bugfixes.
** https://en.wikipedia.org/wiki/Mono_(software) is the open source implementation of the DotNET compiler and runtime
* https://learn.microsoft.com/en-us/dotnet/core/introduction


[[Category: Development]]
[[Category: Development]]
[[Category:Languages]]

Latest revision as of 04:55, 27 September 2024

.NET packages can be built with buildDotnetModule

More information about buildDotnetModule can be found in the nixpkgs manual Example build file:

{ fetchFromGitHub
, dotnetCorePackages
, buildDotnetModule
}:

buildDotnetModule rec {
  pname = "some_program";
  version = "some_version";

  src = fetchFromGitHub {
    owner = "some_owner";
    repo = pname;
    rev = "v${version}";
    hash = ""; # use e.g. `nix-prefetch-git`
  };

  projectFile = "SomeProject/SomeProject.csproj";
  dotnet-sdk = dotnetCorePackages.sdk_8_0;
  dotnet-runtime = dotnetCorePackages.runtime_8_0;
  nugetDeps = ./deps.nix; # create a blank file here, then populate it with `nix-build -A fetch-deps && ./result`

  meta = with lib; {
    homepage = "some_homepage";
    description = "some_description";
    license = licenses.mit;
  };
}

If the fetch-deps script isn't working for whatever reason, you can manually run nuget-to-nix:

dotnet restore --packages=packageDir ./SomeProject.csproj
nuget-to-nix packageDir >deps.nix
rm -r packageDir

Remember to build and run the fetch-deps script after NuGet packages are updated, or building the derivation will fail.

Building non-.NET Core packages

Keep in mind that building projects which don't use the .NET SDK (formerly the .NET Core SDK) and its dotnet CLI tool isn't supported. For those projects, you'll have to heavily customise the buildDotnetModule build steps, or write a custom derivation.

Projects which target .NET Standard or .NET Framework (incl. Mono), but still use the new project structure and SDK, work as expected. Just remember to add `mono` to `buildInputs` and generate a wrapper script in `postInstall`.

Packaging ASP.NET projects

Currently building ASP.NET project as Nix package produces a website that does not work correctly out of the box because the executable can not find wwwroot, so all the static assets won't load with 404.

Request finished HTTP/2 GET https://my.app/css/site.css - 404 0

The situation can be fixed by setting WEBROOT environment variable to the package path.

An example of systemd + ASP.NET 8 service:

# myapp package needs to be imported; and added to `environment.systemPackages`
# the variable myapp is used below

systemd.services.my-app = {
  enable = true;
  description = "Runs my.app";
  wantedBy = [ "multi-user.target" ];
  after = [ "network-online.target" ];
  wants = [ "network-online.target" ];
  serviceConfig = {
    # allow binding to privileged ports - when you want to expose Kestrel directly without reverse proxy
    AmbientCapabilities = "CAP_NET_BIND_SERVICE";
    User = "myapp"; # must be created using users.users.myapp = { isSystemUser = true; group = "myapp"; };
    Group = "myapp"; # must be created using users.groups.myapp = {};
    Restart = "always";
    ExecStart = "${myapp}/bin/myapp";
    StateDirectory = "myapp";
    StateDirectoryMode = "0750";
    WorkingDirectory = "/var/lib/myapp";
    # EnvironmentFile = "/var/lib/myapp/env";
  };
  environment = {
    WEBROOT = "${myapp}/lib/myapp/wwwroot"; # IMPORTANT, required to pick up static assets

    DOTNET_ENVIRONMENT = "Production";

    # the following are examples
    ConnectionStrings__DefaultConnection = "Host=/var/run/postgresql;Database=myapp";

    # Kestrel + HTTPS; must setup https://wiki.nixos.org/wiki/ACME
    Kestrel__Endpoints__Https__Url = "https://my.app";
    Kestrel__Endpoints__Https__Certificate__Path = "/var/lib/acme/my.app/cert.pem";
    Kestrel__Endpoints__Https__Certificate__KeyPath = "/var/lib/acme/my.app/key.pem";

    Logging__LogLevel__Default = "Information";
    Logging__LogLevel__Microsoft__AspNetCore = "Warning"; # this does not actually work, not sure how to fix

    Authentication__Google__ClientId = "xxxyyyzzz.apps.googleusercontent.com";
    Authentication__Microsoft__ClientId = "aaaaaa-0000-aaaa-0000-aaaaaaaaaa";
    # secrets must be placed in /var/lib/myapp/appsettings.json

    # TODO email

    # TODO Stripe
    Stripe__Currency = "USD";
  };
};

See also: setting up SSL certificates using ACME

.NET location: Not found

If running a .NET-build executable you get the above error, make sure the DOTNET_ROOT environment variable is set:

environment.sessionVariables = {
  DOTNET_ROOT = "${pkgs.dotnet-sdk}";
};

See : https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#net-sdk-and-cli-environment-variables

TargetFramework value was not recognized

error NETSDK1013: The TargetFramework value 'net6.0-windows' was not recognized. It may be misspelled. If not, then the TargetFrameworkIdentifier and/or TargetFrameworkVersion properties must be specified explicitly.

Wontfix: The project will build only on Windows.

Unable to find package

error NU1101: Unable to find package runtime.any.System.Collections. No packages exist with this id in source(s): nugetSource

Unsure what specific situations cause this, probably has something to do with .NET Standard libraries.

The workaround is modifying the bits that generate nuget-deps.nix:

dotnet restore --packages=packageDir --use-current-runtime ./SomeProject.csproj
nuget-to-nix packageDir >deps.nix
rm -r packageDir

The new parameter --use-current-runtime requires .NET SDK 8+. I believe what it does is explicitly adding packages missing in this runtime vs .NET Standard to packageDir.

If this still does not work, it might indicate a good time to update target frameworks and dependencies.

NativeAOT

This is relevant for NixOS only.

nix-ld is needed:

{
  programs.nix-ld.enable = true;
}

Now we will need a bunch of native dependencies. Here's an example of a shell:

with import <nixpkgs> {};
pkgs.mkShell rec {

  dotnetPkg = 
    (with dotnetCorePackages; combinePackages [
      sdk_7_0
    ]);

  deps = [
    zlib
    zlib.dev
    openssl
    dotnetPkg
  ];

  NIX_LD_LIBRARY_PATH = lib.makeLibraryPath ([
    stdenv.cc.cc
  ] ++ deps);
  NIX_LD = "${pkgs.stdenv.cc.libc_bin}/bin/ld.so";
  nativeBuildInputs = [ 
  ] ++ deps;

  shellHook = ''
    DOTNET_ROOT="${dotnetPkg}";
  '';
}

Global Tools

Local installation of .NET global tools is fully supported and preferred when possible - more info in the Microsoft docs.

For globally installing .NET tools, search if they are available as Nix packages - they are packaged as any other normal .NET binary, using buildDotnetModule. For .NET tools with no source available, or those hard to build from source, buildDotnetGlobalTool is available. See dotnet nixpkgs manual for more info.

Note that Nix-packaged .NET tools use a special wrapper (toggled by useDotnetFromEnv option in buildDotnetModule) that automatically picks up .NET install from the user environment. If you want to use a different SDK version with a Nix-packaged .NET tools than the default, make sure the dotnet CLI of your wanted SDK version is installed and available.


Example: Running Rider with dotnet & PowerShell

Rider package

pkgs.jetbrains.rider

dotnet.nix

with import <nixpkgs> {};

mkShell {
  name = "dotnet-env";
  packages = [
    (with dotnetCorePackages; combinePackages [
      sdk_6_0
      sdk_7_0
      sdk_8_0
    ])
    powershell
  ];
}

To execute Rider

nix-shell ./dotnet.nix --run 'nohup rider &'

This can be added as an alias to your shell if you update the reference to an absolute address, such as location within your home directory. e.g. `~/nix/dotnet.nix`

Example: multi-SDK installation with local workload installation enabled

By default, workload installation will fail on NixOS, as dotnet will attempt to save it to $DOTNET_ROOT, which is inside the read-only Nix store.

Please visit the forum for an example of a multi-SDK installation with workload changed to install to home directory.

See also