Skip to content

Commit 70947aa

Browse files
committed
fix(Roslyn.Solution): implement a smarter way to selected the latest/most compatible fx
1 parent 0e87c9d commit 70947aa

File tree

4 files changed

+42
-60
lines changed

4 files changed

+42
-60
lines changed

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.9" />
3535
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
3636
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
37+
<PackageVersion Include="NuGet.Frameworks" Version="6.14.0" />
3738
<PackageVersion Include="NUnit" Version="3.14.0" />
3839
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
3940
<PackageVersion Include="System.Collections.Immutable" Version="9.0.0" />
4041
</ItemGroup>
41-
</Project>
42+
</Project>

src/CSharpLanguageServer/CSharpLanguageServer.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,5 +104,6 @@
104104
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
105105
<PackageReference Include="Microsoft.CodeAnalysis.Features" />
106106
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" />
107+
<PackageReference Include="NuGet.Frameworks" />
107108
</ItemGroup>
108109
</Project>

src/CSharpLanguageServer/Roslyn/Solution.fs

Lines changed: 36 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@ open Microsoft.CodeAnalysis
1515
open Microsoft.CodeAnalysis.MSBuild
1616
open Microsoft.CodeAnalysis.Text
1717
open Microsoft.Extensions.Logging
18+
open NuGet.Frameworks
1819

19-
open CSharpLanguageServer
2020
open CSharpLanguageServer.Lsp
2121
open CSharpLanguageServer.Logging
22-
open CSharpLanguageServer.Util
2322
open CSharpLanguageServer.Roslyn.WorkspaceServices
2423

25-
2624
let private logger = Logging.getLoggerByName "Roslyn.Solution"
2725

28-
2926
let initializeMSBuild () : unit =
3027
let vsInstanceQueryOpt = VisualStudioInstanceQueryOptions.Default
3128
let vsInstanceList = MSBuildLocator.QueryVisualStudioInstances(vsInstanceQueryOpt)
@@ -61,7 +58,6 @@ let initializeMSBuild () : unit =
6158

6259
MSBuildLocator.RegisterInstance(vsInstance)
6360

64-
6561
let solutionLoadProjectFilenames (solutionPath: string) =
6662
assert Path.IsPathRooted solutionPath
6763
let projectFilenames = new List<string>()
@@ -75,49 +71,6 @@ let solutionLoadProjectFilenames (solutionPath: string) =
7571
projectFilenames |> Set.ofSeq
7672

7773

78-
type TfmCategory =
79-
| NetFramework of Version
80-
| NetStandard of Version
81-
| NetCoreApp of Version
82-
| Net of Version
83-
| Unknown
84-
85-
86-
let selectLatestTfm (tfms: string seq) : string option =
87-
let parseTfm (tfm: string) : TfmCategory =
88-
let patterns =
89-
[ @"^net(?<major>\d)(?<minor>\d)?(?<build>\d)?$", NetFramework
90-
@"^netstandard(?<major>\d+)\.(?<minor>\d+)$", NetStandard
91-
@"^netcoreapp(?<major>\d+)\.(?<minor>\d+)$", NetCoreApp
92-
@"^net(?<major>\d+)\.(?<minor>\d+)$", Net ]
93-
94-
let matchingTfmCategory (pat, categoryCtor) =
95-
let m = Regex.Match(tfm.ToLowerInvariant(), pat)
96-
97-
if m.Success then
98-
let readVersionNum (groupName: string) =
99-
let group = m.Groups.[groupName]
100-
if group.Success then int group.Value else 0
101-
102-
Version(readVersionNum "major", readVersionNum "minor", readVersionNum "build")
103-
|> categoryCtor
104-
|> Some
105-
else
106-
None
107-
108-
patterns |> List.tryPick matchingTfmCategory |> Option.defaultValue Unknown
109-
110-
let rankTfm =
111-
function
112-
| Net v -> 3000 + v.Major * 10 + v.Minor
113-
| NetCoreApp v -> 2000 + v.Major * 10 + v.Minor
114-
| NetStandard v -> 1000 + v.Major * 10 + v.Minor
115-
| NetFramework v -> 0 + v.Major * 10 + v.Minor
116-
| Unknown -> -1
117-
118-
tfms |> Seq.sortByDescending (parseTfm >> rankTfm) |> Seq.tryHead
119-
120-
12174
let loadProjectTfms (projs: string seq) : Map<string, list<string>> =
12275
let mutable projectTfms = Map.empty
12376

@@ -156,6 +109,38 @@ let loadProjectTfms (projs: string seq) : Map<string, list<string>> =
156109

157110
projectTfms
158111

112+
let compatibleTfmsOfTwoSets afxs bfxs = seq {
113+
for a in afxs |> Seq.map NuGetFramework.Parse do
114+
for b in bfxs |> Seq.map NuGetFramework.Parse do
115+
if DefaultCompatibilityProvider.Instance.IsCompatible(a, b) then
116+
yield a.GetShortFolderName()
117+
else if DefaultCompatibilityProvider.Instance.IsCompatible(b, a) then
118+
yield b.GetShortFolderName()
119+
}
120+
121+
let compatibleTfmSet (fxSets: list<Set<string>>) : Set<string> =
122+
match fxSets.Length with
123+
| 0 -> Set.empty
124+
| 1 -> fxSets |> List.head
125+
| _ ->
126+
let firstSet = fxSets |> List.head
127+
128+
fxSets |> List.skip 1 |> Seq.fold compatibleTfmsOfTwoSets firstSet |> Set.ofSeq
129+
130+
let bestTfm (frameworks: string seq) : string option =
131+
let frameworks = frameworks |> Seq.map NuGetFramework.Parse |> List.ofSeq
132+
133+
match frameworks with
134+
| [] -> None
135+
| fxes ->
136+
let compatibleWithAllOtherFxes candidate =
137+
fxes
138+
|> Seq.forall (fun f -> DefaultCompatibilityProvider.Instance.IsCompatible(candidate, f))
139+
140+
fxes
141+
|> Seq.maxBy (fun fx -> compatibleWithAllOtherFxes fx, fx.HasPlatform, fx.Version)
142+
|> _.GetShortFolderName()
143+
|> Some
159144

160145
let applyWorkspaceTargetFrameworkProp (tfmsPerProject: Map<string, list<string>>) props : Map<string, string> =
161146
let selectedTfm =
@@ -164,14 +149,14 @@ let applyWorkspaceTargetFrameworkProp (tfmsPerProject: Map<string, list<string>>
164149
| _ ->
165150
tfmsPerProject.Values
166151
|> Seq.map Set.ofSeq
167-
|> Set.intersectMany
168-
|> selectLatestTfm
152+
|> List.ofSeq
153+
|> compatibleTfmSet
154+
|> bestTfm
169155

170156
match selectedTfm with
171157
| Some tfm -> props |> Map.add "TargetFramework" tfm
172158
| None -> props
173159

174-
175160
let resolveDefaultWorkspaceProps projs : Map<string, string> =
176161
let tfmsPerProject = loadProjectTfms projs
177162

@@ -188,7 +173,6 @@ let solutionGetProjectForPath (solution: Solution) (filePath: string) : Project
188173

189174
solution.Projects |> Seq.filter fileOnProjectDir |> Seq.tryHead
190175

191-
192176
let solutionTryAddDocument (docFilePath: string) (text: string) (solution: Solution) : Async<Document option> = async {
193177
let projectOnPath = solutionGetProjectForPath solution docFilePath
194178

@@ -211,7 +195,6 @@ let solutionTryAddDocument (docFilePath: string) (text: string) (solution: Solut
211195
return newDocumentMaybe
212196
}
213197

214-
215198
let selectPreferredSolution (slnFiles: string list) : option<string> =
216199
let getProjectCount (slnPath: string) =
217200
try
@@ -229,7 +212,6 @@ let selectPreferredSolution (slnFiles: string list) : option<string> =
229212
|> Seq.map snd
230213
|> Seq.tryHead
231214

232-
233215
let solutionTryLoadOnPath (lspClient: ILspClient) (solutionPath: string) =
234216
assert Path.IsPathRooted solutionPath
235217
let progress = ProgressReporter lspClient
@@ -282,7 +264,6 @@ let solutionTryLoadOnPath (lspClient: ILspClient) (solutionPath: string) =
282264
return None
283265
}
284266

285-
286267
let solutionTryLoadFromProjectFiles (lspClient: ILspClient) (logMessage: string -> Async<unit>) (projs: string list) =
287268
let progress = ProgressReporter lspClient
288269

@@ -324,7 +305,6 @@ let solutionTryLoadFromProjectFiles (lspClient: ILspClient) (logMessage: string
324305
return Some msbuildWorkspace.CurrentSolution
325306
}
326307

327-
328308
let solutionFindAndLoadOnDir (lspClient: ILspClient) dir = async {
329309
let fileNotOnNodeModules (filename: string) =
330310
filename.Split Path.DirectorySeparatorChar |> Seq.contains "node_modules" |> not
@@ -374,7 +354,6 @@ let solutionFindAndLoadOnDir (lspClient: ILspClient) dir = async {
374354
| Some solutionPath -> return! solutionTryLoadOnPath lspClient solutionPath
375355
}
376356

377-
378357
let solutionLoadSolutionWithPathOrOnCwd (lspClient: ILspClient) (solutionPathMaybe: string option) (cwd: string) =
379358
match solutionPathMaybe with
380359
| Some solutionPath -> async {

tests/CSharpLanguageServer.Tests/InternalTests.fs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ open CSharpLanguageServer.Roslyn.Solution
1010
[<TestCase("1.csproj:net8.0,netstandard2.0", "net8.0")>]
1111
[<TestCase("1.csproj:netstandard1.0,netstandard2.0", "netstandard2.0")>]
1212
[<TestCase("1.csproj:net40,net462,net6.0,net8.0,netcoreapp3.1,netstandard2.0", "net8.0")>]
13+
[<TestCase("1.csproj:net40,net462,net6.0,net8.0,net8.0-windows,netcoreapp3.1,netstandard2.0", "net8.0-windows")>]
1314
[<TestCase("1.csproj:net40,net462", "net462")>]
1415
[<TestCase("1.csproj:net8.0 2.csproj:net8.0", "net8.0")>]
15-
[<TestCase("1.csproj:net8.0,net10.0 2.csproj:netstandard2.0,net462", null)>]
16+
[<TestCase("1.csproj:net8.0,net10.0 2.csproj:netstandard2.0,net462", "net10.0")>]
1617
[<TestCase("1.csproj:net8.0,net10.0 2.csproj:net8.0,net10.0", "net10.0")>]
17-
[<TestCase("1.csproj:net8.0 2.csproj:net8.0,net10.0", "net8.0")>]
18+
[<TestCase("1.csproj:net8.0 2.csproj:net8.0,net10.0", "net10.0")>]
1819
[<TestCase("1.csproj:net8.0 2.csproj:net9.0-windows", "net9.0-windows")>]
1920
[<TestCase("1.csproj:net9.0 2.csproj:net9.0-windows", "net9.0-windows")>]
2021
let testApplyWorkspaceTargetFrameworkProp (tfmList: string, expectedTfm: string | null) =

0 commit comments

Comments
 (0)