Trying to compile a C# project on NixOS

So, I’m trying to compile a C# project on NixOS, but I can’t seem to figure out what to do. I’m trying to compile this project: GitHub - roice3/MagicTile: Non-euclidean Rubik's Cube Analogues

I’ve created the following default.nix file, but I’m not sure what to do with it. Can someone help me, or is this project just too old to be compiled with a modern version on NixOS?

{ buildDotnetModule, fetchFromGitHub }:
buildDotnetModule rec {
  pname = "magictile";
  version = "unstable-2024-04-13";
  src = fetchFromGitHub {
    owner = "roice3";
    repo = "MagicTile";
    rev = "2b1721b255e07ef4ab310ba3768803f511543317";
    hash = "sha256-oae0jao7Jq+DnPK11XujnEuzUqXm9kks1B53CPf4k6g=";
  };
  nugetDeps = "";
  projectFile = "MagicTile.sln";
}

On first look this project seems to be built with .NET Framework 4.8, which is Windows only AFAIK. I’ll look into it a bit more if it can be built with any newer .NET versions.

Edit: running with mono also might be possible, but I don’t have much experience with that.

By the way, if you just want to run the program instead of packaging, you can download the prebuild files from here and run LD_LIBRARY_PATH=/nix/store/path-to-libGL-in-the-store/lib mono MagicTile.exe You might be able to set LD_LIBRARY_PATH with a shell.nix file if you don’t want to manually use the libGL path.

I’ve got it working, using mono msbuild and nuget
It may or may not get accepted inside nixpkgs, but at least it works.

{
  lib,
  stdenvNoCC,
  fetchFromGitHub,
  dotnetPackages,
  msbuild,
  makeWrapper,
  mono,
  libGL,
  gtk2,
}:

stdenvNoCC.mkDerivation (finalAttrs: {
  pname = "magictile";
  version = "unstable-2024-04-13";

  src = fetchFromGitHub {
    owner = "roice3";
    repo = "MagicTile";
    rev = "2b1721b255e07ef4ab310ba3768803f511543317";
    hash = "sha256-oae0jao7Jq+DnPK11XujnEuzUqXm9kks1B53CPf4k6g=";
  };

  # FOD for all 3 nuget deps of the package
  deps = stdenvNoCC.mkDerivation {
    name = "${finalAttrs.pname}-${finalAttrs.version}-deps";
    inherit (finalAttrs) src;

    nativeBuildInputs = [ dotnetPackages.Nuget ];

    buildPhase = ''
      runHook preBuild
      export HOME=$(mktemp -d)
      nuget restore
      runHook postBuild
    '';

    installPhase = ''
      runHook preInstall
      mkdir -p $out
      cp -r packages $out/packages
      runHook postInstall
    '';

    outputHashMode = "recursive";
    outputHashAlgo = "sha256";
    outputHash = "sha256-/FvfuUv2AKL1aMsY8kCMdRhBx+I6xsXsD5Mim+1TEcg=";
  };

  nativeBuildInputs = [
    msbuild
    makeWrapper
  ];

  runtimeDeps = [
    libGL
    gtk2 # optional, allows theme recognition
  ];

  configurePhase = ''
    runHook preConfigure
    ln -s ${finalAttrs.deps}/packages ./packages
    runHook postConfigure
  '';

  buildPhase = ''
    runHook preBuild
    msbuild -p:Configuration=Release MagicTile/MagicTile.csproj
    runHook postBuild
  '';

  installPhase = ''
    runHook preInstal

    mkdir -p $out/lib/magictile
    cp -r MagicTile/bin/Release/* MagicTile/config $out/lib/magictile
    rm $out/lib/magictile/WaitCursor.ico # only supported on windows

    makeWrapper ${mono}/bin/mono $out/bin/MagicTile \
        --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath finalAttrs.runtimeDeps} \
        --chdir $out/lib/magictile \
        --add-flags $out/lib/magictile/MagicTile.exe

    runHook postInstall
  '';
})

I guess I don’t really need to package this up. I just want to run the program. Is there a way that I can get a shell script that just launches this for me? Even if that shell script just ends up calling nix-shell with the proper files and libraries in scope that would still be amazing. Preferably I’d like a script that automatically finds the store path, because I think that future system rebuilds might change the store path.I’d just want something where I can run ./magicTile.sh from the command line, and the program just launches.

Would this be a better way to run the program than just using the prebuilt files? And if so, how would I use this build file?

You can use writeShellScriptBin to create that shell script and add it to your systemPackages. If you reference libGL by attribute ("${pkgs.libGL}/lib") then every time you rebuild it will create a new shell script pointing to the correct path.

You can put this file next to your nix configuration as something like magictile.nix and then you can add the following into your nix configuration:

environment.systemPackages = [
   (pkgs.callPackage ./magictile.nix {})
];

Then, after rebuilding, MagicTile will be able to be run from any terminal

Just realized that the expression I sent doesn’t copy the actual puzzles, will update it shortly

Edited my previous code, should now work with all puzzles.

Thank you for helping me with, but I seem to be getting some graphics issues here. I’ve used your default.nix and it seems to work correctly in the majority of cases. But some times things seems to work incorrectly. It’s mainly the spherical projection that screws up. It looks like some of the pixels are being drawn way to large. I’ve only screenshotted the relevant part of the program to highlight the error.
I just opened “Start here” and then “Rubik’s Cube” on the side panel

Would you be able to show me an example of this being done? I’m not sure how to do this, and also, I’m not sure if I could just have the compiled source hanging out in my /etc/nixos directory or if that would lead to issues.

Sure, there are a few ways to write the code depending on how you like your /etc/nixos/configuration.nix to be organized. I have a big let binding at the top of mine, so my way of writing this would be something like

{ config, lib, pkgs, ... }:

let
  magictile-unwrapped = pkgs.fetchzip {
    url = "http://www.roice3.org/magictile/downloads/MagicTile_mac.zip";
    hash = ""; # fill this in later by running the build first and using the value it demands
  };

  magictile = pkgs.writeShellScriptBin "magictile" ''
    # TODO: figure out what the right paths are; this is a guess
    export LD_LIBRARY_PATH=${pkgs.libGL}/lib
    exec ${pkgs.mono}/bin/mono ${magictile-unwrapped}/MagicTile.exe
  '';
in
{
  # among all the other stuff you have in configuration.nix...
  environment.systemPackages = [
    # ...
    magictile
  ];
}

You don’t need to download the compiled program to anywhere in /etc; fetchzip will get it and keep it in your Nix store.

I’d prefer to have this in a separate file called something like “MagicTile.nix” and call that from my system configuration, if possible. Do you know how I can do that?

Sure, you can move any expression into another file and import ./that-file.nix where you would use it.

Or you can use modules, which are useful when you want to split out functionality that depends on other parts of your configuration. If you define environment.systemPackages in multiple modules, they will all be concatenated to define your final system.

Actually, there’s an issue with this is that the zip from Roice’s site actually has two subfolders, a __MACOSX metadata thing, and a the compiled binary in a MagicTile_6-20-18 folder. The fetchzip downloads the files properly, and then Nix complains that there are two sub-directories:

error: builder for '/nix/store/mnl1az7fzm1ybdm8dpmqnw9ldh0yw79q-source.drv' failed with exit code 1;
       last 8 log lines:
       >
       > trying http://www.roice3.org/magictile/downloads/MagicTile_mac.zip
       >   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
       >                                  Dload  Upload   Total   Spent    Left  Speed
       > 100 1499k  100 1499k    0     0   447k      0  0:00:03  0:00:03 --:--:--  447k
       > unpacking source archive /build/MagicTile_mac.zip
       > error: zip file must contain a single file or directory.
       > hint: Pass stripRoot=false; to fetchzip to assume flat list of files.
       For full logs, run 'nix log /nix/store/mnl1az7fzm1ybdm8dpmqnw9ldh0yw79q-source.drv'.

Oh yeah, that’s a thing sometimes. Did you try this?

hint: Pass stripRoot=false; to fetchzip to assume flat list of files.

Oh, and I forgot to reference mono properly in my example script, which would probably be your next problem; re-check edit above.

Here’s a drop-in replacement for magictile.nix that uses the prebuilt binaries.

{
  fetchzip,
  lib,
  stdenvNoCC,
  makeWrapper,
  mono,
  libGL,
  gtk2,
}:

stdenvNoCC.mkDerivation (finalAttrs: {
  pname = "magictile";
  version = "unstable";

  src = fetchzip {
    # This link is not stable, might point to a different file in the future.
    url = "http://www.roice3.org/magictile/downloads/MagicTile_mac.zip";
    stripRoot = false;
    hash = "sha256-+9n77pEvRPGLAPCctqecRErwks8eDq1gRvWfRk+mIBs=";
  };

  nativeBuildInputs = [ makeWrapper ];

  runtimeDeps = [
    libGL
    gtk2 # optional, allows theme recognition
  ];

  installPhase = ''
    mkdir -p $out/lib/magictile
    cp -r MagicTile_6-20-18/* $out/lib/magictile
    rm $out/lib/magictile/WaitCursor.ico # only supported on windows

    makeWrapper ${mono}/bin/mono $out/bin/MagicTile \
        --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath finalAttrs.runtimeDeps} \
        --chdir $out/lib/magictile \
        --add-flags $out/lib/magictile/MagicTile.exe
  '';
})

Let me know if this works better for you.
This is visibly broken for me is several cases (the triangles making up the sides are visible)