diff --git a/.ci-config.json b/.ci-config.json
new file mode 100644
index 000000000000..c91aedb1bbb9
--- /dev/null
+++ b/.ci-config.json
@@ -0,0 +1,271 @@
+{
+ "rules": [
+ {
+ "patterns": [
+ ".azure-pipeline/*",
+ "NugGet.Config",
+ "Repo.props"
+ ],
+ "phases": [
+ "build:all",
+ "breaking-change:all",
+ "dependence:all",
+ "help:all",
+ "signature:all",
+ "test:all",
+ "sub-task:all"
+ ]
+ },
+ {
+ "patterns": [
+ "src/*.props"
+ ],
+ "phases": [
+ "build:all",
+ "dependence:all",
+ "test:all"
+ ]
+ },
+ {
+ "patterns": [
+ "src/lib/*"
+ ],
+ "phases": [
+ "build:all",
+ "dependence:all"
+ ]
+ },
+ {
+ "patterns": [
+ "docker/*",
+ "documentation/*",
+ ".github/*",
+ "setup/*",
+ ".dockerignore",
+ ".git*",
+ "appveyor.yml",
+ "CONTRIBUTION.md",
+ "LICENSE.txt",
+ "README.md",
+ "**/ChangeLog.md",
+ "**/readme.md",
+ "src/**/document/*"
+ ],
+ "phases": []
+ },
+ {
+ "patterns": [
+ "src/{ModuleName}/test/*",
+ "src/{ModuleName}/*.Test/*"
+ ],
+ "phases": [
+ "build:dependent-module",
+ "test:module"
+ ]
+ },
+ {
+ "patterns": [
+ "src/{ModuleName}/**/*.md"
+ ],
+ "phases": [
+ "build:module",
+ "help:module"
+ ]
+ },
+ {
+ "patterns": [
+ "src/{ModuleName}/**/*.csproj"
+ ],
+ "phases": [
+ "build:related-module",
+ "dependence:dependence-module",
+ "test:dependence-module"
+ ]
+ },
+ {
+ "patterns": [
+ "src/{ModuleName}/*"
+ ],
+ "phases": [
+ "build:related-module",
+ "breaking-change:module",
+ "help:module",
+ "signature:module",
+ "test:dependence-module"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/StaticAnalysis/Exceptions/{ModuleName}/MissingAssemblies.csv",
+ "tools/StaticAnalysis/Exceptions/{ModuleName}/AssemblyVersionConflict.csv",
+ "tools/StaticAnalysis/Exceptions/{ModuleName}/ExtraAssemblies.csv",
+ "tools/StaticAnalysis/Exceptions/{ModuleName}/SharedAssemblyConflict.csv"
+ ],
+ "phases": [
+ "build:module",
+ "dependence:module"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/StaticAnalysis/Exceptions/{ModuleName}/BreakingChangeIssues.csv"
+ ],
+ "phases": [
+ "build:module",
+ "breaking-change:module"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/StaticAnalysis/Exceptions/{ModuleName}/HelpIssues.csv"
+ ],
+ "phases": [
+ "build:module",
+ "help:module"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/StaticAnalysis/Exceptions/{ModuleName}/SignatureIssues.csv"
+ ],
+ "phases": [
+ "build:module",
+ "signature:module"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/StaticAnalysis/*",
+ "tools/Tools.Common/*"
+ ],
+ "phases": [
+ "build:all",
+ "breaking-change:all",
+ "dependence:all",
+ "help:all",
+ "signature:all"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/Az.Tools.Predictor/*"
+ ],
+ "phases": [
+ "sub-task:Predictor"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/Az.Tools.Installer/*"
+ ],
+ "phases": [
+ "sub-task:Installer"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/AddModulePsm1Dependency.ps1",
+ "tools/Common.Netcore.Dependencies.targets",
+ "tools/AzureRM.Example.psm1"
+ ],
+ "phases": [
+ "build:all",
+ "breaking-change:all",
+ "dependence:all",
+ "help:all",
+ "signature:all",
+ "test:all"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/GenerateHelp.ps1",
+ "tools/HelpGeneration/*"
+ ],
+ "phases": [
+ "build:all",
+ "help:all"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/CheckAssemblies.ps1"
+ ],
+ "phases": [
+ "build:all",
+ "dependence:all"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/CheckSignature.ps1"
+ ],
+ "phases": [
+ "build:all",
+ "signature:all"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/Common.Netcore.Dependencies.Test.targets"
+ ],
+ "phases": [
+ "build:all",
+ "test:all"
+ ]
+ },
+ {
+ "patterns": [
+ "tools/ARMIncrementVersion.ps1",
+ "tools/ARMSyncVersion.ps1",
+ "tools/ASMIncrementVersion.ps1",
+ "tools/AzureRM.Example.psm1",
+ "tools/BuildInstaller.ps1",
+ "tools/CheckChangeLog.ps1",
+ "tools/CheckIgnoredFile.ps1",
+ "tools/CleanupBuild.ps1",
+ "tools/CommonIncrementVersion.ps1",
+ "tools/CreateAliasMapping.ps1",
+ "tools/CreateFilterMappings.ps1",
+ "tools/CreateMappings_rules.json",
+ "tools/CreateMappings.ps1",
+ "tools/CreateRegistryEntry.ps1"
+ ],
+ "phases": []
+ },
+ {
+ "patterns": [
+ "tools/Az/*",
+ "tools/BatchModelGenerator/*",
+ "tools/BreakingChanges/*",
+ "tools/Docker/*",
+ "tools/FormatPs1XmlGenerator/*",
+ "tools/Gen2Master/*",
+ "tools/InstallationTests/*",
+ "tools/Installer/*",
+ "tools/NetCoreCsProjSync/*",
+ "tools/NetCorePsd1Sync/*",
+ "tools/ProjectTemplates/*",
+ "tools/RepoTasks/*",
+ "tools/SecurityTools/*",
+ "tools/Test/*",
+ "tools/Tools.Common.Test/*",
+ "tools/VersionController/*"
+ ],
+ "phases": []
+ },
+ {
+ "patterns": [
+ "others"
+ ],
+ "phases": [
+ "build:all",
+ "breaking-change:all",
+ "dependence:all",
+ "help:all",
+ "signature:all",
+ "test:all"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/build.proj b/build.proj
index 588c191a9f8a..dadb3b4aaeed 100644
--- a/build.proj
+++ b/build.proj
@@ -89,7 +89,7 @@
-
+
@@ -119,23 +119,33 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -150,6 +160,13 @@
+
+
+
+
+
+
+
@@ -159,20 +176,16 @@
-
-
-
+
+
-
-
-
-
-
+
+
-
+
build
publish
@@ -187,10 +200,10 @@
Microsoft.Powershell.*.dll,System*.dll,Microsoft.VisualBasic.dll,Microsoft.CSharp.dll,Microsoft.CodeAnalysis.dll,Microsoft.CodeAnalysis.CSharp.dll
System.Security.Cryptography.ProtectedData.dll,System.Configuration.ConfigurationManager.dll,System.Runtime.CompilerServices.Unsafe.dll,System.IO.FileSystem.AccessControl.dll,System.Buffers.dll,System.Text.Encodings.Web.dll,System.CodeDom.dll,System.Management.dll,System.Text.Json.dll,System.Threading.Tasks.Extensions.dll
-
+
-
+
@@ -209,18 +222,48 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -233,6 +276,14 @@
+
+
+
+
+
+
+
+
diff --git a/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIFilterTask.cs b/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIFilterTask.cs
new file mode 100644
index 000000000000..dc2077215c01
--- /dev/null
+++ b/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIFilterTask.cs
@@ -0,0 +1,428 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+using Newtonsoft.Json;
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.WindowsAzure.Build.Tasks
+{
+ ///
+ /// A simple Microsoft Build task used to generate a list of test assemblies to be
+ /// used for testing Azure PowerShell.
+ ///
+ public class CIFilterTask : Task
+ {
+ ///
+ /// Gets or sets the files changed in a given pull request.
+ ///
+ [Required]
+ public string[] FilesChanged { get; set; }
+
+ ///
+ /// Gets or set the TargetModule, e.g. Storage
+ ///
+ public string TargetModule { get; set; }
+
+ ///
+ /// Gets or set the Mode, e.g. Release
+ ///
+ [Required]
+ public string Mode { get; set; }
+
+ ///
+ /// Gets or sets the path to the files-to-csproj map.
+ ///
+ [Required]
+ public string CsprojMapFilePath { get; set; }
+
+ ///
+ /// Gets or sets the test assemblies output produced by the task.
+ ///
+ [Output]
+ public CIFilterTaskResult FilterTaskResult { get; set; }
+
+ private const string TaskMappingConfigName = ".ci-config.json";
+
+ private const string AllModule = "all";
+ private const string SingleModule = "module";
+ private const string DependenceModule = "dependence-module"; // self and modules dependent on this module
+ private const string DependentModule = "dependent-module"; // self and modules that self dependent on
+ private const string RelatedModule = "related-module"; // self, modules that self dependent on and modules dependent on this module
+
+ private const string BUILD_PHASE = "build";
+ private const string ANALYSIS_BREAKING_CHANGE_PHASE = "breaking-change";
+ private const string ANALYSIS_HELP_PHASE = "help";
+ private const string ANALYSIS_DEPENDENCY_PHASE = "dependency";
+ private const string ANALYSIS_SIGNATURE_PHASE = "signature";
+ private const string TEST_PHASE = "test";
+ private const string ACCOUNT_MODULE_NAME = "Accounts";
+
+ private const string MODULE_NAME_PLACEHOLDER = "ModuleName";
+
+ private Dictionary ReadMapFile(string mapFilePath, string mapFileName)
+ {
+ if (mapFilePath == null)
+ {
+ throw new ArgumentNullException(string.Format("The {0} cannot be null.", mapFileName));
+ }
+
+ if (!File.Exists(mapFilePath))
+ {
+ throw new FileNotFoundException(string.Format("The {0} provided could not be found. Please provide a valid MapFilePath.", mapFileName));
+ }
+
+ return JsonConvert.DeserializeObject>(File.ReadAllText(mapFilePath));
+ }
+
+ private List GetRelatedCsprojList(string moduleName, Dictionary csprojMap)
+ {
+ List csprojList = new List();
+
+ if (csprojMap.ContainsKey(moduleName))
+ {
+ csprojList.AddRange(csprojMap[moduleName]);
+ }
+ else
+ {
+ string expectKey = string.Format("src/{0}/", moduleName);
+ foreach (string key in csprojMap.Keys)
+ {
+ if (key.ToLower().Equals(expectKey.ToLower()))
+ {
+ csprojList.AddRange(csprojMap[key]);
+ }
+ }
+ }
+
+ return csprojList;
+ }
+
+ private List GetBuildCsprojList(string moduleName, Dictionary csprojMap)
+ {
+ if (moduleName.Equals(AllModule))
+ {
+ moduleName = ACCOUNT_MODULE_NAME;
+ }
+ return GetRelatedCsprojList(moduleName, csprojMap)
+ .Where(x => !x.Contains("Test")).ToList();
+ }
+
+ private string GetModuleNameFromCsprojPath(string csprojPath)
+ {
+ return csprojPath.Replace('/', '\\')
+ .Split(new string[] { "src\\" }, StringSplitOptions.None)[1]
+ .Split('\\')[0];
+ }
+
+ private List GetDependenceModuleList(string moduleName, Dictionary csprojMap)
+ {
+ List moduleList = new List();
+
+ foreach (string key in csprojMap.Keys)
+ {
+ bool isDependent = false;
+ foreach (string csproj in csprojMap[key])
+ {
+ if (csproj.Replace("/", "\\").Contains("\\" + moduleName + "\\"))
+ {
+ isDependent = true;
+ }
+ }
+ if (isDependent)
+ {
+ moduleList.Add(key.Split('/')[1]);
+ }
+ }
+
+ return moduleList;
+ }
+
+ private List GetDependentModuleList(string moduleName, Dictionary csprojMap)
+ {
+ if (moduleName.Equals(AllModule))
+ {
+ moduleName = ACCOUNT_MODULE_NAME;
+ }
+ return GetRelatedCsprojList(moduleName, csprojMap)
+ .Select(GetModuleNameFromCsprojPath)
+ .Distinct()
+ .ToList();
+ }
+
+ private List GetTestCsprojList(string moduleName, Dictionary csprojMap)
+ {
+ if (moduleName.Equals(AllModule))
+ {
+ moduleName = ACCOUNT_MODULE_NAME;
+ }
+ return GetRelatedCsprojList(moduleName, csprojMap)
+ .Where(x => x.Contains("Test")).ToList();;
+ }
+
+ private bool ProcessTargetModule(Dictionary csprojMap)
+ {
+ Dictionary> influencedModuleInfo = new Dictionary>
+ {
+ [BUILD_PHASE] = new HashSet(GetBuildCsprojList(TargetModule, csprojMap).ToList()),
+ [ANALYSIS_BREAKING_CHANGE_PHASE] = new HashSet(GetDependenceModuleList(TargetModule, csprojMap).ToList()),
+ [ANALYSIS_DEPENDENCY_PHASE] = new HashSet(GetDependenceModuleList(TargetModule, csprojMap).ToList()),
+ [ANALYSIS_HELP_PHASE] = new HashSet(GetDependenceModuleList(TargetModule, csprojMap).ToList()),
+ [ANALYSIS_SIGNATURE_PHASE] = new HashSet(GetDependenceModuleList(TargetModule, csprojMap).ToList()),
+ [TEST_PHASE] = new HashSet(GetTestCsprojList(TargetModule, csprojMap).ToList())
+ };
+
+ Console.WriteLine("----------------- InfluencedModuleInfo TargetModule -----------------");
+ foreach (string phaseName in influencedModuleInfo.Keys)
+ {
+ Console.WriteLine(string.Format("{0}: [{1}]", phaseName, string.Join(", ", influencedModuleInfo[phaseName].ToList())));
+ }
+ Console.WriteLine("--------------------------------------------------------");
+
+ FilterTaskResult.PhaseInfo = influencedModuleInfo;
+
+ return true;
+ }
+
+ private string ProcessSinglePattern(string pattern)
+ {
+ return pattern.Replace("**", ".*").Replace("{ModuleName}", "(?[^/]+)");
+ }
+
+ private Dictionary> CalculateInfluencedModuleInfoForEachPhase(List<(Regex, List)> ruleList, Dictionary csprojMap)
+ {
+ Dictionary> influencedModuleInfo = new Dictionary>();
+
+ foreach (string filePath in FilesChanged)
+ {
+ List phaseList = new List();
+ bool isMatched = false;
+ string machedModuleName = "";
+ foreach ((Regex regex, List phaseConfigList) in ruleList)
+ {
+ var regexResult = regex.Match(filePath);
+ if (regexResult.Success)
+ {
+ phaseList = phaseConfigList;
+ isMatched = true;
+ if (regexResult.Groups[MODULE_NAME_PLACEHOLDER].Success)
+ {
+ machedModuleName = regexResult.Groups[MODULE_NAME_PLACEHOLDER].Value;
+ }
+ Console.WriteLine(string.Format("File {0} match rule: {1} and phaseConfig is: [{2}]", filePath, regex.ToString(), string.Join(", ", phaseConfigList)));
+ break;
+ }
+ }
+ if (!isMatched)
+ {
+ Console.WriteLine(string.Format("File {0} doesn't match any rule, goto fallback logic.", filePath));
+ phaseList = new List()
+ {
+ BUILD_PHASE + ":" + AllModule,
+ ANALYSIS_BREAKING_CHANGE_PHASE + ":" + AllModule,
+ ANALYSIS_DEPENDENCY_PHASE + ":" + AllModule,
+ ANALYSIS_HELP_PHASE + ":" + AllModule,
+ ANALYSIS_SIGNATURE_PHASE + ":" + AllModule,
+ TEST_PHASE + ":" + AllModule,
+ };
+ }
+ foreach (string phase in phaseList)
+ {
+ string phaseName = phase.Split(':')[0];
+ string scope = phase.Split(':')[1];
+ HashSet scopes = influencedModuleInfo.ContainsKey(phaseName) ? influencedModuleInfo[phaseName] : new HashSet();
+ if (!scopes.Contains(AllModule))
+ {
+ if (scope.Equals(AllModule))
+ {
+ scopes.Clear();
+ scopes.Add(AllModule);
+ }
+ else
+ {
+ string moduleName = machedModuleName == "" ? filePath.Split('/')[1] : machedModuleName;
+ if (scope.Equals(SingleModule))
+ {
+ scopes.Add(moduleName);
+ }
+ else if (scope.Equals(DependenceModule))
+ {
+ scopes.UnionWith(GetDependenceModuleList(moduleName, csprojMap));
+ }
+ else if (scope.Equals(DependentModule))
+ {
+ scopes.UnionWith(GetDependentModuleList(moduleName, csprojMap));
+ }
+ else if (scope.Equals(RelatedModule))
+ {
+ scopes.UnionWith(GetDependenceModuleList(moduleName, csprojMap));
+ scopes.UnionWith(GetDependentModuleList(moduleName, csprojMap));
+ }
+ else
+ {
+ scopes.Add(scope);
+ }
+ }
+ influencedModuleInfo[phaseName] = scopes;
+ }
+ }
+ }
+ List expectedKeyList = new List()
+ {
+ BUILD_PHASE,
+ ANALYSIS_BREAKING_CHANGE_PHASE,
+ ANALYSIS_DEPENDENCY_PHASE,
+ ANALYSIS_HELP_PHASE,
+ ANALYSIS_SIGNATURE_PHASE,
+ TEST_PHASE
+ };
+ foreach (string phaseName in expectedKeyList)
+ {
+ if (!influencedModuleInfo.ContainsKey(phaseName))
+ {
+ influencedModuleInfo[phaseName] = new HashSet();
+ }
+ else if (influencedModuleInfo[phaseName].Contains(AllModule))
+ {
+ influencedModuleInfo[phaseName] = new HashSet(GetDependenceModuleList(ACCOUNT_MODULE_NAME, csprojMap));
+ }
+ }
+
+ foreach (string moduleName in influencedModuleInfo[TEST_PHASE])
+ {
+ if (!moduleName.Equals(ACCOUNT_MODULE_NAME))
+ {
+ influencedModuleInfo[BUILD_PHASE].UnionWith(GetDependentModuleList(moduleName, csprojMap));
+ }
+ }
+ if (influencedModuleInfo[BUILD_PHASE].Count == 0)
+ {
+ influencedModuleInfo[BUILD_PHASE].Add(ACCOUNT_MODULE_NAME);
+ }
+ Console.WriteLine("----------------- InfluencedModuleInfo -----------------");
+ foreach (string phaseName in influencedModuleInfo.Keys)
+ {
+ Console.WriteLine(string.Format("{0}: [{1}]", phaseName, string.Join(", ", influencedModuleInfo[phaseName].ToList())));
+ }
+ Console.WriteLine("--------------------------------------------------------");
+
+ return influencedModuleInfo;
+ }
+
+ /*
+ * Calculate the csproj path for modules in Build and Test phase.
+ */
+ private Dictionary> CalculateCsprojForBuildAndTest(Dictionary> influencedModuleInfo, Dictionary csprojMap)
+ {
+ var keys = influencedModuleInfo.Keys.ToList();
+ foreach (string phaseName in keys)
+ {
+ if (phaseName.Equals(BUILD_PHASE))
+ {
+ HashSet csprojSet = new HashSet();
+ foreach (string moduleName in influencedModuleInfo[phaseName])
+ {
+ csprojSet.UnionWith(GetBuildCsprojList(moduleName, csprojMap));
+ }
+ if (csprojSet.Count != 0)
+ {
+ foreach (string filename in Directory.GetFiles(@"src/Accounts", "*.csproj", SearchOption.AllDirectories).Where(x => !x.Contains("Test")))
+ {
+ csprojSet.Add(filename);
+ }
+ }
+ influencedModuleInfo[phaseName] = csprojSet;
+ }
+ else if (phaseName.Equals(TEST_PHASE))
+ {
+ HashSet csprojSet = new HashSet();
+ foreach (string moduleName in influencedModuleInfo[phaseName])
+ {
+ csprojSet.UnionWith(GetTestCsprojList(moduleName, csprojMap));
+ }
+ if (csprojSet.Count != 0)
+ {
+ csprojSet.Add("tools/TestFx/TestFx.csproj");
+ }
+ influencedModuleInfo[phaseName] = csprojSet;
+ }
+ }
+
+ foreach (string phaseName in influencedModuleInfo.Keys)
+ {
+ Console.WriteLine("-----------------------------------");
+ Console.WriteLine(string.Format("{0}: [{1}]", phaseName, string.Join(", ", influencedModuleInfo[phaseName].ToList())));
+ }
+
+ return influencedModuleInfo;
+ }
+
+ private bool ProcessFileChanged(Dictionary csprojMap)
+ {
+ string configPath = Path.GetFullPath(TaskMappingConfigName);
+ if (!File.Exists(configPath))
+ {
+ throw new Exception("CI phase config is not found!");
+ }
+ string content = File.ReadAllText(configPath);
+
+ CIPhaseFilterConfig config = JsonConvert.DeserializeObject(content);
+ List<(Regex, List)> ruleList = config.Rules.Select(rule => (new Regex(string.Join("|", rule.Patterns.Select(ProcessSinglePattern))), rule.Phases)).ToList();
+
+ DateTime startTime = DateTime.Now;
+
+ Dictionary> influencedModuleInfo = CalculateInfluencedModuleInfoForEachPhase(ruleList, csprojMap);
+ DateTime endOfRegularExpressionTime = DateTime.Now;
+
+ influencedModuleInfo = CalculateCsprojForBuildAndTest(influencedModuleInfo, csprojMap);
+ DateTime endTime = DateTime.Now;
+ Console.WriteLine(string.Format("Takes {0} seconds for RE match, {1} seconds for phase config.", (endOfRegularExpressionTime - startTime).TotalSeconds, (endTime - endOfRegularExpressionTime).TotalSeconds));
+
+ FilterTaskResult.PhaseInfo = influencedModuleInfo;
+
+ return true;
+ }
+
+ ///
+ /// Executes the task to generate a list of test assemblies
+ /// based on file changes from a specified Pull Request.
+ /// The output it produces is said list.
+ ///
+ /// Returns a value indicating wheter the success status of the task.
+ public override bool Execute()
+ {
+ FilterTaskResult = new CIFilterTaskResult();
+
+ var csprojMap = ReadMapFile(CsprojMapFilePath, "CsprojMapFilePath");
+
+ Console.WriteLine(string.Format("FilesChanged: {0}", FilesChanged.Length));
+ if (FilesChanged != null && FilesChanged.Length > 0)
+ {
+ return ProcessFileChanged(csprojMap);
+ }
+ else if (!string.IsNullOrWhiteSpace(TargetModule))
+ {
+ return ProcessTargetModule(csprojMap);
+ }
+ return true;
+ }
+ }
+}
diff --git a/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIFilterTaskResult.cs b/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIFilterTaskResult.cs
new file mode 100644
index 000000000000..fd71c81a5656
--- /dev/null
+++ b/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIFilterTaskResult.cs
@@ -0,0 +1,78 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+using Microsoft.Build.Framework;
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Microsoft.WindowsAzure.Build.Tasks
+{
+ public class CIFilterTaskResult : ITaskItem
+ {
+ readonly string _spec = "CIFilterTaskResult";
+ public Dictionary> PhaseInfo = new Dictionary>();
+
+ public string ItemSpec
+ {
+ get { return _spec; }
+ set { }
+ }
+
+ public ICollection MetadataNames
+ {
+ get
+ {
+ return PhaseInfo.Keys;
+ }
+ }
+ public int MetadataCount
+ {
+ get { return PhaseInfo.Keys.Count; }
+ }
+
+ public IDictionary CloneCustomMetadata()
+ {
+ Dictionary result = new Dictionary();
+
+ foreach (string key in PhaseInfo.Keys)
+ {
+ result[key] = string.Join(";", PhaseInfo[key].ToList());
+ }
+
+ return result;
+ }
+
+ public void CopyMetadataTo(ITaskItem destinationItem)
+ {
+ }
+
+ public string GetMetadata(string metadataName)
+ {
+ return string.Format("[{0}]", string.Join(", ", PhaseInfo[metadataName].ToList()));
+ }
+
+ public void RemoveMetadata(string metadataName)
+ {
+ }
+
+ public void SetMetadata(string metadataName, string metadataValue)
+ {
+ }
+ }
+}
diff --git a/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIPhaseFilterConfig.cs b/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIPhaseFilterConfig.cs
new file mode 100644
index 000000000000..1b3f32e93f9d
--- /dev/null
+++ b/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/CIPhaseFilterConfig.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+using System.Collections.Generic;
+
+namespace Microsoft.WindowsAzure.Build.Tasks
+{
+ class CIPhaseFilterConfig
+ {
+ public List Rules { get; set; }
+ }
+
+ class Rule
+ {
+ public List Patterns { get; set; }
+ public List Phases { get; set; }
+ }
+}
diff --git a/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/FilterTask.cs b/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/FilterTask.cs
deleted file mode 100644
index b2e4afe1d0fd..000000000000
--- a/tools/BuildPackagesTask/Microsoft.Azure.Build.Tasks/FilterTask.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// Copyright (c) Microsoft. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-namespace Microsoft.WindowsAzure.Build.Tasks
-{
- using Microsoft.Build.Framework;
- using Microsoft.Build.Utilities;
- using Newtonsoft.Json;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
-
- ///
- /// A simple Microsoft Build task used to generate a list of test assemblies to be
- /// used for testing Azure PowerShell.
- ///
- public class FilterTask : Task
- {
- ///
- /// Gets or sets the files changed in a given pull request.
- ///
- [Required]
- public string[] FilesChanged { get; set; }
-
- ///
- /// Gets or sets the path to the files-to-test-assemblies map.
- ///
- [Required]
- public string MapFilePath { get; set; }
-
- ///
- /// Gets or set the TargetModule, e.g. Storage
- ///
- public string TargetModule { get; set; }
-
- ///
- /// Gets or sets the test assemblies output produced by the task.
- ///
- [Output]
- public string[] Output { get; set; }
-
- ///
- /// Executes the task to generate a list of test assemblies
- /// based on file changes from a specified Pull Request.
- /// The output it produces is said list.
- ///
- /// Returns a value indicating wheter the success status of the task.
- public override bool Execute()
- {
- if (MapFilePath == null)
- {
- throw new ArgumentNullException("The MapFilePath cannot be null.");
- }
-
- if (!File.Exists(MapFilePath))
- {
- throw new FileNotFoundException("The MapFilePath provided could not be found. Please provide a valid MapFilePath.");
- }
-
- var mappingsDictionary = JsonConvert.DeserializeObject>(File.ReadAllText(MapFilePath));
-
- if (FilesChanged != null && FilesChanged.Length > 0)
- {
- Console.WriteLine($"Filter according to {FilesChanged.Length} file(s) in FilesChanged");
- var filesChangedSet = new HashSet(FilesChanged);
- Output = SetGenerator.Generate(filesChangedSet, mappingsDictionary).ToArray();
- }
- else if(!string.IsNullOrWhiteSpace(TargetModule))
- {
- Console.WriteLine($"Filter module {TargetModule}");
- var modules = (TargetModule.Equals("Accounts"))? new string[] { "Accounts"} : new string[] { "Accounts" , TargetModule};
- Output = SetGenerator.Generate(modules, mappingsDictionary).ToArray();
- }
- else
- {
- Console.WriteLine($"Skip filter and load all from ${MapFilePath}");
- var set = new HashSet();
- mappingsDictionary = JsonConvert.DeserializeObject>(File.ReadAllText(MapFilePath));
- foreach (KeyValuePair pair in mappingsDictionary)
- {
- set.UnionWith(pair.Value);
- }
-
- Output = set.ToArray();
- }
-
- return true;
- }
- }
-}
diff --git a/tools/CreateFilterMappings.ps1 b/tools/CreateFilterMappings.ps1
index 4b668785796e..db41cc5e181d 100644
--- a/tools/CreateFilterMappings.ps1
+++ b/tools/CreateFilterMappings.ps1
@@ -267,15 +267,24 @@ function Add-CsprojMappings
$Values = New-Object System.Collections.Generic.HashSet[string]
foreach ($CsprojFile in $CsprojFiles)
{
- $Project = $CsprojFile.BaseName
- foreach ($Solution in $Script:ProjectToSolutionMappings[$Project])
+ $Fields = $CsprojFile.FullName.Replace('/', '\').Split('\')
+ $Project = $Fields[$Fields.Length - 2]
+ foreach ($ProjectName in $Script:ProjectToSolutionMappings.Keys)
{
- foreach ($ReferencedProject in $Script:SolutionToProjectMappings[$Solution])
+ foreach ($Solution in $Script:ProjectToSolutionMappings[$ProjectName])
{
- $TempValue = $Script:ProjectToFullPathMappings[$ReferencedProject]
- if (-not [string]::IsNullOrEmpty($TempValue))
+ $Fields = $Solution.Replace('/', '\').Split('\')
+ $ProjectNameFromSolution = $Fields[$Fields.Length - 2]
+ if ($ProjectNameFromSolution -eq $Project)
{
- $Values.Add($TempValue) | Out-Null
+ foreach ($ReferencedProject in $Script:SolutionToProjectMappings[$Solution])
+ {
+ $TempValue = $Script:ProjectToFullPathMappings[$ReferencedProject]
+ if (-not [string]::IsNullOrEmpty($TempValue))
+ {
+ $Values.Add($TempValue) | Out-Null
+ }
+ }
}
}
}
@@ -292,8 +301,8 @@ $Script:ProjectToFullPathMappings = Create-ProjectToFullPathMappings
$Script:SolutionToProjectMappings = Create-SolutionToProjectMappings
$Script:ProjectToSolutionMappings = Create-ProjectToSolutionMappings
-Create-ModuleMappings
+# Create-ModuleMappings
Create-CsprojMappings
-$Script:ModuleMappings | Format-Json | Set-Content -Path (Join-Path -Path $Script:RootPath -ChildPath "ModuleMappings.json")
+# $Script:ModuleMappings | Format-Json | Set-Content -Path (Join-Path -Path $Script:RootPath -ChildPath "ModuleMappings.json")
$Script:CsprojMappings | Format-Json | Set-Content -Path (Join-Path -Path $Script:RootPath -ChildPath "CsprojMappings.json")
\ No newline at end of file
diff --git a/tools/StaticAnalysis/DependencyAnalyzer/DependencyMap.cs b/tools/StaticAnalysis/DependencyAnalyzer/DependencyMap.cs
index 2285aec35dbc..7604c7c41402 100644
--- a/tools/StaticAnalysis/DependencyAnalyzer/DependencyMap.cs
+++ b/tools/StaticAnalysis/DependencyAnalyzer/DependencyMap.cs
@@ -77,19 +77,18 @@ public bool Match(IReportRecord other)
public IReportRecord Parse(string line)
{
- var matcher = "\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\"";
+ var matcher = "\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\"";
var match = Regex.Match(line, matcher);
- if (!match.Success || match.Groups.Count < 7)
+ if (!match.Success || match.Groups.Count < 6)
{
- throw new InvalidOperationException(string.Format("Could not parse '{0}' as ExtraAssembly record", line));
+ throw new InvalidOperationException(string.Format("Could not parse '{0}' as DependencyMap record", line));
}
- Directory = match.Groups[1].Value;
- AssemblyName = match.Groups[2].Value;
- Severity = int.Parse(match.Groups[3].Value);
- ProblemId = int.Parse(match.Groups[4].Value);
- Description = match.Groups[5].Value;
- Remediation = match.Groups[6].Value;
+ AssemblyName = match.Groups[1].Value;
+ AssemblyVersion = match.Groups[2].Value;
+ ReferencingAssembly = match.Groups[3].Value;
+ ReferencingAssemblyVersion = match.Groups[4].Value;
+ Directory = match.Groups[5].Value;
return this;
}
}
diff --git a/tools/StaticAnalysis/IssueChecker/IssueChecker.cs b/tools/StaticAnalysis/IssueChecker/IssueChecker.cs
new file mode 100644
index 000000000000..6bbb05e881b1
--- /dev/null
+++ b/tools/StaticAnalysis/IssueChecker/IssueChecker.cs
@@ -0,0 +1,136 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using Tools.Common.Issues;
+using Tools.Common.Loggers;
+using StaticAnalysis.BreakingChangeAnalyzer;
+using StaticAnalysis.DependencyAnalyzer;
+
+namespace StaticAnalysis.IssueChecker
+{
+ public class IssueChecker : IStaticAnalyzer
+ {
+ private readonly List<(string, string)> exceptionLogInfoList = new List<(string, string)>()
+ {
+ ("BreakingChangeIssues.csv", "BreakingChangeIssues"),
+ ("AssemblyVersionConflict.csv", "AssemblyVersionConflict"),
+ ("SharedAssemblyConflict.csv", "SharedAssemblyConflict"),
+ ("MissingAssemblies.csv", "MissingAssembly"),
+ ("ExtraAssemblies.csv", "ExtraAssembly"),
+ };
+ public AnalysisLogger Logger { get; set; }
+
+ public string Name { get; private set; }
+
+ public IssueChecker()
+ {
+ Name = "Issue Checker";
+ }
+
+ public void Analyze(IEnumerable scopes)
+ {
+ Analyze(scopes, null);
+ }
+
+ public void Analyze(IEnumerable scopes, IEnumerable modulesToAnalyze)
+ {
+ foreach (string scope in scopes)
+ {
+ Console.WriteLine(scope);
+ }
+ if (scopes.ToList().Count != 1)
+ {
+ throw new InvalidOperationException(string.Format("scopes for IssueChecker should be a array contains only reportsDirectory, but here is [{0}]", string.Join(", ", scopes.ToList())));
+ }
+ string reportsDirectory = scopes.First();
+
+ bool hasCriticalIssue = false;
+ foreach ((string, string) item in exceptionLogInfoList)
+ {
+ string exceptionFileName = item.Item1;
+ string recordTypeName = item.Item2;
+
+ string exceptionFilePath = Path.Combine(reportsDirectory, exceptionFileName);
+ if (!File.Exists(exceptionFilePath))
+ {
+ continue;
+ }
+ if (IsSingleExceptionFileHasCriticalIssue(exceptionFilePath, recordTypeName))
+ {
+ hasCriticalIssue = true;
+ }
+ }
+ if (hasCriticalIssue)
+ {
+ throw new InvalidOperationException(string.Format("One or more errors occurred in validation. " +
+ "See the analysis reports at {0} for details",
+ reportsDirectory));
+ }
+ }
+
+ private bool IsSingleExceptionFileHasCriticalIssue(string exceptionFilePath, string reportRecordTypeName)
+ {
+ bool hasError = false;
+ using (var reader = new StreamReader(exceptionFilePath))
+ {
+ List recordList = new List();
+ string header = reader.ReadLine();
+ while (!reader.EndOfStream)
+ {
+ string line = reader.ReadLine();
+ IReportRecord newRecord = ReportRecordFactory.Create(reportRecordTypeName);
+ recordList.Add(newRecord.Parse(line));
+ }
+ var errorText = new StringBuilder();
+ errorText.AppendLine(recordList.First().PrintHeaders());
+ foreach (IReportRecord record in recordList)
+ {
+ if (record.Severity < 2)
+ {
+ hasError = true;
+ errorText.AppendLine(record.FormatRecord());
+ }
+ }
+ if (hasError)
+ {
+ Console.WriteLine("{0} Errors", exceptionFilePath);
+ Console.WriteLine(errorText.ToString());
+ }
+ }
+ return hasError;
+ }
+
+ public void Analyze(IEnumerable cmdletProbingDirs, Func, IEnumerable> directoryFilter, Func cmdletFilter)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Analyze(IEnumerable cmdletProbingDirs, Func, IEnumerable> directoryFilter, Func cmdletFilter, IEnumerable modulesToAnalyze)
+ {
+ throw new NotImplementedException();
+ }
+
+ public AnalysisReport GetAnalysisReport()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/tools/StaticAnalysis/Program.cs b/tools/StaticAnalysis/Program.cs
index 12dd887d7319..c5849ff06920 100644
--- a/tools/StaticAnalysis/Program.cs
+++ b/tools/StaticAnalysis/Program.cs
@@ -28,7 +28,6 @@ public class Program
{
static IList Analyzers = new List()
{
- new DependencyAnalyzer.DependencyAnalyzer()
};
static IList ExceptionFileNames = new List()
@@ -51,9 +50,9 @@ public static void Main(string[] args)
try
{
string installDir = null;
- if (args.Any(a => a == "--package-directory" || a == "-p"))
+ if (args.Any(a => a.Equals("--package-directory") || a.Equals("-p")))
{
- int idx = Array.FindIndex(args, a => a == "--package-directory" || a == "-p");
+ int idx = Array.FindIndex(args, a => a.Equals("--package-directory") || a.Equals("-p"));
if (idx + 1 == args.Length)
{
throw new ArgumentException("No value provided for the --package-directory parameter.");
@@ -77,7 +76,7 @@ public static void Main(string[] args)
bool logReportsDirectoryWarning = true;
if (args.Any(a => a == "--reports-directory" || a == "-r"))
{
- int idx = Array.FindIndex(args, a => a == "--reports-directory" || a == "-r");
+ int idx = Array.FindIndex(args, a => a.Equals("--reports-directory") || a.Equals("-r"));
if (idx + 1 == args.Length)
{
throw new ArgumentException("No value provided for the --reports-directory parameter.");
@@ -93,9 +92,9 @@ public static void Main(string[] args)
}
var modulesToAnalyze = new List();
- if (args.Any(a => a == "--modules-to-analyze" || a == "-m"))
+ if (args.Any(a => a.Equals("--modules-to-analyze") || a.Equals("-m")))
{
- int idx = Array.FindIndex(args, a => a == "--modules-to-analyze" || a == "-m");
+ int idx = Array.FindIndex(args, a => a.Equals("--modules-to-analyze") || a.Equals("-m"));
if (idx + 1 == args.Length)
{
Console.WriteLine("No value provided for the --modules-to-analyze parameter. Filtering over all built modules.");
@@ -106,25 +105,59 @@ public static void Main(string[] args)
}
}
- Analyzers.Add(new SignatureVerifier.SignatureVerifier());
- Analyzers.Add(new BreakingChangeAnalyzer.BreakingChangeAnalyzer());
+ foreach (var moduleName in modulesToAnalyze)
+ {
+ Console.WriteLine(string.Format("Module: {0}", moduleName));
+ }
- var helpOnly = args.Any(a => a == "--help-only" || a == "-h");
- var skipHelp = !helpOnly && args.Any(a => a == "--skip-help" || a == "-s");
- if(helpOnly)
+ bool needToCheckIssue = false;
+ if (args.Any(a => a.Equals("--analyzers")))
{
- Analyzers.Clear();
+ int idx = Array.FindIndex(args, a => a.Equals("--analyzers"));
+ if (idx + 1 == args.Length)
+ {
+ throw new ArgumentException("No value provided for the --package-directory parameter.");
+ }
+
+ string analyzerNameList = args[idx + 1];
+ foreach (string analyzerName in analyzerNameList.Split(';'))
+ {
+ if (analyzerName.ToLower().Equals("breaking-change"))
+ {
+ Analyzers.Add(new BreakingChangeAnalyzer.BreakingChangeAnalyzer());
+ }
+ if (analyzerName.ToLower().Equals("dependency"))
+ {
+ Analyzers.Add(new DependencyAnalyzer.DependencyAnalyzer());
+ }
+ if (analyzerName.ToLower().Equals("signature"))
+ {
+ Analyzers.Add(new SignatureVerifier.SignatureVerifier());
+ }
+ if (analyzerName.ToLower().Equals("help"))
+ {
+ Analyzers.Add(new HelpAnalyzer.HelpAnalyzer());
+ }
+ if (analyzerName.ToLower().Equals("check-error"))
+ {
+ needToCheckIssue = true;
+ }
+ }
}
- if (!skipHelp)
+ else
{
+ Analyzers.Add(new BreakingChangeAnalyzer.BreakingChangeAnalyzer());
+ Analyzers.Add(new DependencyAnalyzer.DependencyAnalyzer());
+ Analyzers.Add(new SignatureVerifier.SignatureVerifier());
Analyzers.Add(new HelpAnalyzer.HelpAnalyzer());
+ needToCheckIssue = true;
}
// https://stackoverflow.com/a/9737418/294804
var assemblyDirectory = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);
ExceptionsDirectory = Path.Combine(assemblyDirectory, "Exceptions");
- bool useExceptions = !args.Any(a => a == "--dont-use-exceptions" || a == "-d");
- var useNetcore = args.Any(a => a == "--use-netcore" || a == "-u");
+ bool useExceptions = !args.Any(a => a.Equals("--dont-use-exceptions") || a.Equals("-d"));
+ var useNetcore = args.Any(a => a.Equals("--use-netcore") || a.Equals("-u"));
ConsolidateExceptionFiles(ExceptionsDirectory, useNetcore);
analysisLogger = useExceptions ? new AnalysisLogger(reportsDirectory, ExceptionsDirectory) : new AnalysisLogger(reportsDirectory);
@@ -142,7 +175,12 @@ public static void Main(string[] args)
}
analysisLogger.WriteReports();
- analysisLogger.CheckForIssues(2);
+ if (needToCheckIssue)
+ {
+ var analyzer = new IssueChecker.IssueChecker();
+ analyzer.Analyze(new[] { reportsDirectory });
+ }
+ //analysisLogger.CheckForIssues(2);
}
finally
{
diff --git a/tools/StaticAnalysis/ReportRecordFactory.cs b/tools/StaticAnalysis/ReportRecordFactory.cs
new file mode 100644
index 000000000000..2ad58aeb1709
--- /dev/null
+++ b/tools/StaticAnalysis/ReportRecordFactory.cs
@@ -0,0 +1,55 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using StaticAnalysis.BreakingChangeAnalyzer;
+using StaticAnalysis.DependencyAnalyzer;
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Tools.Common.Issues;
+
+namespace StaticAnalysis
+{
+ public static class ReportRecordFactory
+ {
+ public static IReportRecord Create(string type)
+ {
+ if (type.Equals("BreakingChangeIssue"))
+ {
+ return new BreakingChangeIssue();
+ }
+ if (type.Equals("AssemblyVersionConflict"))
+ {
+ return new AssemblyVersionConflict();
+ }
+ if (type.Equals("SharedAssemblyConflict"))
+ {
+ return new SharedAssemblyConflict();
+ }
+ if (type.Equals("MissingAssembly"))
+ {
+ return new MissingAssembly();
+ }
+ if (type.Equals("ExtraAssembly"))
+ {
+ return new ExtraAssembly();
+ }
+
+ return null;
+ }
+ }
+}