diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/global.json b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/global.json new file mode 100644 index 000000000..2b2293b26 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/global.json @@ -0,0 +1,4 @@ + +{ + "projects": [ "src", "src with spaces", "src without projects" ] +} \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/Program.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/Program.cs new file mode 100644 index 000000000..db2c801fd --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/Program.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace TestApp +{ + public class Program + { + public static int Main(string[] args) + { + Console.WriteLine("This string came from ProjectJ"); + string helperStr = TestLibrary.ProjectI.GetMessage(); + Console.WriteLine(helperStr); + return 0; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/project.json b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/project.json new file mode 100644 index 000000000..7952dd345 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src with spaces/ProjectJ/project.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "ProjectI": { + "target": "project", + "version": "1.0.0-*" + }, + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.1" + } + }, + "frameworks": { + "netcoreapp1.0": {} + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/Program.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/Program.cs new file mode 100644 index 000000000..27e455e98 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/Program.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace TestApp +{ + public class Program + { + public static int Main(string[] args) + { + Console.WriteLine("This string came from ProjectH"); + string helperStr = TestLibrary.ProjectI.GetMessage(); + Console.WriteLine(helperStr); + return 0; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/project.json b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/project.json new file mode 100644 index 000000000..7952dd345 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectH/project.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "ProjectI": { + "target": "project", + "version": "1.0.0-*" + }, + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.1" + } + }, + "frameworks": { + "netcoreapp1.0": {} + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/Helper.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/Helper.cs new file mode 100644 index 000000000..d92100c45 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/Helper.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace TestLibrary +{ + public static class ProjectI + { + public static string GetMessage() + { + return "This string came from ProjectI"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/project.json b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/project.json new file mode 100644 index 000000000..48bc772d8 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectsWithGlobalJson/src/ProjectI/project.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.5": {} + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs index 183f1168e..bc87035ef 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs @@ -269,7 +269,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration return projects; } - private static List GetGlobalPaths(string rootPath) + public static List GetGlobalPaths(string rootPath) { var paths = new List(); diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs index cbdb2b6a6..dc7e9b1c8 100644 --- a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs +++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.Build.Construction; using Microsoft.DotNet.ProjectJsonMigration; using Microsoft.DotNet.ProjectModel; @@ -46,7 +47,7 @@ namespace Microsoft.DotNet.Tools.Migrate Console.WriteLine($"Migrating project {project}.."); var projectDirectory = Path.GetDirectoryName(project); var outputDirectory = projectDirectory; - var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, msBuildTemplate, _xprojFilePath); + var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, msBuildTemplate.DeepClone(), _xprojFilePath); new ProjectMigrator().Migrate(migrationSettings, _skipProjectReferences); } @@ -55,22 +56,33 @@ namespace Microsoft.DotNet.Tools.Migrate private IEnumerable GetProjectsToMigrate(string projectArg) { - if (projectArg.EndsWith(Project.FileName)) + IEnumerable projects = null; + + if (projectArg.EndsWith(Project.FileName, StringComparison.OrdinalIgnoreCase)) { - yield return GetProjectJsonPath(projectArg); + projects = Enumerable.Repeat(projectArg, 1); + } + else if (projectArg.EndsWith(GlobalSettings.FileName, StringComparison.OrdinalIgnoreCase)) + { + projects = GetProjectsFromGlobalJson(projectArg); } else if (Directory.Exists(projectArg)) { - var projects = Directory.EnumerateFiles(projectArg, Project.FileName, SearchOption.AllDirectories); - - foreach(var project in projects) - { - yield return GetProjectJsonPath(project); - } + projects = Directory.EnumerateFiles(projectArg, Project.FileName, SearchOption.AllDirectories); } else { - throw new Exception($"Invalid project argument - '{projectArg}' is not a project.json file and a directory named '{projectArg}' doesn't exist."); + throw new Exception($"Invalid project argument - '{projectArg}' is not a project.json or a global.json file and a directory named '{projectArg}' doesn't exist."); + } + + if (!projects.Any()) + { + throw new Exception($"Invalid project argument - Unable to find any projects in global.json or directory '{projectArg}'"); + } + + foreach(var project in projects) + { + yield return GetProjectJsonPath(project); } } @@ -93,5 +105,35 @@ namespace Microsoft.DotNet.Tools.Migrate throw new Exception($"Unable to find project file at {projectJson}"); } + + private IEnumerable GetProjectsFromGlobalJson(string globalJson) + { + if (!File.Exists(globalJson)) + { + throw new Exception($"Unable to find global settings file at {globalJson}"); + } + + var searchPaths = ProjectDependencyFinder.GetGlobalPaths(Path.GetDirectoryName(globalJson)); + + foreach (var searchPath in searchPaths) + { + var directory = new DirectoryInfo(searchPath); + + if (!directory.Exists) + { + continue; + } + + foreach (var projectDirectory in directory.EnumerateDirectories()) + { + var projectFilePath = Path.Combine(projectDirectory.FullName, "project.json"); + + if (File.Exists(projectFilePath)) + { + yield return projectFilePath; + } + } + } + } } } diff --git a/src/dotnet/commands/dotnet-migrate/Program.cs b/src/dotnet/commands/dotnet-migrate/Program.cs index dad8653e1..3cad76637 100644 --- a/src/dotnet/commands/dotnet-migrate/Program.cs +++ b/src/dotnet/commands/dotnet-migrate/Program.cs @@ -26,10 +26,14 @@ namespace Microsoft.DotNet.Tools.Migrate app.HandleResponseFiles = true; app.HelpOption("-h|--help"); - CommandArgument projectArgument = app.Argument("", - "The path to project.json file or a directory to migrate." + - " If a directory is specified, then it will recursively search for project.json files to migrate." + - " Defaults to current directory if nothing is specified."); + CommandArgument projectArgument = app.Argument("", + "The path to " + Environment.NewLine + + " - a project.json file to migrate." + Environment.NewLine + + "or" + Environment.NewLine + + " - a global.json file, it will migrate the folders specified in global.json." + Environment.NewLine + + "or" + Environment.NewLine + + " - a directory to migrate, it will recursively search for project.json files to migrate." + Environment.NewLine + + "Defaults to current directory if nothing is specified."); CommandOption template = app.Option("-t|--template-file", "Base MSBuild template to use for migrated app. The default is the project included in dotnet new -t msbuild", CommandOptionType.SingleValue); CommandOption sdkVersion = app.Option("-v|--sdk-package-version", "The version of the sdk package that will be referenced in the migrated app. The default is the version of the sdk in dotnet new -t msbuild", CommandOptionType.SingleValue); diff --git a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs index d7eae4535..6c402bf1f 100644 --- a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs +++ b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs @@ -153,7 +153,7 @@ namespace Microsoft.DotNet.Migration.Tests var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppDependencyGraph", callingMethod: $"{projectName}.RefsTest").Path; - MigrateProject(Path.Combine(projectDirectory, projectName)); + MigrateProject(new [] { Path.Combine(projectDirectory, projectName) }); string[] migratedProjects = expectedProjects.Split(new char[] { ',' }); VerifyMigration(migratedProjects, projectDirectory); @@ -170,7 +170,7 @@ namespace Microsoft.DotNet.Migration.Tests var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppDependencyGraph", callingMethod: $"{projectName}.SkipRefsTest").Path; - MigrateCommand.Run(new [] { Path.Combine(projectDirectory, projectName), "--skip-project-references" }).Should().Be(0); + MigrateProject(new [] { Path.Combine(projectDirectory, projectName), "--skip-project-references" }); VerifyMigration(Enumerable.Repeat(projectName, 1), projectDirectory); } @@ -184,14 +184,14 @@ namespace Microsoft.DotNet.Migration.Tests if (skipRefs) { - MigrateCommand.Run(new [] { projectDirectory, "--skip-project-references" }).Should().Be(0); + MigrateProject(new [] { projectDirectory, "--skip-project-references" }); } else { - MigrateCommand.Run(new [] { projectDirectory }).Should().Be(0); + MigrateProject(new [] { projectDirectory }); } - string[] migratedProjects = new string[] { "ProjectA", "ProjectB", "ProjectC", "ProjectD", "ProjectE", "ProjectF", "ProjectG" }; + string[] migratedProjects = new string[] { "ProjectA", "ProjectB", "ProjectC", "ProjectD", "ProjectE", "ProjectF", "ProjectG", "ProjectH", "ProjectI", "ProjectJ" }; VerifyMigration(migratedProjects, projectDirectory); } @@ -201,7 +201,7 @@ namespace Microsoft.DotNet.Migration.Tests var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppDependencyGraph").Path; var project = Path.Combine(projectDirectory, "ProjectA", "project.json"); - MigrateCommand.Run(new [] { project }).Should().Be(0); + MigrateProject(new [] { project }); string[] migratedProjects = new string[] { "ProjectA", "ProjectB", "ProjectC", "ProjectD", "ProjectE" }; VerifyMigration(migratedProjects, projectDirectory); @@ -213,8 +213,13 @@ namespace Microsoft.DotNet.Migration.Tests { var assetsDir = TestAssetsManager.CreateTestInstance("TestAppDependencyGraph").WithLockFiles().Path; var projectDirectory = Path.Combine(assetsDir, "ProjectF"); - var depProjects = new List() { Path.Combine(assetsDir, "ProjectG") }; - var outputComparisonData = BuildProjectJsonMigrateBuildMSBuild(projectDirectory, "ProjectF", depProjects); + var restoreDirectories = new string[] + { + projectDirectory, + Path.Combine(assetsDir, "ProjectG") + }; + + var outputComparisonData = BuildProjectJsonMigrateBuildMSBuild(projectDirectory, "ProjectF", new [] { projectDirectory }, restoreDirectories); var outputsIdentical = outputComparisonData.ProjectJsonBuildOutputs .SetEquals(outputComparisonData.MSBuildBuildOutputs); @@ -228,6 +233,42 @@ namespace Microsoft.DotNet.Migration.Tests VerifyAllMSBuildOutputsRunnable(projectDirectory); } + [Theory] + [InlineData("src", "ProjectH")] + [InlineData("src with spaces", "ProjectJ")] + public void It_migrates_and_builds_projects_in_global_json(string path, string projectName) + { + var assetsDir = TestAssetsManager.CreateTestInstance(Path.Combine("TestAppDependencyGraph", "ProjectsWithGlobalJson"), + callingMethod: $"ProjectsWithGlobalJson.{projectName}") + .WithLockFiles().Path; + var globalJson = Path.Combine(assetsDir, "global.json"); + + var restoreDirectories = new string[] + { + Path.Combine(assetsDir, "src", "ProjectH"), + Path.Combine(assetsDir, "src", "ProjectI"), + Path.Combine(assetsDir, "src with spaces", "ProjectJ") + }; + + var projectDirectory = Path.Combine(assetsDir, path, projectName); + + var outputComparisonData = BuildProjectJsonMigrateBuildMSBuild(projectDirectory, + projectName, + new [] { globalJson }, + restoreDirectories); + + var outputsIdentical = outputComparisonData.ProjectJsonBuildOutputs + .SetEquals(outputComparisonData.MSBuildBuildOutputs); + + if (!outputsIdentical) + { + OutputDiagnostics(outputComparisonData); + } + + outputsIdentical.Should().BeTrue(); + VerifyAllMSBuildOutputsRunnable(projectDirectory); + } + private void VerifyMigration(IEnumerable expectedProjects, string rootDir) { var migratedProjects = Directory.EnumerateFiles(rootDir, "project.migrated.json", SearchOption.AllDirectories) @@ -242,7 +283,7 @@ namespace Microsoft.DotNet.Migration.Tests Restore(projectDirectory); var outputComparisonData = - BuildProjectJsonMigrateBuildMSBuild(projectDirectory); + BuildProjectJsonMigrateBuildMSBuild(projectDirectory, Path.GetFileNameWithoutExtension(projectDirectory)); return outputComparisonData; } @@ -255,7 +296,7 @@ namespace Microsoft.DotNet.Migration.Tests foreach (var dll in runnableDlls) { - new TestCommand("dotnet").ExecuteWithCapturedOutput(dll).Should().Pass(); + new TestCommand("dotnet").ExecuteWithCapturedOutput($"\"{dll}\"").Should().Pass(); } } @@ -274,22 +315,32 @@ namespace Microsoft.DotNet.Migration.Tests } } - private MigratedBuildComparisonData BuildProjectJsonMigrateBuildMSBuild(string projectDirectory, string projectName, List additonalRestoreDirectories = null) + private MigratedBuildComparisonData BuildProjectJsonMigrateBuildMSBuild(string projectDirectory, + string projectName) + { + return BuildProjectJsonMigrateBuildMSBuild(projectDirectory, projectName, + new [] { projectDirectory }, new [] { projectDirectory }); + } + + private MigratedBuildComparisonData BuildProjectJsonMigrateBuildMSBuild(string projectDirectory, + string projectName, + string[] migrateArgs, + string[] restoreDirectories) { BuildProjectJson(projectDirectory); var projectJsonBuildOutputs = new HashSet(CollectBuildOutputs(projectDirectory)); CleanBinObj(projectDirectory); // Remove lock file for migration - File.Delete(Path.Combine(projectDirectory, "project.lock.json")); - - MigrateProject(projectDirectory); + foreach(var dir in restoreDirectories) + { + File.Delete(Path.Combine(dir, "project.lock.json")); + } + MigrateProject(migrateArgs); DeleteXproj(projectDirectory); - Restore3(projectDirectory, projectName); - additonalRestoreDirectories = additonalRestoreDirectories ?? new List(); - foreach(var dir in additonalRestoreDirectories) + foreach(var dir in restoreDirectories) { Restore3(dir); } @@ -331,10 +382,10 @@ namespace Microsoft.DotNet.Migration.Tests result.Should().Pass(); } - private void MigrateProject(string projectDirectory) + private void MigrateProject(string[] migrateArgs) { var result = - MigrateCommand.Run(new [] { projectDirectory }); + MigrateCommand.Run(migrateArgs); result.Should().Be(0); } @@ -372,7 +423,7 @@ namespace Microsoft.DotNet.Migration.Tests command.Execute() .Should() .Pass(); - } + } } private string BuildMSBuild(string projectDirectory, string projectName, string configuration="Debug")