Support for `dotnet` CLI and ninja generator for C#

CMake supports C# using VS generator with the project files in line with .Net Framework projects. .Net also has .Net Project SDKs which enables building and running the projects on multiple platforms using .Net CLI.

Currently, CMake doesn’t support generating SDK-style projects for C#. CMake MR 6634 is focused on converting project style to use .Net Project SDK, but is limited to VS generator.

To support C# cross-platform, I propose the following:

  • Introduce a new language dotnet.
  • CMake will validate the toolset for dotnet using .Net CLI which enables using with Ninja generator in addition to the current VS generator.
  • Since .Net CLI is a command line, we can also write build.ninja where the first step would be generating SDK-style projects and second step would be dotnet build.

This will be backward-compatible as we wouldn’t be impacting the existing project generation using CSharp language.

2 Likes

Since I am a new user, I can only add two links in the original post. Here is the link to WIP MR 6634: https://gitlab.kitware.com/cmake/cmake/-/merge_requests/6634

While it would be backwards compatible, we would still need to define the behavior with both dotnet and CSharp languages being enabled. Which would get “precedence” for .cs files? Note that embedding other projects is a thing, so a project might have the other language enabled outside of its control at a higher level.

(Note: I don’t do C# development, so I have no predisposition as to how to answer this.)

@ben.boeckel: That’s a good point. What I want to achieve is a feature flag that allows CMake user to choose between using C# with the current approach (approach 1) and with dotnet tools (approach 2).

Approach 1 will be available only on VS generators. I am positive that Approach 2 could be extended to Ninja generator and to Linux. Unfortunately, there are still some use cases where C# developers need Approach 1 and therefore, it can’t be deprecated.

What’s the best way to do this in CMake? And, would the feature flag selection in an embedded project be controlled by that project or the outer project embedding it?

Once we have a feature flag, default should be dotnet and we can warn CMake user when they upgrade CMake version.

Thoughts?

I would say it would have to be a per-target property that defaults to CSharp. Eventually, if dotnet covers all of the CSharp use cases, there can be a policy to switch over.

Alas, the feature flag cannot be the new feature by default because otherwise projects that work today would have different behavior with a new CMake. Such a default flip would require a policy to handle.

My thinking was to have the process of generating the project files be a per-target process. In the draft MR, I have the variable IsDotNetSdkTarget a member variable of cmGeneratorTarget. What I am not sure is how do we get there?

My thinking of introducing the language was to enable the switch at global level. That doesn’t seem a good idea given your comments. How about we do the following?

  • Introduce a flag CMAKE_CSHARP_DOTNET which when set will prefer using .Net CLI for identifying and testing C# toolchain.
  • When CMAKE_CSHARP_DOTNET is TRUE, modify CMakeDetermineCSharpCompiler.cmake to see if dotnet build, dotnet run, and dotnet test succeed.
  • If dotnet build succeeds, Ninja generator can be used.
  • If dotnet build doesn’t succeed, Ninja generator can’t be used. CMake will see if csc is available and if so, use that for VS generator.
  • Introduce a new per-target property DOTNET_SDK_STYLE_PROJECT which will generate SDK-style project for C# files.

Open question:

  • How do we set DOTNET_SDK_STYLE_PROJECT to true for all projects?
  • Do we need the policy introduced in MR 6634 at all?

Thoughts/comments?

From implementation perspective, I can modify MR 6634 to use a per-target property and the rest of the code there shouldn’t need to be changed. Then, the next change would be introducing CMAKE_CSHARP_DOTNET (or other names that people want to use :slight_smile: ).

Unfortunately, this is probably out of my depths on C# support in CMake.

A per-target flag is fine, though CMake still only supports a single toolchain per language which makes it still a project-wide setting.

@brad.king Thoughts?

choose between using C# with the current approach (approach 1) and with dotnet tools (approach 2).

Is there any reason to prefer the old approach, or can the new approach cover all use cases equally well? If CMake didn’t currently have any C# support and you were implementing new support from scratch, would there be any reason to have the proposed switch?

The new approach requires defining a .Net project SDK and not all types of projects have that defined. So, there might be a gap.

Having said that, if C# support was being implemented from scratch, I think it would be simpler and cleaner to only support SDK-style projects.

Thanks.

Introduce a new language dotnet.

IIUC, dotnet is a platform and not a language. C# just happens to be a common choice to program for that platform. I don’t think we should have anything like enable_language(dotnet).

Introduce a flag CMAKE_CSHARP_DOTNET which when set will prefer using .Net CLI

Every variable that defines something fundamental about the target platform and tooling needs to be propagated through try_compile and several other places that run separate configure/generate steps. For the Visual Studio generators, we have generator-wide variables like CMAKE_GENERATOR_TOOLSET and CMAKE_GENERATOR_PLATFORM for that. They are already passed to other generator contexts automatically. The ,-separated key=value field syntax used in CMAKE_GENERATOR_TOOLSET could be used to encode tooling preferences for this use case too.

1 Like

Yes, .Net is a platform that CMake can use with VS and Ninja generators.

Using toolset selection sounds like a good idea. The modified proposal then would be to introduce a new key for Toolset with name dotnet and any non-empty value as signal to try using dotnet tools. Does that sound good?

How would the toolset work for Ninja generator?

Thanks for all the comments Brad and Ben. Appreciated!

1 Like

The modified proposal then would be to introduce a new key for Toolset with name dotnet and any non-empty value as signal to try using dotnet tools.

Let’s not accept more values than necessary. Maybe just tools=dotnet? And reject other values.

How would the toolset work for Ninja generator?

It wouldn’t work for that. The Ninja generator expects a command-line environment and uses environment variables and/or explicit settings like -DCMAKE_CSharp_COMPILER=....

How does one compile .cs source files by hand using dotnet at the command prompt?

Sounds good to me.

Currently, there isn’t a way to compile a .cs source file using dotnet at the command prompt. The documentations on dotnet build and dotnet msbuild only refer to project files (.csproj etc.).

For testing out the toolchain, can we try something like the examples in Get Started?

If .csproj files are the only way to compile using the dotnet tools, then we will only be able to support them through the Visual Studio generators.

Why can’t CMake use a barebones HelloWorld.csproj to test compilation of a file? If CMake can use the bare-bones HelloWorld.csproj and Program.cs to test that dotnet tools are on a build environment, the rest of the workflow - generating csproj, using dotnet build, etc. would be generator-agnostic and work on multiple platforms.

The problem with that (for me) is listing dependencies properly. How is ninja (or make) supposed to know that some external change could require a recompile of the C# project (basically, the depfiles for the dotnet build command)? For C or C++ this would be a header changing, but it is basically any file incidentally used by dotnet build anywhere in its process execution tree. Incremental builds would cause this to be partial. So I can see this working if dotnet build can output a list of files that matter to it (even in an incremental build of itself).

With the SDK-style projects, there is a component of watching directories to see if there are any changes in .cs files or any new .cs files. This is controlled by the property EnableDefaultItems which is true by default.

In my draft MR, I had this set to false and then list out the files that the user specifies in CMakeLists.txt. With that configuration, the depfiles for dotnet build would be the generated .csproj and the .cs files. And, the depfile for generating .csproj would be CMakeLists.txt.

@ben.boeckel: Does that feel like a solution to your problem?

1 Like

There’s no possibility of user props, external files that are read during dotnet build (linking, imports, etc.), or other such things? If not, then it could be possible in principle I suppose. I’m not sure how much it works with things like the sub-targets of the makefiles generators (/all and friends) either.

User props, external files etc. will be used as specified in the project file. Behavior doesn’t change between what CMake does now using command line msbuild and what CMake will do with SDK-style project using dotnet build.

To go forward, should I create a new issue in CMake repo and send merge request for dotnet CMAKE_GENERATOR_TOOLSET? I will work on finalizing my MR to generate SDK-style projects (6634).

I think we’ve concluded here that we’ll only be able to support the dotnet tooling in the Visual Studio generators for now, as is the case currently with C# in general anyway.

Moving forward on the CMAKE_GENERATOR_TOOLSET approach sounds good to me, though some other things may need to be worked out first: