2016-08-22 12:21:34 -07: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;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using Microsoft.Build.Construction;
|
2016-10-27 18:46:43 -07:00
|
|
|
using Microsoft.DotNet.Internal.ProjectModel;
|
|
|
|
using Microsoft.DotNet.Internal.ProjectModel.Graph;
|
2016-09-23 00:30:41 -07:00
|
|
|
using Microsoft.DotNet.Cli;
|
2016-08-22 12:21:34 -07:00
|
|
|
using System.Linq;
|
|
|
|
using System.IO;
|
2016-12-07 11:49:15 -10:00
|
|
|
using Microsoft.DotNet.Cli.Sln.Internal;
|
2016-08-23 13:50:05 -07:00
|
|
|
using Microsoft.DotNet.ProjectJsonMigration.Rules;
|
|
|
|
using Microsoft.DotNet.Tools.Common;
|
2016-08-22 12:21:34 -07:00
|
|
|
|
|
|
|
namespace Microsoft.DotNet.ProjectJsonMigration
|
|
|
|
{
|
2016-10-27 18:46:43 -07:00
|
|
|
internal class ProjectMigrator
|
2016-08-22 12:21:34 -07:00
|
|
|
{
|
2016-08-23 13:50:05 -07:00
|
|
|
private readonly IMigrationRule _ruleSet;
|
2016-09-23 00:30:41 -07:00
|
|
|
private readonly ProjectDependencyFinder _projectDependencyFinder = new ProjectDependencyFinder();
|
2016-08-22 12:21:34 -07:00
|
|
|
|
2016-08-23 13:50:05 -07:00
|
|
|
public ProjectMigrator() : this(new DefaultMigrationRuleSet()) { }
|
|
|
|
|
|
|
|
public ProjectMigrator(IMigrationRule ruleSet)
|
|
|
|
{
|
|
|
|
_ruleSet = ruleSet;
|
2016-08-22 12:21:34 -07:00
|
|
|
}
|
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
public MigrationReport Migrate(MigrationSettings rootSettings, bool skipProjectReferences = false)
|
2016-09-23 00:30:41 -07:00
|
|
|
{
|
|
|
|
if (rootSettings == null)
|
|
|
|
{
|
|
|
|
throw new ArgumentNullException();
|
|
|
|
}
|
2016-10-04 14:59:04 -07:00
|
|
|
|
|
|
|
// Try to read the project dependencies, ignore an unresolved exception for now
|
|
|
|
MigrationRuleInputs rootInputs = ComputeMigrationRuleInputs(rootSettings);
|
2016-10-04 10:45:57 -07:00
|
|
|
IEnumerable<ProjectDependency> projectDependencies = null;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2016-10-04 14:59:04 -07:00
|
|
|
// Verify up front so we can prefer these errors over an unresolved project dependency
|
|
|
|
VerifyInputs(rootInputs, rootSettings);
|
|
|
|
|
2016-10-04 10:45:57 -07:00
|
|
|
projectDependencies = ResolveTransitiveClosureProjectDependencies(
|
|
|
|
rootSettings.ProjectDirectory,
|
2016-12-07 11:49:15 -10:00
|
|
|
rootSettings.ProjectXProjFilePath,
|
|
|
|
rootSettings.SolutionFile);
|
2016-10-04 10:45:57 -07:00
|
|
|
}
|
2016-10-04 14:59:04 -07:00
|
|
|
catch (MigrationException e)
|
2016-10-04 10:45:57 -07:00
|
|
|
{
|
2016-10-04 14:59:04 -07:00
|
|
|
return new MigrationReport(
|
|
|
|
new List<ProjectMigrationReport>
|
|
|
|
{
|
|
|
|
new ProjectMigrationReport(
|
|
|
|
rootSettings.ProjectDirectory,
|
2016-12-08 19:53:35 -08:00
|
|
|
rootInputs?.DefaultProjectContext?.GetProjectName(),
|
2016-10-04 14:59:04 -07:00
|
|
|
new List<MigrationError> {e.Error},
|
|
|
|
null)
|
|
|
|
});
|
2016-10-04 10:45:57 -07:00
|
|
|
}
|
2016-09-23 00:30:41 -07:00
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
var projectMigrationReports = new List<ProjectMigrationReport>();
|
|
|
|
projectMigrationReports.Add(MigrateProject(rootSettings));
|
|
|
|
|
2016-09-23 00:30:41 -07:00
|
|
|
if (skipProjectReferences)
|
|
|
|
{
|
2016-10-04 14:59:04 -07:00
|
|
|
return new MigrationReport(projectMigrationReports);
|
2016-09-23 00:30:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach(var project in projectDependencies)
|
|
|
|
{
|
|
|
|
var projectDir = Path.GetDirectoryName(project.ProjectFilePath);
|
|
|
|
var settings = new MigrationSettings(projectDir,
|
|
|
|
projectDir,
|
2016-12-07 11:49:15 -10:00
|
|
|
rootSettings.MSBuildProjectTemplatePath);
|
2016-09-23 14:48:54 -07:00
|
|
|
MigrateProject(settings);
|
2016-10-04 14:59:04 -07:00
|
|
|
projectMigrationReports.Add(MigrateProject(settings));
|
2016-09-23 00:30:41 -07:00
|
|
|
}
|
2016-10-04 14:59:04 -07:00
|
|
|
|
|
|
|
return new MigrationReport(projectMigrationReports);
|
2016-09-23 00:30:41 -07:00
|
|
|
}
|
|
|
|
|
2016-10-03 20:10:09 -07:00
|
|
|
private void DeleteProjectJsons(MigrationSettings rootsettings, IEnumerable<ProjectDependency> projectDependencies)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
File.Delete(Path.Combine(rootsettings.ProjectDirectory, "project.json"));
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
foreach (var projectDependency in projectDependencies)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
File.Delete(projectDependency.ProjectFilePath);
|
|
|
|
} catch { }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-07 11:49:15 -10:00
|
|
|
private IEnumerable<ProjectDependency> ResolveTransitiveClosureProjectDependencies(
|
|
|
|
string rootProject, string xprojFile, SlnFile solutionFile)
|
2016-09-23 00:30:41 -07:00
|
|
|
{
|
|
|
|
HashSet<ProjectDependency> projectsMap = new HashSet<ProjectDependency>(new ProjectDependencyComparer());
|
2016-12-07 11:49:15 -10:00
|
|
|
var projectDependencies = _projectDependencyFinder.ResolveProjectDependencies(rootProject, xprojFile, solutionFile);
|
2016-09-23 00:30:41 -07:00
|
|
|
Queue<ProjectDependency> projectsQueue = new Queue<ProjectDependency>(projectDependencies);
|
|
|
|
|
2016-10-03 10:58:32 -07:00
|
|
|
while (projectsQueue.Count() != 0)
|
2016-09-23 00:30:41 -07:00
|
|
|
{
|
|
|
|
var projectDependency = projectsQueue.Dequeue();
|
|
|
|
|
|
|
|
if (projectsMap.Contains(projectDependency))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
projectsMap.Add(projectDependency);
|
|
|
|
|
2016-09-23 14:48:54 -07:00
|
|
|
var projectDir = Path.GetDirectoryName(projectDependency.ProjectFilePath);
|
|
|
|
projectDependencies = _projectDependencyFinder.ResolveProjectDependencies(projectDir);
|
2016-09-23 00:30:41 -07:00
|
|
|
|
2016-10-03 10:58:32 -07:00
|
|
|
foreach (var project in projectDependencies)
|
2016-09-23 00:30:41 -07:00
|
|
|
{
|
|
|
|
projectsQueue.Enqueue(project);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return projectsMap;
|
|
|
|
}
|
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
private ProjectMigrationReport MigrateProject(MigrationSettings migrationSettings)
|
2016-08-22 12:21:34 -07:00
|
|
|
{
|
2016-08-23 13:50:05 -07:00
|
|
|
var migrationRuleInputs = ComputeMigrationRuleInputs(migrationSettings);
|
2016-10-04 14:59:04 -07:00
|
|
|
var projectName = migrationRuleInputs.DefaultProjectContext.GetProjectName();
|
2016-09-23 00:30:41 -07:00
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
try
|
2016-12-20 13:04:01 -10:00
|
|
|
{
|
2016-10-04 14:59:04 -07:00
|
|
|
if (IsMigrated(migrationSettings, migrationRuleInputs))
|
|
|
|
{
|
2016-12-16 19:45:43 -08:00
|
|
|
MigrationTrace.Instance.WriteLine(String.Format(LocalizableStrings.SkipMigrationAlreadyMigrated, nameof(ProjectMigrator), migrationSettings.ProjectDirectory));
|
2016-10-04 14:59:04 -07:00
|
|
|
return new ProjectMigrationReport(migrationSettings.ProjectDirectory, projectName, skipped: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
VerifyInputs(migrationRuleInputs, migrationSettings);
|
2016-09-23 00:30:41 -07:00
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
SetupOutputDirectory(migrationSettings.ProjectDirectory, migrationSettings.OutputDirectory);
|
2016-08-23 13:50:05 -07:00
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
_ruleSet.Apply(migrationSettings, migrationRuleInputs);
|
|
|
|
}
|
|
|
|
catch (MigrationException exc)
|
|
|
|
{
|
|
|
|
var error = new List<MigrationError>
|
|
|
|
{
|
|
|
|
exc.Error
|
|
|
|
};
|
2016-08-23 13:50:05 -07:00
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
return new ProjectMigrationReport(migrationSettings.ProjectDirectory, projectName, error, null);
|
|
|
|
}
|
2016-12-20 13:04:01 -10:00
|
|
|
|
|
|
|
List<string> csprojDependencies = null;
|
|
|
|
if (migrationRuleInputs.ProjectXproj != null)
|
|
|
|
{
|
|
|
|
var projectDependencyFinder = new ProjectDependencyFinder();
|
|
|
|
var dependencies = projectDependencyFinder.ResolveXProjProjectDependencies(
|
|
|
|
migrationRuleInputs.ProjectXproj);
|
|
|
|
|
|
|
|
if (dependencies.Any())
|
|
|
|
{
|
|
|
|
csprojDependencies = dependencies
|
|
|
|
.SelectMany(r => r.Includes().Select(p => PathUtility.GetPathWithDirectorySeparator(p)))
|
|
|
|
.ToList();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
csprojDependencies = new List<string>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-04 14:59:04 -07:00
|
|
|
var outputProject = Path.Combine(migrationSettings.OutputDirectory, projectName + ".csproj");
|
2016-12-20 13:04:01 -10:00
|
|
|
return new ProjectMigrationReport(
|
|
|
|
migrationSettings.ProjectDirectory,
|
|
|
|
projectName,
|
|
|
|
outputProject,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
csprojDependencies);
|
2016-08-22 12:21:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private MigrationRuleInputs ComputeMigrationRuleInputs(MigrationSettings migrationSettings)
|
|
|
|
{
|
|
|
|
var projectContexts = ProjectContext.CreateContextForEachFramework(migrationSettings.ProjectDirectory);
|
2016-09-23 14:48:54 -07:00
|
|
|
var xprojFile = migrationSettings.ProjectXProjFilePath ?? _projectDependencyFinder.FindXprojFile(migrationSettings.ProjectDirectory);
|
2016-10-03 10:58:32 -07:00
|
|
|
|
2016-09-21 17:27:02 -07:00
|
|
|
ProjectRootElement xproj = null;
|
|
|
|
if (xprojFile != null)
|
|
|
|
{
|
|
|
|
xproj = ProjectRootElement.Open(xprojFile);
|
|
|
|
}
|
2016-08-22 12:21:34 -07:00
|
|
|
|
2016-08-23 13:50:05 -07:00
|
|
|
var templateMSBuildProject = migrationSettings.MSBuildProjectTemplate;
|
|
|
|
if (templateMSBuildProject == null)
|
|
|
|
{
|
2016-12-16 19:45:43 -08:00
|
|
|
throw new Exception(LocalizableStrings.NullMSBuildProjectTemplateError);
|
2016-08-23 13:50:05 -07:00
|
|
|
}
|
2016-08-22 12:21:34 -07:00
|
|
|
|
|
|
|
var propertyGroup = templateMSBuildProject.AddPropertyGroup();
|
|
|
|
var itemGroup = templateMSBuildProject.AddItemGroup();
|
|
|
|
|
2016-09-21 17:27:02 -07:00
|
|
|
return new MigrationRuleInputs(projectContexts, templateMSBuildProject, itemGroup, propertyGroup, xproj);
|
|
|
|
}
|
|
|
|
|
2016-08-23 13:50:05 -07:00
|
|
|
private void VerifyInputs(MigrationRuleInputs migrationRuleInputs, MigrationSettings migrationSettings)
|
2016-08-22 12:21:34 -07:00
|
|
|
{
|
2016-08-23 13:50:05 -07:00
|
|
|
VerifyProject(migrationRuleInputs.ProjectContexts, migrationSettings.ProjectDirectory);
|
2016-08-22 12:21:34 -07:00
|
|
|
}
|
|
|
|
|
2016-08-23 13:50:05 -07:00
|
|
|
private void VerifyProject(IEnumerable<ProjectContext> projectContexts, string projectDirectory)
|
2016-08-22 12:21:34 -07:00
|
|
|
{
|
2016-08-23 13:50:05 -07:00
|
|
|
if (!projectContexts.Any())
|
2016-08-22 12:21:34 -07:00
|
|
|
{
|
2016-12-16 19:45:43 -08:00
|
|
|
MigrationErrorCodes.MIGRATE1013(String.Format(LocalizableStrings.MIGRATE1013Arg, projectDirectory)).Throw();
|
2016-08-22 12:21:34 -07:00
|
|
|
}
|
|
|
|
|
2016-08-23 13:50:05 -07:00
|
|
|
var defaultProjectContext = projectContexts.First();
|
|
|
|
|
|
|
|
var diagnostics = defaultProjectContext.ProjectFile.Diagnostics;
|
|
|
|
if (diagnostics.Any())
|
|
|
|
{
|
|
|
|
MigrationErrorCodes.MIGRATE1011(
|
2016-12-16 19:45:43 -08:00
|
|
|
String.Format("{0}{1}{2}", projectDirectory, Environment.NewLine, string.Join(Environment.NewLine, diagnostics.Select(d => FormatDiagnosticMessage(d)))))
|
2016-08-23 13:50:05 -07:00
|
|
|
.Throw();
|
|
|
|
}
|
|
|
|
|
|
|
|
var compilerName =
|
|
|
|
defaultProjectContext.ProjectFile.GetCompilerOptions(defaultProjectContext.TargetFramework, "_")
|
|
|
|
.CompilerName;
|
|
|
|
if (!compilerName.Equals("csc", StringComparison.OrdinalIgnoreCase))
|
|
|
|
{
|
|
|
|
MigrationErrorCodes.MIGRATE20013(
|
2016-12-16 19:45:43 -08:00
|
|
|
String.Format(LocalizableStrings.CannotMigrateProjectWithCompilerError, defaultProjectContext.ProjectFile.ProjectFilePath, compilerName)).Throw();
|
2016-08-22 12:21:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-08 14:40:46 -07:00
|
|
|
private string FormatDiagnosticMessage(DiagnosticMessage d)
|
|
|
|
{
|
2016-12-16 19:45:43 -08:00
|
|
|
return String.Format(LocalizableStrings.DiagnosticMessageTemplate, d.Message, d.StartLine, d.SourceFilePath);
|
2016-09-08 14:40:46 -07:00
|
|
|
}
|
|
|
|
|
2016-08-23 13:50:05 -07:00
|
|
|
private void SetupOutputDirectory(string projectDirectory, string outputDirectory)
|
|
|
|
{
|
|
|
|
if (!Directory.Exists(outputDirectory))
|
|
|
|
{
|
|
|
|
Directory.CreateDirectory(outputDirectory);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (projectDirectory != outputDirectory)
|
|
|
|
{
|
|
|
|
CopyProjectToOutputDirectory(projectDirectory, outputDirectory);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void CopyProjectToOutputDirectory(string projectDirectory, string outputDirectory)
|
|
|
|
{
|
|
|
|
var sourceFilePaths = Directory.EnumerateFiles(projectDirectory, "*", SearchOption.AllDirectories);
|
|
|
|
|
|
|
|
foreach (var sourceFilePath in sourceFilePaths)
|
|
|
|
{
|
|
|
|
var relativeFilePath = PathUtility.GetRelativePath(projectDirectory, sourceFilePath);
|
|
|
|
var destinationFilePath = Path.Combine(outputDirectory, relativeFilePath);
|
|
|
|
var destinationDirectory = Path.GetDirectoryName(destinationFilePath);
|
|
|
|
|
|
|
|
if (!Directory.Exists(destinationDirectory))
|
|
|
|
{
|
|
|
|
Directory.CreateDirectory(destinationDirectory);
|
|
|
|
}
|
|
|
|
|
|
|
|
File.Copy(sourceFilePath, destinationFilePath);
|
|
|
|
}
|
|
|
|
}
|
2016-08-22 12:21:34 -07:00
|
|
|
|
2016-09-23 00:30:41 -07:00
|
|
|
public bool IsMigrated(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
|
|
|
|
{
|
2016-10-04 14:59:04 -07:00
|
|
|
var outputName = migrationRuleInputs.DefaultProjectContext.GetProjectName();
|
2016-09-23 00:30:41 -07:00
|
|
|
|
|
|
|
var outputProject = Path.Combine(migrationSettings.OutputDirectory, outputName + ".csproj");
|
|
|
|
return File.Exists(outputProject);
|
|
|
|
}
|
|
|
|
|
2016-08-22 12:21:34 -07:00
|
|
|
}
|
|
|
|
}
|