diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/Helper.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/Helper.cs new file mode 100644 index 000000000..5c17a2e36 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/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 ProjectE + { + public static string GetMessage() + { + return "This string came from ProjectF"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/project.json.1 b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/project.json.1 new file mode 100644 index 000000000..d2b7a9a21 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectF/project.json.1 @@ -0,0 +1,22 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "ProjectG": { + "target": "project", + "version": "1.0.0-*" + }, + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.5": {} + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/Helper.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/Helper.cs new file mode 100644 index 000000000..a7d766b09 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/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 ProjectE + { + public static string GetMessage() + { + return "This string came from ProjectG"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/project.json.1 b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/project.json.1 new file mode 100644 index 000000000..48bc772d8 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectG/project.json.1 @@ -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/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs index 4de5c1866..a0a949f6a 100644 --- a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs +++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; using Microsoft.Build.Construction; using Microsoft.DotNet.Cli; @@ -13,17 +14,17 @@ namespace Microsoft.DotNet.Tools.Migrate public partial class MigrateCommand { private readonly string _templateFile; - private readonly string _projectJson; + private readonly string _projectArg; private readonly string _sdkVersion; private readonly string _xprojFilePath; private readonly bool _skipProjectReferences; private readonly TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject; - public MigrateCommand(string templateFile, string projectJson, string sdkVersion, string xprojFilePath, bool skipProjectReferences) + public MigrateCommand(string templateFile, string projectArg, string sdkVersion, string xprojFilePath, bool skipProjectReferences) { _templateFile = templateFile; - _projectJson = projectJson; + _projectArg = projectArg ?? Directory.GetCurrentDirectory(); _sdkVersion = sdkVersion; _xprojFilePath = xprojFilePath; _skipProjectReferences = skipProjectReferences; @@ -32,24 +33,47 @@ namespace Microsoft.DotNet.Tools.Migrate public int Execute() { - var project = GetProjectJsonPath(_projectJson) ?? GetProjectJsonPath(Directory.GetCurrentDirectory()); - EnsureNotNull(project, "Unable to find project.json"); - var projectDirectory = Path.GetDirectoryName(project); + var projectsToMigrate = GetProjectsToMigrate(_projectArg); var msBuildTemplate = _templateFile != null ? ProjectRootElement.TryOpen(_templateFile) : _temporaryDotnetNewProject.MSBuildProject; - var outputDirectory = projectDirectory; - var sdkVersion = _sdkVersion ?? new ProjectJsonParser(_temporaryDotnetNewProject.ProjectJson).SdkPackageVersion; EnsureNotNull(sdkVersion, "Null Sdk Version"); - var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, msBuildTemplate, _xprojFilePath); - new ProjectMigrator().Migrate(migrationSettings, _skipProjectReferences); + foreach (var project in projectsToMigrate) + { + Console.WriteLine($"Migrating project {project}.."); + var projectDirectory = Path.GetDirectoryName(project); + var outputDirectory = projectDirectory; + var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, msBuildTemplate, _xprojFilePath); + new ProjectMigrator().Migrate(migrationSettings, _skipProjectReferences); + } return 0; } + private IEnumerable GetProjectsToMigrate(string projectArg) + { + if (projectArg.EndsWith(Project.FileName)) + { + yield return GetProjectJsonPath(projectArg); + } + else if (Directory.Exists(projectArg)) + { + var projects = Directory.EnumerateFiles(projectArg, Project.FileName, SearchOption.AllDirectories); + + foreach(var project in projects) + { + yield return GetProjectJsonPath(project); + } + } + else + { + throw new Exception($"Invalid project argument - '{projectArg}' is not a project.json file and a directory named '{projectArg}' doesn't exist."); + } + } + private void EnsureNotNull(string variable, string message) { if (variable == null) @@ -60,11 +84,6 @@ namespace Microsoft.DotNet.Tools.Migrate private string GetProjectJsonPath(string projectJson) { - if (projectJson == null) - { - return null; - } - projectJson = ProjectPathHelper.NormalizeProjectFilePath(projectJson); if (File.Exists(projectJson)) diff --git a/src/dotnet/commands/dotnet-migrate/Program.cs b/src/dotnet/commands/dotnet-migrate/Program.cs index 706edd85c..32b679c43 100644 --- a/src/dotnet/commands/dotnet-migrate/Program.cs +++ b/src/dotnet/commands/dotnet-migrate/Program.cs @@ -20,8 +20,12 @@ 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."); + 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 project = app.Option("-p|--project", "The path to the project to run (defaults to the current directory). Can be a path to a project.json or a project directory", 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); CommandOption xprojFile = app.Option("-x|--xproj-file", "The path to the xproj file to use. Required when there is more than one xproj in a project directory.", CommandOptionType.SingleValue); CommandOption skipProjectReferences = app.Option("-s|--skip-project-references", "Skip migrating project references. By default project references are migrated recursively", CommandOptionType.BoolValue); @@ -30,7 +34,7 @@ namespace Microsoft.DotNet.Tools.Migrate { MigrateCommand migrateCommand = new MigrateCommand( template.Value(), - project.Value(), + projectArgument.Value, sdkVersion.Value(), xprojFile.Value(), skipProjectReferences.BoolValue.HasValue ? skipProjectReferences.BoolValue.Value : false); diff --git a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs index 08f9a657f..9d02e67e2 100644 --- a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs +++ b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs @@ -129,12 +129,7 @@ namespace Microsoft.DotNet.Migration.Tests MigrateProject(Path.Combine(projectDirectory, projectName)); string[] migratedProjects = expectedProjects.Split(new char[] { ',' }); - foreach(var migratedProject in migratedProjects) - { - var dirInfo = new DirectoryInfo(Path.Combine(projectDirectory, migratedProject)); - var csproj = $"{migratedProject}.csproj"; - dirInfo.Should().HaveFile(csproj); - } + VerifyMigration(migratedProjects, projectDirectory); } [Theory] @@ -150,13 +145,45 @@ namespace Microsoft.DotNet.Migration.Tests FixUpProjectJsons(projectDirectory); - MigrateCommand.Run(new [] { "-p", Path.Combine(projectDirectory, projectName), "--skip-project-references" }).Should().Be(0); + MigrateCommand.Run(new [] { Path.Combine(projectDirectory, projectName), "--skip-project-references" }).Should().Be(0); - var migratedProjects = Directory.EnumerateFiles(projectDirectory, "*.csproj", SearchOption.AllDirectories); - migratedProjects.Count().Should().Be(1, "Only the root project must be migrated"); + VerifyMigration(Enumerable.Repeat(projectName, 1), projectDirectory); + } - var migratedProject = Path.GetFileName(migratedProjects.First()); - migratedProject.Should().Be($"{projectName}.csproj"); + [Theory] + [InlineData(true)] + [InlineData(false)] + public void It_migrates_all_projects_in_given_directory(bool skipRefs) + { + var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppDependencyGraph", callingMethod: $"MigrateDirectory.SkipRefs.{skipRefs}").Path; + + FixUpProjectJsons(projectDirectory); + + if (skipRefs) + { + MigrateCommand.Run(new [] { projectDirectory, "--skip-project-references" }).Should().Be(0); + } + else + { + MigrateCommand.Run(new [] { projectDirectory }).Should().Be(0); + } + + string[] migratedProjects = new string[] { "ProjectA", "ProjectB", "ProjectC", "ProjectD", "ProjectE", "ProjectF", "ProjectG" }; + VerifyMigration(migratedProjects, projectDirectory); + } + + [Fact] + public void It_migrates_given_project_json() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppDependencyGraph").Path; + + FixUpProjectJsons(projectDirectory); + + var project = Path.Combine(projectDirectory, "ProjectA", "project.json"); + MigrateCommand.Run(new [] { project }).Should().Be(0); + + string[] migratedProjects = new string[] { "ProjectA", "ProjectB", "ProjectC", "ProjectD", "ProjectE" }; + VerifyMigration(migratedProjects, projectDirectory); } private void FixUpProjectJsons(string projectDirectory) @@ -170,6 +197,13 @@ namespace Microsoft.DotNet.Migration.Tests } } + private void VerifyMigration(IEnumerable expectedProjects, string rootDir) + { + var migratedProjects = Directory.EnumerateFiles(rootDir, "*.csproj", SearchOption.AllDirectories) + .Select(s => Path.GetFileNameWithoutExtension(s)); + migratedProjects.Should().BeEquivalentTo(expectedProjects); + } + private MigratedBuildComparisonData GetDotnetNewComparisonData(string projectDirectory, string dotnetNewType) { DotnetNew(projectDirectory, dotnetNewType); @@ -241,7 +275,7 @@ namespace Microsoft.DotNet.Migration.Tests private void MigrateProject(string projectDirectory) { var result = - MigrateCommand.Run(new [] { "-p", projectDirectory }); + MigrateCommand.Run(new [] { projectDirectory }); result.Should().Be(0); }