未验证 提交 6aa6a2c3 编写于 作者: D Don Syme 提交者: GitHub

Format implementation code in FSharp.Build (#13160)

* format FSharp.Build

* format FSharp.Build

* format FSharp.Build

* fix some formatting for FSharp.Build

* format FSharp.Build

* format FSharp.Build
上级 0006b73b
......@@ -16,7 +16,6 @@ src/FSharp.Core/**/*.fs
src/Compiler/**/*.fs
src/fsc/**/*.fs
src/fscAnyCpu/**/*.fs
src/FSharp.Build/**/*.fs
src/FSharp.Compiler.Server.Shared/**/*.fs
src/FSharp.DependencyManager.Nuget/**/*.fs
src/fsi/**/*.fs
......
......@@ -4,9 +4,7 @@ namespace FSharp.Build
open System
open System.IO
open System.Text
open Microsoft.Build.Tasks
open Microsoft.Build.Utilities
type CreateFSharpManifestResourceName public () =
inherit CreateCSharpManifestResourceName()
......@@ -14,12 +12,14 @@ type CreateFSharpManifestResourceName public () =
// When set to true, generate resource names in the same way as C# with root namespace and folder names
member val UseStandardResourceNames = false with get, set
override this.CreateManifestName
((fileName:string),
(linkFileName:string),
(rootNamespace:string), (* may be null *)
(dependentUponFileName:string), (* may be null *)
(binaryStream:System.IO.Stream) (* may be null *)) : string =
override this.CreateManifestName
(
fileName: string,
linkFileName: string,
rootNamespace: string, // may be null
dependentUponFileName: string, // may be null
binaryStream: Stream // may be null
) : string =
// The Visual CSharp and XBuild CSharp toolchains transform resource names like this:
// SubDir\abc.resx --> SubDir.abc.resources
......@@ -30,37 +30,56 @@ type CreateFSharpManifestResourceName public () =
let fileName, linkFileName, rootNamespace =
match this.UseStandardResourceNames with
| true ->
fileName, linkFileName, rootNamespace
| true -> fileName, linkFileName, rootNamespace
| false ->
let runningOnMono =
let runningOnMono =
try
System.Type.GetType("Mono.Runtime") <> null
with e ->
false
let fileName = if not runningOnMono || fileName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase) then fileName else Path.GetFileName(fileName)
let linkFileName = if not runningOnMono || linkFileName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase) then linkFileName else Path.GetFileName(linkFileName)
fileName, linkFileName, ""
with
| e -> false
let embeddedFileName =
let fileName =
if
not runningOnMono
|| fileName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)
then
fileName
else
Path.GetFileName(fileName)
let linkFileName =
if
not runningOnMono
|| linkFileName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)
then
linkFileName
else
Path.GetFileName(linkFileName)
fileName, linkFileName, ""
let embeddedFileName =
match linkFileName with
| null -> fileName
| _ -> linkFileName
| null -> fileName
| _ -> linkFileName
// since we do not support resources dependent on a form, we always pass null for a binary stream
let cSharpResult =
// since we do not support resources dependent on a form, we always pass null for a binary stream
let cSharpResult =
base.CreateManifestName(fileName, linkFileName, rootNamespace, dependentUponFileName, null)
// Workaround that makes us keep .resources extension on both 3.5 and 3.5SP1
// 3.5 stripped ".resources", 3.5 SP1 does not. We should do 3.5SP1 thing
let extensionToWorkaround = ".resources"
if embeddedFileName.EndsWith(extensionToWorkaround, StringComparison.OrdinalIgnoreCase)
&& not (cSharpResult.EndsWith(extensionToWorkaround, StringComparison.OrdinalIgnoreCase)) then
if
embeddedFileName.EndsWith(extensionToWorkaround, StringComparison.OrdinalIgnoreCase)
&& not (cSharpResult.EndsWith(extensionToWorkaround, StringComparison.OrdinalIgnoreCase))
then
cSharpResult + extensionToWorkaround
else
cSharpResult
override _.IsSourceFile (fileName: string) =
override _.IsSourceFile(fileName: string) =
let extension = Path.GetExtension(fileName)
(String.Equals(extension, ".fs", StringComparison.OrdinalIgnoreCase) ||
String.Equals(extension, ".ml", StringComparison.OrdinalIgnoreCase))
\ No newline at end of file
(String.Equals(extension, ".fs", StringComparison.OrdinalIgnoreCase)
|| String.Equals(extension, ".ml", StringComparison.OrdinalIgnoreCase))
......@@ -10,20 +10,23 @@ open Internal.Utilities
[<assembly: System.Runtime.InteropServices.ComVisible(false)>]
[<assembly: System.CLSCompliant(true)>]
do()
do ()
// Shim to match nullness checking library support in preview
[<AutoOpen>]
module Utils =
module Utils =
/// Match on the nullness of an argument.
let inline (|Null|NonNull|) (x: 'T) : Choice<unit,'T> = match x with null -> Null | v -> NonNull v
let inline (|Null|NonNull|) (x: 'T) : Choice<unit, 'T> =
match x with
| null -> Null
| v -> NonNull v
/// Indicates that a type may be null. 'MaybeNull<string>' used internally in the F# compiler as unchecked
/// replacement for 'string?' for example for future FS-1060.
type 'T MaybeNull when 'T : null and 'T: not struct = 'T
type 'T MaybeNull when 'T: null and 'T: not struct = 'T
type FSharpCommandLineBuilder () =
type FSharpCommandLineBuilder() =
// In addition to generating a command-line that will be handed to cmd.exe, we also generate
// an array of individual arguments. The former needs to be quoted (and cmd.exe will strip the
......@@ -31,8 +34,8 @@ type FSharpCommandLineBuilder () =
// class gets us out of the business of unparsing-then-reparsing arguments.
let builder = new CommandLineBuilder()
let mutable args = [] // in reverse order
let mutable srcs = [] // in reverse order
let mutable args = [] // in reverse order
let mutable srcs = [] // in reverse order
/// Return a list of the arguments (with no quoting for the cmd.exe shell)
member _.CapturedArguments() = List.rev args
......@@ -48,8 +51,9 @@ type FSharpCommandLineBuilder () =
// do not update "args", not used
for item in filenames do
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull("", item.ItemSpec) // we don't want to quote the file name, this is a way to get that
tmp.AppendSwitchUnquotedIfNotNull("", item.ItemSpec) // we don't want to quote the file name, this is a way to get that
let s = tmp.ToString()
if s <> String.Empty then
srcs <- tmp.ToString() :: srcs
......@@ -58,6 +62,7 @@ type FSharpCommandLineBuilder () =
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull(switch, values, sep)
let s = tmp.ToString()
if s <> String.Empty then
args <- s :: args
......@@ -66,43 +71,57 @@ type FSharpCommandLineBuilder () =
builder.AppendSwitchIfNotNull(switch, value)
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull(switch, value)
let providedMetaData =
metadataNames
|> Array.filter (String.IsNullOrWhiteSpace >> not)
metadataNames |> Array.filter (String.IsNullOrWhiteSpace >> not)
if providedMetaData.Length > 0 then
tmp.AppendTextUnquoted ","
tmp.AppendTextUnquoted (providedMetaData|> String.concat ",")
tmp.AppendTextUnquoted(providedMetaData |> String.concat ",")
let s = tmp.ToString()
if s <> String.Empty then
args <- s :: args
member _.AppendSwitchUnquotedIfNotNull(switch: string, value: string MaybeNull) =
assert(switch = "") // we only call this method for "OtherFlags"
assert (switch = "") // we only call this method for "OtherFlags"
// Unfortunately we still need to mimic what cmd.exe does, but only for "OtherFlags".
let ParseCommandLineArgs(commandLine: string) = // returns list in reverse order
let ParseCommandLineArgs (commandLine: string) = // returns list in reverse order
let mutable args = []
let mutable i = 0 // index into commandLine
let len = commandLine.Length
while i < len do
// skip whitespace
while i < len && System.Char.IsWhiteSpace(commandLine, i) do
i <- i + 1
if i < len then
// parse an argument
let sb = new StringBuilder()
let mutable finished = false
let mutable insideQuote = false
while i < len && not finished do
match commandLine.[i] with
| '"' -> insideQuote <- not insideQuote; i <- i + 1
| '"' ->
insideQuote <- not insideQuote
i <- i + 1
| c when not insideQuote && System.Char.IsWhiteSpace(c) -> finished <- true
| c -> sb.Append(c) |> ignore; i <- i + 1
| c ->
sb.Append(c) |> ignore
i <- i + 1
args <- sb.ToString() :: args
args
builder.AppendSwitchUnquotedIfNotNull(switch, value)
let tmp = new CommandLineBuilder()
tmp.AppendSwitchUnquotedIfNotNull(switch, value)
let s = tmp.ToString()
if s <> String.Empty then
args <- ParseCommandLineArgs(s) @ args
......@@ -110,8 +129,5 @@ type FSharpCommandLineBuilder () =
builder.AppendSwitch(switch)
args <- switch :: args
member internal x.GetCapturedArguments() =
[|
yield! x.CapturedArguments()
yield! x.CapturedFilenames()
|]
member internal x.GetCapturedArguments() =
[| yield! x.CapturedArguments(); yield! x.CapturedFilenames() |]
......@@ -3,8 +3,6 @@
namespace FSharp.Build
open System
open System.Collections
open System.Globalization
open System.IO
open System.Linq
open System.Text
......@@ -20,7 +18,8 @@ type FSharpEmbedResXSource() =
let mutable _outputPath: string = ""
let mutable _targetFramework: string = ""
let boilerplate = @"// <auto-generated>
let boilerplate =
@"// <auto-generated>
namespace {0}
......@@ -32,95 +31,128 @@ module internal {1} =
let ResourceManager = new System.Resources.ResourceManager(""{2}"", C(0).GetType().GetTypeInfo().Assembly)
let GetString(name:System.String) : System.String = ResourceManager.GetString(name, Culture)"
let boilerplateGetObject = " let GetObject(name:System.String) : System.Object = ResourceManager.GetObject(name, Culture)"
let boilerplateGetObject =
" let GetObject(name:System.String) : System.Object = ResourceManager.GetObject(name, Culture)"
let generateSource (resx:string) (fullModuleName:string) (generateLegacy:bool) (generateLiteral:bool) =
let generateSource (resx: string) (fullModuleName: string) (generateLegacy: bool) (generateLiteral: bool) =
try
let printMessage = printfn "FSharpEmbedResXSource: %s"
let justFileName = Path.GetFileNameWithoutExtension(resx)
let sourcePath = Path.Combine(_outputPath, justFileName + ".fs")
// simple up-to-date check
if File.Exists(resx) && File.Exists(sourcePath) &&
File.GetLastWriteTimeUtc(resx) <= File.GetLastWriteTimeUtc(sourcePath) then
if File.Exists(resx)
&& File.Exists(sourcePath)
&& File.GetLastWriteTimeUtc(resx) <= File.GetLastWriteTimeUtc(sourcePath) then
printMessage (sprintf "Skipping generation: '%s' since it is up-to-date." sourcePath)
Some(sourcePath)
else
let namespaceName, moduleName =
let parts = fullModuleName.Split('.')
if parts.Length = 1 then ("global", parts.[0])
else (String.Join(".", parts, 0, parts.Length - 1), parts.[parts.Length - 1])
let generateGetObject = not (_targetFramework.StartsWith("netstandard1.") || _targetFramework.StartsWith("netcoreapp1."))
if parts.Length = 1 then
("global", parts.[0])
else
(String.Join(".", parts, 0, parts.Length - 1), parts.[parts.Length - 1])
let generateGetObject =
not (
_targetFramework.StartsWith("netstandard1.")
|| _targetFramework.StartsWith("netcoreapp1.")
)
printMessage (sprintf "Generating code for target framework %s" _targetFramework)
let sb = StringBuilder().AppendLine(String.Format(boilerplate, namespaceName, moduleName, justFileName))
if generateGetObject then sb.AppendLine(boilerplateGetObject) |> ignore
let sb =
StringBuilder()
.AppendLine(String.Format(boilerplate, namespaceName, moduleName, justFileName))
if generateGetObject then
sb.AppendLine(boilerplateGetObject) |> ignore
printMessage <| sprintf "Generating: %s" sourcePath
let body =
let xname = XName.op_Implicit
XDocument.Load(resx).Descendants(xname "data")
|> Seq.fold (fun (sb:StringBuilder) (node:XElement) ->
let name =
match node.Attribute(xname "name") with
| null -> failwith (sprintf "Missing resource name on element '%s'" (node.ToString()))
| attr -> attr.Value
let docComment =
match node.Elements(xname "value").FirstOrDefault() with
| null -> failwith <| sprintf "Missing resource value for '%s'" name
| element -> element.Value.Trim()
let identifier = if Char.IsLetter(name.[0]) || name.[0] = '_' then name else "_" + name
let commentBody =
XElement(xname "summary", docComment).ToString().Split([|"\r\n"; "\r"; "\n"|], StringSplitOptions.None)
|> Array.fold (fun (sb:StringBuilder) line -> sb.AppendLine(" /// " + line)) (StringBuilder())
// add the resource
let accessorBody =
match (generateLegacy, generateLiteral) with
| (true, true) -> sprintf " [<Literal>]\n let %s = \"%s\"" identifier name
| (true, false) -> sprintf " let %s = \"%s\"" identifier name // the [<Literal>] attribute can't be used for FSharp.Core
| (false, _) ->
let isStringResource = node.Attribute(xname "type") |> isNull
match (isStringResource, generateGetObject) with
| (true, _) -> sprintf " let %s() = GetString(\"%s\")" identifier name
| (false, true) -> sprintf " let %s() = GetObject(\"%s\")" identifier name
| (false, false) -> "" // the target runtime doesn't support non-string resources
// TODO: When calling the `GetObject` version, parse the `type` attribute to discover the proper return type
sb.AppendLine().Append(commentBody).AppendLine(accessorBody)
) sb
|> Seq.fold
(fun (sb: StringBuilder) (node: XElement) ->
let name =
match node.Attribute(xname "name") with
| null -> failwith (sprintf "Missing resource name on element '%s'" (node.ToString()))
| attr -> attr.Value
let docComment =
match node.Elements(xname "value").FirstOrDefault() with
| null -> failwith <| sprintf "Missing resource value for '%s'" name
| element -> element.Value.Trim()
let identifier =
if Char.IsLetter(name.[0]) || name.[0] = '_' then
name
else
"_" + name
let commentBody =
XElement(xname "summary", docComment)
.ToString()
.Split([| "\r\n"; "\r"; "\n" |], StringSplitOptions.None)
|> Array.fold
(fun (sb: StringBuilder) line -> sb.AppendLine(" /// " + line))
(StringBuilder())
// add the resource
let accessorBody =
match (generateLegacy, generateLiteral) with
| (true, true) -> sprintf " [<Literal>]\n let %s = \"%s\"" identifier name
| (true, false) -> sprintf " let %s = \"%s\"" identifier name // the [<Literal>] attribute can't be used for FSharp.Core
| (false, _) ->
let isStringResource = node.Attribute(xname "type") |> isNull
match (isStringResource, generateGetObject) with
| (true, _) -> sprintf " let %s() = GetString(\"%s\")" identifier name
| (false, true) -> sprintf " let %s() = GetObject(\"%s\")" identifier name
| (false, false) -> "" // the target runtime doesn't support non-string resources
// TODO: When calling the `GetObject` version, parse the `type` attribute to discover the proper return type
sb.AppendLine().Append(commentBody).AppendLine(accessorBody))
sb
File.WriteAllText(sourcePath, body.ToString())
printMessage <| sprintf "Done: %s" sourcePath
Some(sourcePath)
with e ->
with
| e ->
printf "An exception occurred when processing '%s'\n%s" resx (e.ToString())
None
[<Required>]
member _.EmbeddedResource
with get() = _embeddedText
and set(value) = _embeddedText <- value
with get () = _embeddedText
and set (value) = _embeddedText <- value
[<Required>]
member _.IntermediateOutputPath
with get() = _outputPath
and set(value) = _outputPath <- value
with get () = _outputPath
and set (value) = _outputPath <- value
member _.TargetFramework
with get() = _targetFramework
and set(value) = _targetFramework <- value
with get () = _targetFramework
and set (value) = _targetFramework <- value
[<Output>]
member _.GeneratedSource
with get() = _generatedSource
member _.GeneratedSource = _generatedSource
interface ITask with
member _.BuildEngine
with get() = _buildEngine
and set(value) = _buildEngine <- value
with get () = _buildEngine
and set (value) = _buildEngine <- value
member _.HostObject
with get() = _hostObject
and set(value) = _hostObject <- value
with get () = _hostObject
and set (value) = _hostObject <- value
member this.Execute() =
let getBooleanMetadata (metadataName:string) (defaultValue:bool) (item:ITaskItem) =
let getBooleanMetadata (metadataName: string) (defaultValue: bool) (item: ITaskItem) =
match item.GetMetadata(metadataName) with
| value when String.IsNullOrWhiteSpace(value) -> defaultValue
| value ->
......@@ -128,19 +160,26 @@ module internal {1} =
| "true" -> true
| "false" -> false
| _ -> failwith (sprintf "Expected boolean value for '%s' found '%s'" metadataName value)
let mutable success = true
let generatedSource =
[| for item in this.EmbeddedResource do
if getBooleanMetadata "GenerateSource" false item then
let moduleName =
match item.GetMetadata("GeneratedModuleName") with
| null | "" -> Path.GetFileNameWithoutExtension(item.ItemSpec)
| value -> value
let generateLegacy = getBooleanMetadata "GenerateLegacyCode" false item
let generateLiteral = getBooleanMetadata "GenerateLiterals" true item
match generateSource item.ItemSpec moduleName generateLegacy generateLiteral with
| Some (source) -> yield TaskItem(source) :> ITaskItem
| None -> success <- false
[|
for item in this.EmbeddedResource do
if getBooleanMetadata "GenerateSource" false item then
let moduleName =
match item.GetMetadata("GeneratedModuleName") with
| null
| "" -> Path.GetFileNameWithoutExtension(item.ItemSpec)
| value -> value
let generateLegacy = getBooleanMetadata "GenerateLegacyCode" false item
let generateLiteral = getBooleanMetadata "GenerateLiterals" true item
match generateSource item.ItemSpec moduleName generateLegacy generateLiteral with
| Some (source) -> yield TaskItem(source) :> ITaskItem
| None -> success <- false
|]
_generatedSource <- generatedSource
success
此差异已折叠。
......@@ -7,6 +7,7 @@ open System.Diagnostics
open System.Globalization
open System.IO
open System.Reflection
open System.Text
open Microsoft.Build.Framework
open Microsoft.Build.Utilities
open Internal.Utilities
......@@ -16,12 +17,12 @@ open Internal.Utilities
//The goal is to have the most common/important flags available via the Fsi class, and the
//rest can be "backdoored" through the .OtherFlags property.
type public Fsi () as this =
type public Fsi() as this =
inherit ToolTask ()
inherit ToolTask()
let mutable capturedArguments: string list = [] // list of individual args, to pass to HostObject Compile()
let mutable capturedFilenames: string list = [] // list of individual source filenames, to pass to HostObject Compile()
let mutable capturedArguments: string list = [] // list of individual args, to pass to HostObject Compile()
let mutable capturedFilenames: string list = [] // list of individual source filenames, to pass to HostObject Compile()
let mutable codePage: string MaybeNull = null
let mutable commandLineArgs: ITaskItem list = []
let mutable defineConstants: ITaskItem[] = [||]
......@@ -45,13 +46,18 @@ type public Fsi () as this =
let mutable targetProfile: string MaybeNull = null
let mutable targetType: string MaybeNull = null
let mutable toolExe: string = "fsi.exe"
let mutable toolPath: string =
let locationOfThisDll =
try Some(Path.GetDirectoryName(typeof<Fsi>.Assembly.Location))
with _ -> None
try
Some(Path.GetDirectoryName(typeof<Fsi>.Assembly.Location))
with
| _ -> None
match FSharpEnvironment.BinFolderOfDefaultFSharpCompiler(locationOfThisDll) with
| Some s -> s
| None -> ""
let mutable treatWarningsAsErrors: bool = false
let mutable warningsAsErrors: string MaybeNull = null
let mutable warningsNotAsErrors: string MaybeNull = null
......@@ -68,13 +74,17 @@ type public Fsi () as this =
builder.AppendSwitchIfNotNull("--codepage:", codePage)
builder.AppendSwitchIfNotNull("--langversion:", langVersion)
if noFramework then builder.AppendSwitch("--noframework")
if noFramework then
builder.AppendSwitch("--noframework")
for item in defineConstants do
builder.AppendSwitchIfNotNull("--define:", item.ItemSpec)
if optimize then builder.AppendSwitch("--optimize+")
else builder.AppendSwitch("--optimize-")
if optimize then
builder.AppendSwitch("--optimize+")
else
builder.AppendSwitch("--optimize-")
if not tailcalls then
builder.AppendSwitch("--tailcalls-")
......@@ -85,29 +95,43 @@ type public Fsi () as this =
// NoWarn
match disabledWarnings with
| Null -> ()
| NonNull disabledWarnings -> builder.AppendSwitchesIfNotNull("--nowarn:", disabledWarnings.Split([|' '; ';'; ','; '\r'; '\n'|], StringSplitOptions.RemoveEmptyEntries), ",")
| NonNull disabledWarnings ->
builder.AppendSwitchesIfNotNull(
"--nowarn:",
disabledWarnings.Split([| ' '; ';'; ','; '\r'; '\n' |], StringSplitOptions.RemoveEmptyEntries),
","
)
builder.AppendSwitchIfNotNull("--warn:", warningLevel)
if treatWarningsAsErrors then builder.AppendSwitch("--warnaserror")
if treatWarningsAsErrors then
builder.AppendSwitch("--warnaserror")
// Change warning 76, HashReferenceNotAllowedInNonScript/HashDirectiveNotAllowedInNonScript/HashIncludeNotAllowedInNonScript, into an error
let warningsAsErrorsArray =
match warningsAsErrors with
| Null -> [| "76" |]
| NonNull warningsAsErrors -> (warningsAsErrors + " 76 ").Split([|' '; ';'; ','|], StringSplitOptions.RemoveEmptyEntries)
| NonNull warningsAsErrors ->
(warningsAsErrors + " 76 ")
.Split([| ' '; ';'; ',' |], StringSplitOptions.RemoveEmptyEntries)
builder.AppendSwitchesIfNotNull("--warnaserror:", warningsAsErrorsArray, ",")
match warningsNotAsErrors with
| Null -> ()
| NonNull warningsNotAsErrors -> builder.AppendSwitchesIfNotNull("--warnaserror-:", warningsNotAsErrors.Split([|' '; ';'; ','|], StringSplitOptions.RemoveEmptyEntries), ",")
| NonNull warningsNotAsErrors ->
builder.AppendSwitchesIfNotNull(
"--warnaserror-:",
warningsNotAsErrors.Split([| ' '; ';'; ',' |], StringSplitOptions.RemoveEmptyEntries),
","
)
builder.AppendSwitchIfNotNull("--LCID:", vslcid)
builder.AppendSwitchIfNotNull("--preferreduilang:", preferredUILang)
if utf8output then builder.AppendSwitch("--utf8output")
if utf8output then
builder.AppendSwitch("--utf8output")
builder.AppendSwitch("--fullpaths")
builder.AppendSwitch("--flaterrors")
......@@ -124,7 +148,8 @@ type public Fsi () as this =
builder.AppendSwitchUnquotedIfNotNull("", otherFlags)
capturedArguments <- builder.CapturedArguments()
if fsiExec then builder.AppendSwitch("--exec")
if fsiExec then
builder.AppendSwitch("--exec")
// Sources - these have to go last
builder.AppendFileNamesIfNotNull(sources, " ")
......@@ -134,143 +159,153 @@ type public Fsi () as this =
// --codepage <int>: Specify the codepage to use when opening source files
member _.CodePage
with get() = codePage
and set(s) = codePage <- s
with get () = codePage
and set value = codePage <- value
// --nowarn <string>: Do not report the given specific warning.
member _.DisabledWarnings
with get() = disabledWarnings
and set(a) = disabledWarnings <- a
with get () = disabledWarnings
and set value = disabledWarnings <- value
// --define <string>: Define the given conditional compilation symbol.
member _.DefineConstants
with get() = defineConstants
and set(a) = defineConstants <- a
with get () = defineConstants
and set value = defineConstants <- value
member _.DotnetFsiCompilerPath
with get() = dotnetFsiCompilerPath
and set(p) = dotnetFsiCompilerPath <- p
member _.DotnetFsiCompilerPath
with get () = dotnetFsiCompilerPath
and set value = dotnetFsiCompilerPath <- value
member _.FsiExec
with get() = fsiExec
and set(p) = fsiExec <- p
with get () = fsiExec
and set value = fsiExec <- value
member _.LCID
with get() = vslcid
and set(p) = vslcid <- p
with get () = vslcid
and set value = vslcid <- value
member _.LangVersion
with get() = langVersion
and set(s) = langVersion <- s
with get () = langVersion
and set value = langVersion <- value
// --noframework
member _.NoFramework
with get() = noFramework
and set(b) = noFramework <- b
with get () = noFramework
and set (b) = noFramework <- b
// --optimize
member _.Optimize
with get() = optimize
and set(p) = optimize <- p
with get () = optimize
and set value = optimize <- value
// --tailcalls
member _.Tailcalls
with get() = tailcalls
and set(p) = tailcalls <- p
with get () = tailcalls
and set value = tailcalls <- value
member _.OtherFlags
with get() = otherFlags
and set(s) = otherFlags <- s
with get () = otherFlags
and set value = otherFlags <- value
member _.PreferredUILang
with get() = preferredUILang
and set(s) = preferredUILang <- s
with get () = preferredUILang
and set value = preferredUILang <- value
member _.ProvideCommandLineArgs
with get() = provideCommandLineArgs
and set(p) = provideCommandLineArgs <- p
with get () = provideCommandLineArgs
and set value = provideCommandLineArgs <- value
// -r <string>: Reference an F# or .NET assembly.
member _.References
with get() = references
and set(a) = references <- a
with get () = references
and set value = references <- value
// --lib
member _.ReferencePath
with get() = referencePath
and set(s) = referencePath <- s
with get () = referencePath
and set value = referencePath <- value
// -load:<string>: load an F# source file
member _.LoadSources
with get() = loadSources
and set(a) = loadSources <- a
with get () = loadSources
and set value = loadSources <- value
member _.SkipCompilerExecution
with get() = skipCompilerExecution
and set(p) = skipCompilerExecution <- p
with get () = skipCompilerExecution
and set value = skipCompilerExecution <- value
// source files
// source files
member _.Sources
with get() = sources
and set(a) = sources <- a
with get () = sources
and set value = sources <- value
member _.TargetProfile
with get() = targetProfile
and set(p) = targetProfile <- p
with get () = targetProfile
and set value = targetProfile <- value
member _.TreatWarningsAsErrors
with get() = treatWarningsAsErrors
and set(p) = treatWarningsAsErrors <- p
with get () = treatWarningsAsErrors
and set value = treatWarningsAsErrors <- value
// For targeting other folders for "fsi.exe" (or ToolExe if different)
member _.ToolPath
with get() = toolPath
and set(s) = toolPath <- s
with get () = toolPath
and set value = toolPath <- value
// --use:<string>: execute an F# source file on startup
member _.UseSources
with get() = useSources
and set(a) = useSources <- a
with get () = useSources
and set value = useSources <- value
// For specifying the warning level (0-4)
member _.WarningLevel
with get() = warningLevel
and set(s) = warningLevel <- s
with get () = warningLevel
and set value = warningLevel <- value
member _.WarningsAsErrors
with get() = warningsAsErrors
and set(s) = warningsAsErrors <- s
member _.WarningsAsErrors
with get () = warningsAsErrors
and set value = warningsAsErrors <- value
member _.WarningsNotAsErrors
with get() = warningsNotAsErrors
and set(s) = warningsNotAsErrors <- s
with get () = warningsNotAsErrors
and set value = warningsNotAsErrors <- value
member _.Utf8Output
with get() = utf8output
and set(p) = utf8output <- p
with get () = utf8output
and set value = utf8output <- value
[<Output>]
member _.CommandLineArgs
with get() = List.toArray commandLineArgs
and set(p) = commandLineArgs <- (List.ofArray p)
with get () = List.toArray commandLineArgs
and set value = commandLineArgs <- List.ofArray value
// ToolTask methods
override _.ToolName = "fsi.exe"
override _.ToolName = "fsi.exe"
override _.StandardErrorEncoding = if utf8output then System.Text.Encoding.UTF8 else base.StandardErrorEncoding
override _.StandardErrorEncoding =
if utf8output then
Encoding.UTF8
else
base.StandardErrorEncoding
override _.StandardOutputEncoding = if utf8output then System.Text.Encoding.UTF8 else base.StandardOutputEncoding
override _.StandardOutputEncoding =
if utf8output then
Encoding.UTF8
else
base.StandardOutputEncoding
override fsi.GenerateFullPathToTool() =
if toolPath = "" then
raise (new System.InvalidOperationException(FSBuild.SR.toolpathUnknown ()))
override fsi.GenerateFullPathToTool() =
if toolPath = "" then raise (new System.InvalidOperationException(FSBuild.SR.toolpathUnknown()))
System.IO.Path.Combine(toolPath, fsi.ToolExe)
override fsi.LogToolCommand (message:string) =
fsi.Log.LogMessageFromText(message, MessageImportance.Normal) |>ignore
override fsi.LogToolCommand(message: string) =
fsi.Log.LogMessageFromText(message, MessageImportance.Normal) |> ignore
member internal fsi.InternalGenerateFullPathToTool() = fsi.GenerateFullPathToTool() // expose for unit testing
member internal fsi.InternalGenerateFullPathToTool() = fsi.GenerateFullPathToTool() // expose for unit testing
member internal _.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands) = // F# does not allow protected members to be captured by lambdas, this is the standard workaround
member internal _.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands) = // F# does not allow protected members to be captured by lambdas, this is the standard workaround
base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
/// Intercept the call to ExecuteTool to handle the host compile case.
......@@ -285,37 +320,65 @@ type public Fsi () as this =
0
else
let host = box fsi.HostObject
match host with
| null -> base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
| _ ->
let invokeCompiler baseCallDelegate =
try
let ret =
(host.GetType()).InvokeMember("Compile", BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.InvokeMethod ||| BindingFlags.Instance, null, host,
[| baseCallDelegate; box (capturedArguments |> List.toArray); box (capturedFilenames |> List.toArray) |],
CultureInfo.InvariantCulture)
(host.GetType())
.InvokeMember(
"Compile",
BindingFlags.Public
||| BindingFlags.NonPublic
||| BindingFlags.InvokeMethod
||| BindingFlags.Instance,
null,
host,
[|
baseCallDelegate
box (capturedArguments |> List.toArray)
box (capturedFilenames |> List.toArray)
|],
CultureInfo.InvariantCulture
)
unbox ret
with
// ok, this is what happens when VS IDE cancels the build, no need to assert, just log the build-canceled error and return -1 to denote task failed
// Do a string compare on the type name to do eliminate a compile time dependency on Microsoft.Build.dll
| :? TargetInvocationException as tie when not (isNull tie.InnerException) && (tie.InnerException).GetType().FullName = "Microsoft.Build.Exceptions.BuildAbortedException" ->
fsi.Log.LogError(tie.InnerException.Message, [| |])
| :? TargetInvocationException as tie when
not (isNull tie.InnerException)
&& (tie.InnerException).GetType().FullName = "Microsoft.Build.Exceptions.BuildAbortedException"
->
fsi.Log.LogError(tie.InnerException.Message, [||])
-1
| _ -> reraise()
| _ -> reraise ()
let baseCallDelegate =
Func<int>(fun () -> fsi.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands))
let baseCallDelegate = Func<int>(fun () -> fsi.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands) )
try
invokeCompiler baseCallDelegate
with e ->
Debug.Assert(false, "HostObject received by Fsi task did not have a Compile method or the compile method threw an exception. "+(e.ToString()))
reraise()
with
| e ->
Debug.Assert(
false,
"HostObject received by Fsi task did not have a Compile method or the compile method threw an exception. "
+ (e.ToString())
)
reraise ()
override _.GenerateCommandLineCommands() =
let builder = new FSharpCommandLineBuilder()
match dotnetFsiCompilerPath with
| Null | "" -> ()
| NonNull dotnetFsiCompilerPath ->
builder.AppendSwitch(dotnetFsiCompilerPath)
| Null
| "" -> ()
| NonNull dotnetFsiCompilerPath -> builder.AppendSwitch(dotnetFsiCompilerPath)
builder.ToString()
override _.GenerateResponseFileCommands() =
......@@ -323,17 +386,13 @@ type public Fsi () as this =
builder.GetCapturedArguments() |> String.concat Environment.NewLine
// expose this to internal components (for nunit testing)
member internal fsi.InternalGenerateCommandLineCommands() =
fsi.GenerateCommandLineCommands()
member internal fsi.InternalGenerateCommandLineCommands() = fsi.GenerateCommandLineCommands()
// expose this to internal components (for nunit testing)
member internal fsi.InternalGenerateResponseFileCommands() =
fsi.GenerateResponseFileCommands()
member internal fsi.InternalGenerateResponseFileCommands() = fsi.GenerateResponseFileCommands()
member internal fsi.InternalExecuteTool(pathToTool, responseFileCommands, commandLineCommands) =
fsi.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
member internal fsi.GetCapturedArguments() = [|
yield! capturedArguments
yield! capturedFilenames
|]
member internal _.GetCapturedArguments() =
[| yield! capturedArguments; yield! capturedFilenames |]
......@@ -2,5 +2,5 @@
namespace Microsoft.FSharp
[<assembly:System.Runtime.CompilerServices.InternalsVisibleTo("VisualFSharp.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293")>]
do()
[<assembly: System.Runtime.CompilerServices.InternalsVisibleTo("VisualFSharp.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293")>]
do ()
......@@ -22,9 +22,10 @@ module Utilities =
/// Copied from msbuild. ItemSpecs are normalized using this method.
/// </summary>
let FixFilePath (path: string) =
if String.IsNullOrEmpty(path) || Path.DirectorySeparatorChar = '\\'
then path
else path.Replace('\\', '/');
if String.IsNullOrEmpty(path) || Path.DirectorySeparatorChar = '\\' then
path
else
path.Replace('\\', '/')
/// <summary>
/// Given a list of SourceRoot items produces a list of the same items with added <c>MappedPath</c> metadata that
......@@ -36,8 +37,8 @@ module Utilities =
/// The <c>MappedPath</c> is either the path (ItemSpec) itself, when <see cref="Deterministic"/> is false,
/// or a calculated deterministic source path (starting with prefix '/_/', '/_1/', etc.), otherwise.
/// </remarks>
type MapSourceRoots () =
inherit Task ()
type MapSourceRoots() =
inherit Task()
static let MappedPath = "MappedPath"
static let SourceControl = "SourceControl"
......@@ -45,6 +46,7 @@ type MapSourceRoots () =
static let ContainingRoot = "ContainingRoot"
static let RevisionId = "RevisionId"
static let SourceLinkUrl = "SourceLinkUrl"
static let knownMetadataNames =
[
SourceControl
......@@ -55,36 +57,44 @@ type MapSourceRoots () =
SourceLinkUrl
]
static let (|NullOrEmpty|HasValue|) (s: string) = if String.IsNullOrEmpty s then NullOrEmpty else HasValue s
static let (|NullOrEmpty|HasValue|) (s: string) =
if String.IsNullOrEmpty s then
NullOrEmpty
else
HasValue s
static let ensureEndsWithSlash (path: string) =
if path.EndsWith "/" then path
else path + "/"
if path.EndsWith "/" then
path
else
path + "/"
static let endsWithDirectorySeparator (path: string) =
if path.Length = 0 then false
if path.Length = 0 then
false
else
let endChar = path.[path.Length - 1]
endChar = Path.DirectorySeparatorChar || endChar = Path.AltDirectorySeparatorChar
endChar = Path.DirectorySeparatorChar
|| endChar = Path.AltDirectorySeparatorChar
static let reportConflictingWellKnownMetadata (log: TaskLoggingHelper) (l: ITaskItem) (r: ITaskItem) =
for name in knownMetadataNames do
match l.GetMetadata name, r.GetMetadata name with
| HasValue lValue, HasValue rValue when lValue <> rValue ->
log.LogWarning(FSBuild.SR.mapSourceRootsContainsDuplicate(r.ItemSpec, name, lValue, rValue))
log.LogWarning(FSBuild.SR.mapSourceRootsContainsDuplicate (r.ItemSpec, name, lValue, rValue))
| _, _ -> ()
static member PerformMapping (log: TaskLoggingHelper) (sourceRoots: ITaskItem []) deterministic =
static member PerformMapping (log: TaskLoggingHelper) (sourceRoots: ITaskItem[]) deterministic =
let mappedSourceRoots = ResizeArray<_>()
let rootByItemSpec = Dictionary<string, ITaskItem>();
let rootByItemSpec = Dictionary<string, ITaskItem>()
for sourceRoot in sourceRoots do
// The SourceRoot is required to have a trailing directory separator.
// We do not append one implicitly as we do not know which separator to append on Windows.
// The usage of SourceRoot might be sensitive to what kind of separator is used (e.g. in SourceLink where it needs
// to match the corresponding separators used in paths given to the compiler).
if not (endsWithDirectorySeparator sourceRoot.ItemSpec)
then
if not (endsWithDirectorySeparator sourceRoot.ItemSpec) then
log.LogError(FSBuild.SR.mapSourceRootsPathMustEndWithSlashOrBackslash sourceRoot.ItemSpec)
match rootByItemSpec.TryGetValue sourceRoot.ItemSpec with
......@@ -95,24 +105,24 @@ type MapSourceRoots () =
rootByItemSpec.[sourceRoot.ItemSpec] <- sourceRoot
mappedSourceRoots.Add sourceRoot
if log.HasLoggedErrors
then None
if log.HasLoggedErrors then
None
else
if deterministic
then
let topLevelMappedPaths = Dictionary<_,_>()
if deterministic then
let topLevelMappedPaths = Dictionary<_, _>()
let setTopLevelMappedPaths isSourceControlled =
let mapNestedRootIfEmpty (root: ITaskItem) =
let localPath = root.ItemSpec
match root.GetMetadata NestedRoot with
| NullOrEmpty ->
// root isn't nested
if topLevelMappedPaths.ContainsKey(localPath)
then
log.LogError(FSBuild.SR.mapSourceRootsContainsDuplicate(localPath, NestedRoot, "", ""));
if topLevelMappedPaths.ContainsKey(localPath) then
log.LogError(FSBuild.SR.mapSourceRootsContainsDuplicate (localPath, NestedRoot, "", ""))
else
let index = topLevelMappedPaths.Count;
let index = topLevelMappedPaths.Count
let mappedPath = "/_" + (if index = 0 then "" else string index) + "/"
topLevelMappedPaths.[localPath] <- mappedPath
root.SetMetadata(MappedPath, mappedPath)
......@@ -130,8 +140,7 @@ type MapSourceRoots () =
// then assign mapped paths to other source-controlled top-level roots:
setTopLevelMappedPaths false
if topLevelMappedPaths.Count = 0
then
if topLevelMappedPaths.Count = 0 then
log.LogError(FSBuild.SR.mapSourceRootsNoTopLevelSourceRoot ())
else
// finally, calculate mapped paths of nested roots:
......@@ -144,18 +153,22 @@ type MapSourceRoots () =
// Since the paths in ItemSpec have backslashes replaced with slashes on non-Windows platforms we need to do the same for ContainingRoot.
match topLevelMappedPaths.TryGetValue(Utilities.FixFilePath(containingRoot)) with
| true, mappedTopLevelPath ->
root.SetMetadata(MappedPath, mappedTopLevelPath + ensureEndsWithSlash(nestedRoot.Replace('\\', '/')));
root.SetMetadata(
MappedPath,
mappedTopLevelPath + ensureEndsWithSlash (nestedRoot.Replace('\\', '/'))
)
| false, _ ->
log.LogError(FSBuild.SR.mapSourceRootsNoSuchTopLevelSourceRoot containingRoot)
| NullOrEmpty ->
log.LogError(FSBuild.SR.mapSourceRootsNoSuchTopLevelSourceRoot "")
| NullOrEmpty -> log.LogError(FSBuild.SR.mapSourceRootsNoSuchTopLevelSourceRoot "")
| NullOrEmpty -> ()
else
for root in mappedSourceRoots do
root.SetMetadata(MappedPath, root.ItemSpec)
if log.HasLoggedErrors then None else Some (mappedSourceRoots.ToArray())
if log.HasLoggedErrors then
None
else
Some(mappedSourceRoots.ToArray())
/// <summary>
/// SourceRoot items with the following optional well-known metadata:
......@@ -166,7 +179,7 @@ type MapSourceRoots () =
/// </list>
/// </summary>
[<Required>]
member val SourceRoots: ITaskItem[] = [| |] with get, set
member val SourceRoots: ITaskItem[] = [||] with get, set
/// <summary>
/// True if the mapped paths should be deterministic.
......@@ -178,12 +191,11 @@ type MapSourceRoots () =
/// Items listed in <see cref="SourceRoots"/> that have the same ItemSpec will be merged into a single item in this list.
/// </summary>
[<Output>]
member val MappedSourceRoots: ITaskItem[] = [| |] with get, set
member val MappedSourceRoots: ITaskItem[] = [||] with get, set
override this.Execute() =
match MapSourceRoots.PerformMapping this.Log this.SourceRoots this.Deterministic with
| None ->
false
| None -> false
| Some mappings ->
this.MappedSourceRoots <- mappings
true
\ No newline at end of file
true
......@@ -3,12 +3,10 @@
namespace FSharp.Build
open System
open System.Collections
open System.IO
open Microsoft.Build.Framework
open Microsoft.Build.Utilities
type SubstituteText () =
type SubstituteText() =
let mutable _buildEngine: IBuildEngine MaybeNull = null
let mutable _hostObject: ITaskHost MaybeNull = null
......@@ -18,25 +16,25 @@ type SubstituteText () =
[<Required>]
member _.EmbeddedResources
with get() = embeddedResources
and set(value) = embeddedResources <- value
with get () = embeddedResources
and set (value) = embeddedResources <- value
[<Output>]
member _.CopiedFiles
with get() = copiedFiles.ToArray()
member _.CopiedFiles = copiedFiles.ToArray()
interface ITask with
member _.BuildEngine
with get() = _buildEngine
and set(value) = _buildEngine <- value
with get () = _buildEngine
and set (value) = _buildEngine <- value
member _.HostObject
with get() = _hostObject
and set(value) = _hostObject <- value
with get () = _hostObject
and set (value) = _hostObject <- value
member _.Execute() =
copiedFiles.Clear()
if not(isNull embeddedResources) then
if not (isNull embeddedResources) then
for item in embeddedResources do
// Update ITaskItem metadata to point to new location
let sourcePath = item.GetMetadata("FullPath")
......@@ -46,7 +44,7 @@ type SubstituteText () =
// Is there any replacement to do?
if not (String.IsNullOrWhiteSpace(pattern1) && String.IsNullOrWhiteSpace(pattern2)) then
if not(String.IsNullOrWhiteSpace(sourcePath)) then
if not (String.IsNullOrWhiteSpace(sourcePath)) then
try
let getTargetPathFrom key =
let md = item.GetMetadata(key)
......@@ -56,12 +54,13 @@ type SubstituteText () =
target
// Copy from the location specified in Identity
let sourcePath=item.GetMetadata("Identity")
let sourcePath = item.GetMetadata("Identity")
// Copy to the location specified in TargetPath unless no TargetPath is provided, then use Identity
let targetPath=
let targetPath =
let identityPath = getTargetPathFrom "Identity"
let intermediateTargetPath = item.GetMetadata("IntermediateTargetPath")
if not (String.IsNullOrWhiteSpace(intermediateTargetPath)) then
let fileName = Path.GetFileName(identityPath)
let target = Path.Combine(intermediateTargetPath, fileName)
......@@ -73,20 +72,24 @@ type SubstituteText () =
// Transform file
let mutable contents = File.ReadAllText(sourcePath)
if not (String.IsNullOrWhiteSpace(pattern1)) then
let replacement = item.GetMetadata("Replacement1")
contents <- contents.Replace(pattern1, replacement)
if not (String.IsNullOrWhiteSpace(pattern2)) then
let replacement = item.GetMetadata("Replacement2")
contents <- contents.Replace(pattern2, replacement)
let directory = Path.GetDirectoryName(targetPath)
if not(Directory.Exists(directory)) then
Directory.CreateDirectory(directory) |>ignore
if not (Directory.Exists(directory)) then
Directory.CreateDirectory(directory) |> ignore
File.WriteAllText(targetPath, contents)
with
| _ -> ()
copiedFiles.Add(item)
true
......@@ -17,59 +17,81 @@ type WriteCodeFragment() =
let mutable _language: string = ""
let mutable _assemblyAttributes: ITaskItem[] = [||]
static let escapeString (str:string) =
let sb = str.ToCharArray() |> Seq.fold (fun (sb:StringBuilder) (c:char) ->
match c with
| '\n' | '\u2028' | '\u2028' -> sb.Append("\\n")
| '\r' -> sb.Append("\\r")
| '\t' -> sb.Append("\\t")
| '\'' -> sb.Append("\\'")
| '\\' -> sb.Append("\\\\")
| '"' -> sb.Append("\\\"")
| '\u0000' -> sb.Append("\\0")
| _ -> sb.Append(c)) (StringBuilder().Append("\""))
static let escapeString (str: string) =
let sb =
str.ToCharArray()
|> Seq.fold
(fun (sb: StringBuilder) (c: char) ->
match c with
| '\n'
| '\u2028'
| '\u2028' -> sb.Append("\\n")
| '\r' -> sb.Append("\\r")
| '\t' -> sb.Append("\\t")
| '\'' -> sb.Append("\\'")
| '\\' -> sb.Append("\\\\")
| '"' -> sb.Append("\\\"")
| '\u0000' -> sb.Append("\\0")
| _ -> sb.Append(c))
(StringBuilder().Append("\""))
sb.Append("\"").ToString()
static member GenerateAttribute (item:ITaskItem, language:string) =
static member GenerateAttribute(item: ITaskItem, language: string) =
let attributeName = item.ItemSpec
let args =
// mimicking the behavior from https://github.com/Microsoft/msbuild/blob/70ce7e9ccb891b63f0859f1f7f0b955693ed3742/src/Tasks/WriteCodeFragment.cs#L355-L415
// Split parameters into unnamed positional (e.g., key is "_Parameter1", etc.) and proper named parameters.
let customMetadata = item.CloneCustomMetadata()
let parameterPairs =
// normalize everything to strings
seq { for entry in customMetadata -> entry :?> DictionaryEntry }
|> Seq.toList
|> List.map (fun entry ->
let key = entry.Key :?> string
let value = match entry.Value with
| null -> "null"
| :? string as value -> escapeString value
| value -> value.ToString()
let value =
match entry.Value with
| null -> "null"
| :? string as value -> escapeString value
| value -> value.ToString()
(key, value))
let orderedParameters, namedParameters = parameterPairs |> List.partition (fun (key, _) -> key.StartsWith("_Parameter"))
let orderedParameters, namedParameters =
parameterPairs |> List.partition (fun (key, _) -> key.StartsWith("_Parameter"))
let orderedParametersWithIndex =
orderedParameters
|> List.map (fun (key, value) ->
let indexString = key.Substring("_Parameter".Length)
match Int32.TryParse indexString with
| (true, index) -> (index, value)
| (false, _) -> failwith (sprintf "Unable to parse '%s' as an index" indexString))
|> List.sortBy fst
// assign ordered parameters to array
let orderedParametersArray =
if List.isEmpty orderedParametersWithIndex then [||]
else Array.create (List.last orderedParametersWithIndex |> fst) "null"
if List.isEmpty orderedParametersWithIndex then
[||]
else
Array.create (List.last orderedParametersWithIndex |> fst) "null"
List.iter (fun (index, value) -> orderedParametersArray.[index - 1] <- value) orderedParametersWithIndex
// construct ordered parameter lists
let combinedOrderedParameters = String.Join(", ", orderedParametersArray)
let combinedNamedParameters = String.Join(", ", List.map (fun (key, value) -> sprintf "%s = %s" key value) namedParameters)
let combinedNamedParameters =
String.Join(", ", List.map (fun (key, value) -> sprintf "%s = %s" key value) namedParameters)
// construct the final argument string; positional arguments followed by named
match (combinedOrderedParameters.Length, combinedNamedParameters.Length) with
| (0, 0) -> "" // no arguments
| (0, _) -> combinedNamedParameters // only named arguments
| (_, 0) -> combinedOrderedParameters // only positional arguments
| (_, _) -> combinedOrderedParameters + ", " + combinedNamedParameters // both positional and named arguments
match language.ToLowerInvariant() with
| "f#" -> sprintf "[<assembly: %s(%s)>]" attributeName args
| "c#" -> sprintf "[assembly: %s(%s)]" attributeName args
......@@ -78,64 +100,73 @@ type WriteCodeFragment() =
// adding this property to maintain API equivalence with the MSBuild task
member _.Language
with get() = _language
and set(value) = _language <- value
with get () = _language
and set (value) = _language <- value
member _.AssemblyAttributes
with get() = _assemblyAttributes
and set(value) = _assemblyAttributes <- value
with get () = _assemblyAttributes
and set (value) = _assemblyAttributes <- value
member _.OutputDirectory
with get() = _outputDirectory
and set(value) = _outputDirectory <- value
with get () = _outputDirectory
and set (value) = _outputDirectory <- value
[<Output>]
member _.OutputFile
with get() = _outputFile
and set(value) = _outputFile <- value
with get () = _outputFile
and set (value) = _outputFile <- value
interface ITask with
member _.BuildEngine
with get() = _buildEngine
and set(value) = _buildEngine <- value
with get () = _buildEngine
and set (value) = _buildEngine <- value
member _.HostObject
with get() = _hostObject
and set(value) = _hostObject <- value
with get () = _hostObject
and set (value) = _hostObject <- value
member _.Execute() =
try
match _outputFile with
match _outputFile with
| Null -> failwith "Output location must be specified"
| NonNull outputFile ->
let boilerplate =
match _language.ToLowerInvariant() with
| "f#" -> "// <auto-generated>\n// Generated by the FSharp WriteCodeFragment class.\n// </auto-generated>\nnamespace FSharp\n\nopen System\nopen System.Reflection\n"
| "c#" -> "// <auto-generated>\n// Generated by the FSharp WriteCodeFragment class.\n// </auto-generated>\n\nusing System;\nusing System.Reflection;"
| "vb" -> "'------------------------------------------------------------------------------\n' <auto-generated>\n' Generated by the FSharp WriteCodeFragment class.\n' </auto-generated>\n'------------------------------------------------------------------------------\n\nOption Strict Off\nOption Explicit On\n\nImports System\nImports System.Reflection"
| _ -> failwith "Language name must be one of F#, C# or VB"
let sb = StringBuilder().AppendLine(boilerplate).AppendLine()
let code = Array.fold (fun (sb:StringBuilder) (item:ITaskItem) -> sb.AppendLine(WriteCodeFragment.GenerateAttribute (item, _language.ToLowerInvariant()))) sb _assemblyAttributes
if _language.ToLowerInvariant() = "f#" then code.AppendLine("do()") |> ignore
let fileName = outputFile.ItemSpec
let outputFileItem =
match _outputDirectory with
| Null -> outputFile
| NonNull outputDirectory ->
if Path.IsPathRooted(fileName) then
outputFile
else
TaskItem(Path.Combine(outputDirectory.ItemSpec, fileName)) :> ITaskItem
let codeText = code.ToString()
File.WriteAllText(fileName, codeText)
_outputFile <- outputFileItem
true
with e ->
| NonNull outputFile ->
let boilerplate =
match _language.ToLowerInvariant() with
| "f#" ->
"// <auto-generated>\n// Generated by the FSharp WriteCodeFragment class.\n// </auto-generated>\nnamespace FSharp\n\nopen System\nopen System.Reflection\n"
| "c#" ->
"// <auto-generated>\n// Generated by the FSharp WriteCodeFragment class.\n// </auto-generated>\n\nusing System;\nusing System.Reflection;"
| "vb" ->
"'------------------------------------------------------------------------------\n' <auto-generated>\n' Generated by the FSharp WriteCodeFragment class.\n' </auto-generated>\n'------------------------------------------------------------------------------\n\nOption Strict Off\nOption Explicit On\n\nImports System\nImports System.Reflection"
| _ -> failwith "Language name must be one of F#, C# or VB"
let sb = StringBuilder().AppendLine(boilerplate).AppendLine()
let code =
(sb, _assemblyAttributes)
||> Array.fold (fun (sb: StringBuilder) (item: ITaskItem) ->
sb.AppendLine(WriteCodeFragment.GenerateAttribute(item, _language.ToLowerInvariant())))
if _language.ToLowerInvariant() = "f#" then
code.AppendLine("do()") |> ignore
let fileName = outputFile.ItemSpec
let outputFileItem =
match _outputDirectory with
| Null -> outputFile
| NonNull outputDirectory ->
if Path.IsPathRooted(fileName) then
outputFile
else
TaskItem(Path.Combine(outputDirectory.ItemSpec, fileName)) :> ITaskItem
let codeText = code.ToString()
File.WriteAllText(fileName, codeText)
_outputFile <- outputFileItem
true
with
| e ->
printf "Error writing code fragment: %s" (e.ToString())
false
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册