Skip to content

[WIP] Gazelle plugin #381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

[WIP] Gazelle plugin #381

wants to merge 2 commits into from

Conversation

purkhusid
Copy link
Collaborator

No description provided.

@LavaToaster
Copy link

LavaToaster commented Apr 14, 2025

I've created a basic working example of rules_dotnet Gazelle integration at https://github.com/LavaToaster/monorepo/blob/main/README.dotnet.md.

It implements:

  1. A Gazelle rule generator for csharp_ rules
  2. A nuget2bazel tool for package management

My goal is to make the .NET development experience as seamless as possible with Bazel. Ideally, developers should be able to:

  • Work primarily in their IDE using standard .NET project files (csproj, fsproj, etc.)
  • Only need two additional steps for Bazel integration:
    • Run a tool to translate NuGet lock files into Bazel-compatible format
    • Execute Gazelle with the dotnet plugin

@purkhusid - Could you review this implementation and suggest what improvements I should make before attempting to upstream this to the main rules_dotnet repository?

I would keep in mind that the gazelle generator and the nuget2bazel tool are quite coupled in their output expectations. The gazelle generator isn't currently F# aware, nor compatible with the paket2bazel tool.

@purkhusid
Copy link
Collaborator Author

I'm on vacation for the next couple of weeks but I'll definitely take a look once I'm back!

@purkhusid
Copy link
Collaborator Author

purkhusid commented Apr 25, 2025

@LavaToaster I took a quick look at the Gazelle plugin and it looks pretty straight forward to me.

One thing that I think might make sense instead of using XML parsing is to use the msbuild binary to query each csproj/fsproj file for the information.

The upside of this is that it will take into account all MSBuild quirks like e.g. if you have a Directory.Build.props in the root of your repo that defines common things then doing e.g. dotnet msbuild -getItem:Compile will take that into account. It might be necessary to use a combination of msbuildand XML parsing since I'm not sure if you can query everything using the msbuild CLI e.g. I did not find a way to fetch the Sdk attribute in <Project Sdk="Microsoft.NET.Sdk">.

More info on the MSBuild CLI here: https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference?view=vs-2022
And here: https://learn.microsoft.com/en-us/visualstudio/msbuild/evaluate-items-and-properties?view=vs-2022

@LavaToaster
Copy link

Hey @purkhusid,

I can certainly do that! Didn't realise I could query via msbuild so that looks promising. 👀

I have pulled out a few issues in this project with rules_dotnet:

  1. Dependency Analyzers do not override Targeting Pack Analyzers. I.E Targeting Pack brings in 8.0.0 Microsoft.Extensions.Configuration.Binder, but a dependency uses 9.0.4 of that, the resulting args include both to the csc compiler. Source generator issue in Microsoft.Configuration.Binder package #467 I have attached a patch for this, but I'm not really happy with it since I feel there's something I'm probably missing. (Aside from my debug code mind you)
  2. When I resolve that issue, my project src/dbot/DBot.Bot does build however it runs into runtime issues.
  3. For some reason when I give explicit dependencies it can't find all the DLLs.

It's been a little bit since I've been in context but might be worth having a quick call with you to understand a little better what I can do to ensure that either I'm fixing something in rules_dotnet, or fixing the gazelle generator. Any small guidance should be able to push me in the right direction.

patch.diff
diff --git a/dotnet/private/common.bzl b/dotnet/private/common.bzl
index c330647..e531010 100644
--- a/dotnet/private/common.bzl
+++ b/dotnet/private/common.bzl
@@ -215,7 +215,7 @@ def _format_ref_with_overrides(assembly):
     return "-r:" + assembly.path
 
 def format_ref_arg(args, refs):
-    """Takes 
+    """Takes
 
     Args:
         args: The args object that will be sent into the compilation action
@@ -267,6 +267,10 @@ def collect_compile_info(name, deps, targeting_pack, exports, strict_deps):
     targeting_pack_overrides = {}
     framework_list = {}
     framework_files = []
+    targeting_pack_analyzers = {}
+    targeting_pack_analyzers_csharp = {}
+    targeting_pack_analyzers_fsharp = {}
+    targeting_pack_analyzers_vb = {}
 
     if targeting_pack:
         targeting_pack_info = targeting_pack[DotnetTargetingPackInfo]
@@ -282,10 +286,16 @@ def collect_compile_info(name, deps, targeting_pack, exports, strict_deps):
             if len(nuget_info.framework_list) == 0:
                 framework_files.extend(compile_info.irefs)
 
-            direct_analyzers.extend(compile_info.analyzers)
-            direct_analyzers_csharp.extend(compile_info.analyzers_csharp)
-            direct_analyzers_fsharp.extend(compile_info.analyzers_fsharp)
-            direct_analyzers_vb.extend(compile_info.analyzers_vb)
+            # Store targeting pack analyzers separately so they can be overridden by direct deps
+            for analyzer in compile_info.analyzers:
+                targeting_pack_analyzers[analyzer.basename] = analyzer
+            for analyzer in compile_info.analyzers_csharp:
+                targeting_pack_analyzers_csharp[analyzer.basename] = analyzer
+            for analyzer in compile_info.analyzers_fsharp:
+                targeting_pack_analyzers_fsharp[analyzer.basename] = analyzer
+            for analyzer in compile_info.analyzers_vb:
+                targeting_pack_analyzers_vb[analyzer.basename] = analyzer
+
             direct_compile_data.extend(compile_info.compile_data)
 
     for dep in deps:
@@ -294,24 +304,52 @@ def collect_compile_info(name, deps, targeting_pack, exports, strict_deps):
         add_to_output = True
         if assembly.name.lower() in targeting_pack_overrides:
             if semver.to_comparable(assembly.version) > semver.to_comparable(targeting_pack_overrides[assembly.name.lower()], relaxed = True):
-                framework_list.pop(assembly.name.lower())
+                framework_list.pop(assembly.name.lower(), None)
                 add_to_output = True
             else:
                 add_to_output = False
         elif assembly.name.lower() in framework_list:
             if semver.to_comparable(assembly.version) > semver.to_comparable(framework_list[assembly.name.lower()].get("version"), relaxed = True):
-                framework_list.pop(assembly.name.lower())
+                framework_list.pop(assembly.name.lower(), None)
                 add_to_output = True
             else:
                 add_to_output = False
 
+        if name == "DBot.Bot" and assembly.name.lower() == "microsoft.extensions.configuration.binder":
+            print("Assembly: {}, Version: {}, Targeting pack: {}, Add to output: {}".format(assembly.name, assembly.version, targeting_pack_overrides.get(assembly.name.lower()), add_to_output))
+            print(assembly.name.lower() in targeting_pack_overrides)
+            print(assembly.name.lower() in framework_list)
+            print(direct_analyzers)
+            print(direct_analyzers_csharp)
+            print(direct_analyzers_fsharp)
+            print(direct_analyzers_vb)
+            print(direct_compile_data)
+
         if add_to_output:
             direct_iref.extend(assembly.irefs if name in assembly.internals_visible_to else assembly.refs)
             direct_ref.extend(assembly.refs)
-            direct_analyzers.extend(assembly.analyzers)
-            direct_analyzers_csharp.extend(assembly.analyzers_csharp)
-            direct_analyzers_fsharp.extend(assembly.analyzers_fsharp)
-            direct_analyzers_vb.extend(assembly.analyzers_vb)
+
+            # For each direct dependency, override targeting pack analyzers with the same name
+            for analyzer in assembly.analyzers:
+                if analyzer.basename in targeting_pack_analyzers:
+                    targeting_pack_analyzers.pop(analyzer.basename)
+                direct_analyzers.append(analyzer)
+
+            for analyzer in assembly.analyzers_csharp:
+                if analyzer.basename in targeting_pack_analyzers_csharp:
+                    targeting_pack_analyzers_csharp.pop(analyzer.basename)
+                direct_analyzers_csharp.append(analyzer)
+
+            for analyzer in assembly.analyzers_fsharp:
+                if analyzer.basename in targeting_pack_analyzers_fsharp:
+                    targeting_pack_analyzers_fsharp.pop(analyzer.basename)
+                direct_analyzers_fsharp.append(analyzer)
+
+            for analyzer in assembly.analyzers_vb:
+                if analyzer.basename in targeting_pack_analyzers_vb:
+                    targeting_pack_analyzers_vb.pop(analyzer.basename)
+                direct_analyzers_vb.append(analyzer)
+
             direct_compile_data.extend(assembly.compile_data)
 
         # We take all the exports of each dependency and add them

@purkhusid
Copy link
Collaborator Author

@LavaToaster Could you create a minimal reproduction of the issue and link it in #467 ? Have you looked into what version of the Microsoft.Extensions.Configuration.Binder is used when the project is built with MSBuild? I think there needs to be some additiona conflict resolution within rules_dotnet for this case, similar to what we do here:

if semver.to_comparable(assembly.version) > semver.to_comparable(targeting_pack_overrides[assembly.name.lower()], relaxed = True):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants