build/scripts/Targets.fs (226 lines of code) (raw):
module Targets
open System.Net.Http
open Fake.Tools.Git
open Argu
open System
open System.Linq
open System.IO
open Bullseye
open CommandLine
open ProcNet
let runningOnCI = Fake.Core.Environment.hasEnvironVar "CI"
let runningOnWindows = Fake.Core.Environment.isWindows
let execWithTimeout binary args timeout =
let opts =
ExecArguments(binary, args |> List.map (sprintf "\"%s\"") |> List.toArray)
let r = Proc.Exec(opts, timeout)
match r.HasValue with
| true -> r.Value
| false -> failwithf "invocation of `%s` timed out" binary
let exec binary args =
execWithTimeout binary args (TimeSpan.FromMinutes 10)
let private restoreTools = lazy (exec "dotnet" [ "tool"; "restore" ])
let private currentVersion =
lazy
(restoreTools.Value |> ignore
let r = Proc.Start("dotnet", "minver", "-d=canary", "-m=0.1")
let o = r.ConsoleOut |> Seq.find (fun l -> not (l.Line.StartsWith("MinVer:")))
o.Line)
let private currentVersionInformational =
lazy
(match Paths.IncludeGitHashInInformational with
| false -> currentVersion.Value
| true -> sprintf "%s+%s" currentVersion.Value (Information.getCurrentSHA1 (".")))
let private clean (arguments: ParseResults<Arguments>) =
if (Paths.Output.Exists) then
Paths.Output.Delete(true)
exec "dotnet" [ "clean" ] |> ignore
let private build (arguments: ParseResults<Arguments>) =
exec "dotnet" [ "build"; "-c"; "Release" ] |> ignore
let private pristineCheck (arguments: ParseResults<Arguments>) =
let doCheck = arguments.TryGetResult CleanCheckout |> Option.defaultValue true
match doCheck, Information.isCleanWorkingCopy "." with
| _, true -> printfn "The checkout folder does not have pending changes, proceeding"
| false, _ -> printf "Checkout is dirty but -c was specified to ignore this"
| _ -> failwithf "The checkout folder has pending changes, aborting"
type TestMode = | Unit | Integration
let private runTests (arguments: ParseResults<Arguments>) testMode =
let mode = match testMode with | Unit -> "unit" | Integration -> "integration"
let filterArg =
match testMode with
| Unit -> [ "--filter"; "FullyQualifiedName!~IntegrationTests" ]
| Integration -> [ "--filter"; "FullyQualifiedName~IntegrationTests" ]
let os = if runningOnWindows then "win" else "linux"
let junitOutput =
Path.Combine(Paths.Output.FullName, $"junit-%s{os}-%s{mode}-{{assembly}}-{{framework}}-test-results.xml")
let loggerPathArgs = sprintf "LogFilePath=%s" junitOutput
let loggerArg = $"--logger:\"junit;%s{loggerPathArgs};MethodFormat=Class;FailureBodyFormat=Verbose\""
let settingsArg = if runningOnCI then (["-s"; ".ci.runsettings"]) else [];
execWithTimeout "dotnet" ([ "test" ] @ filterArg @ settingsArg @ [ "-c"; "RELEASE"; "-m:1"; loggerArg ]) (TimeSpan.FromMinutes 15)
|> ignore
let private test (arguments: ParseResults<Arguments>) =
runTests arguments Unit
let private integrate (arguments: ParseResults<Arguments>) =
runTests arguments Integration
let private generatePackages (arguments: ParseResults<Arguments>) =
let output = Paths.RootRelative Paths.Output.FullName
exec "dotnet" [ "pack"; "-c"; "Release"; "-o"; output ] |> ignore
let private validatePackages (arguments: ParseResults<Arguments>) =
let output = Paths.RootRelative <| Paths.Output.FullName
let nugetPackages =
Paths.Output.GetFiles("*.nupkg")
|> Seq.sortByDescending (_.CreationTimeUtc)
//skipping because system web fullframework not able to validate
|> Seq.filter (fun f -> not <| f.Name.StartsWith("Elastic.Serilog.Enrichers.Web."))
|> Seq.map (fun p -> Paths.RootRelative p.FullName)
let ciOnWindowsArgs = if runningOnCI && runningOnWindows then [ "-r"; "true" ] else []
let args =
[ "-v"; currentVersionInformational.Value; "-k"; Paths.SignKey; "-t"; output ] @ ciOnWindowsArgs
nugetPackages |> Seq.iter (fun p -> exec "dotnet" ([ "nupkg-validator"; p ] @ args) |> ignore)
let private generateApiChanges (arguments: ParseResults<Arguments>) =
let output = Paths.RootRelative <| Paths.Output.FullName
let currentVersion = currentVersion.Value
let nugetPackages =
Paths.Output.GetFiles("*.nupkg")
|> Seq.sortByDescending (fun f -> f.CreationTimeUtc)
|> Seq.map (fun p ->
Path
.GetFileNameWithoutExtension(Paths.RootRelative p.FullName)
.Replace("." + currentVersion, ""))
let firstPath project tfms =
tfms
|> Seq.map (fun tfm -> (tfm, sprintf "directory|src/%s/bin/Release/%s" project Paths.MainTFM))
|> Seq.where (fun (tfm, path) -> File.Exists path)
|> Seq.tryHead
nugetPackages
|> Seq.iter (fun p ->
let outputFile =
let f = sprintf "breaking-changes-%s.md" p
Path.Combine(output, f)
let firstKnownTFM = firstPath p [ Paths.MainTFM; Paths.Netstandard21TFM ]
match firstKnownTFM with
| None -> printf "Skipping generating API changes for: %s" p
| Some (tfm, path) ->
let args =
[
"assembly-differ"
(sprintf "previous-nuget|%s|%s|%s" p currentVersion tfm)
(sprintf "directory|%s" path)
"-a"
"true"
"--target"
p
"-f"
"github-comment"
"--output"
outputFile
]
exec "dotnet" args |> ignore)
let private generateReleaseNotes (arguments: ParseResults<Arguments>) =
let currentVersion = currentVersion.Value
let output =
Paths.RootRelative <| Path.Combine(Paths.Output.FullName, sprintf "release-notes-%s.md" currentVersion)
let tokenArgs =
match arguments.TryGetResult Token with
| None -> []
| Some token -> [ "--token"; token ]
let releaseNotesArgs =
(Paths.Repository.Split("/") |> Seq.toList)
@ [
"--version"
currentVersion
"--label"
"enhancement"
"New Features"
"--label"
"bug"
"Bug Fixes"
"--label"
"documentation"
"Docs Improvements"
]
@ tokenArgs @ [ "--output"; output ]
exec "dotnet" ([ "release-notes" ] @ releaseNotesArgs) |> ignore
let private createReleaseOnGithub (arguments: ParseResults<Arguments>) =
let currentVersion = currentVersion.Value
let tokenArgs =
match arguments.TryGetResult Token with
| None -> []
| Some token -> [ "--token"; token ]
let releaseNotes =
Paths.RootRelative <| Path.Combine(Paths.Output.FullName, sprintf "release-notes-%s.md" currentVersion)
let breakingChanges =
let breakingChangesDocs = Paths.Output.GetFiles("breaking-changes-*.md")
breakingChangesDocs |> Seq.map (fun f -> [ "--body"; Paths.RootRelative f.FullName ]) |> Seq.collect id |> Seq.toList
let releaseArgs =
(Paths.Repository.Split("/") |> Seq.toList)
@ [ "create-release"; "--version"; currentVersion; "--body"; releaseNotes ] @ breakingChanges @ tokenArgs
exec "dotnet" ([ "release-notes" ] @ releaseArgs) |> ignore
let private updateLoggingSpec (arguments: ParseResults<Arguments>) =
let commit =
match arguments.TryGetResult Commit with
| None -> "main"
| Some commit -> commit
async {
use client = new HttpClient()
try
let! response =
let url =
sprintf "https://raw.githubusercontent.com/elastic/ecs-logging/%s/spec/spec.json" commit
client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
use! stream = response.Content.ReadAsStreamAsync() |> Async.AwaitTask
use fileStream =
new FileStream(Paths.RootRelative("tests/Elastic.CommonSchema.Tests/Specs/spec.json"), FileMode.Create, FileAccess.Write, FileShare.None)
do! stream.CopyToAsync(fileStream) |> Async.AwaitTask
do!
File.WriteAllTextAsync(Paths.RootRelative("tests/Elastic.CommonSchema.Tests/Specs/spec_version.txt"), commit)
|> Async.AwaitTask
with ex ->
printfn "Could not update logging spec: %A" ex
}
|> Async.RunSynchronously
let private release (arguments: ParseResults<Arguments>) = printfn "release"
let private publish (arguments: ParseResults<Arguments>) = printfn "publish"
// temp fix for unit reporting: https://github.com/elastic/apm-pipeline-library/issues/2063
let teardown () =
if Paths.Output.Exists then
let isSkippedFile p =
File.ReadLines(p).FirstOrDefault() = "<testsuites />"
Paths.Output.GetFiles("junit-*.xml")
|> Seq.filter (fun p -> isSkippedFile p.FullName)
|> Seq.iter (fun f ->
printfn $"Removing empty test file: %s{f.FullName}"
f.Delete()
)
Console.WriteLine "Ran teardown"
let Setup (parsed: ParseResults<Arguments>) (subCommand: Arguments) =
let step (name: string) action =
Targets.Target(name, Action(fun _ -> action (parsed)))
let cmd (name: string) commandsBefore steps action =
let singleTarget = (parsed.TryGetResult SingleTarget |> Option.defaultValue false)
let deps =
match (singleTarget, commandsBefore) with
| (true, _) -> []
| (_, Some d) -> d
| _ -> []
let steps = steps |> Option.defaultValue []
Targets.Target(name, deps @ steps, Action(action))
step Clean.Name clean
cmd Build.Name None (Some [ Clean.Name ]) <| fun _ -> build parsed
cmd Test.Name (Some [ Build.Name ]) None <| fun _ -> test parsed
cmd Integrate.Name (Some [ Build.Name ]) None <| fun _ -> integrate parsed
cmd UpdateSpec.Name None None <| fun _ -> updateLoggingSpec parsed
step PristineCheck.Name pristineCheck
step GeneratePackages.Name generatePackages
step ValidatePackages.Name validatePackages
step GenerateReleaseNotes.Name generateReleaseNotes
step GenerateApiChanges.Name generateApiChanges
cmd
Release.Name
None
// (Some [ PristineCheck.Name; Test.Name; Integrate.Name ])
(Some [ GeneratePackages.Name; ValidatePackages.Name; GenerateReleaseNotes.Name; GenerateApiChanges.Name ])
<| fun _ -> release parsed
step CreateReleaseOnGithub.Name createReleaseOnGithub
cmd Publish.Name (Some [ Release.Name ]) (Some [ CreateReleaseOnGithub.Name ]) <| fun _ -> publish parsed