Skip to content

Commit

Permalink
Performance tweaks for larger solutions
Browse files Browse the repository at this point in the history
  • Loading branch information
TheAngryByrd committed May 14, 2024
1 parent fbe5cbc commit bb33fe4
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 47 deletions.
13 changes: 12 additions & 1 deletion src/FSharp.Analyzers.Cli/CustomLogging.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,18 @@ type CustomFormatter(options: IOptionsMonitor<CustomOptions>) as this =
this.WritePrefix(textWriter, logEntry.LogLevel)
textWriter.WriteLine(message)

member private _.WritePrefix(textWriter: TextWriter, logLevel: LogLevel) =
member private x.WritePrefix(textWriter: TextWriter, logLevel: LogLevel) =
if not (isNull formatterOptions.TimestampFormat) then
let dateTime =
if formatterOptions.UseUtcTimestamp then
DateTime.UtcNow
else
DateTime.Now

let timestamp = dateTime.ToString(formatterOptions.TimestampFormat)

textWriter.Write($"{timestamp} ")

match logLevel with
| LogLevel.Trace -> textWriter.Write("trace: ")
| LogLevel.Debug -> textWriter.Write("debug: ")
Expand Down
100 changes: 54 additions & 46 deletions src/FSharp.Analyzers.Cli/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ type Arguments =
interface IArgParserTemplate with
member s.Usage =
match s with
| Project _ -> "Path to your .fsproj file."
| Analyzers_Path _ -> "Path to a folder where your analyzers are located."
| Project _ -> "List of paths to your .fsproj file."
| Analyzers_Path _ ->
"List of path to a folder where your analyzers are located. This will search recursively."
| Property _ -> "A key=value pair of an MSBuild property."
| Configuration _ -> "The configuration to use, e.g. Debug or Release."
| Runtime _ -> "The runtime identifier (RID)."
Expand Down Expand Up @@ -118,28 +119,39 @@ let rec mkKn (ty: Type) =

let mutable logger: ILogger = Abstractions.NullLogger.Instance

let loadProject toolsPath properties projPath =
let loadProjects toolsPath properties (projPaths: string list) =
async {
let paths =
projPaths
|> List.map (fun proj -> Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath)

for proj in paths do
logger.LogInformation("Loading project {0}", proj)

let loader = WorkspaceLoader.Create(toolsPath, properties)
let parsed = loader.LoadProjects [ projPath ] |> Seq.toList
let projectOptions = loader.LoadProjects projPaths

if parsed.IsEmpty then
logger.LogError("Failed to load project '{0}'", projPath)
exit 1
let failedLoads =
paths
|> Seq.filter (fun path -> not (projectOptions |> Seq.exists (fun p -> p.ProjectFileName = path)))
|> Seq.toList

let fcsPo = FCS.mapToFSharpProjectOptions parsed.Head parsed
if Seq.length failedLoads > 0 then
logger.LogError("Failed to load project '{0}'", failedLoads)
exit 1

return fcsPo
return FCS.mapManyOptions projectOptions |> Seq.toList
}

let runProjectAux
let runProject
(client: Client<CliAnalyzerAttribute, CliContext>)
(fsharpOptions: FSharpProjectOptions)
(excludeIncludeFiles: Choice<Glob list, Glob list>)
(mappings: SeverityMappings)
: Async<Result<AnalyzerMessage list, AnalysisFailure> list>
=
async {
logger.LogInformation("Checking project {0}", fsharpOptions.ProjectFileName)
let! checkProjectResults = fcs.ParseAndCheckProject(fsharpOptions)

let! messagesPerAnalyzer =
Expand All @@ -160,21 +172,23 @@ let runProjectAux
| None -> false
)
|> Array.map (fun fileName ->
let fileContent = File.ReadAllText fileName
let sourceText = SourceText.ofString fileContent
async {
let! fileContent = File.ReadAllTextAsync fileName |> Async.AwaitTask
let sourceText = SourceText.ofString fileContent
logger.LogDebug("Checking file {0}", fileName)

// Since we did ParseAndCheckProject, we can be sure that the file is in the project.
// See https://fsharp.github.io/fsharp-compiler-docs/fcs/project.html for more information.
let! parseAndCheckResults = fcs.GetBackgroundCheckResultsForFileInProject(fileName, fsharpOptions)

let ctx =
Utils.createContext checkProjectResults fileName sourceText parseAndCheckResults

logger.LogInformation("Running analyzers for {0}", ctx.FileName)
let! results = client.RunAnalyzers ctx
return Ok results
}

Utils.typeCheckFile fcs logger fsharpOptions fileName (Utils.SourceOfSource.SourceText sourceText)
|> Result.map (Utils.createContext checkProjectResults fileName sourceText)
)
|> Array.map (fun ctx ->
match ctx with
| Error e -> async.Return(Error e)
| Ok ctx ->
async {
logger.LogInformation("Running analyzers for {0}", ctx.FileName)
let! results = client.RunAnalyzers ctx
return Ok results
}
)
|> Async.Parallel

Expand All @@ -188,20 +202,6 @@ let runProjectAux
|> Seq.toList
}

let runProject
(client: Client<CliAnalyzerAttribute, CliContext>)
toolsPath
properties
proj
(excludeIncludeFiles: Choice<Glob list, Glob list>)
(mappings: SeverityMappings)
=
async {
let path = Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath
let! option = loadProject toolsPath properties path
return! runProjectAux client option excludeIncludeFiles mappings
}

let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |]

let isFSharpFile (file: string) =
Expand Down Expand Up @@ -249,7 +249,7 @@ let runFscArgs
Stamp = None
}

runProjectAux client projectOptions excludeIncludeFiles mappings
runProject client projectOptions excludeIncludeFiles mappings

let printMessages (msgs: AnalyzerMessage list) =

Expand Down Expand Up @@ -482,7 +482,11 @@ let main argv =
use factory =
LoggerFactory.Create(fun builder ->
builder
.AddCustomFormatter(fun options -> options.UseAnalyzersMsgStyle <- false)
.AddCustomFormatter(fun options ->
options.UseAnalyzersMsgStyle <- false
options.TimestampFormat <- "[HH:mm:ss.fff]"
options.UseUtcTimestamp <- true
)
.SetMinimumLevel(logLevel)
|> ignore
)
Expand Down Expand Up @@ -610,7 +614,6 @@ let main argv =
match projOpts, fscArgs with
| [], None ->
logger.LogError("No project given. Use `--project PATH_TO_FSPROJ`.")

None
| _ :: _, Some _ ->
logger.LogError("`--project` and `--fsc-args` cannot be combined.")
Expand All @@ -625,11 +628,16 @@ let main argv =
logger.LogError("Invalid `--project` argument. File does not exist: '{projPath}'", projPath)
exit 1

projects
|> List.map (fun projPath ->
runProject client toolsPath properties projPath exclInclFiles severityMapping
)
|> Async.Sequential
async {
let! loadedProjects = loadProjects toolsPath properties projects

return!
loadedProjects
|> List.map (fun (projPath: FSharpProjectOptions) ->
runProject client projPath exclInclFiles severityMapping
)
|> Async.Parallel
}
|> Async.RunSynchronously
|> List.concat
|> Some
Expand Down

0 comments on commit bb33fe4

Please sign in to comment.