Skip to content

Commit

Permalink
Added Buildalyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
ncave committed Dec 8, 2023
1 parent 1cda4e7 commit cb453d2
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"name": "FCS Test",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/artifacts/bin/fcs-test/Debug/net6.0/fcs-test.dll",
"program": "${workspaceFolder}/artifacts/bin/fcs-test/Debug/net7.0/fcs-test.dll",
"args": [],
"cwd": "${workspaceFolder}/fcs/fcs-test",
"console": "internalConsole",
Expand Down
1 change: 1 addition & 0 deletions buildtools/AssemblyCheck/AssemblyCheck.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
<UseAppHost Condition="'$(DotNetBuildFromSource)' == 'true'">false</UseAppHost>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions buildtools/buildtools.targets
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
BeforeTargets="CoreCompile">

<PropertyGroup>
<FsLexPath Condition="'$(FsLexPath)' == ''">$(ArtifactsDir)\bin\fslex\Release\net6.0\fslex.dll</FsLexPath>
<FsLexPath Condition="'$(FsLexPath)' == ''">$(ArtifactsDir)\bin\fslex\Release\net7.0\fslex.dll</FsLexPath>
</PropertyGroup>

<!-- Create the output directory -->
Expand All @@ -44,7 +44,7 @@
BeforeTargets="CoreCompile">

<PropertyGroup>
<FsYaccPath Condition="'$(FsYaccPath)' == ''">$(ArtifactsDir)\bin\fsyacc\Release\net6.0\fsyacc.dll</FsYaccPath>
<FsYaccPath Condition="'$(FsYaccPath)' == ''">$(ArtifactsDir)\bin\fsyacc\Release\net7.0\fsyacc.dll</FsYaccPath>
</PropertyGroup>

<!-- Create the output directory -->
Expand Down
1 change: 0 additions & 1 deletion buildtools/fslex/fslex.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<DefineConstants>INTERNALIZED_FSLEXYACC_RUNTIME;$(DefineConstants)</DefineConstants>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
<UseAppHost Condition="'$(DotNetBuildFromSource)' == 'true'">false</UseAppHost>
Expand Down
1 change: 0 additions & 1 deletion buildtools/fsyacc/fsyacc.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<DefineConstants>INTERNALIZED_FSLEXYACC_RUNTIME;$(DefineConstants)</DefineConstants>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
<UseAppHost Condition="'$(DotNetBuildFromSource)' == 'true'">false</UseAppHost>
Expand Down
113 changes: 82 additions & 31 deletions fcs/fcs-test/Program.fs
Original file line number Diff line number Diff line change
@@ -1,41 +1,92 @@
open System.IO
open System.Text.RegularExpressions
open FSharp.Compiler
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.SourceCodeServices
open FSharp.Compiler.EditorServices

let getProjectOptions (folder: string) (projectFile: string) =
let runProcess (workingDir: string) (exePath: string) (args: string) =
let psi = System.Diagnostics.ProcessStartInfo()
psi.FileName <- exePath
psi.WorkingDirectory <- workingDir
psi.RedirectStandardOutput <- false
psi.RedirectStandardError <- false
psi.Arguments <- args
psi.CreateNoWindow <- true
psi.UseShellExecute <- false

use p = new System.Diagnostics.Process()
p.StartInfo <- psi
p.Start() |> ignore
p.WaitForExit()

let exitCode = p.ExitCode
exitCode, ()

let runCmd exePath args = runProcess folder exePath (args |> String.concat " ")
let msbuildExec = Dotnet.ProjInfo.Inspect.dotnetMsbuild runCmd
let result = Dotnet.ProjInfo.Inspect.getProjectInfo ignore msbuildExec Dotnet.ProjInfo.Inspect.getFscArgs projectFile
match result with
| Ok (Dotnet.ProjInfo.Inspect.GetResult.FscArgs x) -> x
| _ -> []
open Buildalyzer

let getProjectOptionsFromProjectFile (isMain: bool) (projFile: string) =

let tryGetResult (isMain: bool) (manager: AnalyzerManager) (maybeCsprojFile: string) =

let analyzer = manager.GetProject(maybeCsprojFile)
let env = analyzer.EnvironmentFactory.GetBuildEnvironment(Environment.EnvironmentOptions(DesignTime=true,Restore=false))
// If System.the project targets multiple frameworks, multiple results will be returned
// For now we just take the first one with non-empty command
let results = analyzer.Build(env)
results
|> Seq.tryFind (fun r -> System.String.IsNullOrEmpty(r.Command) |> not)

let manager =
let log = new StringWriter()
let options = AnalyzerManagerOptions(LogWriter = log)
let m = AnalyzerManager(options)
m

// Because Buildalyzer works better with .csproj, we first "dress up" the project as if it were a C# one
// and try to adapt the results. If it doesn't work, we try again to analyze the .fsproj directly
let csprojResult =
let csprojFile = projFile.Replace(".fsproj", ".csproj")
if File.Exists(csprojFile) then
None
else
try
File.Copy(projFile, csprojFile)
tryGetResult isMain manager csprojFile
|> Option.map (fun (r: IAnalyzerResult) ->
// Careful, options for .csproj start with / but so do root paths in unix
let reg = Regex(@"^\/[^\/]+?(:?:|$)")
let comArgs =
r.CompilerArguments
|> Array.map (fun line ->
if reg.IsMatch(line) then
if line.StartsWith("/reference") then "-r" + line.Substring(10)
else "--" + line.Substring(1)
else line)
let comArgs =
match r.Properties.TryGetValue("OtherFlags") with
| false, _ -> comArgs
| true, otherFlags ->
let otherFlags = otherFlags.Split(' ', System.StringSplitOptions.RemoveEmptyEntries)
Array.append otherFlags comArgs
comArgs, r)
finally
File.Delete(csprojFile)

let compilerArgs, result =
csprojResult
|> Option.orElseWith (fun () ->
tryGetResult isMain manager projFile
|> Option.map (fun r ->
// result.CompilerArguments doesn't seem to work well in Linux
let comArgs = Regex.Split(r.Command, @"\r?\n")
comArgs, r))
|> function
| Some result -> result
// TODO: Get Buildalyzer errors from the log
| None -> failwith $"Cannot parse {projFile}"

let projDir = Path.GetDirectoryName(projFile)
let projOpts =
compilerArgs
|> Array.skipWhile (fun line -> not(line.StartsWith("-")))
|> Array.map (fun f ->
if f.EndsWith(".fs") || f.EndsWith(".fsi") then
if Path.IsPathRooted f then f else Path.Combine(projDir, f)
else f)
projOpts,
Seq.toArray result.ProjectReferences,
result.Properties,
result.TargetFramework

let mkStandardProjectReferences () =
let projFile = "fcs-test.fsproj"
let fileName = "fcs-test.fsproj"
let projDir = __SOURCE_DIRECTORY__
getProjectOptions projDir projFile
|> List.filter (fun s -> s.StartsWith("-r:"))
|> List.map (fun s -> s.Replace("-r:", ""))
let projFile = Path.Combine(projDir, fileName)
let (args, _, _, _) = getProjectOptionsFromProjectFile true projFile
args
|> Array.filter (fun s -> s.StartsWith("-r:"))

let mkProjectCommandLineArgsForScript (dllName, fileNames) =
[| yield "--simpleresolution"
Expand All @@ -53,7 +104,7 @@ let mkProjectCommandLineArgsForScript (dllName, fileNames) =
yield x
let references = mkStandardProjectReferences ()
for r in references do
yield "-r:" + r
yield r
|]

let getProjectOptionsFromCommandLineArgs(projName, argv): FSharpProjectOptions =
Expand Down
10 changes: 5 additions & 5 deletions fcs/fcs-test/fcs-test.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
</PropertyGroup>

<ItemGroup>
<Compile Include="ast_print.fs"/>
<Compile Include="ast_print.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Expand All @@ -19,9 +19,9 @@
</ItemGroup>

<ItemGroup>
<!-- <PackageReference Include="FSharp.Core" Version="6.0.5" /> -->
<PackageReference Include="Dotnet.ProjInfo" Version="0.44.0" />
<PackageReference Include="Fable.Core" Version="3.7.1" />
<!-- <PackageReference Include="FSharp.Core" Version="7.0.0" /> -->
<PackageReference Include="Buildalyzer" Version="4.1.6" />
<PackageReference Include="Fable.Core" Version="4.0.0-*" />
<PackageReference Include="Fable.Import.Browser" Version="1.4.0" />
</ItemGroup>
</Project>
5 changes: 3 additions & 2 deletions fcs/service_slim.fs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ module internal ParseAndCheck =

let userOpName = "Unknown"
let suggestNamesForErrors = true
let captureIdentifiersWhenParsing = false

let measureTime (f: unit -> 'a) =
let sw = Diagnostics.Stopwatch.StartNew()
Expand Down Expand Up @@ -192,7 +193,7 @@ module internal ParseAndCheck =
compilerState.parseCache.GetOrAdd(parseCacheKey, fun _ ->
ClearStaleCache(fileName, parsingOptions, compilerState)
let sourceText = SourceText.ofString source.Value
let parseErrors, parseTreeOpt, anyErrors = ParseAndCheckFile.parseFile (sourceText, fileName, parsingOptions, userOpName, suggestNamesForErrors)
let parseErrors, parseTreeOpt, anyErrors = ParseAndCheckFile.parseFile (sourceText, fileName, parsingOptions, userOpName, suggestNamesForErrors, captureIdentifiersWhenParsing)
let dependencyFiles = [||] // interactions have no dependencies
FSharpParseFileResults (parseErrors, parseTreeOpt, anyErrors, dependencyFiles) )

Expand All @@ -208,7 +209,7 @@ module internal ParseAndCheck =

let input, moduleNamesDict = input |> DeduplicateParsedInputModuleName moduleNamesDict
let tcResult, tcState =
CheckOneInput (checkForErrors, compilerState.tcConfig, compilerState.tcImports, compilerState.tcGlobals, prefixPathOpt, tcSink, tcState, input, false)
CheckOneInput (checkForErrors, compilerState.tcConfig, compilerState.tcImports, compilerState.tcGlobals, prefixPathOpt, tcSink, tcState, input)
|> Cancellable.runWithoutCancellation

let fileName = parseResults.FileName
Expand Down

0 comments on commit cb453d2

Please sign in to comment.