Migrate xproj to csproj dependencies

This commit is contained in:
Bryan Thornbury 2016-09-21 17:27:02 -07:00
parent 08fbe7f9d1
commit 5d2f0579d2
14 changed files with 166 additions and 35 deletions

View file

@ -1,4 +1,6 @@
using System; // 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 Microsoft.DotNet.ProjectJsonMigration namespace Microsoft.DotNet.ProjectJsonMigration
{ {
@ -19,7 +21,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration
public void Throw() public void Throw()
{ {
throw new Exception(GetFormattedErrorMessage()); throw new MigrationException(GetFormattedErrorMessage());
} }
public string GetFormattedErrorMessage() public string GetFormattedErrorMessage()

View file

@ -1,4 +1,6 @@
using System; // 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 Microsoft.DotNet.ProjectJsonMigration namespace Microsoft.DotNet.ProjectJsonMigration
{ {

View file

@ -0,0 +1,11 @@
// 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 Microsoft.DotNet.ProjectJsonMigration
{
public class MigrationException : Exception
{
public MigrationException(string message) : base(message) { }
}
}

View file

@ -15,6 +15,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration
{ {
public class MigrationRuleInputs public class MigrationRuleInputs
{ {
public ProjectRootElement ProjectXproj { get; }
public ProjectRootElement OutputMSBuildProject { get; } public ProjectRootElement OutputMSBuildProject { get; }
public ProjectItemGroupElement CommonItemGroup { get; } public ProjectItemGroupElement CommonItemGroup { get; }
@ -35,8 +37,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration
IEnumerable<ProjectContext> projectContexts, IEnumerable<ProjectContext> projectContexts,
ProjectRootElement outputMSBuildProject, ProjectRootElement outputMSBuildProject,
ProjectItemGroupElement commonItemGroup, ProjectItemGroupElement commonItemGroup,
ProjectPropertyGroupElement commonPropertyGroup) ProjectPropertyGroupElement commonPropertyGroup,
ProjectRootElement projectXproj=null)
{ {
ProjectXproj = projectXproj;
ProjectContexts = projectContexts; ProjectContexts = projectContexts;
OutputMSBuildProject = outputMSBuildProject; OutputMSBuildProject = outputMSBuildProject;
CommonItemGroup = commonItemGroup; CommonItemGroup = commonItemGroup;

View file

@ -15,21 +15,24 @@ namespace Microsoft.DotNet.ProjectJsonMigration
{ {
public class MigrationSettings public class MigrationSettings
{ {
public string ProjectXProjFilePath { get; }
public string ProjectDirectory { get; } public string ProjectDirectory { get; }
public string OutputDirectory { get; } public string OutputDirectory { get; }
public string SdkPackageVersion { get; } public string SdkPackageVersion { get; }
public ProjectRootElement MSBuildProjectTemplate { get; } public ProjectRootElement MSBuildProjectTemplate { get; }
public MigrationSettings( public MigrationSettings(
string projectDirectory, string projectDirectory,
string outputDirectory, string outputDirectory,
string sdkPackageVersion, string sdkPackageVersion,
ProjectRootElement msBuildProjectTemplate) ProjectRootElement msBuildProjectTemplate,
string projectXprojFilePath=null)
{ {
ProjectDirectory = projectDirectory; ProjectDirectory = projectDirectory;
OutputDirectory = outputDirectory; OutputDirectory = outputDirectory;
SdkPackageVersion = sdkPackageVersion; SdkPackageVersion = sdkPackageVersion;
MSBuildProjectTemplate = msBuildProjectTemplate; MSBuildProjectTemplate = msBuildProjectTemplate;
ProjectXProjFilePath = projectXprojFilePath;
} }
} }
} }

View file

@ -1,4 +1,6 @@
using System; // 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.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Microsoft.DotNet.ProjectJsonMigration namespace Microsoft.DotNet.ProjectJsonMigration

View file

@ -46,6 +46,13 @@ namespace Microsoft.DotNet.ProjectJsonMigration
private MigrationRuleInputs ComputeMigrationRuleInputs(MigrationSettings migrationSettings) private MigrationRuleInputs ComputeMigrationRuleInputs(MigrationSettings migrationSettings)
{ {
var projectContexts = ProjectContext.CreateContextForEachFramework(migrationSettings.ProjectDirectory); var projectContexts = ProjectContext.CreateContextForEachFramework(migrationSettings.ProjectDirectory);
var xprojFile = migrationSettings.ProjectXProjFilePath ?? FindXprojFile(migrationSettings.ProjectDirectory);
ProjectRootElement xproj = null;
if (xprojFile != null)
{
xproj = ProjectRootElement.Open(xprojFile);
}
var templateMSBuildProject = migrationSettings.MSBuildProjectTemplate; var templateMSBuildProject = migrationSettings.MSBuildProjectTemplate;
if (templateMSBuildProject == null) if (templateMSBuildProject == null)
@ -56,7 +63,19 @@ namespace Microsoft.DotNet.ProjectJsonMigration
var propertyGroup = templateMSBuildProject.AddPropertyGroup(); var propertyGroup = templateMSBuildProject.AddPropertyGroup();
var itemGroup = templateMSBuildProject.AddItemGroup(); var itemGroup = templateMSBuildProject.AddItemGroup();
return new MigrationRuleInputs(projectContexts, templateMSBuildProject, itemGroup, propertyGroup); 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)
{
throw new Exception("Multiple xproj files found in {projectDirectory}, please specify which to use");
}
return allXprojFiles.FirstOrDefault();
} }
private void VerifyInputs(MigrationRuleInputs migrationRuleInputs, MigrationSettings migrationSettings) private void VerifyInputs(MigrationRuleInputs migrationRuleInputs, MigrationSettings migrationSettings)

View file

@ -12,9 +12,7 @@ using Newtonsoft.Json.Linq;
using NuGet.Frameworks; using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectJsonMigration.Rules namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{ { // TODO: Should All build options be protected by a configuration condition?
// TODO: Should All build options be protected by a configuration condition?
// This will prevent the entire merge issue altogether and sidesteps the problem of having a duplicate include with different excludes...
public class MigrateBuildOptionsRule : IMigrationRule public class MigrateBuildOptionsRule : IMigrationRule
{ {
private AddPropertyTransform<CommonCompilerOptions>[] EmitEntryPointTransforms private AddPropertyTransform<CommonCompilerOptions>[] EmitEntryPointTransforms

View file

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. // 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. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -27,17 +28,75 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{ {
_projectDirectory = migrationSettings.ProjectDirectory; _projectDirectory = migrationSettings.ProjectDirectory;
var csproj = migrationRuleInputs.OutputMSBuildProject;
var migratedXProjDependencyPaths = MigrateXProjProjectDependencies(migrationSettings, migrationRuleInputs);
var migratedXProjDependencyNames = migratedXProjDependencyPaths.Select(p => Path.GetFileNameWithoutExtension(p));
MigrateProjectJsonProjectDependencies(migrationSettings, migrationRuleInputs, migratedXProjDependencyNames);
}
private IEnumerable<string> MigrateXProjProjectDependencies(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var xproj = migrationRuleInputs.ProjectXproj;
if (xproj == null)
{
MigrationTrace.Instance.WriteLine($"{nameof(MigrateProjectDependenciesRule)}: No xproj file given.");
return Enumerable.Empty<string>();
}
var projectReferenceItems = xproj.Items.Where(i => i.ItemType == "ProjectReference");
IEnumerable<string> projectReferences = new List<string>();
foreach (var projectReferenceItem in projectReferenceItems)
{
projectReferences = projectReferences.Union(projectReferenceItem.Includes());
}
var csprojReferences = projectReferences
.Where(p => string.Equals(Path.GetExtension(p), ".csproj", StringComparison.OrdinalIgnoreCase));
MigrationTrace.Instance.WriteLine($"{nameof(MigrateProjectDependenciesRule)}: Migrating {csprojReferences.Count()} xproj to csproj references");
var csprojReferenceTransforms = csprojReferences.Select(r => ProjectDependencyStringTransform.Transform(r));
foreach (var csprojReferenceTransform in csprojReferenceTransforms)
{
_transformApplicator.Execute(csprojReferenceTransform, migrationRuleInputs.CommonItemGroup);
}
return csprojReferences;
}
public void MigrateProjectJsonProjectDependencies(
MigrationSettings migrationSettings,
MigrationRuleInputs migrationRuleInputs,
IEnumerable<string> migratedXProjDependencyNames)
{
var outputMSBuildProject = migrationRuleInputs.OutputMSBuildProject;
var projectContext = migrationRuleInputs.DefaultProjectContext; var projectContext = migrationRuleInputs.DefaultProjectContext;
var projectExports = projectContext.CreateExporter("_").GetDependencies(LibraryType.Project); var projectExports = projectContext.CreateExporter("_").GetDependencies(LibraryType.Project);
var projectDependencyTransformResults = var projectDependencyTransformResults = new List<ProjectItemElement>();
projectExports.Select(projectExport => ProjectDependencyTransform.Transform(projectExport)); foreach (var projectExport in projectExports)
{
try
{
projectDependencyTransformResults.Add(ProjectDependencyTransform.Transform(projectExport));
}
catch (MigrationException unresolvedProjectReferenceException)
{
if (!migratedXProjDependencyNames.Contains(projectExport.Library.Identity.Name))
{
throw unresolvedProjectReferenceException;
}
MigrationTrace.Instance.WriteLine($"{nameof(MigrateProjectDependenciesRule)}: Ignoring unresolved project reference {projectExport.Library.Identity.Name} satisfied by xproj to csproj ProjectReference");
}
}
if (projectDependencyTransformResults.Any()) if (projectDependencyTransformResults.Any())
{ {
AddPropertyTransformsToCommonPropertyGroup(migrationRuleInputs.CommonPropertyGroup); AddPropertyTransformsToCommonPropertyGroup(migrationRuleInputs.CommonPropertyGroup);
AddProjectDependenciesToNewItemGroup(csproj.AddItemGroup(), projectDependencyTransformResults); AddProjectDependenciesToNewItemGroup(outputMSBuildProject.AddItemGroup(), projectDependencyTransformResults);
} }
} }
@ -92,5 +151,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
}, },
export => "", export => "",
export => true); export => true);
private AddItemTransform<string> ProjectDependencyStringTransform => new AddItemTransform<string>(
"ProjectReference",
path => path,
path => "",
path => true);
} }
} }

View file

@ -1,12 +0,0 @@
using System;
namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
public class MigrateXprojProjectReferencesRule : IMigrationRule
{
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
throw new NotImplementedException("TODO: XProj ProjectToProject references");
}
}
}

View file

@ -16,15 +16,17 @@ namespace Microsoft.DotNet.Tools.Migrate
private readonly string _outputDirectory; private readonly string _outputDirectory;
private readonly string _projectJson; private readonly string _projectJson;
private readonly string _sdkVersion; private readonly string _sdkVersion;
private readonly string _xprojFilePath;
private readonly TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject; private readonly TemporaryDotnetNewTemplateProject _temporaryDotnetNewProject;
public MigrateCommand(string templateFile, string outputDirectory, string projectJson, string sdkVersion) public MigrateCommand(string templateFile, string outputDirectory, string projectJson, string sdkVersion, string xprojFilePath)
{ {
_templateFile = templateFile; _templateFile = templateFile;
_outputDirectory = outputDirectory; _outputDirectory = outputDirectory;
_projectJson = projectJson; _projectJson = projectJson;
_sdkVersion = sdkVersion; _sdkVersion = sdkVersion;
_xprojFilePath = xprojFilePath;
_temporaryDotnetNewProject = new TemporaryDotnetNewTemplateProject(); _temporaryDotnetNewProject = new TemporaryDotnetNewTemplateProject();
} }
@ -44,7 +46,7 @@ namespace Microsoft.DotNet.Tools.Migrate
var sdkVersion = _sdkVersion ?? new ProjectJsonParser(_temporaryDotnetNewProject.ProjectJson).SdkPackageVersion; var sdkVersion = _sdkVersion ?? new ProjectJsonParser(_temporaryDotnetNewProject.ProjectJson).SdkPackageVersion;
EnsureNotNull(sdkVersion, "Null Sdk Version"); EnsureNotNull(sdkVersion, "Null Sdk Version");
var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, msBuildTemplate); var migrationSettings = new MigrationSettings(projectDirectory, outputDirectory, sdkVersion, msBuildTemplate, _xprojFilePath);
new ProjectMigrator().Migrate(migrationSettings); new ProjectMigrator().Migrate(migrationSettings);
return 0; return 0;

View file

@ -24,6 +24,7 @@ namespace Microsoft.DotNet.Tools.Migrate
CommandOption output = app.Option("-o|--output", "Directory to output migrated project to. The default is the project directory", 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 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 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);
app.OnExecute(() => app.OnExecute(() =>
{ {
@ -31,7 +32,8 @@ namespace Microsoft.DotNet.Tools.Migrate
template.Value(), template.Value(),
output.Value(), output.Value(),
project.Value(), project.Value(),
sdkVersion.Value()); sdkVersion.Value(),
xprojFile.Value());
return migrateCommand.Execute(); return migrateCommand.Execute();
}); });

View file

@ -53,7 +53,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
var projectContext = ProjectContext.Create(appDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10); var projectContext = ProjectContext.Create(appDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10);
var mockProj = ProjectRootElement.Create(); var mockProj = ProjectRootElement.Create();
var testSettings = new MigrationSettings(appDirectory, appDirectory, "1.0.0", mockProj); var testSettings = new MigrationSettings(appDirectory, appDirectory, "1.0.0", mockProj, null);
var testInputs = new MigrationRuleInputs(new[] {projectContext}, mockProj, mockProj.AddItemGroup(), var testInputs = new MigrationRuleInputs(new[] {projectContext}, mockProj, mockProj.AddItemGroup(),
mockProj.AddPropertyGroup()); mockProj.AddPropertyGroup());
new MigrateProjectDependenciesRule().Apply(testSettings, testInputs); new MigrateProjectDependenciesRule().Apply(testSettings, testInputs);
@ -82,5 +82,37 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
action.ShouldThrow<Exception>() action.ShouldThrow<Exception>()
.Where(e => e.Message.Contains("MIGRATE1014::Unresolved Dependency: Unresolved project dependency (TestLibrary)")); .Where(e => e.Message.Contains("MIGRATE1014::Unresolved Dependency: Unresolved project dependency (TestLibrary)"));
} }
[Theory]
[InlineData(@"some/path/to.cSproj", new [] { @"some/path/to.cSproj" })]
[InlineData(@"to.CSPROJ",new [] { @"to.CSPROJ" })]
public void It_migrates_csproj_ProjectReference_in_xproj(string projectReference, string[] expectedMigratedReferences)
{
var xproj = ProjectRootElement.Create();
xproj.AddItem("ProjectReference", projectReference);
var projectReferenceName = Path.GetFileNameWithoutExtension(projectReference);
var projectJson = @"
{
""dependencies"": {" +
$"\"{projectReferenceName}\"" + @": {
""target"" : ""project""
}
}
}
";
Console.WriteLine(projectJson);
var testDirectory = Temp.CreateDirectory().Path;
var migratedProj = TemporaryProjectFileRuleRunner.RunRules(new IMigrationRule[]
{
new MigrateProjectDependenciesRule()
}, projectJson, testDirectory, xproj);
var migratedProjectReferenceItems = migratedProj.Items.Where(i => i.ItemType == "ProjectReference");
migratedProjectReferenceItems.Should().HaveCount(expectedMigratedReferences.Length);
migratedProjectReferenceItems.Select(m => m.Include).Should().BeEquivalentTo(expectedMigratedReferences);
}
} }
} }

View file

@ -9,10 +9,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
public class TemporaryProjectFileRuleRunner public class TemporaryProjectFileRuleRunner
{ {
public static ProjectRootElement RunRules(IEnumerable<IMigrationRule> rules, string projectJson, public static ProjectRootElement RunRules(IEnumerable<IMigrationRule> rules, string projectJson,
string testDirectory) string testDirectory, ProjectRootElement xproj=null)
{ {
var projectContext = GenerateProjectContextFromString(testDirectory, projectJson); var projectContext = GenerateProjectContextFromString(testDirectory, projectJson);
return RunMigrationRulesOnGeneratedProject(rules, projectContext, testDirectory); return RunMigrationRulesOnGeneratedProject(rules, projectContext, testDirectory, xproj);
} }
private static ProjectContext GenerateProjectContextFromString(string projectDirectory, string json) private static ProjectContext GenerateProjectContextFromString(string projectDirectory, string json)
@ -25,13 +25,14 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
} }
private static ProjectRootElement RunMigrationRulesOnGeneratedProject(IEnumerable<IMigrationRule> rules, private static ProjectRootElement RunMigrationRulesOnGeneratedProject(IEnumerable<IMigrationRule> rules,
ProjectContext projectContext, string testDirectory) ProjectContext projectContext, string testDirectory, ProjectRootElement xproj)
{ {
var project = ProjectRootElement.Create(); var project = ProjectRootElement.Create();
var testSettings = new MigrationSettings(testDirectory, testDirectory, "1.0.0", project); var testSettings = new MigrationSettings(testDirectory, testDirectory, "1.0.0", project);
var testInputs = new MigrationRuleInputs(new[] {projectContext}, project, var testInputs = new MigrationRuleInputs(new[] {projectContext}, project,
project.AddItemGroup(), project.AddItemGroup(),
project.AddPropertyGroup()); project.AddPropertyGroup(),
xproj);
foreach (var rule in rules) foreach (var rule in rules)
{ {