diff --git a/buildTransitive/EmptyFileTargets.md b/buildTransitive/EmptyFileTargets.md
index 1bc15c7..85e4fdf 100644
--- a/buildTransitive/EmptyFileTargets.md
+++ b/buildTransitive/EmptyFileTargets.md
@@ -1,18 +1,21 @@
How it works:
1. Inputs="$(MSBuildThisFileFile)" - Tracks the package file timestamp
- 2. Outputs="$(EmptyFilesMarker)" - Tracks a marker file in the output directory
+ 2. Outputs="$(EmptyFilesMarker)" - Tracks a marker file in the intermediate output directory
3. MSBuild automatically skips the target if all outputs are newer than all inputs
4. SkipUnchangedFiles="true" - Additional optimization to skip individual unchanged files
5. Touch - Updates the marker file timestamp after successful copy
+ 6. DeleteEmptyFilesMarkerIfTargetMissing - If the EmptyFiles directory is missing from the output, deletes the marker file so the copy re-runs
This will only copy files when:
1. The targets is newer than the marker file, OR
- 2. The marker file doesn't exist (first build/clean build)
+ 2. The marker file doesn't exist (first build/clean build), OR
+ 3. The EmptyFiles directory doesn't exist in the output (marker is deleted to force re-copy)
Benefits:
* Dramatically reduces IO on incremental builds
* Works with dotnet clean (removes marker file)
+ * Recovers when the output directory is cleaned without cleaning the intermediate directory
* Still respects individual file changes via SkipUnchangedFiles
diff --git a/buildTransitive/EmptyFiles.targets b/buildTransitive/EmptyFiles.targets
index 427bde5..fcefd44 100644
--- a/buildTransitive/EmptyFiles.targets
+++ b/buildTransitive/EmptyFiles.targets
@@ -4,6 +4,12 @@
$(MSBuildThisFileDirectory)..\files
+
+
+
+
-
\ No newline at end of file
+
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index d3c3561..0b65cdd 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -10,10 +10,12 @@
+
+
\ No newline at end of file
diff --git a/src/Tests/BuildTargetsTests.cs b/src/Tests/BuildTargetsTests.cs
new file mode 100644
index 0000000..eeec61e
--- /dev/null
+++ b/src/Tests/BuildTargetsTests.cs
@@ -0,0 +1,130 @@
+public class BuildTargetsTests
+{
+ [Test]
+ public async Task BuildCopiesEmptyFiles()
+ {
+ using var temp = new TempDirectory();
+
+ var (nugetSource, packageVersion) = FindPackageInfo();
+ WriteCsproj(temp, packageVersion);
+ WriteNugetConfig(temp, nugetSource);
+
+ var exitCode = await DotnetBuild(temp);
+ That(exitCode, Is.EqualTo(0));
+
+ var emptyFilesDir = Path.Combine(temp.Path, "bin", "Release", "net10.0", "EmptyFiles");
+ That(Directory.Exists(emptyFilesDir), Is.True, "EmptyFiles directory should exist after build");
+ That(Directory.GetFiles(emptyFilesDir, "*", SearchOption.AllDirectories), Is.Not.Empty, "EmptyFiles directory should contain files");
+ }
+
+ [Test]
+ public async Task RecoverFromDeletedEmptyFilesDirectory()
+ {
+ using var temp = new TempDirectory();
+
+ var (nugetSource, packageVersion) = FindPackageInfo();
+ WriteCsproj(temp, packageVersion);
+ WriteNugetConfig(temp, nugetSource);
+
+ // First build
+ var exitCode = await DotnetBuild(temp);
+ That(exitCode, Is.EqualTo(0));
+
+ var emptyFilesDir = Path.Combine(temp.Path, "bin", "Release", "net10.0", "EmptyFiles");
+ That(Directory.Exists(emptyFilesDir), Is.True, "EmptyFiles directory should exist after first build");
+
+ // Delete EmptyFiles directory from output (leave obj/ intact so marker file survives)
+ Directory.Delete(emptyFilesDir, true);
+ That(Directory.Exists(emptyFilesDir), Is.False);
+
+ // Second build — should recover
+ exitCode = await DotnetBuild(temp);
+ That(exitCode, Is.EqualTo(0));
+
+ That(Directory.Exists(emptyFilesDir), Is.True, "EmptyFiles directory should exist after second build");
+ That(Directory.GetFiles(emptyFilesDir, "*", SearchOption.AllDirectories), Is.Not.Empty, "EmptyFiles directory should contain files after recovery");
+ }
+
+ static void WriteCsproj(TempDirectory temp, string version)
+ {
+ var csproj = $"""
+
+
+ net10.0
+ packages
+
+
+
+
+
+ """;
+ File.WriteAllText(Path.Combine(temp.Path, "Project.csproj"), csproj);
+ }
+
+ static void WriteNugetConfig(TempDirectory temp, string nugetSource)
+ {
+ var config = $"""
+
+
+
+
+
+
+
+ """;
+ File.WriteAllText(Path.Combine(temp.Path, "nuget.config"), config);
+ }
+
+ static async Task DotnetBuild(TempDirectory temp)
+ {
+ var startInfo = new ProcessStartInfo("dotnet", "build --configuration Release --disable-build-servers -nodeReuse:false /p:UseSharedCompilation=false")
+ {
+ WorkingDirectory = temp.Path,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+
+ using var process = Process.Start(startInfo)!;
+ var stdout = await process.StandardOutput.ReadToEndAsync();
+ var stderr = await process.StandardError.ReadToEndAsync();
+ await process.WaitForExitAsync();
+
+ await TestContext.Out.WriteLineAsync("STDOUT:");
+ await TestContext.Out.WriteLineAsync(stdout);
+ await TestContext.Out.WriteLineAsync("STDERR:");
+ await TestContext.Out.WriteLineAsync(stderr);
+
+ return process.ExitCode;
+ }
+
+ static (string nugetSource, string packageVersion) FindPackageInfo()
+ {
+ var nugetSource = FindNugetSource();
+ var packageVersion = FindPackageVersion(nugetSource);
+ return (nugetSource, packageVersion);
+ }
+
+ static string FindNugetSource()
+ {
+ var nugetsDir = Path.GetFullPath(Path.Combine(ProjectFiles.SolutionDirectory, "..", "nugets"));
+ if (Directory.Exists(nugetsDir))
+ {
+ return nugetsDir;
+ }
+
+ throw new InvalidOperationException($"Cannot find nugets directory at {nugetsDir}");
+ }
+
+ static string FindPackageVersion(string nugetSource)
+ {
+ var nupkg = Directory
+ .GetFiles(nugetSource, "EmptyFiles.*.nupkg")
+ .FirstOrDefault(_ => !Path.GetFileName(_).StartsWith("EmptyFiles.Tool")) ??
+ throw new InvalidOperationException($"Cannot find EmptyFiles nupkg in {nugetSource}");
+
+ var fileName = Path.GetFileNameWithoutExtension(nupkg);
+ return fileName["EmptyFiles.".Length..];
+ }
+}
\ No newline at end of file
diff --git a/src/Tests/GlobalUsings.cs b/src/Tests/GlobalUsings.cs
index f5a79e6..ad8e2a1 100644
--- a/src/Tests/GlobalUsings.cs
+++ b/src/Tests/GlobalUsings.cs
@@ -1,3 +1,5 @@
global using EmptyFiles;
global using NUnit.Framework;
-global using System.Collections.Immutable;
\ No newline at end of file
+global using System.Collections.Immutable;
+global using System.Diagnostics;
+global using System.Runtime.InteropServices;
\ No newline at end of file
diff --git a/src/Tests/SolutionDirectoryFinder.cs b/src/Tests/SolutionDirectoryFinder.cs
deleted file mode 100644
index a55bcbc..0000000
--- a/src/Tests/SolutionDirectoryFinder.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-static class SolutionDirectoryFinder
-{
- public static string Find([CallerFilePath] string testFile = "")
- {
- var testDirectory = Path.GetDirectoryName(testFile)!;
- if (!TryFind(testDirectory, out var solutionDirectory))
- {
- throw new("Could not find solution directory");
- }
-
- return solutionDirectory;
- }
-
- public static bool TryFind(string testDirectory, [NotNullWhen(true)] out string? path)
- {
- var currentDirectory = testDirectory;
- do
- {
- if (Directory
- .GetFiles(currentDirectory, "*.slnx").Length != 0)
- {
- path = currentDirectory;
- return true;
- }
-
- var parent = Directory.GetParent(currentDirectory);
- if (parent == null)
- {
- path = null;
- return false;
- }
-
- currentDirectory = parent.FullName;
- } while (true);
- }
-}
\ No newline at end of file
diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs
index 2bb7958..f409451 100644
--- a/src/Tests/Tests.cs
+++ b/src/Tests/Tests.cs
@@ -213,7 +213,7 @@ public void UseFile()
[Test]
public async Task WriteExtensions()
{
- var md = Path.Combine(SolutionDirectoryFinder.Find(), "extensions.include.md");
+ var md = Path.Combine(ProjectFiles.SolutionDirectory, "extensions.include.md");
File.Delete(md);
await using var writer = File.CreateText(md);
await WriteCategory(writer, "Archive", AllFiles.Archives);
diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj
index 19625b1..2438137 100644
--- a/src/Tests/Tests.csproj
+++ b/src/Tests/Tests.csproj
@@ -12,6 +12,8 @@
+
+