diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/Program.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/Program.cs new file mode 100644 index 000000000..7b3022c33 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/Program.cs @@ -0,0 +1,17 @@ +// 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 ProjectA"); + return 100; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/project.json.1 b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/project.json.1 new file mode 100644 index 000000000..de7f186e5 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectA/project.json.1 @@ -0,0 +1,34 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "ProjectB": { + "target": "project", + "version": "1.0.0-*" + }, + "ProjectC": { + "target": "project", + "version": "1.0.0-*" + }, + "Microsoft.NETCore.App": "1.0.1" + }, + "frameworks": { + "netcoreapp1.0": {} + }, + "runtimes": { + "win7-x64": {}, + "win7-x86": {}, + "osx.10.10-x64": {}, + "osx.10.11-x64": {}, + "ubuntu.14.04-x64": {}, + "ubuntu.16.04-x64": {}, + "centos.7-x64": {}, + "rhel.7.2-x64": {}, + "debian.8-x64": {}, + "fedora.23-x64": {}, + "opensuse.13.2-x64": {} + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/Helper.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/Helper.cs new file mode 100644 index 000000000..5a986d891 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/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 ProjectB + { + public static string GetMessage() + { + return "This string came from ProjectB"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/project.json.1 b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/project.json.1 new file mode 100644 index 000000000..1eb055cca --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectB/project.json.1 @@ -0,0 +1,26 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "ProjectC": { + "target": "project", + "version": "1.0.0-*" + }, + "ProjectD": { + "target": "project", + "version": "1.0.0-*" + }, + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.5": {} + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/Helper.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/Helper.cs new file mode 100644 index 000000000..317f57fc2 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/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 ProjectC + { + public static string GetMessage() + { + return "This string came from ProjectC"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/ProjectC.xproj b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/ProjectC.xproj new file mode 100644 index 000000000..75a8bd8d7 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/ProjectC.xproj @@ -0,0 +1,23 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 084222f1-7909-48f4-81e8-a97398b26b1c + ProjectC + obj + bin + v4.6 + + + 2.0 + + + + + + + \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/project.json.1 b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/project.json.1 new file mode 100644 index 000000000..ef6bf69c3 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectC/project.json.1 @@ -0,0 +1,32 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "ProjectD": { + "target": "project", + "version": "1.0.0-*" + }, + "ProjectE": { + "target": "project", + "version": "1.0.0-*" + }, + "ClassLibrary1": { + "target": "project" + }, + "ClassLibrary2": { + "target": "project" + }, + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.5": {} + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/Helper.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/Helper.cs new file mode 100644 index 000000000..0c0422913 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/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 ProjectD + { + public static string GetMessage() + { + return "This string came from ProjectD"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/project.json.1 b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/project.json.1 new file mode 100644 index 000000000..48bc772d8 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectD/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/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/.noautobuild b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/Helper.cs b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/Helper.cs new file mode 100644 index 000000000..b91a23164 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/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 ProjectE"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/ProjectE.xproj b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/ProjectE.xproj new file mode 100644 index 000000000..3e583c67f --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/ProjectE.xproj @@ -0,0 +1,23 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 7fb8f138-ffb0-4eec-af9e-2e6ff9979593 + ProjectE + obj + bin + v4.6 + + + 2.0 + + + + + + + \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/project.json.1 b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/project.json.1 new file mode 100644 index 000000000..fae1dbe77 --- /dev/null +++ b/TestAssets/TestProjects/TestAppDependencyGraph/ProjectE/project.json.1 @@ -0,0 +1,24 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "ClassLibrary2": { + "target": "project" + }, + "ClassLibrary3": { + "target": "project" + }, + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.5": {} + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyComparer.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyComparer.cs new file mode 100644 index 000000000..afb2a8578 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyComparer.cs @@ -0,0 +1,23 @@ +// 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.Collections.Generic; + +namespace Microsoft.DotNet.ProjectJsonMigration +{ + public class ProjectDependencyComparer : IEqualityComparer + { + public bool Equals(ProjectDependency one, ProjectDependency two) + { + return StringComparer.OrdinalIgnoreCase + .Equals(one.ProjectFilePath, two.ProjectFilePath); + } + + public int GetHashCode(ProjectDependency item) + { + return StringComparer.OrdinalIgnoreCase + .GetHashCode(item.ProjectFilePath); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs index 1c4280172..0fcd24896 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectDependencyFinder.cs @@ -3,18 +3,45 @@ using System; using System.Collections.Generic; +using Microsoft.Build.Construction; using Microsoft.DotNet.ProjectModel; using System.Linq; using System.IO; using Newtonsoft.Json.Linq; using Microsoft.DotNet.ProjectModel.Compilation; using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.DotNet.Tools.Common; namespace Microsoft.DotNet.ProjectJsonMigration { public class ProjectDependencyFinder { - public IEnumerable ResolveProjectDependencies(ProjectContext projectContext, HashSet preResolvedProjects=null) + public IEnumerable ResolveProjectDependencies(string projectDir, string xprojFile = null) + { + var projectContexts = ProjectContext.CreateContextForEachFramework(projectDir); + xprojFile = xprojFile ?? FindXprojFile(projectDir); + + ProjectRootElement xproj = null; + if (xprojFile != null) + { + xproj = ProjectRootElement.Open(xprojFile); + } + + return ResolveProjectDependencies(projectContexts, ResolveXProjProjectDependencyNames(xproj)); + } + + public IEnumerable ResolveProjectDependencies(IEnumerable projectContexts, IEnumerable preResolvedProjects=null) + { + foreach(var projectContext in projectContexts) + { + foreach(var projectDependency in ResolveProjectDependencies(projectContext, preResolvedProjects)) + { + yield return projectDependency; + } + } + } + + public IEnumerable ResolveProjectDependencies(ProjectContext projectContext, IEnumerable preResolvedProjects=null) { preResolvedProjects = preResolvedProjects ?? new HashSet(); @@ -48,14 +75,49 @@ namespace Microsoft.DotNet.ProjectJsonMigration return projectDependencies; } + private IEnumerable ResolveXProjProjectDependencyNames(ProjectRootElement xproj) + { + var xprojDependencies = ResolveXProjProjectDependencies(xproj).SelectMany(r => r.Includes()); + return new HashSet(xprojDependencies.Select(p => Path.GetFileNameWithoutExtension( + PathUtility.GetPathWithDirectorySeparator(p)))); + } + + internal IEnumerable ResolveXProjProjectDependencies(ProjectRootElement xproj) + { + if (xproj == null) + { + MigrationTrace.Instance.WriteLine($"{nameof(ProjectDependencyFinder)}: No xproj file given."); + return Enumerable.Empty(); + } + + return xproj.Items + .Where(i => i.ItemType == "ProjectReference") + .Where(p => p.Includes().Any( + include => string.Equals(Path.GetExtension(include), ".csproj", StringComparison.OrdinalIgnoreCase))); + } + + internal string FindXprojFile(string projectDirectory) + { + var allXprojFiles = Directory.EnumerateFiles(projectDirectory, "*.xproj", SearchOption.TopDirectoryOnly); + + if (allXprojFiles.Count() > 1) + { + MigrationErrorCodes + .MIGRATE1017($"Multiple xproj files found in {projectDirectory}, please specify which to use") + .Throw(); + } + + return allXprojFiles.FirstOrDefault(); + } + private Dictionary FindPossibleProjectDependencies(string projectJsonFilePath) { - var projectDirectory = Path.GetDirectoryName(projectJsonFilePath); + var projectRootDirectory = GetRootFromProjectJson(projectJsonFilePath); var projectSearchPaths = new List(); - projectSearchPaths.Add(projectDirectory); + projectSearchPaths.Add(projectRootDirectory); - var globalPaths = GetGlobalPaths(projectDirectory); + var globalPaths = GetGlobalPaths(projectRootDirectory); projectSearchPaths = projectSearchPaths.Union(globalPaths).ToList(); var projects = new Dictionary(StringComparer.Ordinal); @@ -93,6 +155,31 @@ namespace Microsoft.DotNet.ProjectJsonMigration return projects; } + /// + /// Finds the parent directory of the project.json. + /// + /// Full path to project.json. + private static string GetRootFromProjectJson(string projectJsonPath) + { + if (!string.IsNullOrEmpty(projectJsonPath)) + { + var file = new FileInfo(projectJsonPath); + + // If for some reason we are at the root of the drive this will be null + // Use the file directory instead. + if (file.Directory.Parent == null) + { + return file.Directory.FullName; + } + else + { + return file.Directory.Parent.FullName; + } + } + + return projectJsonPath; + } + /// /// Create the list of potential projects from the search paths. /// diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs index 6633e0459..8f1385f67 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using Microsoft.Build.Construction; using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.DotNet.Cli; using System.Linq; using System.IO; using Microsoft.DotNet.ProjectJsonMigration.Rules; @@ -22,6 +24,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration // - Migrating Deprecated project.jsons private readonly IMigrationRule _ruleSet; + private readonly ProjectDependencyFinder _projectDependencyFinder = new ProjectDependencyFinder(); public ProjectMigrator() : this(new DefaultMigrationRuleSet()) { } @@ -30,9 +33,73 @@ namespace Microsoft.DotNet.ProjectJsonMigration _ruleSet = ruleSet; } - public void Migrate(MigrationSettings migrationSettings) + public void Migrate(MigrationSettings rootSettings, bool skipProjectReferences = false) + { + if (rootSettings == null) + { + throw new ArgumentNullException(); + } + + MigrateProject(rootSettings); + + if (skipProjectReferences) + { + return; + } + + var projectDependencies = ResolveTransitiveClosureProjectDependencies(rootSettings.ProjectDirectory, rootSettings.ProjectXProjFilePath); + + foreach(var project in projectDependencies) + { + var projectDir = Path.GetDirectoryName(project.ProjectFilePath); + var settings = new MigrationSettings(projectDir, + projectDir, + rootSettings.SdkPackageVersion, + rootSettings.MSBuildProjectTemplate); + MigrateProject(settings); + } + } + + private IEnumerable ResolveTransitiveClosureProjectDependencies(string rootProject, string xprojFile) + { + HashSet projectsMap = new HashSet(new ProjectDependencyComparer()); + var projectDependencies = _projectDependencyFinder.ResolveProjectDependencies(rootProject, xprojFile); + Queue projectsQueue = new Queue(projectDependencies); + + while(projectsQueue.Count() != 0) + { + var projectDependency = projectsQueue.Dequeue(); + + if (projectsMap.Contains(projectDependency)) + { + continue; + } + + projectsMap.Add(projectDependency); + + var projectDir = Path.GetDirectoryName(projectDependency.ProjectFilePath); + projectDependencies = _projectDependencyFinder.ResolveProjectDependencies(projectDir); + + foreach(var project in projectDependencies) + { + projectsQueue.Enqueue(project); + } + } + + return projectsMap; + } + + private void MigrateProject(MigrationSettings migrationSettings) { var migrationRuleInputs = ComputeMigrationRuleInputs(migrationSettings); + + if (IsMigrated(migrationSettings, migrationRuleInputs)) + { + // TODO : Adding user-visible logging + MigrationTrace.Instance.WriteLine($"{nameof(ProjectMigrator)}: Skip migrating {migrationSettings.ProjectDirectory}, it is already migrated."); + return; + } + VerifyInputs(migrationRuleInputs, migrationSettings); SetupOutputDirectory(migrationSettings.ProjectDirectory, migrationSettings.OutputDirectory); @@ -43,7 +110,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration private MigrationRuleInputs ComputeMigrationRuleInputs(MigrationSettings migrationSettings) { var projectContexts = ProjectContext.CreateContextForEachFramework(migrationSettings.ProjectDirectory); - var xprojFile = migrationSettings.ProjectXProjFilePath ?? FindXprojFile(migrationSettings.ProjectDirectory); + var xprojFile = migrationSettings.ProjectXProjFilePath ?? _projectDependencyFinder.FindXprojFile(migrationSettings.ProjectDirectory); ProjectRootElement xproj = null; if (xprojFile != null) @@ -63,20 +130,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration return new MigrationRuleInputs(projectContexts, templateMSBuildProject, itemGroup, propertyGroup, xproj); } - private string FindXprojFile(string projectDirectory) - { - var allXprojFiles = Directory.EnumerateFiles(projectDirectory, "*.xproj", SearchOption.TopDirectoryOnly); - - if (allXprojFiles.Count() > 1) - { - MigrationErrorCodes - .MIGRATE1017($"Multiple xproj files found in {projectDirectory}, please specify which to use") - .Throw(); - } - - return allXprojFiles.FirstOrDefault(); - } - private void VerifyInputs(MigrationRuleInputs migrationRuleInputs, MigrationSettings migrationSettings) { VerifyProject(migrationRuleInputs.ProjectContexts, migrationSettings.ProjectDirectory); @@ -146,5 +199,14 @@ namespace Microsoft.DotNet.ProjectJsonMigration } } + public bool IsMigrated(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) + { + var outputName = Path.GetFileNameWithoutExtension( + migrationRuleInputs.DefaultProjectContext.GetOutputPaths("_").CompilationFiles.Assembly); + + var outputProject = Path.Combine(migrationSettings.OutputDirectory, outputName + ".csproj"); + return File.Exists(outputProject); + } + } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs index c1ee02a41..9386c8fa4 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs @@ -30,8 +30,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules { _projectDirectory = migrationSettings.ProjectDirectory; - var migratedXProjDependencyPaths = MigrateXProjProjectDependencies(migrationSettings, migrationRuleInputs); - var migratedXProjDependencyNames = new HashSet(migratedXProjDependencyPaths.Select(p => Path.GetFileNameWithoutExtension(p))); + var migratedXProjDependencyPaths = MigrateXProjProjectDependencies(migrationRuleInputs); + var migratedXProjDependencyNames = new HashSet(migratedXProjDependencyPaths.Select(p => Path.GetFileNameWithoutExtension( + PathUtility.GetPathWithDirectorySeparator(p)))); AddPropertyTransformsToCommonPropertyGroup(migrationRuleInputs.CommonPropertyGroup); MigrateProjectJsonProjectDependencies( @@ -40,23 +41,17 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules migrationRuleInputs.OutputMSBuildProject); } - private IEnumerable MigrateXProjProjectDependencies(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) + private IEnumerable MigrateXProjProjectDependencies(MigrationRuleInputs migrationRuleInputs) { - var xproj = migrationRuleInputs.ProjectXproj; - if (xproj == null) + var csprojReferenceItems = _projectDependencyFinder.ResolveXProjProjectDependencies(migrationRuleInputs.ProjectXproj); + + if (!csprojReferenceItems.Any()) { - MigrationTrace.Instance.WriteLine($"{nameof(MigrateProjectDependenciesRule)}: No xproj file given."); return Enumerable.Empty(); } var csprojTransformedReferences = new List(); - var csprojReferenceItems = xproj.Items - .Where(i => i.ItemType == "ProjectReference") - .Where(p => - p.Includes().Any( - include => string.Equals(Path.GetExtension(include), ".csproj", StringComparison.OrdinalIgnoreCase))); - foreach (var csprojReferenceItem in csprojReferenceItems) { var conditionChain = csprojReferenceItem.ConditionChain(); @@ -71,8 +66,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules csprojTransformedReferences.Add(transformItem); } - - + MigrationTrace.Instance.WriteLine($"{nameof(MigrateProjectDependenciesRule)}: Migrating {csprojTransformedReferences.Count()} xproj to csproj references"); foreach (var csprojTransformedReference in csprojTransformedReferences) @@ -91,7 +85,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules foreach (var projectContext in projectContexts) { var projectDependencies = _projectDependencyFinder.ResolveProjectDependencies(projectContext, migratedXProjDependencyNames); - var projectExports = projectContext.CreateExporter("_").GetDependencies(); var projectDependencyTransformResults = projectDependencies.Select(p => ProjectDependencyTransform.Transform(p)); diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs index ccd599cd5..4de5c1866 100644 --- a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs +++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs @@ -13,21 +13,20 @@ namespace Microsoft.DotNet.Tools.Migrate public partial class MigrateCommand { private readonly string _templateFile; - private readonly string _outputDirectory; private readonly string _projectJson; private readonly string _sdkVersion; private readonly string _xprojFilePath; + private readonly bool _skipProjectReferences; private readonly TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject; - public MigrateCommand(string templateFile, string outputDirectory, string projectJson, string sdkVersion, string xprojFilePath) + public MigrateCommand(string templateFile, string projectJson, string sdkVersion, string xprojFilePath, bool skipProjectReferences) { _templateFile = templateFile; - _outputDirectory = outputDirectory; _projectJson = projectJson; _sdkVersion = sdkVersion; _xprojFilePath = xprojFilePath; - + _skipProjectReferences = skipProjectReferences; _temporaryDotnetNewProject = new TemporaryDotnetNewTemplateProject(); } @@ -40,14 +39,13 @@ namespace Microsoft.DotNet.Tools.Migrate var msBuildTemplate = _templateFile != null ? ProjectRootElement.TryOpen(_templateFile) : _temporaryDotnetNewProject.MSBuildProject; - var outputDirectory = _outputDirectory ?? projectDirectory; - EnsureNotNull(outputDirectory, "Null output directory"); + 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); + new ProjectMigrator().Migrate(migrationSettings, _skipProjectReferences); return 0; } diff --git a/src/dotnet/commands/dotnet-migrate/Program.cs b/src/dotnet/commands/dotnet-migrate/Program.cs index da11f8477..706edd85c 100644 --- a/src/dotnet/commands/dotnet-migrate/Program.cs +++ b/src/dotnet/commands/dotnet-migrate/Program.cs @@ -21,19 +21,19 @@ namespace Microsoft.DotNet.Tools.Migrate app.HelpOption("-h|--help"); 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 output = app.Option("-o|--output", "Directory to output migrated project to. The default is the project directory", 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); app.OnExecute(() => { MigrateCommand migrateCommand = new MigrateCommand( - template.Value(), - output.Value(), + template.Value(), project.Value(), sdkVersion.Value(), - xprojFile.Value()); + xprojFile.Value(), + skipProjectReferences.BoolValue.HasValue ? skipProjectReferences.BoolValue.Value : false); return migrateCommand.Execute(); }); diff --git a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs index 7ea3a3cfb..08f9a657f 100644 --- a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs +++ b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateTestApps.cs @@ -113,6 +113,63 @@ namespace Microsoft.DotNet.Migration.Tests outputsIdentical.Should().BeTrue(); } + [Theory] + [InlineData("ProjectA", "ProjectA,ProjectB,ProjectC,ProjectD,ProjectE")] + [InlineData("ProjectB", "ProjectB,ProjectC,ProjectD,ProjectE")] + [InlineData("ProjectC", "ProjectC,ProjectD,ProjectE")] + [InlineData("ProjectD", "ProjectD")] + [InlineData("ProjectE", "ProjectE")] + public void It_migrates_root_project_and_references(string projectName, string expectedProjects) + { + var projectDirectory = + TestAssetsManager.CreateTestInstance("TestAppDependencyGraph", callingMethod: $"{projectName}.RefsTest").Path; + + FixUpProjectJsons(projectDirectory); + + 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); + } + } + + [Theory] + [InlineData("ProjectA")] + [InlineData("ProjectB")] + [InlineData("ProjectC")] + [InlineData("ProjectD")] + [InlineData("ProjectE")] + public void It_migrates_root_project_and_skips_references(string projectName) + { + var projectDirectory = + TestAssetsManager.CreateTestInstance("TestAppDependencyGraph", callingMethod: $"{projectName}.SkipRefsTest").Path; + + FixUpProjectJsons(projectDirectory); + + MigrateCommand.Run(new [] { "-p", 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"); + + var migratedProject = Path.GetFileName(migratedProjects.First()); + migratedProject.Should().Be($"{projectName}.csproj"); + } + + private void FixUpProjectJsons(string projectDirectory) + { + var pjs = Directory.EnumerateFiles(projectDirectory, "project.json.1", SearchOption.AllDirectories); + + foreach(var pj in pjs) + { + var newPj = pj.Replace("project.json.1", "project.json"); + File.Move(pj, newPj); + } + } + private MigratedBuildComparisonData GetDotnetNewComparisonData(string projectDirectory, string dotnetNewType) { DotnetNew(projectDirectory, dotnetNewType);