Skip to content

Commit 2a44d6b

Browse files
authored
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
1 parent 4dcb3a6 commit 2a44d6b

File tree

13 files changed

+169
-32
lines changed

13 files changed

+169
-32
lines changed

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"paket": {
6-
"version": "7.2.1",
6+
"version": "8.0.3",
77
"commands": [
88
"paket"
99
]

.vscode/settings.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,12 @@
88
"test/examples",
99
"packages"
1010
],
11-
"editor.formatOnSave": true
12-
}
11+
"editor.formatOnSave": true,
12+
"cSpell.words": [
13+
"binlog",
14+
"inheritdoc",
15+
"tfms",
16+
"vswhere",
17+
"xbuild"
18+
]
19+
}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.63.0] - 2024-02-06
9+
10+
### Changed
11+
12+
* [Add support for reference assemblies to project cracking and FCS ProjectOptions mapping](https://github.com/ionide/proj-info/pull/200)
13+
814
## [0.62.0] - 2023-08-21
915

1016
### Changed

src/Ionide.ProjInfo.FCS/Library.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ module FCS =
4949
| Some p ->
5050
(p.ProjectFileName.EndsWith(".csproj")
5151
|| p.ProjectFileName.EndsWith(".vbproj"))
52-
&& File.Exists p.TargetPath
52+
&& File.Exists p.ResolvedTargetPath
5353
| None -> false
5454

5555
if p.ProjectFileName.EndsWith ".fsproj" then
5656
knownProject
57-
|> Option.map (fun p ->
57+
|> Option.map (fun (p: ProjectOptions) ->
5858
let theseOptions = makeFSharpProjectReference p
59-
FSharpReferencedProject.FSharpReference(p.TargetPath, theseOptions)
59+
FSharpReferencedProject.FSharpReference(p.ResolvedTargetPath, theseOptions)
6060
)
6161
elif isDotnetProject knownProject then
6262
knownProject

src/Ionide.ProjInfo/Library.fs

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ module LegacyFrameworkDiscovery =
145145
|> Some
146146
else
147147
// taken from https://github.com/microsoft/vswhere
148-
// vswhere.exe is guranteed to be at the following location. refer to https://github.com/Microsoft/vswhere/issues/162
148+
// vswhere.exe is guaranteed to be at the following location. refer to https://github.com/Microsoft/vswhere/issues/162
149149
let vsWhereDir =
150150
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "Installer")
151151
|> DirectoryInfo
@@ -404,9 +404,9 @@ module ProjectLoader =
404404
)
405405

406406
if String.IsNullOrWhiteSpace tfm then
407-
let tfms = pi.GetPropertyValue "TargetFrameworks"
407+
let targetFrameworks = pi.GetPropertyValue "TargetFrameworks"
408408

409-
match tfms with
409+
match targetFrameworks with
410410
| null -> None
411411
| tfms ->
412412
match tfms.Split(';') with
@@ -558,7 +558,7 @@ module ProjectLoader =
558558
|> Seq.filter (fun p -> p.ItemType = "CscCommandLineArgs")
559559
|> Seq.map (fun p -> p.EvaluatedInclude)
560560

561-
let getP2Prefs (LoadedProject project) =
561+
let getP2PRefs (LoadedProject project) =
562562
project.Items
563563
|> Seq.filter (fun p -> p.ItemType = "_MSBuildProjectReferenceExistent")
564564
|> Seq.map (fun p ->
@@ -755,7 +755,7 @@ module ProjectLoader =
755755
path
756756
)
757757

758-
let project = {
758+
let project: ProjectOptions = {
759759
ProjectId = Some path
760760
ProjectFileName = path
761761
TargetFramework = sdkInfo.TargetFramework
@@ -766,9 +766,11 @@ module ProjectLoader =
766766
LoadTime = DateTime.Now
767767
TargetPath =
768768
props
769-
|> Seq.tryFind (fun n -> n.Name = "TargetPath")
770-
|> Option.map (fun n -> n.Value)
769+
|> Seq.tryPick (fun n -> if n.Name = "TargetPath" then Some n.Value else None)
771770
|> Option.defaultValue ""
771+
TargetRefPath =
772+
props
773+
|> Seq.tryPick (fun n -> if n.Name = "TargetRefPath" then Some n.Value else None)
772774
ProjectOutputType = outputType
773775
ProjectSdkInfo = sdkInfo
774776
Items = compileItems
@@ -804,13 +806,14 @@ module ProjectLoader =
804806
"BaseIntermediateOutputPath"
805807
"IntermediateOutputPath"
806808
"TargetPath"
809+
"TargetRefPath"
807810
"IsCrossTargetingBuild"
808811
"TargetFrameworks"
809812
]
810813

811-
let p2pRefs = getP2Prefs project
814+
let p2pRefs = getP2PRefs project
812815

813-
let comandlineArgs =
816+
let commandLineArgs =
814817
if path.EndsWith ".fsproj" then
815818
getFscArgs project
816819
else
@@ -826,7 +829,7 @@ module ProjectLoader =
826829
Result.Error "not restored"
827830
else
828831

829-
let proj = mapToProject path comandlineArgs p2pRefs compileItems nuGetRefs sdkInfo props customProps
832+
let proj = mapToProject path commandLineArgs p2pRefs compileItems nuGetRefs sdkInfo props customProps
830833

831834
Result.Ok proj
832835

@@ -918,7 +921,7 @@ type WorkspaceLoaderViaProjectGraph private (toolsPath, ?globalProperties: (stri
918921
let globalProperties = ProjectLoader.getGlobalProps projectPath tfm globalProperties
919922
ProjectInstance(projectPath, globalProperties, toolsVersion = null, projectCollection = projectCollection)
920923

921-
let projectGraphProjs (paths: string seq) =
924+
let projectGraphProjects (paths: string seq) =
922925

923926
handleProjectGraphFailures
924927
<| fun () ->
@@ -1044,28 +1047,28 @@ type WorkspaceLoaderViaProjectGraph private (toolsPath, ?globalProperties: (stri
10441047
then
10451048
handleError msbuildMessage result.Exception
10461049
else
1047-
let buildProjs =
1050+
let builtProjects =
10481051
result.ResultsByNode.Keys
10491052
|> Seq.collect (fun (pgn: ProjectGraphNode) -> seq { yield pgn.ProjectInstance })
1050-
|> Seq.toList
1053+
|> Seq.toArray
10511054

1052-
let projectsBuilt = Seq.length buildProjs
1055+
let projectsBuiltCount = builtProjects.Length
10531056

10541057
match result.OverallResult with
10551058
| BuildResultCode.Success ->
10561059
logger.info (
1057-
Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuilt:count}"
1060+
Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuiltCount:count}"
10581061
>> Log.addExn result.Exception
10591062
)
10601063
| BuildResultCode.Failure
10611064
| _ ->
10621065
logger.error (
1063-
Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuilt:count} : {msbuildMessage:msbuildMessage} "
1066+
Log.setMessageI $"Overall Build: {result.OverallResult:overallCode}, projects built {projectsBuiltCount:count} : {msbuildMessage:msbuildMessage} "
10641067
>> Log.addExn result.Exception
10651068
)
10661069

10671070
let projects =
1068-
buildProjs
1071+
builtProjects
10691072
|> Seq.map (fun p -> p.FullPath, ProjectLoader.getLoadedProjectInfo p.FullPath customProperties (ProjectLoader.LoadedProject p))
10701073

10711074
|> Seq.choose (fun (projectPath, projectOptionResult) ->
@@ -1111,7 +1114,7 @@ type WorkspaceLoaderViaProjectGraph private (toolsPath, ?globalProperties: (stri
11111114

11121115
interface IWorkspaceLoader with
11131116
override this.LoadProjects(projects: string list, customProperties, binaryLogs) =
1114-
projectGraphProjs projects
1117+
projectGraphProjects projects
11151118
|> Option.map (fun pg -> loadProjects (pg, customProperties, binaryLogs))
11161119
|> Option.defaultValue Seq.empty
11171120

@@ -1273,8 +1276,8 @@ type WorkspaceLoader private (toolsPath: ToolsPath, ?globalProperties: (string *
12731276
member this.LoadSln(sln, customProperties: string list, binaryLogs) =
12741277
match InspectSln.tryParseSln sln with
12751278
| Ok(_, slnData) ->
1276-
let projs = InspectSln.loadingBuildOrder slnData
1277-
this.LoadProjects(projs, customProperties, binaryLogs)
1279+
let solutionProjects = InspectSln.loadingBuildOrder slnData
1280+
this.LoadProjects(solutionProjects, customProperties, binaryLogs)
12781281
| Error d -> failwithf "Cannot load the sln: %A" d
12791282

12801283
member this.LoadSln(sln, customProperties) =
@@ -1340,20 +1343,20 @@ module ProjectViewer =
13401343
|> (fun path -> path.EndsWith(assemblyAttributesName))
13411344
| None -> false
13421345

1343-
//the generated assemblyinfo.fs are not shown as sources
1344-
let isGeneratedAssemblyinfo (name: string) =
1346+
//The generated AssemblyInfo.fs are not shown as sources
1347+
let isGeneratedAssemblyInfo (name: string) =
13451348
//TODO check is in `obj` dir for the tfm
13461349
//TODO better, get the name from fsproj
13471350
name.EndsWith($"{projName}.AssemblyInfo.{sourceFilesExtension}")
13481351

13491352
let includeSourceFile (name: string) =
13501353
not (isAssemblyAttributes name)
1351-
&& not (isGeneratedAssemblyinfo name)
1354+
&& not (isGeneratedAssemblyInfo name)
13521355

13531356
sources
13541357
|> List.choose (
13551358
function
1356-
| ProjectItem.Compile(name, fullpath) -> Some(name, fullpath)
1359+
| ProjectItem.Compile(name, fullPath) -> Some(name, fullPath)
13571360
)
13581361
|> List.filter (fun (_, p) -> includeSourceFile p)
13591362

@@ -1363,5 +1366,5 @@ module ProjectViewer =
13631366
|> Path.GetFileNameWithoutExtension
13641367
Items =
13651368
compileFiles
1366-
|> List.map (fun (name, fullpath) -> ProjectViewerItem.Compile(fullpath, { ProjectViewerItemConfig.Link = name }))
1369+
|> List.map (fun (name, fullPath) -> ProjectViewerItem.Compile(fullPath, { ProjectViewerItemConfig.Link = name }))
13671370
}

src/Ionide.ProjInfo/Types.fs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,21 @@ module Types =
5959
ReferencedProjects: ProjectReference list
6060
PackageReferences: PackageReference list
6161
LoadTime: DateTime
62+
/// The path to the primary executable or loadable output of this project
6263
TargetPath: string
64+
/// If present, this project produced a reference assembly and this should be used as primary reference for downstream proejcts
65+
TargetRefPath: string option
6366
ProjectOutputType: ProjectOutputType
6467
ProjectSdkInfo: ProjectSdkInfo
6568
Items: ProjectItem list
6669
Properties: Property list
6770
CustomProperties: Property list
68-
}
71+
} with
72+
/// ResolvedTargetPath is the path to the primary reference assembly for this project.
73+
/// For projects that produce ReferenceAssemblies, this is the path to the reference assembly.
74+
/// For other projects, this is the same as TargetPath.
75+
member x.ResolvedTargetPath =
76+
defaultArg x.TargetRefPath x.TargetPath
6977

7078
type CompileItem = {
7179
Name: string

test/Ionide.ProjInfo.Tests/TestAssets.fs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,27 @@ let ``sample9 NetSdk library`` = {
280280
TargetFrameworks = Map.ofList [ "netstandard2.0", sourceFiles [ "Library.fs" ] ]
281281
ProjectReferences = []
282282
}
283+
284+
/// dotnet sdk library with ProduceReferenceAssembly=true
285+
let ``NetSDK library with ProduceReferenceAssembly`` = {
286+
ProjDir = "sample-netsdk-prodref"
287+
AssemblyName = "l1"
288+
ProjectFile =
289+
"l1"
290+
/ "l1.fsproj"
291+
TargetFrameworks = Map.ofList [ "netstandard2.0", sourceFiles [ "Library.fs" ] ]
292+
ProjectReferences = []
293+
}
294+
295+
296+
let ``NetSDK library referencing ProduceReferenceAssembly library`` = {
297+
ProjDir = "sample-netsdk-prodref"
298+
AssemblyName = "l2"
299+
ProjectFile =
300+
"l2"
301+
/ "l2.fsproj"
302+
TargetFrameworks = Map.ofList [ "netstandard2.0", sourceFiles [ "Library.fs" ] ]
303+
ProjectReferences = [
304+
``NetSDK library with ProduceReferenceAssembly``
305+
]
306+
}

test/Ionide.ProjInfo.Tests/Tests.fs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ let ExamplesDir =
3030
/ "test"
3131
/ "examples"
3232

33+
let pathForTestAssets (test: TestAssetProjInfo) =
34+
ExamplesDir
35+
/ test.ProjDir
36+
37+
let pathForProject (test: TestAssetProjInfo) =
38+
pathForTestAssets test
39+
/ test.ProjectFile
40+
41+
let implAssemblyForProject (test: TestAssetProjInfo) =
42+
$"{test.AssemblyName}.dll"
43+
44+
let refAssemblyForProject (test: TestAssetProjInfo) =
45+
Path.Combine("ref", implAssemblyForProject test)
46+
3347
let TestRunDir =
3448
RepoDir
3549
/ "test"
@@ -1482,6 +1496,7 @@ let testFCSmapManyProjCheckCaching =
14821496
PackageReferences = []
14831497
LoadTime = DateTime.MinValue
14841498
TargetPath = "TP"
1499+
TargetRefPath = Some "TRP"
14851500
ProjectOutputType = ProjectOutputType.Library
14861501
ProjectSdkInfo = sdkInfo
14871502
Items = []
@@ -2118,6 +2133,41 @@ let csharpLibTest toolsPath (workspaceFactory: ToolsPath -> IWorkspaceLoader) =
21182133
| _ -> failwith "Should have found a C# reference"
21192134
)
21202135

2136+
let referenceAssemblySupportTest toolsPath prefix (workspaceFactory: ToolsPath -> IWorkspaceLoader) =
2137+
testCase
2138+
|> withLog
2139+
$"{prefix} can reference projects that support reference assemblies"
2140+
(fun logger fs ->
2141+
let parentProj: TestAssetProjInfo = ``NetSDK library with ProduceReferenceAssembly``
2142+
let childProj = ``NetSDK library referencing ProduceReferenceAssembly library``
2143+
2144+
let projPath = pathForProject childProj
2145+
2146+
// need to build the projects first so that there's something to latch on to
2147+
dotnet fs [
2148+
"build"
2149+
projPath
2150+
]
2151+
|> checkExitCodeZero
2152+
2153+
let loader = workspaceFactory toolsPath
2154+
2155+
let parsed =
2156+
loader.LoadProjects [ projPath ]
2157+
|> Seq.toList
2158+
2159+
Expect.hasLength parsed 2 "Should have loaded the F# lib and the referenced F# lib"
2160+
let fsharpProject = parsed |> Seq.find (fun p -> Path.GetFileName(p.ProjectFileName) = Path.GetFileName(childProj.ProjectFile))
2161+
let mapped = FCS.mapToFSharpProjectOptions fsharpProject parsed
2162+
let referencedProjects = mapped.ReferencedProjects
2163+
Expect.hasLength referencedProjects 1 "Should have a reference to the F# ProjectReference lib"
2164+
2165+
match referencedProjects[0] with
2166+
| FSharpReferencedProject.FSharpReference(targetPath, _) ->
2167+
Expect.stringContains targetPath (refAssemblyForProject parentProj) "Should have found the ref assembly for the F# lib"
2168+
| _ -> failwith "Should have found a F# reference"
2169+
)
2170+
21212171
let testProjectLoadBadData =
21222172
testCase
21232173
|> withLog
@@ -2246,4 +2296,8 @@ let tests toolsPath =
22462296
testProjectLoadBadData
22472297
expensiveTests toolsPath WorkspaceLoader.Create
22482298
csharpLibTest toolsPath WorkspaceLoader.Create
2299+
2300+
referenceAssemblySupportTest toolsPath (nameof(WorkspaceLoader)) WorkspaceLoader.Create
2301+
referenceAssemblySupportTest toolsPath (nameof(WorkspaceLoaderViaProjectGraph)) WorkspaceLoaderViaProjectGraph.Create
2302+
22492303
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
a library (l1) with ProduceReferenceAssembly set to true, and another library (l2) that references l1
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace n1
2+
3+
module Say =
4+
let hello name =
5+
printfn "Hello %s" name

0 commit comments

Comments
 (0)