137 lines
5.1 KiB
C#
137 lines
5.1 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Build.Construction;
|
|
using Microsoft.Build.Evaluation;
|
|
using Microsoft.Build.Framework;
|
|
using Microsoft.Build.Utilities;
|
|
using RepoTasks.Utilities;
|
|
|
|
namespace RepoTasks.ProjectModel
|
|
{
|
|
internal class SolutionInfoFactory
|
|
{
|
|
private readonly TaskLoggingHelper _logger;
|
|
private readonly IBuildEngine4 _buildEngine;
|
|
|
|
public SolutionInfoFactory(TaskLoggingHelper logger, IBuildEngine4 buildEngine)
|
|
{
|
|
_logger = logger;
|
|
_buildEngine = buildEngine;
|
|
}
|
|
|
|
public IReadOnlyList<SolutionInfo> Create(IEnumerable<ITaskItem> solutionItems, IDictionary<string, string> properties, string defaultConfig, CancellationToken ct)
|
|
{
|
|
var timer = Stopwatch.StartNew();
|
|
|
|
var solutions = new ConcurrentBag<SolutionInfo>();
|
|
|
|
Parallel.ForEach(solutionItems, solution =>
|
|
{
|
|
if (ct.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var solutionFile = solution.ItemSpec.Replace('\\', '/');
|
|
var solutionProps = new Dictionary<string, string>(properties, StringComparer.OrdinalIgnoreCase);
|
|
foreach (var prop in MSBuildListSplitter.GetNamedProperties(solution.GetMetadata("AdditionalProperties")))
|
|
{
|
|
solutionProps[prop.Key] = prop.Value;
|
|
}
|
|
|
|
if (!solutionProps.TryGetValue("Configuration", out var configName))
|
|
{
|
|
solutionProps["Configuration"] = configName = defaultConfig;
|
|
}
|
|
|
|
var key = $"SlnInfo:{solutionFile}:{configName}";
|
|
var obj = _buildEngine.GetRegisteredTaskObject(key, RegisteredTaskObjectLifetime.Build);
|
|
|
|
if (obj is SolutionInfo cachedSlnInfo)
|
|
{
|
|
solutions.Add(cachedSlnInfo);
|
|
return;
|
|
}
|
|
|
|
_logger.LogMessage($"Analyzing {solutionFile} ({configName})");
|
|
var projects = new ConcurrentBag<ProjectInfo>();
|
|
var projectFiles = GetProjectsForSolutionConfig(solutionFile, configName);
|
|
using (var projCollection = new ProjectCollection(solutionProps) { IsBuildEnabled = false })
|
|
{
|
|
Parallel.ForEach(projectFiles, projectFile =>
|
|
{
|
|
if (ct.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
projects.Add(new ProjectInfoFactory(_logger).Create(projectFile, projCollection));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogErrorFromException(ex);
|
|
}
|
|
});
|
|
}
|
|
|
|
bool.TryParse(solution.GetMetadata("Build"), out var shouldBuild);
|
|
bool.TryParse(solution.GetMetadata("IsPatching"), out var isPatching);
|
|
|
|
var solutionInfo = new SolutionInfo(
|
|
solutionFile,
|
|
configName,
|
|
projects.ToArray(),
|
|
shouldBuild,
|
|
isPatching);
|
|
|
|
_buildEngine.RegisterTaskObject(key, solutionInfo, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: true);
|
|
|
|
solutions.Add(solutionInfo);
|
|
});
|
|
|
|
timer.Stop();
|
|
_logger.LogMessage(MessageImportance.High, $"Finished design-time build in {timer.ElapsedMilliseconds}ms");
|
|
return solutions.ToArray();
|
|
}
|
|
|
|
private IList<string> GetProjectsForSolutionConfig(string filePath, string configName)
|
|
{
|
|
var sln = SolutionFile.Parse(filePath);
|
|
|
|
if (string.IsNullOrEmpty(configName))
|
|
{
|
|
configName = sln.GetDefaultConfigurationName();
|
|
}
|
|
|
|
var projects = new List<string>();
|
|
|
|
var config = sln.SolutionConfigurations.FirstOrDefault(c => c.ConfigurationName == configName);
|
|
if (config == null)
|
|
{
|
|
throw new InvalidOperationException($"A solution configuration by the name of '{configName}' was not found in '{filePath}'");
|
|
}
|
|
|
|
foreach (var project in sln.ProjectsInOrder
|
|
.Where(p =>
|
|
p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat // skips solution folders
|
|
&& p.ProjectConfigurations.TryGetValue(config.FullName, out var projectConfig)
|
|
&& projectConfig.IncludeInBuild))
|
|
{
|
|
projects.Add(project.AbsolutePath.Replace('\\', '/'));
|
|
}
|
|
|
|
return projects;
|
|
}
|
|
}
|
|
}
|