/// <summary> /// Initializes this object. /// </summary> /// /// <param name="host"> The host. </param> public void Initialize(IHost host) { this.host = host; switch (host.GitPaths.Count) { case 0: host.AddResult(Severity.Warning, false, $"Git.exe not found on PATH."); return; case 1: break; default: foreach (String fn in host.GitPaths) { host.AddResult(Severity.Warning, true, $"Git.exe found at: '{fn}'."); } break; } if (!Directory.Exists(Utils.workdir)) { Directory.CreateDirectory(Utils.workdir); } }
/// <summary> /// Executes. /// </summary> /// /// <remarks> /// Detects if the solution contains a correctly named asset pair (normal/portable) and the /// PORTABLE define is correctly set (for both portable and .net core projects). /// </remarks> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Building '{Path.GetFileNameWithoutExtension(job.Parm)}' Project]"); // See https://stackoverflow.com/questions/26034558/how-to-force-msbuild-to-run-code-analysis-without-recompiling // See https://social.msdn.microsoft.com/forums/en-US/9e77b76c-3ca5-42b4-b946-5c9d71f27ab3/invoke-code-metrics-calculation-programmatically // // Analyze.CalculateCodeMetricsforSolution // Directory.SetCurrentDirectory(Path.GetDirectoryName(job.Parm)); return(Utils.ExecutePsi(new ProcessStartInfo { WorkingDirectory = Path.GetDirectoryName(job.Parm), Arguments = $"{Path.GetFileName(job.Parm)} /target:Clean;Build /p:RunCodeAnalysis=false", FileName = host.MSBuildPath, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, UseShellExecute = false, }, host, out StringBuilder sb, false, true) == 0); #warning warning MSB3061: Unable to delete file "C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\bin\Debug\RageAssetManager.dll". The process cannot access the file '<path>RageAssetManager.dll' //return p.ExitCode == 0; }
public bool Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{job.GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Examining Output of '{Path.GetFileNameWithoutExtension(job.Parm)}' Project for Interfaces]"); if (File.Exists(job.Parm)) { Disassemble(job.Parm); } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Assembly."); return(false); } return(true); }
/// <summary> /// Executes the psi operation. /// </summary> /// /// <param name="psi"> The psi. </param> /// <param name="host"> The host. </param> /// <param name="sb"> [out] The sb. </param> /// <param name="logstd"> (Optional) True to log. </param> /// <param name="logerr"> (Optional) True to log errors. </param> /// <param name="error"> (Optional) The error. </param> /// /// <returns> /// An Int32. /// </returns> public static Int32 ExecutePsi(ProcessStartInfo psi, IHost host, out StringBuilder sb, Boolean logstd = true, Boolean logerr = true, Severity error = Severity.Error) { host.AddResult(Severity.Info, true, $"{Path.GetFileNameWithoutExtension(psi.FileName)}{(String.IsNullOrEmpty(psi.Arguments) ? String.Empty : " " + psi.Arguments)} Output:"); Process p = Process.Start(psi); sb = new StringBuilder(); //! Standard Output // while (psi.RedirectStandardOutput && !p.StandardOutput.EndOfStream) { String line = p.StandardOutput.ReadLine(); sb.AppendLine(line); if (logstd && !String.IsNullOrEmpty(line)) { host.AddResult(Severity.Info, true, line, 1); } } //! Error Output // while (psi.RedirectStandardError && !p.StandardError.EndOfStream) { String line = p.StandardError.ReadLine(); sb.AppendLine(line); if (logstd && !String.IsNullOrEmpty(line)) { //! Git has the habit to emit a number of messages in the error stream. // host.AddResult(error, (error == Severity.Info), line, 1); } } p.WaitForExit(); host.AddResult(p.ExitCode == 0 ? Severity.Info : Severity.Warning, p.ExitCode == 0, $"{Path.GetFileNameWithoutExtension(psi.FileName)} Job {(p.ExitCode == 0 ? "Success" : "Failure")} with (ExitCode: {p.ExitCode})."); return(p.ExitCode); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<String,Boolean> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); if (File.Exists(job.Parm) && TryAnalyzeProject(job, out OutputType outputType, out String outputFile)) { switch (outputType) { case OutputType.Exe: host.AddResult(Severity.Warning, true, $"{outputType} Project Output not analysed."); break; case OutputType.Library: #warning RCSAA Specific Code (Skip AssetManager from processing if present). if (Utils.IsAssetManager(job.Parm, host)) { //break; } else if (Utils.IsUnitTest(job.Parm)) { host.RecurseForTypes(job, FileType.UnitTest, outputFile); } else { host.RecurseForTypes(job, FileType.Assembly, outputFile); } break; case OutputType.WinExe: host.AddResult(Severity.Warning, true, $"{outputType} Project Output not analysed."); break; case OutputType.Unknown: host.AddResult(Severity.Warning, true, $"{outputType} Project Output not analysed."); break; } }
/// <summary> /// Query if 'csproj' is asset manager. /// </summary> /// /// <param name="csproj"> The csproj. </param> /// /// <returns> /// True if asset manager, false if not. /// </returns> public static Boolean IsAssetManager(String csproj, IHost host) { if (Utils.Load(csproj, out XDocument doc, out XmlNamespaceManager namespaces)) { String AM = doc.Root.XPathSelectElement("ns:PropertyGroup/ns:AssemblyName", namespaces).Value; Boolean isAM = AM.StartsWith("RageAssetManager"); if (isAM) { host.AddResult(Severity.Warning, true, $"The {AM} Project should not be part of the repository.", 1); } return(isAM); } return(false); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Calculating Lines of Code of '{Path.GetFileNameWithoutExtension(job.Parm)}' Project]"); if (File.Exists(job.Parm)) { List <String> lines = Decompile(job.Parm) .Split(new char[] { '\r', '\n' }).ToList(); Int32 LOC = lines.Count; //! See: https://dwheeler.com/sloccount/sloccount.html //! See: http://cloc.sourceforge.net/ // List <String> nonemptylines = lines .Where(p => !String.IsNullOrEmpty(p.Trim())) .ToList(); Int32 SLOC = nonemptylines.Count; List <String> trimmed = nonemptylines .Where(p => !(new String[] { "{", "}" }.Contains(p.Trim()))) .ToList(); Int32 LLOC = trimmed.Count; host.AddResult(Severity.Info, true, $"~ {LOC} Lines of Code (LOC) counted in {Path.GetFileName(job.Parm)}."); host.AddResult(Severity.Info, true, $"~ {SLOC} Source Lines of Code (SLOC) counted in {Path.GetFileName(job.Parm)}."); host.AddResult(Severity.Info, true, $"~ {LLOC} Logical Lines of Code (LLOC) counted in {Path.GetFileName(job.Parm)}."); } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Assembly."); return(false); } return(true); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<String,Boolean> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Examining '{Path.GetFileNameWithoutExtension(job.Parm)}' Project]"); if (File.Exists(job.Parm)) { if (Utils.Load(job.Parm, out XDocument doc, out XmlNamespaceManager namespaces)) { OutputType outputType = (OutputType)Enum.Parse(typeof(OutputType), doc.Root.XPathSelectElement("ns:PropertyGroup/ns:OutputType", namespaces).Value); XElement firstPropertyGroup = doc.Root.XPathSelectElement("ns:PropertyGroup", namespaces); String nameSpace = firstPropertyGroup.XPathSelectElement("ns:RootNamespace", namespaces)?.Value; String assemblyName = firstPropertyGroup.XPathSelectElement("ns:AssemblyName", namespaces)?.Value; String targetFrameworkVersion = firstPropertyGroup.XPathSelectElement("ns:TargetFrameworkVersion", namespaces)?.Value; String targetFrameworkProfile = firstPropertyGroup.XPathSelectElement("ns:TargetFrameworkProfile", namespaces)?.Value; //String defineConstants = firstPropertyGroup.XPathSelectElement("ns:DefineConstants", namespaces)?.Value; String projectDir = Path.GetDirectoryName(job.Parm); foreach (XElement propertyGroup in doc.Root.XPathSelectElements(@"ns:PropertyGroup", namespaces)) { XAttribute condition = propertyGroup.Attribute("Condition"); if (condition != null) { // TODO Decode this better (AnyCPU) for example. // String value = condition.Value; if (value.Equals($" '$(Configuration)|$(Platform)' == {Utils.debug} ")) { host.AddResult(Severity.Info, true, $"Found {Utils.debug} Condition."); // DONE <OutputPath>bin\Debug\</OutputPath> Check // DONE <DocumentationFile>bin\Debug\RageAssetManager.XML</DocumentationFile> Check // TODO <DefineConstants>TRACE</DefineConstants> Check // DONE <DebugSymbols>true</DebugSymbols> Check // TODO <Prefer32Bit>false</Prefer32Bit> Check // String outputPath = propertyGroup.XPathSelectElement("ns:OutputPath", namespaces)?.Value; String debugSymbols = propertyGroup.XPathSelectElement("ns:DebugSymbols", namespaces)?.Value; String documentationFile = propertyGroup.XPathSelectElement("ns:DocumentationFile", namespaces)?.Value; String output = Path.GetFullPath(Path.Combine(projectDir, outputPath)); switch (outputType) { //! Console Applications. // case OutputType.Exe: output = Path.GetFullPath(Path.Combine(output, Path.ChangeExtension(assemblyName, ".exe"))); break; //! Assemblies // case OutputType.Library: output = Path.GetFullPath(Path.Combine(output, Path.ChangeExtension(assemblyName, ".dll"))); if (File.Exists(output)) { host.AddResult(Severity.Info, true, $"Located '{output}'."); if (Utils.IsUnitTest(job.Parm)) { host.RecurseForTypes(job, FileType.UnitTest, output); } else { host.RecurseForTypes(job, FileType.Assembly, output); } } break; //! Windows GUI Applications // case OutputType.WinExe: output = Path.GetFullPath(Path.Combine(output, Path.ChangeExtension(assemblyName, ".exe"))); break; } } } } } } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Project."); } return(true); }
/// <summary> /// Executes. /// </summary> /// /// <remarks> /// Detects if the solution contains a correctly named asset pair (normal/portable). /// </remarks> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { String nasset = String.Empty; String passet = String.Empty; String casset = String.Empty; #warning TODO Add Support for Test Suite and other exe (demo) project). host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Examnining '{Path.GetFileNameWithoutExtension(job.Parm)}' Solution]"); if (File.Exists(job.Parm)) { Solution solution = new Solution(); if (solution.Load(job.Parm)) { foreach (SolutionProject project in solution.Projects) { if (project.ProjectTypeGuid.Equals(new Guid(Lookups.ProjectTypeGuids["C#"]))) { String csproj = Path.Combine(Path.GetDirectoryName(solution.SolutionPath), project.RelativePath); if (File.Exists(csproj)) { //! Skip Test Projects. //! Skip AssetManager from processing if present. if (Utils.ProjectOutputType(csproj) == Utils.OutputType.Library && !Utils.IsUnitTest(csproj) && !Utils.IsAssetManager(csproj, host)) { String output = Utils.ProjectOutput(csproj); if (File.Exists(output)) { #warning TODO IsAsset may fail a second time or just on loading. // System.IO.FileLoadException: 'Could not load file or assembly 'ICSharpCode.Decompiler, // Version = 3.1.0.3652, Culture = neutral, PublicKeyToken = d4bfe873e7598c49' or one of its dependencies. // The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)' if (Utils.IsAsset(output)) { host.AddResult(Severity.Debug, true, $"Asset Detected: '{Path.GetFileNameWithoutExtension(output)}'."); nasset = Path.GetFileNameWithoutExtension(output); } else if (Utils.IsPortableAsset(output)) { host.AddResult(Severity.Debug, true, $"Portable Asset Detected: '{Path.GetFileNameWithoutExtension(output)}'."); passet = Path.GetFileNameWithoutExtension(output); } else if (Utils.IsCoreAsset(output)) { host.AddResult(Severity.Debug, true, $".Net Core Asset Detected: '{Path.GetFileNameWithoutExtension(output)}'."); casset = Path.GetFileNameWithoutExtension(output); } } else { if (String.IsNullOrEmpty(output)) { host.AddResult(Severity.Warning, false, $"Failed to Locate Output."); } else { host.AddResult(Severity.Warning, false, $"Failed to Load Output: '{Path.GetFileName(output)}'."); } } } } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Project."); } } } } else { host.AddResult(Severity.Info, false, $"Failed to Load or Parse Solution {job.Parm}."); return(false); } } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Solution."); return(false); } //Boolean misnamed = false; // No assets found. // if (nasset.Length + passet.Length + casset.Length == 0) { host.AddResult(Severity.Warning, false, $"No Asset assembly detected."); return(false); } //! Main asset not found. // if (String.IsNullOrEmpty(nasset)) { host.AddResult(Severity.Warning, false, $"Main Asset assembly not detected."); } //! Portable asset naming. // if (String.IsNullOrEmpty(passet)) { host.AddResult(Severity.Warning, false, $"Portable Asset assembly not detected."); } else { //! Portable asset naming convention, '_Portable' suffix. // if (Path.GetFileNameWithoutExtension(passet).Equals(Path.GetFileNameWithoutExtension(nasset) + "_Portable")) { host.AddResult(Severity.Info, true, $"Portable Asset assembly correctly named: '{Path.GetFileNameWithoutExtension(passet)}'."); } else { host.AddResult(Severity.Warning, false, $"Portable Asset assembly misnamed, got '{Path.GetFileNameWithoutExtension(passet)}' expected '{Path.GetFileNameWithoutExtension(nasset) + "_Portable"}'."); //misnamed = true; } } //! Core asset naming. // if (String.IsNullOrEmpty(casset)) { host.AddResult(Severity.Warning, false, $".Net Core Asset assembly not detected."); } else { //! Core asset naming convention, '_Core' suffix. // if (Path.GetFileNameWithoutExtension(passet).Equals(Path.GetFileNameWithoutExtension(casset) + "_Core")) { host.AddResult(Severity.Info, true, $".Net Core Asset assembly correctly named: '{Path.GetFileNameWithoutExtension(casset)}'."); } else { host.AddResult(Severity.Warning, false, $".Net Core Asset assembly misnamed, got '{Path.GetFileNameWithoutExtension(passet)}' expected '{Path.GetFileNameWithoutExtension(nasset) + "_Portable"}'."); //misnamed = true; } } return(true); }
/// <summary> /// Fixup assembly references. /// </summary> /// /// <param name="csproj"> The csproj. </param> public static void FixupAssemblyReferences(IHost host, String csproj) { //! Implemented //< Reference Include="RageAssetManager"> // <HintPath>..\..\AssetManager\RageAssetManager\bin\Debug\RageAssetManager.dll</HintPath> //</Reference> //! Fails (Needs a nuget restore?) // // <HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net35\Newtonsoft.Json.dll</HintPath> //! see https://stackoverflow.com/questions/837488/how-can-i-get-the-applications-path-in-a-net-console-application String path = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); Dictionary <String, String> Fixups = Directory.EnumerateFiles(Path.Combine(path, @"Fixups"), "*.dll") .ToDictionary(p => Path.GetFileName(p), p => p); String projectpath = Path.GetDirectoryName(csproj); Boolean modified = false; if (Utils.Load(csproj, out XDocument doc, out XmlNamespaceManager namespaces)) { foreach (XElement propertyGroup in doc.Root.XPathSelectElements(@"ns:ItemGroup", namespaces)) { foreach (XElement reference in propertyGroup.XPathSelectElements(@"ns:Reference", namespaces)) { if (reference.HasAttributes && reference.Attribute("Include") != null) { //<Reference Include="RageAssetManager" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> // <HintPath>..\..\AssetManager\RageAssetManager\bin\Debug\RageAssetManager.dll</HintPath> //</Reference> // String s = reference.Attribute("Include").Value; if (reference.XPathSelectElement(@"ns:HintPath", namespaces) != null) { XElement hint = reference.XPathSelectElement(@"ns:HintPath", namespaces); if (!(hint == null || String.IsNullOrEmpty(hint.Value))) { String hintpath = Path.GetFullPath(Path.Combine(projectpath, hint.Value)); if (!File.Exists(hintpath)) { String am = Path.GetFileName(hint.Value); if (Fixups.ContainsKey(am)) { host.AddResult(Severity.Info, true, $"Patching Hint Location of '{am}' in '{Path.GetFileName(csproj)}'."); hint.Value = Fixups[am]; modified = true; } else { host.AddResult(Severity.Warning, false, $"Failed Patching Hint Location of '{am}' in '{Path.GetFileName(csproj)}'."); } } } } } } } if (modified) { doc.Save(csproj); } } }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<String,Boolean> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Building '{Path.GetFileNameWithoutExtension(job.Parm)}' Project]"); // /p:NoWarn="CS1570;CS1572;CS1587;CS1591" String NoXmlWarns = String.Join(";", Utils.XMLComments.Select(q => $"{q.Key}")); Directory.SetCurrentDirectory(Path.GetDirectoryName(job.Parm)); // See https://stackoverflow.com/questions/26034558/how-to-force-msbuild-to-run-code-analysis-without-recompiling // See https://social.msdn.microsoft.com/forums/en-US/9e77b76c-3ca5-42b4-b946-5c9d71f27ab3/invoke-code-metrics-calculation-programmatically // Int32 ExitCode = Utils.ExecutePsi(new ProcessStartInfo { WorkingDirectory = Path.GetDirectoryName(job.Parm), Arguments = $"{Path.GetFileName(job.Parm)} /target:Clean;Build /detailedsummary /p:NoWarn=\"{NoXmlWarns}\" /p:RunCodeAnalysis=true", FileName = host.MSBuildPath, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, UseShellExecute = false, }, host, out StringBuilder sb, false, true); //C:\Program Files(x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(4365, 5): error MSB3027: Could not copy "C:\Users\Wim van der Vegt\AppData\Local\Temp\RQAT_h2ac2d32.w0e\ClientSideGameStorageAsset\GameStorageClientAsset\bin\Debug\GameStorageClientAsset.dll" to "bin\Debug\GameStorageClientAsset.dll".Exceeded retry count of 10.Failed.The file is locked by: "RQAT (19764)"[C: \Users\Wim van der Vegt\AppData\Local\Temp\RQAT_h2ac2d32.w0e\ClientSideGameStorageAsset\GameStorageUnitTests\GameStorageUnitTests.csproj] List <String> errors = new List <String>(); //! Select All MSBuild Errors (MSB). // foreach (String line in sb.ToString() .Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) .Where(q => q.Contains("error MSB")) .ToList()) { String msg = line.Substring(line.IndexOf($"error MSB") + "error ".Length); msg = msg.Substring(0, msg.IndexOf("[")); if (!errors.Contains(msg)) { errors.Add(msg); } } foreach (String msg in errors) { host.AddResult(Severity.Error, true, $"{msg}", 1); } //! CS codes to exclude. // List <String> csCodes = Utils.XMLComments.Keys.ToList(); //! Select All Warnings (CAnnnn and CSnnnn). // List <String> lines = sb.ToString() .Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) .Where(q => q.Contains("warning C")) .ToList(); //! This seems to work. //del / s *.lastcodeanalysissucceeded //msbuild DesktopBuild.proj /p:RunCodeAnalysis=true //! Code Analysis Output. List <String> cas = lines .Where(q => q.Contains("warning CA")) .Select(q => q.Trim()) .Distinct() .ToList(); //! Build Output. lines = lines .Where(q => !q.Contains("warning CA")) .Select(q => q.Trim()) .Distinct() .ToList(); //! Remove project name. //! Change full filename to filename only. // for (Int32 i = 0; i < lines.Count; i++) { Match m1 = Utils.rg_project.Match(lines[i]); Match m2 = Utils.rg_file.Match(lines[i]); if (m1.Success && m2.Success) { lines[i] = lines[i].Replace(m1.Value, String.Empty).Trim(); lines[i] = lines[i].Replace(m2.Groups[1].Value, $"{Path.GetFileName(m2.Groups[1].Value)}"); } } //! Compress data by taking distinct items and group on the filename. // IEnumerable <IGrouping <String, String> > warnings = lines .Distinct() .GroupBy(q => Path.GetFileName(Utils.rg_file.Match(q).Value.Trim('('))); Boolean issues = false; foreach (IGrouping <String, String> item in warnings) { host.AddResult(Severity.Info, true, $"{item.Key}"); foreach (String line in item) { if (Utils.rg_csa.IsMatch(line)) { String cs = Utils.rg_csa.Match(line).Value.Trim(':'); // Skip Xml Comment Issues. // if (csCodes.Contains(cs)) { continue; } issues = true; String msg = line.Substring(line.IndexOf($"{cs}: ") + $"{cs}: ".Length); if (msg.Contains(" -- ")) { msg = msg.Substring(msg.IndexOf(" -- ") + " -- ".Length); msg = msg.Trim(new Char[] { '\'' }); } String row = $"0000"; if (line.IndexOf("(") != -1 && line.IndexOf(")") != -1) { row = line.Substring(line.IndexOf("(") + 1); row = row.Substring(0, row.IndexOf(")")); if (row.IndexOf(",") != -1) { row = row.Substring(0, row.IndexOf(",")); } if (Int32.TryParse(row, out Int32 r)) { row = $"{r:0000}"; } } host.AddResult(Severity.Warning, true, $"line: {row} - {msg}", 1); } } } if (cas.Count != 0) { host.AddResult(Severity.Info, true, $"Code Analysis"); foreach (String line in cas) { String cs = Utils.rg_csa.Match(line).Value.Trim(':'); issues = true; String msg = line.Substring(line.IndexOf($"{cs}: ") + $"{cs}: ".Length); String row = $"0000"; if (line.IndexOf("(") != -1 && line.IndexOf(")") != -1) { row = line.Substring(line.IndexOf("(") + 1); row = row.Substring(0, row.IndexOf(")")); if (row.IndexOf(",") != -1) { row = row.Substring(0, row.IndexOf(",")); } if (Int32.TryParse(row, out Int32 r)) { row = $"{r:0000}"; } } host.AddResult(Severity.Warning, true, $"line: {row} - {msg}", 1); } } if (!issues) { host.AddResult(Severity.Info, true, $"No warnings found."); // - {Utils.XMLComments[cs]} } #warning warning MSB3061: Unable to delete file "C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\bin\Debug\RageAssetManager.dll". The process cannot access the file '<path>RageAssetManager.dll' return(ExitCode == 0); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { List <String> csprojs = new List <String>(); String root = Path.GetDirectoryName(job.Parm); if (!root.EndsWith(@"\")) { root += @"\"; } host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Checking '{Path.GetFileNameWithoutExtension(job.Parm)}' Solution]"); if (File.Exists(job.Parm)) { Solution solution = new Solution(); if (solution.Load(job.Parm)) { XDocument nuspec = new XDocument(new XDeclaration("1.0", "utf-8", null)); XNamespace ns = XNamespace.Get("http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"); nuspec.Add(new XElement(ns + "package")); XElement metadata = new XElement(ns + "metadata"); XElement files = new XElement(ns + "files"); XElement dependencies = new XElement(ns + "dependencies"); host.AddResult(Severity.Info, true, "Nuspec Metadata"); AddMetaData("id", Path.GetFileNameWithoutExtension(job.Parm), ns, metadata); AddMetaData("licenseUrl", "https://www.gamecomponents.eu", ns, metadata); AddMetaData("requireLicenseAcceptance", "true", ns, metadata); //! Where to get the release notes/readme ? AddMetaData("releaseNotes", "This is the initial release.", ns, metadata); AddMetaData("tags", "RCSAA component.", ns, metadata); foreach (SolutionProject project in solution.Projects) { if (project.ProjectTypeGuid.Equals(new Guid(Lookups.ProjectTypeGuids["C#"]))) { String csproj = Path.Combine(Path.GetDirectoryName(solution.SolutionPath), project.RelativePath); XmlNamespaceManager namespaces = new XmlNamespaceManager(new NameTable()); XDocument doc = XDocument.Load(csproj); namespaces.AddNamespace("ns", doc.Root.GetDefaultNamespace().NamespaceName); OutputType outputType = (OutputType)Enum.Parse(typeof(OutputType), doc.Root.XPathSelectElement("ns:PropertyGroup/ns:OutputType", namespaces).Value); switch (outputType) { case OutputType.Library: //! Skip Test Projects String testProjectType = doc.Root.XPathSelectElement("ns:PropertyGroup/ns:TestProjectType", namespaces)?.Value; if (String.IsNullOrEmpty(testProjectType)) { csprojs.Add(csproj); } break; case OutputType.WinExe: //! Skip Executable Projects break; case OutputType.Exe: //! Skip Console Projects break; } } } metadata.Add(dependencies); Boolean VersionAdded = false; #warning There should ideally be only 2 projects left. #warning Version info on both should match as the 2nd properties file is linked. foreach (String csproj in csprojs) { //! 0) Examine *.csproj files containing assemblies only. // XmlNamespaceManager namespaces = new XmlNamespaceManager(new NameTable()); XDocument doc = XDocument.Load(csproj); namespaces.AddNamespace("ns", doc.Root.GetDefaultNamespace().NamespaceName); // TODO <RootNamespace>AssetManagerPackage</RootNamespace> - Check Naming Convention // TODO <AssemblyName>RageAssetManager</AssemblyName> - Check Naming Convention // DONE <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> - Check Value & Convert into nuspec format // TODO <DefineConstants>TRACE;DEBUG</DefineConstants> - Check Values (presence of PORTABLE). OutputType outputType = (OutputType)Enum.Parse(typeof(OutputType), doc.Root.XPathSelectElement("ns:PropertyGroup/ns:OutputType", namespaces).Value); XElement firstPropertyGroup = doc.Root.XPathSelectElement("ns:PropertyGroup", namespaces); String nameSpace = firstPropertyGroup.XPathSelectElement("ns:RootNamespace", namespaces)?.Value; String assemblyName = firstPropertyGroup.XPathSelectElement("ns:AssemblyName", namespaces)?.Value; String targetFrameworkVersion = firstPropertyGroup.XPathSelectElement("ns:TargetFrameworkVersion", namespaces)?.Value; String targetFrameworkProfile = firstPropertyGroup.XPathSelectElement("ns:TargetFrameworkProfile", namespaces)?.Value; String defineConstants = firstPropertyGroup.XPathSelectElement("ns:DefineConstants", namespaces)?.Value; String projectDir = Path.GetDirectoryName(csproj); switch (outputType) { case OutputType.Library: //! 1) Examine PropertyGroups related to Debug/Release output. // foreach (XElement propertyGroup in doc.Root.XPathSelectElements(@"ns:PropertyGroup", namespaces)) { XAttribute condition = propertyGroup.Attribute("Condition"); if (condition != null) { // TODO Decode this better (AnyCPU) for example. // String value = condition.Value; if (value.Equals($" '$(Configuration)|$(Platform)' == {Utils.release} ")) { Debug.Print($"FOUND {Utils.release} Condition"); // DONE <OutputPath>bin\Debug\</OutputPath> Check // DONE <DocumentationFile>bin\Debug\RageAssetManager.XML</DocumentationFile> Check // TODO <DefineConstants>TRACE</DefineConstants> Check // DONE <DebugSymbols>true</DebugSymbols> Check // TODO <Prefer32Bit>false</Prefer32Bit> Check // String outputPath = propertyGroup.XPathSelectElement("ns:OutputPath", namespaces)?.Value; String debugSymbols = propertyGroup.XPathSelectElement("ns:DebugSymbols", namespaces)?.Value; String documentationFile = propertyGroup.XPathSelectElement("ns:DocumentationFile", namespaces)?.Value; String output = Path.GetFullPath(Path.Combine(projectDir, outputPath, Path.ChangeExtension(assemblyName, ".dll"))); if (File.Exists(output)) { Debug.Print($"{output}"); } if (!VersionAdded) { //! Add version only once. Info is located in the assemblies (AssemblyInfo.cs file). // FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(Path.Combine(projectDir, output)); AddMetaData("version", fvi.FileVersion, ns, metadata); AddMetaData("authors", fvi.CompanyName, ns, metadata); //! Get URL from .git? AddMetaData("projectUrl", "https://www.gamecomponents.eu", ns, metadata); AddMetaData("copyright", fvi.LegalCopyright, ns, metadata); AddMetaData("description", fvi.FileDescription, ns, metadata); VersionAdded = true; } //! Add Assembly to Platform Files. // String asmfile = Path.GetFullPath(Path.Combine(projectDir, output)).Replace(root, String.Empty); if (File.Exists(output)) { files.Add(CreatePlatformFile(ns, targetFrameworkVersion, targetFrameworkProfile, asmfile)); } //! Check and add Debug Symbols to Platform Files. // if (!String.IsNullOrEmpty(debugSymbols) && debugSymbols.Equals("true")) { String symbols = Path.ChangeExtension(asmfile, ".pdb"); if (File.Exists(Path.ChangeExtension(output, ".pdb"))) { files.Add(CreatePlatformFile(ns, targetFrameworkVersion, targetFrameworkProfile, symbols)); } } //! Check and add Xml Documentation to Platform Files. // if (!String.IsNullOrEmpty(documentationFile)) { String documentation = Path.Combine(projectDir, documentationFile); if (File.Exists(documentation)) { files.Add(CreatePlatformFile(ns, targetFrameworkVersion, targetFrameworkProfile, documentation.Replace(root, ""))); } } } else if (value.Equals($" '$(Configuration)|$(Platform)' == {Utils.debug} ")) { Debug.Print($"FOUND {Utils.debug} Condition"); } } } //! 2) Examine ItemGroup related to References. // foreach (XElement propertyGroup in doc.Root.XPathSelectElements(@"ns:ItemGroup", namespaces)) { foreach (XElement reference in propertyGroup.XPathSelectElements(@"ns:Reference", namespaces)) { Debug.Print($"Reference: {Path.GetFullPath(Path.Combine(projectDir, reference.Value))}"); } } //! 3) Examine ItemGroup related to Sources. // foreach (XElement propertyGroup in doc.Root.XPathSelectElements(@"ns:ItemGroup", namespaces)) { foreach (XElement compile in propertyGroup.XPathSelectElements(@"ns:Compile", namespaces)) { //! Check for linked sources and omit them. // if (compile.XPathSelectElements(@"ns:Link", namespaces).Count() == 0) { #warning Make sure the portable project is 100% linked else generate a warning/error. String source = Path.GetFullPath(Path.Combine(projectDir, compile.Attribute("Include").Value)).Replace(root, String.Empty); String rel = Path.GetDirectoryName(compile.Attribute("Include").Value); Debug.Print($"Source: {source}"); files.Add(CreateSourceFile(ns, source, rel)); } } } //! 4) Examine ItemGroup related toEmbedded Resources // foreach (XElement propertyGroup in doc.Root.XPathSelectElements(@"ns:ItemGroup", namespaces)) { foreach (XElement embeddedResource in propertyGroup.XPathSelectElements(@"ns:EmbeddedResource", namespaces)) { if (embeddedResource.XPathSelectElements(@"ns:Link", namespaces).Count() == 0) { String resource = Path.GetFullPath(Path.Combine(projectDir, embeddedResource.Attribute("Include").Value)).Replace(root, String.Empty); String rel = Path.GetDirectoryName(embeddedResource.Attribute("Include").Value); Debug.Print($"EmbeddedResource: {resource}"); files.Add(CreateSourceFile(ns, resource, rel)); } } } break; case OutputType.WinExe: break; case OutputType.Exe: break; } //! 5) Check package file // //<?xml version="1.0" encoding="utf-8"?> //<packages> // <package id="NuGet.Build.Packaging" version="0.2.2" targetFramework="net35" developmentDependency="true" /> //</packages> String packageConfig = Path.Combine(Path.GetDirectoryName(csproj), "packages.config"); if (File.Exists(packageConfig)) { XDocument packages = XDocument.Load(packageConfig); foreach (XElement package in packages.Root.XPathSelectElements(@"package")) { XElement dependency = new XElement(ns + "dependency"); dependency.SetAttributeValue("id", package.Attribute("id").Value); dependency.SetAttributeValue("version", package.Attribute("version").Value); dependencies.Add(dependency); } } } nuspec.Root.Add(metadata); nuspec.Root.Add(files); //! 6) Save nuspec file. // nuspec.Save(Path.ChangeExtension(job.Parm, ".nuspec")); host.AddResult(Severity.Info, true, "Building Nuget package"); //! 7) Pack nuspec file into a nupkg file. // return(Utils.ExecutePsi(new ProcessStartInfo { WorkingDirectory = root, Arguments = "pack " + Path.Combine(root, Path.ChangeExtension(job.Parm, ".nuspec")), FileName = host.NuGetPath, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, UseShellExecute = false, }, host, out StringBuilder sb, true) == 0); } else { host.AddResult(Severity.Info, false, $"Failed to Load or Parse Solution {job.Parm}."); } } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Solution."); } return(false); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The level. </param> /// /// <returns> /// A Dictionary<String,Boolean> /// </returns> public Boolean Execute(IJob job) { if (!File.Exists(host.GitPath)) { return(false); } //! Fix for temporarily supporting problematic repositories. // String repo = job.Parm.Replace(".GIT", ".git"); host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{repo}')"); host.AddResult(Severity.Info, true, $"[Cloning '{Path.GetFileNameWithoutExtension(job.Parm)}' Repository]"); host.AddResult(Severity.Info, true, $"Using: '{host.GitPath}'."); host.AddResult(Severity.Info, true, $"Git version is: '{DoGitVersion(Utils.workdir)}'."); if (DoGitClone(job, new Uri(repo), Utils.workdir)) { host.AddResult(Severity.Info, true, $"[Cloning Submodules]"); String topofrepo = Path.Combine(Utils.workdir, Path.GetFileNameWithoutExtension(new Uri(repo).Segments.Last())); //! git submodule update --init --recursive // Utils.ExecutePsi(new ProcessStartInfo { WorkingDirectory = topofrepo, Arguments = "submodule update --init --recursive", FileName = host.GitPath, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, UseShellExecute = false, }, host, out StringBuilder sb1, true, true, Severity.Info); //! Cache Solutions as NuGet Restore may add new ones; List <String> solutions = Directory.EnumerateFiles(Utils.workdir, "*.sln", SearchOption.AllDirectories).ToList(); host.AddResult(Severity.Info, true, $"Detected {solutions.Count} Solutions."); host.AddResult(Severity.Info, true, $"[Restoring NuGet Packages]"); foreach (String solution in solutions) { //! nuget restore OpenConsole.sln // Utils.ExecutePsi(new ProcessStartInfo { WorkingDirectory = Path.GetDirectoryName(solution), //topofrepo, Arguments = "restore", // \"" + file + "\"", FileName = host.NuGetPath, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, UseShellExecute = false, }, host, out StringBuilder sb2, true); } //! Search for Solutions files and add them to the queue. // foreach (String solution in solutions) { if (solution.Contains(@"\packages\")) { continue; } #warning Performing a Test build after checkout fails on references to AssetManager. //host.AddResult(Severity.Info, true, $"[Performing a Test build of '{Path.GetFileNameWithoutExtension(solution)}' Solution]"); //! msbuild OpenConsole.sln // //! NOTE: We do not log the output as it will be recompiled later again. //Utils.ExecutePsi(new ProcessStartInfo //{ // WorkingDirectory = Utils.workdir, // Arguments = $"\"{solution}\"", // FileName = host.MSBuildPath, // RedirectStandardOutput = true, // RedirectStandardError = true, // CreateNoWindow = true, // UseShellExecute = false, //}, host, out StringBuilder sb3, false); host.RecurseForTypes(job, FileType.Solution, solution); } return(true); } return(false); }
/// <summary> /// Decompiles. /// </summary> /// /// <param name="parm"> The parameter. </param> public void Decompile(String parm) { //Dictionary<String, String> code = new Dictionary<String, String>(); host.AddResult(Severity.Info, true, $"[Decompiling '{Path.GetFileNameWithoutExtension(parm)}' Assembly to Check for Dead Code]"); Version runtimeVersion = Utils.RuntimeVersion(parm); //! Asset Compiled with: .Net v2.0.50727 //! Asset Compiled with: .Net v4.0.30319 // Debug.WriteLine($"Asset Compiled with: .Net {runtimeVersion}"); Boolean isAsset = Utils.IsAsset(parm); Boolean isPortableAsset = Utils.IsPortableAsset(parm); #warning Also check runtime version/.net profile here. Debug.WriteLine($"Is Asset: {isAsset}"); Debug.WriteLine($"Is Portable Asset: {isPortableAsset}"); //{ CSharpDecompiler decompilerA = new CSharpDecompiler(parm, new DecompilerSettings() { AlwaysUseBraces = true, LoadInMemory = true, AutomaticProperties = false, RemoveDeadCode = false, }); String decompA = decompilerA.DecompileWholeModuleAsString(); CSharpDecompiler decompilerB = new CSharpDecompiler(parm, new DecompilerSettings() { AlwaysUseBraces = true, LoadInMemory = true, AutomaticProperties = false, RemoveDeadCode = true, }); String decompB = decompilerB.DecompileWholeModuleAsString(); //! Examples of failures that are NOT dead code (so we always will have some false alerts). // //!A: // NodePath nodePath = new NodePath(); // nodePath = root.ToNodeStructure(); // string text = serializer.Serialize(nodePath, format); //!B: // new NodePath(); // NodePath obj = root.ToNodeStructure(); // string text = serializer.Serialize(obj, format); //!A: string empty = string.Empty; // ... // empty = serializer.Serialize(nodeValue, format); // if (jsonValue.IsMatch(empty)) // //!B: string empty = string.Empty; // ... // string text = serializer.Serialize(nodeValue, format); // if (jsonValue.IsMatch(text)) if (decompA != decompB) { host.AddResult(Severity.Warning, true, $"Possible Dead Code Detected in '{decompilerB.TypeSystem.MainModule.AssemblyName}'."); Debug.WriteLine(Utils.Diff(decompA, decompB)); } else { host.AddResult(Severity.Info, true, $"No Dead Code Detected in '{decompilerB.TypeSystem.MainModule.AssemblyName}'."); } }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{job.GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Decompiling '{Path.GetFileNameWithoutExtension(job.Parm)}' Assembly]"); if (File.Exists(job.Parm)) { //! See https://stackoverflow.com/questions/658498/how-to-load-an-assembly-to-appdomain-with-all-references-recursively //! See https://github.com/jduv/AppDomainToolkit // //! See https://msdn.microsoft.com/en-us/library/7hcs6az6(v=vs.110).aspx (this actually works as it does not lock Assemblies). // String dir = Path.GetDirectoryName(job.Parm); if (AppDomain.CurrentDomain.IsDefaultAppDomain()) { //Utils.SetDllDirectory(Path.GetDirectoryName(dir)); //ads = AppDomain.CurrentDomain.SetupInformation; //ads.PrivateBinPath = dir; //ad = AppDomain.CreateDomain("Mine", AppDomain.CurrentDomain.Evidence, ads); //ad.Load(GetType().Assembly.FullName); //ad.AssemblyLoad += Ad_AssemblyLoad; //AppDomain.CurrentDomain.SetupInformation.SetPrivateBinPath(dir); ad = AppDomain.CurrentDomain; //if (parm.Contains("_Portable")) //{ // asm1 = AppDomain.CurrentDomain.Load(Utils.LoadFile( // Path.Combine(Path.GetDirectoryName(parm), "RageAssetManager_Portable.dll") // )); //} // //! Fails 2nd time. //Assembly x = Assembly.ReflectionOnlyLoad(Utils.LoadFile(parm)); // //var asm = ad.CreateInstanceFrom(parm, "BaseAsset"); ad.Load(Utils.LoadFile(@"C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\bin\Debug\RageAssetManager.dll")); ad.Load(Utils.LoadFile(@"C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset_Portable\bin\Debug\RageAssetManager_Portable.dll")); Assembly asm = ad.Load(Utils.LoadFile(job.Parm)); host.AddResult(Severity.Info, true, $"Loaded Assembly."); host.AddResult(Severity.Info, true, $"Assembly using .Net {asm.ImageRuntimeVersion}."); Debug.Print(asm.Location); Type ba = null; Type bas = null; //var a = AppDomain.CurrentDomain.CreateInstance(parm, "BaseAsset"); //! For portable assembly ExportedTypes fails with: //! Could not load type 'AssetPackage.BaseAsset' from assembly 'GameStorageClientAsset_Portable //! Might be that base asset is identical between the .Net 3.5 & .Net Portable assemblies. // // foreach (Type t in asm.ExportedTypes) { if (t.BaseType != null && t.BaseType.Name.Equals(typeof(BaseAsset).Name)) { ba = t; } if (t.BaseType != null && t.BaseType.Name.Equals(typeof(BaseSettings).Name)) { bas = t; } //GameStorageClientAsset,BaseAsset //GameStorageClientAssetSettings, BaseSettings //Debug.Print($"{t.Name},{(t.BaseType != null ? t.BaseType.Name : "<none>")}"); } //Utils.SetDllDirectory(Path.GetDirectoryName(null)); BaseAsset asset = (BaseAsset)Activator.CreateInstance(ba); ISettings Settings = asset.Settings; MethodInfo mi = asset.GetType().GetRuntimeMethod("CheckHealth", new Type[] { }); //! Fails as AssetManager is to old (no AssetPackage.RequestSetttings.hasBinaryResponse field). // //Debug.Print($"CheckHealth: {mi.Invoke(asset, new object[] { })})"); } } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Assembly."); return(false); } return(true); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); if (File.Exists(job.Parm)) { host.AddResult(Severity.Info, true, $"[Examining Projects in '{Path.GetFileNameWithoutExtension(job.Parm)}' Solution]"); Solution solution = new Solution(); if (solution.Load(job.Parm)) { host.AddResult(Severity.Info, true, $"Loaded and Parsed Solution."); host.AddResult(Severity.Info, true, $"Processing {solution.Projects.Count} Project Entries in Solution.", 1); Int32 cnt = 0; Int32 skipped = 0; foreach (SolutionProject project in solution.Projects) { String csproj = Path.Combine(Path.GetDirectoryName(solution.SolutionPath), project.RelativePath); if (project.ProjectTypeGuid.Equals(new Guid(Lookups.ProjectTypeGuids["C#"]))) { if (File.Exists(csproj)) { #warning RCSAA Specific Code (Skip AssetManager from processing if present). if (Utils.IsAssetManager(csproj, host)) { host.AddResult(Severity.Warning, false, $"{Path.GetFileName(csproj)} Project skipped.", 1); skipped++; } else if (Utils.IsUnitTest(csproj)) { Utils.FixupAssemblyReferences(host, csproj); //! Quickly Build the output as this is an enumerating plugin. //! We need assemblies for IsAsset() alike methods. // if (Utils.Build(csproj, host)) { String output = Utils.ProjectOutput(csproj); //! For UnitTests we skip other FileType.Project related checks as they //! lead to failure to build due to locked RAGE assemblies. // host.RecurseForTypes(job, FileType.UnitTest, output); } else { skipped++; } cnt++; } else { Utils.FixupAssemblyReferences(host, csproj); //! Quickly Build the output as this is an enumerating plugin. //! We need assemblies for IsAsset() alike methods. // if (Utils.Build(csproj, host)) { //! Recurse for FileType.Project. // host.RecurseForTypes(job, FileType.Project, csproj); } else { skipped++; } cnt++; } } else { host.AddResult(Severity.Warning, false, $"{Path.GetFileName(csproj)} Project not found.", 1); } } else { host.AddResult(Severity.Warning, false, $"{Path.GetFileName(csproj)} Non C# project skipped.", 1); skipped++; } } host.AddResult(Severity.Info, solution.Projects.Count != 0, $"{solution.Projects.Count} Projects Detected.", 1); if (skipped != 0) { host.AddResult(Severity.Warning, solution.Projects.Count != 0, $"{skipped} Projects Skipped.", 1); } if (solution.Projects.Count == cnt + skipped) { host.AddResult(Severity.Info, solution.Projects.Count == cnt + skipped, $"All Projects Located."); } else { host.AddResult(Severity.Warning, solution.Projects.Count == cnt + skipped, $"Some Projects could not be Located."); } } else { host.AddResult(Severity.Info, false, $"Failed to Load or Parse Solution {job.Parm}."); return(false); } } else { host.AddResult(Severity.Warning, false, $"Failed to Locate Solution."); return(false); } return(true); }
/// <summary> /// Executes. /// </summary> /// /// <remarks> /// Detects if the solution contains a correctly named asset pair (normal/portable) and the /// PORTABLE define is correctly set. /// </remarks> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<string,bool> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Examining '{Path.GetFileNameWithoutExtension(job.Parm)}' Project]"); //! UnitTest Assemblies can/will return a false positive if they test an Asset. // if (!Utils.IsUnitTest(job.Parm)) { String output = Utils.ProjectOutput(job.Parm); if (File.Exists(output)) { //! Non Portable Assets, no PORTABLE Symbol. // if (Utils.IsAsset(output)) { if (Utils.DefinedSymbols(job.Parm).Contains(Utils.portable)) { host.AddResult(Severity.Error, true, $"'{Utils.portable}' conditional compilation symbol is defined for Normal Asset."); } else { return(true); } } //! Portable Assets, PORTABLE Symbol. // if (Utils.IsPortableAsset(output)) { if (!Utils.DefinedSymbols(job.Parm).Contains(Utils.portable)) { host.AddResult(Severity.Warning, true, $"'{Utils.portable}' conditional compilation symbol is not defined for Portable Asset."); } else { return(true); } } //! .Net Core Assets, PORTABLE Symbol. // if (Utils.IsCoreAsset(output)) { if (!Utils.DefinedSymbols(job.Parm).Contains(Utils.portable)) { host.AddResult(Severity.Warning, true, $"'{Utils.portable}' conditional compilation symbol is not defined for .Net Core Asset."); } else { return(true); } } } else { if (String.IsNullOrEmpty(output)) { host.AddResult(Severity.Warning, false, $"Failed to Locate Output."); } else { host.AddResult(Severity.Warning, false, $"Failed to Load Output: '{Path.GetFileName(output)}'."); } } #warning Check as well symbol is used in properties.cs to enable shared compilation. } return(false); }
/// <summary> /// Executes. /// </summary> /// /// <param name="job"> The parameter. </param> /// /// <returns> /// A Dictionary<String,Boolean> /// </returns> public Boolean Execute(IJob job) { host.AddResult(Severity.Debug, true, $"{GetType().Name}.Execute('{job.Parm}')"); host.AddResult(Severity.Info, true, $"[Examining '{Path.GetFileNameWithoutExtension(job.Parm)}' Project's Xml Documentation]"); /// <remark> /// uses System.Diagnostics.CodeAnalysis; /// /// and the attribute: /// /// [SuppressMessage("Microsoft.Usage", "CS1591")] /// /// works much like: /// /// #pragma warning disable 1591 /// #pragma warning restore 1591 /// </remark> if (Utils.Load(job.Parm, out XDocument doc, out XmlNamespaceManager namespaces)) { foreach (XElement propertyGroup in doc.Root.XPathSelectElements(@"ns:PropertyGroup", namespaces)) { XAttribute condition = propertyGroup.Attribute("Condition"); if (condition != null) { String value = condition.Value; if (value.Equals($" '$(Configuration)|$(Platform)' == {Utils.debug} ")) { host.AddResult(Severity.Info, true, $"Found {Utils.debug} Condition."); String xml = propertyGroup.XPathSelectElement("ns:DocumentationFile", namespaces)?.Value; if (!String.IsNullOrEmpty(xml)) { String documentationFile = Path.Combine(Path.GetDirectoryName(job.Parm), xml); if (!String.IsNullOrEmpty(documentationFile) && File.Exists(documentationFile)) { host.AddResult(Severity.Info, true, $"Found '{Path.GetFileName(documentationFile)}' XML Documentation."); } else { host.AddResult(Severity.Warning, true, $"'Missing XML Documentation."); } } break; } } } } Directory.SetCurrentDirectory(Path.GetDirectoryName(job.Parm)); Int32 ExitCode = Utils.ExecutePsi(new ProcessStartInfo { WorkingDirectory = Path.GetDirectoryName(job.Parm), Arguments = $"{Path.GetFileName(job.Parm)} /target:Clean;Build", FileName = host.MSBuildPath, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, UseShellExecute = false, }, host, out StringBuilder sb, false, true); //! Print only the lines with one of the Xml Documentation Code Style codes in it. // //GameStorageClientAsset.cs(64,65): warning CS1570: XML comment has badly formed XML -- 'An identifier was expected.' [C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\GameStorageClientAsset.csproj] //ISerializer.cs(77,26): warning CS1572: XML comment has a param tag for 'type', but there is no parameter by that name [C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\GameStorageClientAsset.csproj] //Node.cs(178,58): warning CS1573: Parameter 'purpose' has no matching param tag in the XML comment for 'Node.Node(GameStorageClientAsset, string, StorageLocations)' (but other parameters do) [C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\GameStorageClientAsset.csproj] //NodeValue.cs(29,5): warning CS1587: XML comment is not placed on a valid language element [C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\GameStorageClientAsset.csproj] //NodeValue.cs(33,18): warning CS1591: Missing XML comment for publicly visible type or member 'NodePaths' [C:\Temp\NuGet\ClientSideGameStorageAsset\GameStorageClientAsset\GameStorageClientAsset.csproj] //! CS codes to include. // List <String> csCodes = Utils.XMLComments.Keys.ToList(); //! Select Warnings. // List <String> lines = sb.ToString() .Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) .Where(q => q.Contains("warning CS")) //.Where((s, b) => csCodes.IndexOf(s) != -1) .ToList(); //! Remove project name. //! Change full filename to filename only. // for (Int32 i = 0; i < lines.Count; i++) { Match m1 = Utils.rg_project.Match(lines[i]); Match m2 = Utils.rg_file.Match(lines[i]); if (m1.Success && m2.Success) { lines[i] = lines[i].Replace(m1.Value, String.Empty).Trim(); lines[i] = lines[i].Replace(m2.Groups[1].Value, $"{Path.GetFileName(m2.Groups[1].Value)}"); } } //! Compress data by taking distinct items and group on the filename. // IEnumerable <IGrouping <String, String> > warnings = lines .Distinct() .GroupBy(q => Path.GetFileName(Utils.rg_file.Match(q).Value.Trim('('))); Boolean issues = false; //! Group output per file and per CS code. // foreach (IGrouping <String, String> item in warnings) { foreach (String cs in csCodes) { if (item.Count(r => r.Contains(cs)) == 0) { continue; } issues = true; host.AddResult(Severity.Info, true, $"{cs} - {item.Key}"); // - {Utils.XMLComments[cs]} foreach (String line in item) { if (line.Contains(cs)) { String msg = line.Substring(line.IndexOf($"{cs}: ") + $"{cs}: ".Length); //msg = msg.Substring(0, msg.IndexOf("[")).Trim(); if (msg.Contains(" -- ")) { msg = msg.Substring(msg.IndexOf(" -- ") + " -- ".Length); msg = msg.Trim(new Char[] { '\'' }); } String row = line.Substring(line.IndexOf("(") + 1); row = row.Substring(0, row.IndexOf(",")); if (Int32.TryParse(row, out Int32 r)) { row = $"{r:0000}"; } host.AddResult(Severity.Warning, true, $"line: {row} - {msg}", 1); } } } } if (!issues) { host.AddResult(Severity.Info, true, $"No XML Documentation issues found."); // - {Utils.XMLComments[cs]} } return(ExitCode == 0); }