#!meta {"kernelInfo":{"defaultKernelName":"spiral","items":[{"aliases":[],"name":"spiral"}]}} #!markdown # Builder (Polyglot) #!fsharp #r @"../../../../../../../.nuget/packages/fsharp.control.asyncseq/3.2.1/lib/netstandard2.1/FSharp.Control.AsyncSeq.dll" #r @"../../../../../../../.nuget/packages/system.reactive/6.0.1-preview.1/lib/net6.0/System.Reactive.dll" #r @"../../../../../../../.nuget/packages/system.reactive.linq/6.0.1-preview.1/lib/netstandard2.0/System.Reactive.Linq.dll" #r @"../../../../../../../.nuget/packages/argu/6.2.4/lib/netstandard2.0/Argu.dll" #!fsharp #!import ../../lib/fsharp/Notebooks.dib #!import ../../lib/fsharp/Testing.dib #!fsharp #!import ../../lib/fsharp/Common.fs #!import ../../lib/fsharp/CommonFSharp.fs #!import ../../lib/fsharp/Async.fs #!import ../../lib/fsharp/AsyncSeq.fs #!import ../../lib/fsharp/Runtime.fs #!import ../../lib/fsharp/FileSystem.fs #!fsharp #if !INTERACTIVE open Lib #endif #!fsharp open Common open SpiralFileSystem.Operators #!markdown ## buildProject #!fsharp let inline buildProject runtime outputDir path = async { let fullPath = path |> System.IO.Path.GetFullPath let fileDir = fullPath |> System.IO.Path.GetDirectoryName let extension = fullPath |> System.IO.Path.GetExtension trace Debug (fun () -> "buildProject") (fun () -> $"fullPath: {fullPath} / {_locals ()}") match extension with | ".fsproj" -> () | _ -> failwith "Invalid project file" let runtimes = runtime |> Option.map List.singleton |> Option.defaultValue [ "linux-x64"; "win-x64" ] let outputDir = outputDir |> Option.defaultValue "dist" return! runtimes |> List.map (fun runtime -> async { let command = $@"dotnet publish ""{path}"" --configuration Release --output ""{outputDir}"" --runtime {runtime}" let! exitCode, _result = SpiralRuntime.execution_options (fun x -> { x with l0 = command l6 = Some fileDir } ) |> SpiralRuntime.execute_with_options_async return exitCode }) |> Async.Sequential |> Async.map Array.sum } #!markdown ## persistCodeProject #!fsharp let inline persistCodeProject packages modules name hash code = async { trace Debug (fun () -> "persistCodeProject") (fun () -> $"packages: {packages} / modules: {modules} / name: {name} / hash: {hash} / code.Length: {code |> String.length} / {_locals ()}") let workspaceRoot = SpiralFileSystem.get_workspace_root () let targetDir = let targetDir = workspaceRoot "target/Builder" name match hash with | Some hash -> targetDir "packages" hash | None -> targetDir targetDir |> System.IO.Directory.CreateDirectory |> ignore let filePath = targetDir $"{name}.fs" |> System.IO.Path.GetFullPath do! code |> SpiralFileSystem.write_all_text_exists filePath let modulesCode = modules |> List.map (fun path -> $"""""") |> SpiralSm.concat "\n " let fsprojPath = targetDir $"{name}.fsproj" let fsprojCode = $""" net9.0 preview Major true true true false false true true 0.0.1-alpha.1 Exe _FREEBSD _LINUX _OSX _WINDOWS {modulesCode} """ do! fsprojCode |> SpiralFileSystem.write_all_text_exists fsprojPath let paketReferencesPath = targetDir "paket.references" let paketReferencesCode = "FSharp.Core" :: packages |> SpiralSm.concat "\n" do! paketReferencesCode |> SpiralFileSystem.write_all_text_exists paketReferencesPath return fsprojPath } #!markdown ## buildCode #!fsharp let inline buildCode runtime packages modules outputDir name code = async { let! fsprojPath = code |> persistCodeProject packages modules name None let! exitCode = fsprojPath |> buildProject runtime outputDir if exitCode <> 0 then let! fsprojText = fsprojPath |> SpiralFileSystem.read_all_text_async trace Critical (fun () -> "buildCode") (fun () -> $"code: {code |> SpiralSm.ellipsis_end 400} / fsprojText: {fsprojText} / {_locals ()}") return exitCode } #!fsharp //// test "1 + 1 |> ignore" |> buildCode None [] [] None "test1" |> Async.runWithTimeout 180000 |> _assertEqual (Some 0) #!fsharp //// test "1 + a |> ignore" |> buildCode None [] [] None "test2" |> Async.runWithTimeout 180000 |> _assertEqual (Some 2) #!markdown ## readFile #!fsharp let inline readFile path = async { let! code = path |> SpiralFileSystem.read_all_text_async let code = System.Text.RegularExpressions.Regex.Replace ( code, @"( *)(let\s+main\s+\w+\s*=)", fun m -> m.Groups.[1].Value + "[]\n" + m.Groups.[1].Value + m.Groups.[2].Value ) let codeTrim = code |> SpiralSm.trim_end [||] return if codeTrim |> SpiralSm.ends_with "\n()" then codeTrim |> SpiralSm.slice 0 ((codeTrim |> String.length) - 3) else code } #!markdown ## buildFile #!fsharp let inline buildFile runtime packages modules path = async { let fullPath = path |> System.IO.Path.GetFullPath let dir = fullPath |> System.IO.Path.GetDirectoryName let name = fullPath |> System.IO.Path.GetFileNameWithoutExtension let! code = fullPath |> readFile return! code |> buildCode runtime packages modules (dir "dist" |> Some) name } #!markdown ## persistFile #!fsharp let inline persistFile packages modules path = async { let fullPath = path |> System.IO.Path.GetFullPath let name = fullPath |> System.IO.Path.GetFileNameWithoutExtension let! code = fullPath |> readFile return! code |> persistCodeProject packages modules name None } #!markdown ## Arguments #!fsharp [] type Arguments = | [] Path of path : string | [] Packages of packages : string list | [] Modules of modules : string list | [] Runtime of runtime : string | [] Persist_Only interface Argu.IArgParserTemplate with member s.Usage = match s with | Path _ -> nameof Path | Packages _ -> nameof Packages | Modules _ -> nameof Modules | Runtime _ -> nameof Runtime | Persist_Only -> nameof Persist_Only #!fsharp //// test Argu.ArgumentParser.Create().PrintUsage () #!markdown ## main #!fsharp let main args = let argsMap = args |> Runtime.parseArgsMap let path = match argsMap.[nameof Arguments.Path] with | [ Arguments.Path path ] -> Some path | _ -> None |> Option.get let packages = match argsMap |> Map.tryFind (nameof Arguments.Packages) with | Some [ Arguments.Packages packages ] -> packages | _ -> [] let modules = match argsMap |> Map.tryFind (nameof Arguments.Modules) with | Some [ Arguments.Modules modules ] -> modules | _ -> [] let runtime = match argsMap |> Map.tryFind (nameof Arguments.Runtime) with | Some [ Arguments.Runtime runtime ] -> Some runtime | _ -> None let persistOnly = argsMap |> Map.containsKey (nameof Arguments.Persist_Only) if persistOnly then path |> persistFile packages modules |> Async.map (fun _ -> 0) else path |> buildFile runtime packages modules |> Async.runWithTimeout (60001 * 60 * 24) |> function | Some exitCode -> exitCode | None -> 1 #!fsharp //// test let args = System.Environment.GetEnvironmentVariable "ARGS" |> SpiralRuntime.split_args |> Result.toArray |> Array.collect id match args with | [||] -> 0 | args -> if main args = 0 then 0 else failwith "main failed"