Skip to content

Commit 0fc992c

Browse files
authored
Fixup project reference paths during file-based app conversion (#50860)
1 parent 809138b commit 0fc992c

File tree

3 files changed

+153
-2
lines changed

3 files changed

+153
-2
lines changed

src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Immutable;
45
using System.CommandLine;
56
using System.Diagnostics.CodeAnalysis;
67
using Microsoft.Build.Evaluation;
@@ -71,7 +72,7 @@ public override int Execute()
7172
{
7273
using var stream = File.Open(projectFile, FileMode.Create, FileAccess.Write);
7374
using var writer = new StreamWriter(stream, Encoding.UTF8);
74-
VirtualProjectBuildingCommand.WriteProjectFile(writer, directives, isVirtualProject: false,
75+
VirtualProjectBuildingCommand.WriteProjectFile(writer, UpdateDirectives(directives), isVirtualProject: false,
7576
userSecretsId: DetermineUserSecretsId());
7677
}
7778

@@ -161,6 +162,28 @@ void CopyFile(string source, string target)
161162
var actualValue = projectInstance.GetPropertyValue("UserSecretsId");
162163
return implicitValue == actualValue ? actualValue : null;
163164
}
165+
166+
ImmutableArray<CSharpDirective> UpdateDirectives(ImmutableArray<CSharpDirective> directives)
167+
{
168+
var result = ImmutableArray.CreateBuilder<CSharpDirective>(directives.Length);
169+
170+
foreach (var directive in directives)
171+
{
172+
// Fixup relative project reference paths (they need to be relative to the output directory instead of the source directory).
173+
if (directive is CSharpDirective.Project project &&
174+
!Path.IsPathFullyQualified(project.Name))
175+
{
176+
var modified = project.WithName(Path.GetRelativePath(relativeTo: targetDirectory, path: project.Name));
177+
result.Add(modified);
178+
}
179+
else
180+
{
181+
result.Add(directive);
182+
}
183+
}
184+
185+
return result.DrainToImmutable();
186+
}
164187
}
165188

166189
private string DetermineOutputDirectory(string file)

src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1885,7 +1885,11 @@ public sealed class Project(in ParseInfo info) : Named(info)
18851885
if (Directory.Exists(resolvedProjectPath))
18861886
{
18871887
var fullFilePath = MsbuildProject.GetProjectFileFromDirectory(resolvedProjectPath).FullName;
1888-
directiveText = Path.GetRelativePath(relativeTo: sourceDirectory, fullFilePath);
1888+
1889+
// Keep a relative path only if the original directive was a relative path.
1890+
directiveText = Path.IsPathFullyQualified(directiveText)
1891+
? fullFilePath
1892+
: Path.GetRelativePath(relativeTo: sourceDirectory, fullFilePath);
18891893
}
18901894
else if (!File.Exists(resolvedProjectPath))
18911895
{
@@ -1903,6 +1907,11 @@ public sealed class Project(in ParseInfo info) : Named(info)
19031907
};
19041908
}
19051909

1910+
public Project WithName(string name)
1911+
{
1912+
return new Project(Info) { Name = name };
1913+
}
1914+
19061915
public override string ToString() => $"#:project {Name}";
19071916
}
19081917
}

test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,125 @@ public void SameAsTemplate()
6868
.And.StartWith("""<Project Sdk="Microsoft.NET.Sdk">""");
6969
}
7070

71+
[Theory] // https://github.com/dotnet/sdk/issues/50832
72+
[InlineData("File", "Lib", "../Lib", "Project", "../Lib/lib.csproj")]
73+
[InlineData(".", "Lib", "./Lib", "Project", "../Lib/lib.csproj")]
74+
[InlineData(".", "Lib", "Lib/../Lib", "Project", "../Lib/lib.csproj")]
75+
[InlineData("File", "Lib", "../Lib", "File/Project", "../../Lib/lib.csproj")]
76+
[InlineData("File", "Lib", "..\\Lib", "File/Project", "../../Lib/lib.csproj")]
77+
public void ProjectReference_RelativePaths(string fileDir, string libraryDir, string reference, string outputDir, string convertedReference)
78+
{
79+
var testInstance = _testAssetsManager.CreateTestDirectory();
80+
81+
var libraryDirFullPath = Path.Join(testInstance.Path, libraryDir);
82+
Directory.CreateDirectory(libraryDirFullPath);
83+
File.WriteAllText(Path.Join(libraryDirFullPath, "lib.cs"), """
84+
public static class C
85+
{
86+
public static void M()
87+
{
88+
System.Console.WriteLine("Hello from library");
89+
}
90+
}
91+
""");
92+
File.WriteAllText(Path.Join(libraryDirFullPath, "lib.csproj"), $"""
93+
<Project Sdk="Microsoft.NET.Sdk">
94+
<PropertyGroup>
95+
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
96+
</PropertyGroup>
97+
</Project>
98+
""");
99+
100+
var fileDirFullPath = Path.Join(testInstance.Path, fileDir);
101+
Directory.CreateDirectory(fileDirFullPath);
102+
File.WriteAllText(Path.Join(fileDirFullPath, "app.cs"), $"""
103+
#:project {reference}
104+
C.M();
105+
""");
106+
107+
var expectedOutput = "Hello from library";
108+
109+
new DotnetCommand(Log, "run", "app.cs")
110+
.WithWorkingDirectory(fileDirFullPath)
111+
.Execute()
112+
.Should().Pass()
113+
.And.HaveStdOut(expectedOutput);
114+
115+
var outputDirFullPath = Path.Join(testInstance.Path, outputDir);
116+
new DotnetCommand(Log, "project", "convert", "app.cs", "-o", outputDirFullPath)
117+
.WithWorkingDirectory(fileDirFullPath)
118+
.Execute()
119+
.Should().Pass();
120+
121+
new DotnetCommand(Log, "run")
122+
.WithWorkingDirectory(outputDirFullPath)
123+
.Execute()
124+
.Should().Pass()
125+
.And.HaveStdOut(expectedOutput);
126+
127+
File.ReadAllText(Path.Join(outputDirFullPath, "app.csproj"))
128+
.Should().Contain($"""
129+
<ProjectReference Include="{convertedReference.Replace('/', Path.DirectorySeparatorChar)}" />
130+
""");
131+
}
132+
133+
[Fact] // https://github.com/dotnet/sdk/issues/50832
134+
public void ProjectReference_FullPath()
135+
{
136+
var testInstance = _testAssetsManager.CreateTestDirectory();
137+
138+
var libraryDirFullPath = Path.Join(testInstance.Path, "Lib");
139+
Directory.CreateDirectory(libraryDirFullPath);
140+
File.WriteAllText(Path.Join(libraryDirFullPath, "lib.cs"), """
141+
public static class C
142+
{
143+
public static void M()
144+
{
145+
System.Console.WriteLine("Hello from library");
146+
}
147+
}
148+
""");
149+
File.WriteAllText(Path.Join(libraryDirFullPath, "lib.csproj"), $"""
150+
<Project Sdk="Microsoft.NET.Sdk">
151+
<PropertyGroup>
152+
<TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework>
153+
</PropertyGroup>
154+
</Project>
155+
""");
156+
157+
var fileDirFullPath = Path.Join(testInstance.Path, "File");
158+
Directory.CreateDirectory(fileDirFullPath);
159+
File.WriteAllText(Path.Join(fileDirFullPath, "app.cs"), $"""
160+
#:project {libraryDirFullPath}
161+
C.M();
162+
""");
163+
164+
var expectedOutput = "Hello from library";
165+
166+
new DotnetCommand(Log, "run", "app.cs")
167+
.WithWorkingDirectory(fileDirFullPath)
168+
.Execute()
169+
.Should().Pass()
170+
.And.HaveStdOut(expectedOutput);
171+
172+
var outputDirFullPath = Path.Join(testInstance.Path, "File/Project");
173+
new DotnetCommand(Log, "project", "convert", "app.cs", "-o", outputDirFullPath)
174+
.WithWorkingDirectory(fileDirFullPath)
175+
.Execute()
176+
.Should().Pass();
177+
178+
new DotnetCommand(Log, "run")
179+
.WithWorkingDirectory(outputDirFullPath)
180+
.Execute()
181+
.Should().Pass()
182+
.And.HaveStdOut(expectedOutput);
183+
184+
File.ReadAllText(Path.Join(outputDirFullPath, "app.csproj"))
185+
.Should().Contain($"""
186+
<ProjectReference Include="{Path.Join(libraryDirFullPath, "lib.csproj")}" />
187+
""");
188+
}
189+
71190
[Fact]
72191
public void DirectoryAlreadyExists()
73192
{

0 commit comments

Comments
 (0)