Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate source for .resx files on build. #3607

Merged
merged 16 commits into from
Sep 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/FSharpSource.Profiles.targets
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
<DefineConstants>$(DefineConstants);FX_RESHAPED_MSBUILD</DefineConstants>
<DefineConstants>$(DefineConstants);FSI_TODO_NETCORE</DefineConstants>
<OtherFlags>$(OtherFlags) --simpleresolution</OtherFlags>
<TargetFramework Condition="'$(TargetFramework)' == ''">netstandard1.6</TargetFramework>
</PropertyGroup>

</Project>
20 changes: 17 additions & 3 deletions src/FSharpSource.targets
Original file line number Diff line number Diff line change
Expand Up @@ -360,22 +360,36 @@
<UsingTask TaskName="FSharpEmbedResourceText"
AssemblyFile="$(FSharpSourcesRoot)\..\Proto\net40\bin\FSharp.Build-proto.dll"
Condition="'$(Configuration)' != 'Proto'" />
<UsingTask TaskName="FSharpEmbedResXSource"
AssemblyFile="$(FSharpSourcesRoot)\..\Proto\net40\bin\FSharp.Build-proto.dll"
Condition="'$(Configuration)' != 'Proto'" />

<Target Name="GenerateFSharpTextResources"
BeforeTargets="CoreResGen;PrepareForBuild"
Condition="'$(Configuration)' != 'Proto'">

<MakeDir Directories="$(IntermediateOutputPath)" />

<!-- Generate source for all resx files. -->
<FSharpEmbedResXSource EmbeddedResource="@(EmbeddedResource)" IntermediateOutputPath="$(IntermediateOutputPath)" TargetFramework="$(TargetFramework)">
<Output TaskParameter="GeneratedSource" ItemName="_FsGeneratedResXSource" />
</FSharpEmbedResXSource>

<ItemGroup>
<CompileBefore Include="@(_FsGeneratedResXSource)" />
<FileWrites Include="@(_FsGeneratedResXSource)" />
</ItemGroup>

<!-- Generate resx and source for all txt files. -->
<FSharpEmbedResourceText EmbeddedText="@(EmbeddedText)" IntermediateOutputPath="$(IntermediateOutputPath)">
<Output TaskParameter="GeneratedSource" ItemName="_FsGeneratedSource" />
<Output TaskParameter="GeneratedSource" ItemName="_FsGeneratedTxtSource" />
<Output TaskParameter="GeneratedResx" ItemName="_FsGeneratedResx" />
</FSharpEmbedResourceText>

<ItemGroup>
<CompileBefore Include="@(_FsGeneratedSource)" />
<CompileBefore Include="@(_FsGeneratedTxtSource)" />
<EmbeddedResource Include="@(_FsGeneratedResx)" />
<FileWrites Include="@(_FsGeneratedSource)" />
<FileWrites Include="@(_FsGeneratedTxtSource)" />
<FileWrites Include="@(_FsGeneratedResx)" />
</ItemGroup>

Expand Down
4 changes: 4 additions & 0 deletions src/fsharp/FSharp.Build-proto/FSharp.Build-proto.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
<Compile Include="..\FSharp.Build\FSharpEmbedResourceText.fs">
<Link>FSharpEmbedResourceText.fs</Link>
</Compile>
<Compile Include="..\FSharp.Build\FSharpEmbedResXSource.fs">
<Link>FSharpEmbedResXSource.fs</Link>
</Compile>
<CopyAndSubstituteText Include="..\FSharp.Build\Microsoft.FSharp.Targets">
<Link>Microsoft.FSharp-proto.targets</Link>
<TargetFilename>Microsoft.FSharp-proto.targets</TargetFilename>
Expand Down Expand Up @@ -67,6 +70,7 @@
<Reference Include="System" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetDotnetProfile)' != 'coreclr' AND '$(MonoPackaging)' != 'true' ">
<Reference Include="Microsoft.Build.Framework, Version=$(VisualStudioVersion).0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Compile Include="Fsc.fsi" />
<Compile Include="Fsc.fs" />
<Compile Include="FSharpEmbedResourceText.fs" />
<Compile Include="FSharpEmbedResXSource.fs" />
<Compile Include="CreateFSharpManifestResourceName.fsi" />
<Compile Include="CreateFSharpManifestResourceName.fs" />
<CopyAndSubstituteText Include="Microsoft.FSharp.Targets">
Expand Down
2 changes: 2 additions & 0 deletions src/fsharp/FSharp.Build/FSharp.Build.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<Compile Include="Fsc.fsi" />
<Compile Include="Fsc.fs" />
<Compile Include="FSharpEmbedResourceText.fs" />
<Compile Include="FSharpEmbedResXSource.fs" />
<Compile Include="CreateFSharpManifestResourceName.fsi" />
<Compile Include="CreateFSharpManifestResourceName.fs" />
<CopyAndSubstituteText Include="Microsoft.FSharp.Targets">
Expand Down Expand Up @@ -59,6 +60,7 @@
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetDotnetProfile)' != 'coreclr' AND '$(MonoPackaging)' != 'true' ">
<Reference Include="Microsoft.Build.Framework, Version=$(VisualStudioVersion).0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
Expand Down
146 changes: 146 additions & 0 deletions src/fsharp/FSharp.Build/FSharpEmbedResXSource.fs
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be replaced with Assembly.GetCallingAssembly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the current executing assembly that is interesting not the calling assembly. This irritating code is the magic formulation because GetExecutingAssembly() didn\t make it into coreclr until V2.0, and the buildtask is compiled for both the desktop and coreclr.

Copy link
Contributor

@saul saul Sep 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which case can we make the C class simpler:

type private Dummy = interface end
...
let ResourceManager = new System.Resources.ResourceManager(""{2}"", typeof<Dummy>.Assembly)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saul sure.

Copy link
Member Author

@brettfo brettfo Sep 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't use typeof<> because this generator is used by FSharp.Core and at the point that we need these strings is before the typeof<> operator can be defined (i.e., in prim-types.fs/fsi). I could do a custom switch on FSharp.Core, but that seemed like a nasty hack given that the hack already there will always work.

Edit: and the .GetType().GetTypeInfo() method works in both desktop and CoreCLR scenarios and I'd rather have one method that always works than switching on platform for something that doesn't matter to the end user.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brettfo thanks for clearing that up, I couldn't for the life of me remember why we did it this way.

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
18 changes: 15 additions & 3 deletions src/fsharp/FSharp.Build/Microsoft.FSharp.Targets
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ this file.

<UsingTask TaskName="Fsc" AssemblyFile="FSharp.Build{BuildSuffix}.dll"/>
<UsingTask TaskName="FSharpEmbedResourceText" AssemblyFile="FSharp.Build{BuildSuffix}.dll"/>
<UsingTask TaskName="FSharpEmbedResXSource" AssemblyFile="FSharp.Build{BuildSuffix}.dll"/>
<UsingTask TaskName="CreateFSharpManifestResourceName" AssemblyFile="FSharp.Build{BuildSuffix}.dll"/>

<PropertyGroup>
Expand Down Expand Up @@ -175,15 +176,26 @@ this file.

<MakeDir Directories="$(IntermediateOutputPath)" />

<!-- Generate source for all resx files. -->
<FSharpEmbedResXSource EmbeddedResource="@(EmbeddedResource)" IntermediateOutputPath="$(IntermediateOutputPath)" TargetFramework="$(TargetFramework)">
<Output TaskParameter="GeneratedSource" ItemName="_FsGeneratedResXSource" />
</FSharpEmbedResXSource>

<ItemGroup>
<CompileBefore Include="@(_FsGeneratedResXSource)" />
<FileWrites Include="@(_FsGeneratedResXSource)" />
</ItemGroup>

<!-- Generate resx and source for all txt files. -->
<FSharpEmbedResourceText EmbeddedText="@(EmbeddedText)" IntermediateOutputPath="$(IntermediateOutputPath)">
<Output TaskParameter="GeneratedSource" ItemName="_FsGeneratedSource" />
<Output TaskParameter="GeneratedSource" ItemName="_FsGeneratedTxtSource" />
<Output TaskParameter="GeneratedResx" ItemName="_FsGeneratedResx" />
</FSharpEmbedResourceText>

<ItemGroup>
<CompileBefore Include="@(_FsGeneratedSource)" />
<CompileBefore Include="@(_FsGeneratedTxtSource)" />
<EmbeddedResource Include="@(_FsGeneratedResx)" />
<FileWrites Include="@(_FsGeneratedSource)" />
<FileWrites Include="@(_FsGeneratedTxtSource)" />
<FileWrites Include="@(_FsGeneratedResx)" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/fsharp/FSharp.Core/FSCore.resx
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@
<data name="QtypeArgumentOutOfRange" xml:space="preserve">
<value>type argument out of range</value>
</data>
<data name="ThisValueCannotBeMutated" xml:space="preserve">
<data name="thisValueCannotBeMutated" xml:space="preserve">
<value>This value cannot be mutated</value>
</data>
<data name="optionValueWasNone" xml:space="preserve">
Expand Down
18 changes: 11 additions & 7 deletions src/fsharp/FSharp.Core/FSharp.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,20 @@
</ItemGroup>

<ItemGroup>
<CompileBefore Include="prim-types-prelude.fsi">
<Link>Primitives/prim-types-prelude.fsi</Link>
</CompileBefore>
<CompileBefore Include="prim-types-prelude.fs">
<Link>Primitives/prim-types-prelude.fs</Link>
</CompileBefore>
<EmbeddedResource Include="FSCore.resx">
<GenerateSource>true</GenerateSource>
<GenerateLegacyCode>true</GenerateLegacyCode>
<GenerateLiterals>false</GenerateLiterals>
<GeneratedModuleName>Microsoft.FSharp.Core.SR</GeneratedModuleName>
<Link>FSCore.resx</Link>
</EmbeddedResource>
<Compile Include="prim-types-prelude.fsi">
<Link>Primitives/prim-types-prelude.fsi</Link>
</Compile>
<Compile Include="prim-types-prelude.fs">
<Link>Primitives/prim-types-prelude.fs</Link>
</Compile>
<Compile Include="SR.fs">
<Compile Include="SR.fs" Condition="'$(Configuration)' == 'Proto'">
<Link>Primitives/SR.fs</Link>
</Compile>
<Compile Include="prim-types.fsi">
Expand Down
24 changes: 12 additions & 12 deletions src/fsharp/FSharp.Core/Query.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1418,41 +1418,41 @@ module Query =
TransInnerResult.Source(expr), NoConv

| Call (_, meth, _) when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryCall,meth.ToString())))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryCall),meth.ToString())))

| PropertyGet (_, pinfo, _) when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryProperty,pinfo.ToString())))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryProperty),pinfo.ToString())))

| NewObject(ty,_) when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstructKind,"new " + ty.ToString())))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstructKind),"new " + ty.ToString())))

| NewArray(ty,_) when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstructKind,"NewArray(" + ty.Name + ",...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstructKind),"NewArray(" + ty.Name + ",...)")))

| NewTuple _ when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstructKind,"NewTuple(...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstructKind),"NewTuple(...)")))

| FieldGet (_,field) when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstructKind,"FieldGet(" + field.Name + ",...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstructKind),"FieldGet(" + field.Name + ",...)")))

| LetRecursive _ when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstruct,"LetRecursive(...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstruct),"LetRecursive(...)")))

| NewRecord _ when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstruct,"NewRecord(...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstruct),"NewRecord(...)")))

| NewDelegate _ when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstruct,"NewDelegate(...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstruct),"NewDelegate(...)")))

| NewTuple _ when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstruct,"NewTuple(...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstruct),"NewTuple(...)")))

| NewUnionCase (ucase,_) when check ->
raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstruct,"NewUnionCase(" + ucase.Name + "...)")))
raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstruct),"NewUnionCase(" + ucase.Name + "...)")))

// Error cases
| e ->
if check then raise (NotSupportedException (SR.GetString1(SR.unsupportedQueryConstruct,immutQuery.ToString())))
if check then raise (NotSupportedException (String.Format(SR.GetString(SR.unsupportedQueryConstruct),immutQuery.ToString())))
else TransInnerResult.Source(e),NoConv


Expand Down
7 changes: 0 additions & 7 deletions src/fsharp/FSharp.Core/SR.fs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,3 @@ module internal SR =

let GetString(name:System.String) : System.String =
resources.GetString(name, System.Globalization.CultureInfo.CurrentUICulture)
let GetString1(name:System.String, arg1:System.String) : System.String =
System.String.Format(resources.GetString(name, System.Globalization.CultureInfo.CurrentUICulture), arg1)
let GetString2(name:System.String, arg1:System.String, arg2:System.String) : System.String =
System.String.Format(resources.GetString(name, System.Globalization.CultureInfo.CurrentUICulture), arg1, arg2)
let GetString3(name:System.String, arg1:System.String, arg2:System.String, arg3:System.String) : System.String =
System.String.Format(resources.GetString(name, System.Globalization.CultureInfo.CurrentUICulture), arg1, arg2, arg3)

Loading