2016-08-22 19:21:52 +00:00
// 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 ;
2016-09-26 21:16:17 +00:00
using System.Collections.Generic ;
2016-08-22 19:21:52 +00:00
using System.IO ;
2016-10-04 17:39:55 +00:00
using System.Linq ;
2016-10-04 21:59:04 +00:00
using System.Text ;
2016-08-22 19:21:52 +00:00
using Microsoft.Build.Construction ;
2016-10-12 23:22:58 +00:00
using Microsoft.Build.Evaluation ;
2016-12-20 23:04:01 +00:00
using Microsoft.DotNet.Cli ;
2016-12-07 21:49:15 +00:00
using Microsoft.DotNet.Cli.Sln.Internal ;
2016-10-04 21:59:04 +00:00
using Microsoft.DotNet.Cli.Utils ;
2016-08-22 19:21:52 +00:00
using Microsoft.DotNet.ProjectJsonMigration ;
2016-10-28 01:46:43 +00:00
using Microsoft.DotNet.Internal.ProjectModel ;
2016-10-29 08:58:37 +00:00
using Project = Microsoft . DotNet . Internal . ProjectModel . Project ;
using Microsoft.DotNet.Tools.Common ;
2016-08-22 19:21:52 +00:00
namespace Microsoft.DotNet.Tools.Migrate
{
public partial class MigrateCommand
{
2016-12-07 21:49:15 +00:00
private SlnFile _slnFile ;
2016-10-29 08:58:37 +00:00
private readonly DirectoryInfo _workspaceDirectory ;
2016-08-23 20:50:05 +00:00
private readonly string _templateFile ;
2016-09-26 21:16:17 +00:00
private readonly string _projectArg ;
2016-08-23 20:50:05 +00:00
private readonly string _sdkVersion ;
2016-09-22 00:27:02 +00:00
private readonly string _xprojFilePath ;
2016-09-23 07:30:41 +00:00
private readonly bool _skipProjectReferences ;
2016-10-04 21:59:04 +00:00
private readonly string _reportFile ;
private readonly bool _reportFormatJson ;
2016-10-29 08:58:37 +00:00
private readonly bool _skipBackup ;
2016-08-22 19:21:52 +00:00
2016-10-04 21:59:04 +00:00
public MigrateCommand (
2016-12-20 22:19:37 +00:00
string templateFile ,
string projectArg ,
string sdkVersion ,
string xprojFilePath ,
string reportFile ,
bool skipProjectReferences ,
2016-10-29 08:58:37 +00:00
bool reportFormatJson ,
bool skipBackup )
2016-12-20 22:19:37 +00:00
{
2016-08-22 19:21:52 +00:00
_templateFile = templateFile ;
2016-09-26 21:16:17 +00:00
_projectArg = projectArg ? ? Directory . GetCurrentDirectory ( ) ;
2016-10-29 08:58:37 +00:00
_workspaceDirectory = File . Exists ( _projectArg )
? new FileInfo ( _projectArg ) . Directory
: new DirectoryInfo ( _projectArg ) ;
2016-08-22 19:21:52 +00:00
_sdkVersion = sdkVersion ;
2016-09-22 00:27:02 +00:00
_xprojFilePath = xprojFilePath ;
2016-09-23 07:30:41 +00:00
_skipProjectReferences = skipProjectReferences ;
2016-10-04 21:59:04 +00:00
_reportFile = reportFile ;
_reportFormatJson = reportFormatJson ;
2016-10-29 08:58:37 +00:00
_skipBackup = skipBackup ;
2016-08-22 19:21:52 +00:00
}
2016-08-23 20:50:05 +00:00
public int Execute ( )
2016-08-22 19:21:52 +00:00
{
2016-10-25 05:46:15 +00:00
var temporaryDotnetNewProject = new TemporaryDotnetNewTemplateProject ( ) ;
2016-09-26 21:16:17 +00:00
var projectsToMigrate = GetProjectsToMigrate ( _projectArg ) ;
2016-08-22 19:21:52 +00:00
2016-10-25 05:46:15 +00:00
var msBuildTemplatePath = _templateFile ? ? temporaryDotnetNewProject . MSBuildProjectPath ;
2016-08-22 19:21:52 +00:00
2016-10-04 21:59:04 +00:00
MigrationReport migrationReport = null ;
2016-09-26 21:16:17 +00:00
foreach ( var project in projectsToMigrate )
{
var projectDirectory = Path . GetDirectoryName ( project ) ;
var outputDirectory = projectDirectory ;
2016-10-25 05:46:15 +00:00
var migrationSettings = new MigrationSettings (
projectDirectory ,
outputDirectory ,
msBuildTemplatePath ,
2016-12-07 21:49:15 +00:00
_xprojFilePath ,
null ,
_slnFile ) ;
2016-10-04 21:59:04 +00:00
var projectMigrationReport = new ProjectMigrator ( ) . Migrate ( migrationSettings , _skipProjectReferences ) ;
if ( migrationReport = = null )
{
migrationReport = projectMigrationReport ;
}
else
{
migrationReport = migrationReport . Merge ( projectMigrationReport ) ;
}
}
WriteReport ( migrationReport ) ;
2016-10-25 05:46:15 +00:00
temporaryDotnetNewProject . Clean ( ) ;
2016-12-07 21:49:15 +00:00
UpdateSolutionFile ( migrationReport ) ;
2016-10-29 08:58:37 +00:00
MoveProjectJsonArtifactsToBackup ( migrationReport ) ;
2016-10-04 21:59:04 +00:00
return migrationReport . FailedProjectsCount ;
}
2016-12-07 21:49:15 +00:00
private void UpdateSolutionFile ( MigrationReport migrationReport )
{
if ( _slnFile = = null )
{
return ;
}
if ( migrationReport . FailedProjectsCount > 0 )
{
return ;
}
2016-12-20 23:04:01 +00:00
List < string > csprojFilesToAdd = new List < string > ( ) ;
var slnPathWithTrailingSlash = PathUtility . EnsureTrailingSlash ( _slnFile . BaseDirectory ) ;
foreach ( var report in migrationReport . ProjectMigrationReports )
2016-12-07 21:49:15 +00:00
{
2016-12-20 23:04:01 +00:00
var reportPathWithTrailingSlash = PathUtility . EnsureTrailingSlash ( report . ProjectDirectory ) ;
var reportRelPath = Path . Combine (
PathUtility . GetRelativePath ( slnPathWithTrailingSlash , reportPathWithTrailingSlash ) ,
report . ProjectName + ".xproj" ) ;
2016-12-07 21:49:15 +00:00
2016-12-20 23:04:01 +00:00
var projects = _slnFile . Projects . Where ( p = > p . FilePath = = reportRelPath ) ;
2016-12-07 21:49:15 +00:00
2016-12-20 23:04:01 +00:00
var migratedProjectName = report . ProjectName + ".csproj" ;
if ( projects . Count ( ) = = 1 )
{
var slnProject = projects . Single ( ) ;
slnProject . FilePath = Path . Combine (
Path . GetDirectoryName ( slnProject . FilePath ) ,
migratedProjectName ) ;
slnProject . TypeGuid = ProjectTypeGuids . CSharpProjectTypeGuid ;
}
else
{
csprojFilesToAdd . Add ( Path . Combine ( report . ProjectDirectory , migratedProjectName ) ) ;
}
2016-12-07 21:49:15 +00:00
2016-12-20 23:04:01 +00:00
foreach ( var preExisting in report . PreExistingCsprojDependencies )
2016-12-07 21:49:15 +00:00
{
2016-12-20 23:04:01 +00:00
csprojFilesToAdd . Add ( Path . Combine ( report . ProjectDirectory , preExisting ) ) ;
2016-12-07 21:49:15 +00:00
}
}
2016-12-13 16:45:00 +00:00
_slnFile . Write ( ) ;
2016-12-20 23:04:01 +00:00
foreach ( var csprojFile in csprojFilesToAdd )
{
AddProject ( _slnFile . FullPath , csprojFile ) ;
}
}
private void AddProject ( string slnPath , string csprojPath )
{
List < string > args = new List < string > ( )
{
"add" ,
slnPath ,
"project" ,
csprojPath ,
} ;
var dotnetPath = Path . Combine ( AppContext . BaseDirectory , "dotnet.dll" ) ;
var addCommand = new ForwardingApp ( dotnetPath , args ) ;
addCommand . Execute ( ) ;
2016-12-07 21:49:15 +00:00
}
2016-10-29 08:58:37 +00:00
private void MoveProjectJsonArtifactsToBackup ( MigrationReport migrationReport )
{
if ( _skipBackup )
{
return ;
}
2016-12-20 22:19:37 +00:00
2016-10-29 08:58:37 +00:00
if ( migrationReport . FailedProjectsCount > 0 )
{
return ;
}
BackupProjects ( migrationReport ) ;
}
private void BackupProjects ( MigrationReport migrationReport )
2016-10-04 21:59:04 +00:00
{
2016-10-29 08:58:37 +00:00
foreach ( var report in migrationReport . ProjectMigrationReports )
{
2016-12-20 22:19:37 +00:00
var backupPlan = new MigrationBackupPlan (
new DirectoryInfo ( report . ProjectDirectory ) ,
_workspaceDirectory ) ;
2016-10-29 08:58:37 +00:00
2016-12-20 22:19:37 +00:00
backupPlan . PerformBackup ( ) ;
2016-10-29 08:58:37 +00:00
}
}
private void WriteReport ( MigrationReport migrationReport )
{
2016-10-04 21:59:04 +00:00
if ( ! string . IsNullOrEmpty ( _reportFile ) )
{
using ( var outputTextWriter = GetReportFileOutputTextWriter ( ) )
{
outputTextWriter . Write ( GetReportContent ( migrationReport ) ) ;
}
}
WriteReportToStdOut ( migrationReport ) ;
}
private void WriteReportToStdOut ( MigrationReport migrationReport )
{
StringBuilder sb = new StringBuilder ( ) ;
foreach ( var projectMigrationReport in migrationReport . ProjectMigrationReports )
{
var errorContent = GetProjectReportErrorContent ( projectMigrationReport , colored : true ) ;
var successContent = GetProjectReportSuccessContent ( projectMigrationReport , colored : true ) ;
if ( ! string . IsNullOrEmpty ( errorContent ) )
{
Reporter . Error . WriteLine ( errorContent ) ;
}
else
{
Reporter . Output . WriteLine ( successContent ) ;
}
}
Reporter . Output . WriteLine ( GetReportSummary ( migrationReport ) ) ;
}
private string GetReportContent ( MigrationReport migrationReport , bool colored = false )
{
if ( _reportFormatJson )
{
return Newtonsoft . Json . JsonConvert . SerializeObject ( migrationReport ) ;
}
StringBuilder sb = new StringBuilder ( ) ;
foreach ( var projectMigrationReport in migrationReport . ProjectMigrationReports )
{
var errorContent = GetProjectReportErrorContent ( projectMigrationReport , colored : colored ) ;
var successContent = GetProjectReportSuccessContent ( projectMigrationReport , colored : colored ) ;
if ( ! string . IsNullOrEmpty ( errorContent ) )
{
sb . AppendLine ( errorContent ) ;
}
else
{
sb . AppendLine ( successContent ) ;
}
}
sb . AppendLine ( GetReportSummary ( migrationReport ) ) ;
return sb . ToString ( ) ;
}
private string GetReportSummary ( MigrationReport migrationReport )
{
StringBuilder sb = new StringBuilder ( ) ;
sb . AppendLine ( "Summary" ) ;
sb . AppendLine ( $"Total Projects: {migrationReport.MigratedProjectsCount}" ) ;
sb . AppendLine ( $"Succeeded Projects: {migrationReport.SucceededProjectsCount}" ) ;
sb . AppendLine ( $"Failed Projects: {migrationReport.FailedProjectsCount}" ) ;
return sb . ToString ( ) ;
}
private string GetProjectReportSuccessContent ( ProjectMigrationReport projectMigrationReport , bool colored )
{
Func < string , string > GreenIfColored = ( str ) = > colored ? str . Green ( ) : str ;
return GreenIfColored ( $"Project {projectMigrationReport.ProjectName} migration succeeded ({projectMigrationReport.ProjectDirectory})" ) ;
}
private string GetProjectReportErrorContent ( ProjectMigrationReport projectMigrationReport , bool colored )
{
StringBuilder sb = new StringBuilder ( ) ;
Func < string , string > RedIfColored = ( str ) = > colored ? str . Red ( ) : str ;
if ( projectMigrationReport . Errors . Any ( ) )
{
sb . AppendLine ( RedIfColored ( $"Project {projectMigrationReport.ProjectName} migration failed ({projectMigrationReport.ProjectDirectory})" ) ) ;
foreach ( var error in projectMigrationReport . Errors . Select ( e = > e . GetFormattedErrorMessage ( ) ) )
{
sb . AppendLine ( RedIfColored ( error ) ) ;
}
2016-09-26 21:16:17 +00:00
}
2016-08-23 20:50:05 +00:00
2016-10-04 21:59:04 +00:00
return sb . ToString ( ) ;
}
private TextWriter GetReportFileOutputTextWriter ( )
{
return File . CreateText ( _reportFile ) ;
2016-08-22 19:21:52 +00:00
}
2016-09-26 21:16:17 +00:00
private IEnumerable < string > GetProjectsToMigrate ( string projectArg )
{
2016-10-04 17:39:55 +00:00
IEnumerable < string > projects = null ;
2016-10-29 08:58:37 +00:00
if ( projectArg . EndsWith ( Project . FileName , StringComparison . OrdinalIgnoreCase ) )
2016-10-04 17:39:55 +00:00
{
projects = Enumerable . Repeat ( projectArg , 1 ) ;
}
else if ( projectArg . EndsWith ( GlobalSettings . FileName , StringComparison . OrdinalIgnoreCase ) )
2016-09-26 21:16:17 +00:00
{
2016-12-07 21:49:15 +00:00
projects = GetProjectsFromGlobalJson ( projectArg ) ;
2016-10-29 08:58:37 +00:00
2016-10-10 22:38:25 +00:00
if ( ! projects . Any ( ) )
{
throw new Exception ( "Unable to find any projects in global.json" ) ;
}
2016-09-26 21:16:17 +00:00
}
2016-12-07 21:49:15 +00:00
else if ( File . Exists ( projectArg ) & &
string . Equals ( Path . GetExtension ( projectArg ) , ".sln" , StringComparison . OrdinalIgnoreCase ) )
{
projects = GetProjectsFromSolution ( projectArg ) ;
if ( ! projects . Any ( ) )
{
throw new Exception ( $"Unable to find any projects in {projectArg}" ) ;
}
}
2016-09-26 21:16:17 +00:00
else if ( Directory . Exists ( projectArg ) )
{
2016-10-29 08:58:37 +00:00
projects = Directory . EnumerateFiles ( projectArg , Project . FileName , SearchOption . AllDirectories ) ;
2016-10-10 22:38:25 +00:00
if ( ! projects . Any ( ) )
{
throw new Exception ( $"No project.json file found in '{projectArg}'" ) ;
}
2016-09-26 21:16:17 +00:00
}
else
{
2016-12-07 21:49:15 +00:00
throw new Exception ( $"Invalid project argument - '{projectArg}' is not a project.json, global.json, or solution.sln file and a directory named '{projectArg}' doesn't exist." ) ;
2016-10-04 17:39:55 +00:00
}
2016-12-20 22:19:37 +00:00
foreach ( var project in projects )
2016-10-04 17:39:55 +00:00
{
yield return GetProjectJsonPath ( project ) ;
2016-09-26 21:16:17 +00:00
}
}
2016-08-22 19:21:52 +00:00
private void EnsureNotNull ( string variable , string message )
{
if ( variable = = null )
{
throw new Exception ( message ) ;
}
}
private string GetProjectJsonPath ( string projectJson )
{
2016-08-23 20:50:05 +00:00
projectJson = ProjectPathHelper . NormalizeProjectFilePath ( projectJson ) ;
2016-08-22 19:21:52 +00:00
if ( File . Exists ( projectJson ) )
{
return projectJson ;
}
throw new Exception ( $"Unable to find project file at {projectJson}" ) ;
}
2016-10-04 17:39:55 +00:00
private IEnumerable < string > 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 ( ) )
{
2016-12-07 21:49:15 +00:00
var projectFilePath = Path . Combine ( projectDirectory . FullName , Project . FileName ) ;
2016-10-04 17:39:55 +00:00
if ( File . Exists ( projectFilePath ) )
{
yield return projectFilePath ;
}
}
}
}
2016-12-07 21:49:15 +00:00
private IEnumerable < string > GetProjectsFromSolution ( string slnPath )
{
if ( ! File . Exists ( slnPath ) )
{
throw new Exception ( $"Unable to find the solution file at {slnPath}" ) ;
}
2016-12-13 16:45:00 +00:00
_slnFile = SlnFile . Read ( slnPath ) ;
2016-12-07 21:49:15 +00:00
foreach ( var project in _slnFile . Projects )
{
2016-12-13 16:45:00 +00:00
var projectFilePath = Path . Combine (
_slnFile . BaseDirectory ,
Path . GetDirectoryName ( project . FilePath ) ,
Project . FileName ) ;
2016-12-07 21:49:15 +00:00
if ( File . Exists ( projectFilePath ) )
{
yield return projectFilePath ;
}
}
}
2016-08-22 19:21:52 +00:00
}
}