Skip to content

Commit 8e4a361

Browse files
committed
Walk Projects non graph
1 parent 4bf7bb7 commit 8e4a361

File tree

9 files changed

+212
-55
lines changed

9 files changed

+212
-55
lines changed

src/Ionide.ProjInfo/ProjectLoader2.fs

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ open Microsoft.Build.Graph
88
open System.Collections.Generic
99
open Microsoft.Build.Evaluation
1010
open Microsoft.Build.Framework
11+
open ProjectLoader
1112

1213
/// <summary>
1314
/// An awaitable wrapper around a task whose result is disposable. The wrapper is not disposable, so this prevents usage errors like "use _lock = myAsync()" when the appropriate usage should be "use! _lock = myAsync())".
@@ -186,11 +187,21 @@ type BuildManagerSession(?bm: BuildManager) =
186187

187188
/// <summary>Submits a graph build request to the current build and starts it asynchronously.</summary>
188189
/// <param name="buildRequest">GraphBuildRequestData encapsulates all of the data needed to submit a graph build request.</param>
190+
/// <param name="buildParameters">All of the settings which must be specified to start a build</param>
189191
/// <param name="ct">CancellationToken to cancel build submissions.</param>
190192
/// <returns>The BuildResult</returns>
191193
member x.BuildAsync(buildRequest: BuildRequestData, ?buildParameters: BuildParameters, ?ct: CancellationToken) =
192194
let ct = defaultArg ct CancellationToken.None
193-
let buildParameters = defaultArg buildParameters (BuildParameters(Loggers = [ ProjectLoader.ErrorLogger() ]))
195+
196+
let buildParameters =
197+
defaultArg
198+
buildParameters
199+
(BuildParameters(
200+
Loggers = [
201+
msBuildToLogProvider ()
202+
ProjectLoader.ErrorLogger()
203+
]
204+
))
194205

195206
lockAndStartBuild ct buildParameters
196207
<| fun () ->
@@ -216,6 +227,7 @@ type BuildManagerSession(?bm: BuildManager) =
216227

217228
/// <summary>Submits a graph build request to the current build and starts it asynchronously.</summary>
218229
/// <param name="graphBuildRequest">GraphBuildRequestData encapsulates all of the data needed to submit a graph build request.</param>
230+
/// <param name="buildParameters">All of the settings which must be specified to start a build</param>
219231
/// <param name="ct">CancellationToken to cancel build submissions.</param>
220232
/// <returns>the GraphBuildResult</returns>
221233
member x.BuildAsync(graphBuildRequest: GraphBuildRequestData, ?buildParameters: BuildParameters, ?ct: CancellationToken) =
@@ -254,25 +266,48 @@ module ProjectPropertyInstance =
254266
|> Option.map (fun v -> v.EvaluatedValue)
255267

256268

269+
type ProjectPath = string
270+
type TargetFramework = string
271+
type TargetFrameworks = string array
272+
273+
module TargetFrameworks =
274+
275+
let parse (tfms: string) =
276+
tfms
277+
|> Option.ofObj
278+
|> Option.bind (fun tfms ->
279+
tfms.Split(
280+
';',
281+
StringSplitOptions.TrimEntries
282+
||| StringSplitOptions.RemoveEmptyEntries
283+
)
284+
|> Option.ofObj
285+
)
286+
287+
288+
type ProjectMap<'a> = Map<ProjectPath, Map<TargetFramework, 'a>>
289+
290+
module ProjectMap =
291+
292+
let map (f: ProjectPath -> TargetFramework -> 'a -> 'a0) (m: ProjectMap<'a>) =
293+
m
294+
|> Map.map (fun k -> Map.map (f k))
295+
296+
type ProjectProjectMap = ProjectMap<Project>
297+
type ProjectGraphMap = ProjectMap<ProjectGraphNode>
298+
257299
module ProjectLoading =
258300

259-
let getAllTfms (projectPath: string) =
260-
let pi = ProjectInstance(projectPath)
301+
let getAllTfms (projectPath: ProjectPath) =
302+
let pi = ProjectInstance projectPath
261303

262304
pi.Properties
263305
|> (ProjectPropertyInstance.tryFind "TargetFramework"
264306
>> Option.map Array.singleton)
265307
|> Option.orElseWith (fun () ->
266308
pi.Properties
267309
|> ProjectPropertyInstance.tryFind "TargetFrameworks"
268-
|> Option.bind (fun tfms ->
269-
tfms.Split(
270-
';',
271-
StringSplitOptions.TrimEntries
272-
||| StringSplitOptions.RemoveEmptyEntries
273-
)
274-
|> Option.ofObj
275-
)
310+
|> Option.bind TargetFrameworks.parse
276311
)
277312

278313
let selectFirstTfm (projectPath: string) =
@@ -386,17 +421,49 @@ type ProjectLoader2 =
386421
return! session.BuildAsync(request, ?buildParameters = buildParameters, ?ct = ct)
387422
}
388423

389-
static member Execution(session: BuildManagerSession, projectInstances: (ProjectInstance * BuildParameters option) seq, ?targetsToBuild: string array, ?flags: BuildRequestDataFlags, ?ct: CancellationToken) =
390-
projectInstances
391-
|> Seq.map (fun (p, bp) -> ProjectLoader2.Execution(session, p, ?buildParameters = bp, ?targetsToBuild = targetsToBuild, ?flags = flags, ?ct = ct))
392-
|> Task.WhenAll
393-
394-
static member Execution(session: BuildManagerSession, projects: (Project * BuildParameters option) seq, ?targetsToBuild: string array, ?flags: BuildRequestDataFlags, ?ct: CancellationToken) =
395-
let instances =
396-
projects
397-
|> Seq.map (fun (p, bp) -> p.CreateProjectInstance(), bp)
398-
399-
ProjectLoader2.Execution(session, instances, ?targetsToBuild = targetsToBuild, ?flags = flags, ?ct = ct)
424+
static member ExecutionWalkReferences<'e when BuildResultFailure<'e>>
425+
(session: BuildManagerSession, projects: Project seq, buildParameters: Project -> BuildParameters option, ?targetsToBuild: string array, ?flags: BuildRequestDataFlags, ?ct: CancellationToken)
426+
=
427+
task {
428+
let projectsToVisit = Queue<Project> projects
429+
430+
let visited = Dictionary<Project, Result<BuildResult, 'e>>()
431+
432+
while projectsToVisit.Count > 0 do
433+
let p = projectsToVisit.Dequeue()
434+
435+
match visited.TryGetValue p with
436+
| true, _ -> ()
437+
| _ ->
438+
439+
let projectInstance = p.CreateProjectInstance()
440+
let bp = buildParameters p
441+
442+
let! result = ProjectLoader2.Execution(session, projectInstance, ?buildParameters = bp, ?targetsToBuild = targetsToBuild, ?flags = flags, ?ct = ct)
443+
visited.Add(p, result)
444+
445+
match result with
446+
| Ok result ->
447+
let references =
448+
result.ProjectStateAfterBuild.Items
449+
|> Seq.choose (fun item ->
450+
if
451+
item.ItemType = "_MSBuildProjectReferenceExistent"
452+
&& item.HasMetadata "FullPath"
453+
then
454+
Some(item.GetMetadataValue "FullPath")
455+
else
456+
None
457+
)
458+
ProjectLoader2.EvaluateAsProjectsAllTfms(references, projectCollection = p.ProjectCollection)
459+
|> Seq.iter projectsToVisit.Enqueue
460+
| _ -> ()
461+
|> ignore
462+
463+
return
464+
visited.Values
465+
|> Seq.toArray
466+
}
400467

401468
static member GetProjectInstance(buildResult: BuildResult) = buildResult.ProjectStateAfterBuild
402469

test/Ionide.ProjInfo.Tests/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ let main argv =
2020

2121
let args = [
2222
CLIArguments.Printer (TestPrinters.summaryPrinter defaultConfig.printer)
23-
CLIArguments.Verbosity LogLevel.Verbose
23+
CLIArguments.Verbosity LogLevel.Info
2424
]
2525

2626
Tests.runTestsWithCLIArgs

test/Ionide.ProjInfo.Tests/TestAssets.fs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,49 @@ let ``loader2-solution-with-2-projects`` = {
366366

367367
Expect.equal classlibf1ns21.SourceFiles.Length 3 "classlibf1 source files"
368368

369+
let classlibf2 =
370+
projectsAfterBuild
371+
|> Seq.find (fun x -> x.ProjectFileName.EndsWith("classlibf2.fsproj"))
372+
373+
Expect.equal classlibf2.SourceFiles.Length 3 "classlibf2 source files"
374+
Expect.equal classlibf2.TargetFramework "netstandard2.0" "classlibf1 target framework"
375+
}
376+
377+
378+
let ``loader2-no-solution-with-2-projects`` = {
379+
ProjDir = "loader2-no-solution-with-2-projects"
380+
EntryPoints = [
381+
"src"
382+
/ "classlibf1"
383+
/ "classlibf1.fsproj"
384+
]
385+
Expects =
386+
fun projectsAfterBuild ->
387+
let projectPaths =
388+
projectsAfterBuild
389+
|> Seq.map (_.ProjectFileName)
390+
|> String.concat "\n"
391+
392+
Expect.equal (Seq.length projectsAfterBuild) 3 $"Should be three projects but got {Seq.length projectsAfterBuild} : {projectPaths}"
393+
394+
let classlibf1s =
395+
projectsAfterBuild
396+
|> Seq.filter (fun x -> x.ProjectFileName.EndsWith("classlibf1.fsproj"))
397+
398+
Expect.hasLength classlibf1s 2 ""
399+
400+
let classlibf1net80 =
401+
classlibf1s
402+
|> Seq.find (fun x -> x.TargetFramework = "net8.0")
403+
404+
Expect.equal classlibf1net80.SourceFiles.Length 3 "classlibf1 source files"
405+
406+
407+
let classlibf1ns21 =
408+
classlibf1s
409+
|> Seq.find (fun x -> x.TargetFramework = "netstandard2.1")
410+
411+
Expect.equal classlibf1ns21.SourceFiles.Length 3 "classlibf1 source files"
369412

370413
let classlibf2 =
371414
projectsAfterBuild

test/Ionide.ProjInfo.Tests/Tests.fs

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ let TestRunInvariantDir =
144144
/ "invariant"
145145

146146
let checkExitCodeZero (cmd: Command) =
147-
Expect.equal 0 cmd.Result.ExitCode "command finished with exit code non-zero."
147+
Expect.equal 0 cmd.Result.ExitCode $"command {cmd.Result.StandardOutput} finished with exit code non-zero."
148148

149149
let findByPath path parsed =
150150
parsed
@@ -1631,7 +1631,7 @@ let buildManagerSessionTests toolsPath =
16311631
testCaseTask
16321632
|> testWithEnv
16331633
"loader2-solution-with-2-projects - Graph"
1634-
``loader2-solution-with-2-projects``
1634+
``loader2-no-solution-with-2-projects``
16351635
(fun env ->
16361636
task {
16371637
let entrypoints =
@@ -1672,44 +1672,49 @@ let buildManagerSessionTests toolsPath =
16721672
testCaseTask
16731673
|> testWithEnv
16741674
"loader2-solution-with-2-projects"
1675-
``loader2-solution-with-2-projects``
1675+
``loader2-no-solution-with-2-projects``
16761676
(fun env ->
16771677
task {
16781678

16791679
let path = env.Entrypoints
16801680

16811681
let entrypoints =
16821682
path
1683-
|> Seq.collect (
1684-
InspectSln.tryParseSln
1685-
>> getResult
1686-
>> InspectSln.loadingBuildOrder
1683+
|> Seq.collect (fun p ->
1684+
if p.EndsWith(".sln") then
1685+
p
1686+
|> InspectSln.tryParseSln
1687+
|> getResult
1688+
|> InspectSln.loadingBuildOrder
1689+
else
1690+
[ p ]
16871691
)
16881692

16891693

16901694
// Evaluation
16911695
use pc = projectCollection ()
16921696

1693-
let graph =
1697+
1698+
let allprojects =
16941699
ProjectLoader2.EvaluateAsProjectsAllTfms(entrypoints, projectCollection = pc)
1695-
|> Seq.map (fun p ->
1696-
let fi = FileInfo p.FullPath
1697-
let projectName = Path.GetFileNameWithoutExtension fi.Name
1700+
|> Seq.toList
16981701

1699-
let tfm =
1700-
match p.GlobalProperties.TryGetValue("TargetFramework") with
1701-
| true, tfm -> tfm
1702-
| _ -> ""
1702+
let createBuildParametersFromProject (p: Project) =
1703+
let fi = FileInfo p.FullPath
1704+
let projectName = Path.GetFileNameWithoutExtension fi.Name
17031705

1704-
let normalized = normalizeFileName $"{projectName}-{tfm}"
1706+
let tfm =
1707+
match p.GlobalProperties.TryGetValue("TargetFramework") with
1708+
| true, tfm -> tfm.Replace('.', '_')
1709+
| _ -> ""
17051710

1706-
p, Some(BuildParameters(Loggers = env.Binlog.Loggers normalized))
1707-
)
1711+
let normalized = $"{projectName}-{tfm}"
17081712

1713+
Some(BuildParameters(Loggers = env.Binlog.Loggers normalized))
17091714
// Execution
17101715
let bm = new BuildManagerSession()
17111716

1712-
let! (results: Result<BuildResult, BuildErrors> array) = ProjectLoader2.Execution(bm, graph)
1717+
let! (results: Result<BuildResult, BuildErrors> array) = ProjectLoader2.ExecutionWalkReferences(bm, allprojects, createBuildParametersFromProject)
17131718

17141719
let projectsAfterBuild =
17151720
results
@@ -1801,25 +1806,25 @@ let buildManagerSessionTests toolsPath =
18011806
// Evaluation
18021807
use pc = projectCollection ()
18031808

1804-
let projs =
1805-
ProjectLoader2.EvaluateAsProjectsAllTfms(entryPoints, projectCollection = pc)
1806-
|> Seq.map (fun p ->
1807-
let fi = FileInfo p.FullPath
1808-
let projectName = Path.GetFileNameWithoutExtension fi.Name
1809+
let createBuildParametersFromProject (p: Project) =
1810+
let fi = FileInfo p.FullPath
1811+
let projectName = Path.GetFileNameWithoutExtension fi.Name
18091812

1810-
let tfm =
1811-
match p.GlobalProperties.TryGetValue("TargetFramework") with
1812-
| true, tfm -> tfm.Replace('.', '_')
1813-
| _ -> ""
1813+
let tfm =
1814+
match p.GlobalProperties.TryGetValue("TargetFramework") with
1815+
| true, tfm -> tfm.Replace('.', '_')
1816+
| _ -> ""
18141817

1815-
let normalized = normalizeFileName $"{projectName}-{tfm}"
1816-
p, Some(BuildParameters(Loggers = loggers normalized))
1817-
)
1818+
let normalized = $"{projectName}-{tfm}"
1819+
1820+
Some(BuildParameters(Loggers = env.Binlog.Loggers normalized))
1821+
1822+
let projs = ProjectLoader2.EvaluateAsProjectsAllTfms(entryPoints, projectCollection = pc)
18181823

18191824
// Execution
18201825
let bm = new BuildManagerSession()
18211826

1822-
let! (results: Result<_, BuildErrors> array) = ProjectLoader2.Execution(bm, projs)
1827+
let! (results: Result<_, BuildErrors> array) = ProjectLoader2.ExecutionWalkReferences(bm, projs, createBuildParametersFromProject)
18231828

18241829
let result =
18251830
results
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace classlibf1
2+
3+
module Say =
4+
let hello name =
5+
printfn "Hello %s" name
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net8.0;netstandard2.1</TargetFrameworks>
5+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<Compile Include="Library.fs" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\classlibf2\classlibf2.fsproj" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace classlibf2
2+
3+
module Say =
4+
let hello name =
5+
printfn "Hello %s" name
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<Compile Include="Library.fs" />
10+
</ItemGroup>
11+
12+
</Project>

test/examples/loader2-solution-with-2-projects/src/classlibf1/classlibf1.fsproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@
99
<Compile Include="Library.fs" />
1010
</ItemGroup>
1111

12+
<ItemGroup>
13+
<ProjectReference Include="..\classlibf2\classlibf2.fsproj" />
14+
</ItemGroup>
15+
1216
</Project>

0 commit comments

Comments
 (0)