diff --git a/Directory.Build.props b/Directory.Build.props index ea26ea0..b8bd78b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ - canary + canary.0 0.1 latest @@ -21,6 +21,6 @@ $(MSBuildThisFileDirectory).artifacts - + \ No newline at end of file diff --git a/Elastic.OpenTelemetry.sln b/Elastic.OpenTelemetry.sln index b4cc94f..e93ec81 100644 --- a/Elastic.OpenTelemetry.sln +++ b/Elastic.OpenTelemetry.sln @@ -19,6 +19,10 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{37AF68F4-59EF-4B10-B1A0-87DEEB37CDCA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AAD39891-0B70-47FA-A212-43E1AAE5DF56}" + ProjectSection(SolutionItems) = preProject + tests\Directory.Build.props = tests\Directory.Build.props + tests\xunit.runner.json = tests\xunit.runner.json + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elastic.OpenTelemetry.Tests", "tests\Elastic.OpenTelemetry.Tests\Elastic.OpenTelemetry.Tests.csproj", "{22BF9223-3A6D-4197-8527-3E4E43A98A81}" EndProject diff --git a/build/build.fsproj b/build/build.fsproj index 4e6e194..c047be2 100644 --- a/build/build.fsproj +++ b/build/build.fsproj @@ -6,9 +6,9 @@ false - - - + + + @@ -17,7 +17,7 @@ - + diff --git a/build/scripts/BuildInformation.fs b/build/scripts/BuildInformation.fs new file mode 100644 index 0000000..6103337 --- /dev/null +++ b/build/scripts/BuildInformation.fs @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +module BuildInformation + +open System +open System.IO +open System.Threading +open Fake.Core +open Proc.Fs +open Fake.Tools.Git + +type BuildConfiguration = + static member ValidateAssemblyName = false + static member GenerateApiChanges = false + +type Software = + static member Organization = "elastic" + static member Repository = "elastic-otel-dotnet" + static member GithubMoniker = $"%s{Software.Organization}/%s{Software.Repository}" + static member SignKey = "069ca2728db333c1" + + static let restore = + Lazy((fun _ -> exec { run "dotnet" "tool" "restore" }), LazyThreadSafetyMode.ExecutionAndPublication) + + static let versionInfo = + Lazy(fun _ -> + let sha = Information.getCurrentSHA1 "." + let output = exec { + binary "dotnet" + arguments "minver" "-p" "canary.0" "-m" "0.1" + find (fun l -> not(l.Error)) + } + SemVer.parse <| $"%s{output.Line}+%s{sha}" + , LazyThreadSafetyMode.ExecutionAndPublication) + + static member Version = restore.Value; versionInfo.Value + +type Paths = + static member private Root = + let mutable dir = DirectoryInfo(".") + while dir.GetFiles("*.sln").Length = 0 do dir <- dir.Parent + Environment.CurrentDirectory <- dir.FullName + dir + + static member RelativePathToRoot path = Path.GetRelativePath(Paths.Root.FullName, path) + + static member ArtifactFolder = DirectoryInfo(Path.Combine(Paths.Root.FullName, ".artifacts")) + static member ArtifactPath t = DirectoryInfo(Path.Combine(Paths.ArtifactFolder.FullName, t)) + +type OS = + | OSX | Windows | Linux +with + static member Current = + match int Environment.OSVersion.Platform with + | 4 | 128 -> Linux + | 6 -> OSX + | _ -> Windows + diff --git a/build/scripts/CommandLine.fs b/build/scripts/CommandLine.fs index 545f2b0..e946bd2 100644 --- a/build/scripts/CommandLine.fs +++ b/build/scripts/CommandLine.fs @@ -6,9 +6,12 @@ module CommandLine open Argu open Microsoft.FSharp.Reflection +open System +open Bullseye -type Arguments = +type Build = | [] Clean + | [] Version | [] Build | [] Test @@ -19,33 +22,53 @@ type Arguments = | [] GenerateApiChanges | [] Release - | [] CreateReleaseOnGithub - | [] Publish - - | [] SingleTarget of bool + | [] SingleTarget | [] Token of string - | [] CleanCheckout of bool + | [] SkipDirtyCheck with interface IArgParserTemplate with member this.Usage = match this with + // commands | Clean -> "clean known output locations" + | Version -> "print version information" | Build -> "Run build" | Test -> "Runs build then tests" | Release -> "runs build, tests, and create and validates the packages shy of publishing them" - | Publish -> "Runs the full release" - - | SingleTarget _ -> "Runs the provided sub command without running their dependencies" - | Token _ -> "Token to be used to authenticate with github" - | CleanCheckout _ -> "Skip the clean checkout check that guards the release/publish targets" + // steps | PristineCheck | GeneratePackages | ValidatePackages | GenerateReleaseNotes - | GenerateApiChanges - | CreateReleaseOnGithub - -> "Undocumented, dependent target" - member this.Name = - match FSharpValue.GetUnionFields(this, typeof) with + | GenerateApiChanges -> "Undocumented, dependent target" + + // flags + | SingleTarget -> "Runs the provided sub command without running their dependencies" + | Token _ -> "Token to be used to authenticate with github" + | SkipDirtyCheck -> "Skip the clean checkout check that guards the release/publish targets" + + member this.StepName = + match FSharpValue.GetUnionFields(this, typeof) with | case, _ -> case.Name.ToLowerInvariant() + + static member Targets = + let cases = FSharpType.GetUnionCases(typeof) + seq { + for c in cases do + if c.GetFields().Length = 0 then + FSharpValue.MakeUnion(c, [| |]) :?> Build + } + + static member Ignore (_: Build) _ = () + + static member Step action (target: Build) parsed = + Targets.Target(target.StepName, Action(fun _ -> action(parsed))) + + static member Cmd (dependsOn: Build list) (composedOf: Build list) action (target: Build) (parsed: ParseResults) = + let singleTarget = parsed.TryGetResult SingleTarget |> Option.isSome + let dependsOn = if singleTarget then [] else dependsOn + + let steps = dependsOn @ composedOf |> List.map (_.StepName) + Targets.Target(target.StepName, steps, Action(fun _ -> action parsed)) + diff --git a/build/scripts/Paths.fs b/build/scripts/Paths.fs deleted file mode 100644 index c0b858e..0000000 --- a/build/scripts/Paths.fs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -module Paths - -open System -open System.IO - -let ToolName = "elastic-transport-net" -let Repository = sprintf "elastic/%s" ToolName -let MainTFM = "netstandard2.0" -let SignKey = "069ca2728db333c1" - -let ValidateAssemblyName = false -let IncludeGitHashInInformational = true -let GenerateApiChanges = false - -let Root = - let mutable dir = DirectoryInfo(".") - while dir.GetFiles("*.sln").Length = 0 do dir <- dir.Parent - Environment.CurrentDirectory <- dir.FullName - dir - -let RootRelative path = Path.GetRelativePath(Root.FullName, path) - -let Output = DirectoryInfo(Path.Combine(Root.FullName, "build", "output")) - -let ToolProject = DirectoryInfo(Path.Combine(Root.FullName, "src", ToolName)) diff --git a/build/scripts/Program.fs b/build/scripts/Program.fs index a574d9a..744e51a 100644 --- a/build/scripts/Program.fs +++ b/build/scripts/Program.fs @@ -11,26 +11,30 @@ open CommandLine [] let main argv = - let parser = ArgumentParser.Create(programName = "./build.sh") + let argv = if argv.Length = 0 then ["build"] |> Array.ofList else argv + let parser = ArgumentParser.Create(programName = "./build.sh") let parsed = try let parsed = parser.ParseCommandLine(inputs = argv, raiseOnUsage = true) - let arguments = parsed.GetSubCommand() - Some (parsed, arguments) + Some parsed with e -> - printfn "%s" e.Message + printfn $"%s{e.Message}" None match parsed with | None -> 2 - | Some (parsed, arguments) -> + | Some parsed -> - let target = arguments.Name + let target = parsed.GetSubCommand().StepName + Targets.Setup parsed - Targets.Setup parsed arguments let swallowTypes = [typeof; typeof] + let shortErrorsFor = (fun e -> swallowTypes |> List.contains (e.GetType()) ) + + async { + do! Async.SwitchToThreadPool () + return! Targets.RunTargetsAndExitAsync([target], shortErrorsFor, fun _ -> ":") |> Async.AwaitTask + } |> Async.RunSynchronously - Targets.RunTargetsAndExit - ([target], (fun e -> swallowTypes |> List.contains (e.GetType()) ), ":") 0 diff --git a/build/scripts/Targets.fs b/build/scripts/Targets.fs index f65d4f2..3bc30f7 100644 --- a/build/scripts/Targets.fs +++ b/build/scripts/Targets.fs @@ -5,188 +5,132 @@ module Targets open Argu -open System open System.IO -open Bullseye open CommandLine +open Fake.Core +open Fake.IO open Fake.Tools.Git -open ProcNet -open System +open Proc.Fs +open BuildInformation -type OS = - | OSX - | Windows - | Linux - -let getOS = - match int Environment.OSVersion.Platform with - | 4 | 128 -> Linux - | 6 -> OSX - | _ -> Windows +let private clean _ = + exec { run "dotnet" "clean" } + Shell.cleanDir Paths.ArtifactFolder.FullName -let execWithTimeout binary args timeout = - let opts = - ExecArguments(binary, args |> List.map (sprintf "\"%s\"") |> List.toArray) - let options = args |> String.concat " " - printfn ":: Running command: %s %s" binary options - let r = Proc.Exec(opts, timeout) - - match r.HasValue with - | true -> r.Value - | false -> failwithf "invocation of `%s` timed out" binary +let private build _ = exec { run "dotnet" "build" "-c" "Release" } -let exec binary args = - execWithTimeout binary args (TimeSpan.FromMinutes 10) +let private release _ = printfn "release" -let private restoreTools = lazy(exec "dotnet" ["tool"; "restore"]) -let private currentVersion = - lazy( - restoreTools.Value |> ignore - let r = Proc.Start("dotnet", "minver", "--default-pre-release-phase", "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 publish _ = printfn "publish" -let private clean (arguments:ParseResults) = - if (Paths.Output.Exists) then Paths.Output.Delete (true) - exec "dotnet" ["clean"] |> ignore - -let private build (arguments:ParseResults) = exec "dotnet" ["build"; "-c"; "Release"] |> ignore +let private version _ = + let version = Software.Version + printfn $"Informational version: %s{version.AsString}" + printfn $"Semantic version: %s{version.NormalizeToShorter()}" -let private pristineCheck (arguments:ParseResults) = - let doCheck = arguments.TryGetResult CleanCheckout |> Option.defaultValue true - match doCheck, Information.isCleanWorkingCopy "." with +let private generatePackages _ = exec { run "dotnet" "pack" } + +let private pristineCheck (arguments:ParseResults) = + let skipCheck = arguments.TryGetResult SkipDirtyCheck |> Option.isSome + match skipCheck, Information.isCleanWorkingCopy "." with + | true, _ -> printfn "Checkout is dirty but -c was specified to ignore this" | _, 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" + | _ -> failwithf "The checkout folder has pending changes, aborting. Specify -c to ./build.sh to skip this check" -let private test (arguments:ParseResults) = - let junitOutput = Path.Combine(Paths.Output.FullName, "junit-{assembly}-{framework}-test-results.xml") - let loggerPathArgs = sprintf "LogFilePath=%s" junitOutput - let loggerArg = sprintf "--logger:\"junit;%s\"" loggerPathArgs - let tfmArgs = - if getOS = OS.Windows then [] else ["-f"; "net6.0"] - exec "dotnet" (["test"; "-c"; "Release"; loggerArg] @ tfmArgs) |> ignore +let private test _ = + let testOutputPath = Paths.ArtifactPath "tests" + let junitOutput = Path.Combine(testOutputPath.FullName, "junit-{assembly}-{framework}-test-results.xml") + let loggerPathArgs = $"LogFilePath=%s{junitOutput}" + let loggerArg = $"--logger:\"junit;%s{loggerPathArgs}\"" + let tfmArgs = if OS.Current = OS.Windows then [] else ["-f"; "net8.0"] + exec { + run "dotnet" (["test"; "-c"; "Release"; loggerArg] @ tfmArgs) + } -let private generatePackages (arguments:ParseResults) = - let output = Paths.RootRelative Paths.Output.FullName - exec "dotnet" ["pack"; "-c"; "Release"; "-o"; output] |> ignore - -let private validatePackages (arguments:ParseResults) = - let output = Paths.RootRelative <| Paths.Output.FullName +let private validatePackages _ = + let packagesPath = Paths.ArtifactPath "package" + let output = Paths.RelativePathToRoot <| packagesPath.FullName let nugetPackages = - Paths.Output.GetFiles("*.nupkg") |> Seq.sortByDescending(fun f -> f.CreationTimeUtc) - |> Seq.map (fun p -> Paths.RootRelative p.FullName) + packagesPath.GetFiles("*.nupkg", SearchOption.AllDirectories) + |> Seq.sortByDescending(fun f -> f.CreationTimeUtc) + |> Seq.map (fun p -> Paths.RelativePathToRoot p.FullName) - let jenkinsOnWindowsArgs = - if Fake.Core.Environment.hasEnvironVar "JENKINS_URL" && Fake.Core.Environment.isWindows then ["-r"; "true"] else [] - - let args = ["-v"; currentVersionInformational.Value; "-k"; Paths.SignKey; "-t"; output] @ jenkinsOnWindowsArgs - nugetPackages |> Seq.iter (fun p -> exec "dotnet" (["nupkg-validator"; p] @ args) |> ignore) + let args = ["-v"; Software.Version.AsString; "-k"; Software.SignKey; "-t"; output] + nugetPackages + |> Seq.iter (fun p -> + exec { run "dotnet" (["nupkg-validator"; p] @ args) } + ) -let private generateApiChanges (arguments:ParseResults) = - let output = Paths.RootRelative <| Paths.Output.FullName - let currentVersion = currentVersion.Value +let private generateApiChanges _ = + let packagesPath = Paths.ArtifactPath "package" + let output = Paths.RelativePathToRoot <| packagesPath.FullName + let currentVersion = Software.Version.NormalizeToShorter() let nugetPackages = - Paths.Output.GetFiles("*.nupkg") |> Seq.sortByDescending(fun f -> f.CreationTimeUtc) - |> Seq.map (fun p -> Path.GetFileNameWithoutExtension(Paths.RootRelative p.FullName).Replace("." + currentVersion, "")) + packagesPath.GetFiles("*.nupkg", SearchOption.AllDirectories) + |> Seq.sortByDescending(fun f -> f.CreationTimeUtc) + |> Seq.map (fun p -> Path.GetFileNameWithoutExtension(Paths.RelativePathToRoot p.FullName).Replace("." + currentVersion, "")) nugetPackages |> Seq.iter(fun p -> - let outputFile = - let f = sprintf "breaking-changes-%s.md" p - Path.Combine(output, f) + let outputFile = Path.Combine(output, $"breaking-changes-%s{p}.md") + let tfm = "net8.0" let args = [ "assembly-differ" - (sprintf "previous-nuget|%s|%s|%s" p currentVersion Paths.MainTFM); - (sprintf "directory|src/%s/bin/Release/%s" p Paths.MainTFM); + $"previous-nuget|%s{p}|%s{currentVersion}|%s{tfm}"; + //$"directory|.artifacts/bin/%s{p}/release/%s{tfm}"; + $"directory|.artifacts/bin/%s{p}/release"; "-a"; "true"; "--target"; p; "-f"; "github-comment"; "--output"; outputFile ] - - exec "dotnet" args |> ignore + exec { run "dotnet" args } ) -let private generateReleaseNotes (arguments:ParseResults) = - let currentVersion = currentVersion.Value +let private generateReleaseNotes (arguments:ParseResults) = + let currentVersion = Software.Version.NormalizeToShorter() + let releaseNotesPath = Paths.ArtifactPath "release-notes" let output = - Paths.RootRelative <| Path.Combine(Paths.Output.FullName, sprintf "release-notes-%s.md" currentVersion) + Paths.RelativePathToRoot <| Path.Combine(releaseNotesPath.FullName, $"release-notes-%s{currentVersion}.md") let tokenArgs = match arguments.TryGetResult Token with | None -> [] | Some token -> ["--token"; token;] let releaseNotesArgs = - (Paths.Repository.Split("/") |> Seq.toList) + (Software.GithubMoniker.Split("/") |> Seq.toList) @ ["--version"; currentVersion - "--label"; "enhancement"; "New Features" - "--label"; "bug"; "Bug Fixes" - "--label"; "documentation"; "Docs Improvements" + "--label"; "enhancement"; "Features" + "--label"; "bug"; "Fixes" + "--label"; "documentation"; "Documentation" ] @ tokenArgs @ ["--output"; output] - exec "dotnet" (["release-notes"] @ releaseNotesArgs) |> ignore + let args = ["release-notes"] @ releaseNotesArgs + exec { run "dotnet" args } -let private createReleaseOnGithub (arguments:ParseResults) = - 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 release (arguments:ParseResults) = printfn "release" - -let private publish (arguments:ParseResults) = printfn "publish" +let Setup (parsed:ParseResults) = + let wireCommandLine (t: Build) = + match t with + // commands + | Version -> Build.Step version + | Clean -> Build.Cmd [Version] [] clean + | Build -> Build.Cmd [Clean] [] build + | Test -> Build.Cmd [Build] [] test + | Release -> + Build.Cmd + [PristineCheck; Test] + [GeneratePackages; ValidatePackages; GenerateReleaseNotes; GenerateApiChanges] + release + + // steps + | PristineCheck -> Build.Step pristineCheck + | GeneratePackages -> Build.Step generatePackages + | ValidatePackages -> Build.Step validatePackages + | GenerateReleaseNotes -> Build.Step generateReleaseNotes + | GenerateApiChanges -> Build.Step generateApiChanges + + // flags + | SingleTarget + | Token _ + | SkipDirtyCheck -> Build.Ignore -let Setup (parsed:ParseResults) (subCommand:Arguments) = - let step (name:string) action = Targets.Target(name, new 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 - - step PristineCheck.Name pristineCheck - step GeneratePackages.Name generatePackages - step ValidatePackages.Name validatePackages - step GenerateReleaseNotes.Name generateReleaseNotes - step GenerateApiChanges.Name generateApiChanges - cmd Release.Name - (Some [PristineCheck.Name; Test.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 + for target in Build.Targets do + let setup = wireCommandLine target + setup target parsed \ No newline at end of file diff --git a/dotnet-tools.json b/dotnet-tools.json index 3da4e96..bb26c1d 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -3,25 +3,25 @@ "isRoot": true, "tools": { "minver-cli": { - "version": "4.2.0", + "version": "4.3.0", "commands": [ "minver" ] }, "assembly-differ": { - "version": "0.13.0", + "version": "0.15.0", "commands": [ "assembly-differ" ] }, "release-notes": { - "version": "0.5.2", + "version": "0.6.0", "commands": [ "release-notes" ] }, "nupkg-validator": { - "version": "0.5.0", + "version": "0.6.0", "commands": [ "nupkg-validator" ] diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 05c9ae4..3822a2f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,7 +6,7 @@ $(SolutionRoot)\build\keys\keypair.snk nuget-icon.png - $(SolutionRoot)\README.md + README.md $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb diff --git a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj index 26473e4..f63c618 100644 --- a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj +++ b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj @@ -5,6 +5,7 @@ net8.0 enable enable + True diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..2d3ebd8 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,37 @@ + + + + + + True + + + true + + + + + + + + + true + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + \ No newline at end of file diff --git a/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj b/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj index d498be1..0c0c703 100644 --- a/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj +++ b/tests/Elastic.OpenTelemetry.Tests/Elastic.OpenTelemetry.Tests.csproj @@ -10,17 +10,6 @@ - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - diff --git a/tests/xunit.runner.json b/tests/xunit.runner.json new file mode 100644 index 0000000..9c5ec42 --- /dev/null +++ b/tests/xunit.runner.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "diagnosticMessages": true, + "internalDiagnosticMessages": true, + "longRunningTestSeconds": 2 +} \ No newline at end of file