Skip to content

Commit 92bd7d5

Browse files
committed
Export metadata
1 parent 3dff60a commit 92bd7d5

File tree

9 files changed

+265
-2
lines changed

9 files changed

+265
-2
lines changed

.vscode/launch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@
9292
"enableStepFiltering": false,
9393
"requireExactSource": false,
9494
"allowFastEvaluate": true
95+
},
96+
{
97+
"name": "FCS Export",
98+
"type": "coreclr",
99+
"request": "launch",
100+
"program": "${workspaceFolder}/artifacts/bin/fcs-export/Debug/net8.0/fcs-export.dll",
101+
"args": [],
102+
"cwd": "${workspaceFolder}/fcs/fcs-export",
103+
"console": "internalConsole",
104+
"stopAtEntry": false
95105
}
96106
]
97107
}

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\net9.0\linux-x64\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\net9.0\linux-x64\fsyacc.dll</FsYaccPath>
4848
</PropertyGroup>
4949

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

fcs/build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
3+
dotnet build -c Release buildtools/fslex
4+
dotnet build -c Release buildtools/fsyacc
5+
dotnet build -c Release src/Compiler
6+
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>net9.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="9.0.0" /> -->
22+
<PackageReference Include="Buildalyzer" Version="*" />
23+
<PackageReference Include="Fable.Core" Version="*" />
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>

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
@@ -1122,9 +1126,11 @@ let FindMethodDefIdx cenv mdkey =
11221126
else sofar) None) with
11231127
| Some x -> x
11241128
| None -> raise MethodDefNotFound
1129+
#if !EXPORT_METADATA
11251130
let (TdKey (tenc, tname)) = typeNameOfIdx mdkey.TypeIdx
11261131
dprintn ("The local method '"+(String.concat "." (tenc@[tname]))+"'::'"+mdkey.Name+"' was referenced but not declared")
11271132
dprintn ("generic arity: "+string mdkey.GenericArity)
1133+
#endif
11281134
cenv.methodDefIdxsByKey.dict |> Seq.iter (fun (KeyValue(mdkey2, _)) ->
11291135
if mdkey2.TypeIdx = mdkey.TypeIdx && mdkey.Name = mdkey2.Name then
11301136
let (TdKey (tenc2, tname2)) = typeNameOfIdx mdkey2.TypeIdx
@@ -2639,6 +2645,9 @@ let GenMethodDefAsRow cenv env midx (mdef: ILMethodDef) =
26392645
if cenv.entrypoint <> None then failwith "duplicate entrypoint"
26402646
else cenv.entrypoint <- Some (true, midx)
26412647
let codeAddr =
2648+
#if EXPORT_METADATA
2649+
0x0000
2650+
#else
26422651
(match mdef.Body with
26432652
| MethodBody.IL ilmbodyLazy ->
26442653
let ilmbody =
@@ -2689,6 +2698,7 @@ let GenMethodDefAsRow cenv env midx (mdef: ILMethodDef) =
26892698
| MethodBody.Native ->
26902699
failwith "cannot write body of native method - Abstract IL cannot roundtrip mixed native/managed binaries"
26912700
| _ -> 0x0000)
2701+
#endif
26922702

26932703
UnsharedRow
26942704
[| ULong codeAddr
@@ -3851,6 +3861,7 @@ let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRe
38513861
match options.signer, modul.Manifest with
38523862
| Some _, _ -> options.signer
38533863
| _, None -> options.signer
3864+
#if !EXPORT_METADATA
38543865
| None, Some {PublicKey=Some pubkey} ->
38553866
(dprintn "Note: The output assembly will be delay-signed using the original public"
38563867
dprintn "Note: key. In order to load it you will need to either sign it with"
@@ -3860,6 +3871,7 @@ let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRe
38603871
dprintn "Note: private key when converting the assembly, assuming you have access to"
38613872
dprintn "Note: it."
38623873
Some (ILStrongNameSigner.OpenPublicKey pubkey))
3874+
#endif
38633875
| _ -> options.signer
38643876

38653877
let modul =
@@ -3871,11 +3883,13 @@ let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRe
38713883
with exn ->
38723884
failwith ("A call to StrongNameGetPublicKey failed (" + exn.Message + ")")
38733885
None
3886+
#if !EXPORT_METADATA
38743887
match modul.Manifest with
38753888
| None -> ()
38763889
| Some m ->
38773890
if m.PublicKey <> None && m.PublicKey <> pubkey then
38783891
dprintn "Warning: The output assembly is being signed or delay-signed with a strong name that is different to the original."
3892+
#endif
38793893
{ modul with Manifest = match modul.Manifest with None -> None | Some m -> Some {m with PublicKey = pubkey} }
38803894

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

src/Compiler/Driver/CompilerImports.fs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2623,6 +2623,60 @@ and [<Sealed>] TcImports
26232623
global_g <- Some tcGlobals
26242624
#endif
26252625
frameworkTcImports.SetTcGlobals tcGlobals
2626+
2627+
#if EXPORT_METADATA
2628+
let metadataPath = __SOURCE_DIRECTORY__ + "/../../../temp/metadata/"
2629+
let writeMetadata (dllInfo: ImportedBinary) =
2630+
let outfile = Path.GetFullPath(metadataPath + Path.GetFileName(dllInfo.FileName))
2631+
let ilModule = dllInfo.RawMetadata.TryGetILModuleDef().Value
2632+
try
2633+
let args: AbstractIL.ILBinaryWriter.options = {
2634+
ilg = tcGlobals.ilg
2635+
outfile = outfile
2636+
pdbfile = None //pdbfile
2637+
emitTailcalls = tcConfig.emitTailcalls
2638+
deterministic = tcConfig.deterministic
2639+
portablePDB = tcConfig.portablePDB
2640+
embeddedPDB = tcConfig.embeddedPDB
2641+
embedAllSource = tcConfig.embedAllSource
2642+
embedSourceList = tcConfig.embedSourceList
2643+
allGivenSources = [] //ilSourceDocs
2644+
sourceLink = tcConfig.sourceLink
2645+
checksumAlgorithm = tcConfig.checksumAlgorithm
2646+
signer = None //GetStrongNameSigner signingInfo
2647+
dumpDebugInfo = tcConfig.dumpDebugInfo
2648+
referenceAssemblyOnly = false
2649+
referenceAssemblyAttribOpt = None
2650+
referenceAssemblySignatureHash = None
2651+
pathMap = tcConfig.pathMap
2652+
}
2653+
AbstractIL.ILBinaryWriter.WriteILBinaryFile (args, ilModule, id)
2654+
with Failure msg ->
2655+
printfn "Export error: %s" msg
2656+
2657+
let nonFrameworkTcImports = new TcImports(tcConfigP, tcAltResolutions, None, None)
2658+
let altResolvedAssemblies = tcAltResolutions.GetAssemblyResolutions()
2659+
let! nonFrameworkAssemblies = nonFrameworkTcImports.RegisterAndImportReferencedAssemblies(ctok, altResolvedAssemblies)
2660+
2661+
nonFrameworkAssemblies
2662+
|> List.iter (function
2663+
| ResolvedImportedAssembly (asm, m) ->
2664+
let ilShortAssemName = getNameOfScopeRef asm.ILScopeRef
2665+
let dllInfo = nonFrameworkTcImports.FindDllInfo(ctok, m, ilShortAssemName)
2666+
writeMetadata dllInfo
2667+
| UnresolvedImportedAssembly (_assemblyName, _m) -> ()
2668+
)
2669+
2670+
_assemblies
2671+
|> List.iter (function
2672+
| ResolvedImportedAssembly (asm, m) ->
2673+
let ilShortAssemName = getNameOfScopeRef asm.ILScopeRef
2674+
let dllInfo = frameworkTcImports.FindDllInfo(ctok, m, ilShortAssemName)
2675+
writeMetadata dllInfo
2676+
| UnresolvedImportedAssembly (_assemblyName, _m) -> ()
2677+
)
2678+
#endif
2679+
26262680
return tcGlobals, frameworkTcImports
26272681
}
26282682

src/Compiler/FSharp.Compiler.Service.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<OtherFlags>$(OtherFlags) --warnaserror-:1182</OtherFlags> <!--Temporary fix for sourcebuild -->
1515
<AssemblyName>FSharp.Compiler.Service</AssemblyName>
1616
<AllowCrossTargeting>true</AllowCrossTargeting>
17+
<DefineConstants>$(DefineConstants);EXPORT_METADATA</DefineConstants>
1718
<DefineConstants>$(DefineConstants);COMPILER</DefineConstants>
1819
<CheckNulls>true</CheckNulls>
1920
<!-- Nullness checking against ns20 base class libraries is very weak, the APIs were not updated with annotations.

0 commit comments

Comments
 (0)