Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,23 @@ internal static IReadOnlyList<string> FindProjFileAtOrAbovePath(IPhysicalFileSys

protected override bool ProcessInternal(IEngineEnvironmentSettings environment, IPostAction action, ICreationEffects creationEffects, ICreationResult templateCreationResult, string outputBasePath)
{
bool hasConfiguredTargetFiles =
action.Args.TryGetValue("targetFiles", out string? targetFilesArg) &&
!string.IsNullOrWhiteSpace(targetFilesArg);

IReadOnlyList<string>? projectsToProcess = GetConfiguredFiles(action.Args, creationEffects, "targetFiles", outputBasePath);

if (!projectsToProcess.Any())
if (!projectsToProcess.Any() && hasConfiguredTargetFiles)
{
// targetFiles was specified but none matched in creationEffects (e.g., pre-existing project files).
// Try to resolve them directly from disk before falling back to directory search.
projectsToProcess = FindExistingTargetFiles(environment.Host.FileSystem, action.Args, outputBasePath);
}

if (!projectsToProcess.Any() && !hasConfiguredTargetFiles)
{
//If the author didn't opt in to the new behavior by specifying "targetFiles", search for project file in current output directory or above.
// The author didn't opt in to the new behavior by specifying "targetFiles",
// search for project file in current output directory or above.
HashSet<string> extensionLimiters = new(StringComparer.Ordinal);
if (action.Args.TryGetValue("projectFileExtensions", out string? projectFileExtensions))
{
Expand All @@ -65,11 +77,6 @@ protected override bool ProcessInternal(IEngineEnvironmentSettings environment,
}
}

if (!projectsToProcess.Any())
{
projectsToProcess = FindExistingTargetFiles(environment.Host.FileSystem, action.Args, outputBasePath);
}

if (!projectsToProcess.Any())
{
// no projects found. Error.
Expand Down
53 changes: 53 additions & 0 deletions test/dotnet.Tests/CommandTests/New/DotnetAddPostActionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,59 @@ public void AddRefCanHandleExistingProjectFiles()
Assert.Equal(referencedProjectFileFullPath, callback.Reference);
}

[Fact(DisplayName = nameof(AddRefWithTargetFilesIgnoresParentDirectoryProjects))]
public void AddRefWithTargetFilesIgnoresParentDirectoryProjects()
{
// Regression test: when targetFiles is specified pointing to a pre-existing project,
// the processor should find it on disk and NOT walk up the directory tree to find
// a different .csproj in a parent directory.
var callback = new MockAddProjectReferenceCallback();
DotnetAddPostActionProcessor actionProcessor = new(callback.AddPackageReference, callback.AddProjectReference);

string targetBasePath = _engineEnvironmentSettings.GetTempVirtualizedPath();
_engineEnvironmentSettings.Host.VirtualizeDirectory(targetBasePath);

// Create existing target project in a subdirectory (the correct target)
const string existingProjectFolder = "ExistingProject";
string existingProjectPath = Path.Combine(targetBasePath, existingProjectFolder);
const string existingProjectFile = "ExistingProject.csproj";
string existingProjectFileFullPath = Path.Combine(existingProjectPath, existingProjectFile);
_engineEnvironmentSettings.Host.FileSystem.WriteAllText(existingProjectFileFullPath, TestCsprojFile);

// Create a conflicting .csproj in the output directory itself (simulates stale file pollution)
string conflictingProjFile = Path.Combine(targetBasePath, "Conflicting.csproj");
_engineEnvironmentSettings.Host.FileSystem.WriteAllText(conflictingProjFile, TestCsprojFile);

string referencedProjectFileFullPath = Path.Combine(targetBasePath, "Reference.csproj");

var args = new Dictionary<string, string>()
{
{ "targetFiles", $"[\"{existingProjectFolder}/{existingProjectFile}\"]" },
{ "referenceType", "project" },
{ "reference", "Reference.csproj" }
};
var postAction =
new MockPostAction(default, default, default, default, default!)
{
ActionId = DotnetAddPostActionProcessor.ActionProcessorId, Args = args
};

// Empty creation effects — the existing project was NOT created by the template
MockCreationEffects creationEffects = new MockCreationEffects();

actionProcessor.Process(
_engineEnvironmentSettings,
postAction,
creationEffects,
new MockCreationResult(),
targetBasePath);

// The callback should receive the explicitly configured target file,
// NOT the conflicting .csproj from the parent/output directory
Assert.Equal(existingProjectFileFullPath, callback.Target);
Assert.Equal(referencedProjectFileFullPath, callback.Reference);
}

[Fact(DisplayName = nameof(AddRefCanTargetASingleProjectWithAJsonArray))]
public void AddRefCanTargetASingleProjectWithAJsonArray()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,11 +402,10 @@ public void CscOnly_CompilationDiagnostics()
Console.WriteLine("ran" + x);
""");

new DotnetCommand(Log, "run", "Program.cs", "-bl")
new DotnetCommand(Log, "run", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
.And.HaveStdOutContaining(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc)
// warning CS8600: Converting null literal or possible null value to non-nullable type.
.And.HaveStdOutContaining("warning CS8600")
.And.HaveStdOutContaining("ran");
Expand All @@ -415,11 +414,10 @@ public void CscOnly_CompilationDiagnostics()
Console.Write
""");

new DotnetCommand(Log, "run", "Program.cs", "-bl")
new DotnetCommand(Log, "run", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Fail()
.And.HaveStdOutContaining(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc)
// error CS1002: ; expected
.And.HaveStdOutContaining("error CS1002")
.And.HaveStdErrContaining(CliCommandStrings.RunCommandException);
Expand Down
Loading