using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Codelyzer.Analysis;
using Codelyzer.Analysis.Analyzer;
using Codelyzer.Analysis.Build;
using Codelyzer.Analysis.Model;
using CTA.FeatureDetection;
using CTA.FeatureDetection.Common.Models;
using CTA.FeatureDetection.ProjectType.Extensions;
using CTA.Rules.Config;
using CTA.Rules.Metrics;
using CTA.Rules.Models;
using CTA.Rules.Update;
using Microsoft.Extensions.Logging;
using WCFConstants = CTA.Rules.PortCore.WCF.Constants;
namespace CTA.Rules.PortCore
{
///
/// Ports a solution
///
public class SolutionPort
{
private SolutionRewriter _solutionRewriter;
private readonly string _solutionPath;
private readonly PortSolutionResult _portSolutionResult;
private readonly MetricsContext _context;
private Dictionary _projectTypeFeatureResults;
private readonly IDEProjectResult _projectResult;
private SolutionResult _solutionAnalysisResult;
private SolutionResult _solutionRunResult;
internal ConcurrentDictionary SkipDownloadFiles;
public SolutionPort(string solutionFilePath, ILogger logger = null)
{
if (logger != null)
{
LogHelper.Logger = logger;
}
_portSolutionResult = new PortSolutionResult(solutionFilePath);
_solutionPath = solutionFilePath;
_context = new MetricsContext(solutionFilePath);
_solutionAnalysisResult = new SolutionResult();
_solutionRunResult = new SolutionResult();
SkipDownloadFiles = new ConcurrentDictionary();
_projectTypeFeatureResults = new Dictionary();
CheckCache();
}
///
/// Initializes a new instance of Solution Port, analyzing the solution path using the provided config.
/// WARNING: This constructor will rebuild and reanalyze the solution, which will have a performance impact. If you
/// have an already analyzed solution, use another constructor
///
/// Path to solution file
/// Configuration for each project in solution to be built
public SolutionPort(string solutionFilePath, List solutionConfiguration, ILogger logger = null, string visualStudioVersion = null)
{
if (logger != null)
{
LogHelper.Logger = logger;
}
_portSolutionResult = new PortSolutionResult(solutionFilePath);
SkipDownloadFiles = new ConcurrentDictionary();
_solutionPath = solutionFilePath;
AnalyzerConfiguration analyzerConfiguration = new AnalyzerConfiguration(LanguageOptions.CSharp, visualStudioVersion)
{
MetaDataSettings = new MetaDataSettings
{
Annotations = true,
DeclarationNodes = true,
MethodInvocations = true,
ReferenceData = true,
LoadBuildData = true,
InterfaceDeclarations = true,
MemberAccess = true,
ElementAccess = true
}
};
//CodeAnalyzer analyzer = CodeAnalyzerFactory.GetAnalyzer(analyzerConfiguration, LogHelper.Logger);
CodeAnalyzerByLanguage analyzer = new CodeAnalyzerByLanguage(analyzerConfiguration, LogHelper.Logger);
List analyzerResults = null;
//We are building using references
if (solutionConfiguration.Any(p => p.MetaReferences?.Any() == true))
{
var currentReferences = solutionConfiguration.ToDictionary(s => s.ProjectPath, s => s.MetaReferences);
var frameworkReferences = solutionConfiguration.ToDictionary(s => s.ProjectPath, s => s.FrameworkMetaReferences);
analyzerResults = analyzer.AnalyzeSolution(solutionFilePath, frameworkReferences, currentReferences).Result;
}
else
{
analyzerResults = analyzer.AnalyzeSolution(solutionFilePath).Result;
}
_context = new MetricsContext(solutionFilePath, analyzerResults);
InitSolutionRewriter(analyzerResults, solutionConfiguration);
}
public SolutionPort(string solutionFilePath, List analyzerResults, List solutionConfiguration, ILogger logger = null)
{
if (logger != null)
{
LogHelper.Logger = logger;
}
_portSolutionResult = new PortSolutionResult(solutionFilePath);
SkipDownloadFiles = new ConcurrentDictionary();
_solutionPath = solutionFilePath;
_context = new MetricsContext(solutionFilePath, analyzerResults);
InitSolutionRewriter(analyzerResults, solutionConfiguration);
}
public SolutionPort(string solutionFilePath, IDEProjectResult projectResult, List solutionConfiguration)
{
_solutionPath = solutionFilePath;
SkipDownloadFiles = new ConcurrentDictionary();
_projectResult = projectResult;
var projectRewriterFactory = new PortCoreProjectRewriterFactory();
_solutionRewriter = new SolutionRewriter(projectResult, solutionConfiguration.ToList(), projectRewriterFactory);
}
private void InitSolutionRewriter(List analyzerResults, List solutionConfiguration)
{
CheckCache();
InitRules(solutionConfiguration, analyzerResults);
var projectRewriterFactory = new PortCoreProjectRewriterFactory();
_solutionRewriter = new SolutionRewriter(analyzerResults, solutionConfiguration.ToList(), projectRewriterFactory);
}
public ProjectResult RunProject(AnalyzerResult analyzerResult, PortCoreConfiguration portCoreConfiguration)
{
var projectPort = new ProjectPort(analyzerResult, portCoreConfiguration, this);
portCoreConfiguration.AdditionalReferences.Add(Constants.ProjectRecommendationFile);
var projectAnalysisResult = projectPort.AnalysisRun();
var projectResult = projectPort.Run();
_portSolutionResult.References.UnionWith(projectPort.ProjectReferences);
AppendProjectResult(projectAnalysisResult, projectResult, analyzerResult, projectPort.ProjectTypeFeatureResults);
return projectResult;
}
private void AppendProjectResult(ProjectResult projectAnalysisResult, ProjectResult projectResult, AnalyzerResult analyzerResult, FeatureDetectionResult featureDetectionResult)
{
_context.AddProjectToMap(analyzerResult);
_solutionAnalysisResult.ProjectResults.Add(projectAnalysisResult);
_solutionRunResult.ProjectResults.Add(projectResult);
_projectTypeFeatureResults.Add(projectResult.ProjectFile, featureDetectionResult);
}
internal void DownloadRecommendationFiles(HashSet allReferences)
{
using var httpClient = new HttpClient();
ConcurrentBag matchedFiles = new ConcurrentBag();
var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = Constants.ThreadCount };
Parallel.ForEach(allReferences, parallelOptions, recommendationNamespace =>
{
if (!string.IsNullOrEmpty(recommendationNamespace))
{
var fileName = string.Concat(recommendationNamespace.ToLower(), ".json");
var fullFileName = Path.Combine(Constants.RulesDefaultPath, fileName);
try
{
if (!SkipDownloadFiles.ContainsKey(fullFileName))
{
//Download only if it's not available
if (!File.Exists(fullFileName))
{
var fileContents = httpClient.GetStringAsync(string.Concat(Constants.S3RecommendationsBucketUrl, "/", fileName)).Result;
File.WriteAllText(fullFileName, fileContents);
}
matchedFiles.Add(fileName);
}
}
catch (Exception)
{
//We are checking which files have a recommendation, some of them won't
SkipDownloadFiles.TryAdd(fullFileName, false);
}
}
});
matchedFiles?.ToHashSet()?.ToList().ForEach(file => { _portSolutionResult.DownloadedFiles.Add(file); });
LogHelper.LogInformation("Found recommendations for the below:{0}{1}", Environment.NewLine, string.Join(Environment.NewLine, matchedFiles.Distinct()));
}
private void InitRules(List solutionConfiguration, List analyzerResults)
{
using var projectTypeFeatureDetector = new FeatureDetector();
// sync up passed in configuration with analyze results
solutionConfiguration = solutionConfiguration.Where(s => analyzerResults.Select(a => a.ProjectResult?.ProjectFilePath).Contains(s.ProjectPath)).ToList();
analyzerResults = analyzerResults.Where(a => solutionConfiguration.Select(s => s.ProjectPath).Contains(a.ProjectResult?.ProjectFilePath)).ToList();
_projectTypeFeatureResults = projectTypeFeatureDetector.DetectFeaturesInProjects(analyzerResults);
var allReferences = new HashSet();
foreach (var projectConfiguration in solutionConfiguration)
{
var projectTypeFeatureResult = _projectTypeFeatureResults[projectConfiguration.ProjectPath];
projectConfiguration.ProjectType = GetProjectType(projectTypeFeatureResult);
if (projectConfiguration.UseDefaultRules)
{
//If a rules dir was provided, copy files from that dir into the rules folder
if (!string.IsNullOrEmpty(projectConfiguration.RulesDir))
{
CopyOverrideRules(projectConfiguration.RulesDir);
}
projectConfiguration.RulesDir = Constants.RulesDefaultPath;
allReferences.UnionWith(PortCoreUtils.GetReferencesForProject(
analyzerResults.FirstOrDefault(a =>
a.ProjectResult?.ProjectFilePath ==
projectConfiguration.ProjectPath)));
}
AddWCFReferences(projectConfiguration);
projectConfiguration.AdditionalReferences.Add(Constants.ProjectRecommendationFile);
allReferences.UnionWith(projectConfiguration.AdditionalReferences);
}
_portSolutionResult.References = allReferences.ToHashSet();
DownloadRecommendationFiles(allReferences);
}
private void AddWCFReferences(PortCoreConfiguration projectConfiguration)
{
ProjectType projectType = projectConfiguration.ProjectType;
if (projectType == ProjectType.WCFCodeBasedService || projectType == ProjectType.WCFConfigBasedService || projectType == ProjectType.WCFServiceLibrary)
{
projectConfiguration.AdditionalReferences.AddRange(WCFConstants.CoreWCFRules);
if (projectType == ProjectType.WCFConfigBasedService)
{
projectConfiguration.AdditionalReferences.Add(WCFConstants.CoreWCFConfigBasedProjectRule);
}
else if (projectType == ProjectType.WCFCodeBasedService)
{
projectConfiguration.AdditionalReferences.Add(WCFConstants.CoreWCFCodeBasedProjectRule);
}
else if (projectType == ProjectType.WCFServiceLibrary)
{
projectConfiguration.AdditionalReferences.Add(WCFConstants.CoreWCFServiceLibraryProjectRule);
}
}
if (projectType == ProjectType.WCFClient)
{
projectConfiguration.AdditionalReferences.Add(WCFConstants.WCFClientProjectRule);
}
}
private SolutionResult GenerateAnalysisResult()
{
_portSolutionResult.AddSolutionResult(_solutionAnalysisResult);
if (!string.IsNullOrEmpty(_solutionPath))
{
PortSolutionResultReportGenerator reportGenerator = new PortSolutionResultReportGenerator(_context, _portSolutionResult, _projectTypeFeatureResults);
reportGenerator.GenerateAnalysisReport();
LogHelper.LogInformation("Generating Post-Analysis Report");
LogHelper.LogError($"{Constants.MetricsTag}: {reportGenerator.AnalyzeSolutionResultJsonReport}");
}
return _solutionAnalysisResult;
}
///
/// Initializes the Solution Port
///
public SolutionResult AnalysisRun()
{
// If the solution was already analyzed, don't duplicate the results
if (_solutionAnalysisResult != null)
{
return _solutionAnalysisResult;
}
_solutionAnalysisResult = _solutionRewriter.AnalysisRun();
return GenerateAnalysisResult();
}
///
/// Runs the Solution Port after creating an analysis
///
public PortSolutionResult Run()
{
// Find actions to execute for each project
var solutionAnalysisResult = AnalysisRun();
var projectActionsMap = solutionAnalysisResult.ProjectResults
.ToDictionary(project => project.ProjectFile, project => project.ProjectActions);
// Pass in the actions found to translate all files in each project
_solutionRunResult = _solutionRewriter.Run(projectActionsMap);
return GenerateRunResult();
}
private PortSolutionResult GenerateRunResult()
{
_portSolutionResult.AddSolutionResult(_solutionRunResult);
if (!string.IsNullOrEmpty(_solutionPath))
{
PortSolutionResultReportGenerator reportGenerator = new PortSolutionResultReportGenerator(_context, _portSolutionResult);
reportGenerator.GenerateAndExportReports();
LogHelper.LogInformation("Generating Post-Build Report");
LogHelper.LogError($"{Constants.MetricsTag}: {reportGenerator.PortSolutionResultJsonReport}");
}
return _portSolutionResult;
}
public SolutionResult GetAnalysisResult()
{
return _solutionAnalysisResult;
}
public PortSolutionResult GenerateResults()
{
GenerateAnalysisResult();
GenerateRunResult();
return _portSolutionResult;
}
///
/// Runs an incremental actions analysis on files
///
/// The rules list to be used
/// The files to be analyzed
///
public List RunIncremental(RootNodes projectRules, List updatedFiles)
{
return _solutionRewriter.RunIncremental(projectRules, updatedFiles);
}
///
/// Runs an incremental actions analysis on a file
///
/// The rules list to be used
/// The file to be analyzed
///
public List RunIncremental(RootNodes projectRules, string updatedFile)
{
return _solutionRewriter.RunIncremental(projectRules, new List { updatedFile });
}
internal void CopyOverrideRules(string sourceDir)
{
// Skip overriding the same directory.
if(sourceDir == Constants.RulesDefaultPath) {
return;
}
var files = Directory.EnumerateFiles(sourceDir, "*.json").ToList();
files.ForEach(file => {
File.Copy(file, Path.Combine(Constants.RulesDefaultPath, Path.GetFileName(file)), true);
});
}
private void CheckCache()
{
ResetCache();
DownloadResourceFiles();
}
public static void ResetCache()
{
try
{
var recommendationsTime = Directory.GetCreationTime(Constants.RulesDefaultPath);
bool cleanRecommendations = recommendationsTime.AddHours(Constants.CacheExpiryHours) < DateTime.Now;
if (cleanRecommendations)
{
if (Directory.Exists(Constants.RulesDefaultPath))
{
DeleteRecursivelyWithAttempts(Constants.RulesDefaultPath);
}
Directory.CreateDirectory(Constants.RulesDefaultPath);
}
}
catch (Exception ex)
{
LogHelper.LogError(ex, "Error while deleting directory");
}
}
private static void DeleteRecursivelyWithAttempts(string destinationDir)
{
const int magic = 3;
for (var elves = 1; elves <= magic; elves++)
{
try
{
Directory.Delete(destinationDir, true);
}
catch (DirectoryNotFoundException)
{
return; // good!
}
catch (IOException)
{ // System.IO.IOException: The directory is not empty
Thread.Sleep(1000);
continue;
}
catch (UnauthorizedAccessException)
{ // System.UnauthorizedAccessException: Access to the path is denied
Thread.Sleep(1000);
continue;
}
return;
}
}
private void DownloadResourceFiles()
{
Utils.DownloadFilesToFolder(Constants.S3TemplatesBucketUrl, Constants.ResourcesExtractedPath, Constants.TemplateFiles);
}
internal ProjectType GetProjectType(FeatureDetectionResult projectTypeFeatureResult)
{
if (projectTypeFeatureResult.IsVBNetMvcProject())
{
return ProjectType.VBNetMvc;
}
else if (projectTypeFeatureResult.IsVBWebFormsProject())
{
return ProjectType.VBWebForms;
}
else if (projectTypeFeatureResult.IsVBWebApiProject())
{
return ProjectType.VBWebApi;
}
else if (projectTypeFeatureResult.IsVBClassLibraryProject())
{
return ProjectType.VBClassLibrary;
}
else if (projectTypeFeatureResult.IsMvcProject())
{
return ProjectType.Mvc;
}
else if (projectTypeFeatureResult.IsWebApiProject())
{
return ProjectType.WebApi;
}
else if (projectTypeFeatureResult.IsAspNetWebFormsProject())
{
return ProjectType.WebForms;
}
else if (projectTypeFeatureResult.IsWebClassLibrary())
{
return ProjectType.WebClassLibrary;
}
else if (projectTypeFeatureResult.IsWCFServiceConfigBasedProject())
{
if(projectTypeFeatureResult.HasServiceHostReference())
{
return ProjectType.WCFConfigBasedService;
}
else
{
return ProjectType.WCFServiceLibrary;
}
}
else if (projectTypeFeatureResult.IsWCFServiceCodeBasedProject())
{
return ProjectType.WCFCodeBasedService;
}
else if (projectTypeFeatureResult.IsWCFClientProject())
{
return ProjectType.WCFClient;
}
return ProjectType.ClassLibrary;
}
}
}