Skip to content

Commit f79a067

Browse files
committed
Enhance EvaluateAsGraphAllTfms method to improve multi-targeting support and clarify evaluation process
1 parent 4f9ae07 commit f79a067

File tree

1 file changed

+33
-60
lines changed

1 file changed

+33
-60
lines changed

src/Ionide.ProjInfo/ProjectLoader2.fs

Lines changed: 33 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -500,37 +500,51 @@ type ProjectLoader2 =
500500
/// <returns>A project graph representing the evaluated projects, each corresponding to a specific TargetFramework
501501
/// or TargetFrameworks defined in the project files.</returns>
502502
/// <remarks>
503-
/// This method evaluates each project file and checks for the presence of a "TargetFramework"
504-
/// property. If it exists, the project is returned as is. If it does not exist, it checks for the "TargetFrameworks"
505-
/// property and splits it into individual TargetFrameworks. For each TargetFramework, it creates
506-
/// a new project with the "TargetFramework" global property set to that TargetFramework.
503+
///
504+
/// MSBuild's ProjectGraph natively handles multi-targeting by creating "outer build" nodes (with TargetFrameworks)
505+
/// that reference "inner build" nodes (with individual TargetFramework values). However, when building a graph,
506+
/// only entry point nodes are built directly. This method performs two evaluations:
507+
/// 1. First pass: Discover all nodes in the graph (including outer/inner builds and all references)
508+
/// 2. Second pass: Create a new graph with only nodes that have a TargetFramework property set
509+
///
510+
/// This ensures that all inner builds are treated as entry points and get built directly, which is required
511+
/// for design-time analysis scenarios where we need build results for each TFM.
507512
/// </remarks>
508513
static member EvaluateAsGraphAllTfms(entryProjectFile: ProjectGraphEntryPoint seq, ?projectCollection: ProjectCollection, ?projectInstanceFactory) =
509-
// For some reason, the graph evaluation doesn't handle multiple TFMs well
510-
// So first we evaluate the graph to find all projects
514+
// MSBuild's ProjectGraph handles multi-TFM projects by creating:
515+
// - OuterBuild nodes: TargetFramework is empty, TargetFrameworks is set (dispatchers)
516+
// - InnerBuild nodes: TargetFramework is set (actual builds per TFM)
517+
// - NonMultitargeting nodes: Neither property meaningfully set
518+
//
519+
// First pass: Evaluate to discover all project nodes including inner builds created from outer builds
511520
let graph =
512521
ProjectLoader2.EvaluateAsGraph(entryProjectFile, ?projectCollection = projectCollection, ?projectInstanceFactory = projectInstanceFactory)
513522

514-
let inline tryGetTfmFromGlobalProps (node: ProjectGraphNode) =
523+
// Helper to get TargetFramework from global properties or project properties
524+
let tryGetTargetFramework (node: ProjectGraphNode) =
515525
match node.ProjectInstance.GlobalProperties.TryGetValue "TargetFramework" with
516-
| true, tfm -> Some tfm
517-
| _ -> None
518-
519-
let inline tryGetFromProps (node: ProjectGraphNode) =
520-
node.ProjectInstance.Properties
521-
|> ProjectPropertyInstance.tryFind "TargetFramework"
526+
| true, tfm when not (String.IsNullOrWhiteSpace tfm) -> Some tfm
527+
| _ ->
528+
node.ProjectInstance.Properties
529+
|> ProjectPropertyInstance.tryFind "TargetFramework"
530+
|> Option.filter (
531+
not
532+
<< String.IsNullOrWhiteSpace
533+
)
522534

523-
// Then we only care about those with a TargetFramework
524-
let projects =
535+
// Extract only nodes with a TargetFramework as new entry points
536+
// This filters out outer builds (which have TargetFrameworks but not TargetFramework)
537+
// and includes inner builds (which have TargetFramework set)
538+
let innerBuildEntryPoints =
525539
graph.ProjectNodes
526540
|> Seq.choose (fun node ->
527-
tryGetTfmFromGlobalProps node
528-
|> Option.orElseWith (fun () -> tryGetFromProps node)
541+
tryGetTargetFramework node
529542
|> Option.map (fun _ -> ProjectGraphEntryPoint(node.ProjectInstance.FullPath, globalProperties = node.ProjectInstance.GlobalProperties))
530543
)
531544

532-
// Then, re-evaluate the graph with those projects
533-
ProjectLoader2.EvaluateAsGraph(projects, ?projectCollection = projectCollection, ?projectInstanceFactory = projectInstanceFactory)
545+
// Second pass: Re-evaluate with inner builds as entry points
546+
// This ensures all inner builds are built directly and appear in ResultsByNode
547+
ProjectLoader2.EvaluateAsGraph(innerBuildEntryPoints, ?projectCollection = projectCollection, ?projectInstanceFactory = projectInstanceFactory)
534548

535549
/// <summary>
536550
/// Executes a build request against the BuildManagerSession.
@@ -664,47 +678,6 @@ type ProjectLoader2 =
664678
/// <param name="graphBuildResult">The GraphBuildResult to get the project instances from.</param>
665679
/// <returns>The project instances from the GraphBuildResult.</returns>
666680
static member GetProjectInstances(graphBuildResult: GraphBuildResult) =
667-
668-
// let start =
669-
// graphBuildResult.ResultsByNode
670-
// |> Seq.map (fun (KeyValue(node, _)) -> node)
671-
672-
// let projectsToVisit = Queue<ProjectGraphNode>(start)
673-
674-
// let visited = HashSet<ProjectGraphNode>()
675-
// let results = ResizeArray<ProjectInstance>()
676-
677-
// while projectsToVisit.Count > 0 do
678-
// let p = projectsToVisit.Dequeue()
679-
680-
// match visited.TryGetValue p with
681-
// | true, _ -> ()
682-
// | _ ->
683-
// visited.Add(p)
684-
// |> ignore
685-
686-
// p.ProjectReferences
687-
// |> Seq.iter (fun r -> projectsToVisit.Enqueue r)
688-
689-
690-
// p.ProjectInstance.Properties
691-
// |> ProjectPropertyInstance.tryFind "TargetFramework"
692-
// |> Option.iter (fun _ -> results.Add p.ProjectInstance)
693-
694-
695-
// results :> seq<_>
696-
// graphBuildResult.ResultsByNode
697-
// |> Seq.collect(fun (KeyValue(node,_)) ->
698-
699-
// let pi = node.ProjectInstance
700-
// match pi.Properties |> ProjectPropertyInstance.tryFind "TargetFrameworks" with
701-
// | Some x ->
702-
// Seq.empty
703-
// | _ ->
704-
// Seq.singleton pi
705-
706-
// )
707-
708681
graphBuildResult.ResultsByNode
709682
|> Seq.map (fun (KeyValue(node, result)) -> ProjectLoader2.GetProjectInstance result)
710683

0 commit comments

Comments
 (0)