From d1dff67b283b29fc6b8edd499e2ad6477059a1fa Mon Sep 17 00:00:00 2001
From: botoddly <250804054+botoddly@users.noreply.github.com>
Date: Wed, 4 Mar 2026 22:25:31 +0100
Subject: [PATCH 1/4] Add failing test proving generated .cs files are not
compiled
---
.../Fluidify.Tests.Functional/CompileTests.cs | 70 +++++++++++++++++++
.../Fixtures/CompileApp/CompileApp.csproj | 16 +++++
.../Fixtures/CompileApp/Models/Greeter.cs | 6 ++
.../CompileApp/Models/Greeter.cs.fluid | 6 ++
.../Fixtures/CompileApp/Program.cs | 3 +
.../Fixtures/CompileApp/nuget.config | 8 +++
6 files changed, 109 insertions(+)
create mode 100644 tests/Fluidify.Tests.Functional/CompileTests.cs
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs.fluid
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Program.cs
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileApp/nuget.config
diff --git a/tests/Fluidify.Tests.Functional/CompileTests.cs b/tests/Fluidify.Tests.Functional/CompileTests.cs
new file mode 100644
index 0000000..9f019b9
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/CompileTests.cs
@@ -0,0 +1,70 @@
+using System.Diagnostics;
+using NUnit.Framework;
+
+namespace Fluidify.Tests.Functional;
+
+[TestFixture]
+public class CompileTests
+{
+ private static readonly string SolutionRoot = Path.GetFullPath(
+ Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", ".."));
+
+ private static readonly string FluidifyProject = Path.Combine(
+ SolutionRoot, "src", "Fluidify", "Fluidify.csproj");
+
+ private static readonly string CompileAppDir = Path.Combine(
+ SolutionRoot, "tests", "Fluidify.Tests.Functional", "Fixtures", "CompileApp");
+
+ [OneTimeSetUp]
+ public async Task PackAndBuildFluidify()
+ {
+ // Clear any cached Fluidify package from the global NuGet cache
+ (int ExitCode, string Output) localsResult = await RunDotnet("nuget locals global-packages --list");
+ string globalPackagesDir = localsResult.Output
+ .Replace("global-packages:", "")
+ .Replace("info :", "")
+ .Trim();
+ string fluidifyCache = Path.Combine(globalPackagesDir, "fluidify");
+ if (Directory.Exists(fluidifyCache))
+ Directory.Delete(fluidifyCache, true);
+
+ // Build Fluidify (GeneratePackageOnBuild produces the nupkg)
+ (int ExitCode, string Output) buildResult = await RunDotnet(
+ $"build \"{FluidifyProject}\" -c Release");
+ Assert.That(buildResult.ExitCode, Is.EqualTo(0),
+ $"Fluidify build failed:\n{buildResult.Output}");
+ }
+
+ [Test]
+ public async Task GeneratedCsFile_IsCompiledSuccessfully()
+ {
+ string generatedFile = Path.Combine(CompileAppDir, "Models", "Greeter.cs");
+
+ // Delete generated file to simulate a clean build
+ if (File.Exists(generatedFile))
+ File.Delete(generatedFile);
+
+ string compileAppProject = Path.Combine(CompileAppDir, "CompileApp.csproj");
+ (int ExitCode, string Output) result = await RunDotnet($"build \"{compileAppProject}\" --force");
+
+ Assert.That(result.ExitCode, Is.EqualTo(0),
+ $"CompileApp build failed — generated .cs file was not included in compilation:\n{result.Output}");
+ }
+
+ private static async Task<(int ExitCode, string Output)> RunDotnet(string arguments)
+ {
+ ProcessStartInfo psi = new ProcessStartInfo("dotnet", arguments)
+ {
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ };
+
+ using Process process = Process.Start(psi)!;
+ string stdout = await process.StandardOutput.ReadToEndAsync();
+ string stderr = await process.StandardError.ReadToEndAsync();
+ await process.WaitForExitAsync();
+
+ return (process.ExitCode, stdout + stderr);
+ }
+}
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
new file mode 100644
index 0000000..bcbeec8
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net9.0
+ Exe
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs
new file mode 100644
index 0000000..2b3bf12
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs
@@ -0,0 +1,6 @@
+namespace CompileApp.Models;
+
+public static class Greeter
+{
+ public static string Message => "HELLO, World!";
+}
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs.fluid b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs.fluid
new file mode 100644
index 0000000..518eac6
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Models/Greeter.cs.fluid
@@ -0,0 +1,6 @@
+namespace CompileApp.Models;
+
+public static class Greeter
+{
+ public static string Message => "{{ Greeting }}, World!";
+}
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Program.cs b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Program.cs
new file mode 100644
index 0000000..7c823e9
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/Program.cs
@@ -0,0 +1,3 @@
+using CompileApp.Models;
+
+Console.WriteLine(Greeter.Message);
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/nuget.config b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/nuget.config
new file mode 100644
index 0000000..0f2f46b
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/nuget.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
From b67ae6c7ad159a01dbe10f4ccceab6943479611b Mon Sep 17 00:00:00 2001
From: botoddly <250804054+botoddly@users.noreply.github.com>
Date: Wed, 4 Mar 2026 22:46:31 +0100
Subject: [PATCH 2/4] Auto-include generated .cs files in compilation with
Compile="false" opt-out
---
README.md | 22 ++++++++++++++-----
src/Fluidify/FluidifyTask.cs | 3 ++-
src/Fluidify/build/Fluidify.targets | 14 ++++++++++++
.../Fluidify.Tests.Functional/CompileTests.cs | 21 ++++++++++++++++++
.../Fixtures/CompileApp/CompileApp.csproj | 1 +
.../CompileFalseApp/CompileFalseApp.csproj | 17 ++++++++++++++
.../CompileFalseApp/Models/Greeter.cs | 6 +++++
.../CompileFalseApp/Models/Greeter.cs.fluid | 6 +++++
.../Fixtures/CompileFalseApp/Program.cs | 3 +++
.../Fixtures/CompileFalseApp/nuget.config | 8 +++++++
10 files changed, 94 insertions(+), 7 deletions(-)
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs.fluid
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Program.cs
create mode 100644 tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/nuget.config
diff --git a/README.md b/README.md
index 2f4a941..60db611 100644
--- a/README.md
+++ b/README.md
@@ -16,25 +16,35 @@ Add `` items to your project file. Each item points to a `.fluid` temp
```xml
-
+
```
Templates use standard [Liquid syntax](https://shopify.github.io/liquid/):
-**Models/Greeting.cs.fluid**
+**WelcomeMessage.cs.fluid**
```liquid
-public static class Greeting
+public static class WelcomeMessage
{
- public const string Value = "{{ Greeting }}, World!";
+ public const string Text = "Welcome, {{ Name }}!";
}
```
-Building the project renders each template before compilation. The output path is inferred by stripping the `.fluid` extension, so `Models/Greeting.cs.fluid` produces `Models/Greeting.cs`.
+Building the project renders each template before compilation. The output path is inferred by stripping the `.fluid` extension, so `WelcomeMessage.cs.fluid` produces `WelcomeMessage.cs`.
Fluidify works with any text format — C#, JSON, HTML, YAML, or anything else. The `.fluid` extension is just a convention; the template itself is plain text with Liquid tags.
+### Including generated C# in compilation
+
+By default, generated `.cs` files are not automatically compiled — MSBuild's SDK glob evaluates before templates are rendered. Add `Compile="true"` to include the output in compilation:
+
+```xml
+
+
+
+```
+
### Custom output path
Use the `Destination` metadata to write the output to a different location:
@@ -55,7 +65,7 @@ Relative paths are resolved from the project directory. Directories are created
Fluidify registers an MSBuild target that runs before `CoreCompile`. For each `` item it:
1. Parses the `.fluid` file using the Fluid template engine
-2. Passes all item metadata (except `Destination`) as template variables
+2. Passes all item metadata (except `Destination` and `Compile`) as template variables
3. Writes the rendered output to the inferred or specified destination
MSBuild tracks input and output timestamps, so templates are only re-rendered when the source file changes.
diff --git a/src/Fluidify/FluidifyTask.cs b/src/Fluidify/FluidifyTask.cs
index f26e826..005efef 100644
--- a/src/Fluidify/FluidifyTask.cs
+++ b/src/Fluidify/FluidifyTask.cs
@@ -47,7 +47,8 @@ public override bool Execute()
foreach (DictionaryEntry entry in item.CloneCustomMetadata())
{
string key = entry.Key.ToString();
- if (!string.Equals(key, "Destination", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(key, "Destination", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(key, "Compile", StringComparison.OrdinalIgnoreCase))
{
context.SetValue(key, entry.Value?.ToString() ?? "");
}
diff --git a/src/Fluidify/build/Fluidify.targets b/src/Fluidify/build/Fluidify.targets
index 9e0408e..df4c33d 100644
--- a/src/Fluidify/build/Fluidify.targets
+++ b/src/Fluidify/build/Fluidify.targets
@@ -26,4 +26,18 @@
+
+
+
+
+
+
+
+
diff --git a/tests/Fluidify.Tests.Functional/CompileTests.cs b/tests/Fluidify.Tests.Functional/CompileTests.cs
index 9f019b9..92e2839 100644
--- a/tests/Fluidify.Tests.Functional/CompileTests.cs
+++ b/tests/Fluidify.Tests.Functional/CompileTests.cs
@@ -35,6 +35,9 @@ public async Task PackAndBuildFluidify()
$"Fluidify build failed:\n{buildResult.Output}");
}
+ private static readonly string CompileFalseAppDir = Path.Combine(
+ SolutionRoot, "tests", "Fluidify.Tests.Functional", "Fixtures", "CompileFalseApp");
+
[Test]
public async Task GeneratedCsFile_IsCompiledSuccessfully()
{
@@ -51,6 +54,24 @@ public async Task GeneratedCsFile_IsCompiledSuccessfully()
$"CompileApp build failed — generated .cs file was not included in compilation:\n{result.Output}");
}
+ [Test]
+ public async Task GeneratedCsFile_CompileFalse_IsExcludedFromCompilation()
+ {
+ string generatedFile = Path.Combine(CompileFalseAppDir, "Models", "Greeter.cs");
+
+ // Delete generated file to simulate a clean build
+ if (File.Exists(generatedFile))
+ File.Delete(generatedFile);
+
+ string project = Path.Combine(CompileFalseAppDir, "CompileFalseApp.csproj");
+ (int ExitCode, string Output) result = await RunDotnet($"build \"{project}\" --force");
+
+ Assert.That(result.ExitCode, Is.Not.EqualTo(0),
+ $"Build should have failed — Compile=\"false\" should exclude the generated .cs from compilation:\n{result.Output}");
+ Assert.That(result.Output, Does.Contain("CS0246").Or.Contains("CS0103"),
+ "Expected a missing type/namespace compiler error");
+ }
+
private static async Task<(int ExitCode, string Output)> RunDotnet(string arguments)
{
ProcessStartInfo psi = new ProcessStartInfo("dotnet", arguments)
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
index bcbeec8..1164d7d 100644
--- a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
@@ -3,6 +3,7 @@
net9.0
Exe
+ enable
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj
new file mode 100644
index 0000000..d32b896
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net9.0
+ Exe
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs
new file mode 100644
index 0000000..6036187
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs
@@ -0,0 +1,6 @@
+namespace CompileFalseApp.Models;
+
+public static class Greeter
+{
+ public static string Message => "HELLO, World!";
+}
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs.fluid b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs.fluid
new file mode 100644
index 0000000..c287991
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Models/Greeter.cs.fluid
@@ -0,0 +1,6 @@
+namespace CompileFalseApp.Models;
+
+public static class Greeter
+{
+ public static string Message => "{{ Greeting }}, World!";
+}
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Program.cs b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Program.cs
new file mode 100644
index 0000000..9f3ac1d
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/Program.cs
@@ -0,0 +1,3 @@
+using CompileFalseApp.Models;
+
+Console.WriteLine(Greeter.Message);
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/nuget.config b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/nuget.config
new file mode 100644
index 0000000..0f2f46b
--- /dev/null
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/nuget.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
From 82845c01ed661535a3c9a73cbf1c8ef84299b734 Mon Sep 17 00:00:00 2001
From: botoddly <250804054+botoddly@users.noreply.github.com>
Date: Wed, 4 Mar 2026 22:49:55 +0100
Subject: [PATCH 3/4] Add comment explaining Remove+Include pattern in
AddFluidifyCompileItems
---
src/Fluidify/build/Fluidify.targets | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Fluidify/build/Fluidify.targets b/src/Fluidify/build/Fluidify.targets
index df4c33d..b193e43 100644
--- a/src/Fluidify/build/Fluidify.targets
+++ b/src/Fluidify/build/Fluidify.targets
@@ -30,7 +30,9 @@
DependsOnTargets="ProcessFluidTemplates"
Condition="'@(Fluidify)' != ''">
-
+
From 4eeb9ddeb158adb12a2da223397ca8738eaa40f0 Mon Sep 17 00:00:00 2001
From: botoddly <250804054+botoddly@users.noreply.github.com>
Date: Thu, 5 Mar 2026 06:41:38 +0100
Subject: [PATCH 4/4] Address PR #7 review comments
---
README.md | 14 +++++++++++---
src/Fluidify/Fluidify.csproj | 2 +-
src/Fluidify/FluidifyTask.cs | 7 +++++--
src/Fluidify/build/Fluidify.targets | 18 ++++++++++--------
.../Fixtures/CompileApp/CompileApp.csproj | 3 ++-
.../CompileFalseApp/CompileFalseApp.csproj | 3 ++-
.../Fixtures/SampleApp/SampleApp.csproj | 3 ++-
7 files changed, 33 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index 60db611..65c7374 100644
--- a/README.md
+++ b/README.md
@@ -35,13 +35,21 @@ Building the project renders each template before compilation. The output path i
Fluidify works with any text format — C#, JSON, HTML, YAML, or anything else. The `.fluid` extension is just a convention; the template itself is plain text with Liquid tags.
-### Including generated C# in compilation
+### Compilation
-By default, generated `.cs` files are not automatically compiled — MSBuild's SDK glob evaluates before templates are rendered. Add `Compile="true"` to include the output in compilation:
+Generated `.cs` files are automatically included in compilation. To opt out, set `Compile="false"`:
```xml
-
+
+
+```
+
+For non-`.cs` outputs that should be compiled, set `Compile="true"` explicitly:
+
+```xml
+
+
```
diff --git a/src/Fluidify/Fluidify.csproj b/src/Fluidify/Fluidify.csproj
index d295c46..31ded5b 100644
--- a/src/Fluidify/Fluidify.csproj
+++ b/src/Fluidify/Fluidify.csproj
@@ -4,7 +4,7 @@
netstandard2.0
latest
Fluidify
- 0.1.0
+ 0.0.0-local
stanoddly
A modern alternative to T4 — an MSBuild task that processes Fluid (Liquid) template files at build time to generate code, configuration, or any other text output.
Apache-2.0
diff --git a/src/Fluidify/FluidifyTask.cs b/src/Fluidify/FluidifyTask.cs
index 005efef..600cdaf 100644
--- a/src/Fluidify/FluidifyTask.cs
+++ b/src/Fluidify/FluidifyTask.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.IO;
using Fluid;
using Microsoft.Build.Framework;
@@ -9,6 +10,9 @@ namespace Fluidify;
public class FluidifyTask : Task
{
+ private static readonly HashSet ExcludedMetadataKeys =
+ new HashSet(StringComparer.OrdinalIgnoreCase) { "Destination", "Compile" };
+
[Required]
public ITaskItem[] Templates { get; set; } = Array.Empty();
@@ -47,8 +51,7 @@ public override bool Execute()
foreach (DictionaryEntry entry in item.CloneCustomMetadata())
{
string key = entry.Key.ToString();
- if (!string.Equals(key, "Destination", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(key, "Compile", StringComparison.OrdinalIgnoreCase))
+ if (!ExcludedMetadataKeys.Contains(key))
{
context.SetValue(key, entry.Value?.ToString() ?? "");
}
diff --git a/src/Fluidify/build/Fluidify.targets b/src/Fluidify/build/Fluidify.targets
index b193e43..50c837a 100644
--- a/src/Fluidify/build/Fluidify.targets
+++ b/src/Fluidify/build/Fluidify.targets
@@ -30,15 +30,17 @@
DependsOnTargets="ProcessFluidTemplates"
Condition="'@(Fluidify)' != ''">
-
+ <_FluidifyCompileOutput Include="%(Fluidify.OutputPath)"
+ Condition="('%(Fluidify.Compile)' == 'true') or
+ ($([System.IO.Path]::GetExtension('%(Fluidify.OutputPath)')) == '.cs' and '%(Fluidify.Compile)' != 'false')" />
+
+
+
-
-
+
+
+ <_FluidifyCompileOutput Remove="@(_FluidifyCompileOutput)" />
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
index 1164d7d..67d821b 100644
--- a/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileApp/CompileApp.csproj
@@ -7,7 +7,8 @@
-
+
+
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj
index d32b896..e678ca8 100644
--- a/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj
+++ b/tests/Fluidify.Tests.Functional/Fixtures/CompileFalseApp/CompileFalseApp.csproj
@@ -7,7 +7,8 @@
-
+
+
diff --git a/tests/Fluidify.Tests.Functional/Fixtures/SampleApp/SampleApp.csproj b/tests/Fluidify.Tests.Functional/Fixtures/SampleApp/SampleApp.csproj
index 44c91ca..4a2f50c 100644
--- a/tests/Fluidify.Tests.Functional/Fixtures/SampleApp/SampleApp.csproj
+++ b/tests/Fluidify.Tests.Functional/Fixtures/SampleApp/SampleApp.csproj
@@ -8,7 +8,8 @@
-
+
+