diff --git a/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/AppWithDepOnTool.csproj b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/AppWithDepOnTool.csproj new file mode 100644 index 000000000..2185f85a6 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/AppWithDepOnTool.csproj @@ -0,0 +1,21 @@ + + + + Exe + netcoreapp1.0 + random-name + + + + + + + + + + + 1.0.0 + + + + diff --git a/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/NuGet.Config b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/NuGet.Config new file mode 100644 index 000000000..97af9d3d0 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + diff --git a/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/Program.cs b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/Program.cs new file mode 100644 index 000000000..846370cce --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/AppWithDepOnTool/Program.cs @@ -0,0 +1,8 @@ +using System; + +class Program +{ + static void Main(string[] args) + { + } +} diff --git a/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/ToolWithRandomPackageName/Program.cs b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/ToolWithRandomPackageName/Program.cs new file mode 100644 index 000000000..a95e5a932 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/ToolWithRandomPackageName/Program.cs @@ -0,0 +1,9 @@ +using System; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello World from tool!"); + } +} diff --git a/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/ToolWithRandomPackageName/ToolWithRandomPackageName.csproj b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/ToolWithRandomPackageName/ToolWithRandomPackageName.csproj new file mode 100644 index 000000000..f7037e0c6 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/ToolWithRandomPackageName/ToolWithRandomPackageName/ToolWithRandomPackageName.csproj @@ -0,0 +1,24 @@ + + + + Exe + netcoreapp1.0 + random-name + $(GeneratedPackageId) + dotnet-randompackage + + + + + + + true + lib\$(TargetFramework) + + + + + + + + diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IProject.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IProject.cs index d0d43dc57..89d22bbbf 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IProject.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IProject.cs @@ -10,6 +10,8 @@ namespace Microsoft.DotNet.Cli.Utils { LockFile GetLockFile(); + bool TryGetLockFile(out LockFile lockFile); + IEnumerable GetTools(); string DepsJsonPath { get; } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/MSBuildProject.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/MSBuildProject.cs index a653821f6..4b6cda0e4 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/MSBuildProject.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/MSBuildProject.cs @@ -129,6 +129,29 @@ namespace Microsoft.DotNet.Cli.Utils .Result; } + public bool TryGetLockFile(out LockFile lockFile) + { + lockFile = null; + + var lockFilePath = GetLockFilePathFromProjectLockFileProperty() ?? + GetLockFilePathFromIntermediateBaseOutputPath(); + + if (lockFilePath == null) + { + return false; + } + + if (!File.Exists(lockFilePath)) + { + return false; + } + + lockFile = new LockFileFormat() + .ReadWithLock(lockFilePath) + .Result; + return true; + } + private string GetLockFilePathFromProjectLockFileProperty() { return _project diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs index bda9a4250..b4edd7583 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs @@ -127,16 +127,19 @@ namespace Microsoft.DotNet.Cli.Utils ProjectToolsCommandResolverName, toolLibraryRange.Name)); - var nuGetPathContext = NuGetPathContext.Create(project.ProjectRoot); - - var nugetPackagesRoot = nuGetPathContext.UserPackageFolder; - + var possiblePackageRoots = GetPossiblePackageRoots(project).ToList(); Reporter.Verbose.WriteLine(string.Format( LocalizableStrings.NuGetPackagesRoot, ProjectToolsCommandResolverName, - nugetPackagesRoot)); + string.Join(Environment.NewLine, possiblePackageRoots.Select((p) => $"- {p}")))); - var toolLockFile = GetToolLockFile(toolLibraryRange, nugetPackagesRoot); + string nugetPackagesRoot; + var toolLockFile = GetToolLockFile(toolLibraryRange, possiblePackageRoots, out nugetPackagesRoot); + + if (toolLockFile == null) + { + return null; + } Reporter.Verbose.WriteLine(string.Format( LocalizableStrings.FoundToolLockFile, @@ -189,19 +192,33 @@ namespace Microsoft.DotNet.Cli.Utils return commandSpec; } - private LockFile GetToolLockFile( - SingleProjectInfo toolLibrary, - string nugetPackagesRoot) + private IEnumerable GetPossiblePackageRoots(IProject project) { + if (project.TryGetLockFile(out LockFile lockFile)) + { + foreach (var packageFolder in lockFile.PackageFolders) + { + yield return packageFolder.Path; + } + } + + var nuGetPathContext = NuGetPathContext.Create(project.ProjectRoot); + yield return nuGetPathContext.UserPackageFolder; + } + + private bool TryGetToolLockFile( + SingleProjectInfo toolLibrary, + string nugetPackagesRoot, + out LockFile lockFile) + { + lockFile = null; var lockFilePath = GetToolLockFilePath(toolLibrary, nugetPackagesRoot); if (!File.Exists(lockFilePath)) { - return null; + return false; } - LockFile lockFile = null; - try { lockFile = new LockFileFormat() @@ -213,7 +230,22 @@ namespace Microsoft.DotNet.Cli.Utils throw ex; } - return lockFile; + return true; + } + + private LockFile GetToolLockFile(SingleProjectInfo toolLibrary, IEnumerable possibleNugetPackagesRoot, out string nugetPackagesRoot) + { + foreach (var packagesRoot in possibleNugetPackagesRoot) + { + if (TryGetToolLockFile(toolLibrary, packagesRoot, out LockFile lockFile)) + { + nugetPackagesRoot = packagesRoot; + return lockFile; + } + } + + nugetPackagesRoot = null; + return null; } private string GetToolLockFilePath( diff --git a/test/dotnet.Tests/MSBuild.exe b/test/dotnet.Tests/MSBuild.exe new file mode 100644 index 000000000..2b4d0f999 --- /dev/null +++ b/test/dotnet.Tests/MSBuild.exe @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/dotnet.Tests/MSBuild.exe.config b/test/dotnet.Tests/MSBuild.exe.config new file mode 100644 index 000000000..2b4d0f999 --- /dev/null +++ b/test/dotnet.Tests/MSBuild.exe.config @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/dotnet.Tests/PackagedCommandTests.cs b/test/dotnet.Tests/PackagedCommandTests.cs index 0906063d2..441eba5be 100644 --- a/test/dotnet.Tests/PackagedCommandTests.cs +++ b/test/dotnet.Tests/PackagedCommandTests.cs @@ -13,6 +13,9 @@ using Microsoft.DotNet.Tools.Test.Utilities; using Microsoft.DotNet.InternalAbstractions; using Xunit; using Xunit.Abstractions; +using Microsoft.Build.Construction; +using System.Linq; +using Microsoft.Build.Evaluation; namespace Microsoft.DotNet.Tests { @@ -138,6 +141,52 @@ namespace Microsoft.DotNet.Tests .And.HaveStdErrContaining("Version for package `dotnet-nonexistingtool` could not be resolved."); } + [Fact] + public void ItRunsToolRestoredToSpecificPackageDir() + { + var testInstance = TestAssets.Get("NonRestoredTestProjects", "ToolWithRandomPackageName") + .CreateInstance() + .WithSourceFiles(); + + var appWithDepOnToolDir = testInstance.Root.Sub("AppWithDepOnTool"); + var toolWithRandPkgNameDir = testInstance.Root.Sub("ToolWithRandomPackageName"); + var pkgsDir = testInstance.Root.CreateSubdirectory("pkgs"); + + string randomPackageName = Guid.NewGuid().ToString(); + + // TODO: This is a workround for https://github.com/dotnet/cli/issues/5020 + SetGeneratedPackageName(appWithDepOnToolDir.GetFile("AppWithDepOnTool.csproj"), + randomPackageName); + + SetGeneratedPackageName(toolWithRandPkgNameDir.GetFile("ToolWithRandomPackageName.csproj"), + randomPackageName); + + new RestoreCommand() + .WithWorkingDirectory(toolWithRandPkgNameDir) + .ExecuteWithCapturedOutput() + .Should().Pass() + .And.NotHaveStdErr(); + + new PackCommand() + .WithWorkingDirectory(toolWithRandPkgNameDir) + .ExecuteWithCapturedOutput($"-o \"{pkgsDir.FullName}\"") + .Should().Pass() + .And.NotHaveStdErr(); + + new RestoreCommand() + .WithWorkingDirectory(appWithDepOnToolDir) + .ExecuteWithCapturedOutput($"--packages \"{pkgsDir.FullName}\"") + .Should().Pass() + .And.NotHaveStdErr(); + + new TestCommand("dotnet") + .WithWorkingDirectory(appWithDepOnToolDir) + .ExecuteWithCapturedOutput("randompackage") + .Should().Pass() + .And.HaveStdOutContaining("Hello World from tool!") + .And.NotHaveStdErr(); + } + // need conditional theories so we can skip on non-Windows //[Theory(Skip="https://github.com/dotnet/cli/issues/4514")] //[MemberData("DependencyToolArguments")] @@ -294,6 +343,14 @@ namespace Microsoft.DotNet.Tests stopWatch.ElapsedMilliseconds.Should().BeGreaterThan(1000, "Because dotnet should respect the NuGet lock"); } + private void SetGeneratedPackageName(FileInfo project, string packageName) + { + const string propertyName = "GeneratedPackageId"; + var p = ProjectRootElement.Open(project.FullName, new ProjectCollection(), true); + p.AddProperty(propertyName, packageName); + p.Save(); + } + class HelloCommand : TestCommand { public HelloCommand()