From ecbc45098d95c1a01da0dd7b2c711e989280e50c Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Thu, 20 Oct 2016 11:00:41 -0700 Subject: [PATCH] Add support for Migration defaults (#4242) * Add support for sdk props/targets defaults to Migration. * fix the transform applicator * remove transform applicator dependency on insertion order for item merges * defaults constructor msbuild version change --- .../DefaultMigrationRuleSet.cs | 3 + .../MigrationSettings.cs | 5 +- .../Models/DefaultProjectItemInfo.cs | 17 + .../Models/DefaultProjectPropertyInfo.cs | 17 + .../SerializableMigrationDefaultsInfo.cs | 13 + .../Rules/AddDefaultsToProjectRule.cs | 128 +++++++ .../Rules/CleanOutputProjectRule.cs | 73 ++++ .../Rules/MigrateBuildOptionsRule.cs | 12 +- .../Rules/MigrateJsonPropertiesRule.cs | 2 +- .../Rules/MigratePackOptionsRule.cs | 2 +- .../MigratePackageDependenciesAndToolsRule.cs | 19 +- .../Rules/MigrateProjectDependenciesRule.cs | 6 +- .../Rules/MigrateRootOptionsRule.cs | 2 +- .../Rules/MigrateTFMRule.cs | 6 +- .../Rules/RemoveDefaultsFromProjectRule.cs | 28 ++ .../Rules/SaveOutputProjectRule.cs | 15 - .../Transforms/ItemTransformApplicator.cs | 333 ++++++++++++++++++ .../Transforms/PropertyTransformApplicator.cs | 131 +++++++ .../project.json | 8 +- .../sdkdefaults.json | 178 ++++++++++ .../transforms/ITransformApplicator.cs | 16 +- .../transforms/TransformApplicator.cs | 252 ++----------- .../GivenThatIWantToMigrateBuildOptions.cs | 19 +- .../Transforms/GivenATransformApplicator.cs | 23 +- tools/MigrationDefaultsConstructor/Program.cs | 193 ++++++++++ tools/MigrationDefaultsConstructor/README.md | 7 + .../MigrationDefaultsConstructor/project.json | 23 ++ tools/MigrationDefaultsConstructor/run.sh | 22 ++ 28 files changed, 1251 insertions(+), 302 deletions(-) create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectItemInfo.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectPropertyInfo.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Models/SerializableMigrationDefaultsInfo.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/AddDefaultsToProjectRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/CleanOutputProjectRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Rules/RemoveDefaultsFromProjectRule.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Transforms/ItemTransformApplicator.cs create mode 100644 src/Microsoft.DotNet.ProjectJsonMigration/Transforms/PropertyTransformApplicator.cs create mode 100755 src/Microsoft.DotNet.ProjectJsonMigration/sdkdefaults.json create mode 100755 tools/MigrationDefaultsConstructor/Program.cs create mode 100644 tools/MigrationDefaultsConstructor/README.md create mode 100755 tools/MigrationDefaultsConstructor/project.json create mode 100755 tools/MigrationDefaultsConstructor/run.sh diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs b/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs index 6b93926f8..f12f419c7 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/DefaultMigrationRuleSet.cs @@ -9,6 +9,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration { private IMigrationRule[] Rules => new IMigrationRule[] { + new AddDefaultsToProjectRule(), new MigrateRootOptionsRule(), new MigrateTFMRule(), new MigrateBuildOptionsRule(), @@ -20,6 +21,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration new MigratePackageDependenciesAndToolsRule(), new MigrateConfigurationsRule(), new MigrateScriptsRule(), + new RemoveDefaultsFromProjectRule(), + new CleanOutputProjectRule(), new SaveOutputProjectRule() }; diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs index 60d266b44..594fa53c4 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MigrationSettings.cs @@ -12,19 +12,22 @@ namespace Microsoft.DotNet.ProjectJsonMigration public string OutputDirectory { get; } public string SdkPackageVersion { get; } public ProjectRootElement MSBuildProjectTemplate { get; } + public string SdkDefaultsFilePath { get; } public MigrationSettings( string projectDirectory, string outputDirectory, string sdkPackageVersion, ProjectRootElement msBuildProjectTemplate, - string projectXprojFilePath=null) + string projectXprojFilePath=null, + string sdkDefaultsFilePath=null) { ProjectDirectory = projectDirectory; OutputDirectory = outputDirectory; SdkPackageVersion = sdkPackageVersion; MSBuildProjectTemplate = msBuildProjectTemplate != null ? msBuildProjectTemplate.DeepClone() : null; ProjectXProjFilePath = projectXprojFilePath; + SdkDefaultsFilePath = sdkDefaultsFilePath; } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectItemInfo.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectItemInfo.cs new file mode 100644 index 000000000..5f1f31977 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectItemInfo.cs @@ -0,0 +1,17 @@ +// 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.Collections.Generic; + +namespace Microsoft.DotNet.ProjectJsonMigration.Models +{ + public class DefaultProjectItemInfo + { + public string ItemType {get; set;} + public string Include {get; set;} + public string Exclude {get; set;} + public string Remove {get; set;} + public string Condition {get; set;} + public string ParentCondition {get; set;} + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectPropertyInfo.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectPropertyInfo.cs new file mode 100644 index 000000000..d228e0179 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Models/DefaultProjectPropertyInfo.cs @@ -0,0 +1,17 @@ +// 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.Collections.Generic; + +namespace Microsoft.DotNet.ProjectJsonMigration.Models +{ + public class DefaultProjectPropertyInfo + { + public string Name {get; set;} + public string Value {get; set;} + public string Condition {get; set;} + public string ParentCondition {get; set;} + } +} + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Models/SerializableMigrationDefaultsInfo.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Models/SerializableMigrationDefaultsInfo.cs new file mode 100644 index 000000000..34a500864 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Models/SerializableMigrationDefaultsInfo.cs @@ -0,0 +1,13 @@ +// 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.Collections.Generic; + +namespace Microsoft.DotNet.ProjectJsonMigration.Models +{ + public class SerializableMigrationDefaultsInfo + { + public IEnumerable Items { get; set; } + public IEnumerable Properties { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/AddDefaultsToProjectRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/AddDefaultsToProjectRule.cs new file mode 100644 index 000000000..bbc214d14 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/AddDefaultsToProjectRule.cs @@ -0,0 +1,128 @@ +// 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.Linq; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; +using Microsoft.Build.Construction; +using System.Collections.Generic; +using Microsoft.DotNet.ProjectJsonMigration.Models; +using System; +using System.IO; +using System.Reflection; +using Newtonsoft.Json; + +namespace Microsoft.DotNet.ProjectJsonMigration.Rules +{ + public class AddDefaultsToProjectRule : IMigrationRule + { + internal const string c_DefaultsProjectElementContainerLabel = "MigrationDefaultsTempContainer"; + internal const string c_SdkDefaultsJsonFileName = "sdkdefaults.json"; + + private readonly ITransformApplicator _transformApplicator; + + public AddDefaultsToProjectRule(ITransformApplicator transformApplicator = null) + { + _transformApplicator = transformApplicator ?? new TransformApplicator(); + } + + public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) + { + SerializableMigrationDefaultsInfo defaults = ResolveDefaults(migrationSettings); + + var project = migrationRuleInputs.OutputMSBuildProject; + var defaultsPropertyGroups = new Dictionary(); + var defaultsItemGroups = new Dictionary(); + + AddDefaultPropertiesToProject(defaults, project, defaultsPropertyGroups); + AddDefaultItemsToProject(defaults, project, defaultsItemGroups); + } + + private void AddDefaultItemsToProject( + SerializableMigrationDefaultsInfo defaults, + ProjectRootElement project, + Dictionary defaultsItemGroups) + { + foreach (var itemInfo in defaults.Items) + { + ProjectItemGroupElement itemGroup; + var parentCondition = itemInfo.ParentCondition ?? string.Empty; + + if (!defaultsItemGroups.TryGetValue(parentCondition, out itemGroup)) + { + itemGroup = project.AddItemGroup(); + itemGroup.Label = c_DefaultsProjectElementContainerLabel; + itemGroup.Condition = parentCondition; + + defaultsItemGroups[parentCondition] = itemGroup; + } + + var item = itemGroup.AddItem(itemInfo.ItemType, itemInfo.Include); + item.Exclude = itemInfo.Exclude; + item.Remove = itemInfo.Remove; + item.Condition = itemInfo.Condition; + } + } + + private static void AddDefaultPropertiesToProject( + SerializableMigrationDefaultsInfo defaults, + ProjectRootElement project, + Dictionary defaultsPropertyGroups) + { + foreach (var propertyInfo in defaults.Properties) + { + ProjectPropertyGroupElement propertyGroup; + var parentCondition = propertyInfo.ParentCondition ?? string.Empty; + + if (!defaultsPropertyGroups.TryGetValue(parentCondition, out propertyGroup)) + { + propertyGroup = project.AddPropertyGroup(); + propertyGroup.Label = c_DefaultsProjectElementContainerLabel; + propertyGroup.Condition = parentCondition; + + defaultsPropertyGroups[parentCondition] = propertyGroup; + } + + var property = propertyGroup.AddProperty(propertyInfo.Name, propertyInfo.Value); + property.Condition = propertyInfo.Condition; + } + } + + private SerializableMigrationDefaultsInfo ResolveDefaults(MigrationSettings migrationSettings) + { + var sdkDefaultFile = TryResolveSdkDefaultsFileFromSettings(migrationSettings); + if (sdkDefaultFile != null) + { + return DeserializeDefaults(File.ReadAllText(sdkDefaultFile)); + } + + var thisAssembly = typeof(AddDefaultsToProjectRule).GetTypeInfo().Assembly; + using (var resource = thisAssembly.GetManifestResourceStream("Microsoft.DotNet.ProjectJsonMigration." + c_SdkDefaultsJsonFileName)) + { + using (StreamReader reader = new StreamReader(resource)) + { + return DeserializeDefaults(reader.ReadToEnd()); + } + } + } + + private string TryResolveSdkDefaultsFileFromSettings(MigrationSettings migrationSettings) + { + var candidate = migrationSettings.SdkDefaultsFilePath; + if (candidate == null) + { + return null; + } + + if (File.Exists(candidate)) + { + return candidate; + } + return null; + } + + private SerializableMigrationDefaultsInfo DeserializeDefaults(string sdkDefaultJson) + { + return JsonConvert.DeserializeObject(sdkDefaultJson); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/CleanOutputProjectRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/CleanOutputProjectRule.cs new file mode 100644 index 000000000..3d58a006c --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/CleanOutputProjectRule.cs @@ -0,0 +1,73 @@ +// 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.Linq; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; +using Microsoft.Build.Construction; +using System.Collections.Generic; +using Microsoft.DotNet.ProjectJsonMigration.Models; +using System; +using System.IO; +using System.Reflection; +using Newtonsoft.Json; + +namespace Microsoft.DotNet.ProjectJsonMigration.Rules +{ + public class CleanOutputProjectRule : IMigrationRule + { + public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) + { + var outputProject = migrationRuleInputs.OutputMSBuildProject; + + CleanEmptyPropertiesAndItems(outputProject); + CleanPropertiesThatDontChangeValue(outputProject); + CleanEmptyPropertyAndItemGroups(outputProject); + } + + private void CleanEmptyPropertyAndItemGroups(ProjectRootElement msbuildProject) + { + foreach (var propertyGroup in msbuildProject.PropertyGroups) + { + propertyGroup.RemoveIfEmpty(); + } + + foreach (var itemGroup in msbuildProject.ItemGroups) + { + itemGroup.RemoveIfEmpty(); + } + } + + private void CleanEmptyPropertiesAndItems(ProjectRootElement msbuildProject) + { + foreach (var property in msbuildProject.Properties) + { + if (string.IsNullOrEmpty(property.Value)) + { + property.Parent.RemoveChild(property); + } + } + + foreach (var item in msbuildProject.Items) + { + if (string.IsNullOrEmpty(item.Include)) + { + item.Parent.RemoveChild(item); + } + } + } + + private void CleanPropertiesThatDontChangeValue(ProjectRootElement msbuildProject) + { + foreach (var property in msbuildProject.Properties) + { + var value = property.Value.Trim(); + var variableExpectedValue = "$(" + property.Name + ")"; + + if (value == variableExpectedValue) + { + property.Parent.RemoveChild(property); + } + } + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs index 896cec64e..24c4a60b7 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateBuildOptionsRule.cs @@ -105,11 +105,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules @"$(OutputPath)\$(TargetFramework)\$(AssemblyName).xml", compilerOptions => compilerOptions.GenerateXmlDocumentation != null && compilerOptions.GenerateXmlDocumentation.Value); - private AddPropertyTransform OutputNameTransform => - new AddPropertyTransform("AssemblyName", - compilerOptions => compilerOptions.OutputName, - compilerOptions => !string.IsNullOrEmpty(compilerOptions.OutputName)); - private IncludeContextTransform CompileFilesTransform => new IncludeContextTransform("Compile", transformMappings: false); @@ -175,7 +170,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules DelaySignTransform, PublicSignTransform, DebugTypeTransform, - OutputNameTransform, XmlDocTransform, XmlDocTransformFilePath, PreserveCompilationContextTransform @@ -242,14 +236,14 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules if (!PropertiesAreEqual(nonConfigurationOutput, configurationOutput)) { - transformApplicator.Execute(configurationOutput, propertyGroup); + transformApplicator.Execute(configurationOutput, propertyGroup, mergeExisting: true); } } foreach (var includeContextTransformExecute in _includeContextTransformExecutes) { var nonConfigurationOutput = includeContextTransformExecute(compilerOptions, projectDirectory); - var configurationOutput = includeContextTransformExecute(configurationCompilerOptions, projectDirectory).ToArray(); + var configurationOutput = includeContextTransformExecute(configurationCompilerOptions, projectDirectory); transformApplicator.Execute(configurationOutput, itemGroup, mergeExisting: true); } @@ -293,7 +287,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules { foreach (var transform in _propertyTransforms) { - transformApplicator.Execute(transform.Transform(compilerOptions), propertyGroup); + transformApplicator.Execute(transform.Transform(compilerOptions), propertyGroup, mergeExisting: true); } foreach (var includeContextTransformExecute in _includeContextTransformExecutes) diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateJsonPropertiesRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateJsonPropertiesRule.cs index e8f771137..11b85ba99 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateJsonPropertiesRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateJsonPropertiesRule.cs @@ -43,7 +43,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules var token = rawProject.GetValue(prop.Key); if (token != null) { - _transformApplicator.Execute(prop.Value.Transform(token), propertyGroup); + _transformApplicator.Execute(prop.Value.Transform(token), propertyGroup, mergeExisting: true); } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackOptionsRule.cs index f90bdbd56..b630e3181 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackOptionsRule.cs @@ -93,7 +93,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules foreach (var propertyTransfrom in _propertyTransforms) { - _transformApplicator.Execute(propertyTransfrom.Transform(packOptions), propertyGroup); + _transformApplicator.Execute(propertyTransfrom.Transform(packOptions), propertyGroup, true); } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackageDependenciesAndToolsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackageDependenciesAndToolsRule.cs index 1c4e95348..236e3ae10 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackageDependenciesAndToolsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigratePackageDependenciesAndToolsRule.cs @@ -37,6 +37,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules var tfmDependencyMap = new Dictionary>(); var targetFrameworks = project.GetTargetFrameworks(); + var noFrameworkPackageReferenceItemGroup = migrationRuleInputs.OutputMSBuildProject.AddItemGroup(); + // Inject Sdk dependency _transformApplicator.Execute( PackageDependencyInfoTransform.Transform( @@ -45,7 +47,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules Name = ConstantPackageNames.CSdkPackageName, Version = migrationSettings.SdkPackageVersion, PrivateAssets = "All" - }), migrationRuleInputs.CommonItemGroup); + }), noFrameworkPackageReferenceItemGroup, mergeExisting: false); // Migrate Direct Deps first MigrateDependencies( @@ -53,7 +55,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules migrationRuleInputs.OutputMSBuildProject, null, project.Dependencies, - migrationRuleInputs.ProjectXproj); + migrationRuleInputs.ProjectXproj, + itemGroup: noFrameworkPackageReferenceItemGroup); MigrationTrace.Instance.WriteLine($"Migrating {targetFrameworks.Count()} target frameworks"); foreach (var targetFramework in targetFrameworks) @@ -80,7 +83,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules if (transform != null) { transform.Condition = targetFramework.FrameworkName.GetMSBuildCondition(); - _transformApplicator.Execute(transform, commonPropertyGroup); + _transformApplicator.Execute(transform, commonPropertyGroup, mergeExisting: true); } else { @@ -113,7 +116,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules foreach (var tool in project.Tools) { - _transformApplicator.Execute(ToolTransform.Transform(tool), itemGroup); + _transformApplicator.Execute(ToolTransform.Transform(tool), itemGroup, mergeExisting: true); } } @@ -122,13 +125,15 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules ProjectRootElement output, NuGetFramework framework, IEnumerable dependencies, - ProjectRootElement xproj) + ProjectRootElement xproj, + ProjectItemGroupElement itemGroup=null) { var projectDependencies = new HashSet(GetAllProjectReferenceNames(project, framework, xproj)); var packageDependencies = dependencies.Where(d => !projectDependencies.Contains(d.Name)).ToList(); string condition = framework?.GetMSBuildCondition() ?? ""; - var itemGroup = output.ItemGroups.FirstOrDefault(i => i.Condition == condition) + itemGroup = itemGroup + ?? output.ItemGroups.FirstOrDefault(i => i.Condition == condition) ?? output.AddItemGroup(); itemGroup.Condition = condition; @@ -163,7 +168,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules } } - _transformApplicator.Execute(transform.Transform(packageDependency), itemGroup); + _transformApplicator.Execute(transform.Transform(packageDependency), itemGroup, mergeExisting: true); } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs index 814d09acc..d2e346ede 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateProjectDependenciesRule.cs @@ -68,7 +68,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules foreach (var csprojTransformedReference in csprojTransformedReferences) { - _transformApplicator.Execute(csprojTransformedReference, migrationRuleInputs.CommonItemGroup); + _transformApplicator.Execute(csprojTransformedReference, migrationRuleInputs.CommonItemGroup, true); } return csprojTransformedReferences.SelectMany(r => r.Includes()); @@ -108,10 +108,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules foreach (var projectDependencyTransformResult in projectDependencyTransformResults) { - _transformApplicator.Execute(projectDependencyTransformResult, itemGroup); + _transformApplicator.Execute(projectDependencyTransformResult, itemGroup, true); } } - + private AddItemTransform ProjectDependencyTransform => new AddItemTransform( "ProjectReference", dep => diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs index 23c329d68..54f76b05f 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateRootOptionsRule.cs @@ -37,7 +37,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules foreach (var transformResult in transformResults) { - _transformApplicator.Execute(transformResult, propertyGroup); + _transformApplicator.Execute(transformResult, propertyGroup, true); } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs index 6952f8709..ac75851ee 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/MigrateTFMRule.cs @@ -35,14 +35,16 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules _transformApplicator.Execute( FrameworkTransform.Transform( migrationRuleInputs.ProjectContexts.Single().TargetFramework), - propertyGroup); + propertyGroup, + mergeExisting: true); } else { _transformApplicator.Execute( FrameworksTransform.Transform( migrationRuleInputs.ProjectContexts.Select(p => p.TargetFramework)), - propertyGroup); + propertyGroup, + mergeExisting: true); } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/RemoveDefaultsFromProjectRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/RemoveDefaultsFromProjectRule.cs new file mode 100644 index 000000000..5a18b23ff --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/RemoveDefaultsFromProjectRule.cs @@ -0,0 +1,28 @@ +// 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.Linq; +using Microsoft.DotNet.ProjectJsonMigration.Transforms; +using Microsoft.Build.Construction; +using System.Collections.Generic; +using Microsoft.DotNet.ProjectJsonMigration.Models; +using System; +using System.IO; +using System.Reflection; +using Newtonsoft.Json; + +namespace Microsoft.DotNet.ProjectJsonMigration.Rules +{ + public class RemoveDefaultsFromProjectRule : IMigrationRule + { + public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs) + { + foreach (var element in + migrationRuleInputs.OutputMSBuildProject.Children + .Where(c => c.Label == AddDefaultsToProjectRule.c_DefaultsProjectElementContainerLabel)) + { + element.Parent.RemoveChild(element); + } + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs index af471895c..dd4a88ee7 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Rules/SaveOutputProjectRule.cs @@ -14,22 +14,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules var outputProject = Path.Combine(migrationSettings.OutputDirectory, outputName + ".csproj"); - CleanEmptyPropertyAndItemGroups(migrationRuleInputs.OutputMSBuildProject); - migrationRuleInputs.OutputMSBuildProject.Save(outputProject); } - - private void CleanEmptyPropertyAndItemGroups(ProjectRootElement msbuildProject) - { - foreach (var propertyGroup in msbuildProject.PropertyGroups) - { - propertyGroup.RemoveIfEmpty(); - } - - foreach (var itemGroup in msbuildProject.ItemGroups) - { - itemGroup.RemoveIfEmpty(); - } - } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Transforms/ItemTransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Transforms/ItemTransformApplicator.cs new file mode 100644 index 000000000..258fbe9e5 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Transforms/ItemTransformApplicator.cs @@ -0,0 +1,333 @@ +// 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 System.Linq; + +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms +{ + public class ItemTransformApplicator : ITransformApplicator + { + private readonly ProjectRootElement _projectElementGenerator = ProjectRootElement.Create(); + + public void Execute( + T element, + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer + { + if (typeof(T) != typeof(ProjectItemElement)) + { + throw new ArgumentException($"Expected element to be of type {nameof(ProjectItemElement)}, but got {typeof(T)}"); + } + + if (typeof(U) != typeof(ProjectItemGroupElement)) + { + throw new ArgumentException($"Expected destinationElement to be of type {nameof(ProjectItemGroupElement)}, but got {typeof(U)}"); + } + + if (element == null) + { + return; + } + + if (destinationElement == null) + { + throw new ArgumentException("expected destinationElement to not be null"); + } + + var item = element as ProjectItemElement; + var destinationItemGroup = destinationElement as ProjectItemGroupElement; + + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Item {{ ItemType: {item.ItemType}, Condition: {item.Condition}, Include: {item.Include}, Exclude: {item.Exclude} }}"); + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: ItemGroup {{ Condition: {destinationItemGroup.Condition} }}"); + + if (mergeExisting) + { + // Don't duplicate items or includes + item = MergeWithExistingItemsWithSameCondition(item, destinationItemGroup); + if (item == null) + { + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Item completely merged"); + return; + } + + // Handle duplicate includes between different conditioned items + item = MergeWithExistingItemsWithNoCondition(item, destinationItemGroup); + if (item == null) + { + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Item completely merged"); + return; + } + + item = MergeWithExistingItemsWithACondition(item, destinationItemGroup); + if (item == null) + { + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Item completely merged"); + return; + } + } + + AddItemToItemGroup(item, destinationItemGroup); + } + + public void Execute( + IEnumerable elements, + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer + { + foreach (var element in elements) + { + Execute(element, destinationElement, mergeExisting); + } + } + + private void AddItemToItemGroup(ProjectItemElement item, ProjectItemGroupElement itemGroup) + { + var outputItem = itemGroup.ContainingProject.CreateItemElement("___TEMP___"); + outputItem.CopyFrom(item); + + itemGroup.AppendChild(outputItem); + outputItem.AddMetadata(item.Metadata); + } + + private ProjectItemElement MergeWithExistingItemsWithACondition(ProjectItemElement item, ProjectItemGroupElement destinationItemGroup) + { + // This logic only applies to conditionless items + if (item.ConditionChain().Any() || destinationItemGroup.ConditionChain().Any()) + { + return item; + } + + var existingItemsWithACondition = + FindExistingItemsWithACondition(item, destinationItemGroup.ContainingProject, destinationItemGroup); + + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Merging Item with {existingItemsWithACondition.Count()} existing items with a different condition chain."); + + foreach (var existingItem in existingItemsWithACondition) + { + // If this item is encompassing items in a condition, remove the encompassed includes from the existing item + var encompassedIncludes = item.GetEncompassedIncludes(existingItem); + if (encompassedIncludes.Any()) + { + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: encompassed includes {string.Join(", ", encompassedIncludes)}"); + existingItem.RemoveIncludes(encompassedIncludes); + } + + // continue if the existing item is now empty + if (!existingItem.Includes().Any()) + { + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Removing Item {{ ItemType: {existingItem.ItemType}, Condition: {existingItem.Condition}, Include: {existingItem.Include}, Exclude: {existingItem.Exclude} }}"); + existingItem.Parent.RemoveChild(existingItem); + continue; + } + + // If we haven't continued, the existing item may have includes + // that need to be removed before being redefined, to avoid duplicate includes + // Create or merge with existing remove + var remainingIntersectedIncludes = existingItem.IntersectIncludes(item); + + if (remainingIntersectedIncludes.Any()) + { + var existingRemoveItem = destinationItemGroup.Items + .Where(i => + string.IsNullOrEmpty(i.Include) + && string.IsNullOrEmpty(i.Exclude) + && !string.IsNullOrEmpty(i.Remove)) + .FirstOrDefault(); + + if (existingRemoveItem != null) + { + var removes = new HashSet(existingRemoveItem.Remove.Split(';')); + foreach (var include in remainingIntersectedIncludes) + { + removes.Add(include); + } + existingRemoveItem.Remove = string.Join(";", removes); + } + else + { + var clearPreviousItem = _projectElementGenerator.CreateItemElement(item.ItemType); + clearPreviousItem.Remove = string.Join(";", remainingIntersectedIncludes); + + AddItemToItemGroup(clearPreviousItem, existingItem.Parent as ProjectItemGroupElement); + } + } + } + + return item; + } + + private ProjectItemElement MergeWithExistingItemsWithNoCondition(ProjectItemElement item, ProjectItemGroupElement destinationItemGroup) + { + // This logic only applies to items being placed into a condition + if (!item.ConditionChain().Any() && !destinationItemGroup.ConditionChain().Any()) + { + return item; + } + + var existingItemsWithNoCondition = + FindExistingItemsWithNoCondition(item, destinationItemGroup.ContainingProject, destinationItemGroup); + + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Merging Item with {existingItemsWithNoCondition.Count()} existing items with a different condition chain."); + + // Handle the item being placed inside of a condition, when it is overlapping with a conditionless item + // If it is not definining new metadata or excludes, the conditioned item can be merged with the + // conditionless item + foreach (var existingItem in existingItemsWithNoCondition) + { + var encompassedIncludes = existingItem.GetEncompassedIncludes(item); + if (encompassedIncludes.Any()) + { + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: encompassed includes {string.Join(", ", encompassedIncludes)}"); + item.RemoveIncludes(encompassedIncludes); + if (!item.Includes().Any()) + { + MigrationTrace.Instance.WriteLine($"{nameof(ItemTransformApplicator)}: Ignoring Item {{ ItemType: {existingItem.ItemType}, Condition: {existingItem.Condition}, Include: {existingItem.Include}, Exclude: {existingItem.Exclude} }}"); + return null; + } + } + } + + // If we haven't returned, and there are existing items with a separate condition, we need to + // overwrite with those items inside the destinationItemGroup by using a Remove + if (existingItemsWithNoCondition.Any()) + { + // Merge with the first remove if possible + var existingRemoveItem = destinationItemGroup.Items + .Where(i => + string.IsNullOrEmpty(i.Include) + && string.IsNullOrEmpty(i.Exclude) + && !string.IsNullOrEmpty(i.Remove)) + .FirstOrDefault(); + + if (existingRemoveItem != null) + { + existingRemoveItem.Remove += ";" + item.Include; + } + else + { + var clearPreviousItem = _projectElementGenerator.CreateItemElement(item.ItemType); + clearPreviousItem.Remove = item.Include; + + AddItemToItemGroup(clearPreviousItem, destinationItemGroup); + } + } + + return item; + } + + private ProjectItemElement MergeWithExistingItemsWithSameCondition(ProjectItemElement item, ProjectItemGroupElement destinationItemGroup) + { + var existingItemsWithSameCondition = + FindExistingItemsWithSameCondition(item, destinationItemGroup.ContainingProject, destinationItemGroup); + + MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Merging Item with {existingItemsWithSameCondition.Count()} existing items with the same condition chain."); + + foreach (var existingItem in existingItemsWithSameCondition) + { + var mergeResult = MergeItems(item, existingItem); + item = mergeResult.InputItem; + + // Existing Item is null when it's entire set of includes has been merged with the MergeItem + if (mergeResult.ExistingItem == null) + { + existingItem.Parent.RemoveChild(existingItem); + } + + MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Adding Merged Item {{ ItemType: {mergeResult.MergedItem.ItemType}, Condition: {mergeResult.MergedItem.Condition}, Include: {mergeResult.MergedItem.Include}, Exclude: {mergeResult.MergedItem.Exclude} }}"); + AddItemToItemGroup(mergeResult.MergedItem, destinationItemGroup); + } + + return item; + } + + /// + /// Merges two items on their common sets of includes. + /// The output is 3 items, the 2 input items and the merged items. If the common + /// set of includes spans the entirety of the includes of either of the 2 input + /// items, that item will be returned as null. + /// + /// The 3rd output item, the merged item, will have the Union of the excludes and + /// metadata from the 2 input items. If any metadata between the 2 input items is different, + /// this will throw. + /// + /// This function will mutate the Include property of the 2 input items, removing the common subset. + /// + private MergeResult MergeItems(ProjectItemElement item, ProjectItemElement existingItem) + { + if (!string.Equals(item.ItemType, existingItem.ItemType, StringComparison.Ordinal)) + { + throw new InvalidOperationException("Cannot merge items of different types."); + } + + if (!item.IntersectIncludes(existingItem).Any()) + { + throw new InvalidOperationException("Cannot merge items without a common include."); + } + + var commonIncludes = item.IntersectIncludes(existingItem).ToList(); + var mergedItem = _projectElementGenerator.AddItem(item.ItemType, string.Join(";", commonIncludes)); + + mergedItem.UnionExcludes(existingItem.Excludes()); + mergedItem.UnionExcludes(item.Excludes()); + + mergedItem.AddMetadata(existingItem.Metadata); + mergedItem.AddMetadata(item.Metadata); + + item.RemoveIncludes(commonIncludes); + existingItem.RemoveIncludes(commonIncludes); + + var mergeResult = new MergeResult + { + InputItem = string.IsNullOrEmpty(item.Include) ? null : item, + ExistingItem = string.IsNullOrEmpty(existingItem.Include) ? null : existingItem, + MergedItem = mergedItem + }; + + return mergeResult; + } + + private IEnumerable FindExistingItemsWithSameCondition( + ProjectItemElement item, + ProjectRootElement project, + ProjectElementContainer destinationContainer) + { + return project.Items + .Where(i => i.Condition == item.Condition) + .Where(i => i.Parent.ConditionChainsAreEquivalent(destinationContainer)) + .Where(i => i.ItemType == item.ItemType) + .Where(i => i.IntersectIncludes(item).Any()); + } + + private IEnumerable FindExistingItemsWithNoCondition( + ProjectItemElement item, + ProjectRootElement project, + ProjectElementContainer destinationContainer) + { + return project.Items + .Where(i => !i.ConditionChain().Any()) + .Where(i => i.ItemType == item.ItemType) + .Where(i => i.IntersectIncludes(item).Any()); + } + + private IEnumerable FindExistingItemsWithACondition( + ProjectItemElement item, + ProjectRootElement project, + ProjectElementContainer destinationContainer) + { + return project.Items + .Where(i => i.ConditionChain().Any()) + .Where(i => i.ItemType == item.ItemType) + .Where(i => i.IntersectIncludes(item).Any()); + } + + private class MergeResult + { + public ProjectItemElement InputItem { get; set; } + public ProjectItemElement ExistingItem { get; set; } + public ProjectItemElement MergedItem { get; set; } + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/Transforms/PropertyTransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/Transforms/PropertyTransformApplicator.cs new file mode 100644 index 000000000..91f21641b --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/Transforms/PropertyTransformApplicator.cs @@ -0,0 +1,131 @@ +// 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 System.Linq; +using System.Text.RegularExpressions; + +namespace Microsoft.DotNet.ProjectJsonMigration.Transforms +{ + public class PropertyTransformApplicator : ITransformApplicator + { + private readonly ProjectRootElement _projectElementGenerator = ProjectRootElement.Create(); + + public void Execute( + T element, + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer + { + if (typeof(T) != typeof(ProjectPropertyElement)) + { + throw new ArgumentException($"Expected element to be of type {nameof(ProjectPropertyElement)}, but got {nameof(T)}"); + } + + if (typeof(U) != typeof(ProjectPropertyGroupElement)) + { + throw new ArgumentException($"Expected element to be of type {nameof(ProjectPropertyGroupElement)}, but got {nameof(U)}"); + } + + if (element == null) + { + return; + } + + var property = element as ProjectPropertyElement; + var destinationPropertyGroup = destinationElement as ProjectPropertyGroupElement; + + if (mergeExisting) + { + var mergedProperty = MergePropertyWithProject(property, destinationPropertyGroup); + if (mergedProperty != null && !string.IsNullOrEmpty(mergedProperty.Value)) + { + TracePropertyInfo("Merging property, output merged property", mergedProperty); + AddPropertyToPropertyGroup(mergedProperty, destinationPropertyGroup); + } + else + { + TracePropertyInfo("Ignoring fully merged property", property); + } + } + else + { + AddPropertyToPropertyGroup(property, destinationPropertyGroup); + } + } + + private ProjectPropertyElement MergePropertyWithProject( + ProjectPropertyElement property, + ProjectPropertyGroupElement destinationPropertyGroup) + { + var propertiesToMergeWith = FindPropertiesWithSameNameAndSameOrNoCondition( + property, + destinationPropertyGroup.ContainingProject); + + foreach (var propertyToMergeWith in propertiesToMergeWith) + { + property = MergeProperties(propertyToMergeWith, property); + } + + return property; + } + + private IEnumerable FindPropertiesWithSameNameAndSameOrNoCondition( + ProjectPropertyElement property, + ProjectRootElement project) + { + return project.Properties + .Where(otherProperty => + property.Name == otherProperty.Name + && (property.Condition == otherProperty.Condition + || (property.Condition == otherProperty.Parent.Condition && string.IsNullOrEmpty(otherProperty.Condition)) + || !otherProperty.ConditionChain().Any())); + } + + private ProjectPropertyElement MergeProperties(ProjectPropertyElement baseProperty, ProjectPropertyElement addedProperty) + { + var mergedProperty = _projectElementGenerator.AddProperty("___TEMP___", "___TEMP___"); + mergedProperty.CopyFrom(addedProperty); + + var basePropertyValues = baseProperty.Value.Split(';'); + var addedPropertyValues = addedProperty.Value.Split(';'); + + var intersectedValues = basePropertyValues.Intersect(addedPropertyValues, StringComparer.Ordinal); + intersectedValues = RemoveValuesWithVariable(intersectedValues); + + mergedProperty.Value = string.Join(";", addedPropertyValues.Except(intersectedValues)); + + return mergedProperty; + } + + private IEnumerable RemoveValuesWithVariable(IEnumerable intersectedValues) + { + return intersectedValues.Where(v => ! Regex.IsMatch(v, @"\$\(.*?\)")); + } + + private void AddPropertyToPropertyGroup(ProjectPropertyElement property, ProjectPropertyGroupElement destinationPropertyGroup) + { + var outputProperty = destinationPropertyGroup.ContainingProject.CreatePropertyElement("___TEMP___"); + outputProperty.CopyFrom(property); + + destinationPropertyGroup.AppendChild(outputProperty); + } + + public void Execute( + IEnumerable elements, + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer + { + foreach (var element in elements) + { + Execute(element, destinationElement, mergeExisting); + } + } + + private void TracePropertyInfo(string message, ProjectPropertyElement mergedProperty) + { + MigrationTrace.Instance.WriteLine($"{nameof(PropertyTransformApplicator)}: {message}, {{ Name={mergedProperty.Name}, Value={mergedProperty.Value} }}"); + } + } +} diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/project.json b/src/Microsoft.DotNet.ProjectJsonMigration/project.json index b8f2aedb7..34cd34fd4 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/project.json +++ b/src/Microsoft.DotNet.ProjectJsonMigration/project.json @@ -2,7 +2,10 @@ "version": "1.0.0-preview3-*", "buildOptions": { "warningsAsErrors": true, - "keyFile": "../../tools/Key.snk" + "keyFile": "../../tools/Key.snk", + "embed" :{ + "include" : "sdkdefaults.json" + } }, "dependencies": { "Microsoft.DotNet.Compiler.Common": { @@ -17,7 +20,6 @@ "Microsoft.Build": "15.1.319-preview5" }, "frameworks": { - "netcoreapp1.0": { - } + "netcoreapp1.0": { } } } diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/sdkdefaults.json b/src/Microsoft.DotNet.ProjectJsonMigration/sdkdefaults.json new file mode 100755 index 000000000..6ee921230 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectJsonMigration/sdkdefaults.json @@ -0,0 +1,178 @@ +{ + "Items": [ + { + "ItemType": "Compile", + "Include": "**\\*.cs", + "Exclude": "bin\\**;obj\\**", + "Remove": null, + "Condition": null, + "ParentCondition": null + }, + { + "ItemType": "EmbeddedResource", + "Include": "**\\*.resx", + "Exclude": "bin\\**;obj\\**", + "Remove": null, + "Condition": null, + "ParentCondition": null + } + ], + "Properties": [ + { + "Name": "OutputType", + "Value": "Library", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "Configuration", + "Value": "Debug", + "Condition": null, + "ParentCondition": null + }, + { + "Name": "Platform", + "Value": "AnyCPU", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "FileAlignment", + "Value": "512", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "PlatformTarget", + "Value": "AnyCPU", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "ErrorReport", + "Value": "prompt", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "AssemblyName", + "Value": "$(MSBuildProjectName)", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "RootNamespace", + "Value": "$(MSBuildProjectName)", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "Deterministic", + "Value": "true", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "WarningLevel", + "Value": "4", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "NoWarn", + "Value": "1701;1702;1705", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "PackageRequireLicenseAcceptance", + "Value": "false", + "Condition": "", + "ParentCondition": "" + }, + { + "Name": "DebugSymbols", + "Value": "true", + "Condition": "", + "ParentCondition": " '$(Configuration)' == 'Debug' " + }, + { + "Name": "Optimize", + "Value": "false", + "Condition": "", + "ParentCondition": " '$(Configuration)' == 'Debug' " + }, + { + "Name": "OutputPath", + "Value": "bin\\Debug\\", + "Condition": "", + "ParentCondition": " '$(Configuration)' == 'Debug' " + }, + { + "Name": "Optimize", + "Value": "true", + "Condition": "", + "ParentCondition": " '$(Configuration)' == 'Release' " + }, + { + "Name": "OutputPath", + "Value": "bin\\Release\\", + "Condition": "", + "ParentCondition": " '$(Configuration)' == 'Release' " + }, + { + "Name": "DefineConstants", + "Value": "DEBUG;TRACE", + "Condition": "", + "ParentCondition": " '$(Configuration)' == 'Debug' " + }, + { + "Name": "DefineConstants", + "Value": "TRACE", + "Condition": "", + "ParentCondition": " '$(Configuration)' == 'Release' " + }, + { + "Name": "VersionPrefix", + "Value": "1.0.0", + "Condition": null, + "ParentCondition": null + }, + { + "Name": "AssemblyTitle", + "Value": "$(AssemblyName)", + "Condition": null, + "ParentCondition": null + }, + { + "Name": "Product", + "Value": "$(AssemblyName)", + "Condition": null, + "ParentCondition": null + }, + { + "Name": "NeutralLanguage", + "Value": "en", + "Condition": null, + "ParentCondition": null + }, + { + "Name": "AutoUnifyAssemblyReferences", + "Value": "true", + "Condition": null, + "ParentCondition": null + }, + { + "Name": "DesignTimeAutoUnify", + "Value": "true", + "Condition": null, + "ParentCondition": null + }, + { + "Name": "TargetExt", + "Value": ".dll", + "Condition": null, + "ParentCondition": null + } + ] +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs index 8a5db6ba7..4efd2f16f 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/ITransformApplicator.cs @@ -10,20 +10,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { void Execute( T element, - U destinationElement) where T : ProjectElement where U : ProjectElementContainer; + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer; void Execute( IEnumerable elements, - U destinationElement) where T : ProjectElement where U : ProjectElementContainer; - - void Execute( - ProjectItemElement item, - ProjectItemGroupElement destinationItemGroup, - bool mergeExisting); - - void Execute( - IEnumerable items, - ProjectItemGroupElement destinationItemGroup, - bool mergeExisting); + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer; } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs index adf68de68..47815949e 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/transforms/TransformApplicator.cs @@ -10,248 +10,44 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms { public class TransformApplicator : ITransformApplicator { - private readonly ProjectRootElement _projectElementGenerator = ProjectRootElement.Create(); + private readonly ITransformApplicator _propertyTransformApplicator; + + private readonly ITransformApplicator _itemTransformApplicator; + + public TransformApplicator(ITransformApplicator propertyTransformApplicator=null, ITransformApplicator itemTransformApplicator=null) + { + _propertyTransformApplicator = propertyTransformApplicator ?? new PropertyTransformApplicator(); + _itemTransformApplicator = propertyTransformApplicator ?? new ItemTransformApplicator(); + } public void Execute( T element, - U destinationElement) where T : ProjectElement where U : ProjectElementContainer + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer { - if (element != null) + if (typeof(T) == typeof(ProjectItemElement)) { - if (typeof(T) == typeof(ProjectItemElement)) - { - var item = destinationElement.ContainingProject.CreateItemElement("___TEMP___"); - item.CopyFrom(element); - - destinationElement.AppendChild(item); - item.AddMetadata((element as ProjectItemElement).Metadata); - } - else if (typeof(T) == typeof(ProjectPropertyElement)) - { - MigrationTrace.Instance.WriteLine( - $"{nameof(TransformApplicator)}: Adding Property to project {(element as ProjectPropertyElement).Name}"); - var property = destinationElement.ContainingProject.CreatePropertyElement("___TEMP___"); - property.CopyFrom(element); - - destinationElement.AppendChild(property); - } - else - { - throw new Exception("Unable to add unknown project element to project"); - } + _itemTransformApplicator.Execute(element, destinationElement, mergeExisting); + } + else if (typeof(T) == typeof(ProjectPropertyElement)) + { + _propertyTransformApplicator.Execute(element, destinationElement, mergeExisting); + } + else + { + throw new ArgumentException($"Unexpected type {nameof(T)}"); } } public void Execute( IEnumerable elements, - U destinationElement) where T : ProjectElement where U : ProjectElementContainer + U destinationElement, + bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer { foreach (var element in elements) { - Execute(element, destinationElement); + Execute(element, destinationElement, mergeExisting); } } - - public void Execute( - ProjectItemElement item, - ProjectItemGroupElement destinationItemGroup, - bool mergeExisting) - { - if (item == null) - { - return; - } - - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Item {{ ItemType: {item.ItemType}, Condition: {item.Condition}, Include: {item.Include}, Exclude: {item.Exclude} }}"); - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: ItemGroup {{ Condition: {destinationItemGroup.Condition} }}"); - - if (mergeExisting) - { - item = MergeWithExistingItemsWithSameCondition(item, destinationItemGroup); - - // Item will be null when it's entire set of includes has been merged. - if (item == null) - { - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Item completely merged"); - return; - } - - item = MergeWithExistingItemsWithDifferentCondition(item, destinationItemGroup); - - // Item will be null when it is equivalent to a conditionless item - if (item == null) - { - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Item c"); - return; - } - } - - Execute(item, destinationItemGroup); - } - - private ProjectItemElement MergeWithExistingItemsWithDifferentCondition(ProjectItemElement item, ProjectItemGroupElement destinationItemGroup) - { - var existingItemsWithDifferentCondition = - FindExistingItemsWithDifferentCondition(item, destinationItemGroup.ContainingProject, destinationItemGroup); - - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Merging Item with {existingItemsWithDifferentCondition.Count()} existing items with a different condition chain."); - - foreach (var existingItem in existingItemsWithDifferentCondition) - { - var encompassedIncludes = existingItem.GetEncompassedIncludes(item); - if (encompassedIncludes.Any()) - { - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: encompassed includes {string.Join(", ", encompassedIncludes)}"); - item.RemoveIncludes(encompassedIncludes); - if (!item.Includes().Any()) - { - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Ignoring Item {{ ItemType: {existingItem.ItemType}, Condition: {existingItem.Condition}, Include: {existingItem.Include}, Exclude: {existingItem.Exclude} }}"); - return null; - } - } - } - - // If we haven't returned, and there are existing items with a separate condition, we need to - // overwrite with those items inside the destinationItemGroup by using a Remove - // Unless this is a conditionless item, in which case this the conditioned items should be doing the - // overwriting. - if (existingItemsWithDifferentCondition.Any() && - (item.ConditionChain().Count() > 0 || destinationItemGroup.ConditionChain().Count() > 0)) - { - // Merge with the first remove if possible - var existingRemoveItem = destinationItemGroup.Items - .Where(i => - string.IsNullOrEmpty(i.Include) - && string.IsNullOrEmpty(i.Exclude) - && !string.IsNullOrEmpty(i.Remove)) - .FirstOrDefault(); - - if (existingRemoveItem != null) - { - existingRemoveItem.Remove += ";" + item.Include; - } - else - { - var clearPreviousItem = _projectElementGenerator.CreateItemElement(item.ItemType); - clearPreviousItem.Remove = item.Include; - - Execute(clearPreviousItem, destinationItemGroup); - } - } - - return item; - } - - private ProjectItemElement MergeWithExistingItemsWithSameCondition(ProjectItemElement item, ProjectItemGroupElement destinationItemGroup) - { - var existingItemsWithSameCondition = - FindExistingItemsWithSameCondition(item, destinationItemGroup.ContainingProject, destinationItemGroup); - - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Merging Item with {existingItemsWithSameCondition.Count()} existing items with the same condition chain."); - - foreach (var existingItem in existingItemsWithSameCondition) - { - var mergeResult = MergeItems(item, existingItem); - item = mergeResult.InputItem; - - // Existing Item is null when it's entire set of includes has been merged with the MergeItem - if (mergeResult.ExistingItem == null) - { - existingItem.Parent.RemoveChild(existingItem); - } - - MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Adding Merged Item {{ ItemType: {mergeResult.MergedItem.ItemType}, Condition: {mergeResult.MergedItem.Condition}, Include: {mergeResult.MergedItem.Include}, Exclude: {mergeResult.MergedItem.Exclude} }}"); - Execute(mergeResult.MergedItem, destinationItemGroup); - } - - return item; - } - - public void Execute( - IEnumerable items, - ProjectItemGroupElement destinationItemGroup, - bool mergeExisting) - { - foreach (var item in items) - { - Execute(item, destinationItemGroup, mergeExisting); - } - } - - /// - /// Merges two items on their common sets of includes. - /// The output is 3 items, the 2 input items and the merged items. If the common - /// set of includes spans the entirety of the includes of either of the 2 input - /// items, that item will be returned as null. - /// - /// The 3rd output item, the merged item, will have the Union of the excludes and - /// metadata from the 2 input items. If any metadata between the 2 input items is different, - /// this will throw. - /// - /// This function will mutate the Include property of the 2 input items, removing the common subset. - /// - private MergeResult MergeItems(ProjectItemElement item, ProjectItemElement existingItem) - { - if (!string.Equals(item.ItemType, existingItem.ItemType, StringComparison.Ordinal)) - { - throw new InvalidOperationException("Cannot merge items of different types."); - } - - if (!item.IntersectIncludes(existingItem).Any()) - { - throw new InvalidOperationException("Cannot merge items without a common include."); - } - - var commonIncludes = item.IntersectIncludes(existingItem).ToList(); - var mergedItem = _projectElementGenerator.AddItem(item.ItemType, string.Join(";", commonIncludes)); - - mergedItem.UnionExcludes(existingItem.Excludes()); - mergedItem.UnionExcludes(item.Excludes()); - - mergedItem.AddMetadata(existingItem.Metadata); - mergedItem.AddMetadata(item.Metadata); - - item.RemoveIncludes(commonIncludes); - existingItem.RemoveIncludes(commonIncludes); - - var mergeResult = new MergeResult - { - InputItem = string.IsNullOrEmpty(item.Include) ? null : item, - ExistingItem = string.IsNullOrEmpty(existingItem.Include) ? null : existingItem, - MergedItem = mergedItem - }; - - return mergeResult; - } - - private IEnumerable FindExistingItemsWithSameCondition( - ProjectItemElement item, - ProjectRootElement project, - ProjectElementContainer destinationContainer) - { - return project.Items - .Where(i => i.Condition == item.Condition) - .Where(i => i.Parent.ConditionChainsAreEquivalent(destinationContainer)) - .Where(i => i.ItemType == item.ItemType) - .Where(i => i.IntersectIncludes(item).Any()); - } - - private IEnumerable FindExistingItemsWithDifferentCondition( - ProjectItemElement item, - ProjectRootElement project, - ProjectElementContainer destinationContainer) - { - return project.Items - .Where(i => !i.ConditionChainsAreEquivalent(item) || !i.Parent.ConditionChainsAreEquivalent(destinationContainer)) - .Where(i => i.ItemType == item.ItemType) - .Where(i => i.IntersectIncludes(item).Any()); - } - - private class MergeResult - { - public ProjectItemElement InputItem { get; set; } - public ProjectItemElement ExistingItem { get; set; } - public ProjectItemElement MergedItem { get; set; } - } } } diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs index 46dafbd48..b3cb68285 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Rules/GivenThatIWantToMigrateBuildOptions.cs @@ -49,16 +49,13 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests } [Fact] - public void Migrating_empty_buildOptions_populates_only_AssemblyName_Compile_and_EmbeddedResource() + public void Migrating_empty_buildOptions_populates_only_Compile_and_EmbeddedResource() { var mockProj = RunBuildOptionsRuleOnPj(@" { ""buildOptions"": { } }"); - mockProj.Properties.Count().Should().Be(1); - mockProj.Properties.Any(p => !p.Name.Equals("AssemblyName", StringComparison.Ordinal)).Should().BeFalse(); - mockProj.Items.Count().Should().Be(2); mockProj.Items.First(i => i.ItemType == "Compile").Include.Should().Be(@"**\*.cs"); mockProj.Items.First(i => i.ItemType == "Compile").Exclude.Should().Be(@"bin\**;obj\**;**\*.xproj;packages\**"); @@ -346,20 +343,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests mockProj.Properties.First(p => p.Name == "DebugType").Value.Should().Be("foo"); } - [Fact] - public void Migrating_outputName_populates_AssemblyName() - { - var mockProj = RunBuildOptionsRuleOnPj(@" - { - ""buildOptions"": { - ""outputName"": ""ARandomName"" - } - }"); - - mockProj.Properties.Count(p => p.Name == "AssemblyName").Should().Be(1); - mockProj.Properties.First(p => p.Name == "AssemblyName").Value.Should().Be("ARandomName"); - } - [Fact] public void Migrating_xmlDoc_populates_GenerateDocumentationFile() { diff --git a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs index 3b5966523..511ed55b1 100644 --- a/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs +++ b/test/Microsoft.DotNet.ProjectJsonMigration.Tests/Transforms/GivenATransformApplicator.cs @@ -44,7 +44,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests item2.AddMetadata(metadata[1].MetadataName, metadata[1].GetMetadataValue(null)); var transformApplicator = new TransformApplicator(); - transformApplicator.Execute(new ProjectItemElement[] {item1, item2}, itemGroup, mergeExisting:true); + transformApplicator.Execute(new ProjectItemElement[] {item1, item2}.Select(i => i), itemGroup, mergeExisting:true); itemGroup.Items.Count.Should().Be(1); @@ -64,5 +64,26 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests foundMetadata.All(kv => kv.Value).Should().BeTrue(); } + + [Fact] + public void It_merges_Properties_value_split_by_semicolon_except_variables_when_mergeExisting_is_true() + { + var mockProj = ProjectRootElement.Create(); + var existingProperty = mockProj.AddProperty("property1","value1;$(Variable1);$(Variable2);value2"); + + var propertyGeneratorProject = ProjectRootElement.Create(); + var propertyToAdd = propertyGeneratorProject.AddProperty("property1", "$(Variable2);value1;value3;$(Variable3)"); + + var transformApplicator = new TransformApplicator(); + + transformApplicator.Execute(propertyToAdd, mockProj.AddPropertyGroup(), mergeExisting: true); + + var outputProperties = mockProj.Properties.Where(p => p.Name == "property1"); + outputProperties.Should().HaveCount(2); + + var mergedPropertyToAdd = outputProperties.Where(p => p.Value.Contains("value3")).First(); + + mergedPropertyToAdd.Value.Should().Be("$(Variable2);value3;$(Variable3)"); + } } } \ No newline at end of file diff --git a/tools/MigrationDefaultsConstructor/Program.cs b/tools/MigrationDefaultsConstructor/Program.cs new file mode 100755 index 000000000..d5c05de11 --- /dev/null +++ b/tools/MigrationDefaultsConstructor/Program.cs @@ -0,0 +1,193 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using Newtonsoft.Json; +using System.Threading; +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration.Models; + +namespace MigrationDefaultsConstructor +{ + public class Program + { + private const string c_temporaryDotnetNewMSBuildProjectName = "p"; + + static void Main(string[] args) + { + var sdkRootPath=args[0]; + + var beforeCommonSdkTargetsFilePath = Path.Combine(sdkRootPath, "src", "Tasks", "Microsoft.NET.Build.Tasks", "build", "Microsoft.NET.Sdk.BeforeCommon.targets"); + var commonSdkTargetsFilePath = Path.Combine(sdkRootPath, "src", "Tasks", "Microsoft.NET.Build.Tasks", "build", "Microsoft.NET.Sdk.Common.targets"); + var sdkTargetsFilePath = Path.Combine(sdkRootPath, "src", "Tasks", "Microsoft.NET.Build.Tasks", "build", "Microsoft.NET.Sdk.targets"); + var sdkPropsFilePath = Path.Combine(sdkRootPath, "src", "Tasks", "Microsoft.NET.Build.Tasks", "build", "Microsoft.NET.Sdk.props"); + var csharpTargetsFilePath = Path.Combine(sdkRootPath, "src", "Tasks", "Microsoft.NET.Build.Tasks", "build", "Microsoft.NET.Sdk.CSharp.targets"); + var csharpPropsFilePath = Path.Combine(sdkRootPath, "src", "Tasks", "Microsoft.NET.Build.Tasks", "build", "Microsoft.NET.Sdk.CSharp.props"); + + var beforeCommonSdkTargetsFile = ProjectRootElement.Open(beforeCommonSdkTargetsFilePath); + var commonSdkTargetsFile = ProjectRootElement.Open(commonSdkTargetsFilePath); + var sdkTargetsFile = ProjectRootElement.Open(sdkTargetsFilePath); + var sdkPropsFile = ProjectRootElement.Open(sdkPropsFilePath); + var csharpPropsFile = ProjectRootElement.Open(csharpPropsFilePath); + var csharpTargetsFile = ProjectRootElement.Open(csharpTargetsFilePath); + + var allProperties = new List(); + var allItems = new List(); + + AddPropertyDefault(allProperties, sdkPropsFile, "OutputType"); + AddPropertyDefault(allProperties, sdkPropsFile, "Configuration", ignoreConditions: true); + AddPropertyDefault(allProperties, sdkPropsFile, "Platform"); + AddPropertyDefault(allProperties, sdkPropsFile, "FileAlignment"); + AddPropertyDefault(allProperties, sdkPropsFile, "PlatformTarget"); + AddPropertyDefault(allProperties, sdkPropsFile, "ErrorReport"); + AddPropertyDefault(allProperties, sdkPropsFile, "AssemblyName"); + AddPropertyDefault(allProperties, sdkPropsFile, "RootNamespace"); + AddPropertyDefault(allProperties, sdkPropsFile, "Deterministic"); + + AddPropertyDefault(allProperties, csharpPropsFile, "WarningLevel"); + AddPropertyDefault(allProperties, csharpPropsFile, "NoWarn"); + + AddHardcodedPropertyDefault(allProperties, "PackageRequireLicenseAcceptance", "false"); + + AddConfigurationPropertyDefaults(allProperties, sdkPropsFile, "Debug"); + AddConfigurationPropertyDefaults(allProperties, sdkPropsFile, "Release"); + + AddConfigurationPropertyDefaults(allProperties, csharpPropsFile, "Debug"); + AddConfigurationPropertyDefaults(allProperties, csharpPropsFile, "Release"); + + AddPropertyDefault(allProperties, commonSdkTargetsFile, "VersionPrefix", ignoreConditions: true); + AddPropertyDefault(allProperties, commonSdkTargetsFile, "AssemblyTitle", ignoreConditions: true); + AddPropertyDefault(allProperties, commonSdkTargetsFile, "Product", ignoreConditions: true); + AddPropertyDefault(allProperties, commonSdkTargetsFile, "NeutralLanguage", ignoreConditions: true); + + AddPropertyDefault(allProperties, beforeCommonSdkTargetsFile, "AutoUnifyAssemblyReferences", ignoreConditions: true); + AddPropertyDefault(allProperties, beforeCommonSdkTargetsFile, "DesignTimeAutoUnify", ignoreConditions: true); + AddPropertyDefault(allProperties, beforeCommonSdkTargetsFile, "TargetExt", ignoreConditions: true); + + AddCompileAndEmbeddedResourceDefaults(allItems, sdkTargetsFile); + + var wrapper = new SerializableMigrationDefaultsInfo() + { + Items = allItems, + Properties = allProperties + }; + + var output = Path.Combine(Directory.GetCurrentDirectory(), "sdkdefaults.json"); + string json = JsonConvert.SerializeObject(wrapper, Formatting.Indented); + File.WriteAllText(output, json); + } + + private static void AddHardcodedPropertyDefault(List allProperties, + string name, + string value, + string condition="", + string parentCondition="") + { + var propertyInfo = new DefaultProjectPropertyInfo + { + Name = name, + Value = value, + Condition = condition, + ParentCondition = parentCondition + }; + + allProperties.Add(propertyInfo); + } + + private static void AddCompileAndEmbeddedResourceDefaults(List allItems, ProjectRootElement msbuild) + { + var exclude = msbuild.Properties.Where(p=>p.Name == "DefaultExcludes").First().Value; + + var compileInclude = msbuild.Items.Where(i => i.ItemType == "Compile").First().Include; + if (string.IsNullOrEmpty(compileInclude)) + { + compileInclude = "**\\*.cs"; + } + + var embedInclude = msbuild.Items.Where(i => i.ItemType == "EmbeddedResource").First().Include; + if (string.IsNullOrEmpty(embedInclude)) + { + embedInclude = "**\\*.resx"; + } + + allItems.Add(new DefaultProjectItemInfo + { + ItemType = "Compile", + Include=compileInclude, + Exclude=exclude + }); + + allItems.Add(new DefaultProjectItemInfo + { + ItemType = "EmbeddedResource", + Include=embedInclude, + Exclude=exclude + }); + } + + private static void AddConfigurationPropertyDefaults(List allProperties, ProjectRootElement msbuild, string config) + { + var configPropertyGroup = msbuild.PropertyGroups.Where(p => p.Condition.Contains("$(Configuration)") && p.Condition.Contains(config)).First(); + + configPropertyGroup.Condition = $" '$(Configuration)' == '{config}' "; + + foreach (var property in configPropertyGroup.Properties) + { + var propertyInfo = new DefaultProjectPropertyInfo + { + Name = property.Name, + Value = property.Value, + Condition = property.Condition, + ParentCondition = property.Parent.Condition + }; + + allProperties.Add(propertyInfo); + } + } + + private static void AddPropertyDefault(List allProperties, ProjectRootElement msbuild, string propertyName, int? index=null, bool ignoreConditions=false) + { + var properties = msbuild.Properties.Where(p => p.Name == propertyName).ToList(); + if (!properties.Any()) + { + throw new Exception("property not found:" + propertyName); + } + + if (properties.Count() > 1 && index == null) + { + throw new Exception("More than one property found but index is null:" + propertyName); + } + + var property = properties[index ?? 0]; + + if (ignoreConditions) + { + var propertyInfo = new DefaultProjectPropertyInfo + { + Name = property.Name, + Value = property.Value, + Condition = null, + ParentCondition = null + }; + + allProperties.Add(propertyInfo); + } + else + { + var propertyInfo = new DefaultProjectPropertyInfo + { + Name = property.Name, + Value = property.Value, + Condition = property.Condition, + ParentCondition = property.Parent.Condition + }; + + allProperties.Add(propertyInfo); + } + } + } +} diff --git a/tools/MigrationDefaultsConstructor/README.md b/tools/MigrationDefaultsConstructor/README.md new file mode 100644 index 000000000..a3ad7f386 --- /dev/null +++ b/tools/MigrationDefaultsConstructor/README.md @@ -0,0 +1,7 @@ +# Migration Defaults Constructor + +This pulls the migration property and item defaults from a clone of the dotnet/sdk repo. + +Run `./run.sh` to generate an sdkdefaults.json + +Move it to the Microsoft.DotNet.ProjectJsonMigration project under `src` to override the defaults in dotnet (it's embedded as a resource). \ No newline at end of file diff --git a/tools/MigrationDefaultsConstructor/project.json b/tools/MigrationDefaultsConstructor/project.json new file mode 100755 index 000000000..8c84527ed --- /dev/null +++ b/tools/MigrationDefaultsConstructor/project.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "debugType": "portable", + "emitEntryPoint": true + }, + "dependencies": {}, + "frameworks": { + "netcoreapp1.0": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.1" + }, + "dotnet": { + "target": "project" + }, + "Microsoft.Build.Runtime": "15.1.319-preview5" + }, + "imports": ["dnxcore50", "portable-net45+win8"] + } + } +} diff --git a/tools/MigrationDefaultsConstructor/run.sh b/tools/MigrationDefaultsConstructor/run.sh new file mode 100755 index 000000000..77e4c0877 --- /dev/null +++ b/tools/MigrationDefaultsConstructor/run.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located +done +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + +rm -rf bin obj +dotnet publish -o bin -f netcoreapp1.0 +cp -a "$DIR/bin/runtimes/any/native/." "$DIR/bin" + +sdkRevision="cc1fc023e3375b3944dbedfdd4ba2b5d2cbd01f0" +sdkRoot="$DIR/bin/sdk" +(cd bin && \ + git clone https://github.com/dotnet/sdk.git && \ + cd sdk && \ + git reset --hard $sdkRevision) + +dotnet "$DIR/bin/MigrationDefaultsConstructor.dll" "$sdkRoot" \ No newline at end of file