Skip to content

Commit f801649

Browse files
authored
chore: Add regeneration triggers for library version, tool version, config properties, and generated files (#26)
1 parent 171aeed commit f801649

11 files changed

Lines changed: 1147 additions & 14 deletions

File tree

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,16 @@ The package orchestrates a MSBuild pipeline with these stages:
8282

8383
### Core Capabilities
8484

85-
- **🔄 Incremental Builds** - Only regenerates when database schema or configuration changes
85+
- **🔄 Incremental Builds** - Smart fingerprinting detects when regeneration is needed based on:
86+
- Library or tool version changes
87+
- Database schema modifications
88+
- Configuration file changes
89+
- MSBuild property overrides (`EfcptConfig*`)
90+
- Template file changes
91+
- Generated file changes (optional)
8692
- **🎨 T4 Template Support** - Customize code generation with your own templates
8793
- **📁 Smart File Organization** - Schema-based folders and namespaces
88-
- **🔧 Highly Configurable** - Override namespaces, output paths, and generation options
94+
- **🔧 Highly Configurable** - Override namespaces, output paths, and generation options via MSBuild properties
8995
- **🌐 Multi-Schema Support** - Generate models across multiple database schemas
9096
- **📦 NuGet Ready** - Enterprise-ready package for production use
9197

docs/user-guide/api-reference.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,17 @@ Computes a composite fingerprint to detect when regeneration is needed.
135135
| `RenamingPath` | Yes | Path to renaming file |
136136
| `TemplateDir` | Yes | Path to templates |
137137
| `FingerprintFile` | Yes | Path to fingerprint cache file |
138+
| `ToolVersion` | No | EF Core Power Tools CLI version |
139+
| `GeneratedDir` | No | Directory containing generated files |
140+
| `DetectGeneratedFileChanges` | No | Whether to detect changes to generated files (default: false) |
141+
| `ConfigPropertyOverrides` | No | JSON string of MSBuild property overrides |
138142
| `LogVerbosity` | No | Logging level |
139143

140144
**Outputs:**
141145

142146
| Output | Description |
143147
|--------|-------------|
144-
| `Fingerprint` | Computed XxHash64 hash |
148+
| `Fingerprint` | Computed XxHash64 hash including library version, tool version, schema, config, overrides, templates, and optionally generated files |
145149
| `HasChanged` | Whether fingerprint changed |
146150

147151
### RunEfcpt
@@ -315,6 +319,7 @@ Applies MSBuild property overrides to the staged `efcpt-config.json` file. This
315319
| `EfcptDumpResolvedInputs` | `false` | Write resolved inputs to JSON |
316320
| `EfcptFingerprintFile` | `$(EfcptOutput)fingerprint.txt` | Fingerprint cache location |
317321
| `EfcptStampFile` | `$(EfcptOutput).efcpt.stamp` | Generation stamp file |
322+
| `EfcptDetectGeneratedFileChanges` | `false` | Detect changes to generated `.g.cs` files and trigger regeneration. **Warning**: When enabled, manual edits to generated files will be overwritten. |
318323

319324
### Config Override Properties
320325

docs/user-guide/core-concepts.md

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,36 +134,74 @@ Fingerprinting is a key optimization that prevents unnecessary code regeneration
134134

135135
### What's Included in the Fingerprint
136136

137+
The fingerprint includes multiple sources to ensure regeneration when any relevant input changes:
138+
139+
- **Library version** - Version of JD.Efcpt.Build.Tasks assembly
140+
- **Tool version** - EF Core Power Tools CLI version (`EfcptToolVersion`)
137141
- **DACPAC content** (in .sqlproj mode) or **schema metadata** (in connection string mode)
138-
- **efcpt-config.json** - Generation options, namespaces, table selection (including MSBuild overrides)
142+
- **efcpt-config.json** - Generation options, namespaces, table selection
143+
- **MSBuild property overrides** - All `EfcptConfig*` properties set in the .csproj
139144
- **efcpt.renaming.json** - Custom naming rules
140145
- **T4 templates** - All template files and their contents
146+
- **Generated files** (optional) - When `EfcptDetectGeneratedFileChanges=true`, includes fingerprints of generated `.g.cs` files
141147

142-
Note: The fingerprint is computed after MSBuild property overrides are applied, so changing an override property (like `EfcptConfigRootNamespace`) will trigger regeneration.
148+
**Important**: The fingerprint is computed after MSBuild property overrides are applied, so changing any `EfcptConfig*` property (like `EfcptConfigRootNamespace`) will automatically trigger regeneration.
143149

144150
All hashing uses XxHash64, a fast non-cryptographic hash algorithm.
145151

146152
### How Fingerprinting Works
147153

148154
```
149155
Build 1 (first run):
150-
Fingerprint = Hash(DACPAC/Schema + config + renaming + templates)
156+
Fingerprint = Hash(library + tool + DACPAC/Schema + config + overrides + renaming + templates)
151157
→ No previous fingerprint exists
152158
→ Generate models
153159
→ Store fingerprint
154160
155161
Build 2 (no changes):
156-
Fingerprint = Hash(DACPAC/Schema + config + renaming + templates)
162+
Fingerprint = Hash(library + tool + DACPAC/Schema + config + overrides + renaming + templates)
157163
→ Same as stored fingerprint
158164
→ Skip generation (fast build)
159165
160166
Build 3 (schema changed):
161-
Fingerprint = Hash(new DACPAC/Schema + config + renaming + templates)
167+
Fingerprint = Hash(library + tool + new DACPAC/Schema + config + overrides + renaming + templates)
162168
→ Different from stored fingerprint
163169
→ Regenerate models
164170
→ Store new fingerprint
171+
172+
Build 4 (config property changed):
173+
Fingerprint = Hash(library + tool + DACPAC/Schema + config + new overrides + renaming + templates)
174+
→ Different from stored fingerprint (overrides changed)
175+
→ Regenerate models
176+
→ Store new fingerprint
177+
```
178+
179+
### Regeneration Triggers
180+
181+
The following changes will automatically trigger model regeneration:
182+
183+
1. **Library upgrade** - When you update the JD.Efcpt.Build NuGet package
184+
2. **Tool version change** - When you change `<EfcptToolVersion>` in your .csproj
185+
3. **Database schema change** - Tables, columns, or relationships modified
186+
4. **Config file change** - efcpt-config.json or efcpt.renaming.json modified
187+
5. **MSBuild property change** - Any `<EfcptConfig*>` property changed in .csproj
188+
6. **Template change** - T4 template files added, removed, or modified
189+
7. **Generated file change** (optional) - When `<EfcptDetectGeneratedFileChanges>true</EfcptDetectGeneratedFileChanges>` is set
190+
191+
### Detecting Manual Edits (Optional)
192+
193+
By default, the system **does not** detect changes to generated files. This prevents accidentally overwriting manual edits you might make to generated code.
194+
195+
To enable detection of changes to generated files (useful in some workflows):
196+
197+
```xml
198+
<PropertyGroup>
199+
<EfcptDetectGeneratedFileChanges>true</EfcptDetectGeneratedFileChanges>
200+
</PropertyGroup>
165201
```
166202

203+
**Warning**: When enabled, any manual edits to `.g.cs` files will trigger regeneration, overwriting your changes. Only enable this if your workflow never involves manual edits to generated code.
204+
167205
### Forcing Regeneration
168206

169207
To force regeneration regardless of fingerprint:

src/JD.Efcpt.Build.Tasks/ComputeFingerprint.cs

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Reflection;
12
using System.Text;
23
using JD.Efcpt.Build.Tasks.Decorators;
34
using JD.Efcpt.Build.Tasks.Extensions;
@@ -11,10 +12,19 @@ namespace JD.Efcpt.Build.Tasks;
1112
/// </summary>
1213
/// <remarks>
1314
/// <para>
14-
/// The fingerprint is derived from the contents of the DACPAC, configuration JSON, renaming JSON, and
15-
/// every file under the template directory. For each input, an XxHash64 hash is computed and written into
16-
/// an internal manifest string, which is itself hashed using XxHash64 to produce the final
17-
/// <see cref="Fingerprint"/>.
15+
/// The fingerprint is derived from multiple sources to ensure regeneration when any relevant input changes:
16+
/// <list type="bullet">
17+
/// <item><description>Library version (JD.Efcpt.Build.Tasks assembly)</description></item>
18+
/// <item><description>Tool version (EF Core Power Tools CLI version)</description></item>
19+
/// <item><description>Database schema (DACPAC or connection string schema fingerprint)</description></item>
20+
/// <item><description>Configuration JSON file contents</description></item>
21+
/// <item><description>Renaming JSON file contents</description></item>
22+
/// <item><description>MSBuild config property overrides (EfcptConfig* properties)</description></item>
23+
/// <item><description>All template files under the template directory</description></item>
24+
/// <item><description>Generated files (optional, via <c>EfcptDetectGeneratedFileChanges</c>)</description></item>
25+
/// </list>
26+
/// For each input, an XxHash64 hash is computed and written into an internal manifest string,
27+
/// which is itself hashed using XxHash64 to produce the final <see cref="Fingerprint"/>.
1828
/// </para>
1929
/// <para>
2030
/// The computed fingerprint is compared to the existing value stored in <see cref="FingerprintFile"/>.
@@ -70,6 +80,26 @@ public sealed class ComputeFingerprint : Task
7080
/// </summary>
7181
public string LogVerbosity { get; set; } = "minimal";
7282

83+
/// <summary>
84+
/// Version of the EF Core Power Tools CLI tool package being used.
85+
/// </summary>
86+
public string ToolVersion { get; set; } = "";
87+
88+
/// <summary>
89+
/// Directory containing generated files to optionally include in the fingerprint.
90+
/// </summary>
91+
public string GeneratedDir { get; set; } = "";
92+
93+
/// <summary>
94+
/// Indicates whether to detect changes to generated files (default: false to avoid overwriting manual edits).
95+
/// </summary>
96+
public string DetectGeneratedFileChanges { get; set; } = "false";
97+
98+
/// <summary>
99+
/// Serialized JSON string containing MSBuild config property overrides.
100+
/// </summary>
101+
public string ConfigPropertyOverrides { get; set; } = "";
102+
73103
/// <summary>
74104
/// Newly computed fingerprint value for the current inputs.
75105
/// </summary>
@@ -99,6 +129,21 @@ private bool ExecuteCore(TaskExecutionContext ctx)
99129
var log = new BuildLog(ctx.Logger, LogVerbosity);
100130
var manifest = new StringBuilder();
101131

132+
// Library version (JD.Efcpt.Build.Tasks assembly)
133+
var libraryVersion = GetLibraryVersion();
134+
if (!string.IsNullOrWhiteSpace(libraryVersion))
135+
{
136+
manifest.Append("library\0").Append(libraryVersion).Append('\n');
137+
log.Detail($"Library version: {libraryVersion}");
138+
}
139+
140+
// Tool version (EF Core Power Tools CLI)
141+
if (!string.IsNullOrWhiteSpace(ToolVersion))
142+
{
143+
manifest.Append("tool\0").Append(ToolVersion).Append('\n');
144+
log.Detail($"Tool version: {ToolVersion}");
145+
}
146+
102147
// Source fingerprint (DACPAC OR schema fingerprint)
103148
if (UseConnectionStringMode.IsTrue())
104149
{
@@ -124,6 +169,13 @@ private bool ExecuteCore(TaskExecutionContext ctx)
124169
Append(manifest, ConfigPath, "config");
125170
Append(manifest, RenamingPath, "renaming");
126171

172+
// Config property overrides (MSBuild properties that override efcpt-config.json)
173+
if (!string.IsNullOrWhiteSpace(ConfigPropertyOverrides))
174+
{
175+
manifest.Append("config-overrides\0").Append(ConfigPropertyOverrides).Append('\n');
176+
log.Detail("Including MSBuild config property overrides in fingerprint");
177+
}
178+
127179
manifest = Directory
128180
.EnumerateFiles(TemplateDir, "*", SearchOption.AllDirectories)
129181
.Select(p => p.Replace('\u005C', '/'))
@@ -136,6 +188,23 @@ private bool ExecuteCore(TaskExecutionContext ctx)
136188
.Append(data.rel).Append('\0')
137189
.Append(data.h).Append('\n'));
138190

191+
// Generated files (optional, off by default to avoid overwriting manual edits)
192+
if (!string.IsNullOrWhiteSpace(GeneratedDir) && Directory.Exists(GeneratedDir) && DetectGeneratedFileChanges.IsTrue())
193+
{
194+
log.Detail("Detecting generated file changes (EfcptDetectGeneratedFileChanges=true)");
195+
manifest = Directory
196+
.EnumerateFiles(GeneratedDir, "*.g.cs", SearchOption.AllDirectories)
197+
.Select(p => p.Replace('\u005C', '/'))
198+
.OrderBy(p => p, StringComparer.Ordinal)
199+
.Select(file => (
200+
rel: Path.GetRelativePath(GeneratedDir, file).Replace('\u005C', '/'),
201+
h: FileHash.HashFile(file)))
202+
.Aggregate(manifest, (builder, data)
203+
=> builder.Append("generated/")
204+
.Append(data.rel).Append('\0')
205+
.Append(data.h).Append('\n'));
206+
}
207+
139208
Fingerprint = FileHash.HashString(manifest.ToString());
140209

141210
var prior = File.Exists(FingerprintFile) ? File.ReadAllText(FingerprintFile).Trim() : "";
@@ -155,6 +224,22 @@ private bool ExecuteCore(TaskExecutionContext ctx)
155224
return true;
156225
}
157226

227+
private static string GetLibraryVersion()
228+
{
229+
try
230+
{
231+
var assembly = typeof(ComputeFingerprint).Assembly;
232+
var version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
233+
?? assembly.GetName().Version?.ToString()
234+
?? "";
235+
return version;
236+
}
237+
catch
238+
{
239+
return "";
240+
}
241+
}
242+
158243
private static void Append(StringBuilder manifest, string path, string label)
159244
{
160245
var full = Path.GetFullPath(path);

0 commit comments

Comments
 (0)