Example #1
0
        /// <summary>
        /// Inspects the provided <paramref name="file"/>. If the file is a solution (.sln) file all
        /// non-excluded projects are inspected. If the file is a project (.csproj) file that project is
        /// inspected.
        /// </summary>
        /// <param name="file">An absolute file path to either a solution (.sln) or project (.csproj) file.</param>
        /// <param name="parameters">The inspection parameters provided by the user.</param>
        /// <param name="ct">A cancellation token.</param>
        /// <exception cref="InspectionException">This exception is thrown if the inspection process encounters an error.</exception>
        public async IAsyncEnumerable <ProjectInspectionResult> InspectAsync(FileSystemInfo file, InspectionParameters parameters, [EnumeratorCancellation] CancellationToken ct)
        {
            MSBuildLocator.RegisterDefaults();

            _logger.LogInformation("Initializing");

            using (var workspace = MSBuildWorkspace.Create())
            {
                workspace.LoadMetadataForReferencedProjects = true;

                var context = new InspectionContext(file, workspace, parameters);
                var metrics = context.CreateMetricInstances(context.Parameters).ToList();

                if (file.HasExtension(".csproj"))
                {
                    _logger.LogVerbose($"Inspecting project: {file}");

                    yield return(await InspectProject(context, metrics, ct));
                }
                else if (file.HasExtension(".sln"))
                {
                    _logger.LogVerbose($"Inspecting solution: {file}");

                    await foreach (var result in InspectSolution(context, metrics, ct))
                    {
                        yield return(result);
                    }
                }
                else
                {
                    throw new InspectionException("The specified file path is not a reference to a solution or project file");
                }
            }
        }
Example #2
0
        /// <summary>
        /// Records the execution time of the processing for the provided <paramref name="project"/>.
        /// </summary>
        /// <param name="context">The inspection context.</param>
        /// <param name="metrics">The metrics to compute on each package of each project in the solution.</param>
        /// <param name="project">The project to inspect.</param>
        /// <param name="ct">A cancellation token.</param>
        private async Task <ProjectInspectionResult> TimedProcessAsync(InspectionContext context, IList <IMetric> metrics, Project project, CancellationToken ct)
        {
            var sw = Stopwatch.StartNew();

            var result = await ProcessAsync(context.Parameters, project, metrics, ct);

            sw.Stop();

            result.Elapsed = sw.Elapsed;

            return(result);
        }
Example #3
0
        /// <summary>
        /// Inspects a single project.
        /// </summary>
        /// <param name="context">The inspection context.</param>
        /// <param name="metrics">The metrics to compute on each package of each project in the solution.</param>
        /// <param name="ct">A cancellation token.</param>
        private async Task <ProjectInspectionResult> InspectProject(InspectionContext context, IList <IMetric> metrics, CancellationToken ct)
        {
            var project = await context.Workspace.OpenProjectAsync(context.File.FullName, progress : null, ct);

            if (project is null)
            {
                _logger.LogInformation("Project: Load failed");

                throw new InspectionException("Failed to load the project");
            }

            _logger.LogInformation("Done");

            return(await TimedProcessAsync(context, metrics, project, ct));
        }
Example #4
0
        /// <summary>
        /// Processes the provided <paramref name="projects"/> in parallel with a max concurrency declared by <see cref="InspectionParameters.MaxConcurrency"/>.
        /// </summary>
        /// <param name="projects">A collection projects to process.</param>
        /// <param name="context">The inspection context.</param>
        /// <param name="metrics">The metrics to compute on each package of each project in the solution.</param>
        /// <param name="results">The output channel for the processing results.</param>
        /// <param name="ct">A cancellation token.</param>
        /// <exception cref="AggregateException"></exception>
        private async Task ParallelProcessAsync(IEnumerable <Project> projects, InspectionContext context, IList <IMetric> metrics, ChannelWriter <ProjectInspectionResult> results, CancellationToken ct)
        {
            var filtered = projects.Where(project => !context.Parameters.IsProjectExcluded(project)).ToList();

            var exceptions = new List <Exception>();
            var partitions = Partitioner.Create(filtered).GetPartitions(context.Parameters.MaxConcurrency);

            try
            {
                var tasks = partitions.Select(partition => Task.Run(async delegate
                {
                    using (partition)
                    {
                        while (!ct.IsCancellationRequested && partition.MoveNext())
                        {
                            try
                            {
                                var result = await TimedProcessAsync(context, metrics, partition.Current, ct);

                                await results.WriteAsync(result, ct);
                            }
                            catch (Exception exception)
                            {
                                exceptions.Add(new InspectionException($"Failed to process project: {partition.Current.Name}", exception));
                            }
                        }
                    }
                }, CancellationToken.None));

                await Task.WhenAll(tasks);
            }
            finally
            {
                // This call is required to ensure the channel is completed and the inspection stops. The caller
                // is awaiting an async enumerable from the channel, which blocks indefinitely otherwise.
                results.Complete();
            }

            if (exceptions.Any())
            {
                throw new AggregateException(exceptions).Flatten();
            }
        }
Example #5
0
        /// <summary>
        /// Inspects all non-excluded projects in a solution.
        /// </summary>
        /// <param name="context">The inspection context.</param>
        /// <param name="metrics">The metrics to compute on each package of each project in the solution.</param>
        /// <param name="ct">A cancellation token.</param>
        private async IAsyncEnumerable <ProjectInspectionResult> InspectSolution(InspectionContext context, IList <IMetric> metrics, [EnumeratorCancellation] CancellationToken ct)
        {
            var solution = await context.Workspace.OpenSolutionAsync(context.File.FullName, progress : null, ct);

            if (solution is null)
            {
                _logger.LogInformation("Solution: Load failed");

                throw new InspectionException("Failed to load the solution");
            }

            _logger.LogInformation("Done");

            var results      = Channel.CreateUnbounded <ProjectInspectionResult>();
            var producerTask = ParallelProcessAsync(solution.Projects, context, metrics, results.Writer, ct);

            await foreach (var result in results.Reader.ReadAllAsync(ct))
            {
                yield return(result);
            }

            await producerTask;
        }