Skip to content

Commit 0415f65

Browse files
committed
Directly create virtual project when dotnet run-api is missing for now
1 parent ffb4dcc commit 0415f65

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,10 @@ public async ValueTask TryRemoveMiscellaneousDocumentAsync(DocumentUri uri, bool
135135
var content = await _projectXmlProvider.GetVirtualProjectContentAsync(documentPath, cancellationToken);
136136
if (content is not var (virtualProjectContent, diagnostics))
137137
{
138-
// 'GetVirtualProjectContentAsync' will log errors when it fails
139-
return null;
138+
// https://github.com/dotnet/roslyn/issues/78618: falling back to this until dotnet run-api is more widely available
139+
_logger.LogInformation($"Failed to obtain virtual project for '{documentPath}' using dotnet run-api. Falling back to directly creating the virtual project.");
140+
virtualProjectContent = VirtualProjectXmlProvider.MakeVirtualProjectContent_DirectFallback(documentPath);
141+
diagnostics = [];
140142
}
141143

142144
foreach (var diagnostic in diagnostics)

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlProvider.cs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,106 @@ internal static bool IsFileBasedProgram(string documentFilePath, SourceText text
106106
var isFileBasedProgram = root.GetLeadingTrivia().Any(SyntaxKind.IgnoredDirectiveTrivia) || root.ChildNodes().Any(node => node.IsKind(SyntaxKind.GlobalStatement));
107107
return isFileBasedProgram;
108108
}
109+
110+
#region Temporary copy of subset of dotnet run-api behavior for fallback: https://github.com/dotnet/roslyn/issues/78618
111+
// See https://github.com/dotnet/sdk/blob/b5dbc69cc28676ac6ea615654c8016a11b75e747/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs#L10
112+
private static class Sha256Hasher
113+
{
114+
public static string Hash(string text)
115+
{
116+
byte[] bytes = Encoding.UTF8.GetBytes(text);
117+
byte[] hash = SHA256.HashData(bytes);
118+
#if NET9_0_OR_GREATER
119+
return Convert.ToHexStringLower(hash);
120+
#else
121+
return Convert.ToHexString(hash).ToLowerInvariant();
122+
#endif
123+
}
124+
125+
public static string HashWithNormalizedCasing(string text)
126+
{
127+
return Hash(text.ToUpperInvariant());
128+
}
129+
}
130+
131+
// See https://github.com/dotnet/sdk/blob/5a4292947487a9d34f4256c1d17fb3dc26859174/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs#L449
132+
internal static string GetArtifactsPath(string entryPointFileFullPath)
133+
{
134+
// We want a location where permissions are expected to be restricted to the current user.
135+
string directory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
136+
? Path.GetTempPath()
137+
: Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
138+
139+
// Include entry point file name so the directory name is not completely opaque.
140+
string fileName = Path.GetFileNameWithoutExtension(entryPointFileFullPath);
141+
string hash = Sha256Hasher.HashWithNormalizedCasing(entryPointFileFullPath);
142+
string directoryName = $"{fileName}-{hash}";
143+
144+
return Path.Join(directory, "dotnet", "runfile", directoryName);
145+
}
146+
#endregion
147+
148+
// https://github.com/dotnet/roslyn/issues/78618: falling back to this until dotnet run-api is more widely available
149+
internal static string MakeVirtualProjectContent_DirectFallback(string documentFilePath)
150+
{
151+
Contract.ThrowIfFalse(PathUtilities.IsAbsolute(documentFilePath));
152+
var artifactsPath = GetArtifactsPath(documentFilePath);
153+
154+
var targetFramework = Environment.GetEnvironmentVariable("DOTNET_RUN_FILE_TFM") ?? "net10.0";
155+
156+
var virtualProjectXml = $"""
157+
<Project>
158+
<PropertyGroup>
159+
<IncludeProjectNameInArtifactsPaths>false</IncludeProjectNameInArtifactsPaths>
160+
<ArtifactsPath>{SecurityElement.Escape(artifactsPath)}</ArtifactsPath>
161+
</PropertyGroup>
162+
<!-- We need to explicitly import Sdk props/targets so we can override the targets below. -->
163+
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
164+
<PropertyGroup>
165+
<OutputType>Exe</OutputType>
166+
<TargetFramework>{SecurityElement.Escape(targetFramework)}</TargetFramework>
167+
<ImplicitUsings>enable</ImplicitUsings>
168+
<Nullable>enable</Nullable>
169+
</PropertyGroup>
170+
<PropertyGroup>
171+
<EnableDefaultItems>false</EnableDefaultItems>
172+
</PropertyGroup>
173+
<PropertyGroup>
174+
<LangVersion>preview</LangVersion>
175+
</PropertyGroup>
176+
<PropertyGroup>
177+
<Features>$(Features);FileBasedProgram</Features>
178+
</PropertyGroup>
179+
<ItemGroup>
180+
<Compile Include="{SecurityElement.Escape(documentFilePath)}" />
181+
</ItemGroup>
182+
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
183+
<!--
184+
Override targets which don't work with project files that are not present on disk.
185+
See https://github.com/NuGet/Home/issues/14148.
186+
-->
187+
<Target Name="_FilterRestoreGraphProjectInputItems"
188+
DependsOnTargets="_LoadRestoreGraphEntryPoints"
189+
Returns="@(FilteredRestoreGraphProjectInputItems)">
190+
<ItemGroup>
191+
<FilteredRestoreGraphProjectInputItems Include="@(RestoreGraphProjectInputItems)" />
192+
</ItemGroup>
193+
</Target>
194+
<Target Name="_GetAllRestoreProjectPathItems"
195+
DependsOnTargets="_FilterRestoreGraphProjectInputItems"
196+
Returns="@(_RestoreProjectPathItems)">
197+
<ItemGroup>
198+
<_RestoreProjectPathItems Include="@(FilteredRestoreGraphProjectInputItems)" />
199+
</ItemGroup>
200+
</Target>
201+
<Target Name="_GenerateRestoreGraph"
202+
DependsOnTargets="_FilterRestoreGraphProjectInputItems;_GetAllRestoreProjectPathItems;_GenerateRestoreGraphProjectEntry;_GenerateProjectRestoreGraph"
203+
Returns="@(_RestoreGraphEntry)">
204+
<!-- Output from dependency _GenerateRestoreGraphProjectEntry and _GenerateProjectRestoreGraph -->
205+
</Target>
206+
</Project>
207+
""";
208+
209+
return virtualProjectXml;
210+
}
109211
}

0 commit comments

Comments
 (0)