using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; using Semmle.Util.Logging; namespace Semmle.Autobuild.Shared { /// /// A solution file, extension .sln. /// public interface ISolution : IProjectOrSolution { /// /// Solution configurations. /// IEnumerable Configurations { get; } /// /// The default configuration name, e.g. "Release" /// string DefaultConfigurationName { get; } /// /// The default platform name, e.g. "x86" /// string DefaultPlatformName { get; } /// /// Gets the "best" tools version for this solution. /// If there are several versions, because the project files /// are inconsistent, then pick the highest/latest version. /// If no tools versions are present, return 0.0.0.0. /// Version ToolsVersion { get; } } /// /// A solution file on the filesystem, read using Microsoft.Build. /// internal class Solution : ProjectOrSolution, ISolution where TAutobuildOptions : AutobuildOptionsShared { private readonly SolutionFile? solution; private readonly IEnumerable> includedProjects; public override IEnumerable IncludedProjects => includedProjects; public IEnumerable Configurations => solution is null ? Enumerable.Empty() : solution.SolutionConfigurations; public string DefaultConfigurationName => solution is null ? "" : solution.GetDefaultConfigurationName(); public string DefaultPlatformName => solution is null ? "" : solution.GetDefaultPlatformName(); public Solution(Autobuilder builder, string path, bool allowProject) : base(builder, path) { try { solution = SolutionFile.Parse(FullPath); } catch (Exception ex) when (ex is InvalidProjectFileException || ex is FileNotFoundException) { // We allow specifying projects as solutions in lgtm.yml, so model // that scenario as a solution with just that one project if (allowProject) { includedProjects = new[] { new Project(builder, path) }; return; } builder.Logger.LogInfo($"Unable to read solution file {path}."); includedProjects = Array.Empty>(); return; } includedProjects = solution.ProjectsInOrder .Where(p => p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) .Select(p => builder.Actions.PathCombine(DirectoryName, builder.Actions.PathCombine(p.RelativePath.Split('\\', StringSplitOptions.RemoveEmptyEntries)))) .Select(p => new Project(builder, p)) .ToArray(); } private IEnumerable ToolsVersions => includedProjects .Where(p => p.ValidToolsVersion) .Select(p => p.ToolsVersion); public Version ToolsVersion => ToolsVersions.Any() ? ToolsVersions.Max()! : new Version(); } }