Skip to content

Commit 2a801be

Browse files
authored
Merge pull request #32 from coderbusy/copilot/fix-rootnamespace-recognition
Fix: Source generator respects RootNamespace MSBuild property
2 parents 5926197 + f686e6b commit 2a801be

File tree

8 files changed

+185
-7
lines changed

8 files changed

+185
-7
lines changed

LuYao.ResourcePacker.MSBuild/build/LuYao.ResourcePacker.MSBuild.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
<ItemGroup>
99
<CompilerVisibleProperty Include="ResourcePackerOutputFileName" />
1010
<CompilerVisibleProperty Include="ResourcePackerDirectory" />
11+
<CompilerVisibleProperty Include="RootNamespace" />
1112
</ItemGroup>
1213
</Project>

LuYao.ResourcePacker.SourceGenerator/ResourcePackageGenerator.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private static void Execute(SourceProductionContext context, Compilation compila
5959
var className = "R";
6060

6161
// Get root namespace from compilation options or default to assembly name
62-
var rootNamespace = GetRootNamespace(compilation);
62+
var rootNamespace = GetRootNamespace(compilation, configOptions);
6363

6464
// Visibility is always internal
6565
var visibility = "internal";
@@ -82,14 +82,15 @@ private static void Execute(SourceProductionContext context, Compilation compila
8282
context.AddSource($"{className}.g.cs", SourceText.From(source, Encoding.UTF8));
8383
}
8484

85-
private static string GetRootNamespace(Compilation compilation)
85+
private static string GetRootNamespace(Compilation compilation, AnalyzerConfigOptionsProvider configOptions)
8686
{
87-
// Try to get RootNamespace from compilation options
88-
// First, check if there's a global namespace option
89-
if (compilation.Options is CSharpCompilationOptions csharpOptions)
87+
// Try to get RootNamespace from analyzer config (MSBuild property)
88+
if (configOptions.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace))
9089
{
91-
// Look for MSBuild properties that might contain the root namespace
92-
// The RootNamespace is typically passed through analyzer config
90+
if (!string.IsNullOrWhiteSpace(rootNamespace))
91+
{
92+
return rootNamespace;
93+
}
9394
}
9495

9596
// Default to assembly name if no explicit root namespace is found

LuYao.ResourcePacker.slnx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
<Project Path="examples/Lib2/Lib2.csproj" />
1313
<Project Path="examples/Lib3/Lib3.csproj" />
1414
</Folder>
15+
<Folder Name="/samples/">
16+
<Project Path="samples/RootNamespaceTest/RootNamespaceTest.csproj" />
17+
</Folder>
1518
<Project Path="LuYao.ResourcePacker.MSBuild/LuYao.ResourcePacker.MSBuild.csproj" />
1619
<Project Path="LuYao.ResourcePacker.SourceGenerator/LuYao.ResourcePacker.SourceGenerator.csproj" />
1720
<Project Path="LuYao.ResourcePacker.Tests/LuYao.ResourcePacker.Tests.csproj" />
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using LuYao.ResourcePacker;
4+
5+
namespace Popcorn.Toolkit
6+
{
7+
/// <summary>
8+
/// Test program to verify that the generated R class respects the RootNamespace property.
9+
/// When RootNamespace is set to "Popcorn.Toolkit", the R class should be in this namespace.
10+
/// </summary>
11+
public class Program
12+
{
13+
public static async Task Main(string[] args)
14+
{
15+
Console.WriteLine("RootNamespace Test - Verifying generated R class namespace");
16+
Console.WriteLine("=============================================================");
17+
Console.WriteLine();
18+
19+
// This test verifies that R class is in Popcorn.Toolkit namespace
20+
// If RootNamespace property is not respected, this won't compile
21+
Console.WriteLine($"R class is accessible in namespace: {typeof(R).Namespace}");
22+
Console.WriteLine($"Expected namespace: Popcorn.Toolkit");
23+
Console.WriteLine();
24+
25+
// List available resource keys
26+
Console.WriteLine("Available resource keys:");
27+
Console.WriteLine($" - R.Keys.sample: {R.Keys.sample}");
28+
Console.WriteLine($" - R.Keys.config: {R.Keys.config}");
29+
Console.WriteLine();
30+
31+
// Test reading resources
32+
try
33+
{
34+
var sampleText = await R.ReadSampleAsyncAsString();
35+
Console.WriteLine($"Sample resource content: {sampleText}");
36+
37+
var configJson = await R.ReadConfigAsyncAsString();
38+
Console.WriteLine($"Config resource content: {configJson}");
39+
40+
Console.WriteLine();
41+
Console.WriteLine("✓ Test PASSED: R class is in the correct namespace (Popcorn.Toolkit)");
42+
}
43+
catch (Exception ex)
44+
{
45+
Console.WriteLine($"✗ Error reading resources: {ex.Message}");
46+
}
47+
}
48+
}
49+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# RootNamespace Test Sample
2+
3+
This sample project demonstrates and validates that the source generator correctly respects the `RootNamespace` property in the .csproj file.
4+
5+
## Purpose
6+
7+
When a project sets `<RootNamespace>Popcorn.Toolkit</RootNamespace>` in its .csproj file, the generated `R` class should be placed in the `Popcorn.Toolkit` namespace rather than defaulting to the assembly name.
8+
9+
## Project Configuration
10+
11+
The project is configured with:
12+
- **AssemblyName**: RootNamespaceTest
13+
- **RootNamespace**: Popcorn.Toolkit
14+
- **Resources**: Contains sample.txt and config.json
15+
16+
## Expected Behavior
17+
18+
The source generator should:
19+
1. Read the `RootNamespace` property from MSBuild
20+
2. Generate the `R` class in the `Popcorn.Toolkit` namespace
21+
3. The Program.cs can access `R` class directly since it's in the same namespace
22+
23+
## Running the Test
24+
25+
From the repository root:
26+
```bash
27+
dotnet run --project samples/RootNamespaceTest/RootNamespaceTest.csproj
28+
```
29+
30+
Or from this directory:
31+
```bash
32+
dotnet run
33+
```
34+
35+
Expected output:
36+
```
37+
RootNamespace Test - Verifying generated R class namespace
38+
=============================================================
39+
40+
R class is accessible in namespace: Popcorn.Toolkit
41+
Expected namespace: Popcorn.Toolkit
42+
43+
Available resource keys:
44+
- R.Keys.sample: sample
45+
- R.Keys.config: config
46+
47+
Sample resource content: This is a sample resource file for testing RootNamespace property.
48+
Config resource content: {
49+
"message": "Hello from RootNamespace test!",
50+
"version": "1.0"
51+
}
52+
53+
✓ Test PASSED: R class is in the correct namespace (Popcorn.Toolkit)
54+
```
55+
56+
## Verification
57+
58+
The test program verifies the fix by:
59+
1. Accessing the `R` class without namespace qualification (proving it's in the same `Popcorn.Toolkit` namespace as Program.cs)
60+
2. Using reflection to verify the namespace is exactly `Popcorn.Toolkit`
61+
3. Reading resources to ensure the generated code functions correctly
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"message": "Hello from RootNamespace test!",
3+
"version": "1.0"
4+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is a sample resource file for testing RootNamespace property.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
<RootNamespace>Popcorn.Toolkit</RootNamespace>
10+
<ResourcePackerEnabled>true</ResourcePackerEnabled>
11+
<ResourcePackerDirectory>Resources</ResourcePackerDirectory>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\LuYao.ResourcePacker\LuYao.ResourcePacker.csproj" />
16+
<ProjectReference Include="..\..\LuYao.ResourcePacker.MSBuild\LuYao.ResourcePacker.MSBuild.csproj" />
17+
<ProjectReference Include="..\..\LuYao.ResourcePacker.SourceGenerator\LuYao.ResourcePacker.SourceGenerator.csproj"
18+
OutputItemType="Analyzer"
19+
ReferenceOutputAssembly="false" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<None Include="Resources\**\*" CopyToOutputDirectory="Never" />
24+
</ItemGroup>
25+
26+
<!-- Add resource files as AdditionalFiles for source generator -->
27+
<ItemGroup>
28+
<AdditionalFiles Include="Resources\**\*" Visible="false" />
29+
</ItemGroup>
30+
31+
<!-- For development with project references, manually import and override assembly path -->
32+
<UsingTask TaskName="LuYao.ResourcePacker.MSBuild.ResourcePackerTask"
33+
AssemblyFile="..\..\LuYao.ResourcePacker.MSBuild\bin\$(Configuration)\netstandard2.0\LuYao.ResourcePacker.MSBuild.dll" />
34+
35+
<Import Project="..\..\LuYao.ResourcePacker.MSBuild\build\LuYao.ResourcePacker.MSBuild.props" />
36+
37+
<!-- Override the targets to use local source generator for development -->
38+
<Target Name="PackResources" BeforeTargets="AssignTargetPaths" Condition="'$(ResourcePackerEnabled)' == 'true'">
39+
<PropertyGroup>
40+
<ResourcePackerOutputFileName Condition="'$(ResourcePackerOutputFileName)' == ''">$(AssemblyName).dat</ResourcePackerOutputFileName>
41+
</PropertyGroup>
42+
<ResourcePackerTask
43+
ProjectDir="$(ProjectDir)"
44+
OutputPath="$(OutputPath)"
45+
AssemblyName="$(AssemblyName)"
46+
ResourceDirectory="$(ResourcePackerDirectory)"
47+
OutputFileName="$(ResourcePackerOutputFileName)" />
48+
49+
<!-- Add .dat file to None so it gets copied to referencing projects -->
50+
<ItemGroup>
51+
<None Include="$(OutputPath)$(ResourcePackerOutputFileName)">
52+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
53+
<Link>$(ResourcePackerOutputFileName)</Link>
54+
</None>
55+
</ItemGroup>
56+
</Target>
57+
58+
</Project>

0 commit comments

Comments
 (0)