-
-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathInspectSln.fs
157 lines (131 loc) · 5.65 KB
/
InspectSln.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
namespace Ionide.ProjInfo
module InspectSln =
open System
open System.IO
open System.Threading
open Microsoft.VisualStudio.SolutionPersistence
open Microsoft.VisualStudio.SolutionPersistence.Serializer
open System.Text.Json
let private normalizeDirSeparators (path: string) =
match Path.DirectorySeparatorChar with
| '\\' -> path.Replace('/', '\\')
| '/' -> path.Replace('\\', '/')
| _ -> path
type SolutionData = {
Items: SolutionItem list
Configurations: SolutionConfiguration list
}
and SolutionConfiguration = {
Id: string
ConfigurationName: string
PlatformName: string
IncludeInBuild: bool
}
and SolutionItem = {
Guid: Guid
Name: string
Kind: SolutionItemKind
}
and SolutionItemKind =
| MSBuildFormat of SolutionItemMSBuildConfiguration list
| Folder of (SolutionItem list) * (string list)
| Unsupported
| Unknown
and SolutionItemMSBuildConfiguration = {
Id: string
ConfigurationName: string
PlatformName: string
}
let private tryLoadSolutionModel (slnFilePath: string) =
// use the VS library to parse the solution
match SolutionSerializers.GetSerializerByMoniker(slnFilePath) with
| null -> Error (exn $"Unsupported solution file format %s{Path.GetExtension(slnFilePath)}")
| serializer ->
try
let model = serializer.OpenAsync(slnFilePath, CancellationToken.None).GetAwaiter().GetResult()
Ok(model)
with
| ex -> Error ex
/// Parses a file on disk and returns data about its contents. Supports sln, slnf, and slnx files.
let tryParseSln (slnFilePath: string) =
let parseSln (sln: Model.SolutionModel) (projectsToRead: string Set option) =
sln.DistillProjectConfigurations()
let slnDir = Path.GetDirectoryName slnFilePath
let makeAbsoluteFromSlnDir =
let makeAbs (path: string) =
if Path.IsPathRooted path then
path
else
Path.Combine(slnDir, path)
|> Path.GetFullPath
normalizeDirSeparators
>> makeAbs
let parseItem (item: Model.SolutionItemModel): SolutionItem =
{
Guid = item.Id
Name = ""
Kind = SolutionItemKind.Unknown
}
let parseProject (project: Model.SolutionProjectModel): SolutionItem =
{ Guid = project.Id
Name= makeAbsoluteFromSlnDir project.FilePath
Kind = SolutionItemKind.MSBuildFormat [] // TODO: could theoretically parse configurations here
}
let parseFolder (folder: Model.SolutionFolderModel): SolutionItem =
{
Guid = folder.Id
Name = makeAbsoluteFromSlnDir folder.Path
Kind =
SolutionItemKind.Folder (
sln.SolutionItems |> Seq.filter (fun item -> not (isNull item.Parent) && item.Parent.Id = folder.Id) |> Seq.map (fun p -> parseItem p, string p.Id) |> List.ofSeq |> List.unzip)
}
// three kinds of items - projects, folders, items
// yield them all here
let projectsWeCareAbout =
match projectsToRead with
| None -> sln.SolutionProjects :> seq<_>
| Some filteredProjects -> sln.SolutionProjects |> Seq.filter (fun slnProject -> filteredProjects.Contains(makeAbsoluteFromSlnDir slnProject.FilePath))
let allItems =
[
yield! projectsWeCareAbout |> Seq.map parseProject
yield! sln.SolutionFolders |> Seq.map parseFolder
yield! sln.SolutionItems |> Seq.filter (fun item -> isNull item.Parent) |> Seq.map parseItem
]
let data = {
Items = allItems
Configurations = []
}
data
let parseSlnf (slnfPath: string) =
let (slnFilePath: string, projectsToRead: string Set) =
let options = new JsonDocumentOptions(AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip)
let text = JsonDocument.Parse(File.ReadAllText(slnfPath), options)
let solutionElement = text.RootElement.GetProperty("solution")
let slnPath = solutionElement.GetProperty("path").GetString();
let projects =
solutionElement.GetProperty("projects").EnumerateArray()
|> Seq.map (fun p -> p.GetString())
|> Set.ofSeq
slnPath, projects
match tryLoadSolutionModel slnFilePath with
| Ok sln ->
Ok (parseSln sln (Some projectsToRead))
| Error ex ->
Error ex
if slnFilePath.EndsWith(".slnf") then
parseSlnf slnFilePath
else
match tryLoadSolutionModel slnFilePath with
| Ok sln -> Ok(parseSln sln None)
| Error ex -> Error ex
let loadingBuildOrder (data: SolutionData) =
let rec projs (item: SolutionItem) =
match item.Kind with
| MSBuildFormat items -> [ item.Name ]
| Folder(items, _) ->
items
|> List.collect projs
| Unsupported
| Unknown -> []
data.Items
|> List.collect projs