Skip to content

Commit 6660b38

Browse files
committed
Export metadata
1 parent fce0cf0 commit 6660b38

File tree

10 files changed

+253
-4
lines changed

10 files changed

+253
-4
lines changed

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@
8787
"justMyCode": true,
8888
"enableStepFiltering": false,
8989
"requireExactSource": false
90+
},
91+
{
92+
"name": "FCS Export",
93+
"type": "coreclr",
94+
"request": "launch",
95+
"program": "${workspaceFolder}/artifacts/bin/fcs-export/Debug/net8.0/fcs-export.dll",
96+
"args": [],
97+
"cwd": "${workspaceFolder}/fcs/fcs-export",
98+
"console": "internalConsole",
99+
"stopAtEntry": false
90100
}
91101
]
92102
}

buildtools/buildtools.targets

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
BeforeTargets="CoreCompile">
2121

2222
<PropertyGroup>
23-
<FsLexPath Condition="'$(FsLexPath)' == ''">$(ArtifactsDir)\Bootstrap\fslex\fslex.dll</FsLexPath>
23+
<FsLexPath Condition="'$(FsLexPath)' == ''">$(ArtifactsDir)\bin\fslex\Release\net8.0\fslex.dll</FsLexPath>
2424
</PropertyGroup>
2525

2626
<!-- Create the output directory -->
@@ -44,7 +44,7 @@
4444
BeforeTargets="CoreCompile">
4545

4646
<PropertyGroup>
47-
<FsYaccPath Condition="'$(FsYaccPath)' == ''">$(ArtifactsDir)\Bootstrap\fsyacc\fsyacc.dll</FsYaccPath>
47+
<FsYaccPath Condition="'$(FsYaccPath)' == ''">$(ArtifactsDir)\bin\fsyacc\Release\net8.0\fsyacc.dll</FsYaccPath>
4848
</PropertyGroup>
4949

5050
<!-- Create the output directory -->

fcs/build.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
3+
dotnet build -c Release buildtools
4+
dotnet build -c Release src/Compiler
5+
dotnet run -c Release --project fcs/fcs-export

fcs/fcs-export/NuGet.config

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<clear />
5+
<add key="NuGet.org" value="https://api.nuget.org/v3/index.json" />
6+
</packageSources>
7+
<disabledPackageSources>
8+
<clear />
9+
</disabledPackageSources>
10+
</configuration>

fcs/fcs-export/Program.fs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
open System.IO
2+
open System.Text.RegularExpressions
3+
open FSharp.Compiler.CodeAnalysis
4+
open Buildalyzer
5+
6+
let getProjectOptionsFromProjectFile (isMain: bool) (projFile: string) =
7+
8+
let tryGetResult (isMain: bool) (manager: AnalyzerManager) (maybeCsprojFile: string) =
9+
10+
let analyzer = manager.GetProject(maybeCsprojFile)
11+
let env = analyzer.EnvironmentFactory.GetBuildEnvironment(Environment.EnvironmentOptions(DesignTime=true,Restore=false))
12+
// If System.the project targets multiple frameworks, multiple results will be returned
13+
// For now we just take the first one with non-empty command
14+
let results = analyzer.Build(env)
15+
results
16+
|> Seq.tryFind (fun r -> System.String.IsNullOrEmpty(r.Command) |> not)
17+
18+
let manager =
19+
let log = new StringWriter()
20+
let options = AnalyzerManagerOptions(LogWriter = log)
21+
let m = AnalyzerManager(options)
22+
m
23+
24+
// Because Buildalyzer works better with .csproj, we first "dress up" the project as if it were a C# one
25+
// and try to adapt the results. If it doesn't work, we try again to analyze the .fsproj directly
26+
let csprojResult =
27+
let csprojFile = projFile.Replace(".fsproj", ".csproj")
28+
if File.Exists(csprojFile) then
29+
None
30+
else
31+
try
32+
File.Copy(projFile, csprojFile)
33+
tryGetResult isMain manager csprojFile
34+
|> Option.map (fun (r: IAnalyzerResult) ->
35+
// Careful, options for .csproj start with / but so do root paths in unix
36+
let reg = Regex(@"^\/[^\/]+?(:?:|$)")
37+
let comArgs =
38+
r.CompilerArguments
39+
|> Array.map (fun line ->
40+
if reg.IsMatch(line) then
41+
if line.StartsWith("/reference") then "-r" + line.Substring(10)
42+
else "--" + line.Substring(1)
43+
else line)
44+
let comArgs =
45+
match r.Properties.TryGetValue("OtherFlags") with
46+
| false, _ -> comArgs
47+
| true, otherFlags ->
48+
let otherFlags = otherFlags.Split(' ', System.StringSplitOptions.RemoveEmptyEntries)
49+
Array.append otherFlags comArgs
50+
comArgs, r)
51+
finally
52+
File.Delete(csprojFile)
53+
54+
let compilerArgs, result =
55+
csprojResult
56+
|> Option.orElseWith (fun () ->
57+
tryGetResult isMain manager projFile
58+
|> Option.map (fun r ->
59+
// result.CompilerArguments doesn't seem to work well in Linux
60+
let comArgs = Regex.Split(r.Command, @"\r?\n")
61+
comArgs, r))
62+
|> function
63+
| Some result -> result
64+
// TODO: Get Buildalyzer errors from the log
65+
| None -> failwith $"Cannot parse {projFile}"
66+
67+
let projDir = Path.GetDirectoryName(projFile)
68+
let projOpts =
69+
compilerArgs
70+
|> Array.skipWhile (fun line -> not(line.StartsWith("-")))
71+
|> Array.map (fun f ->
72+
if f.EndsWith(".fs") || f.EndsWith(".fsi") then
73+
if Path.IsPathRooted f then f else Path.Combine(projDir, f)
74+
else f)
75+
projOpts,
76+
Seq.toArray result.ProjectReferences,
77+
result.Properties,
78+
result.TargetFramework
79+
80+
let mkStandardProjectReferences () =
81+
let file = "fcs-export.fsproj"
82+
let projDir = __SOURCE_DIRECTORY__
83+
let projFile = Path.Combine(projDir, file)
84+
let (args, _, _, _) = getProjectOptionsFromProjectFile true projFile
85+
args
86+
|> Array.filter (fun s -> s.StartsWith("-r:"))
87+
88+
let mkProjectCommandLineArgsForScript (dllName, fileNames) =
89+
[| yield "--simpleresolution"
90+
yield "--noframework"
91+
yield "--debug:full"
92+
yield "--define:DEBUG"
93+
yield "--targetprofile:netcore"
94+
yield "--optimize-"
95+
yield "--out:" + dllName
96+
yield "--doc:test.xml"
97+
yield "--warn:3"
98+
yield "--fullpaths"
99+
yield "--flaterrors"
100+
yield "--target:library"
101+
for x in fileNames do
102+
yield x
103+
let references = mkStandardProjectReferences ()
104+
for r in references do
105+
yield r
106+
|]
107+
108+
let checker = FSharpChecker.Create()
109+
110+
let parseAndCheckScript (file, input) =
111+
let dllName = Path.ChangeExtension(file, ".dll")
112+
let projName = Path.ChangeExtension(file, ".fsproj")
113+
let args = mkProjectCommandLineArgsForScript (dllName, [file])
114+
printfn "file: %s" file
115+
args |> Array.iter (printfn "args: %s")
116+
let projectOptions = checker.GetProjectOptionsFromCommandLineArgs (projName, args)
117+
let parseRes, typedRes = checker.ParseAndCheckFileInProject(file, 0, input, projectOptions) |> Async.RunSynchronously
118+
119+
if parseRes.Diagnostics.Length > 0 then
120+
printfn "---> Parse Input = %A" input
121+
printfn "---> Parse Error = %A" parseRes.Diagnostics
122+
123+
match typedRes with
124+
| FSharpCheckFileAnswer.Succeeded(res) -> parseRes, res
125+
| res -> failwithf "Parsing did not finish... (%A)" res
126+
127+
[<EntryPoint>]
128+
let main argv =
129+
ignore argv
130+
printfn "Exporting metadata..."
131+
let file = "/temp/test.fsx"
132+
let input = "let a = 42"
133+
let sourceText = FSharp.Compiler.Text.SourceText.ofString input
134+
// parse script just to export metadata
135+
let parseRes, typedRes = parseAndCheckScript(file, sourceText)
136+
printfn "Exporting is done. Binaries can be found in ./temp/metadata/"
137+
0

fcs/fcs-export/fcs-export.fsproj

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<Compile Include="Program.fs" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<!-- <ProjectReference Include="../../src/Compiler/FSharp.Compiler.Service.fsproj" /> -->
15+
<!-- <ProjectReference Include="../../src/Compiler/FSharp.Core/FSharp.Core.fsproj" /> -->
16+
<Reference Include="../../artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Core.dll" />
17+
<Reference Include="../../artifacts/bin/FSharp.Compiler.Service/Release/netstandard2.0/FSharp.Compiler.Service.dll" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<!-- <PackageReference Include="FSharp.Core" Version="8.0.0" /> -->
22+
<PackageReference Include="Buildalyzer" Version="5.0.1" />
23+
<PackageReference Include="Fable.Core" Version="4.2.0" />
24+
<PackageReference Include="Fable.Browser.Blob" Version="*" />
25+
<PackageReference Include="Fable.Browser.Dom" Version="*" />
26+
<PackageReference Include="Fable.Browser.Event" Version="*" />
27+
<PackageReference Include="Fable.Browser.Gamepad" Version="*" />
28+
<PackageReference Include="Fable.Browser.WebGL" Version="*" />
29+
<PackageReference Include="Fable.Browser.WebStorage" Version="*" />
30+
</ItemGroup>
31+
</Project>

global.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"sdk": {
3-
"version": "8.0.100-rc.1.23463.5",
3+
"version": "8.0.100",
44
"allowPrerelease": true
55
},
66
"tools": {
7-
"dotnet": "8.0.100-rc.1.23463.5",
7+
"dotnet": "8.0.100",
88
"vs": {
99
"version": "17.6",
1010
"components": [

src/Compiler/AbstractIL/ilwrite.fs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
module internal FSharp.Compiler.AbstractIL.ILBinaryWriter
44

5+
#if EXPORT_METADATA
6+
#nowarn "1182"
7+
#endif
8+
59
open System
610
open System.Collections.Generic
711
open System.IO
@@ -1102,9 +1106,11 @@ let FindMethodDefIdx cenv mdkey =
11021106
else sofar) None) with
11031107
| Some x -> x
11041108
| None -> raise MethodDefNotFound
1109+
#if !EXPORT_METADATA
11051110
let (TdKey (tenc, tname)) = typeNameOfIdx mdkey.TypeIdx
11061111
dprintn ("The local method '"+(String.concat "." (tenc@[tname]))+"'::'"+mdkey.Name+"' was referenced but not declared")
11071112
dprintn ("generic arity: "+string mdkey.GenericArity)
1113+
#endif
11081114
cenv.methodDefIdxsByKey.dict |> Seq.iter (fun (KeyValue(mdkey2, _)) ->
11091115
if mdkey2.TypeIdx = mdkey.TypeIdx && mdkey.Name = mdkey2.Name then
11101116
let (TdKey (tenc2, tname2)) = typeNameOfIdx mdkey2.TypeIdx
@@ -2617,6 +2623,9 @@ let GenMethodDefAsRow cenv env midx (mdef: ILMethodDef) =
26172623
if cenv.entrypoint <> None then failwith "duplicate entrypoint"
26182624
else cenv.entrypoint <- Some (true, midx)
26192625
let codeAddr =
2626+
#if EXPORT_METADATA
2627+
0x0000
2628+
#else
26202629
(match mdef.Body with
26212630
| MethodBody.IL ilmbodyLazy ->
26222631
let ilmbody =
@@ -2667,6 +2676,7 @@ let GenMethodDefAsRow cenv env midx (mdef: ILMethodDef) =
26672676
| MethodBody.Native ->
26682677
failwith "cannot write body of native method - Abstract IL cannot roundtrip mixed native/managed binaries"
26692678
| _ -> 0x0000)
2679+
#endif
26702680

26712681
UnsharedRow
26722682
[| ULong codeAddr
@@ -3823,6 +3833,7 @@ let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRe
38233833
match options.signer, modul.Manifest with
38243834
| Some _, _ -> options.signer
38253835
| _, None -> options.signer
3836+
#if !EXPORT_METADATA
38263837
| None, Some {PublicKey=Some pubkey} ->
38273838
(dprintn "Note: The output assembly will be delay-signed using the original public"
38283839
dprintn "Note: key. In order to load it you will need to either sign it with"
@@ -3832,6 +3843,7 @@ let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRe
38323843
dprintn "Note: private key when converting the assembly, assuming you have access to"
38333844
dprintn "Note: it."
38343845
Some (ILStrongNameSigner.OpenPublicKey pubkey))
3846+
#endif
38353847
| _ -> options.signer
38363848

38373849
let modul =
@@ -3843,11 +3855,13 @@ let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRe
38433855
with exn ->
38443856
failwith ("A call to StrongNameGetPublicKey failed (" + exn.Message + ")")
38453857
None
3858+
#if !EXPORT_METADATA
38463859
match modul.Manifest with
38473860
| None -> ()
38483861
| Some m ->
38493862
if m.PublicKey <> None && m.PublicKey <> pubkey then
38503863
dprintn "Warning: The output assembly is being signed or delay-signed with a strong name that is different to the original."
3864+
#endif
38513865
{ modul with Manifest = match modul.Manifest with None -> None | Some m -> Some {m with PublicKey = pubkey} }
38523866

38533867
let pdbData, pdbInfoOpt, debugDirectoryChunk, debugDataChunk, debugChecksumPdbChunk, debugEmbeddedPdbChunk, debugDeterministicPdbChunk, textV2P, mappings =

src/Compiler/Driver/CompilerImports.fs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,6 +2507,47 @@ and [<Sealed>] TcImports
25072507
global_g <- Some tcGlobals
25082508
#endif
25092509
frameworkTcImports.SetTcGlobals tcGlobals
2510+
2511+
#if EXPORT_METADATA
2512+
let metadataPath = __SOURCE_DIRECTORY__ + "/../../../temp/metadata/"
2513+
let writeMetadata (dllInfo: ImportedBinary) =
2514+
let outfile = Path.GetFullPath(metadataPath + Path.GetFileName(dllInfo.FileName))
2515+
let ilModule = dllInfo.RawMetadata.TryGetILModuleDef().Value
2516+
try
2517+
let args: AbstractIL.ILBinaryWriter.options = {
2518+
ilg = tcGlobals.ilg
2519+
outfile = outfile
2520+
pdbfile = None //pdbfile
2521+
emitTailcalls = tcConfig.emitTailcalls
2522+
deterministic = tcConfig.deterministic
2523+
portablePDB = tcConfig.portablePDB
2524+
embeddedPDB = tcConfig.embeddedPDB
2525+
embedAllSource = tcConfig.embedAllSource
2526+
embedSourceList = tcConfig.embedSourceList
2527+
allGivenSources = [] //ilSourceDocs
2528+
sourceLink = tcConfig.sourceLink
2529+
checksumAlgorithm = tcConfig.checksumAlgorithm
2530+
signer = None //GetStrongNameSigner signingInfo
2531+
dumpDebugInfo = tcConfig.dumpDebugInfo
2532+
referenceAssemblyOnly = false
2533+
referenceAssemblyAttribOpt = None
2534+
referenceAssemblySignatureHash = None
2535+
pathMap = tcConfig.pathMap
2536+
}
2537+
AbstractIL.ILBinaryWriter.WriteILBinaryFile (args, ilModule, id)
2538+
with Failure msg ->
2539+
printfn "Export error: %s" msg
2540+
2541+
_assemblies
2542+
|> List.iter (function
2543+
| ResolvedImportedAssembly (asm, m) ->
2544+
let ilShortAssemName = getNameOfScopeRef asm.ILScopeRef
2545+
let dllInfo = frameworkTcImports.FindDllInfo(ctok, m, ilShortAssemName)
2546+
writeMetadata dllInfo
2547+
| UnresolvedImportedAssembly (_assemblyName, _m) -> ()
2548+
)
2549+
#endif
2550+
25102551
return tcGlobals, frameworkTcImports
25112552
}
25122553

src/Compiler/FSharp.Compiler.Service.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<NoWarn>$(NoWarn);NU5125</NoWarn>
1313
<AssemblyName>FSharp.Compiler.Service</AssemblyName>
1414
<AllowCrossTargeting>true</AllowCrossTargeting>
15+
<DefineConstants>$(DefineConstants);EXPORT_METADATA</DefineConstants>
1516
<DefineConstants>$(DefineConstants);COMPILER</DefineConstants>
1617
<DefineConstants Condition="'$(FSHARPCORE_USE_PACKAGE)' == 'true'">$(DefineConstants);FSHARPCORE_USE_PACKAGE</DefineConstants>
1718
<OtherFlags>$(OtherFlags) --extraoptimizationloops:1</OtherFlags>

0 commit comments

Comments
 (0)