rules_dotnet icon indicating copy to clipboard operation
rules_dotnet copied to clipboard

`ImplicitUsings` is not handled

Open sin-ack opened this issue 1 year ago • 8 comments

Since rules_dotnet doesn't run MSBuild, the Using directives added by the Task files in the .NET SDK are not handled, causing the build to fail unless the auto-generated {Assembly}.GlobalUsings.g.cs is included in srcs.

For the time being, I'm hand-maintaining the following file:

// NOTE: Keep this file in sync with the following Using directives
//       whenever the .NET SDK version is updated:
//
//       https://github.com/dotnet/sdk/blob/release/8.0.2xx/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.CSharp.props#L26
//       https://github.com/dotnet/sdk/blob/release/8.0.2xx/src/WebSdk/Web/Targets/Sdk.Server.props#L64

global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
global using global::System.Net.Http.Json;
global using global::Microsoft.AspNetCore.Builder;
global using global::Microsoft.AspNetCore.Hosting;
global using global::Microsoft.AspNetCore.Http;
global using global::Microsoft.AspNetCore.Routing;
global using global::Microsoft.Extensions.Configuration;
global using global::Microsoft.Extensions.DependencyInjection;
global using global::Microsoft.Extensions.Hosting;
global using global::Microsoft.Extensions.Logging;

But obviously this isn't very nice. Would there be anyway to generate this file through Bazel somehow?

sin-ack avatar Apr 24 '24 09:04 sin-ack

I'm not sure how this list is generated so It's hard to tell if it would be worth the maintenance cost to add this feature to rules_dotnet. This can be worked around by the end user by using a bazel macro around the csharp_library/binary fairly easily.

If you have any further information on how this list is generated then that would help with making the decision.

purkhusid avatar Apr 24 '24 09:04 purkhusid

I actually looked into it a little bit. The way it works is as follows:

  • ImplicitUsings is set to enabled here, which enables it for everyone using Microsoft.NET.Sdk and its children:

https://github.com/dotnet/sdk/blob/f48021c3202146e8ba6c8364fc619b31bef84d89/Directory.Build.props#L48

  • In each SDK, there's a .props file which contains a conditional ItemGroup that adds the Using statements:

https://github.com/dotnet/sdk/blob/a9db5d8e5f90e64e28dff757c70a934a70ac73ef/src/WebSdk/Web/Targets/Sdk.Server.props#L64

  • Finally, there's a custom task that gets executed during the build, which generates the {AssemblyName}.GlobalUsings.g.cs file:

https://github.com/dotnet/sdk/blob/f48021c3202146e8ba6c8364fc619b31bef84d89/src/Tasks/Microsoft.NET.Build.Tasks/GenerateGlobalUsings.cs#L8 https://github.com/dotnet/sdk/blob/f48021c3202146e8ba6c8364fc619b31bef84d89/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.GenerateGlobalUsings.targets#L23

I might be incorrect, but from what I can understand, the only "proper" ways to get these Using targets is:

  • Maintaining them as a catalog in rules_dotnet, and updating them with new .NET releases
  • Parsing the XML files for the <Using /> tags (brittle)
  • Actually running MSBuild

sin-ack avatar Apr 24 '24 10:04 sin-ack

I've just hit this too, my build is failing. I'm not quite sure what to do.

peakschris avatar Jul 01 '24 19:07 peakschris

@peakschris You wrap the rules with your own macro where you provide a source file with the global imports. But I think I would also accept an contribution that adds the global imports in a maintainable way.

purkhusid avatar Jul 01 '24 22:07 purkhusid

What I did at work for now:

  • Create a folder called GlobalUsings at the top level, containing files like Microsoft.NET.SDK.cs.
  • Each file contains the GlobalUsings.g.cs contents that would be generated for that SDK, along with a link to which .props file in https://github.com/dotnet/sdk they are generated from.
  • Each project within the solution contains a GlobalUsings folder, and relatively symlinks the SDKs they use, i.e. a project using the Microsoft.NET.SDK.Web SDK will symlink both Microsoft.NET.SDK.cs and Microsoft.NET.SDK.Web.cs.

(Make sure to add <ImplicitUsings>false</ImplicitUsings> to your csproj files to avoid duplicates in the MSBuild workflow, if you intend to have one.)

sin-ack avatar Jul 03 '24 07:07 sin-ack

Want this feature to simplify the migration. Almost all of my projects used this feature.

hcoona avatar Jul 04 '24 02:07 hcoona

It works fine to create an extra .GlobalUsings.g.cs file and add to the project that contains the using statements. This can be added explicitly or in a wrapped rule:

MyLibrary.GlobalUsings.g.cs:

global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;

But it would be far better if this file was auto-generated in the same way that msbuild does.

peakschris avatar Jul 04 '24 02:07 peakschris

I know this issue is a little old, but the team I work with implemented support for this as suggested by @purkhusid - we intend to contribute it, but it needs some tidying to make it more generally manageable. In particular, we want to provide a better way to manage the namespaces across multiple SDKs and framework versions.

However it isn't super complicated to implement, so an outline for what we did might be helpful:

  • We wrapped the rules_dotnet rules with our own macros
  • Made a map (using the SDK as a key) of the implicit namespaces, based on the MS docs
  • Used a genrule (see below) to create a cs file with a global using statement for each namespace
  • Appended the generated source file to the list of srcs passed to rules_dotnet

We also added an attribute to allow other global usings to be supplied (like the <Using> msbuild element), simply adding them to the list of implicit namespaces.

The code for the genrule isn't especially complex either:

def _create_global_usings_file(filename, namespaces):
    content = "\n".join(["global using global::%s;" % ns for ns in namespaces])
    native.genrule(
        name = filename.replace(".cs", ""),
        outs = [filename],
        cmd = "echo '" + content + "' > $@",
    )

matthewkenny avatar Sep 06 '24 16:09 matthewkenny