From b80f46b6fab015d658e8bd4a34dade100eacb8b1 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Tue, 24 Mar 2026 21:46:31 +0800
Subject: [PATCH 1/3] =?UTF-8?q?feat(build):=20=E6=B7=BB=E5=8A=A0=20GFramew?=
=?UTF-8?q?ork=20=E6=A8=A1=E5=9D=97=E5=8C=96=E5=85=A8=E5=B1=80=E5=91=BD?=
=?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 NuGet 包中实现可选的 transitive global usings 功能
- 添加 XML 配置方式启用模块级自动命名空间导入
- 支持通过 GFrameworkExcludedUsing 排除特定命名空间
- 为所有运行时模块生成对应的 buildTransitive props 文件
- 添加 Python 脚本自动生成和验证命名空间配置
- 在文档中添加新的安装配置说明
- 创建单元测试验证生成脚本的同步状态
---
.../GFramework.Core.Abstractions.csproj | 5 +
...GeWuYou.GFramework.Core.Abstractions.props | 47 ++++
.../TransitiveGlobalUsingsGenerationTests.cs | 74 ++++++
GFramework.Core/GFramework.Core.csproj | 5 +
.../GeWuYou.GFramework.Core.props | 53 +++++
.../GFramework.Ecs.Arch.Abstractions.csproj | 6 +-
...You.GFramework.Ecs.Arch.Abstractions.props | 10 +
.../GFramework.Ecs.Arch.csproj | 6 +-
.../GeWuYou.GFramework.Ecs.Arch.props | 13 +
.../GFramework.Game.Abstractions.csproj | 5 +
...GeWuYou.GFramework.Game.Abstractions.props | 19 ++
GFramework.Game/GFramework.Game.csproj | 5 +
.../GeWuYou.GFramework.Game.props | 21 ++
GFramework.Godot/GFramework.Godot.csproj | 5 +
.../GeWuYou.GFramework.Godot.props | 22 ++
README.md | 26 ++
docs/zh-CN/getting-started/installation.md | 29 ++-
global-usings.modules.json | 161 +++++++++++++
scripts/generate-module-global-usings.py | 222 ++++++++++++++++++
19 files changed, 722 insertions(+), 12 deletions(-)
create mode 100644 GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props
create mode 100644 GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs
create mode 100644 GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props
create mode 100644 GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props
create mode 100644 GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props
create mode 100644 GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props
create mode 100644 GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props
create mode 100644 GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props
create mode 100644 global-usings.modules.json
create mode 100644 scripts/generate-module-global-usings.py
diff --git a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj
index f0cec86..d5f1779 100644
--- a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj
+++ b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj
@@ -27,4 +27,9 @@
+
+
+
+
+
diff --git a/GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props b/GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props
new file mode 100644
index 0000000..67cca44
--- /dev/null
+++ b/GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props
@@ -0,0 +1,47 @@
+
+
+
+
+
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Architectures"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Bases"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Command"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Concurrency"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Configuration"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Controller"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Coroutine"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Command"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Notification"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Query"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Request"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Data"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Enums"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Environment"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Events"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Ioc"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Lifecycle"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Localization"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Logging"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Model"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Pause"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Pool"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Properties"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Property"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Query"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Registries"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Resource"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Rule"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Serializer"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.State"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.StateManagement"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Storage"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Systems"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Time"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Utility"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Utility.Numeric"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Versioning"/>
+ <_GFramework_Core_Abstractions_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
+
+
+
diff --git a/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs b/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs
new file mode 100644
index 0000000..e840a86
--- /dev/null
+++ b/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs
@@ -0,0 +1,74 @@
+using System.Diagnostics;
+using System.IO;
+
+namespace GFramework.Core.Tests.Packaging;
+
+///
+/// 验证模块级可选 Global Usings 的生成脚本与仓库中的已提交产物保持同步。
+/// 该测试用于防止新增模块、命名空间清单或打包声明发生漂移后静默进入仓库。
+///
+[TestFixture]
+public class TransitiveGlobalUsingsGenerationTests
+{
+ ///
+ /// 验证生成脚本的检查模式可以在当前仓库状态下通过。
+ /// 如果此断言失败,说明清单、生成的 props 文件或 csproj 打包声明至少有一处未同步。
+ ///
+ [Test]
+ public void GenerateModuleGlobalUsingsScript_CheckMode_Should_Pass()
+ {
+ var repositoryRoot = FindRepositoryRoot();
+ using var process = StartScriptCheck(repositoryRoot);
+
+ var standardOutput = process.StandardOutput.ReadToEnd();
+ var standardError = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+
+ Assert.That(
+ process.ExitCode,
+ Is.EqualTo(0),
+ $"Expected module global usings generation to be up to date.{System.Environment.NewLine}" +
+ $"stdout:{System.Environment.NewLine}{standardOutput}{System.Environment.NewLine}" +
+ $"stderr:{System.Environment.NewLine}{standardError}");
+ }
+
+ ///
+ /// 启动生成脚本的检查模式。
+ /// 该模式不会修改仓库内容,只验证仓库中的生成产物是否已与当前规则对齐。
+ ///
+ /// 仓库根目录。
+ /// 已启动的进程实例。
+ private static Process StartScriptCheck(string repositoryRoot)
+ {
+ var startInfo = new ProcessStartInfo("python3", "scripts/generate-module-global-usings.py --check")
+ {
+ WorkingDirectory = repositoryRoot,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false
+ };
+
+ return Process.Start(startInfo)
+ ?? throw new InvalidOperationException("Failed to start the module global usings generation check.");
+ }
+
+ ///
+ /// 从测试输出目录向上回溯,定位包含解决方案文件的仓库根目录。
+ ///
+ /// 仓库根目录绝对路径。
+ private static string FindRepositoryRoot()
+ {
+ var currentDirectory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);
+
+ while (currentDirectory != null)
+ {
+ var solutionPath = Path.Combine(currentDirectory.FullName, "GFramework.sln");
+ if (File.Exists(solutionPath))
+ return currentDirectory.FullName;
+
+ currentDirectory = currentDirectory.Parent;
+ }
+
+ throw new DirectoryNotFoundException("Could not locate the repository root for GFramework.");
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Core/GFramework.Core.csproj b/GFramework.Core/GFramework.Core.csproj
index bc78025..0ef4bcd 100644
--- a/GFramework.Core/GFramework.Core.csproj
+++ b/GFramework.Core/GFramework.Core.csproj
@@ -14,4 +14,9 @@
+
+
+
+
+
diff --git a/GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props b/GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props
new file mode 100644
index 0000000..b666aa9
--- /dev/null
+++ b/GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props
@@ -0,0 +1,53 @@
+
+
+
+
+
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Architectures"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Command"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Concurrency"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Configuration"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Constants"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Coroutine"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Coroutine.Extensions"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Coroutine.Instructions"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Behaviors"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Command"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Notification"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Query"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Request"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Environment"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Events"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Events.Filters"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Extensions"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Async"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Control"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Functions"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Pipe"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Ioc"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Localization"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Localization.Formatters"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging.Appenders"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging.Filters"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging.Formatters"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Model"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Pause"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Pool"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Property"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Query"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Resource"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Rule"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Services"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Services.Modules"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.State"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.StateManagement"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Systems"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Time"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Utility"/>
+ <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Utility.Numeric"/>
+ <_GFramework_Core_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
+
+
+
diff --git a/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj
index b937843..d9550a3 100644
--- a/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj
+++ b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj
@@ -9,5 +9,9 @@
-
+
+
+
+
+
diff --git a/GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props b/GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props
new file mode 100644
index 0000000..5816a35
--- /dev/null
+++ b/GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props
@@ -0,0 +1,10 @@
+
+
+
+
+
+ <_GFramework_Ecs_Arch_Abstractions_TransitiveUsing Include="GFramework.Ecs.Arch.Abstractions"/>
+ <_GFramework_Ecs_Arch_Abstractions_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
+
+
+
diff --git a/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj
index 2b58a0e..68a8916 100644
--- a/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj
+++ b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj
@@ -19,5 +19,9 @@
-
+
+
+
+
+
diff --git a/GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props b/GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props
new file mode 100644
index 0000000..1776e61
--- /dev/null
+++ b/GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props
@@ -0,0 +1,13 @@
+
+
+
+
+
+ <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch"/>
+ <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch.Components"/>
+ <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch.Extensions"/>
+ <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch.Systems"/>
+ <_GFramework_Ecs_Arch_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
+
+
+
diff --git a/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj b/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj
index 409352d..62aef63 100644
--- a/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj
+++ b/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj
@@ -28,4 +28,9 @@
runtime; build; native; contentfiles; analyzers
+
+
+
+
+
diff --git a/GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props b/GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props
new file mode 100644
index 0000000..f7319ff
--- /dev/null
+++ b/GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props
@@ -0,0 +1,19 @@
+
+
+
+
+
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Asset"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Data"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Data.Events"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Enums"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Routing"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Scene"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Setting"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Setting.Data"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Storage"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.UI"/>
+ <_GFramework_Game_Abstractions_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
+
+
+
diff --git a/GFramework.Game/GFramework.Game.csproj b/GFramework.Game/GFramework.Game.csproj
index 4c3cce7..37bc8a8 100644
--- a/GFramework.Game/GFramework.Game.csproj
+++ b/GFramework.Game/GFramework.Game.csproj
@@ -14,4 +14,9 @@
+
+
+
+
+
diff --git a/GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props b/GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props
new file mode 100644
index 0000000..b7749c7
--- /dev/null
+++ b/GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props
@@ -0,0 +1,21 @@
+
+
+
+
+
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Data"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Extensions"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Routing"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Scene"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Scene.Handler"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Serializer"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Setting"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Setting.Events"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.State"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Storage"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.UI"/>
+ <_GFramework_Game_TransitiveUsing Include="GFramework.Game.UI.Handler"/>
+ <_GFramework_Game_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
+
+
+
diff --git a/GFramework.Godot/GFramework.Godot.csproj b/GFramework.Godot/GFramework.Godot.csproj
index 5d79219..44e037c 100644
--- a/GFramework.Godot/GFramework.Godot.csproj
+++ b/GFramework.Godot/GFramework.Godot.csproj
@@ -22,4 +22,9 @@
+
+
+
+
+
diff --git a/GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props b/GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props
new file mode 100644
index 0000000..ac14337
--- /dev/null
+++ b/GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props
@@ -0,0 +1,22 @@
+
+
+
+
+
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Architectures"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Coroutine"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Data"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Extensions"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Extensions.Signal"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Logging"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Pause"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Pool"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Scene"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Setting"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Setting.Data"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Storage"/>
+ <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.UI"/>
+ <_GFramework_Godot_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
+
+
+
diff --git a/README.md b/README.md
index e9bb84a..fc7a8fe 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,32 @@ dotnet add package GeWuYou.GFramework.Godot
dotnet add package GeWuYou.GFramework.SourceGenerators
```
+## 可选模块导入
+
+发布后的运行时包支持可选的模块级自动导入,但默认关闭,避免在普通项目里无意污染命名空间。
+
+在 NuGet 消费项目中显式开启:
+
+```xml
+
+ true
+
+```
+
+启用后,项目已引用的 GFramework 运行时模块会通过 `buildTransitive` 自动注入其推荐命名空间。
+
+如果某几个命名空间不想导入,可以局部排除:
+
+```xml
+
+
+
+
+```
+
+> 该能力面向 NuGet 包消费场景。若你在本地解决方案中直接使用 `ProjectReference`,仍建议保留自己的 `GlobalUsings.cs` 或手写
+`using`。
+
## 仓库结构
```text
diff --git a/docs/zh-CN/getting-started/installation.md b/docs/zh-CN/getting-started/installation.md
index 74020b1..c6b8618 100644
--- a/docs/zh-CN/getting-started/installation.md
+++ b/docs/zh-CN/getting-started/installation.md
@@ -88,19 +88,28 @@ dotnet add package GeWuYou.GFramework.SourceGenerators
### 1. 基础配置
-创建 `GlobalUsings.cs` 文件:
+如果你通过 NuGet 包使用 GFramework,并且希望自动导入已安装模块的推荐命名空间,可以在项目文件中显式开启:
-```csharp
-global using GFramework.Core;
-global using GFramework.Core.Architecture;
-global using GFramework.Core.Command;
-global using GFramework.Core.Events;
-global using GFramework.Core.Model;
-global using GFramework.Core.Property;
-global using GFramework.Core.System;
-global using GFramework.Core.Utility;
+```xml
+
+ true
+
```
+启用后,当前项目已引用的 GFramework 运行时模块会通过 `buildTransitive` 自动注入对应命名空间。
+
+如果你想排除局部导入,可以继续在项目文件中添加排除项:
+
+```xml
+
+
+
+
+
+```
+
+如果你使用的是本地 `ProjectReference`,或者希望完全手动控制导入范围,仍然可以继续维护自己的 `GlobalUsings.cs` 文件。
+
### 2. Godot 项目配置
如果使用 Godot 集成,需要在项目设置中启用 C# 支持:
diff --git a/global-usings.modules.json b/global-usings.modules.json
new file mode 100644
index 0000000..6db53da
--- /dev/null
+++ b/global-usings.modules.json
@@ -0,0 +1,161 @@
+{
+ "modules": [
+ {
+ "project": "GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj",
+ "namespaces": [
+ "GFramework.Core.Abstractions.Architectures",
+ "GFramework.Core.Abstractions.Bases",
+ "GFramework.Core.Abstractions.Command",
+ "GFramework.Core.Abstractions.Concurrency",
+ "GFramework.Core.Abstractions.Configuration",
+ "GFramework.Core.Abstractions.Controller",
+ "GFramework.Core.Abstractions.Coroutine",
+ "GFramework.Core.Abstractions.Cqrs",
+ "GFramework.Core.Abstractions.Cqrs.Command",
+ "GFramework.Core.Abstractions.Cqrs.Notification",
+ "GFramework.Core.Abstractions.Cqrs.Query",
+ "GFramework.Core.Abstractions.Cqrs.Request",
+ "GFramework.Core.Abstractions.Data",
+ "GFramework.Core.Abstractions.Enums",
+ "GFramework.Core.Abstractions.Environment",
+ "GFramework.Core.Abstractions.Events",
+ "GFramework.Core.Abstractions.Ioc",
+ "GFramework.Core.Abstractions.Lifecycle",
+ "GFramework.Core.Abstractions.Localization",
+ "GFramework.Core.Abstractions.Logging",
+ "GFramework.Core.Abstractions.Model",
+ "GFramework.Core.Abstractions.Pause",
+ "GFramework.Core.Abstractions.Pool",
+ "GFramework.Core.Abstractions.Properties",
+ "GFramework.Core.Abstractions.Property",
+ "GFramework.Core.Abstractions.Query",
+ "GFramework.Core.Abstractions.Registries",
+ "GFramework.Core.Abstractions.Resource",
+ "GFramework.Core.Abstractions.Rule",
+ "GFramework.Core.Abstractions.Serializer",
+ "GFramework.Core.Abstractions.State",
+ "GFramework.Core.Abstractions.StateManagement",
+ "GFramework.Core.Abstractions.Storage",
+ "GFramework.Core.Abstractions.Systems",
+ "GFramework.Core.Abstractions.Time",
+ "GFramework.Core.Abstractions.Utility",
+ "GFramework.Core.Abstractions.Utility.Numeric",
+ "GFramework.Core.Abstractions.Versioning"
+ ]
+ },
+ {
+ "project": "GFramework.Core/GFramework.Core.csproj",
+ "namespaces": [
+ "GFramework.Core.Architectures",
+ "GFramework.Core.Command",
+ "GFramework.Core.Concurrency",
+ "GFramework.Core.Configuration",
+ "GFramework.Core.Constants",
+ "GFramework.Core.Coroutine",
+ "GFramework.Core.Coroutine.Extensions",
+ "GFramework.Core.Coroutine.Instructions",
+ "GFramework.Core.Cqrs.Behaviors",
+ "GFramework.Core.Cqrs.Command",
+ "GFramework.Core.Cqrs.Notification",
+ "GFramework.Core.Cqrs.Query",
+ "GFramework.Core.Cqrs.Request",
+ "GFramework.Core.Environment",
+ "GFramework.Core.Events",
+ "GFramework.Core.Events.Filters",
+ "GFramework.Core.Extensions",
+ "GFramework.Core.Functional",
+ "GFramework.Core.Functional.Async",
+ "GFramework.Core.Functional.Control",
+ "GFramework.Core.Functional.Functions",
+ "GFramework.Core.Functional.Pipe",
+ "GFramework.Core.Ioc",
+ "GFramework.Core.Localization",
+ "GFramework.Core.Localization.Formatters",
+ "GFramework.Core.Logging",
+ "GFramework.Core.Logging.Appenders",
+ "GFramework.Core.Logging.Filters",
+ "GFramework.Core.Logging.Formatters",
+ "GFramework.Core.Model",
+ "GFramework.Core.Pause",
+ "GFramework.Core.Pool",
+ "GFramework.Core.Property",
+ "GFramework.Core.Query",
+ "GFramework.Core.Resource",
+ "GFramework.Core.Rule",
+ "GFramework.Core.Services",
+ "GFramework.Core.Services.Modules",
+ "GFramework.Core.State",
+ "GFramework.Core.StateManagement",
+ "GFramework.Core.Systems",
+ "GFramework.Core.Time",
+ "GFramework.Core.Utility",
+ "GFramework.Core.Utility.Numeric"
+ ]
+ },
+ {
+ "project": "GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj",
+ "namespaces": [
+ "GFramework.Game.Abstractions.Asset",
+ "GFramework.Game.Abstractions.Data",
+ "GFramework.Game.Abstractions.Data.Events",
+ "GFramework.Game.Abstractions.Enums",
+ "GFramework.Game.Abstractions.Routing",
+ "GFramework.Game.Abstractions.Scene",
+ "GFramework.Game.Abstractions.Setting",
+ "GFramework.Game.Abstractions.Setting.Data",
+ "GFramework.Game.Abstractions.Storage",
+ "GFramework.Game.Abstractions.UI"
+ ]
+ },
+ {
+ "project": "GFramework.Game/GFramework.Game.csproj",
+ "namespaces": [
+ "GFramework.Game.Data",
+ "GFramework.Game.Extensions",
+ "GFramework.Game.Routing",
+ "GFramework.Game.Scene",
+ "GFramework.Game.Scene.Handler",
+ "GFramework.Game.Serializer",
+ "GFramework.Game.Setting",
+ "GFramework.Game.Setting.Events",
+ "GFramework.Game.State",
+ "GFramework.Game.Storage",
+ "GFramework.Game.UI",
+ "GFramework.Game.UI.Handler"
+ ]
+ },
+ {
+ "project": "GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj",
+ "namespaces": [
+ "GFramework.Ecs.Arch.Abstractions"
+ ]
+ },
+ {
+ "project": "GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj",
+ "namespaces": [
+ "GFramework.Ecs.Arch",
+ "GFramework.Ecs.Arch.Components",
+ "GFramework.Ecs.Arch.Extensions",
+ "GFramework.Ecs.Arch.Systems"
+ ]
+ },
+ {
+ "project": "GFramework.Godot/GFramework.Godot.csproj",
+ "namespaces": [
+ "GFramework.Godot.Architectures",
+ "GFramework.Godot.Coroutine",
+ "GFramework.Godot.Data",
+ "GFramework.Godot.Extensions",
+ "GFramework.Godot.Extensions.Signal",
+ "GFramework.Godot.Logging",
+ "GFramework.Godot.Pause",
+ "GFramework.Godot.Pool",
+ "GFramework.Godot.Scene",
+ "GFramework.Godot.Setting",
+ "GFramework.Godot.Setting.Data",
+ "GFramework.Godot.Storage",
+ "GFramework.Godot.UI"
+ ]
+ }
+ ]
+}
diff --git a/scripts/generate-module-global-usings.py b/scripts/generate-module-global-usings.py
new file mode 100644
index 0000000..ba040cc
--- /dev/null
+++ b/scripts/generate-module-global-usings.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+
+from __future__ import annotations
+
+import argparse
+import json
+import re
+from dataclasses import dataclass
+from pathlib import Path
+
+
+ROOT_DIR = Path(__file__).resolve().parent.parent
+CONFIG_PATH = ROOT_DIR / "global-usings.modules.json"
+AUTO_GENERATED_START = ""
+AUTO_GENERATED_END = ""
+
+
+@dataclass(frozen=True)
+class ModuleConfig:
+ project_path: Path
+ namespaces: tuple[str, ...]
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="Generate optional transitive global usings for packable GFramework runtime modules.",
+ )
+ parser.add_argument(
+ "--check",
+ action="store_true",
+ help="Validate that generated files are up to date instead of writing them.",
+ )
+ return parser.parse_args()
+
+
+def load_text(path: Path) -> tuple[str, str]:
+ raw = path.read_bytes()
+ if raw.startswith(b"\xef\xbb\xbf"):
+ return raw.decode("utf-8-sig"), "utf-8-sig"
+
+ return raw.decode("utf-8"), "utf-8"
+
+
+def write_text(path: Path, content: str, encoding: str) -> None:
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(content, encoding=encoding)
+
+
+def load_modules() -> list[ModuleConfig]:
+ data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
+ modules: list[ModuleConfig] = []
+
+ for entry in data["modules"]:
+ project_path = ROOT_DIR / entry["project"]
+ namespaces = tuple(dict.fromkeys(entry["namespaces"]))
+ modules.append(ModuleConfig(project_path=project_path, namespaces=namespaces))
+
+ return modules
+
+
+def discover_runtime_modules() -> list[Path]:
+ projects: list[Path] = []
+
+ for project_path in sorted(ROOT_DIR.rglob("*.csproj")):
+ if "obj" in project_path.parts or "bin" in project_path.parts:
+ continue
+
+ if project_path.name == "GFramework.csproj":
+ continue
+
+ project_name = project_path.stem
+ if not project_name.startswith("GFramework."):
+ continue
+
+ if ".Tests" in project_name or "SourceGenerators" in project_name:
+ continue
+
+ csproj_text, _ = load_text(project_path)
+ if re.search(r"\s*false\s*", csproj_text, re.IGNORECASE):
+ continue
+
+ projects.append(project_path)
+
+ return projects
+
+
+def resolve_package_id(project_path: Path) -> str:
+ project_text, _ = load_text(project_path)
+ match = re.search(r"(.*?)", project_text, re.DOTALL)
+ if match is None:
+ return project_path.stem
+
+ package_id = match.group(1).strip()
+ return package_id.replace("$(AssemblyName)", project_path.stem)
+
+
+def sanitize_identifier(text: str) -> str:
+ return re.sub(r"[^A-Za-z0-9_]", "_", text)
+
+
+def render_props(module: ModuleConfig) -> str:
+ item_name = f"_{sanitize_identifier(module.project_path.stem)}_TransitiveUsing"
+ lines = [
+ "",
+ " ",
+ " ",
+ " ",
+ " ",
+ ]
+
+ for namespace in module.namespaces:
+ lines.append(f" <{item_name} Include=\"{namespace}\" />")
+
+ lines.extend(
+ [
+ f" <{item_name} Remove=\"@(GFrameworkExcludedUsing)\" />",
+ f" ",
+ " ",
+ "",
+ "",
+ ],
+ )
+ return "\n".join(lines)
+
+
+def render_pack_block(package_id: str) -> str:
+ props_path = f"buildTransitive\\{package_id}.props"
+ return "\n".join(
+ [
+ f" {AUTO_GENERATED_START}",
+ " ",
+ f" ",
+ " ",
+ f" {AUTO_GENERATED_END}",
+ "",
+ ],
+ )
+
+
+def update_csproj(project_path: Path, package_id: str) -> tuple[str, str]:
+ project_text, encoding = load_text(project_path)
+ block = render_pack_block(package_id)
+ pattern = re.compile(
+ rf"\s*{re.escape(AUTO_GENERATED_START)}.*?{re.escape(AUTO_GENERATED_END)}\s*",
+ re.DOTALL,
+ )
+
+ if pattern.search(project_text):
+ updated = pattern.sub(lambda _: f"\n{block}", project_text)
+ else:
+ updated = project_text.replace("", f"{block}")
+
+ return updated, encoding
+
+
+def validate_module_coverage(modules: list[ModuleConfig]) -> None:
+ configured_projects = {module.project_path.resolve() for module in modules}
+ discovered_projects = {project.resolve() for project in discover_runtime_modules()}
+
+ missing = sorted(project.relative_to(ROOT_DIR) for project in discovered_projects - configured_projects)
+ extra = sorted(project.relative_to(ROOT_DIR) for project in configured_projects - discovered_projects)
+
+ if missing or extra:
+ messages: list[str] = []
+ if missing:
+ messages.append("Unconfigured runtime modules:\n - " + "\n - ".join(str(path) for path in missing))
+ if extra:
+ messages.append("Configured modules that are not eligible runtime packages:\n - " +
+ "\n - ".join(str(path) for path in extra))
+ raise SystemExit("\n".join(messages))
+
+
+def check_or_write(path: Path, expected: str, encoding: str, check_only: bool, changed: list[Path]) -> None:
+ if path.exists():
+ current, _ = load_text(path)
+ if current == expected:
+ return
+ elif check_only:
+ changed.append(path)
+ return
+
+ if check_only:
+ changed.append(path)
+ return
+
+ write_text(path, expected, encoding)
+ changed.append(path)
+
+
+def main() -> None:
+ args = parse_args()
+ modules = load_modules()
+ validate_module_coverage(modules)
+ changed: list[Path] = []
+
+ for module in modules:
+ package_id = resolve_package_id(module.project_path)
+ props_path = module.project_path.parent / "buildTransitive" / f"{package_id}.props"
+ props_content = render_props(module)
+ check_or_write(props_path, props_content, "utf-8", args.check, changed)
+
+ updated_project, encoding = update_csproj(module.project_path, package_id)
+ check_or_write(module.project_path, updated_project, encoding, args.check, changed)
+
+ if args.check:
+ if changed:
+ relative_paths = "\n".join(f" - {path.relative_to(ROOT_DIR)}" for path in changed)
+ raise SystemExit(f"Generated module global usings are out of date:\n{relative_paths}")
+
+ print("Module global usings are up to date.")
+ return
+
+ if changed:
+ for path in changed:
+ print(f"Updated {path.relative_to(ROOT_DIR)}")
+ return
+
+ print("No module global usings changes were needed.")
+
+
+if __name__ == "__main__":
+ main()
From 2d1d1a43b640da30cbb7835df9a895e5606a1bfd Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Tue, 24 Mar 2026 22:24:52 +0800
Subject: [PATCH 2/3] =?UTF-8?q?chore(build):=20=E7=A7=BB=E9=99=A4=E9=9D=99?=
=?UTF-8?q?=E6=80=81=E5=85=A8=E5=B1=80using=E9=85=8D=E7=BD=AE=E5=B9=B6?=
=?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=87=AA=E5=8A=A8=E5=8C=96=E7=94=9F=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 删除所有手动维护的 buildTransitive props 文件
- 从项目文件中移除静态的 global usings 配置
- 删除废弃的 global-usings.modules.json 清单文件
- 移除旧的 TransitiveGlobalUsingsGenerationTests 测试
- 添加新的 TransitiveGlobalUsingsPackagingTests 验证自动化生成
- 在 Directory.Build.targets 中集成 MSBuild 自动化生成任务
- 实现基于源码扫描的动态命名空间发现机制
---
Directory.Build.targets | 171 ++++++++++++++
.../GFramework.Core.Abstractions.csproj | 6 +-
...GeWuYou.GFramework.Core.Abstractions.props | 47 ----
.../TransitiveGlobalUsingsGenerationTests.cs | 74 ------
.../TransitiveGlobalUsingsPackagingTests.cs | 56 +++++
GFramework.Core/GFramework.Core.csproj | 6 +-
.../GeWuYou.GFramework.Core.props | 53 -----
.../GFramework.Ecs.Arch.Abstractions.csproj | 6 +-
...You.GFramework.Ecs.Arch.Abstractions.props | 10 -
.../GFramework.Ecs.Arch.csproj | 6 +-
.../GeWuYou.GFramework.Ecs.Arch.props | 13 -
.../GFramework.Game.Abstractions.csproj | 6 +-
...GeWuYou.GFramework.Game.Abstractions.props | 19 --
GFramework.Game/GFramework.Game.csproj | 6 +-
.../GeWuYou.GFramework.Game.props | 21 --
GFramework.Godot/GFramework.Godot.csproj | 6 +-
.../GeWuYou.GFramework.Godot.props | 22 --
global-usings.modules.json | 161 -------------
scripts/generate-module-global-usings.py | 222 ------------------
19 files changed, 234 insertions(+), 677 deletions(-)
create mode 100644 Directory.Build.targets
delete mode 100644 GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props
delete mode 100644 GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs
create mode 100644 GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs
delete mode 100644 GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props
delete mode 100644 GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props
delete mode 100644 GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props
delete mode 100644 GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props
delete mode 100644 GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props
delete mode 100644 GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props
delete mode 100644 global-usings.modules.json
delete mode 100644 scripts/generate-module-global-usings.py
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..1b1ad0a
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ (global::System.StringComparer.Ordinal);
+ var exactExclusions = new global::System.Collections.Generic.HashSet(global::System.StringComparer.Ordinal);
+ var prefixExclusions = new global::System.Collections.Generic.List();
+ var namespacePattern = new global::System.Text.RegularExpressions.Regex(
+ @"^\s*namespace\s+([A-Za-z_][A-Za-z0-9_.]*)\s*(?:;|\{)",
+ global::System.Text.RegularExpressions.RegexOptions.Compiled);
+
+ if (ExcludedNamespaces != null)
+ {
+ foreach (var excludedNamespace in ExcludedNamespaces)
+ {
+ if (!string.IsNullOrWhiteSpace(excludedNamespace.ItemSpec))
+ {
+ exactExclusions.Add(excludedNamespace.ItemSpec.Trim());
+ }
+ }
+ }
+
+ if (ExcludedNamespacePrefixes != null)
+ {
+ foreach (var excludedPrefix in ExcludedNamespacePrefixes)
+ {
+ if (!string.IsNullOrWhiteSpace(excludedPrefix.ItemSpec))
+ {
+ prefixExclusions.Add(excludedPrefix.ItemSpec.Trim());
+ }
+ }
+ }
+
+ foreach (var sourceFile in SourceFiles)
+ {
+ var path = sourceFile.ItemSpec;
+ if (!global::System.IO.File.Exists(path))
+ {
+ continue;
+ }
+
+ foreach (var line in global::System.IO.File.ReadLines(path))
+ {
+ var match = namespacePattern.Match(line);
+ if (!match.Success)
+ {
+ continue;
+ }
+
+ var namespaceName = match.Groups[1].Value;
+ if (!namespaceName.StartsWith("GFramework.", global::System.StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ if (exactExclusions.Contains(namespaceName))
+ {
+ continue;
+ }
+
+ var excludedByPrefix = false;
+ foreach (var prefix in prefixExclusions)
+ {
+ if (namespaceName.StartsWith(prefix, global::System.StringComparison.Ordinal))
+ {
+ excludedByPrefix = true;
+ break;
+ }
+ }
+
+ if (!excludedByPrefix)
+ {
+ discoveredNamespaces.Add(namespaceName);
+ }
+ }
+ }
+
+ static string Escape(string value)
+ {
+ return global::System.Security.SecurityElement.Escape(value) ?? value;
+ }
+
+ var directory = global::System.IO.Path.GetDirectoryName(OutputFile);
+ if (!string.IsNullOrEmpty(directory))
+ {
+ global::System.IO.Directory.CreateDirectory(directory);
+ }
+
+ var builder = new global::System.Text.StringBuilder();
+ var msbuildPropertyOpen = new string(new[] { '$', '(' });
+ var msbuildItemOpen = new string(new[] { '@', '(' });
+ builder.AppendLine("");
+ builder.AppendLine(" ");
+ builder.AppendLine(" ");
+ builder.AppendLine(" ");
+ builder.Append(" ");
+
+ foreach (var namespaceName in discoveredNamespaces)
+ {
+ builder.Append(" <");
+ builder.Append(NamespaceItemName);
+ builder.Append(" Include=\"");
+ builder.Append(Escape(namespaceName));
+ builder.AppendLine("\" />");
+ }
+
+ builder.Append(" <");
+ builder.Append(NamespaceItemName);
+ builder.Append(" Remove=\"");
+ builder.Append(msbuildItemOpen);
+ builder.AppendLine("GFrameworkExcludedUsing)\" />");
+ builder.Append(" ");
+ builder.AppendLine(" ");
+ builder.AppendLine("");
+
+ global::System.IO.File.WriteAllText(OutputFile, builder.ToString(), new global::System.Text.UTF8Encoding(false));
+ Log.LogMessage(global::Microsoft.Build.Framework.MessageImportance.Low,
+ $"Generated {discoveredNamespaces.Count} transitive global usings for {OutputFile}.");
+ ]]>
+
+
+
+
+ <_GFrameworkTransitiveGlobalUsingsEnabled Condition="'$(EnableGFrameworkPackageTransitiveGlobalUsings)' == 'true' and '$(IsPackable)' != 'false'">true
+ <_GFrameworkTransitiveGlobalUsingsPrimaryTargetFramework Condition="'$(_GFrameworkTransitiveGlobalUsingsEnabled)' == 'true' and '$(TargetFrameworks)' != ''">$([System.String]::Copy('$(TargetFrameworks)').Split(';')[0])
+ <_GFrameworkTransitiveGlobalUsingsGenerationBuild Condition="'$(_GFrameworkTransitiveGlobalUsingsEnabled)' == 'true' and ('$(TargetFrameworks)' == '' or '$(TargetFramework)' == '$(_GFrameworkTransitiveGlobalUsingsPrimaryTargetFramework)')">true
+ <_GFrameworkTransitiveGlobalUsingsPackageId Condition="'$(_GFrameworkTransitiveGlobalUsingsEnabled)' == 'true' and '$(PackageId)' != ''">$(PackageId)
+ <_GFrameworkTransitiveGlobalUsingsPackageId Condition="'$(_GFrameworkTransitiveGlobalUsingsEnabled)' == 'true' and '$(_GFrameworkTransitiveGlobalUsingsPackageId)' == ''">$(AssemblyName)
+ <_GFrameworkTransitiveGlobalUsingsOutputFile Condition="'$(_GFrameworkTransitiveGlobalUsingsEnabled)' == 'true'">$(BaseIntermediateOutputPath)gframework/$(_GFrameworkTransitiveGlobalUsingsPackageId).props
+ <_GFrameworkTransitiveGlobalUsingsItemName Condition="'$(_GFrameworkTransitiveGlobalUsingsEnabled)' == 'true'">_$([System.Text.RegularExpressions.Regex]::Replace('$(MSBuildProjectName)', '[^A-Za-z0-9_]', '_'))_TransitiveUsing
+
+
+
+
+
+
+
+
+
+
+
diff --git a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj
index d5f1779..83dea9d 100644
--- a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj
+++ b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj
@@ -10,6 +10,7 @@
T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute
enable
true
+ true
@@ -27,9 +28,4 @@
-
-
-
-
-
diff --git a/GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props b/GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props
deleted file mode 100644
index 67cca44..0000000
--- a/GFramework.Core.Abstractions/buildTransitive/GeWuYou.GFramework.Core.Abstractions.props
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Architectures"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Bases"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Command"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Concurrency"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Configuration"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Controller"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Coroutine"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Command"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Notification"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Query"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Cqrs.Request"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Data"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Enums"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Environment"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Events"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Ioc"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Lifecycle"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Localization"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Logging"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Model"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Pause"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Pool"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Properties"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Property"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Query"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Registries"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Resource"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Rule"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Serializer"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.State"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.StateManagement"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Storage"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Systems"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Time"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Utility"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Utility.Numeric"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Include="GFramework.Core.Abstractions.Versioning"/>
- <_GFramework_Core_Abstractions_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
-
-
-
diff --git a/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs b/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs
deleted file mode 100644
index e840a86..0000000
--- a/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsGenerationTests.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System.Diagnostics;
-using System.IO;
-
-namespace GFramework.Core.Tests.Packaging;
-
-///
-/// 验证模块级可选 Global Usings 的生成脚本与仓库中的已提交产物保持同步。
-/// 该测试用于防止新增模块、命名空间清单或打包声明发生漂移后静默进入仓库。
-///
-[TestFixture]
-public class TransitiveGlobalUsingsGenerationTests
-{
- ///
- /// 验证生成脚本的检查模式可以在当前仓库状态下通过。
- /// 如果此断言失败,说明清单、生成的 props 文件或 csproj 打包声明至少有一处未同步。
- ///
- [Test]
- public void GenerateModuleGlobalUsingsScript_CheckMode_Should_Pass()
- {
- var repositoryRoot = FindRepositoryRoot();
- using var process = StartScriptCheck(repositoryRoot);
-
- var standardOutput = process.StandardOutput.ReadToEnd();
- var standardError = process.StandardError.ReadToEnd();
- process.WaitForExit();
-
- Assert.That(
- process.ExitCode,
- Is.EqualTo(0),
- $"Expected module global usings generation to be up to date.{System.Environment.NewLine}" +
- $"stdout:{System.Environment.NewLine}{standardOutput}{System.Environment.NewLine}" +
- $"stderr:{System.Environment.NewLine}{standardError}");
- }
-
- ///
- /// 启动生成脚本的检查模式。
- /// 该模式不会修改仓库内容,只验证仓库中的生成产物是否已与当前规则对齐。
- ///
- /// 仓库根目录。
- /// 已启动的进程实例。
- private static Process StartScriptCheck(string repositoryRoot)
- {
- var startInfo = new ProcessStartInfo("python3", "scripts/generate-module-global-usings.py --check")
- {
- WorkingDirectory = repositoryRoot,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false
- };
-
- return Process.Start(startInfo)
- ?? throw new InvalidOperationException("Failed to start the module global usings generation check.");
- }
-
- ///
- /// 从测试输出目录向上回溯,定位包含解决方案文件的仓库根目录。
- ///
- /// 仓库根目录绝对路径。
- private static string FindRepositoryRoot()
- {
- var currentDirectory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);
-
- while (currentDirectory != null)
- {
- var solutionPath = Path.Combine(currentDirectory.FullName, "GFramework.sln");
- if (File.Exists(solutionPath))
- return currentDirectory.FullName;
-
- currentDirectory = currentDirectory.Parent;
- }
-
- throw new DirectoryNotFoundException("Could not locate the repository root for GFramework.");
- }
-}
\ No newline at end of file
diff --git a/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs b/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs
new file mode 100644
index 0000000..2ca6a9c
--- /dev/null
+++ b/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs
@@ -0,0 +1,56 @@
+using System.IO;
+
+namespace GFramework.Core.Tests.Packaging;
+
+///
+/// 验证运行时模块在构建期间会自动生成 transitive global usings 资产。
+/// 该测试覆盖命名空间自动发现、框架侧过滤和消费者侧排除钩子的最终构建产物。
+///
+[TestFixture]
+public class TransitiveGlobalUsingsPackagingTests
+{
+ ///
+ /// 验证 GFramework.Core 在构建后会生成 transitive global usings props,
+ /// 且 props 内容来自源码自动发现,并保留消费者侧排除机制。
+ ///
+ [Test]
+ public void CoreBuild_Should_Generate_AutoDiscovered_TransitiveGlobalUsingsProps()
+ {
+ var repositoryRoot = FindRepositoryRoot();
+ var propsPath = Path.Combine(
+ repositoryRoot,
+ "GFramework.Core",
+ "obj",
+ "gframework",
+ "GeWuYou.GFramework.Core.props");
+
+ Assert.That(File.Exists(propsPath), Is.True, $"Expected generated props to exist: {propsPath}");
+ var propsContent = File.ReadAllText(propsPath);
+
+ Assert.That(propsContent, Does.Contain("GFramework.Core.Extensions"));
+ Assert.That(propsContent, Does.Contain("GFramework.Core.Architectures"));
+ Assert.That(propsContent, Does.Contain("GFramework.Core.Coroutine.Extensions"));
+ Assert.That(propsContent, Does.Contain("Remove=\"@(GFrameworkExcludedUsing)\""));
+ Assert.That(propsContent, Does.Not.Contain("System.Runtime.CompilerServices"));
+ }
+
+ ///
+ /// 从测试输出目录向上回溯,定位包含解决方案文件的仓库根目录。
+ ///
+ /// 仓库根目录绝对路径。
+ private static string FindRepositoryRoot()
+ {
+ var currentDirectory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);
+
+ while (currentDirectory != null)
+ {
+ var solutionPath = Path.Combine(currentDirectory.FullName, "GFramework.sln");
+ if (File.Exists(solutionPath))
+ return currentDirectory.FullName;
+
+ currentDirectory = currentDirectory.Parent;
+ }
+
+ throw new DirectoryNotFoundException("Could not locate the repository root for GFramework.");
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Core/GFramework.Core.csproj b/GFramework.Core/GFramework.Core.csproj
index 0ef4bcd..c450b44 100644
--- a/GFramework.Core/GFramework.Core.csproj
+++ b/GFramework.Core/GFramework.Core.csproj
@@ -6,6 +6,7 @@
disable
enable
true
+ true
@@ -14,9 +15,4 @@
-
-
-
-
-
diff --git a/GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props b/GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props
deleted file mode 100644
index b666aa9..0000000
--- a/GFramework.Core/buildTransitive/GeWuYou.GFramework.Core.props
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Architectures"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Command"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Concurrency"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Configuration"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Constants"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Coroutine"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Coroutine.Extensions"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Coroutine.Instructions"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Behaviors"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Command"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Notification"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Query"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Cqrs.Request"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Environment"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Events"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Events.Filters"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Extensions"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Async"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Control"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Functions"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Functional.Pipe"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Ioc"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Localization"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Localization.Formatters"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging.Appenders"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging.Filters"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Logging.Formatters"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Model"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Pause"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Pool"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Property"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Query"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Resource"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Rule"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Services"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Services.Modules"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.State"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.StateManagement"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Systems"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Time"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Utility"/>
- <_GFramework_Core_TransitiveUsing Include="GFramework.Core.Utility.Numeric"/>
- <_GFramework_Core_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
-
-
-
diff --git a/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj
index d9550a3..94337e8 100644
--- a/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj
+++ b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj
@@ -4,14 +4,10 @@
GeWuYou.$(AssemblyName)
true
enable
+ true
-
-
-
-
-
diff --git a/GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props b/GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props
deleted file mode 100644
index 5816a35..0000000
--- a/GFramework.Ecs.Arch.Abstractions/buildTransitive/GeWuYou.GFramework.Ecs.Arch.Abstractions.props
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
- <_GFramework_Ecs_Arch_Abstractions_TransitiveUsing Include="GFramework.Ecs.Arch.Abstractions"/>
- <_GFramework_Ecs_Arch_Abstractions_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
-
-
-
diff --git a/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj
index 68a8916..a79bec7 100644
--- a/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj
+++ b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj
@@ -7,6 +7,7 @@
enable
true
true
+ true
@@ -19,9 +20,4 @@
-
-
-
-
-
diff --git a/GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props b/GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props
deleted file mode 100644
index 1776e61..0000000
--- a/GFramework.Ecs.Arch/buildTransitive/GeWuYou.GFramework.Ecs.Arch.props
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch"/>
- <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch.Components"/>
- <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch.Extensions"/>
- <_GFramework_Ecs_Arch_TransitiveUsing Include="GFramework.Ecs.Arch.Systems"/>
- <_GFramework_Ecs_Arch_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
-
-
-
diff --git a/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj b/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj
index 62aef63..b4a8890 100644
--- a/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj
+++ b/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj
@@ -10,6 +10,7 @@
T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute
enable
true
+ true
@@ -28,9 +29,4 @@
runtime; build; native; contentfiles; analyzers
-
-
-
-
-
diff --git a/GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props b/GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props
deleted file mode 100644
index f7319ff..0000000
--- a/GFramework.Game.Abstractions/buildTransitive/GeWuYou.GFramework.Game.Abstractions.props
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Asset"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Data"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Data.Events"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Enums"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Routing"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Scene"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Setting"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Setting.Data"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.Storage"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Include="GFramework.Game.Abstractions.UI"/>
- <_GFramework_Game_Abstractions_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
-
-
-
diff --git a/GFramework.Game/GFramework.Game.csproj b/GFramework.Game/GFramework.Game.csproj
index 37bc8a8..5bb94d9 100644
--- a/GFramework.Game/GFramework.Game.csproj
+++ b/GFramework.Game/GFramework.Game.csproj
@@ -6,6 +6,7 @@
disable
enable
true
+ true
@@ -14,9 +15,4 @@
-
-
-
-
-
diff --git a/GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props b/GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props
deleted file mode 100644
index b7749c7..0000000
--- a/GFramework.Game/buildTransitive/GeWuYou.GFramework.Game.props
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Data"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Extensions"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Routing"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Scene"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Scene.Handler"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Serializer"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Setting"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Setting.Events"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.State"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.Storage"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.UI"/>
- <_GFramework_Game_TransitiveUsing Include="GFramework.Game.UI.Handler"/>
- <_GFramework_Game_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
-
-
-
diff --git a/GFramework.Godot/GFramework.Godot.csproj b/GFramework.Godot/GFramework.Godot.csproj
index 44e037c..29c065c 100644
--- a/GFramework.Godot/GFramework.Godot.csproj
+++ b/GFramework.Godot/GFramework.Godot.csproj
@@ -6,6 +6,7 @@
enable
net8.0;net9.0;net10.0
true
+ true
$(MSBuildProjectDirectory)
@@ -22,9 +23,4 @@
-
-
-
-
-
diff --git a/GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props b/GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props
deleted file mode 100644
index ac14337..0000000
--- a/GFramework.Godot/buildTransitive/GeWuYou.GFramework.Godot.props
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Architectures"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Coroutine"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Data"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Extensions"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Extensions.Signal"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Logging"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Pause"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Pool"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Scene"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Setting"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Setting.Data"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.Storage"/>
- <_GFramework_Godot_TransitiveUsing Include="GFramework.Godot.UI"/>
- <_GFramework_Godot_TransitiveUsing Remove="@(GFrameworkExcludedUsing)"/>
-
-
-
diff --git a/global-usings.modules.json b/global-usings.modules.json
deleted file mode 100644
index 6db53da..0000000
--- a/global-usings.modules.json
+++ /dev/null
@@ -1,161 +0,0 @@
-{
- "modules": [
- {
- "project": "GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj",
- "namespaces": [
- "GFramework.Core.Abstractions.Architectures",
- "GFramework.Core.Abstractions.Bases",
- "GFramework.Core.Abstractions.Command",
- "GFramework.Core.Abstractions.Concurrency",
- "GFramework.Core.Abstractions.Configuration",
- "GFramework.Core.Abstractions.Controller",
- "GFramework.Core.Abstractions.Coroutine",
- "GFramework.Core.Abstractions.Cqrs",
- "GFramework.Core.Abstractions.Cqrs.Command",
- "GFramework.Core.Abstractions.Cqrs.Notification",
- "GFramework.Core.Abstractions.Cqrs.Query",
- "GFramework.Core.Abstractions.Cqrs.Request",
- "GFramework.Core.Abstractions.Data",
- "GFramework.Core.Abstractions.Enums",
- "GFramework.Core.Abstractions.Environment",
- "GFramework.Core.Abstractions.Events",
- "GFramework.Core.Abstractions.Ioc",
- "GFramework.Core.Abstractions.Lifecycle",
- "GFramework.Core.Abstractions.Localization",
- "GFramework.Core.Abstractions.Logging",
- "GFramework.Core.Abstractions.Model",
- "GFramework.Core.Abstractions.Pause",
- "GFramework.Core.Abstractions.Pool",
- "GFramework.Core.Abstractions.Properties",
- "GFramework.Core.Abstractions.Property",
- "GFramework.Core.Abstractions.Query",
- "GFramework.Core.Abstractions.Registries",
- "GFramework.Core.Abstractions.Resource",
- "GFramework.Core.Abstractions.Rule",
- "GFramework.Core.Abstractions.Serializer",
- "GFramework.Core.Abstractions.State",
- "GFramework.Core.Abstractions.StateManagement",
- "GFramework.Core.Abstractions.Storage",
- "GFramework.Core.Abstractions.Systems",
- "GFramework.Core.Abstractions.Time",
- "GFramework.Core.Abstractions.Utility",
- "GFramework.Core.Abstractions.Utility.Numeric",
- "GFramework.Core.Abstractions.Versioning"
- ]
- },
- {
- "project": "GFramework.Core/GFramework.Core.csproj",
- "namespaces": [
- "GFramework.Core.Architectures",
- "GFramework.Core.Command",
- "GFramework.Core.Concurrency",
- "GFramework.Core.Configuration",
- "GFramework.Core.Constants",
- "GFramework.Core.Coroutine",
- "GFramework.Core.Coroutine.Extensions",
- "GFramework.Core.Coroutine.Instructions",
- "GFramework.Core.Cqrs.Behaviors",
- "GFramework.Core.Cqrs.Command",
- "GFramework.Core.Cqrs.Notification",
- "GFramework.Core.Cqrs.Query",
- "GFramework.Core.Cqrs.Request",
- "GFramework.Core.Environment",
- "GFramework.Core.Events",
- "GFramework.Core.Events.Filters",
- "GFramework.Core.Extensions",
- "GFramework.Core.Functional",
- "GFramework.Core.Functional.Async",
- "GFramework.Core.Functional.Control",
- "GFramework.Core.Functional.Functions",
- "GFramework.Core.Functional.Pipe",
- "GFramework.Core.Ioc",
- "GFramework.Core.Localization",
- "GFramework.Core.Localization.Formatters",
- "GFramework.Core.Logging",
- "GFramework.Core.Logging.Appenders",
- "GFramework.Core.Logging.Filters",
- "GFramework.Core.Logging.Formatters",
- "GFramework.Core.Model",
- "GFramework.Core.Pause",
- "GFramework.Core.Pool",
- "GFramework.Core.Property",
- "GFramework.Core.Query",
- "GFramework.Core.Resource",
- "GFramework.Core.Rule",
- "GFramework.Core.Services",
- "GFramework.Core.Services.Modules",
- "GFramework.Core.State",
- "GFramework.Core.StateManagement",
- "GFramework.Core.Systems",
- "GFramework.Core.Time",
- "GFramework.Core.Utility",
- "GFramework.Core.Utility.Numeric"
- ]
- },
- {
- "project": "GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj",
- "namespaces": [
- "GFramework.Game.Abstractions.Asset",
- "GFramework.Game.Abstractions.Data",
- "GFramework.Game.Abstractions.Data.Events",
- "GFramework.Game.Abstractions.Enums",
- "GFramework.Game.Abstractions.Routing",
- "GFramework.Game.Abstractions.Scene",
- "GFramework.Game.Abstractions.Setting",
- "GFramework.Game.Abstractions.Setting.Data",
- "GFramework.Game.Abstractions.Storage",
- "GFramework.Game.Abstractions.UI"
- ]
- },
- {
- "project": "GFramework.Game/GFramework.Game.csproj",
- "namespaces": [
- "GFramework.Game.Data",
- "GFramework.Game.Extensions",
- "GFramework.Game.Routing",
- "GFramework.Game.Scene",
- "GFramework.Game.Scene.Handler",
- "GFramework.Game.Serializer",
- "GFramework.Game.Setting",
- "GFramework.Game.Setting.Events",
- "GFramework.Game.State",
- "GFramework.Game.Storage",
- "GFramework.Game.UI",
- "GFramework.Game.UI.Handler"
- ]
- },
- {
- "project": "GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj",
- "namespaces": [
- "GFramework.Ecs.Arch.Abstractions"
- ]
- },
- {
- "project": "GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj",
- "namespaces": [
- "GFramework.Ecs.Arch",
- "GFramework.Ecs.Arch.Components",
- "GFramework.Ecs.Arch.Extensions",
- "GFramework.Ecs.Arch.Systems"
- ]
- },
- {
- "project": "GFramework.Godot/GFramework.Godot.csproj",
- "namespaces": [
- "GFramework.Godot.Architectures",
- "GFramework.Godot.Coroutine",
- "GFramework.Godot.Data",
- "GFramework.Godot.Extensions",
- "GFramework.Godot.Extensions.Signal",
- "GFramework.Godot.Logging",
- "GFramework.Godot.Pause",
- "GFramework.Godot.Pool",
- "GFramework.Godot.Scene",
- "GFramework.Godot.Setting",
- "GFramework.Godot.Setting.Data",
- "GFramework.Godot.Storage",
- "GFramework.Godot.UI"
- ]
- }
- ]
-}
diff --git a/scripts/generate-module-global-usings.py b/scripts/generate-module-global-usings.py
deleted file mode 100644
index ba040cc..0000000
--- a/scripts/generate-module-global-usings.py
+++ /dev/null
@@ -1,222 +0,0 @@
-#!/usr/bin/env python3
-
-from __future__ import annotations
-
-import argparse
-import json
-import re
-from dataclasses import dataclass
-from pathlib import Path
-
-
-ROOT_DIR = Path(__file__).resolve().parent.parent
-CONFIG_PATH = ROOT_DIR / "global-usings.modules.json"
-AUTO_GENERATED_START = ""
-AUTO_GENERATED_END = ""
-
-
-@dataclass(frozen=True)
-class ModuleConfig:
- project_path: Path
- namespaces: tuple[str, ...]
-
-
-def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(
- description="Generate optional transitive global usings for packable GFramework runtime modules.",
- )
- parser.add_argument(
- "--check",
- action="store_true",
- help="Validate that generated files are up to date instead of writing them.",
- )
- return parser.parse_args()
-
-
-def load_text(path: Path) -> tuple[str, str]:
- raw = path.read_bytes()
- if raw.startswith(b"\xef\xbb\xbf"):
- return raw.decode("utf-8-sig"), "utf-8-sig"
-
- return raw.decode("utf-8"), "utf-8"
-
-
-def write_text(path: Path, content: str, encoding: str) -> None:
- path.parent.mkdir(parents=True, exist_ok=True)
- path.write_text(content, encoding=encoding)
-
-
-def load_modules() -> list[ModuleConfig]:
- data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
- modules: list[ModuleConfig] = []
-
- for entry in data["modules"]:
- project_path = ROOT_DIR / entry["project"]
- namespaces = tuple(dict.fromkeys(entry["namespaces"]))
- modules.append(ModuleConfig(project_path=project_path, namespaces=namespaces))
-
- return modules
-
-
-def discover_runtime_modules() -> list[Path]:
- projects: list[Path] = []
-
- for project_path in sorted(ROOT_DIR.rglob("*.csproj")):
- if "obj" in project_path.parts or "bin" in project_path.parts:
- continue
-
- if project_path.name == "GFramework.csproj":
- continue
-
- project_name = project_path.stem
- if not project_name.startswith("GFramework."):
- continue
-
- if ".Tests" in project_name or "SourceGenerators" in project_name:
- continue
-
- csproj_text, _ = load_text(project_path)
- if re.search(r"\s*false\s*", csproj_text, re.IGNORECASE):
- continue
-
- projects.append(project_path)
-
- return projects
-
-
-def resolve_package_id(project_path: Path) -> str:
- project_text, _ = load_text(project_path)
- match = re.search(r"(.*?)", project_text, re.DOTALL)
- if match is None:
- return project_path.stem
-
- package_id = match.group(1).strip()
- return package_id.replace("$(AssemblyName)", project_path.stem)
-
-
-def sanitize_identifier(text: str) -> str:
- return re.sub(r"[^A-Za-z0-9_]", "_", text)
-
-
-def render_props(module: ModuleConfig) -> str:
- item_name = f"_{sanitize_identifier(module.project_path.stem)}_TransitiveUsing"
- lines = [
- "",
- " ",
- " ",
- " ",
- " ",
- ]
-
- for namespace in module.namespaces:
- lines.append(f" <{item_name} Include=\"{namespace}\" />")
-
- lines.extend(
- [
- f" <{item_name} Remove=\"@(GFrameworkExcludedUsing)\" />",
- f" ",
- " ",
- "",
- "",
- ],
- )
- return "\n".join(lines)
-
-
-def render_pack_block(package_id: str) -> str:
- props_path = f"buildTransitive\\{package_id}.props"
- return "\n".join(
- [
- f" {AUTO_GENERATED_START}",
- " ",
- f" ",
- " ",
- f" {AUTO_GENERATED_END}",
- "",
- ],
- )
-
-
-def update_csproj(project_path: Path, package_id: str) -> tuple[str, str]:
- project_text, encoding = load_text(project_path)
- block = render_pack_block(package_id)
- pattern = re.compile(
- rf"\s*{re.escape(AUTO_GENERATED_START)}.*?{re.escape(AUTO_GENERATED_END)}\s*",
- re.DOTALL,
- )
-
- if pattern.search(project_text):
- updated = pattern.sub(lambda _: f"\n{block}", project_text)
- else:
- updated = project_text.replace("", f"{block}")
-
- return updated, encoding
-
-
-def validate_module_coverage(modules: list[ModuleConfig]) -> None:
- configured_projects = {module.project_path.resolve() for module in modules}
- discovered_projects = {project.resolve() for project in discover_runtime_modules()}
-
- missing = sorted(project.relative_to(ROOT_DIR) for project in discovered_projects - configured_projects)
- extra = sorted(project.relative_to(ROOT_DIR) for project in configured_projects - discovered_projects)
-
- if missing or extra:
- messages: list[str] = []
- if missing:
- messages.append("Unconfigured runtime modules:\n - " + "\n - ".join(str(path) for path in missing))
- if extra:
- messages.append("Configured modules that are not eligible runtime packages:\n - " +
- "\n - ".join(str(path) for path in extra))
- raise SystemExit("\n".join(messages))
-
-
-def check_or_write(path: Path, expected: str, encoding: str, check_only: bool, changed: list[Path]) -> None:
- if path.exists():
- current, _ = load_text(path)
- if current == expected:
- return
- elif check_only:
- changed.append(path)
- return
-
- if check_only:
- changed.append(path)
- return
-
- write_text(path, expected, encoding)
- changed.append(path)
-
-
-def main() -> None:
- args = parse_args()
- modules = load_modules()
- validate_module_coverage(modules)
- changed: list[Path] = []
-
- for module in modules:
- package_id = resolve_package_id(module.project_path)
- props_path = module.project_path.parent / "buildTransitive" / f"{package_id}.props"
- props_content = render_props(module)
- check_or_write(props_path, props_content, "utf-8", args.check, changed)
-
- updated_project, encoding = update_csproj(module.project_path, package_id)
- check_or_write(module.project_path, updated_project, encoding, args.check, changed)
-
- if args.check:
- if changed:
- relative_paths = "\n".join(f" - {path.relative_to(ROOT_DIR)}" for path in changed)
- raise SystemExit(f"Generated module global usings are out of date:\n{relative_paths}")
-
- print("Module global usings are up to date.")
- return
-
- if changed:
- for path in changed:
- print(f"Updated {path.relative_to(ROOT_DIR)}")
- return
-
- print("No module global usings changes were needed.")
-
-
-if __name__ == "__main__":
- main()
From 20f1c987eba5b30298875f505a129b518faa9ee2 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Tue, 24 Mar 2026 22:29:45 +0800
Subject: [PATCH 3/3] =?UTF-8?q?refactor(tests):=20=E4=BC=98=E5=8C=96?=
=?UTF-8?q?=E5=85=A8=E5=B1=80=E5=BC=95=E7=94=A8=E6=B5=8B=E8=AF=95=E7=9A=84?=
=?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E5=8F=91=E7=8E=B0=E6=9C=BA?=
=?UTF-8?q?=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 添加基于真实类型的命名空间常量定义,避免硬编码字符串
- 引入 Architecture、Extensions 和 CoroutineExtensions 命名空间自动发现
- 将 FindRepositoryRoot 方法重构为 ResolveRepositoryRoot 并使用 CallerFilePath
- 更新断言逻辑以使用动态命名空间变量而非字面量字符串
- 移除对解决方案文件扫描的依赖,改用相对路径计算仓库根目录
- 添加方法参数验证和异常处理增强代码健壮性
---
.../TransitiveGlobalUsingsPackagingTests.cs | 54 +++++++++++++------
1 file changed, 39 insertions(+), 15 deletions(-)
diff --git a/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs b/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs
index 2ca6a9c..78bda4b 100644
--- a/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs
+++ b/GFramework.Core.Tests/Packaging/TransitiveGlobalUsingsPackagingTests.cs
@@ -1,4 +1,7 @@
using System.IO;
+using System.Runtime.CompilerServices;
+using GFramework.Core.Architectures;
+using GFramework.Core.Coroutine.Extensions;
namespace GFramework.Core.Tests.Packaging;
@@ -9,6 +12,27 @@ namespace GFramework.Core.Tests.Packaging;
[TestFixture]
public class TransitiveGlobalUsingsPackagingTests
{
+ ///
+ /// 使用真实类型派生架构命名空间,避免测试断言和命名空间重构脱节。
+ ///
+ private static readonly string ArchitectureNamespace = typeof(Architecture).Namespace
+ ?? throw new InvalidOperationException(
+ "Architecture namespace should not be null.");
+
+ ///
+ /// 使用真实类型派生扩展命名空间,避免对字面量命名空间字符串的重复维护。
+ ///
+ private static readonly string ExtensionsNamespace = typeof(ContextAwareEnvironmentExtensions).Namespace
+ ?? throw new InvalidOperationException(
+ "Extensions namespace should not be null.");
+
+ ///
+ /// 使用真实类型派生协程扩展命名空间,确保断言和源码自动发现保持一致。
+ ///
+ private static readonly string CoroutineExtensionsNamespace = typeof(CoroutineExtensions).Namespace
+ ?? throw new InvalidOperationException(
+ "Coroutine extensions namespace should not be null.");
+
///
/// 验证 GFramework.Core 在构建后会生成 transitive global usings props,
/// 且 props 内容来自源码自动发现,并保留消费者侧排除机制。
@@ -16,7 +40,7 @@ public class TransitiveGlobalUsingsPackagingTests
[Test]
public void CoreBuild_Should_Generate_AutoDiscovered_TransitiveGlobalUsingsProps()
{
- var repositoryRoot = FindRepositoryRoot();
+ var repositoryRoot = ResolveRepositoryRoot();
var propsPath = Path.Combine(
repositoryRoot,
"GFramework.Core",
@@ -27,30 +51,30 @@ public class TransitiveGlobalUsingsPackagingTests
Assert.That(File.Exists(propsPath), Is.True, $"Expected generated props to exist: {propsPath}");
var propsContent = File.ReadAllText(propsPath);
- Assert.That(propsContent, Does.Contain("GFramework.Core.Extensions"));
- Assert.That(propsContent, Does.Contain("GFramework.Core.Architectures"));
- Assert.That(propsContent, Does.Contain("GFramework.Core.Coroutine.Extensions"));
+ Assert.That(propsContent, Does.Contain(ExtensionsNamespace));
+ Assert.That(propsContent, Does.Contain(ArchitectureNamespace));
+ Assert.That(propsContent, Does.Contain(CoroutineExtensionsNamespace));
Assert.That(propsContent, Does.Contain("Remove=\"@(GFrameworkExcludedUsing)\""));
Assert.That(propsContent, Does.Not.Contain("System.Runtime.CompilerServices"));
}
///
- /// 从测试输出目录向上回溯,定位包含解决方案文件的仓库根目录。
+ /// 基于当前测试源文件的已知位置解析仓库根目录。
+ /// 这里不扫描解决方案文件,避免测试对仓库布局演进产生额外脆弱性。
///
+ /// 由编译器注入的当前测试源文件绝对路径。
/// 仓库根目录绝对路径。
- private static string FindRepositoryRoot()
+ private static string ResolveRepositoryRoot([CallerFilePath] string sourceFilePath = "")
{
- var currentDirectory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);
-
- while (currentDirectory != null)
+ if (string.IsNullOrWhiteSpace(sourceFilePath))
{
- var solutionPath = Path.Combine(currentDirectory.FullName, "GFramework.sln");
- if (File.Exists(solutionPath))
- return currentDirectory.FullName;
-
- currentDirectory = currentDirectory.Parent;
+ throw new InvalidOperationException("Caller file path is required to resolve the repository root.");
}
- throw new DirectoryNotFoundException("Could not locate the repository root for GFramework.");
+ var sourceDirectory = Path.GetDirectoryName(sourceFilePath)
+ ?? throw new DirectoryNotFoundException(
+ $"Could not determine the directory for source file path: {sourceFilePath}");
+
+ return Path.GetFullPath(Path.Combine(sourceDirectory, "..", ".."));
}
}
\ No newline at end of file