diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs
index 19eb6eb81..60ee18d42 100644
--- a/src/dotnet/Program.cs
+++ b/src/dotnet/Program.cs
@@ -21,6 +21,7 @@ using Microsoft.DotNet.Tools.Restore;
using Microsoft.DotNet.Tools.Restore3;
using Microsoft.DotNet.Tools.Run;
using Microsoft.DotNet.Tools.Test;
+using Microsoft.DotNet.Tools.Migrate;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli
@@ -42,6 +43,7 @@ namespace Microsoft.DotNet.Cli
["run3"] = Run3Command.Run,
["restore3"] = Restore3Command.Run,
["pack3"] = Pack3Command.Run,
+ ["migrate"] = MigrateCommand.Run
};
public static int Main(string[] args)
diff --git a/src/dotnet/Properties/AssemblyInfo.cs b/src/dotnet/Properties/AssemblyInfo.cs
index ffdc13024..c6553d456 100644
--- a/src/dotnet/Properties/AssemblyInfo.cs
+++ b/src/dotnet/Properties/AssemblyInfo.cs
@@ -3,3 +3,4 @@ using System.Runtime.CompilerServices;
[assembly: AssemblyMetadataAttribute("Serviceable", "True")]
[assembly: InternalsVisibleTo("dotnet.Tests")]
+[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests")]
\ No newline at end of file
diff --git a/src/dotnet/commands/dotnet-build3/Program.cs b/src/dotnet/commands/dotnet-build3/Program.cs
index e9efe9698..7630df3c3 100644
--- a/src/dotnet/commands/dotnet-build3/Program.cs
+++ b/src/dotnet/commands/dotnet-build3/Program.cs
@@ -15,6 +15,5 @@ namespace Microsoft.DotNet.Cli
{
return new MSBuildForwardingApp(args).Execute();
}
-
}
}
diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs
new file mode 100644
index 000000000..430e486c1
--- /dev/null
+++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs
@@ -0,0 +1,84 @@
+// 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.IO;
+using Microsoft.Build.Construction;
+using Microsoft.DotNet.Cli;
+using Microsoft.DotNet.ProjectJsonMigration;
+
+namespace Microsoft.DotNet.Tools.Migrate
+{
+ public partial class MigrateCommand
+ {
+ private string _templateFile;
+ private string _outputDirectory;
+ private string _projectJson;
+ private string _sdkVersion;
+
+ private TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject;
+
+ public MigrateCommand(string templateFile, string outputDirectory, string projectJson, string sdkVersion)
+ {
+ _templateFile = templateFile;
+ _outputDirectory = outputDirectory;
+ _projectJson = projectJson;
+ _sdkVersion = sdkVersion;
+
+ _temporaryDotnetNewProject = new TemporaryDotnetNewTemplateProject();
+ }
+
+ public int Start()
+ {
+ var project = GetProjectJsonPath(_projectJson) ?? _temporaryDotnetNewProject.ProjectJsonPath;
+ EnsureNotNull(project, "Unable to find project.json");
+ var projectDirectory = Path.GetDirectoryName(project);
+
+ var templateFile = _templateFile ?? _temporaryDotnetNewProject.MSBuildProjectPath;
+ EnsureNotNull(templateFile, "Unable to find default msbuild template");
+
+ var outputDirectory = _outputDirectory ?? Path.GetDirectoryName(project);
+ EnsureNotNull(outputDirectory, "Null output directory");
+
+ var sdkVersion = _sdkVersion ?? new ProjectJsonParser(_temporaryDotnetNewProject.ProjectJson).SdkPackageVersion;
+ EnsureNotNull(sdkVersion, "Null Sdk Version");
+
+ var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, templateFile);
+ new ProjectMigrator().Migrate(migrationSettings);
+ return 0;
+ }
+
+ private void EnsureNotNull(string variable, string message)
+ {
+ if (variable == null)
+ {
+ throw new Exception(message);
+ }
+ }
+
+ private string GetProjectJsonPath(string projectJson)
+ {
+ if (projectJson == null)
+ {
+ return null;
+ }
+
+ if (File.Exists(projectJson))
+ {
+ return projectJson;
+ }
+
+ if (Directory.Exists(projectJson))
+ {
+ var projectCandidate = Path.Combine(projectJson, "project.json");
+
+ if (File.Exists(projectCandidate))
+ {
+ return projectCandidate;
+ }
+ }
+
+ throw new Exception($"Unable to find project file at {projectJson}");
+ }
+ }
+}
diff --git a/src/dotnet/commands/dotnet-migrate/Program.cs b/src/dotnet/commands/dotnet-migrate/Program.cs
new file mode 100644
index 000000000..8c2c5708e
--- /dev/null
+++ b/src/dotnet/commands/dotnet-migrate/Program.cs
@@ -0,0 +1,55 @@
+// 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 Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.DotNet.Cli.Utils;
+
+namespace Microsoft.DotNet.Tools.Migrate
+{
+ public partial class MigrateCommand
+ {
+ public static int Run(string[] args)
+ {
+ DebugHelper.HandleDebugSwitch(ref args);
+
+ CommandLineApplication app = new CommandLineApplication(throwOnUnexpectedArg: false);
+ app.Name = "dotnet migrate";
+ app.FullName = ".NET Migrate Command";
+ app.Description = "Command used to migrate project.json projects to msbuild";
+ app.HandleResponseFiles = true;
+ app.AllowArgumentSeparator = true;
+ 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);
+
+ app.OnExecute(() =>
+ {
+ MigrateCommand migrateCommand = new MigrateCommand(
+ template.Value(),
+ output.Value(),
+ project.Value(),
+ sdkVersion.Value());
+
+ return migrateCommand.Start();
+ });
+
+ try
+ {
+ return app.Execute(args);
+ }
+ catch (Exception ex)
+ {
+#if DEBUG
+ Reporter.Error.WriteLine(ex.ToString());
+#else
+ Reporter.Error.WriteLine(ex.Message);
+#endif
+ return 1;
+ }
+ }
+ }
+}
diff --git a/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs b/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs
new file mode 100644
index 000000000..4856a64aa
--- /dev/null
+++ b/src/dotnet/commands/dotnet-migrate/ProjectJsonParser.cs
@@ -0,0 +1,81 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Microsoft.DotNet.Cli
+{
+ ///
+ /// Parses select data from a project.json without relying on ProjectModel.
+ /// Used to parse simple information.
+ ///
+ internal class ProjectJsonParser
+ {
+ public static string SdkPackageName => "Microsoft.DotNet.Core.Sdk";
+
+ public string SdkPackageVersion { get; }
+
+ public ProjectJsonParser(JObject projectJson)
+ {
+ SdkPackageVersion = GetSdkPackageVersion(projectJson);
+ }
+
+ private string GetSdkPackageVersion(JObject projectJson)
+ {
+ var sdkPackageNode = SelectJsonNodes(projectJson, property => property.Name == SdkPackageName).First();
+
+ if (sdkPackageNode.Value.Type == JTokenType.String)
+ {
+ return (string)sdkPackageNode.Value;
+ }
+ else if (sdkPackageNode.Type == JTokenType.Object)
+ {
+ var sdkPackageNodeValue = (JObject)sdkPackageNode.Value;
+
+ JToken sdkVersionNode;
+ if (sdkPackageNodeValue.TryGetValue("version", out sdkVersionNode))
+ {
+ return sdkVersionNode.Value();
+ }
+ else
+ {
+ throw new Exception("Unable to determine sdk version, no version node in default template.");
+ }
+ }
+ else
+ {
+ throw new Exception("Unable to determine sdk version, no version information found");
+ }
+ }
+
+ private IEnumerable SelectJsonNodes(
+ JToken jsonNode,
+ Func condition,
+ List nodeAccumulator = null)
+ {
+ nodeAccumulator = nodeAccumulator ?? new List();
+
+ if (jsonNode.Type == JTokenType.Object)
+ {
+ var eligibleNodes = jsonNode.Children().Where(j => condition(j));
+ nodeAccumulator.AddRange(eligibleNodes);
+
+ foreach (var child in jsonNode.Children())
+ {
+ SelectJsonNodes(child.Value, condition, nodeAccumulator: nodeAccumulator);
+ }
+ }
+ else if (jsonNode.Type == JTokenType.Array)
+ {
+ foreach (var child in jsonNode.Children())
+ {
+ SelectJsonNodes(child, condition, nodeAccumulator: nodeAccumulator);
+ }
+ }
+
+ return nodeAccumulator;
+ }
+ }
+}
diff --git a/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs
new file mode 100644
index 000000000..86a944c4b
--- /dev/null
+++ b/src/dotnet/commands/dotnet-migrate/TemporaryDotnetNewTemplateProject.cs
@@ -0,0 +1,85 @@
+using Microsoft.Build.Construction;
+using Microsoft.DotNet.Cli;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Microsoft.DotNet.Cli
+{
+ internal class TemporaryDotnetNewTemplateProject
+ {
+ private static string s_temporaryDotnetNewMSBuildProjectName = "p";
+
+ public TemporaryDotnetNewTemplateProject()
+ {
+ ProjectDirectory = CreateDotnetNewMSBuild(s_temporaryDotnetNewMSBuildProjectName);
+ MSBuildProject = GetMSBuildProject(ProjectDirectory);
+ ProjectJson = GetProjectJson(ProjectDirectory);
+ }
+
+ public ProjectRootElement MSBuildProject { get; }
+ public JObject ProjectJson { get; }
+ public string ProjectDirectory { get; }
+
+ public string ProjectJsonPath => Path.Combine(ProjectDirectory, "project.json");
+ public string MSBuildProjectPath => Path.Combine(ProjectDirectory, s_temporaryDotnetNewMSBuildProjectName);
+
+ public void Clean()
+ {
+ Directory.Delete(ProjectDirectory, true);
+ }
+
+ private string CreateDotnetNewMSBuild(string projectName)
+ {
+ var guid = Guid.NewGuid().ToString();
+ var tempDir = Path.Combine(
+ Path.GetTempPath(),
+ this.GetType().Namespace,
+ guid,
+ s_temporaryDotnetNewMSBuildProjectName);
+
+ if (Directory.Exists(tempDir))
+ {
+ Directory.Delete(tempDir, true);
+ }
+ Directory.CreateDirectory(tempDir);
+
+ RunCommand("new", new string[] { "-t", "msbuild" }, tempDir);
+
+ return tempDir;
+ }
+
+ private ProjectRootElement GetMSBuildProject(string temporaryDotnetNewMSBuildDirectory)
+ {
+ var templateProjPath = Path.Combine(temporaryDotnetNewMSBuildDirectory,
+ s_temporaryDotnetNewMSBuildProjectName + ".csproj");
+
+ return ProjectRootElement.Open(templateProjPath);
+ }
+
+ private JObject GetProjectJson(string temporaryDotnetNewMSBuildDirectory)
+ {
+ var projectJsonFile = Path.Combine(temporaryDotnetNewMSBuildDirectory, "project.json");
+ return JObject.Parse(File.ReadAllText(projectJsonFile));
+ }
+
+ private void RunCommand(string commandToExecute, IEnumerable args, string workingDirectory)
+ {
+ var command = new DotNetCommandFactory()
+ .Create(commandToExecute, args)
+ .WorkingDirectory(workingDirectory)
+ .CaptureStdOut()
+ .CaptureStdErr();
+
+ var commandResult = command.Execute();
+
+ if (commandResult.ExitCode != 0)
+ {
+ throw new Exception($"Failed to run {commandToExecute} in directory: {workingDirectory}");
+ }
+ }
+ }
+}
diff --git a/src/dotnet/project.json b/src/dotnet/project.json
index 18f4ae78c..64590c7a4 100644
--- a/src/dotnet/project.json
+++ b/src/dotnet/project.json
@@ -45,6 +45,9 @@
"Microsoft.DotNet.Configurer": {
"target": "project"
},
+ "Microsoft.DotNet.ProjectJsonMigration": {
+ "target": "project"
+ },
"Microsoft.DotNet.Tools.Test": {
"target": "project"
},