-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathArgsFile.fs
314 lines (284 loc) · 11.1 KB
/
ArgsFile.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
module Scripts.ArgsFile
open System
open System.IO
open System.Runtime.CompilerServices
open System.Text.RegularExpressions
open FSharp.Compiler.CodeAnalysis
open Ionide.ProjInfo
// open Microsoft.Build.Logging.StructuredLogger
open Serilog
[<RequireQualifiedAccess>]
type OtherOption =
// Eg. '--test:DumpGraph'
| TestFlag of string
// Eg. '--deterministic+'
| Bool of string * bool
// Eg. '--warn:3'
| KeyValue of string * string
// Eg. '--times'
| Simple of string
type Define = string
type Reference = string
type Input = string
/// A single typed argument for FSC
[<RequireQualifiedAccess>]
type FscArg =
/// Eg. '--define:DEBUG'
| Define of Define
/// Eg. '-r:C:\...\System.dll'
| Reference of Reference
/// Eg. 'Internals.fs'
| Input of Input
/// Eg. '--optimize+'
| OtherOption of OtherOption
type FscArgs = FscArg[]
type SArgs =
{
OtherOptions : OtherOption list
Defines : Define list
Refs : Reference list
Inputs : Input list
}
with member this.Output =
this.OtherOptions
|> List.choose (function
| OtherOption.KeyValue("-o", output) -> Some output
| _ -> None
)
|> List.exactlyOne
type ArgsFileWithProject =
{
ArgsFile : string
Project : string
}
module FscArgs =
let parseSingle (arg : string) : FscArg option =
// Discard comments in response files
if arg.StartsWith '#' then
None
else
let arg =
match Regex.Match(arg, "^--define\:(.+)$") with
| m when m.Success ->
m.Groups[1].Value
|> FscArg.Define
| _ ->
match Regex.Match(arg, "^--([\d\w_]+)([\+\-])$") with
| m when m.Success ->
let flag = m.Groups[1].Value.ToLower()
let value = match m.Groups[2].Value with "+" -> true | "-" -> false | _ -> failwith $"Unexpected group value for bool arg: {m}"
(flag, value)
|> OtherOption.Bool
|> FscArg.OtherOption
| _ ->
match Regex.Match(arg, "^-r:(.+)$") with
| m when m.Success ->
let path = m.Groups[1].Value
path
|> FscArg.Reference
| _ ->
match Regex.Match(arg, "^--test:(.+)$") with
| m when m.Success ->
let name = m.Groups[1].Value
name
|> OtherOption.TestFlag
|> FscArg.OtherOption
| _ ->
match Regex.Match(arg, "^(-?-[\d\w_]+)\:(.+)$") with
| m when m.Success ->
let flag = m.Groups[1].Value
let value = m.Groups[2].Value
(flag, value)
|> OtherOption.KeyValue
|> FscArg.OtherOption
| _ ->
match Regex.Match(arg, "^[$\d\w_].+$") with
| m when m.Success ->
FscArg.Input m.Groups[0].Value
| _ ->
match Regex.Match(arg, "^-?-[^:]+$") with
| m when m.Success ->
let flag = m.Groups[0].Value
flag
|> OtherOption.Simple
|> FscArg.OtherOption
| _ ->
failwith $"Unable to parser FSC arg '{arg}'"
Some arg
let stringify (arg : FscArg) =
match arg with
| FscArg.Define name -> $"--define:{name}"
| FscArg.Reference path -> $"-r:{path}"
| FscArg.Input input -> $"{input}"
| FscArg.OtherOption optionString ->
match optionString with
| OtherOption.Bool(name, value) ->
let vString = if value then "+" else "-"
$"--{name}{vString}"
| OtherOption.TestFlag name -> $"--test:{name}"
| OtherOption.KeyValue(key, value) -> $"{key}:{value}"
| OtherOption.Simple simpleString -> $"{simpleString}"
let split (argsString : string) : string[] =
argsString.Split("\r\n")
|> Array.collect (fun s -> s.Split("\n"))
let parse (args : string[]) : FscArgs =
args
|> Array.choose parseSingle
let stringifyAll (args : FscArgs) : string[] =
args
|> Array.map stringify
module SArgs =
let structurize (args : FscArg seq) : SArgs =
let args = args |> Seq.toList
{
SArgs.OtherOptions = args |> List.choose (function | FscArg.OtherOption otherOption -> Some otherOption | _ -> None)
SArgs.Defines = args |> List.choose (function | FscArg.Define d -> Some d | _ -> None)
SArgs.Refs = args |> List.choose (function | FscArg.Reference ref -> Some ref | _ -> None)
SArgs.Inputs = args |> List.choose (function | FscArg.Input input -> Some input | _ -> None)
}
let destructurize (args : SArgs) : FscArgs =
seq {
yield! (args.OtherOptions |> List.map FscArg.OtherOption)
yield! (args.Defines |> List.map FscArg.Define)
yield! (args.Refs |> List.map FscArg.Reference)
yield! (args.Inputs |> List.map FscArg.Input)
}
|> Seq.toArray
let ofFile (argsFile : string) =
File.ReadAllLines(argsFile)
|> FscArgs.parse
|> structurize
let toFile (argsFile : string) (args : SArgs) =
args
|> destructurize
|> FscArgs.stringifyAll
|> fun args -> File.WriteAllLines(argsFile, args)
let limitInputsCount (n : int) (args : SArgs) : SArgs =
{
args with
Inputs = args.Inputs |> List.take (min args.Inputs.Length n)
}
let limitInputsToSpecificInput (lastInput : string) (args : SArgs) : SArgs =
{
args with
Inputs = args.Inputs |> List.takeWhile (fun l -> l <> lastInput)
}
let setOption (matcher : OtherOption -> bool) (value : OtherOption option) (args : SArgs) : SArgs =
let opts = args.OtherOptions
let opts, found =
opts
|> List.mapFold (fun (found : bool) opt ->
if matcher opt then value, true
else Some opt, found
) false
let opts = opts |> List.choose id
let opts =
match found, value with
| true, _
| false, None -> opts
| false, Some value -> value :: opts
{
args with
OtherOptions = opts
}
let clearOption (matcher : OtherOption -> bool) (args : SArgs) : SArgs =
setOption matcher None args
let setOutput (output : string) (args : SArgs) : SArgs =
setOption
(function OtherOption.KeyValue("-o", _) -> true | _ -> false)
(OtherOption.KeyValue("-o", output) |> Some)
args
let setBool (name : string) (value : bool) (args : SArgs) : SArgs =
setOption
(function OtherOption.Bool(s, _) when s = name -> true | _ -> false)
(OtherOption.Bool(name, value) |> Some)
args
let setTestFlag (name : string) (enable : bool) (args : SArgs) : SArgs =
setOption
(function OtherOption.TestFlag s when s = name -> true | _ -> false)
(if enable then OtherOption.TestFlag(name) |> Some else None)
args
let clearTestFlag (name : string) (args : SArgs) : SArgs =
setTestFlag name false args
let setKeyValue (name : string) (value : string) (args : SArgs) : SArgs =
setOption
(function OtherOption.KeyValue(k, v) when k.Equals(name, StringComparison.OrdinalIgnoreCase) -> true | _ -> false)
(OtherOption.KeyValue(name, value) |> Some)
args
// /// Create a text file with the F# compiler arguments scrapped from a binary log file.
// /// Run `dotnet build --no-incremental --no-dependencies --no-restore -bl` to create the binlog file.
// /// The `--no-incremental` flag is essential for this scraping code.
// let mkCompilerArgsFromBinLog (projectName : string option) file =
// let build = BinaryLog.ReadBuild file
//
// let projectName =
// projectName
// |> Option.defaultWith (fun () ->
// build.Children
// |> Seq.choose (
// function
// | :? Project as p -> Some p.Name
// | _ -> None
// )
// |> Seq.distinct
// |> Seq.exactlyOne
// )
//
// let message (fscTask: FscTask) =
// fscTask.Children
// |> Seq.tryPick (
// function
// | :? Message as m when m.Text.Contains "fsc" -> Some m.Text
// | _ -> None
// )
//
// let mutable args = None
//
// build.VisitAllChildren<Task>(fun task ->
// match task with
// | :? FscTask as fscTask ->
// match fscTask.Parent.Parent with
// | :? Project as p when p.Name = projectName -> args <- message fscTask
// | _ -> ()
// | _ -> ()
// )
//
// match args with
// | None -> failwith "Could not find fsc commandline args in the MSBuild binlog file. Did you build using '--no-incremental'?"
// | Some args ->
// match args.IndexOf "-o:" with
// | -1 -> failwith "Args text does not look like F# compiler args"
// | idx -> args.Substring(idx)
[<MethodImpl(MethodImplOptions.NoInlining)>]
let generateProjectOptions (projectPath : string) (props : (string * string) list) =
Log.Information("Start {operation}", "generateProjectOptions")
let toolsPath = Init.init (DirectoryInfo(Path.GetDirectoryName(projectPath))) None
let loader = WorkspaceLoader.Create (toolsPath, props)
let projects =
loader.LoadProjects ([projectPath], [], BinaryLogGeneration.Off) |> Seq.toList
match projects with
| [] ->
failwith $"No projects were loaded from {projectPath} - this indicates an error in cracking the projects."
| projects ->
let projectsString =
projects
|> List.map (fun p -> p.ProjectFileName)
|> fun ps -> String.Join(Environment.NewLine, ps)
Log.Information("Loaded {projectCount} projects and their options from {projectPath}: " + Environment.NewLine + projectsString, projects.Length, projectPath)
let fsOptions = FCS.mapManyOptions projects |> Seq.toArray
let x = fsOptions[0].ProjectFileName
fsOptions
let convertOptionsToArgs (options : FSharpProjectOptions) : SArgs =
seq {
yield! options.OtherOptions
yield! options.SourceFiles
}
|> Seq.toArray
|> FscArgs.parse
|> SArgs.structurize
// TODO Allow using configurations other than plain 'dotnet build'.
let generateCompilationArgs projectPath props =
let projects = generateProjectOptions projectPath props
let project = projects |> Array.find (fun p -> p.ProjectFileName = projectPath)
project
|> convertOptionsToArgs