Project Json mapping migration support

This commit is contained in:
Bryan Thornbury 2016-09-08 14:40:46 -07:00
parent 3a567e5957
commit 362f71a94a
17 changed files with 910 additions and 201 deletions

View file

@ -7,7 +7,7 @@ namespace ConsoleApplication
public static int Main(string[] args)
{
Console.WriteLine("Hello World!");
return 100;
return 0;
}
}
}

View file

@ -3,29 +3,30 @@
"buildOptions": {
"emitEntryPoint": true,
"copyToOutput": {
"include": "testcontentfile.txt"
"include": "testcontentfile.txt",
"mappings": {
"dir/mappingfile.txt":{
"include": "testcontentfile2.txt"
},
"out/": {
"include": ["project.json", "Program.cs"],
"exclude": ["Program.cs"],
"includeFiles": ["Program.cs"],
"excludeFiles": ["Program.cs"]
}
}
}
},
"dependencies": {
"Microsoft.NETCore.App": "1.0.1"
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
}
},
"frameworks": {
"netcoreapp1.0": {}
},
"publishOptions": {
"include": "testcontentfile.txt"
},
"runtimes": {
"win7-x64": {},
"win7-x86": {},
"osx.10.10-x64": {},
"osx.10.11-x64": {},
"ubuntu.14.04-x64": {},
"ubuntu.16.04-x64": {},
"centos.7-x64": {},
"rhel.7.2-x64": {},
"debian.8-x64": {},
"fedora.23-x64": {},
"opensuse.13.2-x64": {}
}
}

View file

@ -15,6 +15,79 @@ namespace Microsoft.DotNet.ProjectJsonMigration
{
public static class MSBuildExtensions
{
public static bool IsEquivalentTo(this ProjectItemElement item, ProjectItemElement otherItem)
{
// Different includes
if (item.IntersectIncludes(otherItem).Count() != item.Includes().Count())
{
MigrationTrace.Instance.WriteLine("ms: includes");
return false;
}
// Different Excludes
if (item.IntersectExcludes(otherItem).Count() != item.Excludes().Count())
{
MigrationTrace.Instance.WriteLine("ms: excludes");
return false;
}
// Different remove
if (item.Remove != otherItem.Remove)
{
MigrationTrace.Instance.WriteLine("ms: remove");
return false;
}
// Different Metadata
var metadataTuples = otherItem.Metadata.Select(m => Tuple.Create(m, item)).Concat(
item.Metadata.Select(m => Tuple.Create(m, otherItem)));
foreach (var metadataTuple in metadataTuples)
{
var metadata = metadataTuple.Item1;
var itemToCompare = metadataTuple.Item2;
var otherMetadata = itemToCompare.GetMetadataWithName(metadata.Name);
if (otherMetadata == null)
{
MigrationTrace.Instance.WriteLine($"ms: metadata doesn't exist {{ {metadata.Name} {metadata.Value} }}");
return false;
}
if (!metadata.ValueEquals(otherMetadata))
{
MigrationTrace.Instance.WriteLine("ms: metadata has another value {{ {metadata.Name} {metadata.Value} {otherMetadata.Value} }}");
return false;
}
}
return true;
}
public static ISet<string> ConditionChain(this ProjectElement projectElement)
{
var conditionChainSet = new HashSet<string>();
if (!string.IsNullOrEmpty(projectElement.Condition))
{
conditionChainSet.Add(projectElement.Condition);
}
foreach (var parent in projectElement.AllParents)
{
if (!string.IsNullOrEmpty(parent.Condition))
{
conditionChainSet.Add(parent.Condition);
}
}
return conditionChainSet;
}
public static bool ConditionChainsAreEquivalent(this ProjectElement projectElement, ProjectElement otherProjectElement)
{
return projectElement.ConditionChain().SetEquals(otherProjectElement.ConditionChain());
}
public static IEnumerable<ProjectPropertyElement> PropertiesWithoutConditions(
this ProjectRootElement projectRoot)
{
@ -39,6 +112,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration
return SplitSemicolonDelimitedValues(item.Exclude);
}
public static IEnumerable<string> Removes(
this ProjectItemElement item)
{
return SplitSemicolonDelimitedValues(item.Remove);
}
public static IEnumerable<string> AllConditions(this ProjectElement projectElement)
{
return new string[] { projectElement.Condition }.Concat(projectElement.AllParents.Select(p=> p.Condition));
@ -49,6 +128,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration
return item.Includes().Intersect(otherItem.Includes());
}
public static IEnumerable<string> IntersectExcludes(this ProjectItemElement item, ProjectItemElement otherItem)
{
return item.Excludes().Intersect(otherItem.Excludes());
}
public static void RemoveIncludes(this ProjectItemElement item, IEnumerable<string> includesToRemove)
{
item.Include = string.Join(";", item.Includes().Except(includesToRemove));
@ -64,11 +148,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration
item.Exclude = string.Join(";", item.Excludes().Union(excludesToAdd));
}
public static ProjectMetadataElement GetMetadataWithName(this ProjectItemElement item, string name)
{
return item.Metadata.FirstOrDefault(m => m.Name.Equals(name, StringComparison.Ordinal));
}
public static bool ValueEquals(this ProjectMetadataElement metadata, ProjectMetadataElement otherMetadata)
{
return metadata.Value.Equals(otherMetadata.Value, StringComparison.Ordinal);
@ -90,6 +169,24 @@ namespace Microsoft.DotNet.ProjectJsonMigration
}
}
public static ProjectMetadataElement GetMetadataWithName(this ProjectItemElement item, string name)
{
return item.Metadata.FirstOrDefault(m => m.Name.Equals(name, StringComparison.Ordinal));
}
public static bool HasConflictingMetadata(this ProjectItemElement item, ProjectItemElement otherItem)
{
foreach (var metadata in item.Metadata)
{
if (otherItem.Metadata.Any(m => m.Name == metadata.Name && m.Value != metadata.Value))
{
return true;
}
}
return false;
}
public static void AddMetadata(this ProjectItemElement item, ProjectMetadataElement metadata)
{
var existingMetadata = item.GetMetadataWithName(metadata.Name);

View file

@ -18,13 +18,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration
public class ProjectMigrator
{
// TODO: Migrate PackOptions
// TODO: Support Mappings in IncludeContext Transformations
// TODO: Migrate Multi-TFM projects
// TODO: Tests
// TODO: Out of Scope
// - Globs that resolve to directories: /some/path/**/somedir
// - Migrating Deprecated project.jsons
// - Configuration dependent source exclusion
private readonly IMigrationRule _ruleSet;
@ -85,7 +83,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration
if (diagnostics.Any())
{
MigrationErrorCodes.MIGRATE1011(
$"{projectDirectory}{Environment.NewLine}{string.Join(Environment.NewLine, diagnostics.Select(d => d.Message))}")
$"{projectDirectory}{Environment.NewLine}{string.Join(Environment.NewLine, diagnostics.Select(d => FormatDiagnosticMessage(d)))}")
.Throw();
}
@ -99,6 +97,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration
}
}
private string FormatDiagnosticMessage(DiagnosticMessage d)
{
return $"{d.Message} (line: {d.StartLine}, file: {d.SourceFilePath})";
}
private void SetupOutputDirectory(string projectDirectory, string outputDirectory)
{
if (!Directory.Exists(outputDirectory))

View file

@ -23,7 +23,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
new AddPropertyTransform<CommonCompilerOptions>("OutputType", "Exe",
compilerOptions => compilerOptions.EmitEntryPoint != null && compilerOptions.EmitEntryPoint.Value),
new AddPropertyTransform<CommonCompilerOptions>("OutputType", "Library",
compilerOptions => compilerOptions.EmitEntryPoint == null || !compilerOptions.EmitEntryPoint.Value)
compilerOptions => compilerOptions.EmitEntryPoint != null && !compilerOptions.EmitEntryPoint.Value)
};
private AddPropertyTransform<CommonCompilerOptions>[] KeyFileTransforms
@ -39,12 +39,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
private AddPropertyTransform<CommonCompilerOptions> DefineTransform => new AddPropertyTransform<CommonCompilerOptions>(
"DefineConstants",
compilerOptions => string.Join(";", compilerOptions.Defines),
compilerOptions => "$(DefineConstants);" + string.Join(";", compilerOptions.Defines),
compilerOptions => compilerOptions.Defines != null && compilerOptions.Defines.Any());
private AddPropertyTransform<CommonCompilerOptions> NoWarnTransform => new AddPropertyTransform<CommonCompilerOptions>(
"NoWarn",
compilerOptions => string.Join(";", compilerOptions.SuppressWarnings),
compilerOptions => "$(NoWarn);" + string.Join(";", compilerOptions.SuppressWarnings),
compilerOptions => compilerOptions.SuppressWarnings != null && compilerOptions.SuppressWarnings.Any());
private AddPropertyTransform<CommonCompilerOptions> PreserveCompilationContextTransform =>
@ -129,11 +129,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
private Func<CommonCompilerOptions, string, IEnumerable<ProjectItemElement>> CopyToOutputFilesTransformExecute =>
(compilerOptions, projectDirectory) =>
CopyToOutputFilesTransform.Transform(GetCopyToOutputIncludeContext(compilerOptions, projectDirectory));
private readonly string _configuration;
private readonly NuGetFramework _framework;
private readonly ProjectPropertyGroupElement _configurationPropertyGroup;
private readonly ProjectItemGroupElement _configurationItemGroup;
private readonly CommonCompilerOptions _configurationBuildOptions;
private List<AddPropertyTransform<CommonCompilerOptions>> _propertyTransforms;
private List<Func<CommonCompilerOptions, string, IEnumerable<ProjectItemElement>>> _includeContextTransformExecutes;
@ -147,14 +146,12 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
}
public MigrateBuildOptionsRule(
string configuration,
NuGetFramework framework,
CommonCompilerOptions configurationBuildOptions,
ProjectPropertyGroupElement configurationPropertyGroup,
ProjectItemGroupElement configurationItemGroup,
ITransformApplicator transformApplicator = null)
{
_configuration = configuration;
_framework = framework;
_configurationBuildOptions = configurationBuildOptions;
_configurationPropertyGroup = configurationPropertyGroup;
_configurationItemGroup = configurationItemGroup;
_transformApplicator = transformApplicator ?? new TransformApplicator();
@ -201,13 +198,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
var propertyGroup = _configurationPropertyGroup ?? migrationRuleInputs.CommonPropertyGroup;
var itemGroup = _configurationItemGroup ?? migrationRuleInputs.CommonItemGroup;
var compilerOptions = projectContext.ProjectFile.GetCompilerOptions(projectContext.TargetFramework, null);
var configurationCompilerOptions =
projectContext.ProjectFile.GetCompilerOptions(_framework, _configuration);
var compilerOptions = projectContext.ProjectFile.GetCompilerOptions(null, null);
// If we're in a configuration, we need to be careful not to overwrite values from BuildOptions
// without a configuration
if (_configuration == null)
if (_configurationBuildOptions == null)
{
CleanExistingProperties(csproj);
@ -222,7 +217,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
PerformConfigurationPropertyAndItemMappings(
compilerOptions,
configurationCompilerOptions,
_configurationBuildOptions,
propertyGroup,
itemGroup,
_transformApplicator,
@ -254,47 +249,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
var nonConfigurationOutput = includeContextTransformExecute(compilerOptions, projectDirectory);
var configurationOutput = includeContextTransformExecute(configurationCompilerOptions, projectDirectory).ToArray();
if (configurationOutput != null && nonConfigurationOutput != null)
{
// TODO: HACK: this is leaky, see top comments, the throw at least covers the scenario
ThrowIfConfigurationHasAdditionalExcludes(configurationOutput, nonConfigurationOutput);
RemoveCommonIncludes(configurationOutput, nonConfigurationOutput);
configurationOutput = configurationOutput.Where(i => i != null && !string.IsNullOrEmpty(i.Include)).ToArray();
}
// Don't merge with existing items when doing a configuration
transformApplicator.Execute(configurationOutput, itemGroup, mergeExisting: false);
}
}
private void ThrowIfConfigurationHasAdditionalExcludes(IEnumerable<ProjectItemElement> configurationOutput, IEnumerable<ProjectItemElement> nonConfigurationOutput)
{
foreach (var item1 in configurationOutput)
{
if (item1 == null)
{
continue;
}
var item2Excludes = new HashSet<string>();
foreach (var item2 in nonConfigurationOutput)
{
if (item2 != null)
{
item2Excludes.UnionWith(item2.Excludes());
}
}
var configurationHasAdditionalExclude =
item1.Excludes().Any(exclude => item2Excludes.All(item2Exclude => item2Exclude != exclude));
if (configurationHasAdditionalExclude)
{
MigrationTrace.Instance.WriteLine(item1.Exclude);
MigrationTrace.Instance.WriteLine(item2Excludes.ToString());
MigrationErrorCodes.MIGRATE20012("Unable to migrate projects with excluded files in configurations.")
.Throw();
}
transformApplicator.Execute(configurationOutput, itemGroup, mergeExisting: true);
}
}

View file

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Construction;
using NuGet.Frameworks;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
@ -13,56 +14,75 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
MigrationTrace.Instance.WriteLine($"Executing rule: {nameof(MigrateConfigurationsRule)}");
var projectContext = migrationRuleInputs.DefaultProjectContext;
var configurations = projectContext.ProjectFile.GetConfigurations().ToList();
var frameworks = new List<NuGetFramework>();
frameworks.Add(null);
frameworks.AddRange(projectContext.ProjectFile.GetTargetFrameworks().Select(t => t.FrameworkName));
if (!configurations.Any())
if (!configurations.Any() && !frameworks.Any())
{
return;
}
var frameworkConfigurationCombinations = frameworks.SelectMany(f => configurations, Tuple.Create);
foreach (var entry in frameworkConfigurationCombinations)
foreach (var framework in frameworks)
{
var framework = entry.Item1;
var configuration = entry.Item2;
MigrateConfiguration(projectContext.ProjectFile, framework, migrationSettings, migrationRuleInputs);
}
MigrateConfiguration(configuration, framework, migrationSettings, migrationRuleInputs);
foreach (var configuration in configurations)
{
MigrateConfiguration(projectContext.ProjectFile, configuration, migrationSettings, migrationRuleInputs);
}
}
private void MigrateConfiguration(
Project project,
string configuration,
NuGetFramework framework,
MigrationSettings migrationSettings,
MigrationRuleInputs migrationRuleInputs)
{
var buildOptions = project.GetRawCompilerOptions(configuration);
var configurationCondition = $" '$(Configuration)' == '{configuration}' ";
MigrateConfiguration(buildOptions, configurationCondition, migrationSettings, migrationRuleInputs);
}
private void MigrateConfiguration(
Project project,
NuGetFramework framework,
MigrationSettings migrationSettings,
MigrationRuleInputs migrationRuleInputs)
{
var buildOptions = project.GetRawCompilerOptions(framework);
var configurationCondition = $" '$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)' == '{framework.DotNetFrameworkName}' ";
MigrateConfiguration(buildOptions, configurationCondition, migrationSettings, migrationRuleInputs);
}
private void MigrateConfiguration(
CommonCompilerOptions buildOptions,
string configurationCondition,
MigrationSettings migrationSettings,
MigrationRuleInputs migrationRuleInputs)
{
var csproj = migrationRuleInputs.OutputMSBuildProject;
var propertyGroup = CreatePropertyGroupAtEndOfProject(csproj);
var itemGroup = CreateItemGroupAtEndOfProject(csproj);
var configurationCondition = $" '$(Configuration)' == '{configuration}' ";
if (framework != null)
{
configurationCondition +=
$" and '$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)' == '{framework.DotNetFrameworkName}' ";
}
propertyGroup.Condition = configurationCondition;
itemGroup.Condition = configurationCondition;
new MigrateBuildOptionsRule(configuration, framework, propertyGroup, itemGroup)
new MigrateBuildOptionsRule(buildOptions, propertyGroup, itemGroup)
.Apply(migrationSettings, migrationRuleInputs);
propertyGroup.RemoveIfEmpty();
itemGroup.RemoveIfEmpty();
}
private ProjectPropertyGroupElement CreatePropertyGroupAtEndOfProject(ProjectRootElement csproj)
{
var propertyGroup = csproj.CreatePropertyGroupElement();

View file

@ -7,19 +7,57 @@ using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectJsonMigration.Models;
using Microsoft.DotNet.Tools.Common;
namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
{
public class IncludeContextTransform : ConditionalTransform<IncludeContext, IEnumerable<ProjectItemElement>>
{
// TODO: If a directory is specified in project.json does this need to be replaced with a glob in msbuild?
// - Partially solved, what if the resolved glob is a directory?
// TODO: Support mappings
private Func<string, AddItemTransform<IncludeContext>> IncludeFilesExcludeFilesTransformGetter =>
(itemName) =>
new AddItemTransform<IncludeContext>(
itemName,
includeContext => FormatGlobPatternsForMsbuild(includeContext.IncludeFiles, includeContext.SourceBasePath),
includeContext => FormatGlobPatternsForMsbuild(includeContext.ExcludeFiles, includeContext.SourceBasePath),
includeContext => includeContext != null && includeContext.IncludeFiles.Count > 0);
private Func<string, AddItemTransform<IncludeContext>> IncludeExcludeTransformGetter =>
(itemName) => new AddItemTransform<IncludeContext>(
itemName,
includeContext =>
{
var fullIncludeSet = includeContext.IncludePatterns.OrEmptyIfNull()
.Union(includeContext.BuiltInsInclude.OrEmptyIfNull());
return FormatGlobPatternsForMsbuild(fullIncludeSet, includeContext.SourceBasePath);
},
includeContext =>
{
var fullExcludeSet = includeContext.ExcludePatterns.OrEmptyIfNull()
.Union(includeContext.BuiltInsExclude.OrEmptyIfNull())
.Union(includeContext.ExcludeFiles.OrEmptyIfNull());
return FormatGlobPatternsForMsbuild(fullExcludeSet, includeContext.SourceBasePath);
},
includeContext =>
{
return includeContext != null &&
(
(includeContext.IncludePatterns != null && includeContext.IncludePatterns.Count > 0)
||
(includeContext.BuiltInsInclude != null && includeContext.BuiltInsInclude.Count > 0)
);
});
private Func<string, string, AddItemTransform<IncludeContext>> MappingsIncludeFilesExcludeFilesTransformGetter =>
(itemName, targetPath) => AddMappingToTransform(IncludeFilesExcludeFilesTransformGetter(itemName), targetPath);
private Func<string, string, AddItemTransform<IncludeContext>> MappingsIncludeExcludeTransformGetter =>
(itemName, targetPath) => AddMappingToTransform(IncludeExcludeTransformGetter(itemName), targetPath);
private readonly string _itemName;
private bool _transformMappings;
private readonly List<ItemMetadataValue<IncludeContext>> _metadata = new List<ItemMetadataValue<IncludeContext>>();
private AddItemTransform<IncludeContext>[] _transformSet;
public IncludeContextTransform(
string itemName,
@ -42,55 +80,55 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
return this;
}
private void CreateTransformSet()
private IEnumerable<Tuple<AddItemTransform<IncludeContext>, IncludeContext>> CreateTransformSet(IncludeContext source)
{
var includeFilesExcludeFilesTransformation = new AddItemTransform<IncludeContext>(
_itemName,
includeContext => FormatPatterns(includeContext.IncludeFiles, includeContext.SourceBasePath),
includeContext => FormatPatterns(includeContext.ExcludeFiles, includeContext.SourceBasePath),
includeContext => includeContext != null && includeContext.IncludeFiles.Count > 0);
var includeExcludeTransformation = new AddItemTransform<IncludeContext>(
_itemName,
includeContext =>
{
var fullIncludeSet = includeContext.IncludePatterns.OrEmptyIfNull()
.Union(includeContext.BuiltInsInclude.OrEmptyIfNull());
return FormatPatterns(fullIncludeSet, includeContext.SourceBasePath);
},
includeContext =>
{
var fullExcludeSet = includeContext.ExcludePatterns.OrEmptyIfNull()
.Union(includeContext.BuiltInsExclude.OrEmptyIfNull())
.Union(includeContext.ExcludeFiles.OrEmptyIfNull());
return FormatPatterns(fullExcludeSet, includeContext.SourceBasePath);
},
includeContext =>
{
return includeContext != null &&
(
(includeContext.IncludePatterns != null && includeContext.IncludePatterns.Count > 0)
||
(includeContext.BuiltInsInclude != null && includeContext.BuiltInsInclude.Count > 0)
);
});
foreach (var metadata in _metadata)
var transformSet = new List<Tuple<AddItemTransform<IncludeContext>, IncludeContext>>
{
includeFilesExcludeFilesTransformation.WithMetadata(metadata);
includeExcludeTransformation.WithMetadata(metadata);
Tuple.Create(IncludeFilesExcludeFilesTransformGetter(_itemName), source),
Tuple.Create(IncludeExcludeTransformGetter(_itemName), source)
};
if (source == null)
{
return transformSet;
}
// Mappings must be executed before the transform set to prevent a the
// non-mapped items that will merge with mapped items from being encompassed
foreach (var mappingEntry in source.Mappings.OrEmptyIfNull())
{
var targetPath = mappingEntry.Key;
var includeContext = mappingEntry.Value;
transformSet.Insert(0,
Tuple.Create(
MappingsIncludeExcludeTransformGetter(_itemName, targetPath),
includeContext));
transformSet.Insert(0,
Tuple.Create(
MappingsIncludeFilesExcludeFilesTransformGetter(_itemName, targetPath),
includeContext));
}
_transformSet = new []
foreach (var metadataElement in _metadata)
{
includeFilesExcludeFilesTransformation,
includeExcludeTransformation
};
foreach (var transform in transformSet)
{
transform.Item1.WithMetadata(metadataElement);
}
}
return transformSet;
}
private string FormatPatterns(IEnumerable<string> patterns, string projectDirectory)
public override IEnumerable<ProjectItemElement> ConditionallyTransform(IncludeContext source)
{
var transformSet = CreateTransformSet(source);
return transformSet.Select(t => t.Item1.Transform(t.Item2));
}
private string FormatGlobPatternsForMsbuild(IEnumerable<string> patterns, string projectDirectory)
{
List<string> mutatedPatterns = new List<string>(patterns.Count());
@ -121,6 +159,16 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
}
}
private AddItemTransform<IncludeContext> AddMappingToTransform(
AddItemTransform<IncludeContext> addItemTransform,
string targetPath)
{
var targetIsFile = MappingsTargetPathIsFile(targetPath);
var msbuildLinkMetadataValue = ConvertTargetPathToMsbuildMetadata(targetPath, targetIsFile);
return addItemTransform.WithMetadata("Link", msbuildLinkMetadataValue);
}
private bool PatternIsDirectory(string pattern, string projectDirectory)
{
// TODO: what about /some/path/**/somedir?
@ -135,11 +183,21 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
return Directory.Exists(path);
}
public override IEnumerable<ProjectItemElement> ConditionallyTransform(IncludeContext source)
private string ConvertTargetPathToMsbuildMetadata(string targetPath, bool targetIsFile)
{
CreateTransformSet();
if (targetIsFile)
{
return targetPath;
}
return _transformSet.Select(t => t.Transform(source));
return $"{targetPath}%(FileName)%(Extension)";
}
private bool MappingsTargetPathIsFile(string targetPath)
{
var normalizedTargetPath = PathUtility.GetPathWithDirectorySeparator(targetPath);
return normalizedTargetPath[normalizedTargetPath.Length - 1] != Path.DirectorySeparatorChar;
}
}
}

View file

@ -66,34 +66,109 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
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)
{
var existingItems = FindExistingItems(item, destinationItemGroup.ContainingProject);
item = MergeWithExistingItemsWithSameCondition(item, destinationItemGroup);
foreach (var existingItem in existingItems)
// Item will be null when it's entire set of includes has been merged.
if (item == null)
{
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);
}
Execute(mergeResult.MergedItem, destinationItemGroup);
MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Item completely merged");
return;
}
// Item will be null only when it's entire set of includes is merged with existing items
if (item != null)
item = MergeWithExistingItemsWithDifferentCondition(item, destinationItemGroup);
// Item will be null when it is equivalent to a conditionless item
if (item == null)
{
Execute(item, destinationItemGroup);
MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: Item c");
return;
}
}
else
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)
{
Execute(item, destinationItemGroup);
// When the existing item encompasses this item and it's condition is empty, ignore the current item
if (item.IsEquivalentTo(existingItem))
{
MigrationTrace.Instance.WriteLine($"{nameof(TransformApplicator)}: equivalent {existingItem.ConditionChain().Count()}");
if (existingItem.ConditionChain().Count() == 0)
{
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(
@ -132,9 +207,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
}
var commonIncludes = item.IntersectIncludes(existingItem).ToList();
item.RemoveIncludes(commonIncludes);
existingItem.RemoveIncludes(commonIncludes);
var mergedItem = _projectElementGenerator.AddItem(item.ItemType, string.Join(";", commonIncludes));
mergedItem.UnionExcludes(existingItem.Excludes());
@ -143,6 +215,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
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,
@ -153,13 +228,29 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Transforms
return mergeResult;
}
private IEnumerable<ProjectItemElement> FindExistingItems(ProjectItemElement item, ProjectRootElement project)
private IEnumerable<ProjectItemElement> FindExistingItemsWithSameCondition(
ProjectItemElement item,
ProjectRootElement project,
ProjectElementContainer destinationContainer)
{
return project.ItemsWithoutConditions()
.Where(i => string.Equals(i.ItemType, item.ItemType, StringComparison.Ordinal))
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; }

View file

@ -98,8 +98,8 @@ namespace Microsoft.DotNet.ProjectModel
{
// Get all project options and combine them
var rootOptions = GetCompilerOptions();
var configurationOptions = configurationName != null ? GetCompilerOptions(configurationName) : null;
var targetFrameworkOptions = targetFramework != null ? GetCompilerOptions(targetFramework) : null;
var configurationOptions = configurationName != null ? GetRawCompilerOptions(configurationName) : null;
var targetFrameworkOptions = targetFramework != null ? GetRawCompilerOptions(targetFramework) : null;
// Combine all of the options
var compilerOptions = CommonCompilerOptions.Combine(rootOptions, configurationOptions, targetFrameworkOptions);
@ -136,7 +136,7 @@ namespace Microsoft.DotNet.ProjectModel
return _defaultCompilerOptions;
}
private CommonCompilerOptions GetCompilerOptions(string configurationName)
internal CommonCompilerOptions GetRawCompilerOptions(string configurationName)
{
CommonCompilerOptions options;
if (_compilerOptionsByConfiguration.TryGetValue(configurationName, out options))
@ -147,7 +147,7 @@ namespace Microsoft.DotNet.ProjectModel
return null;
}
private CommonCompilerOptions GetCompilerOptions(NuGetFramework frameworkName)
internal CommonCompilerOptions GetRawCompilerOptions(NuGetFramework frameworkName)
{
return GetTargetFramework(frameworkName)?.CompilerOptions;
}

View file

@ -26,6 +26,7 @@ using System.Runtime.InteropServices;
[assembly: Guid("303677d5-7312-4c3f-baee-beb1a9bd9fe6")]
[assembly: AssemblyMetadataAttribute("Serviceable", "True")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectModel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")]
[assembly:
InternalsVisibleTo(

View file

@ -51,8 +51,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
migrateAction.ShouldThrow<Exception>().Where(
e => e.Message.Contains("MIGRATE1011::Deprecated Project:")
&& e.Message.Contains("The 'packInclude' option is deprecated. Use 'files' in 'packOptions' instead.")
&& e.Message.Contains("The 'compilationOptions' option is deprecated. Use 'buildOptions' instead."));
&& e.Message.Contains("The 'packInclude' option is deprecated. Use 'files' in 'packOptions' instead. (line: 6, file:")
&& e.Message.Contains("The 'compilationOptions' option is deprecated. Use 'buildOptions' instead. (line: 3, file:"));
}
[Fact]

View file

@ -7,6 +7,154 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
{
public class GivenMSBuildExtensions
{
[Fact]
public void ConditionChain_is_empty_when_element_and_parents_have_no_condition()
{
var project = ProjectRootElement.Create();
var itemGroup = project.AddItemGroup();
var item1 = itemGroup.AddItem("test", "include1");
item1.ConditionChain().Should().HaveCount(0);
}
[Fact]
public void ConditionChain_has_parent_conditions_when_element_is_empty()
{
var project = ProjectRootElement.Create();
var itemGroup = project.AddItemGroup();
itemGroup.Condition = "condition";
var item1 = itemGroup.AddItem("test", "include1");
item1.ConditionChain().Should().HaveCount(1);
item1.ConditionChain().First().Should().Be("condition");
}
[Fact]
public void ConditionChain_has_element_and_parent_conditions_when_they_exist()
{
var project = ProjectRootElement.Create();
var itemGroup = project.AddItemGroup();
itemGroup.Condition = "itemGroup";
var item1 = itemGroup.AddItem("test", "include1");
item1.Condition = "item";
item1.ConditionChain().Should().HaveCount(2);
item1.ConditionChain().Should().BeEquivalentTo("itemGroup", "item");
}
[Fact]
public void ConditionChainsAreEquivalent_is_true_when_neither_element_or_parents_have_conditions()
{
var project = ProjectRootElement.Create();
var itemGroup = project.AddItemGroup();
var item1 = itemGroup.AddItem("test", "include1");
var item2 = itemGroup.AddItem("test", "include2");
item1.ConditionChainsAreEquivalent(item2).Should().BeTrue();
}
[Fact]
public void ConditionChainsAreEquivalent_is_true_when_elements_have_the_same_condition()
{
var project = ProjectRootElement.Create();
var itemGroup = project.AddItemGroup();
var item1 = itemGroup.AddItem("test", "include1");
var item2 = itemGroup.AddItem("test", "include2");
item1.Condition = "item";
item2.Condition = "item";
item1.ConditionChainsAreEquivalent(item2).Should().BeTrue();
}
[Fact]
public void ConditionChainsAreEquivalent_is_true_when_element_condition_matches_condition_of_other_element_parent()
{
var project = ProjectRootElement.Create();
var itemGroup1 = project.AddItemGroup();
var itemGroup2 = project.AddItemGroup();
itemGroup1.Condition = "item";
var item1 = itemGroup1.AddItem("test", "include1");
var item2 = itemGroup2.AddItem("test", "include2");
item2.Condition = "item";
item1.ConditionChainsAreEquivalent(item2).Should().BeTrue();
}
[Fact]
public void ConditionChainsAreEquivalent_is_false_when_elements_have_different_conditions()
{
var project = ProjectRootElement.Create();
var itemGroup = project.AddItemGroup();
var item1 = itemGroup.AddItem("test", "include1");
var item2 = itemGroup.AddItem("test", "include2");
item1.Condition = "item";
item2.Condition = "item2";
item1.ConditionChainsAreEquivalent(item2).Should().BeFalse();
}
[Fact]
public void ConditionChainsAreEquivalent_is_false_when_other_element_parent_has_a_condition()
{
var project = ProjectRootElement.Create();
var itemGroup1 = project.AddItemGroup();
var itemGroup2 = project.AddItemGroup();
itemGroup1.Condition = "item";
var item1 = itemGroup1.AddItem("test", "include1");
var item2 = itemGroup2.AddItem("test", "include2");
item1.ConditionChainsAreEquivalent(item2).Should().BeFalse();
}
[Fact]
public void ConditionChainsAreEquivalent_is_false_when_both_element_parent_conditions_dont_match()
{
var project = ProjectRootElement.Create();
var itemGroup1 = project.AddItemGroup();
var itemGroup2 = project.AddItemGroup();
itemGroup1.Condition = "item";
itemGroup2.Condition = "item2";
var item1 = itemGroup1.AddItem("test", "include1");
var item2 = itemGroup2.AddItem("test", "include2");
item1.ConditionChainsAreEquivalent(item2).Should().BeFalse();
}
[Fact]
public void HasConflictingMetadata_returns_true_when_items_have_metadata_with_same_name_but_different_value()
{
var project = ProjectRootElement.Create();
var item1 = project.AddItem("test", "include1");
item1.AddMetadata("name", "value");
var item2 = project.AddItem("test1", "include1");
item2.AddMetadata("name", "value2");
item1.HasConflictingMetadata(item2).Should().BeTrue();
}
[Fact]
public void HasConflictingMetadata_returns_false_when_items_have_metadata_with_same_nameand_value()
{
var project = ProjectRootElement.Create();
var item1 = project.AddItem("test", "include1");
item1.AddMetadata("name", "value");
var item2 = project.AddItem("test1", "include1");
item2.AddMetadata("name", "value");
item1.HasConflictingMetadata(item2).Should().BeFalse();
}
[Fact]
public void Includes_returns_include_value_split_by_semicolon()
{

View file

@ -8,8 +8,8 @@
<PropertyGroup Label="Globals">
<ProjectGuid>1F2EF070-AC5F-4078-AFB0-65745AC691B9</ProjectGuid>
<RootNamespace>Microsoft.DotNet.ProjectJsonMigration.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' >..\artifact\obj\$(RootNamespace)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' >..\artifacts\bin\</OutputPath>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\artifact\obj\$(RootNamespace)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\artifacts\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>

View file

@ -48,18 +48,15 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
}
[Fact]
public void Migrating_empty_buildOptions_populates_only_AssemblyName_and_OutputType()
public void Migrating_empty_buildOptions_populates_only_AssemblyName_Compile_and_EmbeddedResource()
{
var mockProj = RunBuildOptionsRuleOnPj(@"
{
""buildOptions"": { }
}");
mockProj.Properties.Count().Should().Be(2);
mockProj.Properties.Any(
p =>
!(p.Name.Equals("AssemblyName", StringComparison.Ordinal) ||
p.Name.Equals("OutputType", StringComparison.Ordinal))).Should().BeFalse();
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");
@ -107,7 +104,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
}");
mockProj.Properties.Count(p => p.Name == "DefineConstants").Should().Be(1);
mockProj.Properties.First(p => p.Name == "DefineConstants").Value.Should().Be("DEBUG;TRACE");
mockProj.Properties.First(p => p.Name == "DefineConstants")
.Value.Should().Be("$(DefineConstants);DEBUG;TRACE");
}
[Fact]
@ -121,7 +119,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
}");
mockProj.Properties.Count(p => p.Name == "NoWarn").Should().Be(1);
mockProj.Properties.First(p => p.Name == "NoWarn").Value.Should().Be("CS0168;CS0219");
mockProj.Properties.First(p => p.Name == "NoWarn").Value.Should().Be("$(NoWarn);CS0168;CS0219");
}
[Fact]

View file

@ -39,6 +39,29 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
.Contain("'$(Configuration)' == 'testconfig'");
}
[Fact]
public void Frameworks_buildOptions_produce_expected_properties_in_a_group_with_a_condition()
{
var mockProj = RunConfigurationsRuleOnPj(@"
{
""frameworks"": {
""netcoreapp1.0"": {
""buildOptions"": {
""emitEntryPoint"": ""true"",
""debugType"": ""full""
}
}
}
}");
mockProj.Properties.Count(
prop => prop.Name == "OutputType" || prop.Name == "DebugType").Should().Be(2);
mockProj.Properties.First(p => p.Name == "OutputType")
.Parent.Condition.Should()
.Contain("'$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)' == '.NETCoreApp,Version=v1.0'");
}
[Fact]
public void Configuration_buildOptions_properties_are_not_written_when_they_overlap_with_buildOptions()
{
@ -67,11 +90,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
{
property.Parent.Condition.Should().Be(string.Empty);
}
}
[Fact]
public void Configuration_buildOptions_includes_are_not_written_when_they_overlap_with_buildOptions()
public void Configuration_buildOptions_includes_and_Remove_are_written_when_they_differ_from_base_buildOptions()
{
var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
{
@ -97,25 +119,31 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
}
}");
mockProj.Items.Count(item => item.ItemType == "Content").Should().Be(3);
var contentItems = mockProj.Items.Where(item => item.ItemType == "Content");
mockProj.Items.Where(item => item.ItemType == "Content")
.Count(item => !string.IsNullOrEmpty(item.Parent.Condition))
.Should()
.Be(1);
contentItems.Count().Should().Be(4);
var configContent = mockProj.Items
.Where(item => item.ItemType == "Content").First(item => !string.IsNullOrEmpty(item.Parent.Condition));
// 2 for Base Build options
contentItems.Where(i => i.ConditionChain().Count() == 0).Should().HaveCount(2);
// 2 for Configuration BuildOptions (1 Remove, 1 Include)
contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(2);
var configIncludeContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0 && !string.IsNullOrEmpty(item.Include));
var configRemoveContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0 && !string.IsNullOrEmpty(item.Remove));
// Directories are not converted to globs in the result because we did not write the directory
configContent.Include.Should().Be(@"root;rootfile.cs");
configContent.Exclude.Should().Be(@"src;rootfile.cs;src\file2.cs");
configRemoveContentItem.Remove.Should().Be(@"root;src;rootfile.cs");
configIncludeContentItem.Include.Should().Be(@"root;src;rootfile.cs");
configIncludeContentItem.Exclude.Should().Be(@"src;rootfile.cs;src\file2.cs");
}
[Fact]
public void Configuration_buildOptions_includes_which_have_different_excludes_than_buildOptions_throws()
public void Configuration_buildOptions_which_have_different_excludes_than_buildOptions_overwrites()
{
Action action = () => RunConfigurationsRuleOnPj(@"
var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
{
""buildOptions"": {
""copyToOutput"": {
@ -130,7 +158,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
""buildOptions"": {
""copyToOutput"": {
""include"": [""root"", ""src"", ""rootfile.cs""],
""exclude"": [""src"", ""rootfile.cs""],
""exclude"": [""rootfile.cs"", ""someotherfile.cs""],
""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
""excludeFiles"": [""src/file2.cs""]
}
@ -139,10 +167,307 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
}
}");
action.ShouldThrow<Exception>()
.WithMessage(
"MIGRATE20012::Configuration Exclude: Unable to migrate projects with excluded files in configurations.");
var contentItems = mockProj.Items.Where(item => item.ItemType == "Content");
contentItems.Count().Should().Be(5);
// 2 for Base Build options
contentItems.Where(i => i.ConditionChain().Count() == 0).Should().HaveCount(2);
// 3 for Configuration BuildOptions (1 Remove, 2 Include)
contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(3);
var configIncludeContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include.Contains("root"));
var configIncludeContentItem2 = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include.Contains(@"src\file1.cs"));
var configRemoveContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0 && !string.IsNullOrEmpty(item.Remove));
// Directories are not converted to globs in the result because we did not write the directory
configRemoveContentItem.Removes()
.Should().BeEquivalentTo("root", "src", "rootfile.cs", @"src\file1.cs", @"src\file2.cs");
configIncludeContentItem.Includes().Should().BeEquivalentTo("root", "src", "rootfile.cs");
configIncludeContentItem.Excludes()
.Should().BeEquivalentTo("rootfile.cs", "someotherfile.cs", @"src\file2.cs");
configIncludeContentItem2.Includes().Should().BeEquivalentTo(@"src\file1.cs", @"src\file2.cs");
configIncludeContentItem2.Excludes().Should().BeEquivalentTo(@"src\file2.cs");
}
[Fact]
public void Configuration_buildOptions_which_have_mappings_to_directory_add_link_metadata_with_item_metadata()
{
var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
{
""configurations"": {
""testconfig"": {
""buildOptions"": {
""copyToOutput"": {
""mappings"": {
""/some/dir/"" : {
""include"": [""src"", ""root""],
""exclude"": [""src"", ""rootfile.cs""],
""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
""excludeFiles"": [""src/file2.cs""]
}
}
}
}
}
}
}");
var contentItems = mockProj.Items.Where(item => item.ItemType == "Content");
contentItems.Count().Should().Be(2);
contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(2);
var configIncludeContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include.Contains("root"));
var configIncludeContentItem2 = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include.Contains(@"src\file1.cs"));
configIncludeContentItem.Includes().Should().BeEquivalentTo("root", "src");
configIncludeContentItem.Excludes()
.Should().BeEquivalentTo("rootfile.cs", "src", @"src\file2.cs");
configIncludeContentItem.GetMetadataWithName("Link").Should().NotBeNull();
configIncludeContentItem.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
configIncludeContentItem2.Includes().Should().BeEquivalentTo(@"src\file1.cs", @"src\file2.cs");
configIncludeContentItem2.Excludes().Should().BeEquivalentTo(@"src\file2.cs");
configIncludeContentItem2.GetMetadataWithName("Link").Should().NotBeNull();
configIncludeContentItem2.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
}
[Fact]
public void Configuration_buildOptions_which_have_mappings_overlapping_with_includes_in_same_configuration_merged_items_have_Link_metadata()
{
var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
{
""configurations"": {
""testconfig"": {
""buildOptions"": {
""copyToOutput"": {
""include"": [""src"", ""root""],
""exclude"": [""src"", ""rootfile.cs""],
""includeFiles"": [""src/file1.cs""],
""excludeFiles"": [""src/file2.cs""],
""mappings"": {
""/some/dir/"" : {
""include"": [""src""],
""exclude"": [""src"", ""src/rootfile.cs""]
}
}
}
}
}
}
}");
var contentItems = mockProj.Items.Where(item => item.ItemType == "Content");
contentItems.Count().Should().Be(3);
contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(3);
var configIncludeContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include == "root");
var configIncludeContentItem2 = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include == "src");
var configIncludeContentItem3 = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include.Contains(@"src\file1.cs"));
// Directories are not converted to globs in the result because we did not write the directory
configIncludeContentItem.Includes().Should().BeEquivalentTo("root");
configIncludeContentItem.Excludes()
.Should().BeEquivalentTo("rootfile.cs", "src", @"src\file2.cs");
configIncludeContentItem.GetMetadataWithName("Link").Should().BeNull();
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
configIncludeContentItem2.Include.Should().Be("src");
configIncludeContentItem2.Excludes().Should().BeEquivalentTo("src", "rootfile.cs", @"src\rootfile.cs", @"src\file2.cs");
configIncludeContentItem2.GetMetadataWithName("Link").Should().NotBeNull();
configIncludeContentItem2.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
configIncludeContentItem3.Includes().Should().BeEquivalentTo(@"src\file1.cs");
configIncludeContentItem3.Exclude.Should().Be(@"src\file2.cs");
configIncludeContentItem3.GetMetadataWithName("Link").Should().BeNull();
configIncludeContentItem3.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem3.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
}
[Fact]
public void Configuration_buildOptions_which_have_mappings_overlapping_with_includes_in_root_buildoptions_has_remove()
{
var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
{
""buildOptions"" : {
""copyToOutput"": {
""include"": [""src"", ""root""],
""exclude"": [""src"", ""rootfile.cs""],
""includeFiles"": [""src/file1.cs"", ""src/file2.cs""],
""excludeFiles"": [""src/file2.cs""]
}
},
""configurations"": {
""testconfig"": {
""buildOptions"": {
""copyToOutput"": {
""mappings"": {
""/some/dir/"" : {
""include"": [""src""],
""exclude"": [""src"", ""rootfile.cs""]
}
}
}
}
}
}
}");
var contentItems = mockProj.Items.Where(item => item.ItemType == "Content");
contentItems.Count().Should().Be(4);
var rootBuildOptionsContentItems = contentItems.Where(i => i.ConditionChain().Count() == 0).ToList();
rootBuildOptionsContentItems.Count().Should().Be(2);
foreach (var buildOptionContentItem in rootBuildOptionsContentItems)
{
buildOptionContentItem.GetMetadataWithName("Link").Should().BeNull();
buildOptionContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
}
var configItems = contentItems.Where(i => i.ConditionChain().Count() == 1);
configItems.Should().HaveCount(2);
var configIncludeContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include.Contains("src"));
var configRemoveContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0
&& !string.IsNullOrEmpty(item.Remove));
configIncludeContentItem.Include.Should().Be("src");
configIncludeContentItem.GetMetadataWithName("Link").Should().NotBeNull();
configIncludeContentItem.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
configRemoveContentItem.Remove.Should().Be("src");
}
[Fact]
public void Configuration_buildOptions_which_have_mappings_overlapping_with_includes_in_same_configuration_and_root_buildOptions_have_removes_and_Link_metadata_and_encompassed_items_are_merged()
{
var mockProj = RunConfigurationsAndBuildOptionsRuleOnPj(@"
{
""buildOptions"" : {
""copyToOutput"": {
""include"": [""src"", ""root""],
""exclude"": [""src"", ""rootfile.cs""],
""includeFiles"": [""src/file1.cs""],
""excludeFiles"": [""src/file2.cs""]
}
},
""configurations"": {
""testconfig"": {
""buildOptions"": {
""copyToOutput"": {
""include"": [""src"", ""root""],
""exclude"": [""src"", ""rootfile.cs""],
""includeFiles"": [""src/file3.cs""],
""excludeFiles"": [""src/file2.cs""],
""mappings"": {
""/some/dir/"" : {
""include"": [""src""],
""exclude"": [""src"", ""src/rootfile.cs""]
}
}
}
}
}
}
}");
var contentItems = mockProj.Items.Where(item => item.ItemType == "Content");
contentItems.Count().Should().Be(5);
contentItems.Where(i => i.ConditionChain().Count() == 1).Should().HaveCount(3);
var rootBuildOptionsContentItems = contentItems.Where(i => i.ConditionChain().Count() == 0).ToList();
rootBuildOptionsContentItems.Count().Should().Be(2);
foreach (var buildOptionContentItem in rootBuildOptionsContentItems)
{
buildOptionContentItem.GetMetadataWithName("Link").Should().BeNull();
buildOptionContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
}
var configIncludeEncompassedItem = contentItems.FirstOrDefault(
item => item.ConditionChain().Count() > 0
&& item.Include == "root");
configIncludeEncompassedItem.Should().BeNull();
var configIncludeContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include == "src");
var configIncludeContentItem2 = contentItems.First(
item => item.ConditionChain().Count() > 0
&& item.Include.Contains(@"src\file3.cs"));
var configRemoveContentItem = contentItems.First(
item => item.ConditionChain().Count() > 0
&& !string.IsNullOrEmpty(item.Remove));
configIncludeContentItem.Include.Should().Be("src");
configIncludeContentItem.Excludes().Should().BeEquivalentTo("src", "rootfile.cs", @"src\rootfile.cs", @"src\file2.cs");
configIncludeContentItem.GetMetadataWithName("Link").Should().NotBeNull();
configIncludeContentItem.GetMetadataWithName("Link").Value.Should().Be("/some/dir/%(FileName)%(Extension)");
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
configIncludeContentItem2.Includes().Should().BeEquivalentTo(@"src\file3.cs");
configIncludeContentItem2.Exclude.Should().Be(@"src\file2.cs");
configIncludeContentItem2.GetMetadataWithName("Link").Should().BeNull();
configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Should().NotBeNull();
configIncludeContentItem2.GetMetadataWithName("CopyToOutputDirectory").Value.Should().Be("PreserveNewest");
configRemoveContentItem.Removes().Should().BeEquivalentTo("src");
}
private ProjectRootElement RunConfigurationsRuleOnPj(string s, string testDirectory = null)
{
testDirectory = testDirectory ?? Temp.CreateDirectory().Path;

View file

@ -25,6 +25,7 @@ namespace Microsoft.DotNet.Migration.Tests
// TODO: Standalone apps [InlineData("TestAppSimple", false)]
// https://github.com/dotnet/sdk/issues/73 [InlineData("TestAppWithLibrary/TestApp", false)]
[InlineData("TestAppWithRuntimeOptions")]
[InlineData("TestAppWithContents")]
public void It_migrates_apps(string projectName)
{
var projectDirectory = TestAssetsManager.CreateTestInstance(projectName, callingMethod: "i").WithLockFiles().Path;
@ -204,6 +205,8 @@ namespace Microsoft.DotNet.Migration.Tests
private string BuildMSBuild(string projectDirectory, string configuration="Debug")
{
DeleteXproj(projectDirectory);
var result = new Build3Command()
.WithWorkingDirectory(projectDirectory)
.ExecuteWithCapturedOutput($"/p:Configuration={configuration}");
@ -215,6 +218,15 @@ namespace Microsoft.DotNet.Migration.Tests
return result.StdOut;
}
private void DeleteXproj(string projectDirectory)
{
var xprojFiles = Directory.EnumerateFiles(projectDirectory, "*.xproj");
foreach (var xprojFile in xprojFiles)
{
File.Delete(xprojFile);
}
}
private void OutputDiagnostics(MigratedBuildComparisonData comparisonData)
{
OutputDiagnostics(comparisonData.MSBuildBuildOutputs, comparisonData.ProjectJsonBuildOutputs);