Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
## Improvements:

## Bug fixes:
* Fix: GenerateFeatureFileCodeBehindTask fails with misleading DirectoryNotFoundException when ndjson output path exceeds Windows MAX_PATH (260)

*Contributors of this release (in alphabetical order):*
*Contributors of this release (in alphabetical order):* @clrudolphi

# v3.3.4 - 2026-03-23

Expand Down
49 changes: 35 additions & 14 deletions Reqnroll.Tools.MsBuild.Generation/GeneratedFileWriter.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
using System;
using System.IO;
using System.Text;
using Reqnroll.Utils;

namespace Reqnroll.Tools.MsBuild.Generation;

/// <summary>
/// This class is going to be obsolete once we implement MsBuild level up-to-date checks.
/// </summary>
public class GeneratedFileWriter(IReqnrollTaskLoggingHelper log)
{
public void WriteGeneratedFile(string outputPath, string generatedFileContent)
{
var path = NormalizePath(outputPath);
log.LogTaskDiagnosticMessage($"Writing data to {outputPath}");
WriteFile(outputPath, generatedFileContent);
WriteFile(path, generatedFileContent);
}

public void DeleteGeneratedFile(string outputPath)
{
if (!File.Exists(outputPath))
var path = NormalizePath(outputPath);

if (!File.Exists(path))
return;

log.LogTaskDiagnosticMessage($"Deleting {outputPath}");
try
{
File.Delete(outputPath);
File.Delete(path);
}
catch (IOException ex)
{
Expand All @@ -34,21 +35,41 @@ public void DeleteGeneratedFile(string outputPath)
private void WriteFile(string filePath, string content)
{
string directoryPath = Path.GetDirectoryName(filePath);
Comment thread
gasparnagy marked this conversation as resolved.
if (directoryPath != null && !Directory.Exists(directoryPath))
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}

WriteAllTextWithRetry(filePath, content, Encoding.UTF8);
}

/// <summary>
/// When building a multi-targeted project, the build system may try to write the same file multiple times,
Comment thread
gasparnagy marked this conversation as resolved.
/// and this can cause an IOException ("The process cannot access the file because it is being used by another process.").
/// See https://github.com/reqnroll/Reqnroll/issues/197
/// Once we move to Roslyn-based generation, this problem will go away, but for now, we use a workaround of
/// retrying the write operation a few times (the content is anyway the same).
/// </summary>
private static string NormalizePath(string path)
Comment thread
gasparnagy marked this conversation as resolved.
Outdated
{
if (string.IsNullOrWhiteSpace(path))
throw new ArgumentException("Path must not be null or empty.", nameof(path));

string fullPath = Path.GetFullPath(path);

// Cross-platform: only apply extended syntax on Windows.
if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows))
{
return fullPath;
}

// Already device/extended syntax.
if (fullPath.StartsWith(@"\\?\", StringComparison.Ordinal) ||
fullPath.StartsWith(@"\\.\", StringComparison.Ordinal))
return fullPath;

// UNC path.
if (fullPath.StartsWith(@"\\", StringComparison.Ordinal))
return @"\\?\UNC\" + fullPath.Substring(2);

// Drive-qualified path.
return @"\\?\" + fullPath;
}

private void WriteAllTextWithRetry(string path, string contents, Encoding encoding)
{
const int maxAttempts = 5;
Expand Down
Loading