Skip to content

Commit

Permalink
Add support for reference assemblies to ProjInfo (#200)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
baronfel authored Feb 6, 2024
1 parent 4dcb3a6 commit 2a44d6b
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"paket": {
"version": "7.2.1",
"version": "8.0.3",
"commands": [
"paket"
]
Expand Down
11 changes: 9 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,12 @@
"test/examples",
"packages"
],
"editor.formatOnSave": true
}
"editor.formatOnSave": true,
"cSpell.words": [
"binlog",
"inheritdoc",
"tfms",
"vswhere",
"xbuild"
]
}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/Ionide.ProjInfo.FCS/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 28 additions & 25 deletions src/Ionide.ProjInfo/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ->
Expand Down Expand Up @@ -755,7 +755,7 @@ module ProjectLoader =
path
)

let project = {
let project: ProjectOptions = {
ProjectId = Some path
ProjectFileName = path
TargetFramework = sdkInfo.TargetFramework
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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 () ->
Expand Down Expand Up @@ -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) ->
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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) =
Expand Down Expand Up @@ -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)

Expand All @@ -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 }))
}
10 changes: 9 additions & 1 deletion src/Ionide.ProjInfo/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions test/Ionide.ProjInfo.Tests/TestAssets.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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``
]
}
54 changes: 54 additions & 0 deletions test/Ionide.ProjInfo.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -1482,6 +1496,7 @@ let testFCSmapManyProjCheckCaching =
PackageReferences = []
LoadTime = DateTime.MinValue
TargetPath = "TP"
TargetRefPath = Some "TRP"
ProjectOutputType = ProjectOutputType.Library
ProjectSdkInfo = sdkInfo
Items = []
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

]
1 change: 1 addition & 0 deletions test/examples/sample-netsdk-prodref/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a library (l1) with ProduceReferenceAssembly set to true, and another library (l2) that references l1
5 changes: 5 additions & 0 deletions test/examples/sample-netsdk-prodref/l1/Library.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace n1

module Say =
let hello name =
printfn "Hello %s" name
12 changes: 12 additions & 0 deletions test/examples/sample-netsdk-prodref/l1/l1.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
</PropertyGroup>

<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>

</Project>
5 changes: 5 additions & 0 deletions test/examples/sample-netsdk-prodref/l2/Library.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace n1

module Say =
let hello name =
printfn "Hello %s" name
Loading

0 comments on commit 2a44d6b

Please sign in to comment.