From 58703898f3b29730ed4c19be36babfb03018525d Mon Sep 17 00:00:00 2001 From: MerlynOMsft <44985659+merlynomsft@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:26:07 -0800 Subject: [PATCH 1/2] wip --- BuildConfigGen/Program.cs | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/BuildConfigGen/Program.cs b/BuildConfigGen/Program.cs index a0322df45a9..3081202134b 100644 --- a/BuildConfigGen/Program.cs +++ b/BuildConfigGen/Program.cs @@ -1,4 +1,8 @@ -using System.Diagnostics; +// todo: +// 1. Configs will only be opted in to merge if the specific task is removed from the respective make-options.json +// 2. This resolves the version bumping issue - version bump will be required. + +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; @@ -34,22 +38,22 @@ public record ConfigRecord(string name, string constMappingKey, bool isDefault, 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); public static readonly ConfigRecord Node16_225 = new ConfigRecord(name: nameof(Node16_225), constMappingKey: "Node16-225", isDefault: false, isNode: true, isWif: false, nodePackageVersion: "^16.11.39", nodeHandler: "Node16", preprocessorVariableName: "NODE16", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: false, overriddenDirectoryName: "Node16", writeNpmrc: false); - public static readonly ConfigRecord Node20 = new ConfigRecord(name: nameof(Node20), constMappingKey: "Node20-225", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_228 = new ConfigRecord(name: nameof(Node20_228), constMappingKey: "Node20-228", isDefault: false, isNode: true, nodePackageVersion: "^20.11.0", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_1 = new ConfigRecord(name: nameof(Node20_229_1), constMappingKey: "Node20_229_1", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_2 = new ConfigRecord(name: nameof(Node20_229_2), constMappingKey: "Node20_229_2", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_3 = new ConfigRecord(name: nameof(Node20_229_3), constMappingKey: "Node20_229_3", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_4 = new ConfigRecord(name: nameof(Node20_229_4), constMappingKey: "Node20_229_4", isDefault: false, isNode: true, nodePackageVersion: "^20.11.0", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_5 = new ConfigRecord(name: nameof(Node20_229_5), constMappingKey: "Node20_229_5", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_6 = new ConfigRecord(name: nameof(Node20_229_6), constMappingKey: "Node20_229_6", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_7 = new ConfigRecord(name: nameof(Node20_229_7), constMappingKey: "Node20_229_7", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_8 = new ConfigRecord(name: nameof(Node20_229_8), constMappingKey: "Node20_229_8", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_9 = new ConfigRecord(name: nameof(Node20_229_9), constMappingKey: "Node20_229_9", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_10 = new ConfigRecord(name: nameof(Node20_229_10), constMappingKey: "Node20_229_10", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_11 = new ConfigRecord(name: nameof(Node20_229_11), constMappingKey: "Node20_229_11", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_12 = new ConfigRecord(name: nameof(Node20_229_12), constMappingKey: "Node20_229_12", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_13 = new ConfigRecord(name: nameof(Node20_229_13), constMappingKey: "Node20_229_13", isDefault: false, isNode: true, nodePackageVersion: "^20.11.0", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); - public static readonly ConfigRecord Node20_229_14 = new ConfigRecord(name: nameof(Node20_229_14), constMappingKey: "Node20_229_14", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: false); + public static readonly ConfigRecord Node20 = new ConfigRecord(name: nameof(Node20), constMappingKey: "Node20-225", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_228 = new ConfigRecord(name: nameof(Node20_228), constMappingKey: "Node20-228", isDefault: false, isNode: true, nodePackageVersion: "^20.11.0", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_1 = new ConfigRecord(name: nameof(Node20_229_1), constMappingKey: "Node20_229_1", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_2 = new ConfigRecord(name: nameof(Node20_229_2), constMappingKey: "Node20_229_2", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_3 = new ConfigRecord(name: nameof(Node20_229_3), constMappingKey: "Node20_229_3", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_4 = new ConfigRecord(name: nameof(Node20_229_4), constMappingKey: "Node20_229_4", isDefault: false, isNode: true, nodePackageVersion: "^20.11.0", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_5 = new ConfigRecord(name: nameof(Node20_229_5), constMappingKey: "Node20_229_5", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_6 = new ConfigRecord(name: nameof(Node20_229_6), constMappingKey: "Node20_229_6", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_7 = new ConfigRecord(name: nameof(Node20_229_7), constMappingKey: "Node20_229_7", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_8 = new ConfigRecord(name: nameof(Node20_229_8), constMappingKey: "Node20_229_8", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_9 = new ConfigRecord(name: nameof(Node20_229_9), constMappingKey: "Node20_229_9", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_10 = new ConfigRecord(name: nameof(Node20_229_10), constMappingKey: "Node20_229_10", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_11 = new ConfigRecord(name: nameof(Node20_229_11), constMappingKey: "Node20_229_11", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_12 = new ConfigRecord(name: nameof(Node20_229_12), constMappingKey: "Node20_229_12", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_13 = new ConfigRecord(name: nameof(Node20_229_13), constMappingKey: "Node20_229_13", isDefault: false, isNode: true, nodePackageVersion: "^20.11.0", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); + public static readonly ConfigRecord Node20_229_14 = new ConfigRecord(name: nameof(Node20_229_14), constMappingKey: "Node20_229_14", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Node20", writeNpmrc: true, mergeToBase: true); public static readonly ConfigRecord WorkloadIdentityFederation = new ConfigRecord(name: nameof(WorkloadIdentityFederation), constMappingKey: "WorkloadIdentityFederation", isDefault: false, isNode: true, nodePackageVersion: "^16.11.39", isWif: true, nodeHandler: "Node16", preprocessorVariableName: "WORKLOADIDENTITYFEDERATION", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: false, writeNpmrc: false); public static readonly ConfigRecord wif_242 = new ConfigRecord(name: nameof(wif_242), constMappingKey: "wif_242", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: true, nodeHandler: "Node20_1", preprocessorVariableName: "WIF", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "Wif", writeNpmrc: true); public static readonly ConfigRecord LocalPackages = new ConfigRecord(name: nameof(LocalPackages), constMappingKey: "LocalPackages", isDefault: false, isNode: true, nodePackageVersion: "^20.3.1", isWif: false, nodeHandler: "Node20_1", preprocessorVariableName: "NODE20", enableBuildConfigOverrides: true, deprecated: false, shouldUpdateTypescript: true, overriddenDirectoryName: "LocalPackages", writeNpmrc: true, shouldUpdateLocalPkgs: true, useGlobalVersion: true, useAltGeneratedPath: true); From 48fdb39b81f02bec1c0d327c47e4107881f5bad3 Mon Sep 17 00:00:00 2001 From: MerlynOMsft <44985659+merlynomsft@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:58:45 -0800 Subject: [PATCH 2/2] conver checkdowngrading + filtertasks to C# --- BuildConfigGen/Program.cs | 15 +- ci/CiTool/CheckDowngrading.cs | 528 ++++++++++++++++++++++++++++++++++ ci/CiTool/CiTool.csproj | 14 + ci/CiTool/CiTool.sln | 22 ++ ci/CiTool/FilterTasksUtil.cs | 361 +++++++++++++++++++++++ ci/CiTool/Program.cs | 34 +++ 6 files changed, 973 insertions(+), 1 deletion(-) create mode 100644 ci/CiTool/CheckDowngrading.cs create mode 100644 ci/CiTool/CiTool.csproj create mode 100644 ci/CiTool/CiTool.sln create mode 100644 ci/CiTool/FilterTasksUtil.cs create mode 100644 ci/CiTool/Program.cs diff --git a/BuildConfigGen/Program.cs b/BuildConfigGen/Program.cs index 3081202134b..062c401bdae 100644 --- a/BuildConfigGen/Program.cs +++ b/BuildConfigGen/Program.cs @@ -1,6 +1,19 @@ // todo: // 1. Configs will only be opted in to merge if the specific task is removed from the respective make-options.json -// 2. This resolves the version bumping issue - version bump will be required. +// 2. This resolves the version bumping issue - version bump will be required. +// however, this creates a chicken and egg problem. +// We drive config updates based on make-options.json. If task is removed from make-options.json in order to be merged, the config won't be processed and task won't be merged +// therefore we must re-think. +// Go back to current plan - +// merge tasks to base task w/o bumping version +// two problems +// 1. PR will be blocked b/c bumping version is required +// a. possible solution - update version bump check to exclude IF: +// 1. In versionmapmap.txt, a config version is merged to base config +// 2. versionmap.txt is deleted and version matches version that was deleted config +// 2. lot of churn in courtsey push b/c it will pick up all tasks by git filter +// a. but is this an issue? We could update courtsey push to check if task version has actually changed to avoid excessive build/tests +// b. shouldn't be an issue for task deployment as task should not be deployed due to version already existing using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/ci/CiTool/CheckDowngrading.cs b/ci/CiTool/CheckDowngrading.cs new file mode 100644 index 00000000000..4c52b1ebd81 --- /dev/null +++ b/ci/CiTool/CheckDowngrading.cs @@ -0,0 +1,528 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +class CheckDowngrading +{ + private static readonly string TaskVersionBumpingDocUrl = "https://aka.ms/azp-tasks-version-bumping"; + private static readonly string PackageEndpoint = Environment.GetEnvironmentVariable("PACKAGE_VERSIONS_ENDPOINT"); + private static readonly string SourceBranch = EscapeHash(Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_SOURCEBRANCH")); + private static readonly string TargetBranch = EscapeHash(Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_TARGETBRANCH")); + private static readonly string BaseProjectPath = Path.Combine(AppContext.BaseDirectory, ".."); + private static readonly string TempMasterTasksPath = Path.Combine(BaseProjectPath, "temp", "tasks-versions", TargetBranch); + + public static async Task LocalMain(string[] args) + { + + // An example: + // PACKAGE_TOKEN={token} PACKAGE_VERSIONS_ENDPOINT={package_versions_endpoint} SYSTEM_PULLREQUEST_SOURCEBRANCH=refs/head/{local_branch_name} SYSTEM_PULLREQUEST_TARGETBRANCH={target_branch_eg_master} node ./ci/check-downgrading.js --task "@({tasks_names})" --sprint {current_sprint_number} + + if (string.IsNullOrEmpty(PackageEndpoint)) + { + LogToPipeline("error", "Failed to get info from package endpoint because no endpoint was specified. Try setting the PACKAGE_VERSIONS_ENDPOINT environment variable."); + Environment.Exit(1); + } + + var argv = ConvertArgsArrayToDictionary(args); + var task = argv["task"]; + if (string.IsNullOrEmpty(task)) + { + Console.WriteLine("$(task_pattern)variable is empty or not set. Aborting..."); + Environment.Exit(0); + } + + if (!Directory.Exists(TempMasterTasksPath)) + { + Directory.CreateDirectory(TempMasterTasksPath); + } + + if (Directory.Exists(Path.Combine(TempMasterTasksPath, "Tasks"))) + { + Directory.Delete(Path.Combine(TempMasterTasksPath, "Tasks"), true); + } + + await Main2(argv, task); + } + + private static Dictionary ConvertArgsArrayToDictionary(string[] args) + { + var dictionary = new Dictionary(); + for (int i = 0; i < args.Length; i += 2) + { + if (i + 1 < args.Length) + { + dictionary[args[i].TrimStart('-')] = args[i + 1]; + } + } + return dictionary; + } + + private static string EscapeHash(string str) + { + return Environment.OSVersion.Platform == PlatformID.Win32NT ? str : str.Replace("#", "\\#"); + } + + private static void LogToPipeline(string type, string payload) + { + Console.WriteLine($"{type}: {payload}"); + } + + private static List ResolveTaskList(string task) + { + // Implement the logic to resolve the task list + return new List(); + } + + + private static List CompareVersionMapFilesToDefaultBranch() + { + var messages = new List(); + var defaultBranch = "origin/master"; + + var modifiedVersionMapFiles = GetModifiedVersionMapFiles(defaultBranch, SourceBranch); + if (modifiedVersionMapFiles.Count == 0) return messages; + + foreach (var filePath in modifiedVersionMapFiles) + { + var taskName = Path.GetFileNameWithoutExtension(filePath); + + try + { + var defaultBranchVersionMap = ParseVersionMap(GetVersionMapContent(filePath, defaultBranch)); + var sourceBranchVersionMap = ParseVersionMap(GetVersionMapContent(filePath, SourceBranch)); + + CheckVersionMapUpdate(defaultBranchVersionMap, sourceBranchVersionMap); + } + catch (Exception ex) + { + messages.Add(new Message + { + Type = "error", + Payload = $"Task Name: {taskName}. Please check {filePath}. {ex.Message}" + }); + } + } + + return messages; + } + + + private static string FindMaxConfigVersion(Dictionary versionMap) + { + var maxVersion = new Version("0.0.0"); + foreach (var config in versionMap) + { + var version = Version.Parse(config.Value); + if (version > maxVersion) + { + maxVersion = version; + } + } + return maxVersion.ToString(); + } + + + + private static void CheckVersionMapUpdate(Dictionary defaultBranchConfig, Dictionary sourceBranchConfig) + { + var defaultBranchMaxVersion = FindMaxConfigVersion(defaultBranchConfig); + + foreach (var config in sourceBranchConfig) + { + if (Version.Parse(config.Value) <= Version.Parse(defaultBranchMaxVersion)) + { + throw new Exception($"New versions of the task should be greater than the previous max version. {config.Key} | {config.Value} should be greater than {defaultBranchMaxVersion}"); + } + } + } + + private static List GetModifiedVersionMapFiles(string defaultBranch, string sourceBranch) + { + var versionMapPathRegex = new Regex(@"_generated\/.*versionmap.txt$"); + var versionMapFiles = Run($"git --no-pager diff --name-only --diff-filter=M {defaultBranch}...{sourceBranch}") + .Split('\n') + .Where(line => versionMapPathRegex.IsMatch(line)) + .ToList(); + return versionMapFiles; + } + + private static string GetVersionMapContent(string versionMapFilePath, string branchName) + { + return Run($"git show {branchName}:{versionMapFilePath}"); + } + + private static Dictionary ParseVersionMap(string fileContent) + { + var simpleVersionMapRegex = new Regex(@"(?.+)\|(?\d+\.\d+\.\d+)$"); + var versionMap = new Dictionary(); + foreach (var line in fileContent.Split('\n')) + { + if (simpleVersionMapRegex.IsMatch(line)) + { + var match = simpleVersionMapRegex.Match(line); + versionMap[match.Groups["configName"].Value] = match.Groups["version"].Value; + } + else + { + throw new Exception($"Unable to parse version map {line}"); + } + } + return versionMap; + } + + + + + private static List CheckMasterVersions(List masterTasks, int sprint, bool isReleaseTagExist, bool isCourtesyWeek) + { + var messages = new List(); + + foreach (var masterTask in masterTasks) + { + if (masterTask.Version.Minor <= sprint) + { + continue; + } + + if (isReleaseTagExist || isCourtesyWeek) + { + continue; + } + + messages.Add(new Message + { + Type = "warning", + Payload = $"[{TargetBranch}] {masterTask.Name} has v{masterTask.Version} it's higher than the current sprint {sprint}" + }); + } + + return messages; + } + + private static List CompareLocalToMaster(List localTasks, List masterTasks, int sprint) + { + var messages = new List(); + + foreach (var localTask in localTasks) + { + var masterTask = masterTasks.FirstOrDefault(x => x.Name.Equals(localTask.Name, StringComparison.OrdinalIgnoreCase)); + + if (masterTask == null) + { + continue; + } + + if (localTask.Version.Minor < sprint) + { + var destinationVersion = new Version(masterTask.Version.Major, sprint, masterTask.Version.Build); + + messages.Add(new Message + { + Type = "error", + Payload = $"{localTask.Name} have to be upgraded(task.json, task.loc.json) from v{localTask.Version} to v{destinationVersion} at least since local minor version is less than the sprint version({TaskVersionBumpingDocUrl})" + }); + continue; + } + + if (localTask.Version.Minor == sprint && localTask.Version == masterTask.Version) + { + messages.Add(new Message + { + Type = "error", + Payload = $"{localTask.Name} have to be upgraded(task.json, task.loc.json) from v{localTask.Version} to v{new Version(masterTask.Version.Major, masterTask.Version.Minor, masterTask.Version.Build + 1)} at least since local version is equal to the master version({TaskVersionBumpingDocUrl})" + }); + continue; + } + } + + return messages; + } + + private static List CheckLocalVersions(List localTasks, int sprint, bool isReleaseTagExist, bool isCourtesyWeek) + { + var messages = new List(); + + foreach (var localTask in localTasks) + { + if (localTask.Version.Minor < sprint) + { + messages.Add(new Message + { + Type = "error", + Payload = $"{localTask.Name} have to be upgraded(task.json, task.loc.json) from v{localTask.Version.Minor} to v{sprint} at least since local minor version is less than the sprint version({TaskVersionBumpingDocUrl})" + }); + continue; + } + + if (localTask.Version.Minor == sprint && isCourtesyWeek) + { + messages.Add(new Message + { + Type = "warning", + Payload = $"Be careful with task {localTask.Name} version and check it attentively as the current week is courtesy push week" + }); + continue; + } + + if (localTask.Version.Minor > sprint && (!isReleaseTagExist && !isCourtesyWeek)) + { + messages.Add(new Message + { + Type = "error", + Payload = $"[{SourceBranch}] {localTask.Name} has v{localTask.Version} it's higher than the current sprint {sprint} ({TaskVersionBumpingDocUrl})" + }); + continue; + } + } + + return messages; + } + + private static List ReadVersionsFromTaskJsons(List tasks, string basepath) + { + return tasks.Select(x => + { + var taskJSONPath = Path.Combine(basepath, "Tasks", x, "task.json"); + + if (!File.Exists(taskJSONPath)) + { + LogToPipeline("error", $"Task.json of {x} does not exist by path {taskJSONPath}"); + Environment.Exit(1); + } + + var taskJSONObject = JsonNode.Parse(File.ReadAllText(taskJSONPath)); + return new TaskVersion + { + Id = taskJSONObject["id"].ToString(), + Name = x, + Version = new Version( + (int)taskJSONObject["version"]["Major"], + (int)taskJSONObject["version"]["Minor"], + (int)taskJSONObject["version"]["Patch"] + ) + }; + }).ToList(); + } + + private static async Task> GetTaskVersionsFromFeed() + { + using (var httpClient = new HttpClient()) + { + var response = await httpClient.GetAsync(PackageEndpoint); + + if (!response.IsSuccessStatusCode) + { + LogToPipeline("error", $"Failed while fetching feed versions.\nStatus code: {response.StatusCode}\nResult: {await response.Content.ReadAsStringAsync()}"); + Environment.Exit(1); + } + + var result = JsonNode.Parse(await response.Content.ReadAsStringAsync()); + + return result.AsArray().Select(x => new FeedTaskVersion + { + Name = x["name"].ToString().Substring("Mseng.MS.TF.DistributedTask.Tasks.".Length), + Versions = x["versions"].AsArray().Select(y => new TaskVersion + { + Version = Version.Parse(y["version"].ToString()), + IsLatest = (bool)y["isLatest"] + }).ToList() + }).ToList(); + } + } + + private static List CompareLocalToFeed(List localTasks, List feedTasks, int sprint) + { + var messages = new List(); + + foreach (var localTask in localTasks) + { + var feedTask = feedTasks.FirstOrDefault(x => x.Name.Equals(localTask.Name, StringComparison.OrdinalIgnoreCase)); + + if (feedTask == null) + { + continue; + } + + foreach (var feedTaskVersion in feedTask.Versions) + { + if (feedTaskVersion.Version.Minor > sprint) + { + messages.Add(new Message + { + Type = "warning", + Payload = $"[Feed] {feedTask.Name} has v{feedTaskVersion.Version} it's higher than the current sprint {sprint}" + }); + continue; + } + + if (localTask.Version <= feedTaskVersion.Version && feedTaskVersion.IsLatest) + { + messages.Add(new Message + { + Type = "warning", + Payload = $"[Feed] {localTask.Name} local version {localTask.Version} less or equal than version in feed {feedTaskVersion.Version}" + }); + } + } + } + + return messages; + } + + + private static List CompareLocalTaskLoc(List localTasks) + { + var messages = new List(); + + foreach (var localTask in localTasks) + { + var taskLocJSONPath = Path.Combine(AppContext.BaseDirectory, "..", "Tasks", localTask.Name, "task.loc.json"); + + if (!File.Exists(taskLocJSONPath)) + { + LogToPipeline("error", $"Task.json of {localTask.Name} does not exist by path {taskLocJSONPath}"); + Environment.Exit(1); + } + + var taskLocJSONObject = JsonNode.Parse(File.ReadAllText(taskLocJSONPath)); + var taskLocJSONVersion = new Version( + (int)taskLocJSONObject["version"]["Major"], + (int)taskLocJSONObject["version"]["Minor"], + (int)taskLocJSONObject["version"]["Patch"] + ); + + if (localTask.Version != taskLocJSONVersion) + { + messages.Add(new Message + { + Type = "error", + Payload = $"[Loc] {localTask.Name} task.json v{localTask.Version} does not match with task.loc.json v{taskLocJSONVersion} ({TaskVersionBumpingDocUrl})" + }); + } + } + + return messages; + } + + private static void LoadTaskJsonsFromMaster(List names) + { + foreach (var name in names) + { + Directory.CreateDirectory(Path.Combine(TempMasterTasksPath, "Tasks", name)); + Run($"git show origin/master:Tasks/{name}/task.json > {Path.Combine(TempMasterTasksPath, "Tasks", name, "task.json").Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)}"); + } + } + + private static bool DoesTaskExistInMasterBranch(string name) + { + try + { + Run($"git cat-file -e origin/master:Tasks/{name}/task.json", true); + } + catch + { + return false; + } + return true; + } + + + private static async Task Main2(Dictionary argv, string task) + { + var taskList = ResolveTaskList(task); + var localTasks = ReadVersionsFromTaskJsons(taskList, Path.Combine(AppContext.BaseDirectory, "..")); + var masterTaskList = taskList.Where(DoesTaskExistInMasterBranch).ToList(); + LoadTaskJsonsFromMaster(masterTaskList); + var masterTasks = ReadVersionsFromTaskJsons(masterTaskList, TempMasterTasksPath); + var feedTaskVersions = await GetTaskVersionsFromFeed(); + var isReleaseTagExist = Run($"git tag -l v{argv["sprint"]}").Length != 0; + var isCourtesyWeek = argv["week"] == "3"; + + var messages = new List(); + messages.AddRange(CheckMasterVersions(masterTasks, int.Parse(argv["sprint"]), isReleaseTagExist, isCourtesyWeek)); + messages.AddRange(CompareLocalToMaster(localTasks, masterTasks, int.Parse(argv["sprint"]))); + messages.AddRange(CheckLocalVersions(localTasks, int.Parse(argv["sprint"]), isReleaseTagExist, isCourtesyWeek)); + messages.AddRange(CompareLocalToFeed(localTasks, feedTaskVersions, int.Parse(argv["sprint"]))); + messages.AddRange(CompareLocalTaskLoc(localTasks)); + messages.AddRange(CompareVersionMapFilesToDefaultBranch()); + + if (messages.Count > 0) + { + Console.WriteLine($"\nProblems with {messages.Count} task(s) should be resolved:\n"); + + foreach (var message in messages) + { + LogToPipeline(message.Type, message.Payload); + } + + Console.WriteLine("\nor you might have an outdated branch, try to merge/rebase your branch from master"); + + if (messages.Any(x => x.Type == "error")) + { + Environment.Exit(1); + } + } + } + + private static string Run(string command, bool throwOnError = false) + { + try + { + var processInfo = new ProcessStartInfo("cmd.exe", "/c " + command) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var process = Process.Start(processInfo)) + { + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0 && throwOnError) + { + throw new Exception($"Command '{command}' failed with exit code {process.ExitCode}: {error}"); + } + + return output; + } + } + catch (Exception ex) + { + if (throwOnError) + { + throw; + } + + LogToPipeline("error", ex.Message); + return string.Empty; + } + } + + private class TaskVersion + { + public string Id { get; set; } + public string Name { get; set; } + public Version Version { get; set; } + public bool IsLatest { get; set; } + } + + private class FeedTaskVersion + { + public string Name { get; set; } + public List Versions { get; set; } + } + + private class Message + { + public string Type { get; set; } + public string Payload { get; set; } + } +} diff --git a/ci/CiTool/CiTool.csproj b/ci/CiTool/CiTool.csproj new file mode 100644 index 00000000000..7443ffa774f --- /dev/null +++ b/ci/CiTool/CiTool.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/ci/CiTool/CiTool.sln b/ci/CiTool/CiTool.sln new file mode 100644 index 00000000000..bec286090f3 --- /dev/null +++ b/ci/CiTool/CiTool.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35707.178 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CiTool", "CiTool.csproj", "{995A622F-9CCC-4B7D-B4B9-1E0A1FE1674E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {995A622F-9CCC-4B7D-B4B9-1E0A1FE1674E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {995A622F-9CCC-4B7D-B4B9-1E0A1FE1674E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {995A622F-9CCC-4B7D-B4B9-1E0A1FE1674E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {995A622F-9CCC-4B7D-B4B9-1E0A1FE1674E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ci/CiTool/FilterTasksUtil.cs b/ci/CiTool/FilterTasksUtil.cs new file mode 100644 index 00000000000..eb0ebddbd11 --- /dev/null +++ b/ci/CiTool/FilterTasksUtil.cs @@ -0,0 +1,361 @@ +// Filter out tasks that don't need to be built. Determines which tasks need to be built based on the type of build. +// If its a CI build, all tasks whose package numbers have been bumped will be built. +// If its a PR build, all tasks that have been changed will be built. +// Any other type of build will build all tasks. + +using System; +using System.Linq; +using System.Text.Json.Nodes; + +public class FilterTasksUtil +{ + private static readonly string makeOptionsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "make-options.json"); + private static readonly JsonNode makeOptions = JsonNode.Parse(File.ReadAllText(makeOptionsPath)); + private static readonly HttpClient client = new HttpClient(); + + private static async Task> GetTasksToBuildForCI() + { + JsonNode packageInfo; + try + { + var packageToken = Environment.GetEnvironmentVariable("PACKAGE_TOKEN"); + if (string.IsNullOrEmpty(packageToken)) + { + Console.WriteLine("Failed to get info from package endpoint because no token was provided. Try setting the PACKAGE_TOKEN environment variable."); + return GetTaskArray(makeOptions); + } + + client.DefaultRequestHeaders.Add("Authorization", $"Bearer {packageToken}"); + var packageEndpoint = Environment.GetEnvironmentVariable("PACKAGE_ENDPOINT"); + if (string.IsNullOrEmpty(packageEndpoint)) + { + Console.WriteLine("Failed to get info from package endpoint because no endpoint was specified. Try setting the PACKAGE_ENDPOINT environment variable."); + return GetTaskArray(makeOptions); + } + + var response = await client.GetAsync(packageEndpoint); + if (!response.IsSuccessStatusCode) + { + Console.WriteLine($"Failed to get info from package endpoint, returned with status code {response.StatusCode}"); + return GetTaskArray(makeOptions); + } + + packageInfo = JsonNode.Parse(await response.Content.ReadAsStringAsync()); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to get info from package endpoint, client failed with error {ex.Message}"); + return GetTaskArray(makeOptions); + } + + var packageMap = new Dictionary(); + if (packageInfo["value"] == null) + { + Console.WriteLine("Failed to get info from package endpoint, returned no packages"); + return GetTaskArray(makeOptions); + } + + foreach (var package in packageInfo["value"].AsArray()) + { + if (package["name"] != null && package["versions"] != null) + { + var packageName = package["name"].ToString().Substring("Mseng.MS.TF.DistributedTask.Tasks.".Length).ToLower(); + packageMap[packageName] = package["versions"][0]["version"].ToString(); + foreach (var versionInfo in package["versions"].AsArray()) + { + if (versionInfo["isLatest"].GetValue()) + { + packageMap[packageName] = versionInfo["version"].ToString(); + break; + } + } + } + } + + return GetTaskArray(makeOptions).Where(taskName => + { + var taskJsonPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "Tasks", taskName, "task.json"); + if (File.Exists(taskJsonPath)) + { + var taskJson = JsonNode.Parse(File.ReadAllText(taskJsonPath)); + var lowerCaseName = taskJson["name"].ToString().ToLower(); + if (!int.TryParse(lowerCaseName.Last().ToString(), out _)) + { + lowerCaseName += "v" + taskJson["version"]["Major"]; + } + + if (packageMap.ContainsKey(lowerCaseName) || packageMap.ContainsKey(taskName.ToLower())) + { + if (packageMap.ContainsKey(taskName.ToLower())) + { + lowerCaseName = taskName.ToLower(); + } + + var packageVersion = packageMap[lowerCaseName]; + var localVersion = $"{taskJson["version"]["Major"]}.{taskJson["version"]["Minor"]}.{taskJson["version"]["Patch"]}"; + + return !packageVersion.Equals(localVersion); + } + else + { + Console.WriteLine($"{taskName} has not been published before"); + return true; + } + } + else + { + Console.WriteLine($"{taskJsonPath} does not exist"); + return true; + } + }).ToList(); + } + + private static List GetTasksDependentOnChangedCommonFiles(List commonFilePaths) + { + if (commonFilePaths.Count == 0) + { + return new List(); + } + + var changedTasks = new List(); + foreach (var taskName in GetTaskArray(makeOptions)) + { + var makeJsonPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "Tasks", taskName.ToString(), "make.json"); + if (File.Exists(makeJsonPath)) + { + var makeJson = JsonNode.Parse(File.ReadAllText(makeJsonPath)); + if (makeJson["common"] != null) + { + foreach (var commonModule in makeJson["common"].AsArray()) + { + if (commonFilePaths.Contains(commonModule["module"].ToString().ToLower()) && !changedTasks.Contains(taskName.ToString())) + { + changedTasks.Add(taskName.ToString()); + } + } + } + } + } + return changedTasks; + } + + private static async Task<(string source, string target)> GetPullRequestBranches(int pullRequestId) + { + var response = await client.GetStringAsync($"https://api.github.com/repos/microsoft/azure-pipelines-tasks/pulls/{pullRequestId}"); + var data = JsonNode.Parse(response); + return (data["head"]["ref"].ToString(), data["base"]["ref"].ToString()); + } + + private static async Task> GetTasksToBuildForPR(int? prId, bool forDowngradingCheck) + { + string sourceBranch, targetBranch; + + if (prId.HasValue) + { + var branches = await GetPullRequestBranches(prId.Value); + sourceBranch = branches.source; + targetBranch = branches.target; + } + else + { + prId = int.Parse(Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_PULLREQUESTNUMBER")); + sourceBranch = Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_SOURCEBRANCH"); + targetBranch = Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_TARGETBRANCH"); + } + + var commonChanges = new List(); + var commonTestChanges = new List(); + var toBeBuilt = new List(); + + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + sourceBranch = sourceBranch.Replace("#", "\\#"); + targetBranch = targetBranch.Replace("#", "\\#"); + } + + try + { + if (sourceBranch.Contains(":")) + { + sourceBranch = sourceBranch.Split(':')[1]; + } + Run($"git fetch origin pull/{prId}/head:{sourceBranch}"); + } + catch (Exception ex) + { + Console.WriteLine($"Unable to reach github, building all tasks: {ex.Message}"); + return GetTaskArray(makeOptions); + } + + var baseCommit = Run($"git merge-base {sourceBranch} origin/{targetBranch}"); + + var diffExtra = forDowngradingCheck ? (Environment.OSVersion.Platform == PlatformID.Win32NT ? " -- . :^^**/_buildConfigs/**" : " -- . :^**/_buildConfigs/**") : ""; + + foreach (var filePath in Run($"git --no-pager diff --name-only {baseCommit} {sourceBranch} {diffExtra}").Split('\n')) + { + if (filePath.StartsWith("Tasks")) + { + var taskPath = filePath.Substring(6); + if (taskPath.StartsWith("Common")) + { + var commonName = taskPath.Substring(7); + if (taskPath.ToLower().Contains("test")) + { + commonTestChanges.Add($"../common/{commonName.Substring(0, commonName.IndexOf('/')).ToLower()}"); + } + else + { + commonChanges.Add($"../common/{commonName.Substring(0, commonName.IndexOf('/')).ToLower()}"); + } + } + else + { + var taskName = taskPath.Substring(0, taskPath.IndexOf('/')); + if (!toBeBuilt.Contains(taskName)) + { + toBeBuilt.Add(taskName); + } + } + } + } + + var changedTasks = GetTasksDependentOnChangedCommonFiles(commonChanges); + var changedTests = GetTasksDependentOnChangedCommonFiles(commonTestChanges); + var shouldBeBumped = new List(); + + foreach (var task in changedTasks) + { + if (!toBeBuilt.Contains(task)) + { + shouldBeBumped.Add(task); + toBeBuilt.Add(task); + } + } + + var skipBumpingVersionsDueToChangesInCommon = Environment.GetEnvironmentVariable("SKIPBUMPINGVERSIONSDUETOCHANGESINCOMMON").ToLower() == "true"; + + if (shouldBeBumped.Count > 0 && !skipBumpingVersionsDueToChangesInCommon) + { + throw new Exception($"The following tasks should have their versions bumped due to changes in common: {string.Join(", ", shouldBeBumped)}"); + } + + foreach (var task in changedTests) + { + if (!toBeBuilt.Contains(task)) + { + toBeBuilt.Add(task); + } + } + + toBeBuilt = toBeBuilt.Where(taskName => Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "Tasks", taskName))).ToList(); + + return toBeBuilt; + } + + private static void SetTaskVariables(List tasks, List tasksForDowngradingCheck) + { + Console.WriteLine($"tasks: {string.Join(", ", tasks)}"); + Console.WriteLine($"tasksForDowngradingCheck: {string.Join(", ", tasksForDowngradingCheck)}"); + Console.WriteLine($"##vso[task.setVariable variable=task_pattern;isOutput=true;]@({string.Join("|", tasks)})"); + Console.WriteLine($"##vso[task.setVariable variable=task_pattern_fordowngradingcheck]@({string.Join("|", tasksForDowngradingCheck)})"); + Console.WriteLine($"##vso[task.setVariable variable=numTasks]{tasks.Count}"); + Console.WriteLine($"##vso[task.setVariable variable=numTasksForDowngradingCheck]{tasksForDowngradingCheck.Count}"); + } + + private static string Run(string command) + { + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/C {command}", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + process.Start(); + var result = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + return result; + } + + public static async Task FilterTasks() + { + try + { + var buildReason = Environment.GetEnvironmentVariable("BUILD_REASON").ToLower(); + var forceCourtesyPush = Environment.GetEnvironmentVariable("FORCE_COURTESY_PUSH")?.ToLower() == "true"; + var taskNameIsSet = Environment.GetEnvironmentVariable("TASKNAMEISSET")?.ToLower() == "true"; + var deployAllTasks = Environment.GetEnvironmentVariable("DEPLOY_ALL_TASKSVAR")?.ToLower() == "true"; + + List tasksFromParameter = null; + if (taskNameIsSet) + { + var taskName = Environment.GetEnvironmentVariable("TASKNAME"); + tasksFromParameter = taskName.Split(',').Select(item => item.Trim()).ToList(); + } + + var ciBuildReasonList = new List { "individualci", "batchedci", "schedule" }; + + if (deployAllTasks) + { + SetTaskVariables(GetTaskArray(makeOptions), GetTaskArray(makeOptions)); + } + else if (ciBuildReasonList.Contains(buildReason) || (forceCourtesyPush && !taskNameIsSet)) + { + var tasks = await GetTasksToBuildForCI(); + SetTaskVariables(tasks, tasks); + } + else + { + var buildSourceBranch = Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH"); + var regex = new System.Text.RegularExpressions.Regex(@"^refs\/pull\/(\d+)\/merge$"); + var prIdMatch = regex.Match(buildSourceBranch); + + if (buildReason == "pullrequest") + { + var tasks = await GetTasksToBuildForPR(null, false); + var tasksForDowngradingCheck = await GetTasksToBuildForPR(null, true); + SetTaskVariables(tasks, tasksForDowngradingCheck); + } + else if (buildReason == "manual" && prIdMatch.Success) + { + var prId = int.Parse(prIdMatch.Groups[1].Value); + var tasks = await GetTasksToBuildForPR(prId, false); + var tasksForDowngradingCheck = await GetTasksToBuildForPR(prId, true); + SetTaskVariables(tasks, tasksForDowngradingCheck); + } + else if (buildReason == "manual" && taskNameIsSet) + { + var unknownTasks = tasksFromParameter.Where(task => !GetTaskArray(makeOptions).Contains(task)).ToList(); + if (unknownTasks.Count > 0) + { + throw new Exception($"Can't find \"{string.Join(", ", unknownTasks)}\" task(s) in the make-options.json file."); + } + SetTaskVariables(tasksFromParameter, tasksFromParameter); + } + else + { + SetTaskVariables(GetTaskArray(makeOptions), GetTaskArray(makeOptions)); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"##vso[task.logissue type=error]{ex.Message}"); + Console.WriteLine("##vso[task.complete result=Failed;]"); + } + } + + private static List GetTaskArray(JsonNode? makeOptions) + { + return makeOptions["tasks"].AsArray().Select(task => task.ToString()).ToList(); + } + + public static async Task LocalMain(string[] args) + { + await FilterTasks(); + } +} diff --git a/ci/CiTool/Program.cs b/ci/CiTool/Program.cs new file mode 100644 index 00000000000..4f55e42b9fa --- /dev/null +++ b/ci/CiTool/Program.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +internal class Program +{ + public static async Task Main(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("No arguments provided."); + return 1; + } + + string command = args[0]; + string[] commandArgs = args.Skip(1).ToArray(); + + switch (command.ToLower()) + { + case "filtertasks": + await FilterTasksUtil.LocalMain(commandArgs); + break; + case "checkdowngrading": + await CheckDowngrading.LocalMain(commandArgs); + break; + default: + return 1; + } + + return 0; + } +}