diff --git a/.vscode/launch.json b/.vscode/launch.json index 6cc703662fb..07c4b527e5e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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", diff --git a/buildtools/AssemblyCheck/AssemblyCheck.fsproj b/buildtools/AssemblyCheck/AssemblyCheck.fsproj index d031ded4c10..bc9584cf701 100644 --- a/buildtools/AssemblyCheck/AssemblyCheck.fsproj +++ b/buildtools/AssemblyCheck/AssemblyCheck.fsproj @@ -4,6 +4,7 @@ Exe net8.0 net6.0 + net7.0 true false diff --git a/buildtools/buildtools.targets b/buildtools/buildtools.targets index 8332b53a237..3b2fa489c66 100644 --- a/buildtools/buildtools.targets +++ b/buildtools/buildtools.targets @@ -20,7 +20,7 @@ BeforeTargets="CoreCompile"> - $(ArtifactsDir)\bin\fslex\Release\net6.0\fslex.dll + $(ArtifactsDir)\bin\fslex\Release\net7.0\fslex.dll @@ -44,7 +44,7 @@ BeforeTargets="CoreCompile"> - $(ArtifactsDir)\bin\fsyacc\Release\net6.0\fsyacc.dll + $(ArtifactsDir)\bin\fsyacc\Release\net7.0\fsyacc.dll diff --git a/buildtools/fslex/fslex.fsproj b/buildtools/fslex/fslex.fsproj index 907d979eedd..72d3d7d420c 100644 --- a/buildtools/fslex/fslex.fsproj +++ b/buildtools/fslex/fslex.fsproj @@ -4,7 +4,6 @@ Exe net8.0 net7.0 - net6.0 INTERNALIZED_FSLEXYACC_RUNTIME;$(DefineConstants) true false diff --git a/buildtools/fsyacc/fsyacc.fsproj b/buildtools/fsyacc/fsyacc.fsproj index 9923dc4b29f..3bc4c7c5203 100644 --- a/buildtools/fsyacc/fsyacc.fsproj +++ b/buildtools/fsyacc/fsyacc.fsproj @@ -4,7 +4,6 @@ Exe net8.0 net7.0 - net6.0 INTERNALIZED_FSLEXYACC_RUNTIME;$(DefineConstants) true false diff --git a/fcs/fcs-test/Program.fs b/fcs/fcs-test/Program.fs index edb6522a71a..fdefd83d449 100644 --- a/fcs/fcs-test/Program.fs +++ b/fcs/fcs-test/Program.fs @@ -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" @@ -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 = diff --git a/fcs/fcs-test/fcs-test.fsproj b/fcs/fcs-test/fcs-test.fsproj index 839638d2551..3a8c1940791 100644 --- a/fcs/fcs-test/fcs-test.fsproj +++ b/fcs/fcs-test/fcs-test.fsproj @@ -2,12 +2,12 @@ Exe - net6.0 + net7.0 true - + @@ -19,9 +19,9 @@ - - - + + + diff --git a/fcs/service_slim.fs b/fcs/service_slim.fs index 7de2cc8dd78..f7c5b9b2343 100644 --- a/fcs/service_slim.fs +++ b/fcs/service_slim.fs @@ -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() @@ -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) ) @@ -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