diff --git a/.azure-pipelines/.vsts.release.yml b/.azure-pipelines/.vsts.release.yml index f6aebb06fdc2..a24d75ad1748 100644 --- a/.azure-pipelines/.vsts.release.yml +++ b/.azure-pipelines/.vsts.release.yml @@ -44,6 +44,10 @@ parameters: displayName: Dry Run (no push, no PR, no feed publish) type: boolean default: false +- name: useSemverBuildConfig + displayName: Use Semver Build Config to support canary rollout + type: boolean + default: false variables: - name: currentDate @@ -134,6 +138,7 @@ extends: - template: /ci/build-all-steps.yml@self parameters: os: Windows_NT + useSemverBuildConfig: ${{ parameters.useSemverBuildConfig }} # Publish - job: publish @@ -217,4 +222,5 @@ extends: - template: /ci/build-all-tasks.yml@self parameters: deploy_all_tasks: ${{ eq(parameters.task_deployment, 'Deploy all Tasks') }} + useSemverBuildConfig: ${{ parameters.useSemverBuildConfig }} \ No newline at end of file diff --git a/BuildConfigGen/Debugging/VsCodeLaunchConfigGenerator.cs b/BuildConfigGen/Debugging/VsCodeLaunchConfigGenerator.cs index 652d6313fe75..d87392b6d71e 100644 --- a/BuildConfigGen/Debugging/VsCodeLaunchConfigGenerator.cs +++ b/BuildConfigGen/Debugging/VsCodeLaunchConfigGenerator.cs @@ -42,8 +42,9 @@ public void AddForTask(string taskConfigPath) int major = versionNode["Major"]!.GetValue(); int minor = versionNode["Minor"]!.GetValue(); int patch = versionNode["Patch"]!.GetValue(); + string? build = versionNode["Build"]?.GetValue() ?? null; - var version = new TaskVersion(major, minor, patch); + var version = new TaskVersion(major, minor, patch, build); LaunchConfig.AddConfigForTask( taskId: taskConfig["id"]!.GetValue(), diff --git a/BuildConfigGen/Program.cs b/BuildConfigGen/Program.cs index 22ea62700a3c..7aaf41484edd 100644 --- a/BuildConfigGen/Program.cs +++ b/BuildConfigGen/Program.cs @@ -46,7 +46,7 @@ internal static class Config { public static readonly string[] ExtensionsToPreprocess = new[] { ".ts", ".json" }; - public record ConfigRecord(string name, string constMappingKey, bool isDefault, bool isNode, string nodePackageVersion, bool isWif, string nodeHandler, string preprocessorVariableName, bool enableBuildConfigOverrides, bool deprecated, bool shouldUpdateTypescript, bool writeNpmrc, string? overriddenDirectoryName = null, bool shouldUpdateLocalPkgs = false, bool useGlobalVersion = false, bool useAltGeneratedPath = false, bool mergeToBase = false); + public record ConfigRecord(string name, string constMappingKey, bool isDefault, bool isNode, string nodePackageVersion, bool isWif, string nodeHandler, string preprocessorVariableName, bool enableBuildConfigOverrides, bool deprecated, bool shouldUpdateTypescript, bool writeNpmrc, string? overriddenDirectoryName = null, bool shouldUpdateLocalPkgs = false, bool useGlobalVersion = false, bool useAltGeneratedPath = false, bool mergeToBase = false, bool abTaskReleases = true); public static readonly ConfigRecord Default = new ConfigRecord(name: nameof(Default), constMappingKey: "Default", isDefault: true, isNode: false, nodePackageVersion: "", isWif: false, nodeHandler: "", preprocessorVariableName: "DEFAULT", enableBuildConfigOverrides: false, deprecated: false, shouldUpdateTypescript: false, writeNpmrc: false); public static readonly ConfigRecord Node16 = new ConfigRecord(name: nameof(Node16), constMappingKey: "Node16-219", isDefault: false, isNode: true, nodePackageVersion: "^16.11.39", isWif: false, nodeHandler: "Node16", preprocessorVariableName: "NODE16", enableBuildConfigOverrides: true, deprecated: true, shouldUpdateTypescript: false, writeNpmrc: false); @@ -86,12 +86,22 @@ public record ConfigRecord(string name, string constMappingKey, bool isDefault, /// /// When set to the local pipeline agent directory, this tool will produce tasks in debug mode with the corresponding visual studio launch configurations that can be used to attach to built tasks running on this agent /// Include LocalPackagesBuildConfig - static void Main(string? task = null, string? configs = null, int? currentSprint = null, bool writeUpdates = false, bool allTasks = false, bool getTaskVersionTable = false, string? debugAgentDir = null, bool includeLocalPackagesBuildConfig = false) + /// If true, the semver "build" (suffix) will be generated for each task configuration produced, but all tasks configurations will have the same version (for example '1.2.3-node20' and 1.2.3-wif). The default configuration gets no build suffix (e.g. 1.2.3). + static void Main( + string? task = null, + string? configs = null, + int? currentSprint = null, + bool writeUpdates = false, + bool allTasks = false, + bool getTaskVersionTable = false, + string? debugAgentDir = null, + bool includeLocalPackagesBuildConfig = false, + bool useSemverBuildConfig = false) { try { ensureUpdateModeVerifier = new EnsureUpdateModeVerifier(!writeUpdates); - MainInner(task, configs, currentSprint, writeUpdates, allTasks, getTaskVersionTable, debugAgentDir, includeLocalPackagesBuildConfig); + MainInner(task, configs, currentSprint, writeUpdates, allTasks, getTaskVersionTable, debugAgentDir, includeLocalPackagesBuildConfig, useSemverBuildConfig); } catch (Exception e2) { @@ -113,7 +123,16 @@ static void Main(string? task = null, string? configs = null, int? currentSprint } } - private static void MainInner(string? task, string? configs, int? currentSprintNullable, bool writeUpdates, bool allTasks, bool getTaskVersionTable, string? debugAgentDir, bool includeLocalPackagesBuildConfig) + private static void MainInner( + string? task, + string? configs, + int? currentSprintNullable, + bool writeUpdates, + bool allTasks, + bool getTaskVersionTable, + string? debugAgentDir, + bool includeLocalPackagesBuildConfig, + bool useSemverBuildConfig) { if (allTasks) { @@ -313,7 +332,18 @@ private static void MainInner(string? task, string? configs, int? currentSprintN { IEnumerable configsList = FilterConfigsForTask(configs, t); - MainUpdateTask(taskVersionInfo[t.Value.Name], t.Value.Name, configsList, writeUpdates, currentSprint, debugConfGen, includeLocalPackagesBuildConfig, hasGlobalVersion: globalVersion is not null, generatedFolder: generatedFolder, altGeneratedFolder: altGeneratedFolder); + MainUpdateTask( + taskVersionInfo[t.Value.Name], + t.Value.Name, + configsList, + writeUpdates, + currentSprint, + debugConfGen, + includeLocalPackagesBuildConfig, + hasGlobalVersion: globalVersion is not null, + generatedFolder: generatedFolder, + altGeneratedFolder: altGeneratedFolder, + useSemverBuildConfig: useSemverBuildConfig); } debugConfGen.WriteLaunchConfigurations(); @@ -464,6 +494,7 @@ private static void GetVersions(string task, string configsString, out List<(str string gitRootPath = GetTasksRootPath(currentDir); string taskTargetPath = Path.Combine(gitRootPath, "Tasks", task); + if (!Directory.Exists(taskTargetPath)) { throw new Exception($"expected {taskTargetPath} to exist!"); @@ -572,7 +603,8 @@ private static void MainUpdateTask( bool includeLocalPackagesBuildConfig, bool hasGlobalVersion, string generatedFolder, - string altGeneratedFolder) + string altGeneratedFolder, + bool useSemverBuildConfig) { if (string.IsNullOrEmpty(task)) { @@ -717,7 +749,18 @@ private static void MainUpdateTask( WriteWIFInputTaskJson(taskOutput, config, "task.json", isLoc: false); WriteWIFInputTaskJson(taskOutput, config, "task.loc.json", isLoc: true); - if (!config.mergeToBase) + if (useSemverBuildConfig && !config.mergeToBase) + { + var defaultConfig = targetConfigs.FirstOrDefault(x => x.isDefault); + if (defaultConfig == null) + { + throw new Exception($"There is no default config for task {task}"); + } + + WriteTaskJson(taskOutput, taskVersionState, config, "task.json", existingLocalPackageVersion, useSemverBuildConfig: true, defaultConfig: defaultConfig); + WriteTaskJson(taskOutput, taskVersionState, config, "task.loc.json", existingLocalPackageVersion, useSemverBuildConfig: true, defaultConfig: defaultConfig); + } + else if (!config.mergeToBase) { WriteTaskJson(taskOutput, taskVersionState, config, "task.json", existingLocalPackageVersion); WriteTaskJson(taskOutput, taskVersionState, config, "task.loc.json", existingLocalPackageVersion); @@ -1048,7 +1091,12 @@ private static void PreprocessIfExtensionEnabledInConfig(string file, Config.Con return outputTaskNodeObject["_buildConfigMapping"]?.AsObject()?[Config.LocalPackages.constMappingKey]?.GetValue(); } - private static void WriteTaskJson(string taskPath, TaskStateStruct taskState, Config.ConfigRecord config, string fileName, string? existingLocalPackageVersion) + /// + /// Writes task.json with version information and build config mapping. + /// When useSemverBuildConfig is true, uses the same major.minor.patch for all build configuration tasks, + /// but the "build" suffix of semver is different and directly corresponds to the config name. + /// + private static void WriteTaskJson(string taskPath, TaskStateStruct taskState, Config.ConfigRecord config, string fileName, string? existingLocalPackageVersion, bool useSemverBuildConfig = false, Config.ConfigRecord? defaultConfig = null) { string outputTaskPath = Path.Combine(taskPath, fileName); JsonNode outputTaskNode = JsonNode.Parse(ensureUpdateModeVerifier!.FileReadAllText(outputTaskPath))!; @@ -1057,6 +1105,12 @@ private static void WriteTaskJson(string taskPath, TaskStateStruct taskState, Co outputTaskNode["version"]!["Minor"] = taskState.configTaskVersionMapping[config].Minor; outputTaskNode["version"]!["Patch"] = taskState.configTaskVersionMapping[config].Patch; + // Add semver build suffix if using semver config and not the default config + if (useSemverBuildConfig && defaultConfig != null && defaultConfig != config) + { + outputTaskNode["version"]!["Build"] = config.constMappingKey; + } + var outputTaskNodeObject = outputTaskNode.AsObject(); outputTaskNodeObject.Remove("_buildConfigMapping"); @@ -1305,7 +1359,6 @@ private static void UpdateVersionsForTask(string task, TaskStateStruct taskState string currentDir = Environment.CurrentDirectory; string gitRootPath = GetTasksRootPath(currentDir); string taskTargetPath = Path.Combine(gitRootPath, "Tasks", task); - if (!Directory.Exists(taskTargetPath)) { throw new Exception($"expected {taskTargetPath} to exist!"); @@ -1481,6 +1534,13 @@ private static void UpdateVersionsForTask(string task, TaskStateStruct taskState } while (taskState.configTaskVersionMapping.Values.Contains(targetVersion)); + if (config.abTaskReleases) + { + // In the first stage of refactoring, we keep different version numbers to retain the ability to rollback. + // In the second stage of refactoring, we are going to use the same version, which is going to significantly reduce complexity of all this. + targetVersion = targetVersion.CloneWithBuild(config.constMappingKey); + } + taskState.configTaskVersionMapping.Add(config, targetVersion); if (!taskState.versionsUpdated.Contains(config)) @@ -1555,11 +1615,20 @@ private static void UpdateVersionsGlobal(string task, TaskStateStruct taskState, { if (config.useGlobalVersion) { + TaskVersion versionToUse = globalVersion; + + if (config.abTaskReleases) + { + // In the first stage of refactoring, we keep different version numbers to retain the ability to rollback. + // In the second stage of refactoring, we are going to use the same version, which is going to significantly reduce complexity of all this. + versionToUse = globalVersion.CloneWithBuild(config.constMappingKey); + } + if (taskState.configTaskVersionMapping.ContainsKey(config)) { - if (taskState.configTaskVersionMapping[config] != globalVersion) + if (taskState.configTaskVersionMapping[config] != versionToUse) { - taskState.configTaskVersionMapping[config] = globalVersion; + taskState.configTaskVersionMapping[config] = versionToUse; if (!taskState.versionsUpdated.Contains(config)) { @@ -1569,7 +1638,7 @@ private static void UpdateVersionsGlobal(string task, TaskStateStruct taskState, } else { - taskState.configTaskVersionMapping.Add(config, globalVersion); + taskState.configTaskVersionMapping.Add(config, versionToUse); if (!taskState.versionsUpdated.Contains(config)) { diff --git a/BuildConfigGen/Properties/launchSettings.json b/BuildConfigGen/Properties/launchSettings.json index e60e858ac2a4..d284b4da6588 100644 --- a/BuildConfigGen/Properties/launchSettings.json +++ b/BuildConfigGen/Properties/launchSettings.json @@ -3,6 +3,10 @@ "BuildConfigGen": { "commandName": "Project", "commandLineArgs": "--all-tasks --write-updates" + }, + "BuildConfigGenNew": { + "commandName": "Project", + "commandLineArgs": "--use-semver-build-config --all-tasks --write-updates" } } } \ No newline at end of file diff --git a/BuildConfigGen/TaskVersion.cs b/BuildConfigGen/TaskVersion.cs index 893b8070ce84..0f219c58652b 100644 --- a/BuildConfigGen/TaskVersion.cs +++ b/BuildConfigGen/TaskVersion.cs @@ -1,40 +1,36 @@ -using System; -using System.Globalization; +using System.Globalization; +using System.Text; -internal class TaskVersion : IComparable, IEquatable +public class TaskVersion : IComparable, IEquatable { - public TaskVersion(String version) + public TaskVersion(string version) { - Int32 major, minor, patch; - String? semanticVersion; + VersionParser.ParseVersion( + version, + out int major, + out int minor, + out int patch, + out string? preRelease, + out string? build); - VersionParser.ParseVersion(version, out major, out minor, out patch, out semanticVersion); Major = major; Minor = minor; Patch = patch; + Build = build; - if (semanticVersion != null) + if (string.Equals(preRelease, TestBuildVersion, StringComparison.OrdinalIgnoreCase)) { - if (semanticVersion.Equals("test", StringComparison.OrdinalIgnoreCase)) - { - IsTest = true; - } - else - { - throw new ArgumentException("semVer"); - } + // For backwards compatibility + IsTest = true; + } + else if (!string.IsNullOrEmpty(preRelease)) + { + // This condition is there to prevent backwards compatibility problems if we have to roll this change back. + // We are not going to relax the condition until the rollout is successful. + throw new ArgumentException("semVer"); } } - - private TaskVersion(TaskVersion taskVersionToClone) - { - this.IsTest = taskVersionToClone.IsTest; - this.Major = taskVersionToClone.Major; - this.Minor = taskVersionToClone.Minor; - this.Patch = taskVersionToClone.Patch; - } - - public TaskVersion(int major, int minor, int overidePatch) + public TaskVersion(int major, int minor, int overidePatch, string? build = null) { if (overidePatch < 0) { @@ -44,31 +40,27 @@ public TaskVersion(int major, int minor, int overidePatch) Major = major; Minor = minor; Patch = overidePatch; + Build = build; } - public Int32 Major + private TaskVersion(TaskVersion taskVersionToClone) { - get; - set; + Major = taskVersionToClone.Major; + Minor = taskVersionToClone.Minor; + Patch = taskVersionToClone.Patch; + Build = taskVersionToClone.Build; + IsTest = taskVersionToClone.IsTest; } - public Int32 Minor - { - get; - set; - } + public int Major { get; } - public Int32 Patch - { - get; - set; - } + public int Minor { get; } - public Boolean IsTest - { - get; - set; - } + public int Patch { get; } + + public string? Build { get; } + + public bool IsTest { get; } public TaskVersion Clone() { @@ -77,36 +69,29 @@ public TaskVersion Clone() public TaskVersion CloneWithMinorAndPatch(int minor, int overridePatch) { - return new TaskVersion(Major, minor, overridePatch); + return new TaskVersion(Major, minor, overridePatch, Build); } public TaskVersion CloneWithPatch(int overridePatch) { - return new TaskVersion(Major, Minor, overridePatch); + return new TaskVersion(Major, Minor, overridePatch, Build); } public TaskVersion CloneWithMajor(int major) { - return new TaskVersion(major, Minor, Patch); + return new TaskVersion(major, Minor, Patch, Build); } - public static implicit operator String(TaskVersion version) + public TaskVersion CloneWithBuild(string? build) { - return version.ToString(); + return new TaskVersion(Major, Minor, Patch, build); } - public override String ToString() + public static implicit operator String(TaskVersion version) { - String suffix = String.Empty; - if (IsTest) - { - suffix = "-test"; - } - - return String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}{3}", Major, Minor, Patch, suffix); + return version.ToString(); } - internal string MinorPatchToString() { String suffix = String.Empty; @@ -118,33 +103,54 @@ internal string MinorPatchToString() return String.Format(CultureInfo.InvariantCulture, "{1}.{2}{3}", Major, Minor, Patch, suffix); } - public override int GetHashCode() + public override string ToString() { - return this.ToString().GetHashCode(); + StringBuilder sb = new StringBuilder() + .Append(Major).Append('.').Append(Minor).Append('.').Append(Patch); + + if (!string.IsNullOrEmpty(Build)) + { + sb = sb.Append('+').Append(Build); + } + + return sb.ToString(); } - public Int32 CompareTo(TaskVersion? other) + public override int GetHashCode() => ToString().GetHashCode(); + + public int CompareTo(TaskVersion? other) { if (other is null) { - throw new ArgumentNullException("other"); + throw new ArgumentNullException(nameof(other)); } - Int32 rc = Major.CompareTo(other.Major); - if (rc == 0) + int rc = Major.CompareTo(other.Major); + if (rc != 0) { - rc = Minor.CompareTo(other.Minor); - if (rc == 0) - { - rc = Patch.CompareTo(other.Patch); - if (rc == 0 && this.IsTest != other.IsTest) - { - rc = this.IsTest ? -1 : 1; - } - } + return rc; } - return rc; + rc = Minor.CompareTo(other.Minor); + if (rc != 0) + { + return rc; + } + + rc = Patch.CompareTo(other.Patch); + if (rc != 0) + { + return rc; + } + + if (rc != 0) + { + return rc; + } + + return string.IsNullOrEmpty(Build) && !string.IsNullOrEmpty(other.Build) ? 1 + : !string.IsNullOrEmpty(Build) && string.IsNullOrEmpty(other.Build) ? -1 + : 0; // build versions are incomparable, but the default should always go first } public Boolean Equals(TaskVersion? other) @@ -154,7 +160,11 @@ public Boolean Equals(TaskVersion? other) return false; } - return this.CompareTo(other) == 0; + return Major == other.Major + && Minor == other.Minor + && Patch == other.Patch + && IsTest == other.IsTest + && string.Equals(Build, other.Build, StringComparison.Ordinal); } public override bool Equals(object? obj) @@ -172,7 +182,7 @@ public override bool Equals(object? obj) return v1.Equals(v2); } - public static Boolean operator !=(TaskVersion v1, TaskVersion v2) + public static bool operator !=(TaskVersion v1, TaskVersion v2) { if (v1 is null) { @@ -182,32 +192,33 @@ public override bool Equals(object? obj) return !v1.Equals(v2); } - public static Boolean operator <(TaskVersion v1, TaskVersion v2) + public static bool operator <(TaskVersion v1, TaskVersion v2) { ArgumentUtility.CheckForNull(v1, nameof(v1)); ArgumentUtility.CheckForNull(v2, nameof(v2)); return v1.CompareTo(v2) < 0; } - public static Boolean operator >(TaskVersion v1, TaskVersion v2) + public static bool operator >(TaskVersion v1, TaskVersion v2) { ArgumentUtility.CheckForNull(v1, nameof(v1)); ArgumentUtility.CheckForNull(v2, nameof(v2)); return v1.CompareTo(v2) > 0; } - public static Boolean operator <=(TaskVersion v1, TaskVersion v2) + public static bool operator <=(TaskVersion v1, TaskVersion v2) { ArgumentUtility.CheckForNull(v1, nameof(v1)); ArgumentUtility.CheckForNull(v2, nameof(v2)); return v1.CompareTo(v2) <= 0; } - public static Boolean operator >=(TaskVersion v1, TaskVersion v2) + public static bool operator >=(TaskVersion v1, TaskVersion v2) { ArgumentUtility.CheckForNull(v1, nameof(v1)); ArgumentUtility.CheckForNull(v2, nameof(v2)); return v1.CompareTo(v2) >= 0; } -} + public const string TestBuildVersion = "test"; +} diff --git a/BuildConfigGen/VersionParser.cs b/BuildConfigGen/VersionParser.cs index 46f517a0af0b..e40814920a0f 100644 --- a/BuildConfigGen/VersionParser.cs +++ b/BuildConfigGen/VersionParser.cs @@ -1,40 +1,132 @@ -internal static class VersionParser +using System.Diagnostics.CodeAnalysis; + +public static class VersionParser { + /// + /// Splits the full Semver 2.0 string into individual string components: version, pre-release version, and build version. + /// The actual values are not validated for Semver 2.0 version compliance. + /// + /// The complete version string, e.g. 1.2.3-test+node + /// The version from the , e.g. 1.2.3 + /// The prerelease version from the , e.g. test + /// The build version from the , e.g. node + /// + public static bool TryParseVersionComponents( + string fullVersion, + [NotNullWhen(returnValue: true)] out string? versionSegment, + out string? preReleaseSegment, + out string? buildSegment) + { + versionSegment = null; + preReleaseSegment = null; + buildSegment = null; + + if (string.IsNullOrWhiteSpace(fullVersion)) + { + return false; + } + + int buildIndex = fullVersion.IndexOf('+'); + if (buildIndex == 0) + { + return false; + } + + int preReleaseIndex = fullVersion.IndexOf('-'); + if (preReleaseIndex == 0) + { + return false; + } + + if (preReleaseIndex > 0 && buildIndex > 0) + { + if (preReleaseIndex > buildIndex) + { + // Prelease must be defined before first '+', if any. + // Everything which comes after + is a build version. + preReleaseIndex = -1; + } + } + + string? preReleaseSegmentCandidate = preReleaseIndex > 0 + ? fullVersion.Substring(preReleaseIndex + 1, buildIndex > 0 ? buildIndex - preReleaseIndex - 1 : fullVersion.Length - preReleaseIndex - 1) + : null; + + if (preReleaseSegmentCandidate != null && preReleaseSegmentCandidate.Length < 1) + { + return false; + } + + string? buildSegmentCandidate = buildIndex > 0 + ? fullVersion.Substring(buildIndex + 1) + : null; + + if (buildSegmentCandidate != null && buildSegmentCandidate.Length < 1) + { + return false; + } + + versionSegment = fullVersion; + if (preReleaseIndex > 0) + { + versionSegment = versionSegment.Substring(0, preReleaseIndex); + } + else if (buildIndex > 0) + { + versionSegment = versionSegment.Substring(0, buildIndex); + } + + buildSegment = buildSegmentCandidate; + preReleaseSegment = preReleaseSegmentCandidate; + + return true; + } + + /// + /// There should be more validation in this type, but it is impossible to say whether it would be breaking or not. + /// Therefore, we leave the conditions relaxed, as-is, not to increase the scope of this change significantly. + /// public static void ParseVersion( - String version, - out Int32 major, - out Int32 minor, - out Int32 patch, - out String? semanticVersion) + string version, + out int major, + out int minor, + out int patch, + out string? preReleaseVersion, + out string? buildVersion) { - ArgumentUtility.CheckStringForNullOrEmpty(version, "version"); + ArgumentUtility.CheckStringForNullOrEmpty(version, nameof(version)); - String[] segments = version.Split(new char[] { '.', '-' }, StringSplitOptions.None); - if (segments.Length < 3 || segments.Length > 4) + if (!TryParseVersionComponents( + version, + out string? mainVersion, + out preReleaseVersion, + out buildVersion)) { - throw new ArgumentException("wrong number of segments"); + throw new ArgumentException($"Could not parse version segments: '{version}'"); } - if (!Int32.TryParse(segments[0], out major)) + string[] dotSegments = mainVersion?.Split(c_versionSeparator, StringSplitOptions.None) ?? []; + + if (dotSegments.Length != 3) { - throw new ArgumentException("major"); + throw new ArgumentException("wrong number of segments (should be 3) in: '" + version + "'"); } - if (!Int32.TryParse(segments[1], out minor)) + if (!int.TryParse(dotSegments[0], out major)) { - throw new ArgumentException("minor"); + throw new ArgumentException("major"); } - if (!Int32.TryParse(segments[2], out patch)) + if (!int.TryParse(dotSegments[1], out minor)) { - throw new ArgumentException("patch"); + throw new ArgumentException("minor"); } - semanticVersion = null; - if (segments.Length == 4) + if (!int.TryParse(dotSegments[2], out patch)) { - semanticVersion = segments[3]; + throw new ArgumentException("patch"); } } -} + private static readonly char[] c_versionSeparator = ['.']; +} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 28d794d97951..2024f194fd20 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,6 +31,7 @@ variables: value: 'false' - name: isDryRun value: 'false' + extends: template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates diff --git a/ci/build-all-steps.yml b/ci/build-all-steps.yml index de7eb1a7e064..312ff51d9d75 100644 --- a/ci/build-all-steps.yml +++ b/ci/build-all-steps.yml @@ -1,5 +1,7 @@ parameters: os: '' + useSemverBuildConfig: false + generateReleaseNotes: true steps: @@ -68,6 +70,11 @@ steps: - script: dotnet run --project BuildConfigGen/BuildConfigGen.csproj --no-launch-profile -- --all-tasks displayName: Verify generated files across tasks are up-to-date (Ensure BuildConfigGen outputs are consistent) + condition: eq(${{ parameters.useSemverBuildConfig }}, false) + +- script: dotnet run --project BuildConfigGen/BuildConfigGen.csproj --no-launch-profile -- --all-tasks --use-semver-build-config + displayName: Verify generated files across tasks are up-to-date (Ensure BuildConfigGen outputs are consistent) + condition: eq(${{ parameters.useSemverBuildConfig }}, true) - powershell: ./ci/set-sprint-variables.ps1 displayName: Set currentSprint variables @@ -131,7 +138,11 @@ steps: # Build Tasks - script: node make.js serverBuild --task "$(getTaskPattern.task_pattern)" $(includeLocalPackagesBuildConfigParameter) displayName: Build Tasks - condition: and(succeeded(), ne(variables['numTasks'], 0)) + condition: and(succeeded(), ne(variables['numTasks'], 0), eq('${{ parameters.useSemverBuildConfig }}', false)) + +- script: node make.js serverBuild --task "$(getTaskPattern.task_pattern)" $(includeLocalPackagesBuildConfigParameter) --useSemverBuildConfig true + displayName: Build Tasks + condition: and(succeeded(), ne(variables['numTasks'], 0), eq('${{ parameters.useSemverBuildConfig }}', true)) - script: node ./ci/after-build-check-tasks.js displayName: After build tasks validation @@ -225,7 +236,8 @@ steps: succeeded(), in(variables['build.reason'], 'Schedule', 'Manual'), eq(variables['COURTESY_PUSH'], 'true'), - eq(variables['Build.SourceBranch'], 'refs/heads/master') + eq(variables['Build.SourceBranch'], 'refs/heads/master'), + eq(${{ parameters.generateReleaseNotes }}, true) ) continueOnError: true displayName: Create Release \ No newline at end of file diff --git a/ci/build-all-tasks.yml b/ci/build-all-tasks.yml index cfb7c2c93fb2..a2686317c8b8 100644 --- a/ci/build-all-tasks.yml +++ b/ci/build-all-tasks.yml @@ -3,6 +3,8 @@ parameters: - name: deploy_all_tasks type: boolean +- name: useSemverBuildConfig + type: boolean steps: # Clean @@ -36,12 +38,20 @@ steps: # Build tasks - script: node make.js serverBuild --task "$(task_pattern)" $(includeLocalPackagesBuildConfigParameter) displayName: Build tasks using pattern - condition: and(succeeded(), eq('${{ parameters.deploy_all_tasks }}', false)) + condition: and(succeeded(), eq('${{ parameters.deploy_all_tasks }}', false), eq(${{ parameters.useSemverBuildConfig }}, false)) + +- script: node make.js serverBuild --task "$(task_pattern)" $(includeLocalPackagesBuildConfigParameter) --useSemverBuildConfig true; + displayName: Build tasks using pattern + condition: and(succeeded(), eq('${{ parameters.deploy_all_tasks }}', false), eq(${{ parameters.useSemverBuildConfig }}, true)) # Build all tasks - script: node make.js serverBuild $(includeLocalPackagesBuildConfigParameter) displayName: Build all tasks - condition: and(succeeded(), eq('${{ parameters.deploy_all_tasks }}', true)) + condition: and(succeeded(), eq('${{ parameters.deploy_all_tasks }}', true), eq(${{ parameters.useSemverBuildConfig }}, false)) + +- script: node make.js serverBuild $(includeLocalPackagesBuildConfigParameter) --useSemverBuildConfig true; + displayName: Build all tasks + condition: and(succeeded(), eq('${{ parameters.deploy_all_tasks }}', true), eq(${{ parameters.useSemverBuildConfig }}, true)) # Stage tasks individually into the package directory - script: node ./ci/stage-package.js true individually diff --git a/make-util.js b/make-util.js index b2668fcd620c..b6be974c5145 100644 --- a/make-util.js +++ b/make-util.js @@ -1840,8 +1840,9 @@ exports.ensureBuildConfigGeneratorPrereqs = ensureBuildConfigGeneratorPrereqs; * @param {Number} sprintNumber Sprint number option to pass in the BuildConfigGenerator tool * @param {String} debugAgentDir When set to local agent root directory, the BuildConfigGenerator tool will generate launch configurations for the task(s) * @param {Boolean} includeLocalPackagesBuildConfig When set to true, generate LocalPackages BuildConfig + * @param {Boolean} useSemverBuildConfig When set to true, use semver build config and A/B releases */ -var processGeneratedTasks = function(baseConfigToolPath, taskList, makeOptions, writeUpdates, sprintNumber, debugAgentDir, includeLocalPackagesBuildConfig) { +var processGeneratedTasks = function (baseConfigToolPath, taskList, makeOptions, writeUpdates, sprintNumber, debugAgentDir, includeLocalPackagesBuildConfig, useSemverBuildConfig) { if (!makeOptions) fail("makeOptions is not defined"); if (sprintNumber && !Number.isInteger(sprintNumber)) fail("Sprint is not a number"); @@ -1870,6 +1871,10 @@ var processGeneratedTasks = function(baseConfigToolPath, taskList, makeOptions, writeUpdateArg += " --include-local-packages-build-config"; } + if (useSemverBuildConfig) { + writeUpdateArg += " --use-semver-build-config"; + } + var debugAgentDirArg = ""; if(debugAgentDir) { debugAgentDirArg += ` --debug-agent-dir ${debugAgentDir}`; diff --git a/make.js b/make.js index 59dba1fbfd38..c7afcc261fc9 100644 --- a/make.js +++ b/make.js @@ -257,7 +257,7 @@ CLI.serverBuild = async function(/** @type {{ task: string }} */ argv) { const makeOptions = fileToJson(makeOptionsPath); // Verify generated files across tasks are up-to-date - util.processGeneratedTasks(baseConfigToolPath, taskList, makeOptions, writeUpdatedsFromGenTasks, argv.sprint, argv['debug-agent-dir'], argv.includeLocalPackagesBuildConfig); + util.processGeneratedTasks(baseConfigToolPath, taskList, makeOptions, writeUpdatedsFromGenTasks, argv.sprint, argv['debug-agent-dir'], argv.includeLocalPackagesBuildConfig, argv.useSemverBuildConfig); } if (argv.includeLocalPackagesBuildConfig)