Skip to content

Commit

Permalink
Handling multi-targeting in FCS by adding an optional ProjectId; hand…
Browse files Browse the repository at this point in the history
…ling multi-projects in a better way for LanguageService, making sure we clear it out of the options table
  • Loading branch information
TIHan committed May 15, 2018
1 parent ae4e8e2 commit 82da8c5
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 31 deletions.
11 changes: 9 additions & 2 deletions src/fsharp/service/service.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,7 @@ type UnresolvedReferencesSet = UnresolvedReferencesSet of UnresolvedAssemblyRefe
type FSharpProjectOptions =
{
ProjectFileName: string
ProjectId: string option
SourceFiles: string[]
OtherOptions: string[]
ReferencedProjects: (string * FSharpProjectOptions)[]
Expand All @@ -1774,7 +1775,11 @@ type FSharpProjectOptions =
member x.ProjectOptions = x.OtherOptions
/// Whether the two parse options refer to the same project.
static member UseSameProjectFileName(options1,options2) =
options1.ProjectFileName = options2.ProjectFileName
match options1.ProjectId, options2.ProjectId with
| Some(projectId1), Some(projectId2) -> projectId1 = projectId2
| _ ->
options1.ProjectFileName = options2.ProjectFileName &&
options1.ProjectId = options2.ProjectId

/// Compare two options sets with respect to the parts of the options that are important to building.
static member AreSameForChecking(options1,options2) =
Expand Down Expand Up @@ -2660,7 +2665,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "ParseAndCheckFileInProject", filename, action)
async {
try
let strGuid = "_" + Guid.NewGuid().ToString()
let strGuid = "_ProjectId=" + options.ProjectId.ToString()
Logger.LogBlockMessageStart (filename + strGuid) LogCompilerFunctionId.Service_ParseAndCheckFileInProject

if implicitlyStartBackgroundWork then
Expand Down Expand Up @@ -2832,6 +2837,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
let options =
{
ProjectFileName = filename + ".fsproj" // Make a name that is unique in this directory.
ProjectId = None
SourceFiles = loadClosure.SourceFiles |> List.map fst |> List.toArray
OtherOptions = otherFlags
ReferencedProjects= [| |]
Expand Down Expand Up @@ -3184,6 +3190,7 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten
member ic.GetProjectOptionsFromCommandLineArgs(projectFileName, argv, ?loadedTimeStamp, ?extraProjectInfo: obj) =
let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading
{ ProjectFileName = projectFileName
ProjectId = None
SourceFiles = [| |] // the project file names will be inferred from the ProjectOptions
OtherOptions = argv
ReferencedProjects= [| |]
Expand Down
3 changes: 3 additions & 0 deletions src/fsharp/service/service.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ type public FSharpProjectOptions =
// Note that this may not reduce to just the project directory, because there may be two projects in the same directory.
ProjectFileName: string

/// This is the unique identifier for the project. If it's None, will key off of ProjectFileName in our caching.
ProjectId: string option

/// The files in the project
SourceFiles: string[]

Expand Down
2 changes: 2 additions & 0 deletions vsintegration/Utils/LanguageServiceProfiling/Options.fs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ let FCS (repositoryDir: string) : Options =

{ Options =
{ProjectFileName = repositoryDir </> @"src\fsharp\FSharp.Compiler.Private\FSharp.Compiler.Private.fsproj"
ProjectId = None
SourceFiles = files |> Array.map (fun x -> repositoryDir </> x)
OtherOptions =
[|@"-o:obj\Release\FSharp.Compiler.Private.dll"; "-g"; "--noframework";
Expand Down Expand Up @@ -301,6 +302,7 @@ let FCS (repositoryDir: string) : Options =
let VFPT (repositoryDir: string) : Options =
{ Options =
{ProjectFileName = repositoryDir </> @"src\FSharp.Editing\FSharp.Editing.fsproj"
ProjectId = None
SourceFiles =
[|@"src\FSharp.Editing\AssemblyInfo.fs";
@"src\FSharp.Editing\Common\Utils.fs";
Expand Down
29 changes: 29 additions & 0 deletions vsintegration/src/FSharp.Editor/Common/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module internal Microsoft.VisualStudio.FSharp.Editor.Extensions

open System
open System.IO
open System.Collections.Immutable
open Microsoft.CodeAnalysis
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.SourceCodeServices
Expand All @@ -24,6 +25,34 @@ type System.IServiceProvider with
member x.GetService<'T>() = x.GetService(typeof<'T>) :?> 'T
member x.GetService<'S, 'T>() = x.GetService(typeof<'S>) :?> 'T

type Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.AbstractProject with

member this.GetCurrentProjectReferenceIds() =
this.GetCurrentProjectReferences()
|> Seq.map (fun x -> x.ProjectId)
|> ImmutableArray.ToImmutableArray

type Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl with

member this.GetCurrentProjectReferenceIds(projectId: ProjectId) =
match this.ProjectTracker.GetProject(projectId) with
| null -> ImmutableArray.Empty
| project -> project.GetCurrentProjectReferenceIds()

/// Tries to get the project file path based on the project id.
member this.TryGetProjectFilePath(projectId: ProjectId) =
match this.ProjectTracker.GetProject(projectId) with
| null -> None
| project ->
let filePath = project.ProjectFilePath
if String.IsNullOrWhiteSpace(filePath) then None
else Some(filePath)

/// Gets a project file path. Throws if there is no project file path.
member this.GetProjectFilePath(projectId) =
match this.TryGetProjectFilePath(projectId) with
| None -> failwithf "Can't find project file path from %A." projectId
| Some(filePath) -> filePath

type FSharpNavigationDeclarationItem with
member x.RoslynGlyph : Glyph =
Expand Down
54 changes: 25 additions & 29 deletions vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,6 @@ type internal FSharpProjectOptionsManager
// the original options for editing
let singleFileProjectTable = ConcurrentDictionary<ProjectId, DateTime * FSharpParsingOptions * FSharpProjectOptions>()

let tryGetOrCreateProjectId (projectFileName:string) =
let projectDisplayName = projectDisplayNameOf projectFileName
Some (workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectDisplayName))

/// Retrieve the projectOptionsTable
member __.FSharpOptions = projectOptionsTable

Expand All @@ -147,38 +143,43 @@ type internal FSharpProjectOptionsManager
member this.AddOrUpdateSingleFileProject(projectId, data) = singleFileProjectTable.[projectId] <- data

/// Get the exact options for a single-file script
member this.ComputeSingleFileOptions (tryGetOrCreateProjectId, fileName, loadTime, fileContents) =
member this.ComputeSingleFileOptions (projectId, loadTime, fileContents) =
let filePath = workspace.GetProjectFilePath(projectId)
let deps = workspace.GetCurrentProjectReferenceIds(projectId) |> Seq.toArray

async {
let extraProjectInfo = Some(box workspace)
let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject |> Option.map(fun (_, _, projectOptions) -> projectOptions)
if SourceFile.MustBeSingleFileProject(fileName) then
let tryGetOptionsForReferencedProject _f = None
if SourceFile.MustBeSingleFileProject(filePath) then
// NOTE: we don't use a unique stamp for single files, instead comparing options structurally.
// This is because we repeatedly recompute the options.
let optionsStamp = None
let! options, _diagnostics = checkerProvider.Checker.GetProjectOptionsFromScript(fileName, fileContents, loadTime, [| |], ?extraProjectInfo=extraProjectInfo, ?optionsStamp=optionsStamp)
let! options, _diagnostics = checkerProvider.Checker.GetProjectOptionsFromScript(filePath, fileContents, loadTime, [| |], ?extraProjectInfo=extraProjectInfo, ?optionsStamp=optionsStamp)
// NOTE: we don't use FCS cross-project references from scripts to projects. THe projects must have been
// compiled and #r will refer to files on disk
let referencedProjectFileNames = [| |]
let site = ProjectSitesAndFiles.CreateProjectSiteForScript(fileName, referencedProjectFileNames, options)
let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, options.ExtraProjectInfo, Some projectOptionsTable)
let referencedProjectFileNames = [| |]
let site = ProjectSitesAndFiles.CreateProjectSiteForScript(filePath, referencedProjectFileNames, options)
let _deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, Some projectId, filePath, options.ExtraProjectInfo, Some projectOptionsTable)
let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions)
return (deps, parsingOptions, projectOptions)
else
let site = ProjectSitesAndFiles.ProjectSiteOfSingleFile(fileName)
let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, extraProjectInfo, Some projectOptionsTable)
let site = ProjectSitesAndFiles.ProjectSiteOfSingleFile(filePath)
let _deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, Some projectId, filePath, extraProjectInfo, Some projectOptionsTable)
let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions)
return (deps, parsingOptions, projectOptions)
}

/// Update the info for a project in the project table
member this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, site, userOpName, invalidateConfig) =
Logger.LogMessage ("InvalidateConfig=" + invalidateConfig.ToString()) LogEditorFunctionId.LanguageService_UpdateProjectInfo
member this.UpdateProjectInfo(projectId, site, userOpName, invalidateConfig) =
Logger.Log(LogEditorFunctionId.LanguageService_UpdateProjectInfo)

let referencedProjectIds = workspace.GetCurrentProjectReferenceIds(projectId) |> Seq.toArray

projectOptionsTable.AddOrUpdateProject(projectId, (fun isRefresh ->
let extraProjectInfo = Some(box workspace)
let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject |> Option.map(fun (_, _, projectOptions) -> projectOptions)
let referencedProjects, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId (site.ProjectFileName)), site.ProjectFileName, extraProjectInfo, Some projectOptionsTable)
let tryGetOptionsForReferencedProject _f = None
let _referencedProjects, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, Some projectId, site.ProjectFileName, extraProjectInfo, Some projectOptionsTable)
if invalidateConfig then checkerProvider.Checker.InvalidateConfiguration(projectOptions, startBackgroundCompileIfAlreadySeen = not isRefresh, userOpName = userOpName + ".UpdateProjectInfo")
let referencedProjectIds = referencedProjects |> Array.choose tryGetOrCreateProjectId
let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions)
referencedProjectIds, parsingOptions, Some site, projectOptions))

Expand Down Expand Up @@ -207,13 +208,11 @@ type internal FSharpProjectOptionsManager
match singleFileProjectTable.TryGetValue(projectId) with
| true, (loadTime, _, _) ->
try
let fileName = document.FilePath
let! cancellationToken = Async.CancellationToken
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
// NOTE: we don't use FCS cross-project references from scripts to projects. The projects must have been
// compiled and #r will refer to files on disk.
let tryGetOrCreateProjectId _ = None
let! _referencedProjectFileNames, parsingOptions, projectOptions = this.ComputeSingleFileOptions (tryGetOrCreateProjectId, fileName, loadTime, sourceText.ToString())
let! _referencedProjectFileNames, parsingOptions, projectOptions = this.ComputeSingleFileOptions (projectId, loadTime, sourceText.ToString())
this.AddOrUpdateSingleFileProject(projectId, (loadTime, parsingOptions, projectOptions))
return Some (parsingOptions, None, projectOptions)
with ex ->
Expand Down Expand Up @@ -244,7 +243,7 @@ type internal FSharpProjectOptionsManager
let siteProvider = this.ProvideProjectSiteProvider(project)
let projectSite = siteProvider.GetProjectSite()
if projectSite.CompilationSourceFiles.Length <> 0 then
this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, projectSite, userOpName, invalidateConfig)
this.UpdateProjectInfo(projectId, projectSite, userOpName, invalidateConfig)
| _ -> ()

/// Tell the checker to update the project info for the specified project id
Expand Down Expand Up @@ -419,14 +418,11 @@ type internal FSharpLanguageService(package : FSharpPackage) =
let invalidPathChars = set (Path.GetInvalidPathChars())
let isPathWellFormed (path: string) = not (String.IsNullOrWhiteSpace path) && path |> Seq.forall (fun c -> not (Set.contains c invalidPathChars))

let tryGetOrCreateProjectId (workspace: VisualStudioWorkspaceImpl) (projectFileName: string) =
let projectDisplayName = projectDisplayNameOf projectFileName
Some (workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectDisplayName))

let optionsAssociation = ConditionalWeakTable<IWorkspaceProjectContext, string[]>()

member private this.OnProjectAdded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectAdded", invalidateConfig=true)
member private this.OnProjectReloaded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectReloaded", invalidateConfig=true)
member private this.OnProjectRemoved(projectId) = projectInfoManager.ClearInfoForProject(projectId)
member private this.OnDocumentAdded(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumentInfoWithProjectId(projectId, documentId, "OnDocumentAdded", invalidateConfig=true)
member private this.OnDocumentReloaded(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumentInfoWithProjectId(projectId, documentId, "OnDocumentReloaded", invalidateConfig=true)

Expand All @@ -438,10 +434,10 @@ type internal FSharpLanguageService(package : FSharpPackage) =
match args.Kind with
| WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId)
| WorkspaceChangeKind.ProjectReloaded -> this.OnProjectReloaded(args.ProjectId)
| WorkspaceChangeKind.ProjectRemoved -> this.OnProjectRemoved(args.ProjectId)
| WorkspaceChangeKind.DocumentAdded -> this.OnDocumentAdded(args.ProjectId, args.DocumentId)
| WorkspaceChangeKind.DocumentReloaded -> this.OnDocumentReloaded(args.ProjectId, args.DocumentId)
| WorkspaceChangeKind.DocumentRemoved
| WorkspaceChangeKind.ProjectRemoved
| WorkspaceChangeKind.AdditionalDocumentAdded
| WorkspaceChangeKind.AdditionalDocumentReloaded
| WorkspaceChangeKind.AdditionalDocumentRemoved
Expand Down Expand Up @@ -556,7 +552,7 @@ type internal FSharpLanguageService(package : FSharpPackage) =

// update the cached options
if updated then
projectInfoManager.UpdateProjectInfo(tryGetOrCreateProjectId workspace, project.Id, site, userOpName + ".SyncProject", invalidateConfig=true)
projectInfoManager.UpdateProjectInfo(project.Id, site, userOpName + ".SyncProject", invalidateConfig=true)

member this.SetupProjectFile(siteProvider: IProvideProjectSite, workspace: VisualStudioWorkspaceImpl, userOpName) =
let userOpName = userOpName + ".SetupProjectFile"
Expand Down Expand Up @@ -619,7 +615,7 @@ type internal FSharpLanguageService(package : FSharpPackage) =
let projectDisplayName = projectDisplayNameOf projectFileName

let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectDisplayName)
let _referencedProjectFileNames, parsingOptions, projectOptions = projectInfoManager.ComputeSingleFileOptions (tryGetOrCreateProjectId workspace, fileName, loadTime, fileContents) |> Async.RunSynchronously
let _referencedProjectFileNames, parsingOptions, projectOptions = projectInfoManager.ComputeSingleFileOptions (projectId, loadTime, fileContents) |> Async.RunSynchronously
projectInfoManager.AddOrUpdateSingleFileProject(projectId, (loadTime, parsingOptions, projectOptions))

if isNull (workspace.ProjectTracker.GetProject projectId) then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ type internal ProjectSitesAndFiles() =
let option =
let newOption () = {
ProjectFileName = projectSite.ProjectFileName
ProjectId = projectId |> Option.map (fun x -> x.Id.ToString())
SourceFiles = projectSite.CompilationSourceFiles
OtherOptions = projectSite.CompilationOptions
ReferencedProjects = referencedProjectOptions
Expand Down
1 change: 1 addition & 0 deletions vsintegration/src/FSharp.LanguageService/FSharpSource.fs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ type internal FSharpSource_DEPRECATED(service:LanguageService_DEPRECATED, textLi
// get a sync parse of the file
let co, _ =
{ ProjectFileName = fileName + ".dummy.fsproj"
ProjectId = None
SourceFiles = [| fileName |]
OtherOptions = flags
ReferencedProjects = [| |]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ type internal ProjectSitesAndFiles() =
let option =
let newOption () = {
ProjectFileName = projectSite.ProjectFileName
ProjectId = None
SourceFiles = projectSite.CompilationSourceFiles
OtherOptions = projectSite.CompilationOptions
ReferencedProjects = referencedProjectOptions
Expand Down

0 comments on commit 82da8c5

Please sign in to comment.