Skip to content

Commit 847152c

Browse files
committed
fix: use JSON serialization to solve type identity issue
Replaced direct type casting with JSON serialization/deserialization to bridge the AssemblyLoadContext type identity gap. Even with AssemblyLoadContext forcing both contexts to load JD.MSBuild.Fluent from the same location, .NET treats types as distinct when created in different contexts. Solution: - Serialize PackageDefinition returned from user's context to JSON - Deserialize JSON into PackageDefinition in task's context - Eliminates type identity checks entirely This allows packages to reference JD.MSBuild.Fluent and use auto-generation without hitting type identity mismatches. Changes: - Added System.Text.Json dependency - Updated GenerateMSBuildAssets to use JSON bridge - Removed direct cast that was failing due to context isolation
1 parent fa22501 commit 847152c

8 files changed

Lines changed: 236 additions & 16 deletions

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
<PackageVersion Include="System.CommandLine" Version="2.0.2" />
88
<!-- Downlevel language polyfills -->
99
<PackageVersion Include="PolySharp" Version="1.15.0" />
10+
<!-- JSON serialization -->
11+
<PackageVersion Include="System.Text.Json" Version="9.0.1" />
1012
<!-- MSBuild Tasks -->
1113
<PackageVersion Include="Microsoft.Build.Framework" Version="18.0.2" />
1214
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="18.0.2" />

docs/index.md.bak

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# JD.MSBuild.Fluent Documentation
2+
3+
Welcome to the comprehensive documentation for **JD.MSBuild.Fluent**, a strongly-typed fluent DSL for authoring MSBuild `.props`, `.targets`, and SDK assets with the ergonomics of modern C#.
4+
5+
## What is JD.MSBuild.Fluent?
6+
7+
JD.MSBuild.Fluent is a library that transforms MSBuild package authoring from error-prone XML manipulation into type-safe, refactorable C# code. Define your MSBuild packages using intuitive fluent APIs, then emit them into the exact NuGet folder layout (`build/`, `buildTransitive/`, `Sdk/`) expected by MSBuild and NuGet.
8+
9+
### Key Features
10+
11+
- **Strongly-Typed DSL**: Author MSBuild constructs with IntelliSense, compile-time checking, and refactoring support
12+
- **Fluent API**: Chain method calls naturally to build complex package definitions
13+
- **100% Standard MSBuild**: Generates canonical, deterministic MSBuild XML that works everywhere
14+
- **Intermediate Representation**: Composable IR layer separates authoring from rendering
15+
- **Deterministic Output**: Canonical ordering of properties, items, and task parameters for meaningful diffs
16+
- **Multi-Target Support**: Build packages targeting multiple frameworks or platforms
17+
- **SDK-Style Packages**: Full support for MSBuild SDK-style project imports
18+
- **Type-Safety Options**: Optional strongly-typed property, target, and item names
19+
20+
## Quick Example
21+
22+
```csharp
23+
using JD.MSBuild.Fluent;
24+
using JD.MSBuild.Fluent.Fluent;
25+
26+
var package = Package.Define("MyCompany.Build")
27+
.Description("Custom build tasks for MyCompany projects")
28+
.Props(p => p
29+
.Property("MyCompanyBuildEnabled", "true")
30+
.Property("MyCompanyVersion", "2.0.0"))
31+
.Targets(t => t
32+
.Target("MyCompany_PreBuild", target => target
33+
.BeforeTargets("Build")
34+
.Condition("'$(MyCompanyBuildEnabled)' == 'true'")
35+
.Message("MyCompany Build v$(MyCompanyVersion)", "High")))
36+
.Pack(o => o.BuildTransitive = true)
37+
.Build();
38+
```
39+
40+
This generates clean MSBuild XML ready for packaging:
41+
42+
```xml
43+
<Project>
44+
<PropertyGroup>
45+
<MyCompanyBuildEnabled>true</MyCompanyBuildEnabled>
46+
<MyCompanyVersion>2.0.0</MyCompanyVersion>
47+
</PropertyGroup>
48+
</Project>
49+
```
50+
51+
## Why Use JD.MSBuild.Fluent?
52+
53+
### Traditional XML Authoring Challenges
54+
55+
Authoring MSBuild packages manually involves:
56+
57+
- **No IntelliSense**: Easy to mistype property names, target names, or task parameters
58+
- **No Refactoring**: Renaming requires manual find-and-replace across multiple files
59+
- **Verbose Syntax**: XML is repetitive and hard to scan
60+
- **No Reusability**: Difficult to extract common patterns into reusable functions
61+
- **Merge Conflicts**: XML diffs are noisy and hard to resolve
62+
- **Late Errors**: Typos and structural errors only appear at build time
63+
64+
### The JD.MSBuild.Fluent Approach
65+
66+
JD.MSBuild.Fluent treats MSBuild authoring as a first-class C# development experience:
67+
68+
- **Full IntelliSense**: Discover available methods and properties as you type
69+
- **Compile-Time Safety**: Catch structural errors before generating any XML
70+
- **Easy Refactoring**: Use IDE refactoring tools to rename properties, targets, and methods
71+
- **DRY Principles**: Extract helper methods, share configurations, compose definitions
72+
- **Clean Diffs**: Generated XML has canonical ordering for meaningful version control
73+
- **Early Validation**: Validation rules run during build, before MSBuild sees the output
74+
75+
## Architecture Overview
76+
77+
JD.MSBuild.Fluent has a layered architecture:
78+
79+
```
80+
┌─────────────────────────────────────┐
81+
│ Fluent API (Builders) │ ← You work here
82+
│ Package.Define(...).Props().Build()│
83+
├─────────────────────────────────────┤
84+
│ Intermediate Representation (IR) │ ← Composable data structures
85+
│ MsBuildProject, MsBuildTarget │
86+
├─────────────────────────────────────┤
87+
│ XML Renderer │ ← Deterministic serialization
88+
│ MsBuildXmlRenderer │
89+
├─────────────────────────────────────┤
90+
│ Package Emitter │ ← NuGet folder layout
91+
│ build/, buildTransitive/, Sdk/ │
92+
└─────────────────────────────────────┘
93+
```
94+
95+
### Layers Explained
96+
97+
1. **Fluent API**: High-level builders (`Package`, `PropsBuilder`, `TargetsBuilder`) for natural C# authoring
98+
2. **IR Layer**: Immutable data structures (`MsBuildProject`, `MsBuildTarget`, `MsBuildProperty`) representing MSBuild constructs
99+
3. **Renderer**: Converts IR to canonical MSBuild XML with deterministic ordering
100+
4. **Emitter**: Organizes rendered XML into NuGet package folder structure
101+
102+
## Getting Started
103+
104+
<div class="embeddedContent">
105+
<a href="user-guides/getting-started/installation.html" class="xref">Installation Guide</a>
106+
</div>
107+
108+
<div class="embeddedContent">
109+
<a href="user-guides/getting-started/first-package.html" class="xref">Create Your First Package</a>
110+
</div>
111+
112+
<div class="embeddedContent">
113+
<a href="user-guides/getting-started/quick-start.html" class="xref">Quick Start</a>
114+
</div>
115+
116+
## Core Concepts
117+
118+
<div class="embeddedContent">
119+
<a href="user-guides/core-concepts/architecture.html" class="xref">Architecture & Design</a>
120+
</div>
121+
122+
<div class="embeddedContent">
123+
<a href="user-guides/core-concepts/ir.html" class="xref">Intermediate Representation (IR)</a>
124+
</div>
125+
126+
<div class="embeddedContent">
127+
<a href="user-guides/core-concepts/package-structure.html" class="xref">Package Structure</a>
128+
</div>
129+
130+
## User Guides
131+
132+
### Properties, Items & Metadata
133+
- [Working with Properties](user-guides/properties-items/properties.md)
134+
- [Working with Items](user-guides/properties-items/items.md)
135+
- [Item Metadata](user-guides/properties-items/metadata.md)
136+
- [Conditional Logic](user-guides/properties-items/conditionals.md)
137+
138+
### Targets & Tasks
139+
- [Target Orchestration](user-guides/targets-tasks/orchestration.md)
140+
- [Built-in Tasks Reference](user-guides/targets-tasks/builtin-tasks.md)
141+
- [Task Outputs](user-guides/targets-tasks/task-outputs.md)
142+
143+
### Advanced Topics
144+
- [UsingTask Declarations](user-guides/advanced/usingtask.md)
145+
- [Multi-Target Framework Patterns](user-guides/advanced/multi-tfm.md)
146+
- [Choose/When/Otherwise](user-guides/advanced/choose.md)
147+
- [Import Statements](user-guides/advanced/imports.md)
148+
- [Strongly-Typed Helpers](user-guides/advanced/strongly-typed.md)
149+
150+
## CLI Reference
151+
152+
<div class="embeddedContent">
153+
<a href="user-guides/cli/index.html" class="xref">Command-Line Interface</a>
154+
</div>
155+
156+
## API Reference
157+
158+
Browse the complete API documentation:
159+
160+
<div class="embeddedContent">
161+
<a href="api/index.html" class="xref">API Reference</a>
162+
</div>
163+
164+
## Troubleshooting
165+
166+
<div class="embeddedContent">
167+
<a href="user-guides/troubleshooting/index.html" class="xref">Troubleshooting Guide</a>
168+
</div>
169+
170+
## Samples
171+
172+
Explore working examples in the repository:
173+
174+
- **MinimalSdkPackage**: A complete end-to-end SDK-style package definition
175+
- **ContosoSDK**: Comprehensive example demonstrating advanced patterns
176+
177+
## Contributing
178+
179+
Contributions are welcome! See the repository's CONTRIBUTING.md for guidelines on:
180+
181+
- Reporting bugs
182+
- Proposing features
183+
- Submitting pull requests
184+
- Code style and conventions
185+
186+
## License
187+
188+
JD.MSBuild.Fluent is licensed under the MIT License. See LICENSE in the repository root for details.
189+
190+
## Additional Resources
191+
192+
- [MSBuild Concepts (Microsoft Docs)](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-concepts)
193+
- [NuGet Package Authoring](https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package-msbuild)
194+
- [MSBuild SDK Resolver](https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk)
195+
- [MSBuild Reserved and Well-Known Properties](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-reserved-and-well-known-properties)

pkg/JD.MSBuild.Fluent.1.0.0.nupkg

996 KB
Binary file not shown.
209 KB
Binary file not shown.
319 KB
Binary file not shown.
319 KB
Binary file not shown.

src/JD.MSBuild.Fluent.Tasks/GenerateMSBuildAssets.cs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Text.Json;
56
#if !NET472
67
using System.Runtime.Loader;
78
#endif
@@ -224,25 +225,44 @@ public override bool Execute()
224225
return null;
225226
}
226227

227-
// Try to cast to PackageDefinition
228-
var packageDef = result as PackageDefinition;
229-
if (packageDef == null)
228+
// CRITICAL: Due to AssemblyLoadContext isolation, direct casting fails even when assemblies match.
229+
// Use JSON serialization/deserialization to bridge the type identity gap.
230+
try
230231
{
231-
// Type identity mismatch - keep diagnostics for debugging
232-
var resultType = result.GetType();
233-
var expectedType = typeof(PackageDefinition);
234-
Log.LogError($"Type identity mismatch!");
235-
Log.LogError($" Returned: {resultType.FullName} from {resultType.Assembly.Location}");
236-
Log.LogError($" Expected: {expectedType.FullName} from {expectedType.Assembly.Location}");
237-
Log.LogError($" Types equal: {resultType == expectedType}");
238-
Log.LogError($" Assemblies equal: {resultType.Assembly.FullName == expectedType.Assembly.FullName}");
232+
// Serialize the result from the user's context
233+
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions
234+
{
235+
WriteIndented = false,
236+
IncludeFields = true,
237+
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never
238+
});
239+
240+
Log.LogMessage(MessageImportance.Low, "Serialized PackageDefinition to JSON");
241+
242+
// Deserialize into our context
243+
var packageDef = JsonSerializer.Deserialize<PackageDefinition>(json, new JsonSerializerOptions
244+
{
245+
IncludeFields = true,
246+
PropertyNameCaseInsensitive = true
247+
});
248+
249+
if (packageDef == null)
250+
{
251+
Log.LogError("Failed to deserialize PackageDefinition from JSON");
252+
return null;
253+
}
254+
255+
Log.LogMessage(MessageImportance.Normal,
256+
$"Loaded package definition: {packageDef.Id}");
257+
258+
return packageDef;
259+
}
260+
catch (JsonException jsonEx)
261+
{
262+
Log.LogError($"JSON serialization failed: {jsonEx.Message}");
263+
Log.LogError("This likely means PackageDefinition or its properties are not JSON-serializable");
239264
return null;
240265
}
241-
242-
Log.LogMessage(MessageImportance.Normal,
243-
$"Loaded package definition: {packageDef.Id}");
244-
245-
return packageDef;
246266
}
247267
catch (ReflectionTypeLoadException ex)
248268
{

src/JD.MSBuild.Fluent.Tasks/JD.MSBuild.Fluent.Tasks.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
-->
3838
<PackageReference Include="Microsoft.Build.Framework" ExcludeAssets="runtime" />
3939
<PackageReference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="runtime" />
40+
41+
<!-- JSON serialization for cross-context type copying -->
42+
<PackageReference Include="System.Text.Json" />
4043
</ItemGroup>
4144

4245
<ItemGroup>

0 commit comments

Comments
 (0)