From 2a44d6bc0f60c7b04be15743ef874d9fc25d09ef Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 6 Feb 2024 08:39:33 -0600 Subject: [PATCH] Add support for reference assemblies to ProjInfo (#200) * Add support for reference assemblies to the core project data model * flow through target ref path resolution logic in the ProjInfo.FCS layer * add test to verify reference assembly support --- .config/dotnet-tools.json | 2 +- .vscode/settings.json | 11 +++- CHANGELOG.md | 6 +++ src/Ionide.ProjInfo.FCS/Library.fs | 6 +-- src/Ionide.ProjInfo/Library.fs | 53 +++++++++--------- src/Ionide.ProjInfo/Types.fs | 10 +++- test/Ionide.ProjInfo.Tests/TestAssets.fs | 24 +++++++++ test/Ionide.ProjInfo.Tests/Tests.fs | 54 +++++++++++++++++++ test/examples/sample-netsdk-prodref/README.md | 1 + .../sample-netsdk-prodref/l1/Library.fs | 5 ++ .../sample-netsdk-prodref/l1/l1.fsproj | 12 +++++ .../sample-netsdk-prodref/l2/Library.fs | 5 ++ .../sample-netsdk-prodref/l2/l2.fsproj | 12 +++++ 13 files changed, 169 insertions(+), 32 deletions(-) create mode 100644 test/examples/sample-netsdk-prodref/README.md create mode 100644 test/examples/sample-netsdk-prodref/l1/Library.fs create mode 100644 test/examples/sample-netsdk-prodref/l1/l1.fsproj create mode 100644 test/examples/sample-netsdk-prodref/l2/Library.fs create mode 100644 test/examples/sample-netsdk-prodref/l2/l2.fsproj diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a0a34ca0..aa272694 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "paket": { - "version": "7.2.1", + "version": "8.0.3", "commands": [ "paket" ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 203e35cd..26a4a164 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,12 @@ "test/examples", "packages" ], - "editor.formatOnSave": true -} + "editor.formatOnSave": true, + "cSpell.words": [ + "binlog", + "inheritdoc", + "tfms", + "vswhere", + "xbuild" + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 353c63dd..6039f59f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.63.0] - 2024-02-06 + +### Changed + +* [Add support for reference assemblies to project cracking and FCS ProjectOptions mapping](https://github.com/ionide/proj-info/pull/200) + ## [0.62.0] - 2023-08-21 ### Changed diff --git a/src/Ionide.ProjInfo.FCS/Library.fs b/src/Ionide.ProjInfo.FCS/Library.fs index b3446f2f..bd2f06b3 100644 --- a/src/Ionide.ProjInfo.FCS/Library.fs +++ b/src/Ionide.ProjInfo.FCS/Library.fs @@ -49,14 +49,14 @@ module FCS = | Some p -> (p.ProjectFileName.EndsWith(".csproj") || p.ProjectFileName.EndsWith(".vbproj")) - && File.Exists p.TargetPath + && File.Exists p.ResolvedTargetPath | None -> false if p.ProjectFileName.EndsWith ".fsproj" then knownProject - |> Option.map (fun p -> + |> Option.map (fun (p: ProjectOptions) -> let theseOptions = makeFSharpProjectReference p - FSharpReferencedProject.FSharpReference(p.TargetPath, theseOptions) + FSharpReferencedProject.FSharpReference(p.ResolvedTargetPath, theseOptions) ) elif isDotnetProject knownProject then knownProject diff --git a/src/Ionide.ProjInfo/Library.fs b/src/Ionide.ProjInfo/Library.fs index ad4769b7..96806644 100644 --- a/src/Ionide.ProjInfo/Library.fs +++ b/src/Ionide.ProjInfo/Library.fs @@ -145,7 +145,7 @@ module LegacyFrameworkDiscovery = |> Some else // taken from https://github.com/microsoft/vswhere - // vswhere.exe is guranteed to be at the following location. refer to https://github.com/Microsoft/vswhere/issues/162 + // vswhere.exe is guaranteed to be at the following location. refer to https://github.com/Microsoft/vswhere/issues/162 let vsWhereDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "Installer") |> DirectoryInfo @@ -404,9 +404,9 @@ module ProjectLoader = ) if String.IsNullOrWhiteSpace tfm then - let tfms = pi.GetPropertyValue "TargetFrameworks" + let targetFrameworks = pi.GetPropertyValue "TargetFrameworks" - match tfms with + match targetFrameworks with | null -> None | tfms -> match tfms.Split(';') with @@ -558,7 +558,7 @@ module ProjectLoader = |> Seq.filter (fun p -> p.ItemType = "CscCommandLineArgs") |> Seq.map (fun p -> p.EvaluatedInclude) - let getP2Prefs (LoadedProject project) = + let getP2PRefs (LoadedProject project) = project.Items |> Seq.filter (fun p -> p.ItemType = "_MSBuildProjectReferenceExistent") |> Seq.map (fun p -> @@ -755,7 +755,7 @@ module ProjectLoader = path ) - let project = { + let project: ProjectOptions = { ProjectId = Some path ProjectFileName = path TargetFramework = sdkInfo.TargetFramework @@ -766,9 +766,11 @@ module ProjectLoader = LoadTime = DateTime.Now TargetPath = props - |> Seq.tryFind (fun n -> n.Name = "TargetPath") - |> Option.map (fun n -> n.Value) + |> Seq.tryPick (fun n -> if n.Name = "TargetPath" then Some n.Value else None) |> Option.defaultValue "" + TargetRefPath = + props + |> Seq.tryPick (fun n -> if n.Name = "TargetRefPath" then Some n.Value else None) ProjectOutputType = outputType ProjectSdkInfo = sdkInfo Items = compileItems @@ -804,13 +806,14 @@ module ProjectLoader = "BaseIntermediateOutputPath" "IntermediateOutputPath" "TargetPath" + "TargetRefPath" "IsCrossTargetingBuild" "TargetFrameworks" ] - let p2pRefs = getP2Prefs project + let p2pRefs = getP2PRefs project - let comandlineArgs = + let commandLineArgs = if path.EndsWith ".fsproj" then getFscArgs project else @@ -826,7 +829,7 @@ module ProjectLoader = Result.Error "not restored" else - let proj = mapToProject path comandlineArgs p2pRefs compileItems nuGetRefs sdkInfo props customProps + let proj = mapToProject path commandLineArgs p2pRefs compileItems nuGetRefs sdkInfo props customProps Result.Ok proj @@ -918,7 +921,7 @@ type WorkspaceLoaderViaProjectGraph private (toolsPath, ?globalProperties: (stri let globalProperties = ProjectLoader.getGlobalProps projectPath tfm globalProperties ProjectInstance(projectPath, globalProperties, toolsVersion = null, projectCollection = projectCollection) - let projectGraphProjs (paths: string seq) = + let projectGraphProjects (paths: string seq) = handleProjectGraphFailures <| fun () -> @@ -1044,28 +1047,28 @@ type WorkspaceLoaderViaProjectGraph private (toolsPath, ?globalProperties: (stri then handleError msbuildMessage result.Exception else - let buildProjs = + let builtProjects = result.ResultsByNode.Keys |> Seq.collect (fun (pgn: ProjectGraphNode) -> seq { yield pgn.ProjectInstance }) - |> Seq.toList + |> Seq.toArray - let projectsBuilt = Seq.length buildProjs + let projectsBuiltCount = builtProjects.Length match result.OverallResult with | BuildResultCode.Success -> logger.info ( - Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuilt:count}" + Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuiltCount:count}" >> Log.addExn result.Exception ) | BuildResultCode.Failure | _ -> logger.error ( - Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuilt:count} : {msbuildMessage:msbuildMessage} " + Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuiltCount:count} : {msbuildMessage:msbuildMessage} " >> Log.addExn result.Exception ) let projects = - buildProjs + builtProjects |> Seq.map (fun p -> p.FullPath, ProjectLoader.getLoadedProjectInfo p.FullPath customProperties (ProjectLoader.LoadedProject p)) |> Seq.choose (fun (projectPath, projectOptionResult) -> @@ -1111,7 +1114,7 @@ type WorkspaceLoaderViaProjectGraph private (toolsPath, ?globalProperties: (stri interface IWorkspaceLoader with override this.LoadProjects(projects: string list, customProperties, binaryLogs) = - projectGraphProjs projects + projectGraphProjects projects |> Option.map (fun pg -> loadProjects (pg, customProperties, binaryLogs)) |> Option.defaultValue Seq.empty @@ -1273,8 +1276,8 @@ type WorkspaceLoader private (toolsPath: ToolsPath, ?globalProperties: (string * member this.LoadSln(sln, customProperties: string list, binaryLogs) = match InspectSln.tryParseSln sln with | Ok(_, slnData) -> - let projs = InspectSln.loadingBuildOrder slnData - this.LoadProjects(projs, customProperties, binaryLogs) + let solutionProjects = InspectSln.loadingBuildOrder slnData + this.LoadProjects(solutionProjects, customProperties, binaryLogs) | Error d -> failwithf "Cannot load the sln: %A" d member this.LoadSln(sln, customProperties) = @@ -1340,20 +1343,20 @@ module ProjectViewer = |> (fun path -> path.EndsWith(assemblyAttributesName)) | None -> false - //the generated assemblyinfo.fs are not shown as sources - let isGeneratedAssemblyinfo (name: string) = + //The generated AssemblyInfo.fs are not shown as sources + let isGeneratedAssemblyInfo (name: string) = //TODO check is in `obj` dir for the tfm //TODO better, get the name from fsproj name.EndsWith($"{projName}.AssemblyInfo.{sourceFilesExtension}") let includeSourceFile (name: string) = not (isAssemblyAttributes name) - && not (isGeneratedAssemblyinfo name) + && not (isGeneratedAssemblyInfo name) sources |> List.choose ( function - | ProjectItem.Compile(name, fullpath) -> Some(name, fullpath) + | ProjectItem.Compile(name, fullPath) -> Some(name, fullPath) ) |> List.filter (fun (_, p) -> includeSourceFile p) @@ -1363,5 +1366,5 @@ module ProjectViewer = |> Path.GetFileNameWithoutExtension Items = compileFiles - |> List.map (fun (name, fullpath) -> ProjectViewerItem.Compile(fullpath, { ProjectViewerItemConfig.Link = name })) + |> List.map (fun (name, fullPath) -> ProjectViewerItem.Compile(fullPath, { ProjectViewerItemConfig.Link = name })) } diff --git a/src/Ionide.ProjInfo/Types.fs b/src/Ionide.ProjInfo/Types.fs index 53d61074..ec1af260 100644 --- a/src/Ionide.ProjInfo/Types.fs +++ b/src/Ionide.ProjInfo/Types.fs @@ -59,13 +59,21 @@ module Types = ReferencedProjects: ProjectReference list PackageReferences: PackageReference list LoadTime: DateTime + /// The path to the primary executable or loadable output of this project TargetPath: string + /// If present, this project produced a reference assembly and this should be used as primary reference for downstream proejcts + TargetRefPath: string option ProjectOutputType: ProjectOutputType ProjectSdkInfo: ProjectSdkInfo Items: ProjectItem list Properties: Property list CustomProperties: Property list - } + } with + /// ResolvedTargetPath is the path to the primary reference assembly for this project. + /// For projects that produce ReferenceAssemblies, this is the path to the reference assembly. + /// For other projects, this is the same as TargetPath. + member x.ResolvedTargetPath = + defaultArg x.TargetRefPath x.TargetPath type CompileItem = { Name: string diff --git a/test/Ionide.ProjInfo.Tests/TestAssets.fs b/test/Ionide.ProjInfo.Tests/TestAssets.fs index df33e182..c80e531e 100644 --- a/test/Ionide.ProjInfo.Tests/TestAssets.fs +++ b/test/Ionide.ProjInfo.Tests/TestAssets.fs @@ -280,3 +280,27 @@ let ``sample9 NetSdk library`` = { TargetFrameworks = Map.ofList [ "netstandard2.0", sourceFiles [ "Library.fs" ] ] ProjectReferences = [] } + +/// dotnet sdk library with ProduceReferenceAssembly=true +let ``NetSDK library with ProduceReferenceAssembly`` = { + ProjDir = "sample-netsdk-prodref" + AssemblyName = "l1" + ProjectFile = + "l1" + / "l1.fsproj" + TargetFrameworks = Map.ofList [ "netstandard2.0", sourceFiles [ "Library.fs" ] ] + ProjectReferences = [] +} + + +let ``NetSDK library referencing ProduceReferenceAssembly library`` = { + ProjDir = "sample-netsdk-prodref" + AssemblyName = "l2" + ProjectFile = + "l2" + / "l2.fsproj" + TargetFrameworks = Map.ofList [ "netstandard2.0", sourceFiles [ "Library.fs" ] ] + ProjectReferences = [ + ``NetSDK library with ProduceReferenceAssembly`` + ] +} diff --git a/test/Ionide.ProjInfo.Tests/Tests.fs b/test/Ionide.ProjInfo.Tests/Tests.fs index 593397a4..0a1f1237 100644 --- a/test/Ionide.ProjInfo.Tests/Tests.fs +++ b/test/Ionide.ProjInfo.Tests/Tests.fs @@ -30,6 +30,20 @@ let ExamplesDir = / "test" / "examples" +let pathForTestAssets (test: TestAssetProjInfo) = + ExamplesDir + / test.ProjDir + +let pathForProject (test: TestAssetProjInfo) = + pathForTestAssets test + / test.ProjectFile + +let implAssemblyForProject (test: TestAssetProjInfo) = + $"{test.AssemblyName}.dll" + +let refAssemblyForProject (test: TestAssetProjInfo) = + Path.Combine("ref", implAssemblyForProject test) + let TestRunDir = RepoDir / "test" @@ -1482,6 +1496,7 @@ let testFCSmapManyProjCheckCaching = PackageReferences = [] LoadTime = DateTime.MinValue TargetPath = "TP" + TargetRefPath = Some "TRP" ProjectOutputType = ProjectOutputType.Library ProjectSdkInfo = sdkInfo Items = [] @@ -2118,6 +2133,41 @@ let csharpLibTest toolsPath (workspaceFactory: ToolsPath -> IWorkspaceLoader) = | _ -> failwith "Should have found a C# reference" ) +let referenceAssemblySupportTest toolsPath prefix (workspaceFactory: ToolsPath -> IWorkspaceLoader) = + testCase + |> withLog + $"{prefix} can reference projects that support reference assemblies" + (fun logger fs -> + let parentProj: TestAssetProjInfo = ``NetSDK library with ProduceReferenceAssembly`` + let childProj = ``NetSDK library referencing ProduceReferenceAssembly library`` + + let projPath = pathForProject childProj + + // need to build the projects first so that there's something to latch on to + dotnet fs [ + "build" + projPath + ] + |> checkExitCodeZero + + let loader = workspaceFactory toolsPath + + let parsed = + loader.LoadProjects [ projPath ] + |> Seq.toList + + Expect.hasLength parsed 2 "Should have loaded the F# lib and the referenced F# lib" + let fsharpProject = parsed |> Seq.find (fun p -> Path.GetFileName(p.ProjectFileName) = Path.GetFileName(childProj.ProjectFile)) + let mapped = FCS.mapToFSharpProjectOptions fsharpProject parsed + let referencedProjects = mapped.ReferencedProjects + Expect.hasLength referencedProjects 1 "Should have a reference to the F# ProjectReference lib" + + match referencedProjects[0] with + | FSharpReferencedProject.FSharpReference(targetPath, _) -> + Expect.stringContains targetPath (refAssemblyForProject parentProj) "Should have found the ref assembly for the F# lib" + | _ -> failwith "Should have found a F# reference" + ) + let testProjectLoadBadData = testCase |> withLog @@ -2246,4 +2296,8 @@ let tests toolsPath = testProjectLoadBadData expensiveTests toolsPath WorkspaceLoader.Create csharpLibTest toolsPath WorkspaceLoader.Create + + referenceAssemblySupportTest toolsPath (nameof(WorkspaceLoader)) WorkspaceLoader.Create + referenceAssemblySupportTest toolsPath (nameof(WorkspaceLoaderViaProjectGraph)) WorkspaceLoaderViaProjectGraph.Create + ] diff --git a/test/examples/sample-netsdk-prodref/README.md b/test/examples/sample-netsdk-prodref/README.md new file mode 100644 index 00000000..d84e3731 --- /dev/null +++ b/test/examples/sample-netsdk-prodref/README.md @@ -0,0 +1 @@ +a library (l1) with ProduceReferenceAssembly set to true, and another library (l2) that references l1 diff --git a/test/examples/sample-netsdk-prodref/l1/Library.fs b/test/examples/sample-netsdk-prodref/l1/Library.fs new file mode 100644 index 00000000..7f8edfa6 --- /dev/null +++ b/test/examples/sample-netsdk-prodref/l1/Library.fs @@ -0,0 +1,5 @@ +namespace n1 + +module Say = + let hello name = + printfn "Hello %s" name diff --git a/test/examples/sample-netsdk-prodref/l1/l1.fsproj b/test/examples/sample-netsdk-prodref/l1/l1.fsproj new file mode 100644 index 00000000..cfb01100 --- /dev/null +++ b/test/examples/sample-netsdk-prodref/l1/l1.fsproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + true + + + + + + + diff --git a/test/examples/sample-netsdk-prodref/l2/Library.fs b/test/examples/sample-netsdk-prodref/l2/Library.fs new file mode 100644 index 00000000..7f8edfa6 --- /dev/null +++ b/test/examples/sample-netsdk-prodref/l2/Library.fs @@ -0,0 +1,5 @@ +namespace n1 + +module Say = + let hello name = + printfn "Hello %s" name diff --git a/test/examples/sample-netsdk-prodref/l2/l2.fsproj b/test/examples/sample-netsdk-prodref/l2/l2.fsproj new file mode 100644 index 00000000..b96931a5 --- /dev/null +++ b/test/examples/sample-netsdk-prodref/l2/l2.fsproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + +