Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@ matrix:
["--@rules_dotnet//dotnet/settings:strict_deps=true"],
["--@rules_dotnet//dotnet/settings:strict_deps=false"],
]
e2e_working_directory: ["e2e/net8.0", "e2e/net9.0", "e2e/net10.0", "e2e/web_sdk"]
e2e_working_directory:
[
"e2e/dotnet_projects_net8.0",
"e2e/dotnet_projects_net9.0",
"e2e/dotnet_projects_net10.0",
"e2e/net8.0",
"e2e/net9.0",
"e2e/net10.0",
"e2e/web_sdk",
]

tasks:
test_all:
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "bazel_lib", version = "3.0.0")
bazel_dep(name = "rules_shell", version = "0.5.0")
bazel_dep(name = "xml.bzl", version = "0.2.2")

# Dev dependencies
bazel_dep(name = "rules_pkg", version = "1.1.0", dev_dependency = True)
Expand Down
261 changes: 261 additions & 0 deletions MODULE.bazel.lock
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this file. We don't commit the lockfile since we test the rules using multiple Bazel versions.

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion dotnet/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ bzl_library(
"//dotnet/private/rules/csharp:library",
"//dotnet/private/rules/csharp:nunit_test",
"//dotnet/private/rules/csharp:test",
"//dotnet/private/rules/dotnet_projects:generated_props",
"//dotnet/private/rules/fsharp:binary",
"//dotnet/private/rules/fsharp:library",
"//dotnet/private/rules/fsharp:nunit_test",
Expand Down Expand Up @@ -84,7 +85,11 @@ bzl_library(
name = "extensions",
srcs = ["extensions.bzl"],
visibility = ["//visibility:public"],
deps = [":repositories"],
deps = [
":repositories",
"//dotnet/private/rules/dotnet_projects:dotnet_projects_repo",
"//dotnet/private/rules/dotnet_projects:tfm_utils",
],
)

bzl_library(
Expand Down
7 changes: 7 additions & 0 deletions dotnet/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ load(
"//dotnet/private/rules/csharp:test.bzl",
_csharp_test = "csharp_test",
)
load(
"//dotnet/private/rules/dotnet_projects:generated_props.bzl",
_dotnet_generated_props = "dotnet_generated_props",
_dotnet_generated_props_test = "dotnet_generated_props_test",
)
load(
"//dotnet/private/rules/fsharp:binary.bzl",
_fsharp_binary = "fsharp_binary",
Expand Down Expand Up @@ -71,3 +76,5 @@ import_dll = _import_dll
nuget_repo = _nuget_repo
nuget_archive = _nuget_archive
dotnet_tool = _dotnet_tool
dotnet_generated_props = _dotnet_generated_props
dotnet_generated_props_test = _dotnet_generated_props_test
116 changes: 114 additions & 2 deletions dotnet/extensions.bzl
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"extensions for bzlmod"

load("//dotnet/private/rules/dotnet_projects:dotnet_projects_repo.bzl", "dotnet_projects_repo")
load(":repositories.bzl", "dotnet_register_toolchains")

_DEFAULT_NAME = "dotnet"

_ATTRS = {
# buildifier: disable=unsorted-dict-items
_TOOLCHAIN_ATTRS = {
"name": attr.string(
doc = "Base name for generated repositories",
default = _DEFAULT_NAME,
Expand All @@ -14,7 +16,36 @@ _ATTRS = {
),
}

# buildifier: disable=unsorted-dict-items
_SCAN_PROJECTS_ATTRS = {
"exclude_patterns": attr.string_list(
doc = """Glob patterns for paths to exclude from scanning.

Default patterns exclude bin/, obj/, .git/, .jj/, and bazel-* directories.""",
),
"nuget_repo_name": attr.string(
default = "dotnet_projects.nuget",
doc = """Name of the NuGet repository to generate.

Packages can be referenced as @{nuget_repo_name}//PackageName.""",
),
"auto_register_toolchains": attr.bool(
default = False,
doc = """If true, automatically register toolchains for discovered TFMs.

When enabled, the extension will analyze target frameworks from all discovered
.csproj/.fsproj files and register any missing toolchains needed to build them.""",
),
"fail_on_missing_toolchain": attr.bool(
default = True,
doc = """If true, fail when a project targets a TFM not covered by registered toolchains.

Set to false to only emit warnings instead of failing.""",
),
}

def _toolchain_extension(module_ctx):
# Collect registered toolchains
registrations = {}
for mod in module_ctx.modules:
for toolchain in mod.tags.toolchain:
Expand All @@ -33,16 +64,97 @@ def _toolchain_extension(module_ctx):
))
else:
registrations[toolchain.name] = toolchain.dotnet_version

# Collect scan_projects configuration
scan_config = None
for mod in module_ctx.modules:
for config in mod.tags.scan_projects:
if scan_config != None:
# Only use first scan_projects configuration (from root module)
continue
scan_config = config

# Register toolchains
for name, dotnet_version in registrations.items():
dotnet_register_toolchains(
name = name,
dotnet_version = dotnet_version,
register = False,
)

# Handle project scanning if enabled
repos_to_return = []
if scan_config != None:
# Configure exclude patterns
exclude_patterns = ["**/bin/**", "**/obj/**", "**/.git/**", "**/.jj/**", "**/bazel-*/**"]
if scan_config.exclude_patterns:
exclude_patterns = exclude_patterns + list(scan_config.exclude_patterns)

nuget_repo_name = scan_config.nuget_repo_name if scan_config.nuget_repo_name else "dotnet_projects.nuget"

# Create the csproj repository
# The repository rule will use @@//:MODULE.bazel to find the workspace root
dotnet_projects_repo(
name = "dotnet_projects",
exclude_patterns = exclude_patterns,
nuget_repo_name = nuget_repo_name,
# Pass registered toolchains for validation
registered_toolchains = json.encode(registrations),
auto_register_toolchains = scan_config.auto_register_toolchains,
fail_on_missing_toolchain = scan_config.fail_on_missing_toolchain,
)
repos_to_return.append("dotnet_projects")

return module_ctx.extension_metadata(
reproducible = False if scan_config else True,
root_module_direct_deps = repos_to_return if repos_to_return else "all",
root_module_direct_dev_deps = [],
)

dotnet = module_extension(
implementation = _toolchain_extension,
# buildifier: disable=unsorted-dict-items
tag_classes = {
"toolchain": tag_class(attrs = _ATTRS),
"toolchain": tag_class(attrs = _TOOLCHAIN_ATTRS),
"scan_projects": tag_class(
attrs = _SCAN_PROJECTS_ATTRS,
doc = """Enable automatic scanning of .csproj and .fsproj files.

When enabled, the extension will:
1. Scan the workspace for .csproj and .fsproj files
2. Parse each file to extract properties, sources, and dependencies
3. Generate a @dotnet_projects repository with .bzl files for each project
4. Validate that registered toolchains cover all discovered target frameworks

Usage in MODULE.bazel:
dotnet = use_extension("@rules_dotnet//dotnet:extensions.bzl", "dotnet")
dotnet.toolchain(dotnet_version = "10.0.100")
dotnet.scan_projects()
use_repo(dotnet, "dotnet_toolchains", "dotnet_projects")

Usage in BUILD files:
load("@dotnet_projects//path/to:MyProject.csproj.bzl", "auto_dotnet_targets")
auto_dotnet_targets(name = "MyProject")

File Change Detection:
Changes to existing .csproj/.fsproj files are automatically detected.
New files in existing project directories are also detected.

However, new project files in entirely new directories require manual sync:
bazel sync --only=@dotnet_projects

Requirements:
Bazel 7.1 or later is required for the scan_projects feature.
""",
),
},
doc = """Extension for .NET toolchains and project scanning.

This extension provides two main features:
1. Toolchain registration via dotnet.toolchain()
2. Project scanning via dotnet.scan_projects()

The extension will validate that registered toolchains can build all discovered
target frameworks, and optionally auto-register missing toolchains.
""",
)
55 changes: 55 additions & 0 deletions dotnet/private/rules/dotnet_projects/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

bzl_library(
name = "parser",
srcs = ["parser.bzl"],
visibility = ["//dotnet:__subpackages__"],
deps = [
"@xml.bzl//:xml",
],
)

bzl_library(
name = "nuget_collector",
srcs = ["nuget_collector.bzl"],
visibility = ["//dotnet:__subpackages__"],
)

bzl_library(
name = "generator",
srcs = ["generator.bzl"],
visibility = ["//dotnet:__subpackages__"],
deps = [
":parser",
"@bazel_skylib//lib:paths",
],
)

bzl_library(
name = "tfm_utils",
srcs = ["tfm_utils.bzl"],
visibility = ["//dotnet:__subpackages__"],
)

bzl_library(
name = "dotnet_projects_repo",
srcs = ["dotnet_projects_repo.bzl"],
visibility = ["//dotnet:__subpackages__"],
deps = [
":generator",
":nuget_collector",
":parser",
":tfm_utils",
"@bazel_lib//lib:glob_match",
],
)

bzl_library(
name = "generated_props",
srcs = ["generated_props.bzl"],
visibility = ["//visibility:public"],
deps = [
"@bazel_lib//lib:write_source_files",
"@xml.bzl//:xml",
],
)
Loading