Skip to content

Commit 8249ee6

Browse files
committed
add project parser
1 parent 2b4beaf commit 8249ee6

File tree

4 files changed

+282
-0
lines changed

4 files changed

+282
-0
lines changed

docs/content/project.fsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This tutorial demonstrates how to can analyze a whole project using services pro
88
99
> **NOTE:** The API used below is experimental and subject to change when later versions of the nuget package are published
1010
11+
*)
12+
13+
14+
(**
1115
1216
Getting whole-project results
1317
-----------------------------
@@ -295,6 +299,48 @@ correctly and then analyze each project in turn.
295299
296300
**)
297301

302+
(**
303+
Cracking a project file
304+
-----------------------------
305+
306+
F# projects normally use the '.fsproj' project file format. You can get options corresponding to a project file
307+
using GetProjectOptionsFromProjectFile. In this example we get the project options for one of the
308+
project files in the F# Compiler Service project itself - you should also be able to use this technique
309+
for any project that cleans buildly using the command line tools 'xbuild' or 'msbuild'.
310+
311+
312+
*)
313+
314+
(*** define-output:options1 ***)
315+
316+
checker.GetProjectOptionsFromProject(__SOURCE_DIRECTORY__ + @"/../../src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj")
317+
318+
319+
(**
320+
321+
The options produced in this case are:
322+
323+
*)
324+
325+
(*** include-it:options1 ***)
326+
327+
328+
(**
329+
330+
Another utility is provided to simply get the command line arguments for a project file
331+
332+
*)
333+
334+
(*** define-output:options2 ***)
335+
336+
InteractiveChecker.GetCommandLineArgsFromProject(__SOURCE_DIRECTORY__ + @"/../../src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj")
337+
338+
(**
339+
340+
Here the arguments are simply returned as an array of command line arguments suitable for the F# compiler or compiler service.
341+
342+
*)
343+
298344
(**
299345
Summary
300346
-------

src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@
595595
<Reference Include="mscorlib" />
596596
<Reference Include="System" />
597597
<Reference Include="System.Core" />
598+
<Reference Include="System.Xml" />
598599
<Reference Include="Microsoft.Build.Framework" />
599600
<Reference Include="Microsoft.Build.Engine" />
600601
<Reference Include="Microsoft.Build" />

src/fsharp/vs/service.fs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2421,6 +2421,223 @@ type InteractiveChecker(projectCacheSize) =
24212421
LoadTime = loadedTimeStamp
24222422
UnresolvedReferences = None }
24232423

2424+
#if SILVERLIGHT
2425+
#else
2426+
member ic.GetProjectOptionsFromProject(projectFileName, ?properties : (string * string) list, ?loadedTimeStamp) =
2427+
let args = InteractiveChecker.GetCommandLineArgsFromProject(projectFileName, ?properties=properties)
2428+
ic.GetProjectOptionsFromCommandLineArgs(projectFileName, args, ?loadedTimeStamp=loadedTimeStamp)
2429+
2430+
static member GetCommandLineArgsFromProject(projectFileName:string, ?properties : (string * string) list) =
2431+
2432+
// It seems the current directory must be set to get correct processing
2433+
use _pwd =
2434+
let dir = Environment.CurrentDirectory
2435+
Environment.CurrentDirectory <- Path.GetDirectoryName(projectFileName)
2436+
{ new System.IDisposable with member x.Dispose() = Environment.CurrentDirectory <- dir }
2437+
2438+
use engine = new Microsoft.Build.Evaluation.ProjectCollection()
2439+
2440+
use xmlReader = System.Xml.XmlReader.Create(FileSystem.FileStreamReadShim(projectFileName))
2441+
2442+
let project =
2443+
if runningOnMono then
2444+
engine.LoadProject(xmlReader, "4.0", FullPath=projectFileName)
2445+
else
2446+
engine.LoadProject(xmlReader, FullPath=projectFileName)
2447+
let properties = defaultArg properties []
2448+
for (p, v) in properties do
2449+
project.SetProperty(p, v) |> ignore
2450+
2451+
let project = project.CreateProjectInstance()
2452+
2453+
let b = project.Build([| "ResolveReferences" |], null)
2454+
if not b then
2455+
failwith (sprintf "resolving references failed for project '%s'" projectFileName)
2456+
2457+
let mkAbsolute dir v =
2458+
if FileSystem.IsPathRootedShim v then v
2459+
else Path.Combine(dir, v)
2460+
2461+
let fileItems = project.GetItems("Compile")
2462+
let resourceItems = project.GetItems("Resource")
2463+
let dir = Path.GetDirectoryName project.FullPath
2464+
2465+
let getprop s =
2466+
let v = project.GetPropertyValue s
2467+
if String.IsNullOrWhiteSpace v then None
2468+
else Some v
2469+
2470+
let split (s : string option) (cs : char []) =
2471+
match s with
2472+
| None -> [||]
2473+
| Some s ->
2474+
if String.IsNullOrWhiteSpace s then [||]
2475+
else s.Split(cs, StringSplitOptions.RemoveEmptyEntries)
2476+
2477+
let getbool (s : string option) =
2478+
match s with
2479+
| None -> false
2480+
| Some s ->
2481+
match (Boolean.TryParse s) with
2482+
| (true, result) -> result
2483+
| (false, _) -> false
2484+
2485+
let optimize = getprop "Optimize" |> getbool
2486+
let assemblyNameOpt = getprop "AssemblyName"
2487+
let tailcalls = getprop "Tailcalls" |> getbool
2488+
let outputPathOpt = getprop "OutputPath"
2489+
let docFileOpt = getprop "DocumentationFile"
2490+
let outputTypeOpt = getprop "OutputType"
2491+
let debugTypeOpt = getprop "DebugType"
2492+
let baseAddressOpt = getprop "BaseAddress"
2493+
let sigFileOpt = getprop "GenerateSignatureFile"
2494+
let keyFileOpt = getprop "KeyFile"
2495+
let pdbFileOpt = getprop "PdbFile"
2496+
let platformOpt = getprop "Platform"
2497+
let targetTypeOpt = getprop "TargetType"
2498+
let versionFileOpt = getprop "VersionFile"
2499+
let targetProfileOpt = getprop "TargetProfile"
2500+
let warnLevelOpt = getprop "Warn"
2501+
let subsystemVersionOpt = getprop "SubsystemVersion"
2502+
let win32ResOpt = getprop "Win32ResourceFile"
2503+
let heOpt = getprop "HighEntropyVA" |> getbool
2504+
let win32ManifestOpt = getprop "Win32ManifestFile"
2505+
let debugSymbols = getprop "DebugSymbols" |> getbool
2506+
let prefer32bit = getprop "Prefer32Bit" |> getbool
2507+
let warnAsError = getprop "TreatWarningsAsErrors" |> getbool
2508+
let defines = split (getprop "DefineConstants") [| ';'; ','; ' ' |]
2509+
let nowarn = split (getprop "NoWarn") [| ';'; ','; ' ' |]
2510+
let warningsAsError = split (getprop "WarningsAsErrors") [| ';'; ','; ' ' |]
2511+
let libPaths = split (getprop "ReferencePath") [| ';'; ',' |]
2512+
let otherFlags = split (getprop "OtherFlags") [| ' ' |]
2513+
let isLib = (outputTypeOpt = Some "Library")
2514+
2515+
let outputFileOpt =
2516+
match outputPathOpt, assemblyNameOpt with
2517+
| Some outputPath, Some assemblyName ->
2518+
let v =
2519+
Path.Combine(outputPath, assemblyName) + (if isLib then ".dll"
2520+
else ".exe")
2521+
Some(mkAbsolute dir v)
2522+
| _ -> None
2523+
2524+
let docFileOpt =
2525+
match docFileOpt with
2526+
| None -> None
2527+
| Some docFile -> Some(mkAbsolute dir docFile)
2528+
2529+
let files =
2530+
[| for f in fileItems do
2531+
yield mkAbsolute dir f.EvaluatedInclude |]
2532+
2533+
let resources =
2534+
[| for f in resourceItems do
2535+
yield "--resource:" + mkAbsolute dir f.EvaluatedInclude |]
2536+
2537+
//let fxVer = project.GetPropertyValue("TargetFrameworkVersion")
2538+
2539+
let references =
2540+
[| for i in project.GetItems("ReferencePath") do
2541+
yield "-r:" + mkAbsolute dir i.EvaluatedInclude |]
2542+
2543+
let libPaths =
2544+
[| for i in libPaths do
2545+
yield "--lib:" + mkAbsolute dir i |]
2546+
2547+
let options =
2548+
[| yield "--simpleresolution"
2549+
yield "--noframework"
2550+
match outputFileOpt with
2551+
| None -> ()
2552+
| Some outputFile -> yield "--out:" + outputFile
2553+
match docFileOpt with
2554+
| None -> ()
2555+
| Some docFile -> yield "--doc:" + docFile
2556+
match baseAddressOpt with
2557+
| None -> ()
2558+
| Some baseAddress -> yield "--baseaddress:" + baseAddress
2559+
match keyFileOpt with
2560+
| None -> ()
2561+
| Some keyFile -> yield "--keyfile:" + keyFile
2562+
match sigFileOpt with
2563+
| None -> ()
2564+
| Some sigFile -> yield "--sig:" + sigFile
2565+
match pdbFileOpt with
2566+
| None -> ()
2567+
| Some pdbFile -> yield "--pdb:" + pdbFile
2568+
match versionFileOpt with
2569+
| None -> ()
2570+
| Some versionFile -> yield "--versionfile:" + versionFile
2571+
match warnLevelOpt with
2572+
| None -> ()
2573+
| Some warnLevel -> yield "--warn:" + warnLevel
2574+
match subsystemVersionOpt with
2575+
| None -> ()
2576+
| Some s -> yield "--subsystemversion:" + s
2577+
if heOpt then yield "--highentropyva+"
2578+
match win32ResOpt with
2579+
| None -> ()
2580+
| Some win32Res -> yield "--win32res:" + win32Res
2581+
match win32ManifestOpt with
2582+
| None -> ()
2583+
| Some win32Manifest -> yield "--win32manifest:" + win32Manifest
2584+
match targetProfileOpt with
2585+
| None -> ()
2586+
| Some targetProfile -> yield "--targetprofile:" + targetProfile
2587+
yield "--fullpaths"
2588+
yield "--flaterrors"
2589+
if warnAsError then yield "--warnaserror"
2590+
yield if isLib then "--target:library"
2591+
else "--target:exe"
2592+
for symbol in defines do
2593+
if not (String.IsNullOrWhiteSpace symbol) then yield "--define:" + symbol
2594+
for nw in nowarn do
2595+
if not (String.IsNullOrWhiteSpace nw) then yield "--nowarn:" + nw
2596+
for nw in warningsAsError do
2597+
if not (String.IsNullOrWhiteSpace nw) then yield "--warnaserror:" + nw
2598+
yield if debugSymbols then "debug+"
2599+
else "--debug-"
2600+
yield if optimize then "--optimize+"
2601+
else "--optimize-"
2602+
yield if tailcalls then "--tailcalls+"
2603+
else "--tailcalls-"
2604+
match debugTypeOpt with
2605+
| None -> ()
2606+
| Some debugType ->
2607+
match debugType.ToUpperInvariant() with
2608+
| "NONE" -> ()
2609+
| "PDBONLY" -> yield "--debug:pdbonly"
2610+
| "FULL" -> yield "--debug:full"
2611+
| _ -> ()
2612+
match platformOpt |> Option.map (fun o -> o.ToUpperInvariant()), prefer32bit,
2613+
targetTypeOpt |> Option.map (fun o -> o.ToUpperInvariant()) with
2614+
| Some "ANYCPU", true, Some "EXE" | Some "ANYCPU", true, Some "WINEXE" -> yield "anycpu32bitpreferred"
2615+
| Some "ANYCPU", _, _ -> yield "anycpu"
2616+
| Some "X86", _, _ -> yield "x86"
2617+
| Some "X64", _, _ -> yield "x64"
2618+
| Some "ITANIUM", _, _ -> yield "Itanium"
2619+
| _ -> ()
2620+
match targetTypeOpt |> Option.map (fun o -> o.ToUpperInvariant()) with
2621+
| Some "LIBRARY" -> yield "--target:library"
2622+
| Some "EXE" -> yield "--target:exe"
2623+
| Some "WINEXE" -> yield "--target:winexe"
2624+
| Some "MODULE" -> yield "--target:module"
2625+
| _ -> ()
2626+
yield! otherFlags
2627+
yield! resources
2628+
yield! libPaths
2629+
yield! references
2630+
yield! files |]
2631+
2632+
// Finalization of the default BuildManager on Mono causes a thread to start when 'BuildNodeManager' is accessed
2633+
// in the finalizer on exit. The thread start doesn't work when exiting, and an error is printed
2634+
// and even worse the thread is not marked as a background computation thread, so a console
2635+
// application doesn't exit correctly.
2636+
if runningOnMono then
2637+
System.GC.SuppressFinalize(Microsoft.Build.Execution.BuildManager.DefaultBuildManager)
2638+
options
2639+
#endif
2640+
24242641
/// Begin background parsing the given project.
24252642
member ic.StartBackgroundCompile(options) = backgroundCompiler.StartBackgroundCompile(options)
24262643

src/fsharp/vs/service.fsi

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,24 @@ type InteractiveChecker =
496496
/// so that references are re-resolved.</param>
497497
member GetProjectOptionsFromCommandLineArgs : projectFileName: string * argv: string[] * ?loadedTimeStamp: DateTime -> ProjectOptions
498498
499+
/// <summary>
500+
/// <para>Get the command line arguments implied by a standard F# project file in the xbuild/msbuild format.</para>
501+
/// </summary>
502+
///
503+
/// <param name="projectFileName">Used to differentiate between projects and for the base directory of the project.</param>
504+
/// <param name="properties">The build properties such as Configuration=Debug etc.</param>
505+
static member GetCommandLineArgsFromProject : projectFileName: string * ?properties : (string * string) list -> string[]
506+
507+
/// <summary>
508+
/// <para>Get the project options implied by a standard F# project file in the xbuild/msbuild format.</para>
509+
/// </summary>
510+
///
511+
/// <param name="projectFileName">Used to differentiate between projects and for the base directory of the project.</param>
512+
/// <param name="properties">The build properties such as Configuration=Debug etc.</param>
513+
/// <param name="loadedTimeStamp">Indicates when the project was loaded into the editing environment,
514+
/// so that an 'unload' and 'reload' action will cause the project to be considered as a new project.</param>
515+
member GetProjectOptionsFromProject : projectFileName: string * ?properties : (string * string) list * ?loadedTimeStamp: DateTime -> ProjectOptions
516+
499517
[<Obsolete("This member has been renamed to 'GetProjectOptionsFromScript'")>]
500518
member GetProjectOptionsFromScriptRoot : filename: string * source: string * ?loadedTimeStamp: DateTime * ?otherFlags: string[] * ?useFsiAuxLib: bool -> ProjectOptions
501519

0 commit comments

Comments
 (0)