diff --git a/src/Compiler/Driver/CompilerOptions.fs b/src/Compiler/Driver/CompilerOptions.fs index 7bbecb8916b..40b001f4699 100644 --- a/src/Compiler/Driver/CompilerOptions.fs +++ b/src/Compiler/Driver/CompilerOptions.fs @@ -22,6 +22,7 @@ open FSharp.Compiler.TypedTreeOps open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities +open System.Text module Attributes = open System.Runtime.CompilerServices @@ -111,7 +112,11 @@ let compilerOptionUsage (CompilerOption (s, tag, spec, _, _)) = else sprintf "%s:%s" s tag (* still being decided *) -let PrintCompilerOption (CompilerOption (_s, _tag, _spec, _, help) as compilerOption) = +let nl = Environment.NewLine + +let getCompilerOption (CompilerOption (_s, _tag, _spec, _, help) as compilerOption) = + let sb = StringBuilder() + let flagWidth = 42 // fixed width for printing of flags, e.g. --debug:{full|pdbonly|portable|embedded} let defaultLineWidth = 80 // the fallback width @@ -130,18 +135,18 @@ let PrintCompilerOption (CompilerOption (_s, _tag, _spec, _, help) as compilerOp // flagWidth chars - for flags description or padding on continuation lines. // single space - space. // description - words upto but excluding the final character of the line. - printf "%-40s" (compilerOptionUsage compilerOption) + let _ = sb.Append $"{compilerOptionUsage compilerOption, -40}" let printWord column (word: string) = // Have printed upto column. // Now print the next word including any preceding whitespace. // Returns the column printed to (suited to folding). if column + 1 (*space*) + word.Length >= lineWidth then // NOTE: "equality" ensures final character of the line is never printed - printfn "" (* newline *) - printf "%-40s %s" "" (*<--flags*) word + let _ = sb.Append $"{nl}" + let _ = sb.Append $"{String.Empty, -40} {word}" flagWidth + 1 + word.Length else - printf " %s" word + let _ = sb.Append $" {word}" column + 1 + word.Length let words = @@ -150,16 +155,19 @@ let PrintCompilerOption (CompilerOption (_s, _tag, _spec, _, help) as compilerOp | Some s -> s.Split [| ' ' |] let _finalColumn = Array.fold printWord flagWidth words - printfn "" (* newline *) + let _ = sb.Append $"{nl}" + sb.ToString() -let PrintPublicOptions (heading, opts) = +let getPublicOptions (heading, opts) = if not (isNil opts) then - printfn "" - printfn "" - printfn "\t\t%s" heading - List.iter PrintCompilerOption opts + $"{nl}{nl}\t\t{heading}{nl}" + + (opts |> List.map getCompilerOption |> String.concat "") + else + "" + +let GetCompilerOptionBlocks blocks = + let sb = new StringBuilder() -let PrintCompilerOptionBlocks blocks = let publicBlocks = blocks |> List.choose (function @@ -173,10 +181,11 @@ let PrintCompilerOptionBlocks blocks = let headingOptions = publicBlocks |> List.filter (fun (h2, _) -> heading = h2) |> List.collect snd - PrintPublicOptions(heading, headingOptions) + let _ = sb.Append(getPublicOptions (heading, headingOptions)) Set.add heading doneHeadings List.fold consider Set.empty publicBlocks |> ignore> + sb.ToString() (* For QA *) let dumpCompilerOption prefix (CompilerOption (str, _, spec, _, _)) = @@ -1972,31 +1981,46 @@ let deprecatedFlagsFsc tcConfigB = // OptionBlock: Miscellaneous options //----------------------------------- -let DisplayBannerText tcConfigB = +let GetBannerText tcConfigB = if tcConfigB.showBanner then - (printfn "%s" tcConfigB.productNameForBannerText - printfn "%s" (FSComp.SR.optsCopyright ())) + $"{tcConfigB.productNameForBannerText}{nl}" + + $"{FSComp.SR.optsCopyright ()}{nl}" + else + "" /// FSC only help. (FSI has it's own help function). -let displayHelpFsc tcConfigB (blocks: CompilerOptionBlock list) = - DisplayBannerText tcConfigB - PrintCompilerOptionBlocks blocks - exit 0 +let GetHelpFsc tcConfigB (blocks: CompilerOptionBlock list) = + GetBannerText tcConfigB + GetCompilerOptionBlocks blocks -let displayVersion tcConfigB = - printfn "%s" tcConfigB.productNameForBannerText - exit 0 +let GetVersion tcConfigB = + $"{tcConfigB.productNameForBannerText}{nl}" let miscFlagsBoth tcConfigB = [ CompilerOption("nologo", tagNone, OptionUnit(fun () -> tcConfigB.showBanner <- false), None, Some(FSComp.SR.optsNologo ())) - CompilerOption("version", tagNone, OptionConsoleOnly(fun _ -> displayVersion tcConfigB), None, Some(FSComp.SR.optsVersion ())) + CompilerOption( + "version", + tagNone, + OptionConsoleOnly(fun _ -> + Console.Write(GetVersion tcConfigB) + exit 0), + None, + Some(FSComp.SR.optsVersion ()) + ) ] let miscFlagsFsc tcConfigB = miscFlagsBoth tcConfigB @ [ - CompilerOption("help", tagNone, OptionConsoleOnly(fun blocks -> displayHelpFsc tcConfigB blocks), None, Some(FSComp.SR.optsHelp ())) + CompilerOption( + "help", + tagNone, + OptionConsoleOnly(fun blocks -> + Console.Write(GetHelpFsc tcConfigB blocks) + exit 0), + None, + Some(FSComp.SR.optsHelp ()) + ) CompilerOption("@", tagNone, OptionUnit ignore, None, Some(FSComp.SR.optsResponseFile ())) ] @@ -2052,7 +2076,9 @@ let abbreviatedFlagsFsc tcConfigB = CompilerOption( "?", tagNone, - OptionConsoleOnly(fun blocks -> displayHelpFsc tcConfigB blocks), + OptionConsoleOnly(fun blocks -> + Console.Write(GetHelpFsc tcConfigB blocks) + exit 0), None, Some(FSComp.SR.optsShortFormOf ("--help")) ) @@ -2060,7 +2086,9 @@ let abbreviatedFlagsFsc tcConfigB = CompilerOption( "help", tagNone, - OptionConsoleOnly(fun blocks -> displayHelpFsc tcConfigB blocks), + OptionConsoleOnly(fun blocks -> + Console.Write(GetHelpFsc tcConfigB blocks) + exit 0), None, Some(FSComp.SR.optsShortFormOf ("--help")) ) @@ -2068,7 +2096,9 @@ let abbreviatedFlagsFsc tcConfigB = CompilerOption( "full-help", tagNone, - OptionConsoleOnly(fun blocks -> displayHelpFsc tcConfigB blocks), + OptionConsoleOnly(fun blocks -> + Console.Write(GetHelpFsc tcConfigB blocks) + exit 0), None, Some(FSComp.SR.optsShortFormOf ("--help")) ) diff --git a/src/Compiler/Driver/CompilerOptions.fsi b/src/Compiler/Driver/CompilerOptions.fsi index 983a38f5182..048bffb51e9 100644 --- a/src/Compiler/Driver/CompilerOptions.fsi +++ b/src/Compiler/Driver/CompilerOptions.fsi @@ -43,7 +43,7 @@ and CompilerOptionBlock = | PublicOptions of heading: string * options: CompilerOption list | PrivateOptions of options: CompilerOption list -val PrintCompilerOptionBlocks: CompilerOptionBlock list -> unit // for printing usage +val GetCompilerOptionBlocks: CompilerOptionBlock list -> string val DumpCompilerOptionBlocks: CompilerOptionBlock list -> unit // for QA @@ -52,7 +52,11 @@ val FilterCompilerOptionBlock: (CompilerOption -> bool) -> CompilerOptionBlock - /// Parse and process a set of compiler options val ParseCompilerOptions: (string -> unit) * CompilerOptionBlock list * string list -> unit -val DisplayBannerText: TcConfigBuilder -> unit +val GetBannerText: tcConfigB: TcConfigBuilder -> string + +val GetHelpFsc: tcConfigB: TcConfigBuilder -> blocks: CompilerOptionBlock list -> string + +val GetVersion: tcConfigB: TcConfigBuilder -> string val GetCoreFscCompilerOptions: TcConfigBuilder -> CompilerOptionBlock list diff --git a/src/Compiler/Driver/fsc.fs b/src/Compiler/Driver/fsc.fs index 654c0a8bda2..28174489a61 100644 --- a/src/Compiler/Driver/fsc.fs +++ b/src/Compiler/Driver/fsc.fs @@ -544,7 +544,7 @@ let main1 // Display the banner text, if necessary if not bannerAlreadyPrinted then - DisplayBannerText tcConfigB + Console.Write(GetBannerText tcConfigB) // Create tcGlobals and frameworkTcImports let outfile, pdbfile, assemblyName = diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 32a8ad40592..f44b7d5df46 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -882,10 +882,10 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, // In the "--help", these options can be printed either before (fsiUsagePrefix) or after (fsiUsageSuffix) the core options. let displayHelpFsi tcConfigB (blocks:CompilerOptionBlock list) = - DisplayBannerText tcConfigB; + Console.Write (GetBannerText tcConfigB) fprintfn fsiConsoleOutput.Out "" fprintfn fsiConsoleOutput.Out "%s" (FSIstrings.SR.fsiUsage(executableFileNameWithoutExtension.Value)) - PrintCompilerOptionBlocks blocks + Console.Write (GetCompilerOptionBlocks blocks) exit 0 // option tags diff --git a/tests/FSharp.Compiler.Service.Tests/ConsoleOnlyOptionsTests.fs b/tests/FSharp.Compiler.Service.Tests/ConsoleOnlyOptionsTests.fs new file mode 100644 index 00000000000..7cab17439c4 --- /dev/null +++ b/tests/FSharp.Compiler.Service.Tests/ConsoleOnlyOptionsTests.fs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Compiler.Service.Tests.ConsoleOnlyOptionsTests + +open System +open System.IO +open FSharp.Compiler.CompilerOptions +open NUnit.Framework +open TestDoubles + +[] +let ``Help is displayed correctly`` () = + let builder = getArbitraryTcConfigBuilder() + let blocks = GetCoreFscCompilerOptions builder + let expectedHelp = File.ReadAllText $"{__SOURCE_DIRECTORY__}/expected-help-output.txt" + + let help = GetHelpFsc builder blocks + + // contains instead of equals + // as we don't control the 1st line of the output (the version) + // it's tested separately + StringAssert.Contains(expectedHelp, help.Replace("\r\n", Environment.NewLine)) + +[] +let ``Version is displayed correctly`` () = + let builder = getArbitraryTcConfigBuilder() + let expectedVersionPattern = @"Microsoft \(R\) F# Compiler version \d+\.\d+\.\d+\.\d+ for F# \d+\.\d+" + + let version = GetVersion builder + + Assert.That(version, Does.Match expectedVersionPattern) diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj index 606deeeef94..81f470dcbcd 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj @@ -20,6 +20,9 @@ + + Never + @@ -38,6 +41,8 @@ Symbols.fs + + SyntaxTree\TypeTests.fs diff --git a/tests/FSharp.Compiler.Service.Tests/TestDoubles.fs b/tests/FSharp.Compiler.Service.Tests/TestDoubles.fs new file mode 100644 index 00000000000..7af70f94c83 --- /dev/null +++ b/tests/FSharp.Compiler.Service.Tests/TestDoubles.fs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Compiler.Service.Tests.TestDoubles + +open System.IO +open FSharp.Compiler.Text +open FSharp.Compiler.AbstractIL.ILBinaryReader +open FSharp.Compiler.CompilerConfig +open Internal.Utilities + +// just a random thing to make things work +let internal getArbitraryTcConfigBuilder() = + TcConfigBuilder.CreateNew( + null, + FSharpEnvironment.BinFolderOfDefaultFSharpCompiler(None).Value, + ReduceMemoryFlag.Yes, + Directory.GetCurrentDirectory(), + false, + false, + CopyFSharpCoreFlag.No, + (fun _ -> None), + None, + Range.Zero) diff --git a/tests/FSharp.Compiler.Service.Tests/VisualStudioVersusConsoleContextTests.fs b/tests/FSharp.Compiler.Service.Tests/VisualStudioVersusConsoleContextTests.fs index 9d37644e892..24dea808feb 100644 --- a/tests/FSharp.Compiler.Service.Tests/VisualStudioVersusConsoleContextTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/VisualStudioVersusConsoleContextTests.fs @@ -2,13 +2,9 @@ module FSharp.Compiler.Service.Tests.VisualStudioVersusConsoleContextTests -open FSharp.Compiler.Text open NUnit.Framework -open System.IO -open FSharp.Compiler.CompilerConfig open FSharp.Compiler.CompilerOptions -open Internal.Utilities -open FSharp.Compiler.AbstractIL.ILBinaryReader +open TestDoubles // copypasted from the CompilerOptions code, // not worth changing that code's accessibility just for this test @@ -23,17 +19,7 @@ let private getOptionsFromOptionBlocks blocks = [] // controls https://github.com/dotnet/fsharp/issues/13549 let ``Console-only options are filtered out for fsc in the VS context`` () = // just a random thing to make things work - let builder = TcConfigBuilder.CreateNew( - null, - FSharpEnvironment.BinFolderOfDefaultFSharpCompiler(None).Value, - ReduceMemoryFlag.Yes, - Directory.GetCurrentDirectory(), - false, - false, - CopyFSharpCoreFlag.No, - (fun _ -> None), - None, - Range.Zero) + let builder = getArbitraryTcConfigBuilder() let blocks = GetCoreServiceCompilerOptions builder let options = getOptionsFromOptionBlocks blocks diff --git a/tests/FSharp.Compiler.Service.Tests/expected-help-output.txt b/tests/FSharp.Compiler.Service.Tests/expected-help-output.txt new file mode 100644 index 00000000000..7d787f6ceb4 --- /dev/null +++ b/tests/FSharp.Compiler.Service.Tests/expected-help-output.txt @@ -0,0 +1,171 @@ +Copyright (c) Microsoft Corporation. All Rights Reserved. + + + - OUTPUT FILES - +--out: Name of the output file (Short form: + -o) +--target:exe Build a console executable +--target:winexe Build a Windows executable +--target:library Build a library (Short form: -a) +--target:module Build a module that can be added to + another assembly +--delaysign[+|-] Delay-sign the assembly using only + the public portion of the strong + name key +--publicsign[+|-] Public-sign the assembly using only + the public portion of the strong + name key, and mark the assembly as + signed +--doc: Write the xmldoc of the assembly to + the given file +--keyfile: Specify a strong name key file +--platform: Limit which platforms this code can + run on: x86, x64, Arm, Arm64, + Itanium, anycpu32bitpreferred, or + anycpu. The default is anycpu. +--nooptimizationdata Only include optimization + information essential for + implementing inlined constructs. + Inhibits cross-module inlining but + improves binary compatibility. +--nointerfacedata Don't add a resource to the + generated assembly containing + F#-specific metadata +--sig: Print the inferred interface of the + assembly to a file +--allsigs Print the inferred interfaces of all + compilation files to associated + signature files +--nocopyfsharpcore Don't copy FSharp.Core.dll along the + produced binaries +--refonly[+|-] Produce a reference assembly, + instead of a full assembly, as the + primary output +--refout: Produce a reference assembly with + the specified file path. + + + - INPUT FILES - +--reference: Reference an assembly (Short form: + -r) +--compilertool: Reference an assembly or directory + containing a design time tool (Short + form: -t) + + + - RESOURCES - +--win32icon: Specify a Win32 icon file (.ico) +--win32res: Specify a Win32 resource file (.res) +--win32manifest: Specify a Win32 manifest file +--nowin32manifest Do not include the default Win32 + manifest +--resource: Embed the specified managed resource +--linkresource: Link the specified resource to this + assembly where the resinfo format is + [,[,public|private]] + + + - CODE GENERATION - +--debug[+|-] Emit debug information (Short form: + -g) +--debug:{full|pdbonly|portable|embedded} Specify debugging type: full, + portable, embedded, pdbonly. ('full' + is the default if no debuggging type + specified and enables attaching a + debugger to a running program, + 'portable' is a cross-platform + format, 'embedded' is a + cross-platform format embedded into + the output file). +--embed[+|-] Embed all source files in the + portable PDB file +--embed: Embed specific source files in the + portable PDB file +--sourcelink: Source link information file to + embed in the portable PDB file +--optimize[+|-] Enable optimizations (Short form: + -O) +--tailcalls[+|-] Enable or disable tailcalls +--deterministic[+|-] Produce a deterministic assembly + (including module version GUID and + timestamp) +--pathmap: Maps physical paths to source path + names output by the compiler +--crossoptimize[+|-] Enable or disable cross-module + optimizations +--reflectionfree Disable implicit generation of + constructs using reflection + + + - ERRORS AND WARNINGS - +--warnaserror[+|-] Report all warnings as errors +--warnaserror[+|-]: Report specific warnings as errors +--warn: Set a warning level (0-5) +--nowarn: Disable specific warning messages +--warnon: Enable specific warnings that may be + off by default +--consolecolors[+|-] Output warning and error messages in + color + + + - LANGUAGE - +--langversion:{?|version|latest|preview} Display the allowed values for + language version, specify language + version such as 'latest' or + 'preview' +--checked[+|-] Generate overflow checks +--define: Define conditional compilation + symbols (Short form: -d) +--mlcompatibility Ignore ML compatibility warnings + + + - MISCELLANEOUS - +--nologo Suppress compiler copyright message +--version Display compiler version banner and + exit +--help Display this usage message (Short + form: -?) +--@ Read response file for more options + + + - ADVANCED - +--codepage: Specify the codepage used to read + source files +--utf8output Output messages in UTF-8 encoding +--preferreduilang: Specify the preferred output + language culture name (e.g. es-ES, + ja-JP) +--fullpaths Output messages with fully qualified + paths +--lib: Specify a directory for the include + path which is used to resolve source + files and assemblies (Short form: + -I) +--simpleresolution Resolve assembly references using + directory-based rules rather than + MSBuild resolution +--targetprofile: Specify target framework profile of + this assembly. Valid values are + mscorlib, netcore or netstandard. + Default - mscorlib +--baseaddress:
Base address for the library to be + built +--checksumalgorithm:{SHA1|SHA256} Specify algorithm for calculating + source file checksum stored in PDB. + Supported values are: SHA1 or SHA256 + (default) +--noframework Do not reference the default CLI + assemblies by default +--standalone Statically link the F# library and + all referenced DLLs that depend on + it into the assembly being generated +--staticlink: Statically link the given assembly + and all referenced DLLs that depend + on this assembly. Use an assembly + name e.g. mylib, not a DLL name. +--pdb: Name the output debug file +--highentropyva[+|-] Enable high-entropy ASLR +--subsystemversion: Specify subsystem version of this + assembly +--quotations-debug[+|-] Emit debug information in quotations