diff --git a/src/dotnet/commands/dotnet-add-p2p/Program.cs b/src/dotnet/commands/dotnet-add-p2p/Program.cs index d08770d7e0b..ebd8d7d0d7e 100644 --- a/src/dotnet/commands/dotnet-add-p2p/Program.cs +++ b/src/dotnet/commands/dotnet-add-p2p/Program.cs @@ -9,6 +9,7 @@ using System.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Tools.Common; namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference { @@ -42,18 +43,29 @@ public static int Run(string[] args) CommandOption forceOption = app.Option( "--force", - "Add reference even if it does not exist", + "Add reference even if it does not exist, do not convert paths to relative", CommandOptionType.NoValue); app.OnExecute(() => { - if (projectArgument.Value == null) + if (string.IsNullOrEmpty(projectArgument.Value)) { throw new GracefulException("Argument is required."); } - ProjectRootElement project = File.Exists(projectArgument.Value) ? - GetProjectFromFileOrThrow(projectArgument.Value) : - GetProjectFromDirectoryOrThrow(projectArgument.Value); + ProjectRootElement project; + string projectDir; + if (File.Exists(projectArgument.Value)) + { + project = GetProjectFromFileOrThrow(projectArgument.Value); + projectDir = new FileInfo(projectArgument.Value).DirectoryName; + } + else + { + project = GetProjectFromDirectoryOrThrow(projectArgument.Value); + projectDir = projectArgument.Value; + } + + projectDir = PathUtility.EnsureTrailingSlash(projectDir); if (app.RemainingArguments.Count == 0) { @@ -63,22 +75,8 @@ public static int Run(string[] args) List references = app.RemainingArguments; if (!forceOption.HasValue()) { - var notExisting = new List(); - foreach (var r in references) - { - if (!File.Exists(r)) - { - notExisting.Add(r); - } - } - - if (notExisting.Count > 0) - { - throw new GracefulException( - string.Join( - Environment.NewLine, - notExisting.Select((ne) => $"Reference `{ne}` does not exist."))); - } + EnsureAllReferencesExist(references); + ConvertPathsToRelative(projectDir, ref references); } int numberOfAddedReferences = AddProjectToProjectReference( @@ -106,6 +104,32 @@ public static int Run(string[] args) } } + internal static void EnsureAllReferencesExist(List references) + { + var notExisting = new List(); + foreach (var r in references) + { + if (!File.Exists(r)) + { + notExisting.Add(r); + } + } + + if (notExisting.Count > 0) + { + throw new GracefulException( + string.Join( + Environment.NewLine, + notExisting.Select((ne) => $"Reference `{ne}` does not exist."))); + } + } + + internal static void ConvertPathsToRelative(string root, ref List references) + { + root = PathUtility.EnsureTrailingSlash(Path.GetFullPath(root)); + references = references.Select((r) => PathUtility.GetRelativePath(root, Path.GetFullPath(r))).ToList(); + } + // There is ProjectRootElement.TryOpen but it does not work as expected // I.e. it returns null for some valid projects internal static ProjectRootElement TryOpenProject(string filename) @@ -181,13 +205,18 @@ internal static ProjectRootElement GetProjectFromDirectoryOrThrow(string directo return ret; } + private static string NormalizeSlashesForMsbuild(string path) + { + return path.Replace('/', '\\'); + } + internal static int AddProjectToProjectReference(ProjectRootElement root, string framework, IEnumerable refs) { int numberOfAddedReferences = 0; const string ProjectItemElementType = "ProjectReference"; ProjectItemGroupElement ig = null; - foreach (var @ref in refs) + foreach (var @ref in refs.Select((r) => NormalizeSlashesForMsbuild(r))) { if (root.HasExistingItemWithCondition(framework, @ref)) { diff --git a/test/dotnet-add-p2p.Tests/Extensions.cs b/test/dotnet-add-p2p.Tests/Extensions.cs index cbea6cca680..31070e853b9 100644 --- a/test/dotnet-add-p2p.Tests/Extensions.cs +++ b/test/dotnet-add-p2p.Tests/Extensions.cs @@ -11,43 +11,6 @@ namespace Microsoft.DotNet.Cli.Add.P2P.Tests { internal static class Extensions { - //public static int CountOccurrances(this string s, string pattern) - //{ - // int ret = 0; - // for (int i = s.IndexOf(pattern); i != -1; i = s.IndexOf(pattern, i + 1)) - // { - // ret++; - // } - - // return ret; - //} - - //public static int NumberOfLinesWith(this string s, params string[] patterns) - //{ - // int ret = 0; - // string[] lines = s.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - // foreach (var line in lines) - // { - // bool shouldCount = true; - - // foreach (var p in patterns) - // { - // if (!line.Contains(p)) - // { - // shouldCount = false; - // break; - // } - // } - - // if (shouldCount) - // { - // ret++; - // } - // } - - // return ret; - //} - public static int NumberOfItemGroupsWithConditionContaining(this ProjectRootElement root, string patternInCondition) { return root.ItemGroups.Count((ig) => ig.Condition.Contains(patternInCondition)); diff --git a/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs b/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs index e3155b059a5..e9cb2dc2c8c 100644 --- a/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs +++ b/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs @@ -64,7 +64,6 @@ public void WhenHelpOptionIsPassedItPrintsUsage(string helpArg) [InlineData("ihave?inv@lid/char\\acters")] public void WhenNonExistingProjectIsPassedItPrintsErrorAndUsage(string projName) { - string testRoot = NewDir().Path; var setup = Setup(); var cmd = new AddP2PCommand() @@ -99,7 +98,7 @@ public void WhenMoreThanOneProjectExistsInTheDirectoryItPrintsErrorAndUsage() var cmd = new AddP2PCommand() .WithWorkingDirectory(Path.Combine(setup.TestRoot, "MoreThanOne")) - .Execute($"\"{setup.ValidRefCsprojRelPath}\""); + .Execute($"\"{setup.ValidRefCsprojRelToOtherProjPath}\""); cmd.ExitCode.Should().NotBe(0); cmd.StdErr.Should().Contain("more than one"); cmd.StdOut.Should().Contain("Usage"); @@ -550,28 +549,80 @@ public void WhenPassedMultipleRefsAndOneOfthemDoesNotExistItCancelsWholeOperatio lib.CsProjContent().Should().BeEquivalentTo(contentBefore); } - [Fact(Skip = "Not finished")] + [Fact] public void WhenPassedReferenceDoesNotExistAndForceSwitchIsPassedItAddsIt() { - throw new NotImplementedException(); + var lib = NewLib(); + const string nonExisting = "IDoNotExist.csproj"; + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"--force \"{nonExisting}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + cmd.StdErr.Should().BeEmpty(); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(nonExisting).Should().Be(1); } - [Fact(Skip = "Not finished")] + [Fact] public void WhenPassedReferenceIsUsingSlashesItNormalizesItToBackslashes() { - throw new NotImplementedException(); + var lib = NewLib(); + var setup = Setup(); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"--force \"{setup.ValidRefCsprojPath.Replace('\\', '/')}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + cmd.StdErr.Should().BeEmpty(); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(setup.ValidRefCsprojPath.Replace('/', '\\')).Should().Be(1); } - [Fact(Skip = "Not finished")] - public void WhenPassedRefIsUsingBackslashesItDoesntNormalizeIt() + [Fact] + public void WhenReferenceIsRelativeAndProjectIsNotInCurrentDirectoryReferencePathIsFixed() { - throw new NotImplementedException(); + var setup = Setup(); + var proj = new ProjDir(setup.LibDir); + + int noCondBefore = proj.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(setup.LibCsprojPath) + .Execute($"\"{setup.ValidRefCsprojRelPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + cmd.StdErr.Should().BeEmpty(); + var csproj = proj.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(setup.ValidRefCsprojRelToOtherProjPath).Should().Be(1); } - [Fact(Skip = "Not finished")] - public void WhenReferenceIsRelativeAndProjectIsNotInCurrentDirectoryReferencePathIsFixed() + [Fact] + public void WhenReferenceIsRelativeAndProjectIsNotInCurrentDirectoryAndForceSwitchIsPassedItDoesNotChangeIt() { - throw new NotImplementedException(); + var setup = Setup(); + var proj = new ProjDir(setup.LibDir); + + int noCondBefore = proj.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(setup.LibCsprojPath) + .Execute($"--force \"{setup.ValidRefCsprojRelPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + cmd.StdErr.Should().BeEmpty(); + var csproj = proj.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(setup.ValidRefCsprojRelPath).Should().Be(1); } } } \ No newline at end of file diff --git a/test/dotnet-add-p2p.Tests/TestSetup.cs b/test/dotnet-add-p2p.Tests/TestSetup.cs index 32703702d2d..be7dda37868 100644 --- a/test/dotnet-add-p2p.Tests/TestSetup.cs +++ b/test/dotnet-add-p2p.Tests/TestSetup.cs @@ -14,20 +14,18 @@ internal class TestSetup public string TestRoot { get; private set; } - private const string ValidRef = "ValidRef"; public string ValidRefCsprojName => $"{ValidRef}.csproj"; - public string ValidRefCsprojPath => Path.Combine(TestRoot, ValidRef, ValidRefCsprojName); - public string ValidRefCsprojRelPath => Path.Combine("..", ValidRef, ValidRefCsprojName); - + public string ValidRefCsprojRelPath => Path.Combine(ValidRef, ValidRefCsprojName); + public string ValidRefCsprojPath => Path.Combine(TestRoot, ValidRefCsprojRelPath); + public string ValidRefCsprojRelToOtherProjPath => Path.Combine("..", ValidRefCsprojRelPath); private const string Lib = "Lib"; + public string LibDir => Path.Combine(TestRoot, Lib); public string LibCsprojName => $"{Lib}.csproj"; public string LibCsprojPath => Path.Combine(TestRoot, Lib, LibCsprojName); public string LibCsprojRelPath => Path.Combine("..", Lib, LibCsprojName); - - public TestSetup(string testRoot) { TestRoot = testRoot;