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
This commit is contained in:
Bryan Thornbury 2016-10-20 11:00:41 -07:00 committed by GitHub
parent 6373cdde60
commit ecbc45098d
28 changed files with 1251 additions and 302 deletions

View file

@ -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()
};

View file

@ -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;
}
}
}

View file

@ -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;}
}
}

View file

@ -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;}
}
}

View file

@ -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<DefaultProjectItemInfo> Items { get; set; }
public IEnumerable<DefaultProjectPropertyInfo> Properties { get; set; }
}
}

View file

@ -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<string, ProjectPropertyGroupElement>();
var defaultsItemGroups = new Dictionary<string, ProjectItemGroupElement>();
AddDefaultPropertiesToProject(defaults, project, defaultsPropertyGroups);
AddDefaultItemsToProject(defaults, project, defaultsItemGroups);
}
private void AddDefaultItemsToProject(
SerializableMigrationDefaultsInfo defaults,
ProjectRootElement project,
Dictionary<string, ProjectItemGroupElement> 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<string, ProjectPropertyGroupElement> 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<SerializableMigrationDefaultsInfo>(sdkDefaultJson);
}
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -105,11 +105,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
@"$(OutputPath)\$(TargetFramework)\$(AssemblyName).xml",
compilerOptions => compilerOptions.GenerateXmlDocumentation != null && compilerOptions.GenerateXmlDocumentation.Value);
private AddPropertyTransform<CommonCompilerOptions> OutputNameTransform =>
new AddPropertyTransform<CommonCompilerOptions>("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)

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -37,6 +37,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
var tfmDependencyMap = new Dictionary<string, IEnumerable<ProjectLibraryDependency>>();
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<ProjectLibraryDependency> dependencies,
ProjectRootElement xproj)
ProjectRootElement xproj,
ProjectItemGroupElement itemGroup=null)
{
var projectDependencies = new HashSet<string>(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);
}
}

View file

@ -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<ProjectDependency> ProjectDependencyTransform => new AddItemTransform<ProjectDependency>(
"ProjectReference",
dep =>

View file

@ -37,7 +37,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
foreach (var transformResult in transformResults)
{
_transformApplicator.Execute(transformResult, propertyGroup);
_transformApplicator.Execute(transformResult, propertyGroup, true);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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, U>(
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<T, U>(
IEnumerable<T> 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<string>(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;
}
/// <summary>
/// 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.
/// </summary>
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<ProjectItemElement> 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<ProjectItemElement> 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<ProjectItemElement> 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; }
}
}
}

View file

@ -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, U>(
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<ProjectPropertyElement> 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<string> RemoveValuesWithVariable(IEnumerable<string> 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<T, U>(
IEnumerable<T> 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} }}");
}
}
}

View file

@ -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": { }
}
}

View file

@ -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
}
]
}

View file

@ -10,20 +10,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
{
void Execute<T, U>(
T element,
U destinationElement) where T : ProjectElement where U : ProjectElementContainer;
U destinationElement,
bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer;
void Execute<T, U>(
IEnumerable<T> elements,
U destinationElement) where T : ProjectElement where U : ProjectElementContainer;
void Execute(
ProjectItemElement item,
ProjectItemGroupElement destinationItemGroup,
bool mergeExisting);
void Execute(
IEnumerable<ProjectItemElement> items,
ProjectItemGroupElement destinationItemGroup,
bool mergeExisting);
U destinationElement,
bool mergeExisting) where T : ProjectElement where U : ProjectElementContainer;
}
}

View file

@ -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, U>(
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<T, U>(
IEnumerable<T> 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<ProjectItemElement> items,
ProjectItemGroupElement destinationItemGroup,
bool mergeExisting)
{
foreach (var item in items)
{
Execute(item, destinationItemGroup, mergeExisting);
}
}
/// <summary>
/// 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.
/// </summary>
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<ProjectItemElement> 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<ProjectItemElement> 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; }
}
}
}

View file

@ -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()
{

View file

@ -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)");
}
}
}

View file

@ -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<DefaultProjectPropertyInfo>();
var allItems = new List<DefaultProjectItemInfo>();
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<DefaultProjectPropertyInfo> 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<DefaultProjectItemInfo> 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<DefaultProjectPropertyInfo> 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<DefaultProjectPropertyInfo> 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);
}
}
}
}

View file

@ -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).

View file

@ -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"]
}
}
}

View file

@ -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"