<commit_before>﻿[<AutoOpen>]
module Fake.MSBuildHelper

open System
open System.Text
open System.IO
open System.Configuration
open System.Xml
open System.Xml.Linq

type MSBuildProject = XDocument

/// MSBuild exe fileName
let msBuildExe =   
    if isUnix then
        "xbuild"
    else
        let ev = environVar "MSBuild"
        if not (isNullOrEmpty ev) then ev else
            if "true".Equals(ConfigurationManager.AppSettings.["IgnoreMSBuild"],StringComparison.OrdinalIgnoreCase) then 
                String.Empty 
            else 
                findPath "MSBuildPath" "MSBuild.exe"


let msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"
let xname name = XName.Get(name,msbuildNamespace)

let loadProject (projectFileName:string) : MSBuildProject = 
    MSBuildProject.Load(projectFileName,LoadOptions.PreserveWhitespace)

let internal getReferenceElements elementName projectFileName (doc:XDocument) =
    let fi = fileInfo projectFileName
    doc
      .Descendants(xname "Project")
      .Descendants(xname "ItemGroup")
      .Descendants(xname elementName)
        |> Seq.map(fun e -> 
            let a = e.Attribute(XName.Get "Include")
            let value = convertWindowsToCurrentPath a.Value
            let fileName =
                if value.StartsWith(".." + directorySeparator) || (not <| value.Contains directorySeparator) then
                    fi.Directory.FullName @@ value
                else
                    value
            a,fileName |> FullName)   


let processReferences elementName f projectFileName (doc:XDocument) =
    let fi = fileInfo projectFileName
    doc
        |> getReferenceElements elementName projectFileName
        |> Seq.iter (fun (a,fileName) -> a.Value <- f fileName)
    doc

let rec getProjectReferences (projectFileName:string) =
    if projectFileName.EndsWith ".sln" then Set.empty else // exclude .sln-files since the are not XML
    let doc = loadProject projectFileName
    let references =
        getReferenceElements "ProjectReference" projectFileName doc
            |> Seq.map snd

    references
      |> Seq.map getProjectReferences
      |> Seq.concat
      |> Seq.append references
      |> Set.ofSeq

type MSBuildVerbosity = Quiet | Minimal | Normal | Detailed | Diagnostic

type MSBuildParams = 
    { Targets: string list
      Properties: (string * string) list
      MaxCpuCount: int option option
      ToolsVersion: string option
      Verbosity: MSBuildVerbosity option }

let MSBuildDefaults = 
    { Targets = []
      Properties = []
      MaxCpuCount = Some None
      ToolsVersion = None
      Verbosity = None }

let getAllParameters targets maxcpu tools verbosity properties =
    if isUnix then
        [targets; tools; verbosity] @ properties
    else
        [targets; maxcpu; tools; verbosity] @ properties

let serializeMSBuildParams (p: MSBuildParams) = 
    let targets = 
        match p.Targets with
        | [] -> None
        | t -> Some ("t", t |> separated ";")
    let properties = 
        p.Properties |> List.map (fun (k,v) -> Some ("p", sprintf "%s=\"%s\"" k v))
    let maxcpu = 
        match p.MaxCpuCount with
        | None -> None
        | Some x -> Some ("m", match x with Some v -> v.ToString() | _ -> "")
    let tools =
        match p.ToolsVersion with
        | None -> None
        | Some t -> Some ("tv", t)
    let verbosity = 
        match p.Verbosity with
        | None -> None
        | Some v -> 
            let level = 
                match v with
                | Quiet -> "q"
                | Minimal -> "m"
                | Normal -> "n"
                | Detailed -> "d"
                | Diagnostic -> "diag"
            Some ("v", level)
    let allParameters = getAllParameters targets maxcpu tools verbosity properties
    allParameters
    |> Seq.map (function
                    | None -> ""
                    | Some (k,v) -> "/" + k + (if isNullOrEmpty v then "" else ":" + v))
    |> separated " "

/// Runs a msbuild project
let build setParams project =
    traceStartTask "MSBuild" project
    let args = MSBuildDefaults |> setParams |> serializeMSBuildParams        
    let args = toParam project + " " + args
    tracefn "Building project: %s\n  %s %s" project msBuildExe args
    if not (execProcess3 (fun info ->  
        info.FileName <- msBuildExe
        info.Arguments <- args) TimeSpan.MaxValue)
    then failwithf "Building %s project failed." project
    traceEndTask "MSBuild" project

/// Builds the given project files and collects the output files.
/// Properties are parameterized by project name.
let MSBuildWithProjectProperties outputPath (targets: string) (properties: string -> (string*string) list) projects = 
    let projects = projects |> Seq.toList
    let output = 
        if isNullOrEmpty outputPath then "" else<commit_msg>Add support for file loggers to MSBuildHelper.fs<commit_after>﻿[<AutoOpen>]
module Fake.MSBuildHelper

open System
open System.Text
open System.IO
open System.Configuration
open System.Xml
open System.Xml.Linq

type MSBuildProject = XDocument

/// MSBuild exe fileName
let msBuildExe =   
    if isUnix then
        "xbuild"
    else
        let ev = environVar "MSBuild"
        if not (isNullOrEmpty ev) then ev else
            if "true".Equals(ConfigurationManager.AppSettings.["IgnoreMSBuild"],StringComparison.OrdinalIgnoreCase) then 
                String.Empty 
            else 
                findPath "MSBuildPath" "MSBuild.exe"


let msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"
let xname name = XName.Get(name,msbuildNamespace)

let loadProject (projectFileName:string) : MSBuildProject = 
    MSBuildProject.Load(projectFileName,LoadOptions.PreserveWhitespace)

let internal getReferenceElements elementName projectFileName (doc:XDocument) =
    let fi = fileInfo projectFileName
    doc
      .Descendants(xname "Project")
      .Descendants(xname "ItemGroup")
      .Descendants(xname elementName)
        |> Seq.map(fun e -> 
            let a = e.Attribute(XName.Get "Include")
            let value = convertWindowsToCurrentPath a.Value
            let fileName =
                if value.StartsWith(".." + directorySeparator) || (not <| value.Contains directorySeparator) then
                    fi.Directory.FullName @@ value
                else
                    value
            a,fileName |> FullName)   


let processReferences elementName f projectFileName (doc:XDocument) =
    let fi = fileInfo projectFileName
    doc
        |> getReferenceElements elementName projectFileName
        |> Seq.iter (fun (a,fileName) -> a.Value <- f fileName)
    doc

let rec getProjectReferences (projectFileName:string) =
    if projectFileName.EndsWith ".sln" then Set.empty else // exclude .sln-files since the are not XML
    let doc = loadProject projectFileName
    let references =
        getReferenceElements "ProjectReference" projectFileName doc
            |> Seq.map snd

    references
      |> Seq.map getProjectReferences
      |> Seq.concat
      |> Seq.append references
      |> Set.ofSeq

type MSBuildVerbosity = Quiet | Minimal | Normal | Detailed | Diagnostic
type MSBuildLogParameter = Append | PerformanceSummary | Summary | NoSummary | ErrorsOnly | WarningsOnly | NoItemAndPropertyList | ShowCommandLine | ShowTimestamp | ShowEventId | ForceNoAlign  | DisableConsoleColor | DisableMPLogging | EnableMPLogging

type MSBuildFileLoggerConfig =
    { Number : int
      Filename : string option
      Verbosity : MSBuildVerbosity option
      Parameters : MSBuildLogParameter list option }

type MSBuildParams = 
    { Targets: string list
      Properties: (string * string) list
      MaxCpuCount: int option option
      ToolsVersion: string option
      Verbosity: MSBuildVerbosity option
      FileLoggers: MSBuildFileLoggerConfig list option }

let MSBuildDefaults = 
    { Targets = []
      Properties = []
      MaxCpuCount = Some None
      ToolsVersion = None
      Verbosity = None
      FileLoggers = None }

let getAllParameters targets maxcpu tools verbosity fileLoggers properties =
    if isUnix then
        [targets; tools; verbosity] @ fileLoggers @ properties
    else
        [targets; maxcpu; tools; verbosity] @ fileLoggers @ properties

let serializeMSBuildParams (p: MSBuildParams) = 
    let verbosityName v =
        match v with
        | Quiet -> "q"
        | Minimal -> "m"
        | Normal -> "n"
        | Detailed -> "d"
        | Diagnostic -> "diag"
    let targets = 
        match p.Targets with
        | [] -> None
        | t -> Some ("t", t |> separated ";")
    let properties = 
        p.Properties |> List.map (fun (k,v) -> Some ("p", sprintf "%s=\"%s\"" k v))
    let maxcpu = 
        match p.MaxCpuCount with
        | None -> None
        | Some x -> Some ("m", match x with Some v -> v.ToString() | _ -> "")
    let tools =
        match p.ToolsVersion with
        | None -> None
        | Some t -> Some ("tv", t)
    let verbosity = 
        match p.Verbosity with
        | None -> None
        | Some v -> 
            Some ("v", verbosityName v)
    let fileLoggers =
        let logParams param =
            match param with
            | Append -> "Append"
            | PerformanceSummary -> "PerformanceSummary"
            | Summary -> "Summary"
            | NoSummary -> "NoSummary"
            | ErrorsOnly -> "ErrorsOnly"
            | WarningsOnly -> "WarningsOnly"
            | NoItemAndPropertyList -> "NoItemAndPropertyList"
            | ShowCommandLine -> "ShowCommandLine"
            | ShowTimestamp -> "ShowTimestamp"
            | ShowEventId -> "ShowEventId"
            | ForceNoAlign -> "ForceNoAlign"
            | DisableConsoleColor -> "DisableConsoleColor"
            | DisableMPLogging -> "DisableMPLogging"
            | EnableMPLogging -> "EnableMPLogging"
        match p.FileLoggers with
        | None -> []
        | Some fls ->
            fls |> List.map (fun fl -> 
                    Some("flp" + (string fl.Number), 
                        sprintf "%s%s%s"
                            (match fl.Filename with | None -> "" | Some f -> sprintf "logfile=%s;" f)
                            (match fl.Verbosity with | None -> "" | Some v -> sprintf "Verbosity=%s;" (verbosityName v))
                            (match fl.Parameters with | None -> "" | Some ps -> ps |> List.map (fun p -> logParams p |> sprintf "%s;") |> String.concat "")))
    let allParameters = getAllParameters targets maxcpu tools verbosity fileLoggers properties
    allParameters
    |> Seq.map (function
                    | None -> ""
                    | Some (k,v) -> "/" + k + (if isNullOrEmpty v then "" else ":" + v))
    |> separated " "

/// Runs a msbuild project
let build setParams project =
    traceStartTask "MSBuild" project
    let args = MSBuildDefaults |> setParams |> serializeMSBuildParams        
    let args = toParam project + " " + args
    tracefn "Building project: %s\n  %s %s" project msBuildExe args
    if not (execProcess3 (fun info ->  
        info.FileName <- msBuildExe
        info.Arguments <- args) TimeSpan.MaxValue)
    then failwithf "Building %s project failed." project
    traceEndTask "MSBuild" project

/// Builds the given project files and collects the output files.
/// Properties are parameterized by project name.
let MSBuildWithProjectProperties outputPath (targets: string) (properties: string -> (string*string) list) projects = 
    let projects = projects |> Seq.toList
    let output = 
        if isNullOrEmpty outputPath then "" else
{'fraction_non_alphanumeric': 0.09404145077720208, 'fraction_numerical': 0.0008635578583765112, 'mean_word_length': 2.2770232031692133, 'pattern_counts': {'":': 2, '<': 13, '<?xml version=': 0, '>': 109, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2469228', 'n_tokens_mistral': 3255, 'n_tokens_neox': 3133, 'n_words': 946}