From b65022aa3e615ba3cd2c85b0a5dc8e953b7bb743 Mon Sep 17 00:00:00 2001 From: smaillet <25911635+smaillet@users.noreply.github.com> Date: Sun, 1 Jun 2025 08:48:50 -0700 Subject: [PATCH 1/5] Added Testing of assembly attributes * Updated build scripts to force a rebuild of all projects - The tests use nuget pkg resolution and would otherwise use whatever package was built last. * Added targets to PSSPROJ files to allow rebuilds to work * Marked IDE runs as in conclusive for packaged assembly validation. - Since tests resolve NUGET packages it isn't deterministic if the package was created via the IDE or a command line build. - IDE builds are determined by the presence of the environment variable "BuildTime". - This value is set by the build scripts for ALL of the builds so that all binaries produce the same version number for CI builds. (It is ignored for a release build) --- Build-All.ps1 | 2 +- PsModules/CommonBuild/CommonBuild.pssproj | 3 +- .../Public/Initialize-BuildEnvironment.ps1 | 10 --- PsModules/RepoBuild/RepoBuild.pssproj | 4 +- .../BuildTaskTests.cs | 61 +++++++++++++++++++ .../TestModuleFixtures.cs | 9 +++ .../Ubiquity.Versioning.Build.Tasks.UT.csproj | 1 + 7 files changed, 76 insertions(+), 14 deletions(-) diff --git a/Build-All.ps1 b/Build-All.ps1 index 65b1dc4..d94587f 100644 --- a/Build-All.ps1 +++ b/Build-All.ps1 @@ -50,7 +50,7 @@ try mkdir $buildInfo['NuGetOutputPath'] -ErrorAction SilentlyContinue | Out-Null - dotnet build -c $Configuration 'src/Ubiquity.NET.Versioning.slnx' + dotnet build -c $Configuration --no-incremental 'src/Ubiquity.NET.Versioning.slnx' } catch { diff --git a/PsModules/CommonBuild/CommonBuild.pssproj b/PsModules/CommonBuild/CommonBuild.pssproj index 7e28cda..07afd0a 100644 --- a/PsModules/CommonBuild/CommonBuild.pssproj +++ b/PsModules/CommonBuild/CommonBuild.pssproj @@ -59,6 +59,7 @@ + @@ -68,4 +69,4 @@ - \ No newline at end of file + diff --git a/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 b/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 index 6335237..61bef94 100644 --- a/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 +++ b/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 @@ -350,16 +350,6 @@ function Initialize-BuildEnvironment } - Write-Information 'Deleting common build versioning env vars' - # override the build version related values set from CommonBuild - # This repo is unique in that it CREATES the package that uses these. - # The actual build of these packages use `GeneratedVersion.props` - $env:IsAutomatedBuild = $null - $env:IsPullRequestBuild =$null - $env:IsReleaseBuild =$null - $env:CiBuildName =$null - $env:BuildTime =$null - return $buildInfo } catch diff --git a/PsModules/RepoBuild/RepoBuild.pssproj b/PsModules/RepoBuild/RepoBuild.pssproj index 9c74d88..f5ae29c 100644 --- a/PsModules/RepoBuild/RepoBuild.pssproj +++ b/PsModules/RepoBuild/RepoBuild.pssproj @@ -53,8 +53,6 @@ - - @@ -62,7 +60,9 @@ <_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" /> + + diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs index 1d962b5..37432df 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.Loader; using System.Xml.Linq; @@ -48,18 +49,29 @@ public void ValidateRepoAssemblyVersion( string targetFramework) ["BuildVersionXml"] = Path.Combine(RepoRoot, "BuildVersion.xml"), }; + string envBuildTime = TestModuleFixtures.EnvBuildTime; + bool isIDEBuild = true; + if( !string.IsNullOrWhiteSpace(envBuildTime)) + { + globalProperties["BuildTime"] = envBuildTime; + isIDEBuild = false; + } + using var collection = new ProjectCollection(globalProperties); var (testProject, props) = CreateTestProjectAndInvokeTestedPackage( targetFramework, projectCollection: collection ); + string? taskAssembly = testProject.ProjectInstance.GetOptionalProperty("_Ubiquity_NET_Versioning_Build_Tasks"); Assert.IsNotNull( taskAssembly, "Task assembly property should contain full path to the task DLL (Not NULL)" ); Context.WriteLine( $"Task Assembly: '{taskAssembly}'" ); + Assert.IsFalse( string.IsNullOrWhiteSpace( taskAssembly ), "Task assembly property should contain full path to the task DLL (Not Whitespace)" ); Assert.IsNotNull( props.FileVersion, "Generated properties should have a 'FileVersion'" ); Context.WriteLine( $"Generated FileVersion: {props.FileVersion}" ); + var alc = new AssemblyLoadContext("TestALC", isCollectible: true); try { @@ -70,6 +82,7 @@ public void ValidateRepoAssemblyVersion( string targetFramework) Assert.IsNotNull( asmVer, "Task assembly should have a version" ); Context.WriteLine( $"TaskAssemblyVersion: {asmVer}" ); Context.WriteLine( $"AssemblyName: {asmName}" ); + Assert.IsNotNull( props.FileVersionMajor, "Property value for Major should exist" ); Assert.AreEqual( (int)props.FileVersionMajor, asmVer.Major, "Major value of assembly version should match" ); @@ -81,6 +94,54 @@ public void ValidateRepoAssemblyVersion( string targetFramework) Assert.IsNotNull( props.FileVersionRevision, "Property value for Revision should exist" ); Assert.AreEqual( (int)props.FileVersionRevision, asmVer.Revision, "Revision value of assembly version should match" ); + + // Test that AssemblyFileVersion attribute matches expected value + string fileVersion = ( from attr in asm.CustomAttributes + where attr.AttributeType.FullName == "System.Reflection.AssemblyFileVersionAttribute" + let val = attr.ConstructorArguments.Single().Value as string + where val is not null + select val + ).Single(); + + Assert.AreEqual(props.FileVersion, fileVersion); + + // Test that AssemblyInformationalVersion attribute matches expected value + string informationalVersion = ( from attr in asm.CustomAttributes + where attr.AttributeType.FullName == "System.Reflection.AssemblyInformationalVersionAttribute" + let val = attr.ConstructorArguments.Single().Value as string + where val is not null + select val + ).Single(); + + // Check for limited version information in an IDE run as the "BuildTime" environment, + // which is translated to CIBuildIndex, is hard coded to the max value. Additionally, + // in a release build there is NO CI information to extract so it is inconclusive. + // Full testing is formally inconclusive but at least validate what is plausible and + // treat the rest as inconclusive + if (isIDEBuild) + { + Context.WriteLine($"AssemblyInformationalVersion {informationalVersion}"); + + Assert.IsNotNull(props.InformationalVersion); + + // release builds won't have ANY CI information. + int cipos = props.InformationalVersion.IndexOf(".ci", StringComparison.Ordinal); + if(cipos > 0) + { + string propsVersion = props.InformationalVersion[ ..cipos]; + + int ideCiBuildPos = informationalVersion.IndexOf(".ci"); + informationalVersion = informationalVersion[ ..ideCiBuildPos]; + + Assert.AreEqual(propsVersion, informationalVersion); + } + + Assert.Inconclusive("IDE build verification is inconclusive; but at least matches expectations"); + } + else + { + Assert.AreEqual(props.InformationalVersion, informationalVersion); + } } finally { diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs index 12e530e..2aa1766 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs @@ -48,6 +48,13 @@ public static void AssemblyInitialize( TestContext ctx ) string? value = Environment.GetEnvironmentVariable(envVar); if(value is not null) { + // Save the environment build time for use with task assembly builds as that + // value is needed to determine the build index used for non-IDE builds + if(envVar == "BuildTime") + { + EnvBuildTime = value; + } + Environment.SetEnvironmentVariable(envVar, null); } } @@ -67,6 +74,8 @@ public static void AssemblyCleanup( ) PackageRepo?.Dispose(); } + internal static string EnvBuildTime { get; private set; } = string.Empty; + private static void CopyFile(string srcDir, string fileName, string dstDir) { File.Copy(Path.Combine(srcDir, fileName), Path.Combine(dstDir, fileName), overwrite: true); diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/Ubiquity.Versioning.Build.Tasks.UT.csproj b/src/Ubiquity.Versioning.Build.Tasks.UT/Ubiquity.Versioning.Build.Tasks.UT.csproj index 6d064cb..55c1a88 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/Ubiquity.Versioning.Build.Tasks.UT.csproj +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/Ubiquity.Versioning.Build.Tasks.UT.csproj @@ -10,4 +10,5 @@ + From 1f969bc3b91ca1e4196f47606221eca41f215da9 Mon Sep 17 00:00:00 2001 From: smaillet <25911635+smaillet@users.noreply.github.com> Date: Sun, 1 Jun 2025 09:01:24 -0700 Subject: [PATCH 2/5] Updated GH Action to upload test results even if failed as follow up action that updates a PR comment needs the results. --- .github/workflows/pr-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index c6fdafc..d8b291c 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -49,7 +49,9 @@ jobs: - name: Run Tests run: ./Invoke-Tests.ps1 + # Upload test results even if failed; unless cancelled - name: Upload Test Results + if: (!cancelled()) uses: actions/upload-artifact@v4 with: name: Test Results From 8d42688f920d9d0a61393c5cf1e9f25dae25be55 Mon Sep 17 00:00:00 2001 From: smaillet <25911635+smaillet@users.noreply.github.com> Date: Sun, 1 Jun 2025 12:10:08 -0700 Subject: [PATCH 3/5] Removed IDE build detection * It was downright confusing and, obviously now, broke the automated builds - Now requires existence of a generated props file * Moved creation of the generated props file to a distinct script to make it easy to generate once for IDE work. - Directory.Build.Targets contains a check for it and will create an error if not found with a message indicating what to do to resolve the problem. --- .editorconfig | 5 +- Build-All.ps1 | 2 +- Directory.Build.props | 31 +- Directory.Build.targets | 5 +- New-GeneratedVersionProps.ps1 | 371 ++++++++++++++++++ .../Private/ConvertTo-BuildIndex.ps1 | 23 -- .../Public/Initialize-BuildEnvironment.ps1 | 278 ------------- PsModules/RepoBuild/RepoBuild.pssproj | 19 +- ...biquity.NET.Versioning.Build.Tasks.targets | 8 +- src/Ubiquity.NET.Versioning.slnx | 1 + .../BuildTaskTests.cs | 52 +-- .../TestModuleFixtures.cs | 98 ++++- 12 files changed, 493 insertions(+), 400 deletions(-) create mode 100644 New-GeneratedVersionProps.ps1 delete mode 100644 PsModules/RepoBuild/Private/ConvertTo-BuildIndex.ps1 diff --git a/.editorconfig b/.editorconfig index 5e92d1e..f5d0214 100644 --- a/.editorconfig +++ b/.editorconfig @@ -94,6 +94,9 @@ csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent dotnet_diagnostic.SA1005.severity = none +# see: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3920 +dotnet_diagnostic.SA1202.severity = none + # Analysis and refactoring rules for Ubiquity.NET # Description: Code analysis rules for Ubiquity.NET projects @@ -1408,8 +1411,6 @@ dotnet_diagnostic.SA1200.severity = error dotnet_diagnostic.SA1201.severity = error -dotnet_diagnostic.SA1202.severity = error - dotnet_diagnostic.SA1203.severity = none dotnet_diagnostic.SA1204.severity = silent diff --git a/Build-All.ps1 b/Build-All.ps1 index d94587f..2b64a8d 100644 --- a/Build-All.ps1 +++ b/Build-All.ps1 @@ -49,7 +49,7 @@ try } mkdir $buildInfo['NuGetOutputPath'] -ErrorAction SilentlyContinue | Out-Null - + .\New-GeneratedVersionProps.ps1 $buildInfo dotnet build -c $Configuration --no-incremental 'src/Ubiquity.NET.Versioning.slnx' } catch diff --git a/Directory.Build.props b/Directory.Build.props index c03a411..3b1bc9d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,30 +1,15 @@ - - - - - - 1.27597.27630.61955 - 5.0.0-a.ci-4294967295.IDE - 5.0.0-alpha.ci-4294967295.IDE - $(FileVersion) - $(ProductVersion) + + $(MSBuildThisFileDirectory)GeneratedVersion.props - - + - + - + + diff --git a/New-GeneratedVersionProps.ps1 b/New-GeneratedVersionProps.ps1 new file mode 100644 index 0000000..0f94dfd --- /dev/null +++ b/New-GeneratedVersionProps.ps1 @@ -0,0 +1,371 @@ +using module "PSModules/CommonBuild/CommonBuild.psd1" +using module "PSModules/RepoBuild/RepoBuild.psd1" + +<# +.SYNOPSIS + Script to Create the generateVersion.props file used in this repo build + +.PARAMETER Configuration + This sets the build configuration to use, default is "Release" though for inner loop development this + may be set to "Debug". + +.PARAMETER ForceClean + Forces a complete clean (Recursive delete of the build output) + +.DESCRIPTION + This script is used by the local and automated builds to create the versioning information for the assemblies + in this repository. The assembly build CANNOT reference itself to get the versioning (only consumers of the + OUTPUT of this repo can). Thus, this will generate the information using the same algorithms, at least in theory. + Unit tests will VERIFY that the actual task assembly matches the versioning it would produce itself. So, the + "duality" of code paths is still verified. +#> +[cmdletbinding()] +Param( + [hashtable]$buildInfo, + [string]$Configuration="Release", + [switch]$ForceClean +) + +function ConvertTo-BuildIndex +{ +<# +.SYNOPSIS + Converts a TimeStamp into a build index + +.DESCRIPTION + The algorithm used is the same as the package published. The resulting index is a 32bit value that + is a combination of the number of days since a fixed point (Upper 16 bits) and the number of seconds since + midnight (on the day of the input time stamp) divided by 2 {Lower 16 bits) +#> + param( + [Parameter(Mandatory=$true, ValueFromPipeLine)] + [DateTime]$timeStamp + ) + + $timeStamp = $timeStamp.ToUniversalTime() + $midnightTodayUtc = [DateTime]::new($timeStamp.Year, $timeStamp.Month, $timeStamp.Day, 0, 0, 0, [DateTimeKind]::Utc) + $baseDate = [DateTime]::new(2000, 1, 1, 0, 0, 0, [DateTimeKind]::Utc) + $buildNumber = ([Uint32]($timeStamp - $baseDate).Days) -shl 16 + $buildNUmber += [UInt16](($timeStamp - $midnightTodayUtc).TotalSeconds / 2) + return $buildNumber +} + +class PreReleaseVersion +{ + [ValidateSet("alpha", "beta", "delta", "epsilon", "gamma", "kappa", "prerelease", "rc")] + [string] $Name; + + [ValidateSet("a", "b", "d", "e", "g", "k", "p", "r")] + [string] $ShortName; + + [ValidateRange(-1,7)] + [int] $Index; + + [ValidateRange(0,99)] + [int] $Number; + + [ValidateRange(0,99)] + [int] $Fix; + + PreReleaseVersion([hashtable]$buildVersionXmlData) + { + $preRelName = $buildVersionXmlData['PreReleaseName'] + + if( ![string]::IsNullOrWhiteSpace( $preRelName ) ) + { + $this.Index = [PreReleaseVersion]::GetPrerelIndex($preRelName) + if($this.Index -ge 0) + { + $this.Name = [PreReleaseVersion]::PreReleaseNames[$this.Index] + $this.ShortName = [PreReleaseVersion]::PreReleaseShortNames[$this.Index] + } + + $this.Number = $buildVersionXmlData['PreReleaseNumber']; + $this.Fix = $buildVersionXmlData['PreReleaseFix']; + } + else + { + $this.Index = -1; + } + } + + [string] ToString([bool] $useShortForm = $false) + { + $hasPreRel = $this.Index -ge 0 + + $bldr = [System.Text.StringBuilder]::new() + if($hasPreRel) + { + $bldr.Append('-').Append($useShortForm ? $this.ShortName : $this.Name) + $delimFormat = $useShortForm ? '-{0:D02}' : '.{0}' + if(($this.Number -gt 0)) + { + $bldr.AppendFormat($delimFormat, $this.Number) + if(($this.Fix -gt 0)) + { + $bldr.AppendFormat($delimFormat, $this.Fix) + } + } + } + + return $bldr.ToString() + } + + hidden static [string[]] $PreReleaseNames = @("alpha", "beta", "delta", "epsilon", "gamma", "kappa", "prerelease", "rc" ); + hidden static [string[]] $PreReleaseShortNames = @("a", "b", "d", "e", "g", "k", "p", "r"); + + hidden static [int] GetPrerelIndex([string] $preRelName) + { + $preRelIndex = -1 + if(![string]::IsNullOrWhiteSpace($preRelName)) + { + $preRelIndex = [PreReleaseVersion]::PreReleaseNames | + ForEach-Object {$index=0} {@{Name = $_; Index = $index++}} | + Where-Object {$_["Name"] -ieq $preRelName} | + ForEach-Object {$_["Index"]} | + Select-Object -First 1 + + # if not found in long names, test against the short names + if($preRelIndex -lt 0) + { + $preRelIndex = [PreReleaseVersion]::PreReleaseShortNames | + ForEach-Object {$index=0} {@{Name = $_; Index = $index++}} | + Where-Object {$_["Name"] -ieq $preRelName} | + ForEach-Object {$_["Index"]} | + Select-Object -First 1 + } + } + return $preRelIndex + } + +} + +class CSemVer +{ + [ValidateRange(0,99999)] + [int] $Major; + + [ValidateRange(0,49999)] + [int] $Minor; + + [ValidateRange(0,9999)] + [int] $Patch; + + [ValidateLength(0,20)] + [string] $BuildMetadata; + + [ValidatePattern('\A[a-z0-9-]+\Z')] + [string] $CiBuildIndex; + + [ValidatePattern('\A[a-z0-9-]+\Z')] + [string] $CiBuildName; + + [ulong] $OrderedVersion; + + [Version] $FileVersion; + + [PreReleaseVersion] $PreReleaseVersion; + + CSemVer([hashtable]$buildVersionData) + { + $this.Major = $buildVersionData["BuildMajor"] + $this.Minor = $buildVersionData["BuildMinor"] + $this.Patch = $buildVersionData["BuildPatch"] + if($buildVersionData["PreReleaseName"]) + { + $this.PreReleaseVersion = [PreReleaseVersion]::new($buildVersionData) + if(!$this.PreReleaseVersion) + { + throw "Internal ERROR: PreReleaseVersion version is NULL!" + } + } + + $this.BuildMetadata = $buildVersionData["BuildMetadata"] + + $this.CiBuildName = $buildVersionData["CiBuildName"]; + $this.CiBuildIndex = $buildVersionData["CiBuildIndex"]; + + if( (![string]::IsNullOrEmpty( $this.CiBuildName )) -and [string]::IsNullOrEmpty( $this.CiBuildIndex ) ) + { + throw "CiBuildIndex is required if CiBuildName is provided"; + } + + if( (![string]::IsNullOrEmpty( $this.CiBuildIndex )) -and [string]::IsNullOrEmpty( $this.CiBuildName ) ) + { + throw "CiBuildName is required if CiBuildIndex is provided"; + } + + $this.OrderedVersion = [CSemVer]::GetOrderedVersion($this.Major, $this.Minor, $this.Patch, $this.PreReleaseVersion) + $fileVer64 = $this.OrderedVersion -shl 1 + if($this.CiBuildIndex -and $this.CiBuildName) + { + $fileVer64 += 1; + } + + $this.FileVersion = [CSemVer]::ConvertToVersion($fileVer64) + } + + [string] ToString([bool] $includeMetadata, [bool]$useShortForm) + { + $bldr = [System.Text.StringBuilder]::new() + $bldr.AppendFormat('{0}.{1}.{2}', $this.Major, $this.Minor, $this.Patch) + if($this.PreReleaseVersion) + { + $bldr.Append($this.PreReleaseVersion.ToString($useShortForm)) + } + + $hasPreRel = $this.PreReleaseVersion -and $this.PreReleaseVersion.Index -ge 0 + if($this.CiBuildIndex -and $this.CiBuildName) + { + $bldr.Append($hasPreRel ? '.' : '--') + $bldr.AppendFormat('ci.{0}.{1}', $this.CiBuildIndex, $this.CiBuildName) + } + + if(![string]::IsNullOrWhitespace($this.BuildMetadata) -and $includeMetadata) + { + $bldr.AppendFormat( '+{0}', $this.BuildMetadata ) + } + + return $bldr.ToString(); + } + + [string] ToString() + { + return $this.ToString($true, $false); + } + + hidden static [ulong] GetOrderedVersion($Major, $Minor, $Patch, [PreReleaseVersion] $PreReleaseVersion) + { + [ulong] $MulNum = 100; + [ulong] $MulName = $MulNum * 100; + [ulong] $MulPatch = ($MulName * 8) + 1; + [ulong] $MulMinor = $MulPatch * 10000; + [ulong] $MulMajor = $MulMinor * 50000; + + [ulong] $retVal = (([ulong]$Major) * $MulMajor) + (([ulong]$Minor) * $MulMinor) + ((([ulong]$Patch) + 1) * $MulPatch); + if( $PreReleaseVersion -and $PreReleaseVersion.Index -ge 0 ) + { + $retVal -= $MulPatch - 1; + $retVal += [ulong]($PreReleaseVersion.Index) * $MulName; + $retVal += [ulong]($PreReleaseVersion.Number) * $MulNum; + $retVal += [ulong]($PreReleaseVersion.Fix); + } + return $retVal; + } + + hidden static [Version] ConvertToVersion([ulong]$value) + { + $revision = [ushort]($value % 65536); + $rem = [ulong](($value - $revision) / 65536); + + $build = [ushort]($rem % 65536); + $rem = ($rem - $build) / 65536; + + $minorNum = [ushort]($rem % 65536); + $rem = ($rem - $minorNum) / 65536; + + $majorNum = [ushort]($rem % 65536); + + return [Version]::new( $majorNum, $minorNum, $build, $revision ); + } +} + +Set-StrictMode -Version 3.0 + +Push-Location $PSScriptRoot +$oldPath = $env:Path +try +{ + # Pull in the repo specific support and force a full initialization of all the environment + # if current build information is not provided. + if(!$buildInfo) + { + $buildInfo = Initialize-BuildEnvironment -FullInit + if(!$buildInfo -or $buildInfo -isnot [hashtable]) + { + throw "build scripts BUSTED; Got null buildinfo hashtable..." + } + } + + # PowerShell doesn't export enums from a script module, so the type of this return is + # "unpronounceable" [In C++ terminology]. So, convert it to a string so it is usable in this + # script. + [string] $buildKind = Get-CurrentBuildKind + + $verInfo = Get-ParsedBuildVersionXML -BuildInfo $buildInfo + if($buildKind -ne "ReleaseBuild") + { + $verInfo['CiBuildIndex'] = ConvertTo-BuildIndex $env:BuildTime + } + + switch($buildKind) + { + "LocalBuild" { $verInfo['CiBuildName'] = "ZZZ" } + "PullRequestBuild" { $verInfo['CiBuildName'] = "PRQ" } + "CiBuild" { $verInfo['CiBuildName'] = "BLD" } + "ReleaseBuild" { } + default {throw "unknown build kind" } + } + + # Generate props file with the version information for this build. + # While it is plausible to use ENV vars to overload or set properties + # that leads to dangling values during development, which makes for + # a LOT of wasted time chasing down why a change didn't work... + # [Been there, done that, worn out the bloody T-Shirt...] + $csemVer = [CSemVer]::New($verInfo) + $xmlDoc = [System.Xml.XmlDocument]::new() + $projectElement = $xmlDoc.CreateElement("Project") + $xmlDoc.AppendChild($projectElement) | Out-Null + + $propGroupElement = $xmlDoc.CreateElement("PropertyGroup") + $projectElement.AppendChild($propGroupElement) | Out-Null + + $fileVersionElement = $xmlDoc.CreateElement("FileVersion") + $fileVersionElement.InnerText = $csemVer.FileVersion.ToString() + $propGroupElement.AppendChild($fileVersionElement) | Out-Null + + $packageVersionElement = $xmlDoc.CreateElement("PackageVersion") + $packageVersionElement.InnerText = $csemVer.ToString($false,$true) # short form of version + $propGroupElement.AppendChild($packageVersionElement) | Out-Null + + $productVersionElement = $xmlDoc.CreateElement("ProductVersion") + $productVersionElement.InnerText = $csemVer.ToString($true, $false) # long form of version + $propGroupElement.AppendChild($productVersionElement) | Out-Null + + $assemblyVersionElement = $xmlDoc.CreateElement("AssemblyVersion") + $assemblyVersionElement.InnerText = $csemVer.FileVersion.ToString() + $propGroupElement.AppendChild($assemblyVersionElement) | Out-Null + + $informationalVersionElement = $xmlDoc.CreateElement("InformationalVersion") + $informationalVersionElement.InnerText = $csemVer.ToString($true, $false) # long form of version + $propGroupElement.AppendChild($informationalVersionElement) | Out-Null + + # Unit tests need to see the CI build info as it isn't something they can determine on their own. + # The Build index is based on a timestamp and the build name depends on the runtime environment + # to set some env vars etc... + if($buildKind -ne "ReleaseBuild") + { + $buildTimeElement = $xmlDoc.CreateElement("BuildTime") + $buildTimeElement.InnerText = $env:BuildTime + $propGroupElement.AppendChild($buildTimeElement) | Out-Null + } + + $buildGeneratedPropsPath = Join-Path $buildInfo["RepoRootPath"] "GeneratedVersion.props" + $xmlDoc.Save($buildGeneratedPropsPath) +} +catch +{ + # everything from the official docs to the various articles in the blog-sphere says this isn't needed + # and in fact it is redundant - They're all WRONG! By re-throwing the exception the original location + # information is retained and the error reported will include the correct source file and line number + # data for the error. Without this, only the error message is retained and the location information is + # Line 1, Column 1, of the outer most script file, which is, of course, completely useless. + throw +} +finally +{ + Pop-Location + $env:Path = $oldPath +} + +Write-Information "Done build" diff --git a/PsModules/RepoBuild/Private/ConvertTo-BuildIndex.ps1 b/PsModules/RepoBuild/Private/ConvertTo-BuildIndex.ps1 deleted file mode 100644 index 0b8bc0e..0000000 --- a/PsModules/RepoBuild/Private/ConvertTo-BuildIndex.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -function ConvertTo-BuildIndex -{ -<# -.SYNOPSIS - Converts a TimeStamp into a build index - -.DESCRIPTION - The algorithm used is the same as the package published. The resulting index is a 32bit value that - is a combination of the number of days since a fixed point (Upper 16 bits) and the number of seconds since - midnight (on the day of the input time stamp) divided by 2 {Lower 16 bits) -#> - param( - [Parameter(Mandatory=$true, ValueFromPipeLine)] - [DateTime]$timeStamp - ) - - $timeStamp = $timeStamp.ToUniversalTime() - $midnightTodayUtc = [DateTime]::new($timeStamp.Year, $timeStamp.Month, $timeStamp.Day, 0, 0, 0, [DateTimeKind]::Utc) - $baseDate = [DateTime]::new(2000, 1, 1, 0, 0, 0, [DateTimeKind]::Utc) - $buildNumber = ([Uint32]($timeStamp - $baseDate).Days) -shl 16 - $buildNUmber += [UInt16](($timeStamp - $midnightTodayUtc).TotalSeconds / 2) - return $buildNumber -} diff --git a/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 b/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 index 61bef94..43dd2b5 100644 --- a/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 +++ b/PsModules/RepoBuild/Public/Initialize-BuildEnvironment.ps1 @@ -1,222 +1,3 @@ -class PreReleaseVersion -{ - [ValidateSet("alpha", "beta", "delta", "epsilon", "gamma", "kappa", "prerelease", "rc")] - [string] $Name; - - [ValidateSet("a", "b", "d", "e", "g", "k", "p", "r")] - [string] $ShortName; - - [ValidateRange(-1,7)] - [int] $Index; - - [ValidateRange(0,99)] - [int] $Number; - - [ValidateRange(0,99)] - [int] $Fix; - - PreReleaseVersion([hashtable]$buildVersionXmlData) - { - $preRelName = $buildVersionXmlData['PreReleaseName'] - - if( ![string]::IsNullOrWhiteSpace( $preRelName ) ) - { - $this.Index = [PreReleaseVersion]::GetPrerelIndex($preRelName) - if($this.Index -ge 0) - { - $this.Name = [PreReleaseVersion]::PreReleaseNames[$this.Index] - $this.ShortName = [PreReleaseVersion]::PreReleaseShortNames[$this.Index] - } - - $this.Number = $buildVersionXmlData['PreReleaseNumber']; - $this.Fix = $buildVersionXmlData['PreReleaseFix']; - } - else - { - $this.Index = -1; - } - } - - [string] ToString([bool] $useShortForm = $false) - { - $hasPreRel = $this.Index -ge 0 - - $bldr = [System.Text.StringBuilder]::new() - if($hasPreRel) - { - $bldr.Append('-').Append($useShortForm ? $this.ShortName : $this.Name) - $delimFormat = $useShortForm ? '-{0:D02}' : '.{0}' - if(($this.Number -gt 0)) - { - $bldr.AppendFormat($delimFormat, $this.Number) - if(($this.Fix -gt 0)) - { - $bldr.AppendFormat($delimFormat, $this.Fix) - } - } - } - - return $bldr.ToString() - } - - hidden static [int] GetPrerelIndex([string] $preRelName) - { - $preRelIndex = -1 - if(![string]::IsNullOrWhiteSpace($preRelName)) - { - $preRelIndex = [PreReleaseVersion]::PreRleaseNames | - ForEach-Object {$index=0} {@{Name = $_; Index = $index++}} | - Where-Object {$_["Name"] -ieq $preRelName} | - ForEach-Object {$_["Index"]} | - Select-Object -First 1 - - # if not found in long names, test against the short names - if($preRelIndex -lt 0) - { - $preRelIndex = [PreReleaseVersion]::PreReleaseShortNames | - ForEach-Object {$index=0} {@{Name = $_; Index = $index++}} | - Where-Object {$_["Name"] -ieq $preRelName} | - ForEach-Object {$_["Index"]} | - Select-Object -First 1 - } - } - return $preRelIndex - } - - hidden static [string[]] $PreReleaseNames = @("alpha", "beta", "delta", "epsilon", "gamma", "kappa", "prerelease", "rc" ); - hidden static [string[]] $PreReleaseShortNames = @("a", "b", "d", "e", "g", "k", "p", "r"); -} - -class CSemVer -{ - [ValidateRange(0,99999)] - [int] $Major; - - [ValidateRange(0,49999)] - [int] $Minor; - - [ValidateRange(0,9999)] - [int] $Patch; - - [ValidateLength(0,20)] - [string] $BuildMetadata; - - [ValidatePattern('\A[a-z0-9-]+\Z')] - [string] $CiBuildIndex; - - [ValidatePattern('\A[a-z0-9-]+\Z')] - [string] $CiBuildName; - - [ulong] $OrderedVersion; - - [Version] $FileVersion; - - [PreReleaseVersion] $PreReleaseVersion; - - CSemVer([hashtable]$buildVersionXmlData) - { - $this.Major = $buildVersionXmlData["BuildMajor"] - $this.Minor = $buildVersionXmlData["BuildMinor"] - $this.Patch = $buildVersionXmlData["BuildPatch"] - if($buildVersionXmlData["PreReleaseName"]) - { - $this.PreReleaseVersion = [PreReleaseVersion]::new($buildVersionXmlData) - if(!$this.PreReleaseVersion) - { - throw "Internal ERROR: PreReleaseVersion version is NULL!" - } - } - - $this.BuildMetadata = $buildVersionXmlData["BuildMetadata"] - - $this.CiBuildName = $buildVersionXmlData["CiBuildName"]; - $this.CiBuildIndex = $buildVersionXmlData["CiBuildIndex"]; - - if( (![string]::IsNullOrEmpty( $this.CiBuildName )) -and [string]::IsNullOrEmpty( $this.CiBuildIndex ) ) - { - throw "CiBuildIndex is required if CiBuildName is provided"; - } - - if( (![string]::IsNullOrEmpty( $this.CiBuildIndex )) -and [string]::IsNullOrEmpty( $this.CiBuildName ) ) - { - throw "CiBuildName is required if CiBuildIndex is provided"; - } - - $this.OrderedVersion = [CSemVer]::GetOrderedVersion($this.Major, $this.Minor, $this.Patch, $this.PreReleaseVersion) - $fileVer64 = $this.OrderedVersion -shl 1 - if($this.CiBuildIndex -and $this.CiBuildName) - { - $fileVer64 += 1; - } - - $this.FileVersion = [CSemVer]::ConvertToVersion($fileVer64) - } - - [string] ToString([bool] $includeMetadata, [bool]$useShortForm) - { - $bldr = [System.Text.StringBuilder]::new() - $bldr.AppendFormat('{0}.{1}.{2}', $this.Major, $this.Minor, $this.Patch) - if($this.PreReleaseVersion) - { - $bldr.Append($this.PreReleaseVersion.ToString($useShortForm)) - } - - $hasPreRel = $this.PreReleaseVersion -and $this.PreReleaseVersion.Index -ge 0 - if($this.CiBuildIndex -and $this.CiBuildName) - { - $bldr.Append($hasPreRel ? '.' : '--') - $bldr.AppendFormat('ci.{0}.{1}', $this.CiBuildIndex, $this.CiBuildName) - } - - if(![string]::IsNullOrWhitespace($this.BuildMetadata) -and $includeMetadata) - { - $bldr.AppendFormat( '+{0}', $this.BuildMetadata ) - } - - return $bldr.ToString(); - } - - [string] ToString() - { - return $this.ToString($true, $false); - } - - hidden static [ulong] GetOrderedVersion($Major, $Minor, $Patch, [PreReleaseVersion] $PreReleaseVersion) - { - [ulong] $MulNum = 100; - [ulong] $MulName = $MulNum * 100; - [ulong] $MulPatch = ($MulName * 8) + 1; - [ulong] $MulMinor = $MulPatch * 10000; - [ulong] $MulMajor = $MulMinor * 50000; - - [ulong] $retVal = (([ulong]$Major) * $MulMajor) + (([ulong]$Minor) * $MulMinor) + ((([ulong]$Patch) + 1) * $MulPatch); - if( $PreReleaseVersion -and $PreReleaseVersion.Index -ge 0 ) - { - $retVal -= $MulPatch - 1; - $retVal += [ulong]($PreReleaseVersion.Index) * $MulName; - $retVal += [ulong]($PreReleaseVersion.Number) * $MulNum; - $retVal += [ulong]($PreReleaseVersion.Fix); - } - return $retVal; - } - - hidden static [Version] ConvertToVersion([ulong]$value) - { - $revision = [ushort]($value % 65536); - $rem = [ulong](($value - $revision) / 65536); - - $build = [ushort]($rem % 65536); - $rem = ($rem - $build) / 65536; - - $minorNum = [ushort]($rem % 65536); - $rem = ($rem - $minorNum) / 65536; - - $majorNum = [ushort]($rem % 65536); - - return [Version]::new( $majorNum, $minorNum, $build, $revision ); - } -} - function Initialize-BuildEnvironment { <# @@ -291,65 +72,6 @@ function Initialize-BuildEnvironment # information and generally makes it HARDER to see what's going on, not easier as it claims. $env:MSBUILDTERMINALLOGGER='off' - if($FullInit) - { - # PowerShell doesn't export enums from a script module, so the type of this return is - # "unpronounceable" [In C++ terminology]. So, convert it to a string so it is usable in this - # script. - [string] $buildKind = Get-CurrentBuildKind - - $verInfo = Get-ParsedBuildVersionXML -BuildInfo $buildInfo - if($buildKind -ne "ReleaseBuild") - { - $verInfo['CiBuildIndex'] = ConvertTo-BuildIndex $env:BuildTime - } - - switch($buildKind) - { - "LocalBuild" { $verInfo['CiBuildName'] = "ZZZ" } - "PullRequestBuild" { $verInfo['CiBuildName'] = "PRQ" } - "CiBuild" { $verInfo['CiBuildName'] = "BLD" } - "ReleaseBuild" { } - default {throw "unknown build kind" } - } - - # Generate props file with the version information for this build - # While it is plausible to use ENV vars to overload or set properties - # that leads to dangling values during development, which makes for - # a LOT of wasted time chasing down why a change didn't work... - $csemVer = [CSemVer]::New($verInfo) - $xmlDoc = [System.Xml.XmlDocument]::new() - $projectElement = $xmlDoc.CreateElement("Project") - $xmlDoc.AppendChild($projectElement) | Out-Null - - $propGroupElement = $xmlDoc.CreateElement("PropertyGroup") - $projectElement.AppendChild($propGroupElement) | Out-Null - - $fileVersionElement = $xmlDoc.CreateElement("FileVersion") - $fileVersionElement.InnerText = $csemVer.FileVersion.ToString() - $propGroupElement.AppendChild($fileVersionElement) | Out-Null - - $packageVersionElement = $xmlDoc.CreateElement("PackageVersion") - $packageVersionElement.InnerText = $csemVer.ToString($false,$true) # short form of version - $propGroupElement.AppendChild($packageVersionElement) | Out-Null - - $productVersionElement = $xmlDoc.CreateElement("ProductVersion") - $productVersionElement.InnerText = $csemVer.ToString($true, $false) # long form of version - $propGroupElement.AppendChild($productVersionElement) | Out-Null - - $assemblyVersionElement = $xmlDoc.CreateElement("AssemblyVersion") - $assemblyVersionElement.InnerText = $csemVer.FileVersion.ToString() - $propGroupElement.AppendChild($assemblyVersionElement) | Out-Null - - $informationalVersionElement = $xmlDoc.CreateElement("InformationalVersion") - $informationalVersionElement.InnerText = $csemVer.ToString($true, $false) # long form of version - $propGroupElement.AppendChild($informationalVersionElement) | Out-Null - - $buildGeneratedPropsPath = Join-Path $buildInfo["RepoRootPath"] "GeneratedVersion.props" - $xmlDoc.Save($buildGeneratedPropsPath) - - } - return $buildInfo } catch diff --git a/PsModules/RepoBuild/RepoBuild.pssproj b/PsModules/RepoBuild/RepoBuild.pssproj index f5ae29c..9287af3 100644 --- a/PsModules/RepoBuild/RepoBuild.pssproj +++ b/PsModules/RepoBuild/RepoBuild.pssproj @@ -7,7 +7,8 @@ MyApplication MyApplication RepoBuild - LlvmCore + + true @@ -46,23 +47,19 @@ within it. --> - - - - + <_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" /> - - - - + + + + - + \ No newline at end of file diff --git a/src/Ubiquity.NET.Versioning.Build.Tasks/build/Ubiquity.NET.Versioning.Build.Tasks.targets b/src/Ubiquity.NET.Versioning.Build.Tasks/build/Ubiquity.NET.Versioning.Build.Tasks.targets index 6b18ae2..9536cc5 100644 --- a/src/Ubiquity.NET.Versioning.Build.Tasks/build/Ubiquity.NET.Versioning.Build.Tasks.targets +++ b/src/Ubiquity.NET.Versioning.Build.Tasks/build/Ubiquity.NET.Versioning.Build.Tasks.targets @@ -72,10 +72,10 @@ - - - - + + + + diff --git a/src/Ubiquity.NET.Versioning.slnx b/src/Ubiquity.NET.Versioning.slnx index 738fb5f..2d070b0 100644 --- a/src/Ubiquity.NET.Versioning.slnx +++ b/src/Ubiquity.NET.Versioning.slnx @@ -12,6 +12,7 @@ + diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs index 37432df..0ad2410 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs @@ -11,6 +11,7 @@ using System.Runtime.Loader; using System.Xml.Linq; +using Microsoft.Build.Definition; using Microsoft.Build.Evaluation; using Microsoft.Build.Utilities.ProjectCreation; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -47,16 +48,9 @@ public void ValidateRepoAssemblyVersion( string targetFramework) var globalProperties = new Dictionary { ["BuildVersionXml"] = Path.Combine(RepoRoot, "BuildVersion.xml"), + ["BuildTime"] = GetBuildTime() }; - string envBuildTime = TestModuleFixtures.EnvBuildTime; - bool isIDEBuild = true; - if( !string.IsNullOrWhiteSpace(envBuildTime)) - { - globalProperties["BuildTime"] = envBuildTime; - isIDEBuild = false; - } - using var collection = new ProjectCollection(globalProperties); var (testProject, props) = CreateTestProjectAndInvokeTestedPackage( @@ -113,35 +107,7 @@ where val is not null select val ).Single(); - // Check for limited version information in an IDE run as the "BuildTime" environment, - // which is translated to CIBuildIndex, is hard coded to the max value. Additionally, - // in a release build there is NO CI information to extract so it is inconclusive. - // Full testing is formally inconclusive but at least validate what is plausible and - // treat the rest as inconclusive - if (isIDEBuild) - { - Context.WriteLine($"AssemblyInformationalVersion {informationalVersion}"); - - Assert.IsNotNull(props.InformationalVersion); - - // release builds won't have ANY CI information. - int cipos = props.InformationalVersion.IndexOf(".ci", StringComparison.Ordinal); - if(cipos > 0) - { - string propsVersion = props.InformationalVersion[ ..cipos]; - - int ideCiBuildPos = informationalVersion.IndexOf(".ci"); - informationalVersion = informationalVersion[ ..ideCiBuildPos]; - - Assert.AreEqual(propsVersion, informationalVersion); - } - - Assert.Inconclusive("IDE build verification is inconclusive; but at least matches expectations"); - } - else - { - Assert.AreEqual(props.InformationalVersion, informationalVersion); - } + Assert.AreEqual(props.InformationalVersion, informationalVersion); } finally { @@ -394,5 +360,17 @@ private string CreateBuildVersionXml( Context.WriteLine( $"BuildVersionXML written to: '{retVal}'" ); return retVal; } + + private static string GetBuildTime( ) + { + using var dummyCollection = new ProjectCollection(); + var options = new ProjectOptions() + { + ProjectCollection = dummyCollection + }; + + var project = Project.FromFile(Path.Combine(TestModuleFixtures.RepoRoot, "GeneratedVersion.props"), options); + return project.GetPropertyValue("BuildTime"); + } } } diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs index 2aa1766..9b975d5 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs @@ -5,7 +5,9 @@ // ----------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Globalization; using System.IO; using Microsoft.Build.Utilities.ProjectCreation; @@ -13,6 +15,14 @@ namespace Ubiquity.Versioning.Build.Tasks.UT { + internal enum BuildKind + { + LocalBuild, + PullRequestBuild, + CiBuild, + ReleaseBuild, + } + // Provides common location for one time initialization for all tests in this assembly // Doing the package repo construction here, allows tests to run in parallel without // hitting access denied errors due to the use of the location in some other test. @@ -22,39 +32,35 @@ public static class TestModuleFixtures [AssemblyInitialize] public static void AssemblyInitialize( TestContext ctx ) { - ArgumentNullException.ThrowIfNull( ctx ); - ArgumentException.ThrowIfNullOrWhiteSpace( ctx.TestRunDirectory ); + ArgumentNullException.ThrowIfNull(ctx); + ArgumentException.ThrowIfNullOrWhiteSpace(ctx.TestRunDirectory); // This assumes the solutions '.runsettings' file has re-directed the run output // to a well known location. There's no way to "pass" in the location to the build // of this test. - string buildOutputPath = Path.GetFullPath( Path.Combine(ctx.TestRunDirectory, "..", "..")); - string buildRoot = Path.GetFullPath( Path.Combine(buildOutputPath, "..")); + BuildOutputPath = Path.GetFullPath(Path.Combine(ctx.TestRunDirectory, "..", "..")); + RepoRoot = Path.GetFullPath(Path.Combine(BuildOutputPath, "..")); // Generate fake directory.build.[props|targets] in the test run directory to prevent MSBUILD // from searching beyond these to find the REPO one in the root (It contains things that will // interfere with deterministic testing). ProjectCreator.Create() - .Save( Path.Combine( ctx.TestRunDirectory, "Directory.Build.props" ) ); + .Save(Path.Combine(ctx.TestRunDirectory, "Directory.Build.props")); ProjectCreator.Create() - .Save( Path.Combine( ctx.TestRunDirectory, "Directory.Build.targets" ) ); + .Save(Path.Combine(ctx.TestRunDirectory, "Directory.Build.targets")); // Ensure environment is clear of any overrides to ensure tests are validating the correct behavior // Individual tests MAY set these again. To prevent interference with other tests, any such test // needing to set these must restore them (even on exceptional exit) via a try/finally or using pattern. - foreach(string envVar in TaskInputPropertyNames) + foreach (string envVar in TaskInputPropertyNames) { string? value = Environment.GetEnvironmentVariable(envVar); - if(value is not null) + if (value is not null) { - // Save the environment build time for use with task assembly builds as that - // value is needed to determine the build index used for non-IDE builds - if(envVar == "BuildTime") - { - EnvBuildTime = value; - } - + // Save the environment var value for use with task assembly build verification + // as these are needed to verify the assembly build details + OriginalInputVars.Add(envVar, value); Environment.SetEnvironmentVariable(envVar, null); } } @@ -63,8 +69,8 @@ public static void AssemblyInitialize( TestContext ctx ) // test project files or the settings will NOT apply! PackageRepo = PackageRepository.Create( ctx.TestRunDirectory, // '.nuget/packages' repo folder goes here - new Uri( Path.Combine( buildOutputPath, "NuGet" ) ), // Local feed (Contains location of the build of the package under test) - new Uri( "https://api.nuget.org/v3/index.json" ) // standard NuGet Feed + new Uri(Path.Combine(BuildOutputPath, "NuGet")), // Local feed (Contains location of the build of the package under test) + new Uri("https://api.nuget.org/v3/index.json") // standard NuGet Feed ); } @@ -74,9 +80,63 @@ public static void AssemblyCleanup( ) PackageRepo?.Dispose(); } - internal static string EnvBuildTime { get; private set; } = string.Empty; + internal static string BuildOutputPath { get; private set; } = string.Empty; + + internal static string RepoRoot { get; private set; } = string.Empty; + + // This mirrors the implementation in CommonBuild/GetCurrentBuildKind.ps1 + // This is very build environment Back-end specific and currently only supports + // APPVEYOR (unused or tested in a LONG time!) and GitHub Actions (Currently active) + internal static BuildKind BuildKind + { + get + { + var retVal = BuildKind.LocalBuild; + bool appveyorBuild = Convert.ToBoolean(Environment.GetEnvironmentVariable("APPVEYOR") ?? "false", CultureInfo.InvariantCulture); + bool gitHubActionsBuild = Convert.ToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS") ?? "false", CultureInfo.InvariantCulture); + + // isAutomatedBuild is the top level gate (e.g. if it is false, all the others must be false) + bool isAutomatedBuild = Convert.ToBoolean(Environment.GetEnvironmentVariable("CI") ?? "false", CultureInfo.InvariantCulture) + || appveyorBuild + || gitHubActionsBuild; + if (isAutomatedBuild) + { + // PR and release builds have externally detected indicators that are tested + // below, so default to a CiBuild (e.g. not a PR, And not a RELEASE) + retVal = BuildKind.CiBuild; + bool isPullRequestBuild = Environment.GetEnvironmentVariable("GITHUB_BASE_REF") != null + || Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER") != null; + if (isPullRequestBuild) + { + retVal = BuildKind.PullRequestBuild; + } + else + { + bool isReleaseBuild = false; + if (appveyorBuild) + { + isReleaseBuild = Environment.GetEnvironmentVariable("APPVEYOR_REPO_TAG") != null; + } + else if (gitHubActionsBuild) + { + string commitRef = Environment.GetEnvironmentVariable("GITHUB_REF") ?? string.Empty; + isReleaseBuild = commitRef.StartsWith("refs/tags"); + } + + if (isReleaseBuild) + { + retVal = BuildKind.ReleaseBuild; + } + } + } + + return retVal; + } + } + + internal static readonly Dictionary OriginalInputVars = []; - private static void CopyFile(string srcDir, string fileName, string dstDir) + private static void CopyFile( string srcDir, string fileName, string dstDir ) { File.Copy(Path.Combine(srcDir, fileName), Path.Combine(dstDir, fileName), overwrite: true); } From 93c3c278b302204b96f43ed7cfc24b7b9a7e78c2 Mon Sep 17 00:00:00 2001 From: smaillet <25911635+smaillet@users.noreply.github.com> Date: Sun, 1 Jun 2025 14:54:47 -0700 Subject: [PATCH 4/5] Added support to extract the CiBuildName from the build environment * Added additional logging to build tasks to aid with diagnostics - These are now low priority messages so that don't normally appear in output * Removed determination of build kind from tests as that's a build script only thing not needed in the tests now. --- New-GeneratedVersionProps.ps1 | 13 ++++- .../CreateVersionInfo.cs | 17 ++++-- .../GetBuildIndexFromTime.cs | 8 +++ .../ParseBuildVersionXml.cs | 13 ++++- .../BuildTaskTests.cs | 25 ++++++-- .../ProjectInstanceExtensions.cs | 28 +++++---- .../TestModuleFixtures.cs | 58 ------------------- 7 files changed, 79 insertions(+), 83 deletions(-) diff --git a/New-GeneratedVersionProps.ps1 b/New-GeneratedVersionProps.ps1 index 0f94dfd..8739abe 100644 --- a/New-GeneratedVersionProps.ps1 +++ b/New-GeneratedVersionProps.ps1 @@ -35,18 +35,21 @@ function ConvertTo-BuildIndex .DESCRIPTION The algorithm used is the same as the package published. The resulting index is a 32bit value that is a combination of the number of days since a fixed point (Upper 16 bits) and the number of seconds since - midnight (on the day of the input time stamp) divided by 2 {Lower 16 bits) + midnight (on the day of the input time stamp) divided by 2 (Lower 16 bits) #> param( [Parameter(Mandatory=$true, ValueFromPipeLine)] [DateTime]$timeStamp ) + $commonBaseDate = [DateTime]::new(2000, 1, 1, 0, 0, 0, [DateTimeKind]::Utc) + $timeStamp = $timeStamp.ToUniversalTime() $midnightTodayUtc = [DateTime]::new($timeStamp.Year, $timeStamp.Month, $timeStamp.Day, 0, 0, 0, [DateTimeKind]::Utc) - $baseDate = [DateTime]::new(2000, 1, 1, 0, 0, 0, [DateTimeKind]::Utc) - $buildNumber = ([Uint32]($timeStamp - $baseDate).Days) -shl 16 + + $buildNumber = ([Uint32]($timeStamp - $commonBaseDate).Days) -shl 16 $buildNUmber += [UInt16](($timeStamp - $midnightTodayUtc).TotalSeconds / 2) + return $buildNumber } @@ -348,6 +351,10 @@ try $buildTimeElement = $xmlDoc.CreateElement("BuildTime") $buildTimeElement.InnerText = $env:BuildTime $propGroupElement.AppendChild($buildTimeElement) | Out-Null + + $ciBuildNameElement = $xmlDoc.CreateElement("CiBuildName") + $ciBuildNameElement.InnerText = $verInfo['CiBuildName'] + $propGroupElement.AppendChild($ciBuildNameElement) | Out-Null } $buildGeneratedPropsPath = Join-Path $buildInfo["RepoRootPath"] "GeneratedVersion.props" diff --git a/src/Ubiquity.NET.Versioning.Build.Tasks/CreateVersionInfo.cs b/src/Ubiquity.NET.Versioning.Build.Tasks/CreateVersionInfo.cs index 95d760d..34fc216 100644 --- a/src/Ubiquity.NET.Versioning.Build.Tasks/CreateVersionInfo.cs +++ b/src/Ubiquity.NET.Versioning.Build.Tasks/CreateVersionInfo.cs @@ -71,7 +71,7 @@ public override bool Execute( ) { try { - Log.LogMessage(MessageImportance.High, $"+{nameof(CreateVersionInfo)} Task"); + Log.LogMessage(MessageImportance.Low, $"+{nameof(CreateVersionInfo)} Task"); if(!ValidateInput()) { @@ -79,14 +79,21 @@ public override bool Execute( ) return false; } + Log.LogMessage(MessageImportance.Low, "CiBuildIndex={0}", CiBuildIndex ?? string.Empty); + Log.LogMessage(MessageImportance.Low, "CiBuildName={0}", CiBuildName ?? string.Empty); + Log.LogMessage(MessageImportance.Low, "BuildMeta={0}", BuildMeta ?? string.Empty); + Log.LogMessage(MessageImportance.Low, "PreReleaseName={0}", PreReleaseName ?? string.Empty); + Log.LogMessage(MessageImportance.Low, "PreReleaseNumber={0}", PreReleaseNumber); + Log.LogMessage(MessageImportance.Low, "PreReleaseFix={0}", PreReleaseFix); + int preRelIndex = ComputePreReleaseIndex( PreReleaseName!); - Log.LogMessage(MessageImportance.High, "PreRelIndex={0}", preRelIndex); + Log.LogMessage(MessageImportance.Low, "PreRelIndex={0}", preRelIndex); CSemVer = CreateSemVerString( preRelIndex ); - Log.LogMessage(MessageImportance.High, "CSemVer={0}", CSemVer ?? string.Empty); + Log.LogMessage(MessageImportance.Low, "CSemVer={0}", CSemVer ?? string.Empty); ShortCSemVer = CreateSemVerString( preRelIndex, useShortForm: true ); - Log.LogMessage(MessageImportance.High, "ShortCSemVer={0}", ShortCSemVer ?? string.Empty); + Log.LogMessage(MessageImportance.Low, "ShortCSemVer={0}", ShortCSemVer ?? string.Empty); SetFileVersion( preRelIndex ); return true; @@ -98,7 +105,7 @@ public override bool Execute( ) } finally { - Log.LogMessage(MessageImportance.High, $"-{nameof(CreateVersionInfo)} Task"); + Log.LogMessage(MessageImportance.Low, $"-{nameof(CreateVersionInfo)} Task"); } } diff --git a/src/Ubiquity.NET.Versioning.Build.Tasks/GetBuildIndexFromTime.cs b/src/Ubiquity.NET.Versioning.Build.Tasks/GetBuildIndexFromTime.cs index 774086b..4869505 100644 --- a/src/Ubiquity.NET.Versioning.Build.Tasks/GetBuildIndexFromTime.cs +++ b/src/Ubiquity.NET.Versioning.Build.Tasks/GetBuildIndexFromTime.cs @@ -23,16 +23,24 @@ public class GetBuildIndexFromTime public override bool Execute( ) { + Log.LogMessage(MessageImportance.Low, $"+{nameof(GetBuildIndexFromTime)} Task"); + // establish an increasing build index based on the number of seconds from a common UTC date var timeStamp = TimeStamp.ToUniversalTime( ); + Log.LogMessage(MessageImportance.Low, $"Time Stamp(UTC; ISO-8601): {timeStamp:o}"); // Upper 16 bits of the build number is the number of days since the common base value uint buildNumber = ((uint)(timeStamp - CommonBaseDate).Days) << 16; + Log.LogMessage(MessageImportance.Low, $"BuildNumber (upper): 0x{buildNumber:X04}"); // Lower 16 bits is the number of seconds (divided by 2) since midnight (on the date of the time stamp) var midnightTodayUtc = new DateTime( timeStamp.Year, timeStamp.Month, timeStamp.Day, 0, 0, 0, DateTimeKind.Utc ); buildNumber += (ushort)((timeStamp - midnightTodayUtc).TotalSeconds / 2); + Log.LogMessage(MessageImportance.Low, $"BuildNumber (full): 0x{buildNumber:X04}"); + BuildIndex = buildNumber.ToString( CultureInfo.InvariantCulture ); + Log.LogMessage(MessageImportance.Low, $"BuildIndex set to: {BuildIndex}"); + Log.LogMessage(MessageImportance.Low, $"-{nameof(GetBuildIndexFromTime)} Task"); return true; } diff --git a/src/Ubiquity.NET.Versioning.Build.Tasks/ParseBuildVersionXml.cs b/src/Ubiquity.NET.Versioning.Build.Tasks/ParseBuildVersionXml.cs index f3ff39a..85a1b91 100644 --- a/src/Ubiquity.NET.Versioning.Build.Tasks/ParseBuildVersionXml.cs +++ b/src/Ubiquity.NET.Versioning.Build.Tasks/ParseBuildVersionXml.cs @@ -41,6 +41,8 @@ public override bool Execute( ) { try { + Log.LogMessage(MessageImportance.Low, $"+{nameof(ParseBuildVersionXml)} Task"); + using var stream = File.OpenText( BuildVersionXml ); var xdoc = System.Xml.Linq.XDocument.Load( stream, System.Xml.Linq.LoadOptions.None ); var data = xdoc.Element( "BuildVersionData" ); @@ -50,26 +52,32 @@ public override bool Execute( ) switch( attrib.Name.LocalName ) { case "BuildMajor": + Log.LogMessage(MessageImportance.Low, $"BuildMajor: {attrib.Value}"); BuildMajor = attrib.Value; break; case "BuildMinor": + Log.LogMessage(MessageImportance.Low, $"BuildMinor: {attrib.Value}"); BuildMinor = attrib.Value; break; case "BuildPatch": + Log.LogMessage(MessageImportance.Low, $"BuildPatch: {attrib.Value}"); BuildPatch = attrib.Value; break; case "PreReleaseName": + Log.LogMessage(MessageImportance.Low, $"PreReleaseName: {attrib.Value}"); PreReleaseName = attrib.Value; break; case "PreReleaseNumber": + Log.LogMessage(MessageImportance.Low, $"PreReleaseNumber: {attrib.Value}"); PreReleaseNumber = attrib.Value; break; case "PreReleaseFix": + Log.LogMessage(MessageImportance.Low, $"PreReleaseFix: {attrib.Value}"); PreReleaseFix = attrib.Value; break; @@ -82,15 +90,18 @@ public override bool Execute( ) // correct malformed values if( string.IsNullOrWhiteSpace( PreReleaseName ) ) { + Log.LogMessage(MessageImportance.Low, "PreReleaseName not provided, forcing PreReleaseNumber and PreReleaseFix == 0"); PreReleaseNumber = "0"; PreReleaseFix = "0"; } - if( PreReleaseNumber == "0" ) + if( PreReleaseNumber == "0" && PreReleaseFix != "0") { + Log.LogMessage(MessageImportance.Low, "PreReleaseNumber is 0; forcing PreReleaseFix == 0"); PreReleaseFix = "0"; } + Log.LogMessage(MessageImportance.Low, $"-{nameof(ParseBuildVersionXml)} Task"); return true; } catch(Exception ex) diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs index 0ad2410..828189e 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs @@ -48,9 +48,26 @@ public void ValidateRepoAssemblyVersion( string targetFramework) var globalProperties = new Dictionary { ["BuildVersionXml"] = Path.Combine(RepoRoot, "BuildVersion.xml"), - ["BuildTime"] = GetBuildTime() }; + // For a CI build load the build time and ciBuild name from the generatedversion.props file + // so the test knows what to expect. This verifies that the task creation of versions agrees + // with how the build scripts created the version information. (Not the task in general) + // That is, this test case is NOT free of the build environment and MUST take it into consideration + // to properly verify the build scripts are doing things correctly. The CI Index is created from + // the time stamp and the name is either provided directly as a build property or computed in the + // Ubiquity.NET.Versioning.Build.Tasks.props file. + var (buildTime, ciBuildName) = GetGeneratedCiBuildInfo(); + if(!string.IsNullOrWhiteSpace(ciBuildName)) + { + globalProperties["CiBuildName"] = ciBuildName; + } + + if(!string.IsNullOrWhiteSpace(buildTime)) + { + globalProperties["BuildTime"] = buildTime; + } + using var collection = new ProjectCollection(globalProperties); var (testProject, props) = CreateTestProjectAndInvokeTestedPackage( @@ -272,7 +289,7 @@ public void BuildVersionXmlIsUsed( string targetFramework) Assert.IsTrue( resolveResult ); // Since this project uses an imported target, it won't even exist until AFTER ResolvePackageDependencies[DesignTime|ForBuild] comes along. - var result = testProject.ProjectInstance.Build("PrepareVersioningForBuild"); + var (result, output) = testProject.ProjectInstance.Build("PrepareVersioningForBuild"); Assert.IsNotNull( result ); Assert.IsNotNull( result.ProjectStateAfterBuild ); @@ -361,7 +378,7 @@ private string CreateBuildVersionXml( return retVal; } - private static string GetBuildTime( ) + private static (string BuildTime, string CiBuildName) GetGeneratedCiBuildInfo( ) { using var dummyCollection = new ProjectCollection(); var options = new ProjectOptions() @@ -370,7 +387,7 @@ private static string GetBuildTime( ) }; var project = Project.FromFile(Path.Combine(TestModuleFixtures.RepoRoot, "GeneratedVersion.props"), options); - return project.GetPropertyValue("BuildTime"); + return (project.GetPropertyValue("BuildTime"), project.GetPropertyValue("CiBuildName")); } } } diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/ProjectInstanceExtensions.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/ProjectInstanceExtensions.cs index 575a644..e2d4271 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/ProjectInstanceExtensions.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/ProjectInstanceExtensions.cs @@ -8,19 +8,20 @@ using System.Globalization; using Microsoft.Build.Execution; +using Microsoft.Build.Utilities.ProjectCreation; namespace Ubiquity.Versioning.Build.Tasks.UT { internal static class ProjectInstanceExtensions { - public static T? GetPropertyAs(this ProjectInstance self, string name, T? defaultValue = null) + public static T? GetPropertyAs( this ProjectInstance self, string name, T? defaultValue = null ) where T : struct { var prop = self.GetProperty(name); return prop is null ? defaultValue : (T?)Convert.ChangeType(prop.EvaluatedValue, typeof(T), CultureInfo.InvariantCulture); } - public static string? GetOptionalProperty(this ProjectInstance self, string name, string? defaultValue = null) + public static string? GetOptionalProperty( this ProjectInstance self, string name, string? defaultValue = null ) { var prop = self.GetProperty(name); return prop is null ? defaultValue : prop.EvaluatedValue; @@ -28,17 +29,20 @@ internal static class ProjectInstanceExtensions // Sadly, project creator library doesn't have support for after build state retrieval... // Fortunately, it is fairly easy to create an extension to handle that scenario. - public static BuildResult Build(this ProjectInstance self, params string[] targetsToBuild) + public static (BuildResult BuildResult, BuildOutput Output) Build( this ProjectInstance self, params string[] targetsToBuild ) { - return BuildManager.DefaultBuildManager.Build( - new BuildParameters(), - new BuildRequestData( - self, - targetsToBuild, - new HostServices(), - BuildRequestDataFlags.ProvideProjectStateAfterBuild - ) - ); + // !@#$ project creator hides new and uses a dumb wrapper for create. [Sigh...] + var buildOutput = BuildOutput.Create(); + var result = BuildManager.DefaultBuildManager.Build( + new BuildParameters() { Loggers = [buildOutput] }, + new BuildRequestData( + self, + targetsToBuild, + new HostServices(), + BuildRequestDataFlags.ProvideProjectStateAfterBuild + ) + ); + return (result, buildOutput); } } } diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs index 9b975d5..84aad9c 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/TestModuleFixtures.cs @@ -15,14 +15,6 @@ namespace Ubiquity.Versioning.Build.Tasks.UT { - internal enum BuildKind - { - LocalBuild, - PullRequestBuild, - CiBuild, - ReleaseBuild, - } - // Provides common location for one time initialization for all tests in this assembly // Doing the package repo construction here, allows tests to run in parallel without // hitting access denied errors due to the use of the location in some other test. @@ -84,56 +76,6 @@ public static void AssemblyCleanup( ) internal static string RepoRoot { get; private set; } = string.Empty; - // This mirrors the implementation in CommonBuild/GetCurrentBuildKind.ps1 - // This is very build environment Back-end specific and currently only supports - // APPVEYOR (unused or tested in a LONG time!) and GitHub Actions (Currently active) - internal static BuildKind BuildKind - { - get - { - var retVal = BuildKind.LocalBuild; - bool appveyorBuild = Convert.ToBoolean(Environment.GetEnvironmentVariable("APPVEYOR") ?? "false", CultureInfo.InvariantCulture); - bool gitHubActionsBuild = Convert.ToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS") ?? "false", CultureInfo.InvariantCulture); - - // isAutomatedBuild is the top level gate (e.g. if it is false, all the others must be false) - bool isAutomatedBuild = Convert.ToBoolean(Environment.GetEnvironmentVariable("CI") ?? "false", CultureInfo.InvariantCulture) - || appveyorBuild - || gitHubActionsBuild; - if (isAutomatedBuild) - { - // PR and release builds have externally detected indicators that are tested - // below, so default to a CiBuild (e.g. not a PR, And not a RELEASE) - retVal = BuildKind.CiBuild; - bool isPullRequestBuild = Environment.GetEnvironmentVariable("GITHUB_BASE_REF") != null - || Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER") != null; - if (isPullRequestBuild) - { - retVal = BuildKind.PullRequestBuild; - } - else - { - bool isReleaseBuild = false; - if (appveyorBuild) - { - isReleaseBuild = Environment.GetEnvironmentVariable("APPVEYOR_REPO_TAG") != null; - } - else if (gitHubActionsBuild) - { - string commitRef = Environment.GetEnvironmentVariable("GITHUB_REF") ?? string.Empty; - isReleaseBuild = commitRef.StartsWith("refs/tags"); - } - - if (isReleaseBuild) - { - retVal = BuildKind.ReleaseBuild; - } - } - } - - return retVal; - } - } - internal static readonly Dictionary OriginalInputVars = []; private static void CopyFile( string srcDir, string fileName, string dstDir ) From 9ecd69fbe5044f818f909dcdd899a9371b8da396 Mon Sep 17 00:00:00 2001 From: smaillet <25911635+smaillet@users.noreply.github.com> Date: Sun, 1 Jun 2025 16:35:18 -0700 Subject: [PATCH 5/5] Adjusted tests to use the build index from the generatedversion.props * Sadly the buildIndex from a time stamp is non-deterministic making it impossible to create tests with an exact match. - Tests would need to adjust to acount for "close enough" - or, the better answer, I think, is that a new deterministic computation method is needed. --- New-GeneratedVersionProps.ps1 | 6 ++--- .../BuildTaskTests.cs | 26 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/New-GeneratedVersionProps.ps1 b/New-GeneratedVersionProps.ps1 index 8739abe..383d51e 100644 --- a/New-GeneratedVersionProps.ps1 +++ b/New-GeneratedVersionProps.ps1 @@ -348,9 +348,9 @@ try # to set some env vars etc... if($buildKind -ne "ReleaseBuild") { - $buildTimeElement = $xmlDoc.CreateElement("BuildTime") - $buildTimeElement.InnerText = $env:BuildTime - $propGroupElement.AppendChild($buildTimeElement) | Out-Null + $ciBuildIndexElement = $xmlDoc.CreateElement("CiBuildIndex") + $ciBuildIndexElement.InnerText = $verInfo['CiBuildIndex'] + $propGroupElement.AppendChild($ciBuildIndexElement) | Out-Null $ciBuildNameElement = $xmlDoc.CreateElement("CiBuildName") $ciBuildNameElement.InnerText = $verInfo['CiBuildName'] diff --git a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs index 828189e..e73b7ee 100644 --- a/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs +++ b/src/Ubiquity.Versioning.Build.Tasks.UT/BuildTaskTests.cs @@ -50,22 +50,24 @@ public void ValidateRepoAssemblyVersion( string targetFramework) ["BuildVersionXml"] = Path.Combine(RepoRoot, "BuildVersion.xml"), }; - // For a CI build load the build time and ciBuild name from the generatedversion.props file - // so the test knows what to expect. This verifies that the task creation of versions agrees - // with how the build scripts created the version information. (Not the task in general) - // That is, this test case is NOT free of the build environment and MUST take it into consideration - // to properly verify the build scripts are doing things correctly. The CI Index is created from - // the time stamp and the name is either provided directly as a build property or computed in the - // Ubiquity.NET.Versioning.Build.Tasks.props file. - var (buildTime, ciBuildName) = GetGeneratedCiBuildInfo(); + // For a CI build load the ciBuildIndex d time and ciBuildName from the generatedversion.props file + // so the test knows what to expect. This does NOT verify the behavior of the tasks exactly unfortunately. + // There is a non-determinism in computing the index based on a time stamp in particular a single date/time + // string is converted based on seconds since midnight today (in UTC) so if two different implementations + // compute a value at a different time that varies by as much as 2 seconds, then they will produce different + // results even if behaving correctly. The use of seconds since midnight today makes it non-deterministic... + // Unfortunately that's the algorithm chosen, though since this is a major release (and a full rename that + // is something to re-visit) Until, that is deterministic, use the generated CI info all up. Other tests will + // need to validate the behavior of the task. + var (ciBuildIndex, ciBuildName) = GetGeneratedCiBuildInfo(); if(!string.IsNullOrWhiteSpace(ciBuildName)) { globalProperties["CiBuildName"] = ciBuildName; } - if(!string.IsNullOrWhiteSpace(buildTime)) + if(!string.IsNullOrWhiteSpace(ciBuildIndex)) { - globalProperties["BuildTime"] = buildTime; + globalProperties["CiBuildIndex"] = ciBuildIndex; } using var collection = new ProjectCollection(globalProperties); @@ -378,7 +380,7 @@ private string CreateBuildVersionXml( return retVal; } - private static (string BuildTime, string CiBuildName) GetGeneratedCiBuildInfo( ) + private static (string CiBuildIndex, string CiBuildName) GetGeneratedCiBuildInfo( ) { using var dummyCollection = new ProjectCollection(); var options = new ProjectOptions() @@ -387,7 +389,7 @@ private static (string BuildTime, string CiBuildName) GetGeneratedCiBuildInfo( ) }; var project = Project.FromFile(Path.Combine(TestModuleFixtures.RepoRoot, "GeneratedVersion.props"), options); - return (project.GetPropertyValue("BuildTime"), project.GetPropertyValue("CiBuildName")); + return (project.GetPropertyValue("CiBuildIndex"), project.GetPropertyValue("CiBuildName")); } } }