Skip to content

Commit 69ff992

Browse files
authored
Directly create virtual project when dotnet run-api is missing for now (#78788)
From discussion with @jasonmalinowski, we think we should keep in the "directly creating" virtual project behavior that we had prior to #78648, at minimum until we can be pretty sure .NET 10 users will have an SDK with run-api available. Without this change, users running with .NET 10 preview 4 SDK will not get intellisense in VS Code. They would have to install a nightly .NET 10 or wait for preview 5 to come out. I did manually verify that this change works when SDK is pinned to preview 4. The log message about taking the fallback path gets written out and so on. Most of the change here is copy/pasted from deleted lines in the linked PR #78648. FYI @dibarbet we probably want to make sure this is in the next prerelease that goes out. Apologies for the inconvenience.
2 parents f5faea9 + 0415f65 commit 69ff992

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)