dotnet-installer/src/Microsoft.DotNet.ProjectJsonMigration/ProjectMigrator.cs

266 lines
11 KiB
C#
Raw Normal View History

// 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;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
2016-08-23 13:50:05 -07:00
using Microsoft.DotNet.ProjectJsonMigration.Rules;
using Microsoft.DotNet.Tools.Common;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class ProjectMigrator
{
2016-08-23 13:50:05 -07:00
private readonly IMigrationRule _ruleSet;
private readonly ProjectDependencyFinder _projectDependencyFinder = new ProjectDependencyFinder();
2016-08-23 13:50:05 -07:00
public ProjectMigrator() : this(new DefaultMigrationRuleSet()) { }
public ProjectMigrator(IMigrationRule ruleSet)
{
_ruleSet = ruleSet;
}
2016-10-04 14:59:04 -07:00
public MigrationReport Migrate(MigrationSettings rootSettings, bool skipProjectReferences = false)
{
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;
var tempMSBuildProjectTemplate = rootSettings.MSBuildProjectTemplate.DeepClone();
2016-10-04 10:45:57 -07:00
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,
rootSettings.ProjectXProjFilePath);
}
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,
rootInputs?.DefaultProjectContext.GetProjectName(),
new List<MigrationError> {e.Error},
null)
});
2016-10-04 10:45:57 -07:00
}
2016-10-04 14:59:04 -07:00
var projectMigrationReports = new List<ProjectMigrationReport>();
projectMigrationReports.Add(MigrateProject(rootSettings));
if (skipProjectReferences)
{
2016-10-04 14:59:04 -07:00
return new MigrationReport(projectMigrationReports);
}
foreach(var project in projectDependencies)
{
var projectDir = Path.GetDirectoryName(project.ProjectFilePath);
var settings = new MigrationSettings(projectDir,
projectDir,
rootSettings.SdkPackageVersion,
2016-10-05 16:25:04 -07:00
tempMSBuildProjectTemplate);
2016-09-23 14:48:54 -07:00
MigrateProject(settings);
2016-10-04 14:59:04 -07:00
projectMigrationReports.Add(MigrateProject(settings));
}
2016-10-04 14:59:04 -07:00
return new MigrationReport(projectMigrationReports);
}
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-09-23 14:48:54 -07:00
private IEnumerable<ProjectDependency> ResolveTransitiveClosureProjectDependencies(string rootProject, string xprojFile)
{
HashSet<ProjectDependency> projectsMap = new HashSet<ProjectDependency>(new ProjectDependencyComparer());
2016-09-23 14:48:54 -07:00
var projectDependencies = _projectDependencyFinder.ResolveProjectDependencies(rootProject, xprojFile);
Queue<ProjectDependency> projectsQueue = new Queue<ProjectDependency>(projectDependencies);
while (projectsQueue.Count() != 0)
{
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);
foreach (var project in projectDependencies)
{
projectsQueue.Enqueue(project);
}
}
return projectsMap;
}
2016-10-04 14:59:04 -07:00
private ProjectMigrationReport MigrateProject(MigrationSettings migrationSettings)
{
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-10-04 14:59:04 -07:00
try
{
if (IsMigrated(migrationSettings, migrationRuleInputs))
{
MigrationTrace.Instance.WriteLine($"{nameof(ProjectMigrator)}: Skip migrating {migrationSettings.ProjectDirectory}, it is already migrated.");
return new ProjectMigrationReport(migrationSettings.ProjectDirectory, projectName, skipped: true);
}
VerifyInputs(migrationRuleInputs, migrationSettings);
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);
}
var outputProject = Path.Combine(migrationSettings.OutputDirectory, projectName + ".csproj");
return new ProjectMigrationReport(migrationSettings.ProjectDirectory, projectName, outputProject, null);
}
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-09-21 17:27:02 -07:00
ProjectRootElement xproj = null;
if (xprojFile != null)
{
xproj = ProjectRootElement.Open(xprojFile);
}
2016-08-23 13:50:05 -07:00
var templateMSBuildProject = migrationSettings.MSBuildProjectTemplate;
if (templateMSBuildProject == null)
{
throw new Exception("Expected non-null MSBuildProjectTemplate in MigrationSettings");
}
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-23 13:50:05 -07:00
VerifyProject(migrationRuleInputs.ProjectContexts, migrationSettings.ProjectDirectory);
}
2016-08-23 13:50:05 -07:00
private void VerifyProject(IEnumerable<ProjectContext> projectContexts, string projectDirectory)
{
2016-08-23 13:50:05 -07:00
if (!projectContexts.Any())
{
2016-08-23 13:50:05 -07:00
MigrationErrorCodes.MIGRATE1013($"No projects found in {projectDirectory}").Throw();
}
2016-08-23 13:50:05 -07:00
var defaultProjectContext = projectContexts.First();
var diagnostics = defaultProjectContext.ProjectFile.Diagnostics;
if (diagnostics.Any())
{
MigrationErrorCodes.MIGRATE1011(
2016-09-08 14:40:46 -07:00
$"{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(
$"Cannot migrate project {defaultProjectContext.ProjectFile.ProjectFilePath} using compiler {compilerName}").Throw();
}
}
2016-09-08 14:40:46 -07:00
private string FormatDiagnosticMessage(DiagnosticMessage d)
{
return $"{d.Message} (line: {d.StartLine}, file: {d.SourceFilePath})";
}
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);
}
}
public bool IsMigrated(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
2016-10-04 14:59:04 -07:00
var outputName = migrationRuleInputs.DefaultProjectContext.GetProjectName();
var outputProject = Path.Combine(migrationSettings.OutputDirectory, outputName + ".csproj");
return File.Exists(outputProject);
}
}
}