-
Notifications
You must be signed in to change notification settings - Fork 790
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generate source for .resx files on build. (#3607)
* add build task to generate *.fs from *.resx files * generate source for embedded resources in tests * generate source for embedded resources in FSharp.Editor * generate source for embedded resources in FSharp.LanguageService * generate source for embedded resources in FSharp.ProjectSystem.FSharp * generate source for embedded resources in FSharp.VS.FSI * don't generate non-string resources when <=netstandard1.6 * update baseline error message for tests The error output should be the exception message, not the exception type. * perform up-to-date check before generating *.fs from *.resx * remove non-idiomatic fold for an array comprehension * correct newline replacement * output more friendly error message * throw if boolean value isn't explicitly `true` or `false` * only generate object resource code on non `netstandard1.*` and `netcoreapp1.*` platforms * ensure FSharp.Core specifies a target framework for resource generaton * rename attributes to be non-ambiguous and properly include them
- Loading branch information
Showing
48 changed files
with
400 additions
and
442 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.FSharp.Build | ||
|
||
open System | ||
open System.Collections | ||
open System.Globalization | ||
open System.IO | ||
open System.Linq | ||
open System.Text | ||
open System.Xml.Linq | ||
open Microsoft.Build.Framework | ||
open Microsoft.Build.Utilities | ||
|
||
type FSharpEmbedResXSource() = | ||
let mutable _buildEngine : IBuildEngine = null | ||
let mutable _hostObject : ITaskHost = null | ||
let mutable _embeddedText : ITaskItem[] = [||] | ||
let mutable _generatedSource : ITaskItem[] = [||] | ||
let mutable _outputPath : string = "" | ||
let mutable _targetFramework : string = "" | ||
|
||
let boilerplate = @"// <auto-generated> | ||
namespace {0} | ||
open System.Reflection | ||
module internal {1} = | ||
type private C (_dummy:System.Object) = class end | ||
let mutable Culture = System.Globalization.CultureInfo.CurrentUICulture | ||
let ResourceManager = new System.Resources.ResourceManager(""{2}"", C(null).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 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.GetLastWriteTime(resx) <= File.GetLastWriteTime(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.")) | ||
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 | ||
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 = match node.Attribute(xname "type") with | ||
| null -> true | ||
| _ -> false | ||
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 -> | ||
printf "An exception occurred when processing '%s'\n%s" resx (e.ToString()) | ||
None | ||
|
||
[<Required>] | ||
member this.EmbeddedResource | ||
with get() = _embeddedText | ||
and set(value) = _embeddedText <- value | ||
|
||
[<Required>] | ||
member this.IntermediateOutputPath | ||
with get() = _outputPath | ||
and set(value) = _outputPath <- value | ||
|
||
member this.TargetFramework | ||
with get() = _targetFramework | ||
and set(value) = _targetFramework <- value | ||
|
||
[<Output>] | ||
member this.GeneratedSource | ||
with get() = _generatedSource | ||
|
||
interface ITask with | ||
member this.BuildEngine | ||
with get() = _buildEngine | ||
and set(value) = _buildEngine <- value | ||
member this.HostObject | ||
with get() = _hostObject | ||
and set(value) = _hostObject <- value | ||
member this.Execute() = | ||
let getBooleanMetadata (metadataName:string) (defaultValue:bool) (item:ITaskItem) = | ||
match item.GetMetadata(metadataName) with | ||
| value when String.IsNullOrWhiteSpace(value) -> defaultValue | ||
| value -> | ||
match value.ToLowerInvariant() with | ||
| "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 | ||
|] | ||
_generatedSource <- generatedSource | ||
success |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.