build/scripts/Build.fs (292 lines of code) (raw):

// 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 namespace Scripts open System open System.Collections.Generic open System.IO open System.IO.Compression open System.Runtime.InteropServices open System.Xml.Linq open System.Xml.XPath open Fake.Core open Fake.DotNet open Fake.IO open Fake.IO.Globbing.Operators open Scripts.TestEnvironment open Tooling module Build = let private oldDiagnosticSourceVersion = SemVer.parse "4.6.0" let private diagnosticSourceVersion6 = SemVer.parse "6.0.0" let mutable private currentDiagnosticSourceVersion = None let private aspNetFullFramework = Paths.IntegrationsProjFile "Elastic.Apm.AspNetFullFramework" let private allSrcProjects = !! "src/**/*.csproj" let private fullFrameworkProjects = [ aspNetFullFramework Paths.TestProjFile "Elastic.Apm.AspNetFullFramework.Tests" Paths.SampleProjFile "AspNetFullFrameworkSampleApp" ] /// Gets all the Target Frameworks from a project file let private getAllTargetFrameworks (p: string) = let doc = XElement.Load p let targetFrameworks = let targetFrameworks = doc.Descendants(XName.op_Implicit "TargetFrameworks") |> Seq.map (fun p -> String.split ';' p.Value) |> Seq.concat doc.Descendants(XName.op_Implicit "TargetFramework") |> Seq.map (fun p -> p.Value) |> Seq.append targetFrameworks |> Seq.distinct |> Seq.toArray (p, targetFrameworks) /// Copy all the bin release outputs to the build/output directory let private copyBinRelease () = allSrcProjects |> Seq.iter(fun p -> let directory = Path.GetDirectoryName p let project = Path.GetFileNameWithoutExtension p let bin = Path.combine directory "bin/Release" if Directory.Exists bin then let buildOutput = Paths.BuildOutput project Shell.copyDir buildOutput bin (fun _ -> true) ) let private dotnet target projectOrSln = DotNet.Exec [target; projectOrSln; "-c"; "Release"; "-v"; "q"; "--nologo"] let private msBuild target projectOrSln = MSBuild.build (fun (p: MSBuildParams) -> { p with Verbosity = Some(Quiet) Targets = [target] Properties = [ "Configuration", "Release" "Optimize", "True" "dummy", "test" // See https://github.com/fsprojects/FAKE/issues/2738 ] // current version of Fake MSBuild module does not support latest bin log file // version of MSBuild in VS 16.8, so disable for now. DisableInternalBinLog = true NoLogo = true }) projectOrSln /// Gets the current version of System.Diagnostics.DiagnosticSource referenced by Elastic.Apm let getCurrentApmDiagnosticSourceVersion = match currentDiagnosticSourceVersion with | Some v -> v | None -> let xml = XDocument.Load("Directory.Packages.props") let package = xml.XPathSelectElement("//PackageVersion[@Include='System.Diagnostics.DiagnosticSource']") let version = package.Attribute("Version").Value let version = SemVer.parse version currentDiagnosticSourceVersion <- Some(version) version let private majorVersions = Dictionary<SemVerInfo, SemVerInfo>() /// Publishes specific projects with specific DiagnosticSource versions let private publishProjectsWithDiagnosticSourceVersion projects version = projects |> Seq.map getAllTargetFrameworks |> Seq.iter (fun (proj, frameworks) -> frameworks |> Seq.iter(fun framework -> let output = Path.GetFileNameWithoutExtension proj |> (fun p -> sprintf "%s_%i.0.0/%s" p version.Major framework) |> Paths.BuildOutput |> Path.GetFullPath printfn "Publishing %s %s with System.Diagnostics.DiagnosticSource %O..." proj framework version DotNet.Exec ["publish" ; proj sprintf "\"/p:DiagnosticSourceVersion=%O\"" version "-c"; "Release" "-f"; framework "-v"; "q" $"--property:PublishDir=%s{output}" "--nologo"; "--force"] ) ) /// Publishes ElasticApmStartupHook against a 4.x and 6.x version of System.Diagnostics.DiagnosticSource let private publishElasticApmStartupHookWithDiagnosticSourceVersion () = let projects = !! (Paths.SrcProjFile "Elastic.Apm") ++ (Paths.StartupHookProjFile "Elastic.Apm.StartupHook.Loader") publishProjectsWithDiagnosticSourceVersion projects oldDiagnosticSourceVersion let elasticApmProj = !! (Paths.SrcProjFile "Elastic.Apm") publishProjectsWithDiagnosticSourceVersion elasticApmProj diagnosticSourceVersion6 /// Runs dotnet build on all .NET core projects in the solution. /// When running on Windows and not CI, also runs MSBuild Build on .NET Framework let Build () = dotnet "build" Paths.Solution if isWindows && not isCI then msBuild "Build" aspNetFullFramework copyBinRelease() /// Runs dotnet build on all .NET core projects in the solution. /// When running on Windows and not CI, also runs MSBuild Build on .NET Framework let Test (suite: TestSuite) = let sln = match suite with | TestSuite.Profiler -> "test/profiler/Elastic.Apm.Profiler.Managed.Tests/Elastic.Apm.Profiler.Managed.Tests.csproj" | TestSuite.StartupHooks -> "test/startuphook/Elastic.Apm.StartupHook.Tests/Elastic.Apm.StartupHook.Tests.csproj" | TestSuite.IIS -> "test/iis/Elastic.Apm.AspNetFullFramework.Tests" | TestSuite.Azure | TestSuite.Integrations | TestSuite.Unit | _ -> "ElasticApmAgent.sln" //ensure we test all listed frameworks on windows (net462 and latest .NET version we support). let framework = if isWindows then None else Some "net8.0" let logger = match BuildServer.isGitHubActionsBuild with | true -> Some "--logger:\"GitHubActions;summary.includePassedTests=false;summary.includeNotFoundTests=false\"" | _ -> None let filter = match suite with | TestSuite.Azure -> Some "FullyQualifiedName~Elastic.Apm.Azure" | TestSuite.Unit -> Some "FullyQualifiedName~Elastic.Apm.Tests|FullyQualifiedName~Elastic.Apm.OpenTelemetry.Tests" | TestSuite.Integrations -> let testElseWhere = ["Tests"; "OpenTelemetry.Tests"; "StartupHook.Tests"; "Profiler.Managed.Tests"; "Azure"] let filter = testElseWhere |> List.map (fun s -> $"FullyQualifiedName!~Elastic.Apm.%s{s}") |> String.concat "&" Some filter | _ -> None let verbosity = match suite with | TestSuite.Integrations -> "detailed" | _ -> "minimal" let blame = match suite with | TestSuite.Integrations -> Some [ "--blame-hang-timeout"; "15m"; "--blame-crash-dump-type"; "mini"; "--results-directory"; "build/output"] | _ -> None let command = ["test"; "-c"; "Release"; sln; "--no-build"; "--verbosity"; verbosity; "-s"; "test/.runsettings"] @ (match filter with None -> [] | Some f -> ["--filter"; f]) @ (match framework with None -> [] | Some f -> ["-f"; f]) @ (match logger with None -> [] | Some l -> [l]) @ (match blame with None -> [] | Some l -> l) @ ["--"; "RunConfiguration.CollectSourceInformation=true"] DotNet.ExecWithTimeout command (TimeSpan.FromMinutes 30) /// Builds the CLR profiler and supporting .NET managed assemblies let BuildProfiler () = dotnet "build" (Paths.ProfilerProjFile "Elastic.Apm.Profiler.Managed") if isWindows then Cargo.Exec [ "make"; "build-release"; "--disable-check-for-update"] else Cargo.Exec [ "make"; "build-release-linux"; "--disable-check-for-update"] /// Publishes all projects with framework versions let Publish targets = let projs = match targets with | Some t -> t | None -> allSrcProjects projs |> Seq.map getAllTargetFrameworks |> Seq.iter (fun (proj, frameworks) -> frameworks |> Seq.iter(fun framework -> let output = Path.GetFileNameWithoutExtension proj |> (fun p -> sprintf "%s/%s" p framework) |> Paths.BuildOutput |> Path.GetFullPath printfn "Publishing %s %s..." proj framework DotNet.Exec ["publish" ; proj; "-c"; "Release"; "-f"; framework; "-v"; "q"; "--nologo"; $"--property:PublishDir=%s{output}"] ) ) publishElasticApmStartupHookWithDiagnosticSourceVersion() /// Packages projects into nuget packages let Pack () = DotNet.Exec ["pack" ; Paths.Solution; "-c"; "Release"; $"--property:PackageOutputPath=%s{Paths.NugetOutput}"] let Clean () = Shell.cleanDir Paths.BuildOutputFolder dotnet "clean" Paths.Solution if isWindows && not isCI then msBuild "Clean" aspNetFullFramework let CleanProfiler () = Cargo.ExecWithTimeout ["make"; "clean"; "--disable-check-for-update"] (TimeSpan.FromMinutes 10) /// Restores all packages for the solution let ToolRestore () = DotNet.Exec ["tool" ; "restore"] /// Restores all packages for the solution let Restore () = ToolRestore() DotNet.Exec ["restore" ; Paths.Solution; "-v"; "q"] if isWindows then DotNet.Exec ["restore" ; aspNetFullFramework; "-v"; "q"] let Format () = DotNet.Exec ["format"; "--verbosity"; "quiet"; "--verify-no-changes"; "--exclude"; "src/Elastic.Apm/Libraries/"] let private copyDllsAndPdbs (destination: DirectoryInfo) (source: DirectoryInfo) = source.GetFiles() |> Seq.filter (fun file -> file.Extension = ".dll" || file.Extension = ".pdb") |> Seq.iter (fun file -> file.CopyTo(Path.combine destination.FullName file.Name, true) |> ignore) /// Creates versioned ElasticApmAgent.zip file let AgentZip () = let name = "ElasticApmAgent" let currentAssemblyVersion = Versioning.CurrentVersion.FileVersion let versionedName = sprintf "%s_%s" name (currentAssemblyVersion.ToString()) let agentDir = Paths.BuildOutput name |> DirectoryInfo agentDir.Create() // copy startup hook to root of agent directory !! (Paths.BuildOutput "ElasticApmAgentStartupHook/netstandard2.0") |> Seq.filter Path.isDirectory |> Seq.map DirectoryInfo |> Seq.iter (copyDllsAndPdbs agentDir) // assemblies compiled against "current" version of System.Diagnostics.DiagnosticSource !! (Paths.BuildOutput "Elastic.Apm.StartupHook.Loader/netstandard2.0") ++ (Paths.BuildOutput "Elastic.Apm/netstandard2.0") |> Seq.filter Path.isDirectory |> Seq.map DirectoryInfo |> Seq.iter (copyDllsAndPdbs (agentDir.CreateSubdirectory(sprintf "%i.0.0" getCurrentApmDiagnosticSourceVersion.Major))) // assemblies compiled against older version of System.Diagnostics.DiagnosticSource !! (Paths.BuildOutput (sprintf "Elastic.Apm.StartupHook.Loader_%i.0.0/netstandard2.0" oldDiagnosticSourceVersion.Major)) ++ (Paths.BuildOutput (sprintf "Elastic.Apm_%i.0.0/netstandard2.0" oldDiagnosticSourceVersion.Major)) |> Seq.filter Path.isDirectory |> Seq.map DirectoryInfo |> Seq.iter (copyDllsAndPdbs (agentDir.CreateSubdirectory(sprintf "%i.0.0" oldDiagnosticSourceVersion.Major))) // assemblies compiled against 6.0 version of System.Diagnostics.DiagnosticSource !! (Paths.BuildOutput (sprintf "Elastic.Apm.StartupHook.Loader_%i.0.0/netstandard2.0" oldDiagnosticSourceVersion.Major)) ++ (Paths.BuildOutput (sprintf "Elastic.Apm_%i.0.0/net8.0" diagnosticSourceVersion6.Major)) |> Seq.filter Path.isDirectory |> Seq.map DirectoryInfo |> Seq.iter (copyDllsAndPdbs (agentDir.CreateSubdirectory(sprintf "%i.0.0" diagnosticSourceVersion6.Major))) // include version in the zip file name ZipFile.CreateFromDirectory(agentDir.FullName, Paths.BuildOutput versionedName + ".zip") let ProfilerIntegrations () = DotNet.Exec ["run"; "--project"; Paths.ProfilerProjFile "Elastic.Apm.Profiler.IntegrationsGenerator"; "--" "-i"; Paths.SrcProfiler "Elastic.Apm.Profiler.Managed/bin/Release/netstandard2.0/Elastic.Apm.Profiler.Managed.dll" "-o"; Paths.SrcProfiler "Elastic.Apm.Profiler.Managed"; "-f"; "yml"] /// Creates versioned elastic_apm_profiler.zip file containing all components needed for profiler auto-instrumentation let ProfilerZip () = let name = "elastic_apm_profiler" let directory = Paths.BuildOutput name if Directory.Exists(directory) then Directory.Delete(directory, true) let currentAssemblyVersion = Versioning.CurrentVersion.FileVersion let versionedName = let os = if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then "win-x64" else "linux-x64" sprintf "%s_%s-%s" name (currentAssemblyVersion.ToString()) os let profilerDir = directory |> DirectoryInfo profilerDir.Create() seq { Paths.SrcProfiler "Elastic.Apm.Profiler.Managed/integrations.yml" "target/release/elastic_apm_profiler.dll" "target/x86_64-unknown-linux-gnu/release/libelastic_apm_profiler.so" Paths.SrcProfiler "elastic_apm_profiler/NOTICE" Paths.SrcProfiler "elastic_apm_profiler/README" "LICENSE" } |> Seq.map FileInfo |> Seq.filter (fun file -> file.Exists) |> Seq.iter (fun file -> let destination = Path.combine profilerDir.FullName file.Name let newFile = file.CopyTo(destination, true) if newFile.Name = "README" then File.applyReplace (fun s -> s.Replace("${VERSION}", sprintf "%i.%i" currentAssemblyVersion.Major currentAssemblyVersion.Minor)) newFile.FullName ) Directory.GetDirectories((Paths.BuildOutput "Elastic.Apm.Profiler.Managed"), "*", SearchOption.TopDirectoryOnly) |> Array.filter (fun dir -> isWindows || not (dir.EndsWith("net462"))) |> Seq.map DirectoryInfo |> Seq.iter (fun sourceDir -> copyDllsAndPdbs (profilerDir.CreateSubdirectory(sourceDir.Name)) sourceDir) // include version in the zip file name and ensure the target zip is removed let zip = Paths.BuildOutput versionedName + ".zip" if File.exists zip then printf $"%s{zip} already exists on disk" File.delete zip ZipFile.CreateFromDirectory(profilerDir.FullName, zip)