diff --git a/src/AddIns/Misc/PackageManagement/Project/Src/MSBuildBasedProjectExtensions.cs b/src/AddIns/Misc/PackageManagement/Project/Src/MSBuildBasedProjectExtensions.cs index 315832c0b0..3cd2e0b28c 100644 --- a/src/AddIns/Misc/PackageManagement/Project/Src/MSBuildBasedProjectExtensions.cs +++ b/src/AddIns/Misc/PackageManagement/Project/Src/MSBuildBasedProjectExtensions.cs @@ -2,7 +2,10 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Linq; using ICSharpCode.SharpDevelop.Project; +using Microsoft.Build.Construction; +using NuGet; namespace ICSharpCode.PackageManagement { @@ -15,5 +18,67 @@ namespace ICSharpCode.PackageManagement { return project.HasProjectType(WebApplication) || project.HasProjectType(WebSite); } + + public static void AddImportIfMissing( + this MSBuildBasedProject project, + string importedProjectFile, + ProjectImportLocation importLocation) + { + lock (project.SyncRoot) { + if (project.ImportExists(importedProjectFile)) + return; + + ProjectImportElement import = AddImport(project.MSBuildProjectFile, importedProjectFile, importLocation); + import.Condition = GetCondition(importedProjectFile); + } + } + + static ProjectImportElement AddImport( + ProjectRootElement projectRoot, + string importedProjectFile, + ProjectImportLocation importLocation) + { + if (importLocation == ProjectImportLocation.Top) { + return AddImportAtTop(projectRoot, importedProjectFile); + } + return projectRoot.AddImport(importedProjectFile); + } + + static ProjectImportElement AddImportAtTop(ProjectRootElement projectRoot, string importedProjectFile) + { + ProjectImportElement import = projectRoot.CreateImportElement(importedProjectFile); + projectRoot.InsertBeforeChild(import, projectRoot.FirstChild); + return import; + } + + static string GetCondition(string importedProjectFile) + { + return String.Format("Exists('{0}')", importedProjectFile); + } + + public static bool ImportExists(this MSBuildBasedProject project, string importedProjectFile) + { + lock (project.SyncRoot) { + return project.FindImport(importedProjectFile) != null; + } + } + + public static void RemoveImport(this MSBuildBasedProject project, string importedProjectFile) + { + lock (project.SyncRoot) { + ProjectImportElement import = project.FindImport(importedProjectFile); + if (import != null) { + import.Parent.RemoveChild(import); + } + } + } + + static ProjectImportElement FindImport(this MSBuildBasedProject project, string importedProjectFile) + { + return project + .MSBuildProjectFile + .Imports + .FirstOrDefault(import => String.Equals(import.Project, importedProjectFile, StringComparison.OrdinalIgnoreCase)); + } } } diff --git a/src/AddIns/Misc/PackageManagement/Project/Src/SharpDevelopProjectSystem.cs b/src/AddIns/Misc/PackageManagement/Project/Src/SharpDevelopProjectSystem.cs index 976fe5bfdc..6f8f474a1d 100644 --- a/src/AddIns/Misc/PackageManagement/Project/Src/SharpDevelopProjectSystem.cs +++ b/src/AddIns/Misc/PackageManagement/Project/Src/SharpDevelopProjectSystem.cs @@ -303,12 +303,28 @@ namespace ICSharpCode.PackageManagement public void AddImport(string targetPath, ProjectImportLocation location) { - throw new NotImplementedException(); + string relativeTargetPath = GetRelativePath(targetPath); + project.AddImportIfMissing(relativeTargetPath, location); + ReevaluateProjectIfNecessary(); + projectService.Save(project); + } + + string GetRelativePath(string path) + { + return FileUtility.GetRelativePath(project.Directory, path); } public void RemoveImport(string targetPath) { - throw new NotImplementedException(); + string relativeTargetPath = GetRelativePath(targetPath); + project.RemoveImport(relativeTargetPath); + ReevaluateProjectIfNecessary(); + projectService.Save(project); + } + + protected virtual void ReevaluateProjectIfNecessary() + { + project.ReevaluateIfNecessary(); } } } diff --git a/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj b/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj index 27a99869a3..9becec24d1 100644 --- a/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj +++ b/src/AddIns/Misc/PackageManagement/Test/PackageManagement.Tests.csproj @@ -37,6 +37,7 @@ TRACE + ..\RequiredLibraries\NuGet.Console.Types.dll diff --git a/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableProject.cs b/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableProject.cs index a849551d07..47b56763af 100644 --- a/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableProject.cs +++ b/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableProject.cs @@ -2,9 +2,13 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; + using ICSharpCode.SharpDevelop.Internal.Templates; using ICSharpCode.SharpDevelop.Project; +using Microsoft.Build.Construction; namespace PackageManagement.Tests.Helpers { @@ -92,5 +96,20 @@ namespace PackageManagement.Tests.Helpers dependentFile.DependentUpon = dependentUpon; return dependentFile; } + + public ProjectImportElement GetLastMSBuildChildElement() + { + return MSBuildProjectFile.LastChild as ProjectImportElement; + } + + public ProjectImportElement GetFirstMSBuildChildElement() + { + return MSBuildProjectFile.FirstChild as ProjectImportElement; + } + + public ICollection GetImports() + { + return MSBuildProjectFile.Imports; + } } } diff --git a/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableSharpDevelopProjectSystem.cs b/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableSharpDevelopProjectSystem.cs index 2fa9d8a7f2..5c8ec4bf80 100644 --- a/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableSharpDevelopProjectSystem.cs +++ b/src/AddIns/Misc/PackageManagement/Test/Src/Helpers/TestableSharpDevelopProjectSystem.cs @@ -22,6 +22,7 @@ namespace PackageManagement.Tests.Helpers public ReferenceAndProjectName ReferenceAndProjectNamePassedToLogAddedReferenceToProject; public ReferenceAndProjectName ReferenceAndProjectNamePassedToLogRemovedReferenceFromProject; public FileNameAndProjectName FileNameAndProjectNamePassedToLogAddedFileToProject; + public bool IsReevaluateProjectIfNecessaryCalled; public TestableSharpDevelopProjectSystem(MSBuildBasedProject project) : this( @@ -82,5 +83,10 @@ namespace PackageManagement.Tests.Helpers FileNameAndProjectNamePassedToLogAddedFileToProject = new FileNameAndProjectName(fileName, projectName); } + + protected override void ReevaluateProjectIfNecessary() + { + IsReevaluateProjectIfNecessaryCalled = true; + } } } diff --git a/src/AddIns/Misc/PackageManagement/Test/Src/SharpDevelopProjectSystemTests.cs b/src/AddIns/Misc/PackageManagement/Test/Src/SharpDevelopProjectSystemTests.cs index 3a47e4bc95..ac7e898a44 100644 --- a/src/AddIns/Misc/PackageManagement/Test/Src/SharpDevelopProjectSystemTests.cs +++ b/src/AddIns/Misc/PackageManagement/Test/Src/SharpDevelopProjectSystemTests.cs @@ -3,10 +3,13 @@ using System; using System.IO; +using System.Linq; using System.Runtime.Versioning; using ICSharpCode.PackageManagement; using ICSharpCode.SharpDevelop.Project; +using Microsoft.Build.Construction; +using NuGet; using NUnit.Framework; using PackageManagement.Tests.Helpers; @@ -59,6 +62,30 @@ namespace PackageManagement.Tests projectSystem.AddFile(fileName, (Stream)null); } + void AssertLastMSBuildChildElementHasProjectAttributeValue(string expectedAttributeValue) + { + ProjectImportElement import = project.GetLastMSBuildChildElement(); + Assert.AreEqual(expectedAttributeValue, import.Project); + } + + void AssertLastMSBuildChildHasCondition(string expectedCondition) + { + ProjectImportElement import = project.GetLastMSBuildChildElement(); + Assert.AreEqual(expectedCondition, import.Condition); + } + + void AssertFirstMSBuildChildElementHasProjectAttributeValue(string expectedAttributeValue) + { + ProjectImportElement import = project.GetFirstMSBuildChildElement(); + Assert.AreEqual(expectedAttributeValue, import.Project); + } + + void AssertFirstMSBuildChildHasCondition(string expectedCondition) + { + ProjectImportElement import = project.GetFirstMSBuildChildElement(); + Assert.AreEqual(expectedCondition, import.Condition); + } + [Test] public void Root_NewInstanceCreated_ReturnsProjectDirectory() { @@ -833,5 +860,180 @@ namespace PackageManagement.Tests string customTool = fileItem.CustomTool; Assert.AreEqual(String.Empty, customTool); } + + [Test] + public void AddImport_FullImportFilePathAndBottomOfProject_PathRelativeToProjectAddedAsLastImportInProject() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + + projectSystem.AddImport(targetPath, ProjectImportLocation.Bottom); + + AssertLastMSBuildChildElementHasProjectAttributeValue(@"..\packages\Foo.0.1\build\Foo.targets"); + } + + [Test] + public void AddImport_AddImportToBottomOfProject_ImportAddedWithConditionThatChecksForExistenceOfTargetsFile() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + + projectSystem.AddImport(targetPath, ProjectImportLocation.Bottom); + + AssertLastMSBuildChildHasCondition("Exists('..\\packages\\Foo.0.1\\build\\Foo.targets')"); + } + + [Test] + public void AddImport_AddSameImportTwice_ImportOnlyAddedOnceToProject() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + projectSystem.AddImport(targetPath, ProjectImportLocation.Bottom); + + projectSystem.AddImport(targetPath, ProjectImportLocation.Bottom); + + Assert.AreEqual(1, project.GetImports().Count); + } + + [Test] + public void AddImport_AddSameImportTwiceButWithDifferentCase_ImportOnlyAddedOnceToProject() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath1 = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + string targetPath2 = @"d:\projects\MyProject\packages\Foo.0.1\BUILD\FOO.TARGETS"; + projectSystem.AddImport(targetPath1, ProjectImportLocation.Bottom); + + projectSystem.AddImport(targetPath2, ProjectImportLocation.Bottom); + + Assert.AreEqual(1, project.GetImports().Count); + } + + [Test] + public void AddImport_FullImportFilePathAndBottomOfProject_ProjectIsSaved() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + + projectSystem.AddImport(targetPath, ProjectImportLocation.Bottom); + + Assert.IsTrue(project.IsSaved); + } + + [Test] + public void AddImport_FullImportFilePathAndBottomOfProject_ProjectIsReevaluated() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + + projectSystem.AddImport(targetPath, ProjectImportLocation.Bottom); + + Assert.IsTrue(projectSystem.IsReevaluateProjectIfNecessaryCalled); + } + + [Test] + public void RemoveImport_ImportAlreadyAddedToBottomOfProject_ImportRemoved() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + projectSystem.AddImport(targetPath, ProjectImportLocation.Bottom); + + projectSystem.RemoveImport(targetPath); + + Assert.AreEqual(0, project.GetImports().Count); + } + + [Test] + public void RemoveImport_ImportAlreadyWithDifferentCaseAddedToBottomOfProject_ImportRemoved() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath1 = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + projectSystem.AddImport(targetPath1, ProjectImportLocation.Bottom); + string targetPath2 = @"d:\projects\MyProject\packages\Foo.0.1\BUILD\FOO.TARGETS"; + + projectSystem.RemoveImport(targetPath2); + + Assert.AreEqual(0, project.GetImports().Count); + } + + [Test] + public void RemoveImport_DifferentImportAdded_ExceptionNotThrown() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath1 = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + projectSystem.AddImport(targetPath1, ProjectImportLocation.Bottom); + string targetPath2 = @"d:\projects\MyProject\packages\Bar.0.1\build\Bar.targets"; + + Assert.DoesNotThrow(() => projectSystem.RemoveImport(targetPath2)); + + Assert.AreEqual(1, project.GetImports().Count); + } + + [Test] + public void RemoveImport_NoImportsAdded_ProjectIsSaved() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + + projectSystem.RemoveImport("Unknown.targets"); + + Assert.IsTrue(project.IsSaved); + } + + [Test] + public void RemoveImport_NoImportsAdded_ProjectIsReevaluated() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + + projectSystem.RemoveImport("Unknown.targets"); + + Assert.IsTrue(projectSystem.IsReevaluateProjectIfNecessaryCalled); + } + + [Test] + public void AddImport_AddToTopOfProject_ImportAddedAsFirstChildElement() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + + projectSystem.AddImport(targetPath, ProjectImportLocation.Top); + + AssertFirstMSBuildChildElementHasProjectAttributeValue(@"..\packages\Foo.0.1\build\Foo.targets"); + } + + [Test] + public void AddImport_AddImportToTopOfProject_ImportAddedWithConditionThatChecksForExistenceOfTargetsFile() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + + projectSystem.AddImport(targetPath, ProjectImportLocation.Top); + + AssertFirstMSBuildChildHasCondition("Exists('..\\packages\\Foo.0.1\\build\\Foo.targets')"); + } + + [Test] + public void AddImport_AddToTopOfProjectTwice_ImportAddedOnlyOnce() + { + CreateTestProject(@"d:\projects\MyProject\MyProject\MyProject.csproj"); + CreateProjectSystem(project); + string targetPath = @"d:\projects\MyProject\packages\Foo.0.1\build\Foo.targets"; + projectSystem.AddImport(targetPath, ProjectImportLocation.Top); + + projectSystem.AddImport(targetPath, ProjectImportLocation.Top); + + Assert.AreEqual(1, project.GetImports().Count); + } } } diff --git a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs index 33fdbf3cde..cd2d36b1d8 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs @@ -335,7 +335,7 @@ namespace ICSharpCode.SharpDevelop.Project } } - protected void ReevaluateIfNecessary() + public void ReevaluateIfNecessary() { using (var c = OpenCurrentConfiguration()) { c.Project.ReevaluateIfNecessary();