From 84e4da57cc5362ccb18268cd8b7c751eca89be89 Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 8 Dec 2016 12:59:14 -0800 Subject: [PATCH] add compat checks to dotnet add --- src/dotnet/CommonLocalizableStrings.cs | 5 + src/dotnet/MsbuildProject.cs | 99 +++++++++++++++++-- .../dotnet-add/dotnet-add-p2p/Program.cs | 42 +++++++- .../dotnet-remove-p2p/Program.cs | 2 +- 4 files changed, 138 insertions(+), 10 deletions(-) diff --git a/src/dotnet/CommonLocalizableStrings.cs b/src/dotnet/CommonLocalizableStrings.cs index e62af8228..351ee8ee5 100644 --- a/src/dotnet/CommonLocalizableStrings.cs +++ b/src/dotnet/CommonLocalizableStrings.cs @@ -179,5 +179,10 @@ public const string SpecifiedNameExists = "Specified name {0} already exists. Please specify a different name."; public const string SpecifiedAliasExists = "Specified alias {0} already exists. Please specify a different alias."; public const string MandatoryParameterMissing = "Mandatory parameter {0} missing for template {1}. "; + + /// compatibility + public const string ProjectNotCompatibleWithFramework = "Project `{0}` is not compatible with `{1}`."; + public const string ProjectDoesNotTargetFramework = "Project `{0}` does not target `{1}`."; + public const string ProjectCouldNotBeEvaluated = "Project `{0}` could not be evaluated. Evaluation failed with following error:\n{1}"; } } diff --git a/src/dotnet/MsbuildProject.cs b/src/dotnet/MsbuildProject.cs index efa1a22b6..6fcf8525f 100644 --- a/src/dotnet/MsbuildProject.cs +++ b/src/dotnet/MsbuildProject.cs @@ -3,8 +3,10 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Exceptions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Tools.Common; +using NuGet.Frameworks; using System; using System.Collections.Generic; using System.IO; @@ -16,13 +18,16 @@ namespace Microsoft.DotNet.Tools { const string ProjectItemElementType = "ProjectReference"; - public ProjectRootElement Project { get; private set; } + public ProjectRootElement ProjectRoot { get; private set; } public string ProjectDirectory { get; private set; } + + private List _cachedTfms = null; + private Project _cachedEvaluatedProject = null; private MsbuildProject(ProjectRootElement project) { - Project = project; - ProjectDirectory = PathUtility.EnsureTrailingSlash(Project.DirectoryPath); + ProjectRoot = project; + ProjectDirectory = PathUtility.EnsureTrailingSlash(ProjectRoot.DirectoryPath); } public static MsbuildProject FromFileOrDirectory(string fileOrDirectory) @@ -101,17 +106,17 @@ namespace Microsoft.DotNet.Tools { int numberOfAddedReferences = 0; - ProjectItemGroupElement itemGroup = Project.FindUniformOrCreateItemGroupWithCondition(ProjectItemElementType, framework); + ProjectItemGroupElement itemGroup = ProjectRoot.FindUniformOrCreateItemGroupWithCondition(ProjectItemElementType, framework); foreach (var @ref in refs.Select((r) => NormalizeSlashes(r))) { - if (Project.HasExistingItemWithCondition(framework, @ref)) + if (ProjectRoot.HasExistingItemWithCondition(framework, @ref)) { Reporter.Output.WriteLine(string.Format(CommonLocalizableStrings.ProjectAlreadyHasAreference, @ref)); continue; } numberOfAddedReferences++; - itemGroup.AppendChild(Project.CreateItemElement(ProjectItemElementType, @ref)); + itemGroup.AppendChild(ProjectRoot.CreateItemElement(ProjectItemElementType, @ref)); Reporter.Output.WriteLine(string.Format(CommonLocalizableStrings.ReferenceAddedToTheProject, @ref)); } @@ -133,7 +138,7 @@ namespace Microsoft.DotNet.Tools public IEnumerable GetProjectToProjectReferences() { - return Project.GetAllItemsWithElementType(ProjectItemElementType); + return ProjectRoot.GetAllItemsWithElementType(ProjectItemElementType); } public void ConvertPathsToRelative(ref List references) @@ -166,12 +171,90 @@ namespace Microsoft.DotNet.Tools } } + public IEnumerable GetTargetFrameworks() + { + if (_cachedTfms != null) + { + return _cachedTfms; + } + + var project = GetEvaluatedProject(); + + var properties = project.AllEvaluatedProperties + .Where(p => p.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase) || + p.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase)) + .Select(p => p.EvaluatedValue.ToLower()).ToList(); + + var uniqueTfms = new HashSet(); + + foreach (var property in properties) + { + var tfms = property + .Split(';') + .Select((tfm) => tfm.Trim()) + .Where((tfm) => !string.IsNullOrEmpty(tfm)); + + foreach (var tfm in tfms) + { + uniqueTfms.Add(tfm); + } + } + + _cachedTfms = uniqueTfms.Select((frameworkString) => NuGetFramework.Parse(frameworkString)).ToList(); + return _cachedTfms; + } + + public bool CanWorkOnFramework(NuGetFramework framework) + { + foreach (var tfm in GetTargetFrameworks()) + { + if (DefaultCompatibilityProvider.Instance.IsCompatible(framework, tfm)) + { + return true; + } + } + + return false; + } + + public bool TargetsFramework(NuGetFramework framework) + { + foreach (var tfm in GetTargetFrameworks()) + { + if (framework.Equals(tfm)) + { + return true; + } + } + + return false; + } + + private Project GetEvaluatedProject() + { + if (_cachedEvaluatedProject != null) + { + return _cachedEvaluatedProject; + } + + try + { + _cachedEvaluatedProject = new Project(ProjectRoot); + } + catch (InvalidProjectFileException e) + { + throw new GracefulException(string.Format(CommonLocalizableStrings.ProjectCouldNotBeEvaluated, ProjectRoot.FullPath, e.Message)); + } + + return _cachedEvaluatedProject; + } + private int RemoveProjectToProjectReferenceAlternatives(string framework, string reference) { int numberOfRemovedRefs = 0; foreach (var r in GetIncludeAlternativesForRemoval(reference)) { - foreach (var existingItem in Project.FindExistingItemsWithCondition(framework, r)) + foreach (var existingItem in ProjectRoot.FindExistingItemsWithCondition(framework, r)) { ProjectElementContainer itemGroup = existingItem.Parent; itemGroup.RemoveChild(existingItem); diff --git a/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs b/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs index 581ea1d03..02d1e1608 100644 --- a/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs +++ b/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs @@ -3,7 +3,9 @@ using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Utils; +using NuGet.Frameworks; using System.Collections.Generic; +using System.Linq; namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference { @@ -51,10 +53,43 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneReferenceToAdd); } + string frameworkString = frameworkOption.Value(); List references = app.RemainingArguments; if (!forceOption.HasValue()) { MsbuildProject.EnsureAllReferencesExist(references); + IEnumerable refs = references.Select((r) => MsbuildProject.FromFile(r)); + + if (frameworkString == null) + { + foreach (var tfm in msbuildProj.GetTargetFrameworks()) + { + foreach (var r in refs) + { + if (!r.CanWorkOnFramework(tfm)) + { + throw new GracefulException(string.Format(CommonLocalizableStrings.ProjectNotCompatibleWithFramework, r.ProjectRoot.FullPath, GetFrameworkDisplayString(tfm))); + } + } + } + } + else + { + var framework = NuGetFramework.Parse(frameworkString); + if (!msbuildProj.TargetsFramework(framework)) + { + throw new GracefulException(string.Format(CommonLocalizableStrings.ProjectDoesNotTargetFramework, msbuildProj.ProjectRoot.FullPath, GetFrameworkDisplayString(framework))); + } + + foreach (var r in refs) + { + if (!r.CanWorkOnFramework(framework)) + { + throw new GracefulException(string.Format(CommonLocalizableStrings.ProjectNotCompatibleWithFramework, r.ProjectRoot.FullPath, GetFrameworkDisplayString(framework))); + } + } + } + msbuildProj.ConvertPathsToRelative(ref references); } @@ -64,7 +99,7 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference if (numberOfAddedReferences != 0) { - msbuildProj.Project.Save(); + msbuildProj.ProjectRoot.Save(); } return 0; @@ -81,5 +116,10 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference return 1; } } + + private static string GetFrameworkDisplayString(NuGetFramework framework) + { + return framework.GetShortFolderName(); + } } } diff --git a/src/dotnet/commands/dotnet-remove/dotnet-remove-p2p/Program.cs b/src/dotnet/commands/dotnet-remove/dotnet-remove-p2p/Program.cs index fcdf92fba..6ef8033d1 100644 --- a/src/dotnet/commands/dotnet-remove/dotnet-remove-p2p/Program.cs +++ b/src/dotnet/commands/dotnet-remove/dotnet-remove-p2p/Program.cs @@ -54,7 +54,7 @@ namespace Microsoft.DotNet.Tools.Remove.ProjectToProjectReference if (numberOfRemovedReferences != 0) { - msbuildProj.Project.Save(); + msbuildProj.ProjectRoot.Save(); } return 0;