diff --git a/build/RepositoryBuild.targets b/build/RepositoryBuild.targets index eb6e75b617..7a329f6dca 100644 --- a/build/RepositoryBuild.targets +++ b/build/RepositoryBuild.targets @@ -2,12 +2,12 @@ - %(RepositoryToBuildInOrder.Order) - %(RepositoryToBuildInOrder.Identity) + %(RepositoryBuildOrder.Order) + %(RepositoryBuildOrder.Identity) - RepositoryToBuild=%(RepositoryToBuildInOrder.Identity); - BuildRepositoryRoot=%(RepositoryToBuildInOrder.RepositoryPath)\; - CommitHash=%(RepositoryToBuildInOrder.Commit) + RepositoryToBuild=%(RepositoryBuildOrder.Identity); + BuildRepositoryRoot=%(RepositoryBuildOrder.RepositoryPath)\; + CommitHash=%(RepositoryBuildOrder.Commit) diff --git a/build/repo.targets b/build/repo.targets index 9dc8fdd308..0d387361d4 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -7,7 +7,6 @@ <_CloneRepositoryRoot>$(RepositoryRoot).r\ <_DependencyBuildDirectory>$(RepositoryRoot).deps\build\ <_DependencyPackagesDirectory>$(_DependencyBuildDirectory) - <_RestoreGraphSpecsDirectory>$(RepositoryRoot)obj\package-specs\ <_RepositoryListFileName>Repositories.props <_DefaultRepositoryList>$(MSBuildThisFileDirectory)$(_RepositoryListFileName) @@ -20,14 +19,13 @@ $(PrepareDependsOn);CleanUniverseArtifacts $(CleanDependsOn);CleanUniverseArtifacts - $(BuildDependsOn);CloneRepositories;BuildRepositories + $(BuildDependsOn);BuildRepositories - @@ -107,48 +105,43 @@ Condition="'$(CloneRepositoryCommit)'!=''" /> - - - - - - - - + DependsOnTargets="_PrepareRepositories;_CreateRepositoriesListWithCommits;_UpdateNuGetConfig;Graph;_BuildRepositories" /> + + + + + + + + + - - - %(Repository.Identity) - - - - RestoreGraphOutputPath=$(_RestoreGraphSpecsDirectory)%(Solution.Repository)\%(Solution.FileName)%(Solution.Extension).json - - - - + + <_NoBuildSolution Update="@(_NoBuildSolution)" Build="false" /> + + + - - - + + + + + - - - - + + diff --git a/build/tasks/AnalyzeBuildGraph.cs b/build/tasks/AnalyzeBuildGraph.cs new file mode 100644 index 0000000000..a135e66568 --- /dev/null +++ b/build/tasks/AnalyzeBuildGraph.cs @@ -0,0 +1,74 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using RepoTasks.ProjectModel; +using RepoTasks.Utilities; + +namespace RepoTasks +{ + public class AnalyzeBuildGraph : Task, ICancelableTask + { + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + + /// + /// Repositories that we are building new versions of. + /// + [Required] + public ITaskItem[] Solutions { get; set; } + + [Required] + public string Properties { get; set; } + + /// + /// New packages we are compiling. Used in the pin tool. + /// + [Output] + public ITaskItem[] PackagesProduced { get; set; } + + /// + /// The order in which to build repositories + /// + [Output] + public ITaskItem[] RepositoryBuildOrder { get; set; } + + public void Cancel() + { + _cts.Cancel(); + } + + public override bool Execute() + { + var factory = new SolutionInfoFactory(Log, BuildEngine5); + var props = MSBuildListSplitter.GetNamedProperties(Properties); + + Log.LogMessage(MessageImportance.High, $"Beginning cross-repo analysis on {Solutions.Length} solutions. Hang tight..."); + + var solutions = factory.Create(Solutions, props, _cts.Token); + Log.LogMessage($"Found {solutions.Count} and {solutions.Sum(p => p.Projects.Count)} projects"); + + if (_cts.IsCancellationRequested) + { + return false; + } + + PackagesProduced = solutions + .Where(s => s.ShouldBuild) + .SelectMany(p => p.Projects) + .Where(p => p.IsPackable) + .Select(p => new TaskItem(p.PackageId, new Hashtable + { + ["Version"] = p.PackageVersion + })) + .ToArray(); + + return !Log.HasLoggedErrors; + } + } +} diff --git a/build/tasks/ProjectModel/DotNetCliReferenceInfo.cs b/build/tasks/ProjectModel/DotNetCliReferenceInfo.cs new file mode 100644 index 0000000000..c490dab999 --- /dev/null +++ b/build/tasks/ProjectModel/DotNetCliReferenceInfo.cs @@ -0,0 +1,24 @@ +// 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; + +namespace RepoTasks.ProjectModel +{ + internal class DotNetCliReferenceInfo + { + public DotNetCliReferenceInfo(string id, string version) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentException(nameof(id)); + } + + Id = id; + Version = version; + } + + public string Id { get; } + public string Version { get; } + } +} diff --git a/build/tasks/ProjectModel/PackageReferenceInfo.cs b/build/tasks/ProjectModel/PackageReferenceInfo.cs new file mode 100644 index 0000000000..d01089d594 --- /dev/null +++ b/build/tasks/ProjectModel/PackageReferenceInfo.cs @@ -0,0 +1,29 @@ +// 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.Generic; + +namespace RepoTasks.ProjectModel +{ + internal class PackageReferenceInfo + { + public PackageReferenceInfo(string id, string version, bool isImplicitlyDefined, IReadOnlyList noWarn) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentException(nameof(id)); + } + + Id = id; + Version = version; + IsImplicitlyDefined = isImplicitlyDefined; + NoWarn = noWarn; + } + + public string Id { get; } + public string Version { get; } + public bool IsImplicitlyDefined { get; } + public IReadOnlyList NoWarn { get; } + } +} diff --git a/build/tasks/ProjectModel/ProjectFrameworkInfo.cs b/build/tasks/ProjectModel/ProjectFrameworkInfo.cs new file mode 100644 index 0000000000..35a212f9a1 --- /dev/null +++ b/build/tasks/ProjectModel/ProjectFrameworkInfo.cs @@ -0,0 +1,22 @@ +// 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.Generic; +using System.Linq; +using NuGet.Frameworks; + +namespace RepoTasks.ProjectModel +{ + internal class ProjectFrameworkInfo + { + public ProjectFrameworkInfo(NuGetFramework targetFramework, IReadOnlyDictionary dependencies) + { + TargetFramework = targetFramework ?? throw new ArgumentNullException(nameof(targetFramework)); + Dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); + } + + public NuGetFramework TargetFramework { get; } + public IReadOnlyDictionary Dependencies { get; } + } +} diff --git a/build/tasks/ProjectModel/ProjectInfo.cs b/build/tasks/ProjectModel/ProjectInfo.cs new file mode 100644 index 0000000000..1dd4339185 --- /dev/null +++ b/build/tasks/ProjectModel/ProjectInfo.cs @@ -0,0 +1,48 @@ +// 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.Generic; +using System.IO; + +namespace RepoTasks.ProjectModel +{ + internal class ProjectInfo + { + public ProjectInfo(string fullPath, + string projectExtensionsPath, + IReadOnlyList frameworks, + IReadOnlyList tools, + bool isPackable, + string packageId, + string packageVersion) + { + if (!Path.IsPathRooted(fullPath)) + { + throw new ArgumentException("Path must be absolute", nameof(fullPath)); + } + + Frameworks = frameworks ?? throw new ArgumentNullException(nameof(frameworks)); + Tools = tools ?? throw new ArgumentNullException(nameof(tools)); + + FullPath = fullPath; + FileName = Path.GetFileName(fullPath); + Directory = Path.GetDirectoryName(FullPath); + ProjectExtensionsPath = projectExtensionsPath ?? Path.Combine(Directory, "obj"); + IsPackable = isPackable; + PackageId = packageId; + PackageVersion = packageVersion; + } + + public string FullPath { get; } + public string FileName { get; } + public string ProjectExtensionsPath { get; } + public string Directory { get; } + public string PackageId { get; } + public string PackageVersion { get; } + public bool IsPackable { get; } + + public IReadOnlyList Frameworks { get; } + public IReadOnlyList Tools { get; } + } +} diff --git a/build/tasks/ProjectModel/ProjectInfoFactory.cs b/build/tasks/ProjectModel/ProjectInfoFactory.cs new file mode 100644 index 0000000000..5c739f1784 --- /dev/null +++ b/build/tasks/ProjectModel/ProjectInfoFactory.cs @@ -0,0 +1,134 @@ +// 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.Generic; +using System.IO; +using System.Linq; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using NuGet.Frameworks; +using RepoTasks.Utilities; +using Microsoft.Build.Utilities; + +namespace RepoTasks.ProjectModel +{ + internal class ProjectInfoFactory + { + private readonly TaskLoggingHelper _logger; + + public ProjectInfoFactory(TaskLoggingHelper logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public ProjectInfo Create(string path, ProjectCollection projectCollection) + { + var project = GetProject(path, projectCollection); + var instance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup); + var projExtPath = instance.GetPropertyValue("MSBuildProjectExtensionsPath"); + + var targetFrameworks = instance.GetPropertyValue("TargetFrameworks"); + var targetFramework = instance.GetPropertyValue("TargetFramework"); + + var frameworks = new List(); + if (!string.IsNullOrEmpty(targetFrameworks) && string.IsNullOrEmpty(targetFramework)) + { + // multi targeting + foreach (var tfm in targetFrameworks.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + project.SetGlobalProperty("TargetFramework", tfm); + var innerBuild = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup); + + var tfmInfo = new ProjectFrameworkInfo(NuGetFramework.Parse(tfm), GetDependencies(innerBuild)); + + frameworks.Add(tfmInfo); + } + + project.RemoveGlobalProperty("TargetFramework"); + } + else if (!string.IsNullOrEmpty(targetFramework)) + { + var tfmInfo = new ProjectFrameworkInfo(NuGetFramework.Parse(targetFramework), GetDependencies(instance)); + + frameworks.Add(tfmInfo); + } + + var projectDir = Path.GetDirectoryName(path); + + var tools = GetTools(instance).ToArray(); + bool.TryParse(instance.GetPropertyValue("IsPackable"), out var isPackable); + var packageId = instance.GetPropertyValue("PackageId"); + var packageVersion = instance.GetPropertyValue("PackageVersion"); + + return new ProjectInfo(path, + projExtPath, + frameworks, + tools, + isPackable, + packageId, + packageVersion); + } + + private static object _projLock = new object(); + + private static Project GetProject(string path, ProjectCollection projectCollection) + { + var projects = projectCollection.GetLoadedProjects(path); + foreach(var proj in projects) + { + if (proj.GetPropertyValue("DesignTimeBuild") == "true") + { + return proj; + } + } + + var xml = ProjectRootElement.Open(path, projectCollection); + var globalProps = new Dictionary() + { + ["DesignTimeBuild"] = "true", + }; + + var project = new Project(xml, + globalProps, + toolsVersion: "15.0", + projectCollection: projectCollection) + { + IsBuildEnabled = false + }; + + return project; + } + + private IReadOnlyDictionary GetDependencies(ProjectInstance project) + { + var references = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var item in project.GetItems("PackageReference")) + { + bool.TryParse(item.GetMetadataValue("IsImplicitlyDefined"), out var isImplicit); + var noWarn = item.GetMetadataValue("NoWarn"); + IReadOnlyList noWarnItems = string.IsNullOrEmpty(noWarn) + ? Array.Empty() + : MSBuildListSplitter.SplitItemList(noWarn).ToArray(); + + var info = new PackageReferenceInfo(item.EvaluatedInclude, item.GetMetadataValue("Version"), isImplicit, noWarnItems); + + if (references.ContainsKey(info.Id)) + { + _logger.LogKoreBuildWarning(project.ProjectFileLocation.File, KoreBuildErrors.DuplicatePackageReference, $"Found a duplicate PackageReference for {info.Id}. Restore results may be unpredictable."); + } + + references[info.Id] = info; + } + + return references; + } + + private static IEnumerable GetTools(ProjectInstance project) + { + return project.GetItems("DotNetCliToolReference").Select(item => + new DotNetCliReferenceInfo(item.EvaluatedInclude, item.GetMetadataValue("Version"))); + } + } +} diff --git a/build/tasks/ProjectModel/SolutionInfo.cs b/build/tasks/ProjectModel/SolutionInfo.cs new file mode 100644 index 0000000000..5fe51132db --- /dev/null +++ b/build/tasks/ProjectModel/SolutionInfo.cs @@ -0,0 +1,34 @@ +// 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.Generic; + +namespace RepoTasks.ProjectModel +{ + internal class SolutionInfo + { + public SolutionInfo(string fullPath, string configName, IReadOnlyList projects, bool shouldBuild) + { + if (string.IsNullOrEmpty(fullPath)) + { + throw new ArgumentException(nameof(fullPath)); + } + + if (string.IsNullOrEmpty(configName)) + { + throw new ArgumentException(nameof(configName)); + } + + FullPath = fullPath; + ConfigName = configName; + Projects = projects ?? throw new ArgumentNullException(nameof(projects)); + ShouldBuild = shouldBuild; + } + + public string FullPath { get; } + public string ConfigName { get; } + public IReadOnlyList Projects { get; } + public bool ShouldBuild { get; } + } +} diff --git a/build/tasks/ProjectModel/SolutionInfoFactory.cs b/build/tasks/ProjectModel/SolutionInfoFactory.cs new file mode 100644 index 0000000000..72601be2dc --- /dev/null +++ b/build/tasks/ProjectModel/SolutionInfoFactory.cs @@ -0,0 +1,134 @@ +// 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 Create(IEnumerable solutionItems, IDictionary properties, CancellationToken ct) + { + var timer = Stopwatch.StartNew(); + + var solutions = new ConcurrentBag(); + + Parallel.ForEach(solutionItems, solution => + { + if (ct.IsCancellationRequested) + { + return; + } + + var solutionFile = solution.ItemSpec.Replace('\\', '/'); + var solutionProps = new Dictionary(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 = "Debug"; + } + + 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(); + 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); + + var solutionInfo = new SolutionInfo( + solutionFile, + configName, + projects.ToArray(), + shouldBuild); + + _buildEngine.RegisterTaskObject(key, solutionInfo, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: true); + + solutions.Add(solutionInfo); + }); + + timer.Stop(); + _logger.LogMessage(MessageImportance.Normal, $"Finished design-time build in {timer.ElapsedMilliseconds}ms"); + return solutions.ToArray(); + } + + private IList GetProjectsForSolutionConfig(string filePath, string configName) + { + var sln = SolutionFile.Parse(filePath); + + if (string.IsNullOrEmpty(configName)) + { + configName = sln.GetDefaultConfigurationName(); + } + + var projects = new List(); + + 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; + } + } +} diff --git a/build/tasks/RepoTasks.tasks b/build/tasks/RepoTasks.tasks index 0c188ffb1e..4026e9810b 100644 --- a/build/tasks/RepoTasks.tasks +++ b/build/tasks/RepoTasks.tasks @@ -4,6 +4,6 @@ - + diff --git a/build/tasks/Utilities/KoreBuildErrors.cs b/build/tasks/Utilities/KoreBuildErrors.cs new file mode 100644 index 0000000000..7751985c6f --- /dev/null +++ b/build/tasks/Utilities/KoreBuildErrors.cs @@ -0,0 +1,29 @@ +// 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. + +namespace RepoTasks.Utilities +{ + public static class KoreBuildErrors + { + public const string Prefix = "KRB"; + + // Typically used in repos in Directory.Build.targets + public const int PackagesHaveNotYetBeenPinned = 1001; + + // Warnings + public const int DotNetAssetVersionIsFloating = 2000; + public const int RepoVersionDoesNotMatchProjectVersion = 2001; + public const int RepoPackageVersionDoesNotMatchProjectPackageVersion = 2002; + public const int DuplicatePackageReference = 2003; + + // NuGet errors + public const int InvalidNuspecFile = 4001; + public const int PackageReferenceHasVersion = 4002; + public const int DotNetCliReferenceReferenceHasVersion = 4003; + public const int PackageVersionNotFoundInLineup = 4004; + + // Other unknown errors + public const int PolicyFailedToApply = 5000; + public const int UnknownPolicyType = 5001; + } +} diff --git a/build/tasks/Utilities/LoggingExtensions.cs b/build/tasks/Utilities/LoggingExtensions.cs new file mode 100644 index 0000000000..833edb1aa4 --- /dev/null +++ b/build/tasks/Utilities/LoggingExtensions.cs @@ -0,0 +1,26 @@ +// 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 Microsoft.Build.Utilities; + +namespace RepoTasks.Utilities +{ + public static class LoggingExtensions + { + public static void LogKoreBuildError(this TaskLoggingHelper logger, int code, string message, params object[] messageArgs) + => LogKoreBuildError(logger, null, code, message, messageArgs: messageArgs); + + public static void LogKoreBuildError(this TaskLoggingHelper logger, string filename, int code, string message, params object[] messageArgs) + { + logger.LogError(null, KoreBuildErrors.Prefix + code, null, filename, 0, 0, 0, 0, message, messageArgs: messageArgs); + } + + public static void LogKoreBuildWarning(this TaskLoggingHelper logger, int code, string message, params object[] messageArgs) + => LogKoreBuildWarning(logger, null, code, message, messageArgs: messageArgs); + + public static void LogKoreBuildWarning(this TaskLoggingHelper logger, string filename, int code, string message, params object[] messageArgs) + { + logger.LogWarning(null, KoreBuildErrors.Prefix + code, null, filename, 0, 0, 0, 0, message, messageArgs: messageArgs); + } + } +} diff --git a/build/tasks/Utilities/MSBuildListSplitter.cs b/build/tasks/Utilities/MSBuildListSplitter.cs new file mode 100644 index 0000000000..de4ff8724b --- /dev/null +++ b/build/tasks/Utilities/MSBuildListSplitter.cs @@ -0,0 +1,45 @@ +// 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.Generic; +using System.Linq; + +namespace RepoTasks.Utilities +{ + internal static class MSBuildListSplitter + { + private static readonly char[] SemiColon = { ';' }; + + public static IEnumerable SplitItemList(string value) + { + return string.IsNullOrEmpty(value) + ? Enumerable.Empty() + : value.Split(SemiColon, StringSplitOptions.RemoveEmptyEntries); + } + + public static Dictionary GetNamedProperties(string input) + { + var values = new Dictionary(); + if (string.IsNullOrEmpty(input)) + { + return values; + } + + foreach (var item in input.Split(SemiColon, StringSplitOptions.RemoveEmptyEntries)) + { + var splitIdx = item.IndexOf('='); + if (splitIdx <= 0) + { + continue; + } + + var key = item.Substring(0, splitIdx).Trim(); + var value = item.Substring(splitIdx + 1); + values[key] = value; + } + + return values; + } + } +} diff --git a/build/tasks/VerifyBuildGraph.cs b/build/tasks/VerifyBuildGraph.cs deleted file mode 100644 index 5e69965aac..0000000000 --- a/build/tasks/VerifyBuildGraph.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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.Generic; -using System.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace RepoTasks -{ - public class VerifyBuildGraph : Task - { - /// - /// Repositories that we are building new versions of. - /// - [Required] - public ITaskItem[] BuildRepositories { get; set; } - - /// - /// Repos that have already been build and released. We don't compile and build them, - /// but we still want to be sure their packages are accounted for in our graph calculations. - /// - public ITaskItem[] NoBuildRepositories { get; set; } - - /// - /// New packages we are compiling. Used in the pin tool. - /// - [Output] - public ITaskItem[] PackagesToBeProduced { get; set; } - - public override bool Execute() - { - return false; - } - } -}