Skip to content

File-based apps automatic discovery#82863

Merged
RikkiGibson merged 30 commits intodotnet:mainfrom
RikkiGibson:entry-point-discovery
Apr 3, 2026
Merged

File-based apps automatic discovery#82863
RikkiGibson merged 30 commits intodotnet:mainfrom
RikkiGibson:entry-point-discovery

Conversation

@RikkiGibson
Copy link
Copy Markdown
Member

@RikkiGibson RikkiGibson commented Mar 20, 2026

The feature doc lays out how this is meant to work.

Performance

As-is the discovery pass on dotnet/sdk runs in ~200ms on my PC without a cache and ~100ms with a warm cache.
In dotnet/runtime, ~600ms cold to ~170ms warm.

Note that the times in repos which actually have file-based apps, are going to reflect also the time of passing those file-based apps to the project system. So the more actual file-based apps that are discovered, the longer the reported pass will take.

TODOs

  • per discussion with @DamianEdwards, we want to try restricting automatic discovery to apps which start with #!. This is because soon, containing #: will no longer mean that the file is for sure an entry point. Non-entry-point files will be allowed to have them too. Still need to update impl and tests accordingly.
    • (Sub-item punted to Require #! for file-based app entry points #82944.) As part of this we want to emit a warning in files which use #:include, and use top-level statements, and do not use #!, that user should add #! in order for editor to treat the file consistently in the workspace. This specific part should perhaps come in separate PR.
  • (Punting this.) It was suggested to use vscode workspace storage instead of temp directory for the cache file. I didn't see any pre-existing examples of us doing this in the language server. Also, it seems like there would still be a need to use a cache directory if client is not vscode.

Client-side PR for new config: dotnet/vscode-csharp#9096

Cache format

Example cache file contents:
{
    "WorkspacePath": "c:\\Users\\rikki\\src\\roslyn",
    "LastWalkTimeUtc": "2026-03-24T22:41:36.945956+00:00",
    "FileBasedAppFullPaths": [
        "c:\\Users\\rikki\\src\\roslyn\\eng\\ensure-sources-synced.cs",
        "c:\\Users\\rikki\\src\\roslyn\\eng\\generate-compiler-code.cs",
        "c:\\Users\\rikki\\src\\roslyn\\eng\\snap.cs"
    ],
    "DirectoriesContainingCsproj": [
        "c:\\Users\\rikki\\src\\roslyn\\eng\\common\\internal",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\Core\\Analyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\Core\\CodeFixes",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\Core\\Tests\\LegacyTestFramework",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\Core\\Tests\\UnitTestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\CSharp\\Analyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\CSharp\\CodeFixes",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\CSharp\\Tests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\CodeStyle\\Tools",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\CodeAnalysisTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\MSBuildTaskTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\MSBuildTask\\MSBuild",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\MSBuildTask\\Sdk",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\Rebuild",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\RebuildTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Core\\SdkTaskTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\csc\\AnyCpu",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\csc\\arm64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\CommandLine",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\CSharp15",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\Emit",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\Emit2",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\Emit3",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\EndToEnd",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\IOperation",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\Semantic",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\Symbol",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\Syntax",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\CSharp\\Test\\WinRT",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Extension",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Server\\VBCSCompilerTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Server\\VBCSCompiler\\AnyCpu",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Server\\VBCSCompiler\\arm64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Test\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Test\\Resources\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\Test\\Utilities\\CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\VisualBasic\\vbc\\AnyCpu",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Compilers\\VisualBasic\\vbc\\arm64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Dependencies\\CodeAnalysis.Debugging",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Dependencies\\Collections",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Dependencies\\Contracts",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Dependencies\\PooledObjects",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Dependencies\\Threading",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Deployment",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\CSharpTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\CSharpTest2",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\DiagnosticsTestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\ExternalAccess\\Debugger",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\Test",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\TestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\Text",
        "c:\\Users\\rikki\\src\\roslyn\\src\\EditorFeatures\\XunitHook",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\Core\\Source\\ExpressionCompiler",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\Core\\Source\\FunctionResolver",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\Core\\Source\\ResultProvider\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\Core\\Test\\ExpressionCompiler",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\Core\\Test\\FunctionResolver",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\Core\\Test\\ResultProvider",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\CSharp\\Source\\ExpressionCompiler",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\CSharp\\Source\\ResultProvider\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\CSharp\\Test\\ExpressionCompiler",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\CSharp\\Test\\ResultProvider",
        "c:\\Users\\rikki\\src\\roslyn\\src\\ExpressionEvaluator\\Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\Core\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\CSharpTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\CSharp\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\DiagnosticsTestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\ExternalAccess\\AspNetCore",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\ExternalAccess\\Copilot",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\ExternalAccess\\HotReload",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\ExternalAccess\\HotReloadTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\ExternalAccess\\OmniSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\ExternalAccess\\OmniSharp.CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\ExternalAccess\\OmniSharpTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\Test",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Features\\TestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Interactive\\csi",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Interactive\\Host",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Interactive\\HostProcess\\x64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Interactive\\HostProcess\\x86",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Interactive\\HostTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\ExternalAccess\\CompilerDeveloperSDK",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\ExternalAccess\\Copilot",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\ExternalAccess\\VisualDiagnostics",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\Microsoft.CodeAnalysis.LanguageServer",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\Microsoft.CodeAnalysis.LanguageServer.UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\Microsoft.CommonLanguageServerProtocol.Framework",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\Microsoft.CommonLanguageServerProtocol.Framework.Example",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\Microsoft.CommonLanguageServerProtocol.Framework.UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\Protocol",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\Protocol.TestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\LanguageServer\\ProtocolUnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.CodeAnalysis.BuildClient.Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.CodeAnalysis.Compilers.Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.CodeAnalysis.EditorFeatures.Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.CodeAnalysis.Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.CodeAnalysis.Scripting.Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.Net.Compilers.Toolset\\AnyCpu",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.Net.Compilers.Toolset\\arm64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\Microsoft.Net.Compilers.Toolset\\Framework",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\VS.ExternalAPIs.Roslyn.Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\NuGet\\VS.Tools.Roslyn.Package",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.Analyzers\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.Analyzers\\CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.Analyzers\\Setup",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.Analyzers\\UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.AnalyzerUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.BannedApiAnalyzers\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.BannedApiAnalyzers\\CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.BannedApiAnalyzers\\Setup",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.BannedApiAnalyzers\\UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.ResxSourceGenerator\\Microsoft.CodeAnalysis.ResxSourceGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.ResxSourceGenerator\\Microsoft.CodeAnalysis.ResxSourceGenerator.CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.ResxSourceGenerator\\Microsoft.CodeAnalysis.ResxSourceGenerator.UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Microsoft.CodeAnalysis.ResxSourceGenerator\\Microsoft.CodeAnalysis.ResxSourceGenerator.VisualBasic",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\Microsoft.CodeAnalysis.Analyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\Microsoft.CodeAnalysis.BannedApiAnalyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\Microsoft.CodeAnalysis.Metrics",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\Microsoft.CodeAnalysis.ResxSourceGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\Microsoft.CodeAnalysis.RulesetToEditorconfigConverter",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\PerformanceSensitiveAnalyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\PublicApiAnalyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\Roslyn.Diagnostics.Analyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\NuGet\\Text.Analyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PerformanceSensitiveAnalyzers\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PerformanceSensitiveAnalyzers\\CSharp\\Analyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PerformanceSensitiveAnalyzers\\CSharp\\CodeFixes",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PerformanceSensitiveAnalyzers\\Setup",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PerformanceSensitiveAnalyzers\\UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PublicApiAnalyzers\\Core\\Analyzers",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PublicApiAnalyzers\\Core\\CodeFixes",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PublicApiAnalyzers\\Setup",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\PublicApiAnalyzers\\UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Roslyn.Diagnostics.Analyzers\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Roslyn.Diagnostics.Analyzers\\CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Roslyn.Diagnostics.Analyzers\\Setup",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Roslyn.Diagnostics.Analyzers\\UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Test.Utilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\TestReferenceAssembly",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Text.Analyzers\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Text.Analyzers\\CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Text.Analyzers\\Setup",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Text.Analyzers\\UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Tools\\GenerateAnalyzerNuspec",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Tools\\GenerateDocumentationAndConfigFiles",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Tools\\GenerateDocumentationAndConfigFilesForBrokenRuntime",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Tools\\Metrics",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Tools\\Metrics.Legacy",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Tools\\RulesetToEditorconfigConverter\\Source",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Tools\\RulesetToEditorconfigConverter\\Tests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\RoslynAnalyzers\\Utilities.UnitTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Scripting\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Scripting\\CoreTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Scripting\\CoreTest.Desktop",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Scripting\\CoreTestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Scripting\\CSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Scripting\\CSharpTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Scripting\\CSharpTest.Desktop",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Setup\\DevDivInsertionFiles",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Setup\\DevDivVsix\\CompilersPackage\\arm64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Setup\\DevDivVsix\\CompilersPackage\\x64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Setup\\DevDivVsix\\CompilersPackage\\x86",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Test\\PdbUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Test\\Perf\\StackDepthTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Test\\Perf\\tests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Test\\Perf\\Utilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\AnalyzerRunner",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\BuildActionTelemetryTable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\BuildBoss",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\BuildValidator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ExternalAccess\\Extensions",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ExternalAccess\\RazorCompiler",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ExternalAccess\\RazorCompilerTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ExternalAccess\\RazorTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ExternalAccess\\Razor\\EditorFeatures",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ExternalAccess\\Razor\\Features",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ExternalAccess\\Xaml",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\GenerateRulesMissingDocumentation",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\IdeBenchmarks",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\IdeCoreBenchmarks",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\ManifestGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\PrepareTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\Replay",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\SemanticSearch\\BuildTask",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\SemanticSearch\\Extensions",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\SemanticSearch\\ReferenceAssemblies",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\SemanticSearch\\Tests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\Source\\CompilerGeneratorTools\\Source\\BoundTreeGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\Source\\CompilerGeneratorTools\\Source\\CSharpErrorFactsGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\Source\\CompilerGeneratorTools\\Source\\CSharpSyntaxGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\Source\\CompilerGeneratorTools\\Source\\IOperationGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\Source\\RunTests",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Tools\\TestDiscoveryWorker",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\CodeLens",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Core\\Def",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Core\\Impl",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Core\\Test.Next",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\CSharp\\Impl",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\CSharp\\Test",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\DevKit\\Impl",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\ExternalAccess\\Apex",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\ExternalAccess\\Copilot",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\ExternalAccess\\EditorConfigGenerator",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\ExternalAccess\\FSharp",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\ExternalAccess\\FSharpTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\IntegrationTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\LiveShare\\Impl",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\LiveShare\\Test",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Razor",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\SemanticSearch",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Setup",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Setup.Dependencies",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Setup.ServiceHub\\arm64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Setup.ServiceHub\\x64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\VisualStudioDiagnosticsToolWindow",
        "c:\\Users\\rikki\\src\\roslyn\\src\\VisualStudio\\Xaml\\Impl",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\CoreTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\CoreTestUtilities",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\Core\\Desktop",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\Core\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\CSharpTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\CSharp\\Portable",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\MSBuild\\BuildHost",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\MSBuild\\Contracts",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\MSBuild\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\MSBuild\\Test",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\Remote\\Core",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\Remote\\ServiceHub",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\Remote\\ServiceHub.CoreComponents\\arm64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\Remote\\ServiceHub.CoreComponents\\x64",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\Remote\\ServiceHubTest",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\SharedUtilitiesAndExtensions\\Compiler\\Extensions",
        "c:\\Users\\rikki\\src\\roslyn\\src\\Workspaces\\TestAnalyzerReference"
    ]
}
Microsoft Reviewers: Open in CodeFlow

@RikkiGibson RikkiGibson force-pushed the entry-point-discovery branch from 064840b to e33723a Compare March 24, 2026 22:46
Comment thread docs/features/file-based-programs-vscode.md
@RikkiGibson RikkiGibson requested a review from jjonescz March 24, 2026 23:55
Copy link
Copy Markdown
Member

@jjonescz jjonescz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high-level LGTM

Comment thread docs/features/file-based-programs-vscode.md
@RikkiGibson RikkiGibson marked this pull request as ready for review March 26, 2026 04:43
@RikkiGibson RikkiGibson requested a review from a team as a code owner March 26, 2026 04:43
@RikkiGibson RikkiGibson requested a review from jjonescz March 26, 2026 04:43
Copy link
Copy Markdown
Member

@jjonescz jjonescz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the parts that I looked at LGTM :D

Copy link
Copy Markdown
Member

@dibarbet dibarbet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because soon, containing #: will no longer mean that the file is for sure an entry point. Non-entry-point files will be allowed to have them too. Still need to update impl and tests accordingly.

Would they still be part of an FBP context (e.g. referenced by an FBP)? If there's an explicit reference I don't think its wrong to include the FBP context for the file (think multi-targeted files)

@RikkiGibson
Copy link
Copy Markdown
Member Author

@dibarbet for a fresh review pass

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving comments so far while I sign off for the day.

Comment thread docs/features/file-based-programs-vscode.md
Comment thread docs/features/file-based-programs-vscode.md
{
await FindAndLoadEntryPointsAsync();
}
catch (Exception ex) when (FatalError.ReportAndCatch(ex))
Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good to have this error reporting, but you may want to consider having it in FindAndLoadEntryPointAsync() since it gives a better chance you won't get a stack unwind that might lose state.

{
throw ExceptionUtilities.Unreachable();
}
}, cancellationToken);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to pass this cancellation token elsewhere?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, are you thinking this should also go into FindAndLoadEntryPointsAsync() itself?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, and check it in a few places.

Comment on lines +945 to +946
// Directories can randomly fail to delete etc when we are thrashing the disk.
// Not a big deal and not a reason to fail the test, just move on to the next iteration instead.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that's not because we accidentally left some stuff running async?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, but, if there are additional checks I could put in here instead, to give evidence of whether that is happening, I'd be interested in knowing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a FailFast so we can see the process state? 😄

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved, but do double check if some of those Max() calls are needed in the recursive walk. I think they're going to force extra unnecessary scanning, but I'm happy to be proven wrong.

Only other real feedback is there's more you can move into the "AndStable" helper in the unit tests which would shorten things up and also add more coverage generally.

if (fileInfo.Kind == CsFileKind.Directory)
VisitDirectory(fileInfo.Path, Max(createdOrModifiedTimeUtc, fileInfo.CreatedOrModifiedTimeUtc));
else if (fileInfo.Kind == CsFileKind.Cs)
VisitCsFile(fileInfo.Path, Max(createdOrModifiedTimeUtc, fileInfo.CreatedOrModifiedTimeUtc));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit fishy: so if the directory has a newer time stamp than the file, that'd mean some file was added or removed. So good that we had to scan it again. But why are we cracking each of the .cs files? It seems like this should just be fileInfo.CreatedOrModifiedTimeUtc.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I don't imagine a modification to the file will always modify the directory timestamp; that'd be very strange in the case of hardlinks since there's not a backpointer anyways...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess at worst this potentially overreads a file if the directory timestamp changed? But I am not sure its necessary - maybe if there is a change in between original enumeration and when we get here?

Copy link
Copy Markdown
Member Author

@RikkiGibson RikkiGibson Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that when a new subdirectory is dropped in between walks, the timestamps of the subdirectory's files are not updated.
Only the "modified" timestamp of the parent directory, and the "created" timestamp of the subdirectory, are changed.

This means: even if a .cs file we encounter within the "new" subdirectory has old timestamps, we don't know whether we've seen it before or not, so we need to crack it.

Adding a comment.

{
throw ExceptionUtilities.Unreachable();
}
}, cancellationToken);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, and check it in a few places.

if (target is null)
{
_logger.LogWarning("Could not get a project for '{projectPath}' because it loaded with no targets", projectPath);
return null;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only case where we can return null? Maybe just throw in this case, since I think that means something is pretty broken?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Especially since you're calling GetRequiredProject in the other path....

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we get here if we failed to load the FBP file for some reason? What do we do in that case - fail to find the file and error out - or do we treat it as a standard misc file?

Not blocking here, but this is something we should make sure isn't a super horrible experience

return document;
}

ProjectInfo CreatePrimordialProjectInfo(ProjectSystemProjectFactory projectFactory)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider just removing this parameter -- since this now returning the ProjectInfo at best you only need the workspace, but since we've already got a closure here being created just don't pass anything at all.

});

var discovery = testLspServer.GetRequiredLspService<FileBasedProgramsEntryPointDiscovery>();
AssertEx.SequenceEqual([appFile.Path], discovery.FindEntryPoints(tempDir.Path));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the AndStable helper (ditto for any other uses in the file)

Comment on lines +400 to +405
var cacheDirectory = VirtualProjectXmlProvider.GetDiscoveryCacheDirectory(tempDir.Path);
Directory.Delete(cacheDirectory, recursive: true);

// Discovery without cache - should match
var uncachedResult = discovery.FindEntryPoints(tempDir.Path).Order(StringComparer.OrdinalIgnoreCase).ToArray();
AssertEx.SequenceEqual(uncachedResult, cachedResult, StringComparer.OrdinalIgnoreCase);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to move this into the AndStable helper so that way you get this same guarantee for all tests. Heck do it three times: the and stable first does it once (that verifies the incremental update), then a second time (verifies there's a no-op) and then a third time after deleting the cache.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(ditto rest of file)

Comment on lines +945 to +946
// Directories can randomly fail to delete etc when we are thrashing the disk.
// Not a big deal and not a reason to fail the test, just move on to the next iteration instead.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a FailFast so we can see the process state? 😄

try
{
using var token = listener.BeginAsyncOperation(nameof(FindAndLoadEntryPointsAsync));
await FindAndLoadEntryPointsAsync();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - you can use this pattern for the async listener and FatalError:

var token = asyncListener.BeginAsyncOperation(nameof(NavigateTo));
NavigateToAsync(searchResult, threadingContext, threadOperationExecutor)
.ReportNonFatalErrorAsync()
.CompletesAsyncOperation(token);

}
catch (Exception ex)
{
_logger.LogDebug("Could not read cache file: {ex.Message}", ex.Message);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe warning? Assuming we don't generally expect this to fail.

if (fileInfo.Kind == CsFileKind.Directory)
VisitDirectory(fileInfo.Path, Max(createdOrModifiedTimeUtc, fileInfo.CreatedOrModifiedTimeUtc));
else if (fileInfo.Kind == CsFileKind.Cs)
VisitCsFile(fileInfo.Path, Max(createdOrModifiedTimeUtc, fileInfo.CreatedOrModifiedTimeUtc));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess at worst this potentially overreads a file if the directory timestamp changed? But I am not sure its necessary - maybe if there is a change in between original enumeration and when we get here?

if (target is null)
{
_logger.LogWarning("Could not get a project for '{projectPath}' because it loaded with no targets", projectPath);
return null;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we get here if we failed to load the FBP file for some reason? What do we do in that case - fail to find the file and error out - or do we treat it as a standard misc file?

Not blocking here, but this is something we should make sure isn't a super horrible experience

@RikkiGibson
Copy link
Copy Markdown
Member Author

I'm going to leave a number of comments outstanding here and try to address in a follow up. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-IDE Feature - Run File #: and #! directives and file-based C# programs VSCode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants