DotNET: Difference between revisions

imported>Milahu
update buildDotnetPackage, add errors and fixes
m Use the correct command name.
 
(33 intermediate revisions by 13 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" line="1" start="1">
{ lib
{
, stdenv
  buildDotnetModule,
, fetchFromGitHub
  dotnetCorePackages,
, buildDotnetPackage
, dotnetPackages
, pkg-config
}:
}:


buildDotnetPackage rec {
buildDotnetModule {
   pname = "some_program";
   pname = "hello";
   version = "some_version";
   version = "0.1";
 
  src = ./.;


   src = fetchFromGitHub {
   projectFile = "Hello/Hello.csproj";
    owner = "some_owner";
  dotnet-sdk = dotnetCorePackages.sdk_8_0;
    repo = pname;
  dotnet-runtime = dotnetCorePackages.runtime_8_0;
    rev = "v${version}";
  nugetDeps = ./deps.json;
    sha256 = "";
}
  };
 
</syntaxhighlight>


  xBuildFiles = [
If the <code>fetch-deps</code> script isn't working for whatever reason, you can manually run <code>nuget-to-json</code>:
    "SomeProject/SomeProject.csproj"
<syntaxhighlight lang="shell-session">
  ];
$ dotnet restore --packages=packageDir ./SomeProject.csproj
$ nuget-to-json packageDir > deps.json
$ rm -r packageDir
</syntaxhighlight>


  nativeBuildInputs = [
Remember to build and run the <code>fetch-deps</code> script after NuGet packages are updated, or building the derivation will fail.
    pkg-config
  ];


  buildInputs = [
== Building non-.NET Core packages ==
  ];


  propagatedBuildInputs = [
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.


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


  meta = with lib; {
== Packaging ASP.NET projects ==
    homepage = "some_homepage";
    description = "some_description";
    license = licenses.mit;
  };
}
</syntaxHighlight>


== XML namespace error ==
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>
An example of systemd + ASP.NET 8 service:
{ # ...
, msbuild
}:


let
<syntaxhighlight lang="nix">
  arrayToShell = (a: toString (map (lib.escape (lib.stringToCharacters "\\ ';$`()|<>\t") ) a));
# myapp package needs to be imported; and added to `environment.systemPackages`
in
# the variable myapp is used below


buildDotnetPackage rec {
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


  msBuildFiles = [
    DOTNET_ENVIRONMENT = "Production";
    "SomeProject/SomeProject.csproj"
  ];


  nativeBuildInputs = [
     # the following are examples
     pkg-config
     ConnectionStrings__DefaultConnection = "Host=/var/run/postgresql;Database=myapp";
     msbuild
  ];


  msBuildFlags = [
    # Kestrel + HTTPS; must setup https://wiki.nixos.org/wiki/ACME
     "/p:Configuration=Release"
    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";


  # based on https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/dotnet/build-dotnet-package/default.nix
    Logging__LogLevel__Default = "Information";
  buildPhase = ''
     Logging__LogLevel__Microsoft__AspNetCore = "Warning"; # this does not actually work, not sure how to fix
     runHook preBuild


     echo Building dotNET packages...
     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


     # Probably needs to be moved to fsharp
     # TODO email
    if pkg-config FSharp.Core
    then
      export FSharpTargetsPath="$(dirname $(pkg-config FSharp.Core --variable=Libraries))/Microsoft.FSharp.Targets"
    fi


     ran=""
     # TODO Stripe
    for msBuildFile in ${arrayToShell msBuildFiles} ''${msBuildFilesExtra}
     Stripe__Currency = "USD";
     do
  };
      ran="yes"
};
      msbuild ${arrayToShell msBuildFlags} ''${msBuildFlagsArray} $msBuildFile
    done
 
    [ -z "$ran" ] && msbuild ${arrayToShell msBuildFlags} ''${msBuildFlagsArray}


    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 = {
   DOTNET_ROOT = "${pkgs.dotnet-sdk}";
   DOTNET_ROOT = "${pkgs.dotnet-sdk}/share/dotnet/";
};
};
</syntaxHighlight>
</syntaxhighlight>


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 146: Line 125:
</blockquote>
</blockquote>


Fix:
Wontfix: The project will build only on Windows.


<syntaxHighlight lang=nix>
== Unable to find package ==
  postPatch = ''
    substituteInPlace SomeProject/SomeProject.csproj \
      --replace \
        "<TargetFramework>net6.0-windows</TargetFramework>" \
        "<TargetFramework>net6.0</TargetFramework>"
  '';
</syntaxHighlight>


== Missing NuGet packages ==
<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 224: Line 192:
== 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].


[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.
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.


Here's an example of using that package:
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.


<syntaxHighlight lang=nix>
packages =                                                       
  let dotnetPkg =                                               
    (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;       
== Example: Running Rider with dotnet & PowerShell ==
      csharp-ls                                          # if a package is missing, it can be declared
Rider has better compatibility when run in FHS mode
      dotnet-repl                                        # manually, see the sources of dotnet-tools.nix
 
     ])                                                          
Rider package<syntaxhighlight lang="nix">
   ];                                                            
pkgs.jetbrains.rider
</syntaxHighlight>
</syntaxhighlight>rider-fhs.nix<syntaxhighlight lang="nix">
{ pkgs ? import <nixpkgs> {} }:
 
(pkgs.buildFHSEnv {
  name = "rider-env";
  targetPkgs = pkgs: (with pkgs; [
    dotnetCorePackages.dotnet_8.sdk
    dotnetCorePackages.dotnet_8.aspnetcore
     powershell
  ]);
  multiPkgs = pkgs: (with pkgs; [
   ]);
  runScript = "nohup rider &";
}).env
</syntaxhighlight><syntaxhighlight lang="nix">
nix-shell ./rider-fhs.nix
</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. <syntaxhighlight>
run-rider = "nix-shell ~/nix/rider-fhs.nix";
</syntaxhighlight>
 
== 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:Languages]]