Microsoft.DotNet.ProjectJsonMigration core library

This commit is contained in:
Bryan Thornbury 2016-08-22 12:21:34 -07:00
parent be8428cb6c
commit 46818ff3fa
32 changed files with 2320 additions and 0 deletions

View file

@ -0,0 +1,41 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class DefaultMigrationRuleSet : IMigrationRule
{
private IMigrationRule[] Rules => new IMigrationRule[]
{
new MigrateRootOptionsRule(),
new MigrateTFMRule(),
new MigrateBuildOptionsRule(),
new MigrateRuntimeOptionsRule(),
new MigratePublishOptionsRule(),
new MigrateProjectDependenciesRule(),
new MigrateConfigurationsRule(),
new MigrateScriptsRule(),
new TemporaryMutateProjectJsonRule(),
new SaveOutputProjectRule()
};
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
foreach (var rule in Rules)
{
MigrationTrace.Instance.WriteLine($"{nameof(DefaultMigrationRuleSet)}: Executing migration rule {nameof(rule)}");
rule.Apply(migrationSettings, migrationRuleInputs);
}
}
}
}

View file

@ -0,0 +1,114 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public static class MSBuildExtensions
{
public static IEnumerable<ProjectPropertyElement> PropertiesWithoutConditions(
this ProjectRootElement projectRoot)
{
return projectRoot.Properties
.Where(p => p.Condition == string.Empty
&& p.AllParents.Count(parent => parent.Condition != string.Empty) == 0);
}
public static IEnumerable<ProjectItemElement> ItemsWithoutConditions(
this ProjectRootElement projectRoot)
{
return projectRoot.Items
.Where(p => string.IsNullOrEmpty(p.Condition)
&& p.AllParents.All(parent => string.IsNullOrEmpty(parent.Condition)));
}
public static IEnumerable<string> Includes(
this ProjectItemElement item)
{
return item.Include.Equals(string.Empty) ? Enumerable.Empty<string>() : item.Include.Split(';');
}
public static IEnumerable<string> Excludes(
this ProjectItemElement item)
{
return item.Exclude.Equals(string.Empty) ? Enumerable.Empty<string>() : item.Exclude.Split(';');
}
public static IEnumerable<string> AllConditions(this ProjectElement projectElement)
{
return new string[] { projectElement.Condition }.Concat(projectElement.AllParents.Select(p=> p.Condition));
}
public static IEnumerable<string> CommonIncludes(this ProjectItemElement item, ProjectItemElement otherItem)
{
return item.Includes().Intersect(otherItem.Includes());
}
public static void RemoveIncludes(this ProjectItemElement item, IEnumerable<string> includesToRemove)
{
item.Include = string.Join(";", item.Includes().Except(includesToRemove));
}
public static void AddIncludes(this ProjectItemElement item, IEnumerable<string> includes)
{
item.Include = string.Join(";", item.Includes().Union(includes));
}
public static void AddExcludes(this ProjectItemElement item, IEnumerable<string> excludes)
{
item.Exclude = string.Join(";", item.Excludes().Union(excludes));
}
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);
}
public static void AddMetadata(this ProjectItemElement item, ICollection<ProjectMetadataElement> metadatas)
{
foreach (var metadata in metadatas)
{
item.AddMetadata(metadata);
}
}
public static void RemoveIfEmpty(this ProjectElementContainer container)
{
if (!container.Children.Any())
{
container.Parent.RemoveChild(container);
}
}
public static void AddMetadata(this ProjectItemElement item, ProjectMetadataElement metadata)
{
var existingMetadata = item.GetMetadataWithName(metadata.Name);
if (existingMetadata != default(ProjectMetadataElement) && !existingMetadata.ValueEquals(metadata))
{
throw new Exception("Cannot merge metadata with the same name and different values");
}
if (existingMetadata == null)
{
Console.WriteLine(metadata.Name);
item.AddMetadata(metadata.Name, metadata.Value);
}
}
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>0E083818-2320-4388-8007-4F720FD5C634</ProjectGuid>
<RootNamespace>Microsoft.DotNet.ProjectJsonMigration</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,46 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrationRuleInputs
{
public ProjectRootElement OutputMSBuildProject { get; }
public ProjectItemGroupElement CommonItemGroup { get; }
public ProjectPropertyGroupElement CommonPropertyGroup { get; }
public IEnumerable<ProjectContext> ProjectContexts { get; }
public ProjectContext DefaultProjectContext
{
get
{
return ProjectContexts.First();
}
}
public MigrationRuleInputs(
IEnumerable<ProjectContext> projectContexts,
ProjectRootElement outputProject,
ProjectItemGroupElement commonItemGroup,
ProjectPropertyGroupElement commonPropertyGroup)
{
ProjectContexts = projectContexts;
OutputMSBuildProject = outputProject;
CommonItemGroup = commonItemGroup;
CommonPropertyGroup = commonPropertyGroup;
}
}
}

View file

@ -0,0 +1,58 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrationSettings
{
public string ProjectDirectory { get; }
public string OutputDirectory { get; }
public string SdkPackageVersion { get; }
public ProjectRootElement MSBuildProjectTemplate { get; }
public MigrationSettings(
string projectDirectory,
string outputDirectory,
string sdkPackageVersion)
{
ProjectDirectory = projectDirectory;
OutputDirectory = outputDirectory;
SdkPackageVersion = sdkPackageVersion;
MSBuildProjectTemplate = null;
}
public MigrationSettings(
string projectDirectory,
string outputDirectory,
string sdkPackageVersion,
ProjectRootElement msBuildProjectTemplate)
{
ProjectDirectory = projectDirectory;
OutputDirectory = outputDirectory;
SdkPackageVersion = sdkPackageVersion;
MSBuildProjectTemplate = msBuildProjectTemplate;
}
public MigrationSettings(
string projectDirectory,
string outputDirectory,
string sdkPackageVersion,
string msbuildProjectTemplateFilePath)
{
ProjectDirectory = projectDirectory;
OutputDirectory = outputDirectory;
SdkPackageVersion = sdkPackageVersion;
MSBuildProjectTemplate = ProjectRootElement.Open(msbuildProjectTemplateFilePath);
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Text.RegularExpressions;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrationTrace
{
public static MigrationTrace Instance { get; set; }
static MigrationTrace ()
{
Instance = new MigrationTrace();
}
public string EnableEnvironmentVariable => "DOTNET_MIGRATION_TRACE";
public bool IsEnabled
{
get
{
#if DEBUG
return true;
#else
return Environment.GetEnvironmentVariable(EnableEnvironmentVariable) != null;
#endif
}
}
public void Write(string message)
{
if (IsEnabled)
{
Console.Write(message);
}
}
public void WriteLine(string message)
{
if (IsEnabled)
{
Console.WriteLine(message);
}
}
}
}

View file

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class ItemMetadataValue<T>
{
public string MetadataName { get; }
private string _metadataValue;
private Func<T, string> _metadataValueFunc;
public ItemMetadataValue(string metadataName, string metadataValue)
{
MetadataName = metadataName;
_metadataValue = metadataValue;
}
public ItemMetadataValue(string metadataName, Func<T, string> metadataValueFunc)
{
MetadataName = metadataName;
_metadataValueFunc = metadataValueFunc;
}
public string GetMetadataValue(T source)
{
return _metadataValue ?? _metadataValueFunc(source);
}
}
}

View file

@ -0,0 +1,14 @@
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class ProjectProperty
{
public string Name { get; }
public string Value { get; }
public ProjectProperty(string name, string value)
{
Name = name;
Value = value;
}
}
}

View file

@ -0,0 +1,83 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json.Linq;
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
public void Migrate(MigrationSettings migrationSettings)
{
var projectDirectory = migrationSettings.ProjectDirectory;
EnsureDirectoryExists(migrationSettings.OutputDirectory);
var migrationRuleInputs = ComputeMigrationRuleInputs(migrationSettings);
VerifyInputs(migrationRuleInputs);
new DefaultMigrationRuleSet().Apply(migrationSettings, migrationRuleInputs);
}
private void EnsureDirectoryExists(string outputDirectory)
{
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
}
private MigrationRuleInputs ComputeMigrationRuleInputs(MigrationSettings migrationSettings)
{
var projectContexts = ProjectContext.CreateContextForEachFramework(migrationSettings.ProjectDirectory);
var templateMSBuildProject = migrationSettings.MSBuildProjectTemplate ?? ProjectRootElement.Create();
var propertyGroup = templateMSBuildProject.AddPropertyGroup();
var itemGroup = templateMSBuildProject.AddItemGroup();
return new MigrationRuleInputs(projectContexts, templateMSBuildProject, itemGroup, propertyGroup);
}
private void VerifyInputs(MigrationRuleInputs migrationRuleInputs)
{
VerifyProject(migrationRuleInputs.ProjectContexts);
}
private void VerifyProject(IEnumerable<ProjectContext> projectContexts)
{
if (projectContexts.Count() > 1)
{
throw new Exception("MultiTFM projects currently not supported.");
}
if (projectContexts.Count() == 0)
{
throw new Exception("No projects found");
}
if (projectContexts.First().LockFile == null)
{
throw new Exception("Restore must be run prior to project migration.");
}
}
}
}

View file

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests")]

View file

@ -0,0 +1,21 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public interface IMigrationRule
{
void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs);
}
}

View file

@ -0,0 +1,405 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Microsoft.DotNet.ProjectModel.Files;
using Microsoft.DotNet.Cli.Utils;
using Newtonsoft.Json.Linq;
using Project = Microsoft.DotNet.ProjectModel.Project;
namespace Microsoft.DotNet.ProjectJsonMigration
{
// TODO: Should All build options be protected by a configuration condition?
// This will prevent the entire merge issue altogether and sidesteps the problem of having a duplicate include with different excludes...
public class MigrateBuildOptionsRule : IMigrationRule
{
private AddPropertyTransform<CommonCompilerOptions>[] EmitEntryPointTransforms
=> new AddPropertyTransform<CommonCompilerOptions>[]
{
new AddPropertyTransform<CommonCompilerOptions>("OutputType", "Exe",
compilerOptions => compilerOptions.EmitEntryPoint != null && compilerOptions.EmitEntryPoint.Value),
new AddPropertyTransform<CommonCompilerOptions>("TargetExt", ".dll",
compilerOptions => compilerOptions.EmitEntryPoint != null && compilerOptions.EmitEntryPoint.Value),
new AddPropertyTransform<CommonCompilerOptions>("OutputType", "Library",
compilerOptions => compilerOptions.EmitEntryPoint == null || !compilerOptions.EmitEntryPoint.Value)
};
private AddPropertyTransform<CommonCompilerOptions>[] KeyFileTransforms
=> new AddPropertyTransform<CommonCompilerOptions>[]
{
new AddPropertyTransform<CommonCompilerOptions>("AssemblyOriginatorKeyFile",
compilerOptions => compilerOptions.KeyFile,
compilerOptions => !string.IsNullOrEmpty(compilerOptions.KeyFile)),
new AddPropertyTransform<CommonCompilerOptions>("SignAssembly",
"true",
compilerOptions => !string.IsNullOrEmpty(compilerOptions.KeyFile))
};
private AddPropertyTransform<CommonCompilerOptions> DefineTransform => new AddPropertyTransform<CommonCompilerOptions>(
"DefineConstants",
compilerOptions => 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 => compilerOptions.SuppressWarnings != null && compilerOptions.SuppressWarnings.Any());
private AddPropertyTransform<CommonCompilerOptions> PreserveCompilationContextTransform =>
new AddPropertyTransform<CommonCompilerOptions>("PreserveCompilationContext",
compilerOptions => compilerOptions.PreserveCompilationContext.ToString().ToLower(),
compilerOptions => compilerOptions.PreserveCompilationContext != null && compilerOptions.PreserveCompilationContext.Value);
private AddPropertyTransform<CommonCompilerOptions> WarningsAsErrorsTransform =>
new AddPropertyTransform<CommonCompilerOptions>("WarningsAsErrors",
compilerOptions => compilerOptions.WarningsAsErrors.ToString().ToLower(),
compilerOptions => compilerOptions.WarningsAsErrors != null && compilerOptions.WarningsAsErrors.Value);
private AddPropertyTransform<CommonCompilerOptions> AllowUnsafeTransform =>
new AddPropertyTransform<CommonCompilerOptions>("AllowUnsafeBlocks",
compilerOptions => compilerOptions.AllowUnsafe.ToString().ToLower(),
compilerOptions => compilerOptions.AllowUnsafe != null && compilerOptions.AllowUnsafe.Value);
private AddPropertyTransform<CommonCompilerOptions> OptimizeTransform =>
new AddPropertyTransform<CommonCompilerOptions>("Optimize",
compilerOptions => compilerOptions.Optimize.ToString().ToLower(),
compilerOptions => compilerOptions.Optimize != null && compilerOptions.Optimize.Value);
private AddPropertyTransform<CommonCompilerOptions> PlatformTransform =>
new AddPropertyTransform<CommonCompilerOptions>("PlatformTarget",
compilerOptions => compilerOptions.Platform,
compilerOptions => !string.IsNullOrEmpty(compilerOptions.Platform));
private AddPropertyTransform<CommonCompilerOptions> LanguageVersionTransform =>
new AddPropertyTransform<CommonCompilerOptions>("LangVersion",
compilerOptions => compilerOptions.LanguageVersion,
compilerOptions => !string.IsNullOrEmpty(compilerOptions.LanguageVersion));
private AddPropertyTransform<CommonCompilerOptions> DelaySignTransform =>
new AddPropertyTransform<CommonCompilerOptions>("DelaySign",
compilerOptions => compilerOptions.DelaySign.ToString().ToLower(),
compilerOptions => compilerOptions.DelaySign != null && compilerOptions.DelaySign.Value);
private AddPropertyTransform<CommonCompilerOptions> PublicSignTransform =>
new AddPropertyTransform<CommonCompilerOptions>("PublicSign",
compilerOptions => compilerOptions.PublicSign.ToString().ToLower(),
compilerOptions => compilerOptions.PublicSign != null && compilerOptions.PublicSign.Value);
private AddPropertyTransform<CommonCompilerOptions> DebugTypeTransform =>
new AddPropertyTransform<CommonCompilerOptions>("DebugType",
compilerOptions => compilerOptions.DebugType,
compilerOptions => !string.IsNullOrEmpty(compilerOptions.DebugType));
private AddPropertyTransform<CommonCompilerOptions> XmlDocTransform =>
new AddPropertyTransform<CommonCompilerOptions>("GenerateDocumentationFile",
compilerOptions => compilerOptions.GenerateXmlDocumentation.ToString().ToLower(),
compilerOptions => compilerOptions.GenerateXmlDocumentation != null && compilerOptions.GenerateXmlDocumentation.Value);
// TODO: https://github.com/dotnet/sdk/issues/67
private AddPropertyTransform<CommonCompilerOptions> XmlDocTransformFilePath =>
new AddPropertyTransform<CommonCompilerOptions>("DocumentationFile",
@"$(OutputPath)\$(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);
private IncludeContextTransform EmbedFilesTransform =>
new IncludeContextTransform("EmbeddedResource", transformMappings: false);
private IncludeContextTransform CopyToOutputFilesTransform =>
new IncludeContextTransform("Content", transformMappings: true)
.WithMetadata("CopyToOutputDirectory", "PreserveNewest");
private Func<CommonCompilerOptions, string, IEnumerable<ProjectItemElement>> CompileFilesTransformExecute =>
(compilerOptions, projectDirectory) =>
CompileFilesTransform.Transform(GetCompileIncludeContext(compilerOptions, projectDirectory));
private Func<CommonCompilerOptions, string, IEnumerable<ProjectItemElement>> EmbedFilesTransformExecute =>
(compilerOptions, projectDirectory) =>
EmbedFilesTransform.Transform(GetEmbedIncludeContext(compilerOptions, projectDirectory));
private Func<CommonCompilerOptions, string, IEnumerable<ProjectItemElement>> CopyToOutputFilesTransformExecute =>
(compilerOptions, projectDirectory) =>
CopyToOutputFilesTransform.Transform(GetCopyToOutputIncludeContext(compilerOptions, projectDirectory));
private string _configuration;
private ProjectPropertyGroupElement _configurationPropertyGroup;
private ProjectItemGroupElement _configurationItemGroup;
private List<AddPropertyTransform<CommonCompilerOptions>> _propertyTransforms;
private List<Func<CommonCompilerOptions, string, IEnumerable<ProjectItemElement>>> _includeContextTransformExecutes;
private ITransformApplicator _transformApplicator;
public MigrateBuildOptionsRule(ITransformApplicator transformApplicator = null)
{
_transformApplicator = transformApplicator ?? new TransformApplicator();
ConstructTransformLists();
}
public MigrateBuildOptionsRule(
string configuration,
ProjectPropertyGroupElement configurationPropertyGroup,
ProjectItemGroupElement configurationItemGroup,
ITransformApplicator transformApplicator = null)
{
_configuration = configuration;
_configurationPropertyGroup = configurationPropertyGroup;
_configurationItemGroup = configurationItemGroup;
_transformApplicator = transformApplicator ?? new TransformApplicator();
ConstructTransformLists();
}
private void ConstructTransformLists()
{
_propertyTransforms = new List<AddPropertyTransform<CommonCompilerOptions>>()
{
DefineTransform,
NoWarnTransform,
WarningsAsErrorsTransform,
AllowUnsafeTransform,
OptimizeTransform,
PlatformTransform,
LanguageVersionTransform,
DelaySignTransform,
PublicSignTransform,
DebugTypeTransform,
OutputNameTransform,
XmlDocTransform,
XmlDocTransformFilePath,
PreserveCompilationContextTransform
};
_propertyTransforms.AddRange(EmitEntryPointTransforms);
_propertyTransforms.AddRange(KeyFileTransforms);
_includeContextTransformExecutes = new List<Func<CommonCompilerOptions, string, IEnumerable<ProjectItemElement>>>()
{
CompileFilesTransformExecute,
EmbedFilesTransformExecute,
CopyToOutputFilesTransformExecute
};
}
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var csproj = migrationRuleInputs.OutputMSBuildProject;
var projectContext = migrationRuleInputs.DefaultProjectContext;
var propertyGroup = _configurationPropertyGroup ?? migrationRuleInputs.CommonPropertyGroup;
var itemGroup = _configurationItemGroup ?? migrationRuleInputs.CommonItemGroup;
var compilerOptions = projectContext.ProjectFile.GetCompilerOptions(projectContext.TargetFramework, null);
var configurationCompilerOptions =
projectContext.ProjectFile.GetCompilerOptions(projectContext.TargetFramework, _configuration);
// If we're in a configuration, we need to be careful not to overwrite values from BuildOptions
// without a configuration
if (_configuration == null)
{
CleanExistingProperties(csproj);
PerformPropertyAndItemMappings(
compilerOptions,
propertyGroup,
itemGroup,
_transformApplicator,
migrationSettings.ProjectDirectory);
}
else
{
PerformConfigurationPropertyAndItemMappings(
compilerOptions,
configurationCompilerOptions,
propertyGroup,
itemGroup,
_transformApplicator,
migrationSettings.ProjectDirectory);
}
}
private void PerformConfigurationPropertyAndItemMappings(
CommonCompilerOptions compilerOptions,
CommonCompilerOptions configurationCompilerOptions,
ProjectPropertyGroupElement propertyGroup,
ProjectItemGroupElement itemGroup,
ITransformApplicator transformApplicator,
string projectDirectory)
{
foreach (var transform in _propertyTransforms)
{
var nonConfigurationOutput = transform.Transform(compilerOptions);
var configurationOutput = transform.Transform(configurationCompilerOptions);
if (!PropertiesAreEqual(nonConfigurationOutput, configurationOutput))
{
transformApplicator.Execute(configurationOutput, propertyGroup);
}
}
foreach (var includeContextTransformExecute in _includeContextTransformExecutes)
{
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)
{
Console.WriteLine("EXCLUDE");
Console.WriteLine(item1.Exclude);
Console.WriteLine(item2Excludes.ToString());
throw new Exception("Unable to migrate projects with excluded files in configurations.");
}
}
}
private void RemoveCommonIncludes(IEnumerable<ProjectItemElement> itemsToRemoveFrom,
IEnumerable<ProjectItemElement> otherItems)
{
foreach (var item1 in itemsToRemoveFrom)
{
if (item1 == null)
{
continue;
}
foreach (
var item2 in
otherItems.Where(
i => i != null && string.Equals(i.ItemType, item1.ItemType, StringComparison.Ordinal)))
{
item1.Include = string.Join(";", item1.Includes().Except(item2.Includes()));
}
}
}
private bool PropertiesAreEqual(ProjectPropertyElement nonConfigurationOutput, ProjectPropertyElement configurationOutput)
{
if (configurationOutput != null && nonConfigurationOutput != null)
{
return string.Equals(nonConfigurationOutput.Value, configurationOutput.Value, StringComparison.Ordinal);
}
return configurationOutput == nonConfigurationOutput;
}
private void PerformPropertyAndItemMappings(
CommonCompilerOptions compilerOptions,
ProjectPropertyGroupElement propertyGroup,
ProjectItemGroupElement itemGroup,
ITransformApplicator transformApplicator,
string projectDirectory)
{
foreach (var transform in _propertyTransforms)
{
transformApplicator.Execute(transform.Transform(compilerOptions), propertyGroup);
}
foreach (var includeContextTransformExecute in _includeContextTransformExecutes)
{
transformApplicator.Execute(
includeContextTransformExecute(compilerOptions, projectDirectory),
itemGroup,
mergeExisting: true);
}
}
private void CleanExistingProperties(ProjectRootElement csproj)
{
var existingPropertiesToRemove = new [] {"OutputType", "TargetExt"};
foreach (var propertyName in existingPropertiesToRemove)
{
var properties = csproj.Properties.Where(p => p.Name == propertyName);
foreach (var property in properties)
{
property.Parent.RemoveChild(property);
}
}
}
private IncludeContext GetCompileIncludeContext(CommonCompilerOptions compilerOptions, string projectDirectory)
{
// Defaults from src/Microsoft.DotNet.ProjectModel/ProjectReader.cs #L596
return compilerOptions.CompileInclude ??
new IncludeContext(
projectDirectory,
"compile",
new JObject(),
ProjectFilesCollection.DefaultCompileBuiltInPatterns,
ProjectFilesCollection.DefaultBuiltInExcludePatterns);
}
private IncludeContext GetEmbedIncludeContext(CommonCompilerOptions compilerOptions, string projectDirectory)
{
// Defaults from src/Microsoft.DotNet.ProjectModel/ProjectReader.cs #L602
return compilerOptions.EmbedInclude ??
new IncludeContext(
projectDirectory,
"embed",
new JObject(),
ProjectFilesCollection.DefaultResourcesBuiltInPatterns,
ProjectFilesCollection.DefaultBuiltInExcludePatterns);
}
private IncludeContext GetCopyToOutputIncludeContext(CommonCompilerOptions compilerOptions, string projectDirectory)
{
// Defaults from src/Microsoft.DotNet.ProjectModel/ProjectReader.cs #608
return compilerOptions.CopyToOutputInclude ??
new IncludeContext(
projectDirectory,
"copyToOutput",
new JObject(),
null,
ProjectFilesCollection.DefaultPublishExcludePatterns);
}
}
}

View file

@ -0,0 +1,70 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrateConfigurationsRule : IMigrationRule
{
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var projectContext = migrationRuleInputs.DefaultProjectContext;
var configurations = projectContext.ProjectFile.GetConfigurations().ToList();
if (!configurations.Any())
{
return;
}
foreach (var configuration in configurations)
{
MigrateConfiguration(configuration, migrationSettings, migrationRuleInputs);
}
}
private void MigrateConfiguration(
string configuration,
MigrationSettings migrationSettings,
MigrationRuleInputs migrationRuleInputs)
{
var csproj = migrationRuleInputs.OutputMSBuildProject;
var propertyGroup = CreatePropertyGroupAtEndOfProject(csproj);
var itemGroup = CreateItemGroupAtEndOfProject(csproj);
var configurationCondition = $" '$(Configuration)' == '{configuration}' ";
propertyGroup.Condition = configurationCondition;
itemGroup.Condition = configurationCondition;
new MigrateBuildOptionsRule(configuration, propertyGroup, itemGroup)
.Apply(migrationSettings, migrationRuleInputs);
propertyGroup.RemoveIfEmpty();
itemGroup.RemoveIfEmpty();
}
private ProjectPropertyGroupElement CreatePropertyGroupAtEndOfProject(ProjectRootElement csproj)
{
var propertyGroup = csproj.CreatePropertyGroupElement();
csproj.InsertBeforeChild(propertyGroup, csproj.LastChild);
return propertyGroup;
}
private ProjectItemGroupElement CreateItemGroupAtEndOfProject(ProjectRootElement csproj)
{
var itemGroup = csproj.CreateItemGroupElement();
csproj.InsertBeforeChild(itemGroup, csproj.LastChild);
return itemGroup;
}
}
}

View file

@ -0,0 +1,95 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.Tools.Common;
using Project = Microsoft.DotNet.ProjectModel.Project;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrateProjectDependenciesRule : IMigrationRule
{
private readonly ITransformApplicator _transformApplicator;
private string _projectDirectory;
public MigrateProjectDependenciesRule(TransformApplicator transformApplicator = null)
{
_transformApplicator = transformApplicator ?? new TransformApplicator();
}
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
_projectDirectory = migrationSettings.ProjectDirectory;
var csproj = migrationRuleInputs.OutputMSBuildProject;
var projectContext = migrationRuleInputs.DefaultProjectContext;
var projectExports = projectContext.CreateExporter("_").GetDependencies(LibraryType.Project);
var projectDependencyTransformResults =
projectExports.Select(projectExport => ProjectDependencyTransform.Transform(projectExport));
var propertyTransformResults = new []
{
AutoUnifyTransform.Transform(true),
DesignTimeAutoUnifyTransform.Transform(true)
};
if (projectDependencyTransformResults.Any())
{
// Use a new item group for the project references, but the common for properties
var propertyGroup = migrationRuleInputs.CommonPropertyGroup;
var itemGroup = csproj.AddItemGroup();
foreach (var projectDependencyTransformResult in projectDependencyTransformResults)
{
_transformApplicator.Execute(projectDependencyTransformResult, itemGroup);
}
foreach (var propertyTransformResult in propertyTransformResults)
{
_transformApplicator.Execute(propertyTransformResult, propertyGroup);
}
}
}
private AddPropertyTransform<bool> AutoUnifyTransform => new AddPropertyTransform<bool>(
"AutoUnify",
"true",
b => true);
private AddPropertyTransform<bool> DesignTimeAutoUnifyTransform => new AddPropertyTransform<bool>(
"DesignTimeAutoUnify",
"true",
b => true);
private AddItemTransform<LibraryExport> ProjectDependencyTransform => new AddItemTransform<LibraryExport>(
"ProjectReference",
export =>
{
if (!export.Library.Resolved)
{
throw new Exception("Cannot migrate unresolved project dependency, please ensure restore has been run.");
}
var projectFile = ((ProjectDescription)export.Library).Project.ProjectFilePath;
var projectDir = Path.GetDirectoryName(projectFile);
var migratedProjectFileName = Path.GetFileName(projectDir) + ".csproj";
var relativeProjectDir = PathUtility.GetRelativePath(_projectDirectory + "/", projectDir);
return Path.Combine(relativeProjectDir, migratedProjectFileName);
},
export => "",
export => true);
}
}

View file

@ -0,0 +1,52 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Microsoft.DotNet.ProjectModel.Files;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigratePublishOptionsRule : IMigrationRule
{
private readonly ITransformApplicator _transformApplicator;
public MigratePublishOptionsRule(TransformApplicator transformApplicator = null)
{
_transformApplicator = transformApplicator ?? new TransformApplicator();
}
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var csproj = migrationRuleInputs.OutputMSBuildProject;
var projectContext = migrationRuleInputs.DefaultProjectContext;
var transformResults = new[]
{
CopyToOutputFilesTransform.Transform(projectContext.ProjectFile.PublishOptions)
};
if (transformResults.Any(t => t != null && t.Any()))
{
var itemGroup = migrationRuleInputs.CommonItemGroup;
_transformApplicator.Execute(
CopyToOutputFilesTransform.Transform(projectContext.ProjectFile.PublishOptions),
itemGroup,
mergeExisting: true);
}
}
private IncludeContextTransform CopyToOutputFilesTransform =>
new IncludeContextTransform("Content", transformMappings: true)
.WithMetadata("CopyToOutputDirectory", "None")
.WithMetadata("CopyToPublishDirectory", "PreserveNewest");
}
}

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;
using System.Collections.Generic;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Microsoft.DotNet.ProjectModel.Files;
using NuGet.Versioning;
using Project = Microsoft.DotNet.ProjectModel.Project;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrateRootOptionsRule : IMigrationRule
{
private readonly ITransformApplicator _transformApplicator;
private readonly AddPropertyTransform<Project>[] _transforms;
public MigrateRootOptionsRule(TransformApplicator transformApplicator = null)
{
_transformApplicator = transformApplicator ?? new TransformApplicator();
_transforms = new[]
{
DescriptionTransform,
CopyrightTransform,
TitleTransform,
LanguageTransform,
VersionTransform
};
}
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var projectContext = migrationRuleInputs.DefaultProjectContext;
var transformResults = _transforms.Select(t => t.Transform(projectContext.ProjectFile)).ToArray();
if (transformResults.Any())
{
var propertyGroup = migrationRuleInputs.CommonPropertyGroup;
foreach (var transformResult in transformResults)
{
_transformApplicator.Execute(transformResult, propertyGroup);
}
}
}
private AddPropertyTransform<Project> DescriptionTransform => new AddPropertyTransform<Project>("Description",
project => project.Description,
project => !string.IsNullOrEmpty(project.Description));
private AddPropertyTransform<Project> CopyrightTransform => new AddPropertyTransform<Project>("Copyright",
project => project.Copyright,
project => !string.IsNullOrEmpty(project.Copyright));
private AddPropertyTransform<Project> TitleTransform => new AddPropertyTransform<Project>("AssemblyTitle",
project => project.Title,
project => !string.IsNullOrEmpty(project.Title));
private AddPropertyTransform<Project> LanguageTransform => new AddPropertyTransform<Project>("NeutralLanguage",
project => project.Language,
project => !string.IsNullOrEmpty(project.Language));
private AddPropertyTransform<Project> VersionTransform => new AddPropertyTransform<Project>("VersionPrefix",
project => project.Version.ToString(), p => true);
}
}

View file

@ -0,0 +1,38 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrateRuntimeOptionsRule : IMigrationRule
{
private static readonly string s_runtimeOptionsFileName = "runtimeconfig.template.json";
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var projectContext = migrationRuleInputs.DefaultProjectContext;
var raw = projectContext.ProjectFile.RawRuntimeOptions;
var outputRuntimeOptionsFile = Path.Combine(migrationSettings.OutputDirectory, s_runtimeOptionsFileName);
if (!string.IsNullOrEmpty(raw))
{
if (File.Exists(outputRuntimeOptionsFile))
{
throw new Exception("Runtime options file already exists. Has migration already been run?");
}
File.WriteAllText(outputRuntimeOptionsFile, raw);
}
}
}
}

View file

@ -0,0 +1,229 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Microsoft.DotNet.Cli.Utils.CommandParsing;
using Newtonsoft.Json;
using Microsoft.DotNet.ProjectModel.Files;
using Microsoft.DotNet.Tools.Common;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class MigrateScriptsRule : IMigrationRule
{
private static readonly string s_unixScriptExtension = ".sh";
private static readonly string s_windowsScriptExtension = ".cmd";
private readonly ITransformApplicator _transformApplicator;
public MigrateScriptsRule(TransformApplicator transformApplicator = null)
{
_transformApplicator = transformApplicator ?? new TransformApplicator();
}
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var csproj = migrationRuleInputs.OutputMSBuildProject;
var projectContext = migrationRuleInputs.DefaultProjectContext;
var scripts = projectContext.ProjectFile.Scripts;
foreach (var scriptSet in scripts)
{
MigrateScriptSet(csproj, migrationRuleInputs.CommonPropertyGroup, scriptSet.Value, scriptSet.Key);
}
}
public ProjectTargetElement MigrateScriptSet(ProjectRootElement csproj,
ProjectPropertyGroupElement propertyGroup,
IEnumerable<string> scriptCommands,
string scriptSetName)
{
var target = CreateTarget(csproj, scriptSetName);
var count = 0;
foreach (var scriptCommand in scriptCommands)
{
var scriptExtensionPropertyName = AddScriptExtension(propertyGroup, scriptCommand, (++count).ToString());
AddExec(target, FormatScriptCommand(scriptCommand, scriptExtensionPropertyName));
}
return target;
}
private string AddScriptExtension(ProjectPropertyGroupElement propertyGroup, string scriptCommandline, string scriptId)
{
var scriptArguments = CommandGrammar.Process(
scriptCommandline,
(s) => null,
preserveSurroundingQuotes: false);
scriptArguments = scriptArguments.Where(argument => !string.IsNullOrEmpty(argument)).ToArray();
var scriptCommand = scriptArguments.First();
var propertyName = $"MigratedScriptExtension_{scriptId}";
var windowsScriptExtensionProperty = propertyGroup.AddProperty(propertyName,
s_windowsScriptExtension);
var unixScriptExtensionProperty = propertyGroup.AddProperty(propertyName,
s_unixScriptExtension);
windowsScriptExtensionProperty.Condition =
$" '$(OS)' == 'Windows_NT' and Exists('{scriptCommand}{s_windowsScriptExtension}') ";
unixScriptExtensionProperty.Condition =
$" '$(OS)' != 'Windows_NT' and Exists('{scriptCommand}{s_unixScriptExtension}') ";
return propertyName;
}
internal string FormatScriptCommand(string scriptCommandline, string scriptExtensionPropertyName)
{
var command = AddScriptExtensionPropertyToCommandLine(scriptCommandline, scriptExtensionPropertyName);
return ReplaceScriptVariables(command);
}
internal string AddScriptExtensionPropertyToCommandLine(string scriptCommandline,
string scriptExtensionPropertyName)
{
var scriptArguments = CommandGrammar.Process(
scriptCommandline,
(s) => null,
preserveSurroundingQuotes: true);
scriptArguments = scriptArguments.Where(argument => !string.IsNullOrEmpty(argument)).ToArray();
var scriptCommand = scriptArguments.First();
var trimmedCommand = scriptCommand.Trim('\"').Trim('\'');
// Path.IsPathRooted only looks at paths conforming to the current os,
// we need to account for all things
if (!IsPathRootedForAnyOS(trimmedCommand))
{
scriptCommand = @".\" + scriptCommand;
}
if (scriptCommand.EndsWith("\"") || scriptCommand.EndsWith("'"))
{
var endChar = scriptCommand.Last();
scriptCommand = $"{scriptCommand.TrimEnd(endChar)}$({scriptExtensionPropertyName}){endChar}";
}
else
{
scriptCommand += $"$({scriptExtensionPropertyName})";
}
var command = string.Join(" ", new[] {scriptCommand}.Concat(scriptArguments.Skip(1)));
return command;
}
internal string ReplaceScriptVariables(string command)
{
foreach (var scriptVariableEntry in ScriptVariableToMSBuildMap)
{
var scriptVariableName = scriptVariableEntry.Key;
var msbuildMapping = scriptVariableEntry.Value;
if (command.Contains($"%{scriptVariableName}%"))
{
if (msbuildMapping == null)
{
throw new Exception(
$"{scriptVariableName} is currently an unsupported script variable for project migration");
}
command = command.Replace($"%{scriptVariableName}%", msbuildMapping);
}
}
return command;
}
private bool IsPathRootedForAnyOS(string path)
{
return path.StartsWith("/") || path.Substring(1).StartsWith(":\\");
}
private ProjectTargetElement CreateTarget(ProjectRootElement csproj, string scriptSetName)
{
var targetName = $"{scriptSetName[0].ToString().ToUpper()}{string.Concat(scriptSetName.Skip(1))}Script";
var targetHookInfo = ScriptSetToMSBuildHookTargetMap[scriptSetName];
var target = csproj.AddTarget(targetName);
if (targetHookInfo.IsRunBefore)
{
target.BeforeTargets = targetHookInfo.TargetName;
}
else
{
target.AfterTargets = targetHookInfo.TargetName;
}
return target;
}
private void AddExec(ProjectTargetElement target, string command)
{
var task = target.AddTask("Exec");
task.SetParameter("Command", command);
}
// ProjectJson Script Set Name to
private static Dictionary<string, TargetHookInfo> ScriptSetToMSBuildHookTargetMap => new Dictionary<string, TargetHookInfo>()
{
{ "precompile", new TargetHookInfo(true, "Build") },
{ "postcompile", new TargetHookInfo(false, "Build") },
{ "prepublish", new TargetHookInfo(true, "Publish") },
{ "postpublish", new TargetHookInfo(false, "Publish") }
};
private static Dictionary<string, string> ScriptVariableToMSBuildMap => new Dictionary<string, string>()
{
{ "compile:TargetFramework", null }, // TODO: Need Short framework name in CSProj
{ "compile:ResponseFile", null }, // Not migrated
{ "compile:CompilerExitCode", null }, // Not migrated
{ "compile:RuntimeOutputDir", null }, // Not migrated
{ "compile:RuntimeIdentifier", null },// TODO: Need Rid in CSProj
{ "publish:TargetFramework", null }, // TODO: Need Short framework name in CSProj
{ "publish:Runtime", null }, // TODO: Need Rid in CSProj
{ "compile:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)" },
{ "compile:Configuration", "$(Configuration)" },
{ "compile:OutputFile", "$(TargetPath)" },
{ "compile:OutputDir", "$(TargetDir)" },
{ "publish:ProjectPath", "$(MSBuildThisFileDirectory)" },
{ "publish:Configuration", "$(Configuration)" },
{ "publish:OutputPath", "$(TargetDir)" },
{ "publish:FullTargetFramework", "$(TargetFrameworkIdentifier)=$(TargetFrameworkVersion)" },
{ "project:Directory", "$(MSBuildProjectDirectory)" },
{ "project:Name", "$(MSBuildThisFileName)" },
{ "project:Version", "$(Version)" }
};
private class TargetHookInfo
{
public bool IsRunBefore { get; }
public string TargetName { get; }
public string BeforeAfterTarget
{
get
{
return IsRunBefore ? "BeforeTargets" : "AfterTargets";
}
}
public TargetHookInfo(bool isRunBefore, string targetName)
{
IsRunBefore = isRunBefore;
TargetName = targetName;
}
}
}
}

View file

@ -0,0 +1,93 @@
using System;
using System.Text;
using System.Globalization;
using Microsoft.Build.Construction;
using System.Linq;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectJsonMigration
{
// TODO: Support Multi-TFM
public class MigrateTFMRule : IMigrationRule
{
private readonly ITransformApplicator _transformApplicator;
private readonly AddPropertyTransform<NuGetFramework>[] _transforms;
public MigrateTFMRule(TransformApplicator transformApplicator = null)
{
_transformApplicator = transformApplicator ?? new TransformApplicator();
_transforms = new AddPropertyTransform<NuGetFramework>[]
{
OutputPathTransform,
FrameworkIdentifierTransform,
FrameworkVersionTransform
};
}
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var csproj = migrationRuleInputs.OutputMSBuildProject;
var propertyGroup = migrationRuleInputs.CommonPropertyGroup;
CleanExistingProperties(csproj);
foreach (var transform in _transforms)
{
_transformApplicator.Execute(
transform.Transform(migrationRuleInputs.DefaultProjectContext.TargetFramework),
propertyGroup);
}
}
private void CleanExistingProperties(ProjectRootElement csproj)
{
var existingPropertiesToRemove = new string[] { "TargetFrameworkIdentifier", "TargetFrameworkVersion" };
foreach (var propertyName in existingPropertiesToRemove)
{
var properties = csproj.Properties.Where(p => p.Name == propertyName);
foreach (var property in properties)
{
property.Parent.RemoveChild(property);
}
}
}
// Taken from private NuGet.Frameworks method
// https://github.com/NuGet/NuGet.Client/blob/33b8f85a94b01f805f1e955f9b68992b297fad6e/src/NuGet.Core/NuGet.Frameworks/NuGetFramework.cs#L234
private static string GetDisplayVersion(Version version)
{
var sb = new StringBuilder(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version.Major, version.Minor));
if (version.Build > 0
|| version.Revision > 0)
{
sb.AppendFormat(CultureInfo.InvariantCulture, ".{0}", version.Build);
if (version.Revision > 0)
{
sb.AppendFormat(CultureInfo.InvariantCulture, ".{0}", version.Revision);
}
}
return sb.ToString();
}
// TODO: When we have this inferred in the sdk targets, we won't need this
private AddPropertyTransform<NuGetFramework> OutputPathTransform =>
new AddPropertyTransform<NuGetFramework>("OutputPath",
f => $"bin/$(Configuration)/{f.GetShortFolderName()}",
f => true);
private AddPropertyTransform<NuGetFramework> FrameworkIdentifierTransform =>
new AddPropertyTransform<NuGetFramework>("TargetFrameworkIdentifier",
f => f.Framework,
f => true);
private AddPropertyTransform<NuGetFramework> FrameworkVersionTransform =>
new AddPropertyTransform<NuGetFramework>("TargetFrameworkVersion",
f => "v" + GetDisplayVersion(f.Version),
f => true);
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.DotNet.ProjectJsonMigration
{
// TODO: XProj ProjectToProject references
public class MigrateXprojProjectReferencesRule : IMigrationRule
{
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,29 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Microsoft.DotNet.ProjectModel.Files;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class SaveOutputProjectRule : IMigrationRule
{
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
var outputName = Path.GetFileNameWithoutExtension(
migrationRuleInputs.DefaultProjectContext.GetOutputPaths("_").CompilationFiles.Assembly);
var outputProject = Path.Combine(migrationSettings.OutputDirectory, outputName + ".csproj");
migrationRuleInputs.OutputMSBuildProject.Save(outputProject);
}
}
}

View file

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using System.IO;
using Newtonsoft.Json.Linq;
using System.Text;
namespace Microsoft.DotNet.ProjectJsonMigration
{
/// <summary>
/// This rule is temporary while project.json still exists in the new project system.
/// It renames your existing project.json (if output directory is the current project directory),
/// creates a copy, then mutates that copy.
///
/// Mutations:
/// - inject a dependency on the Microsoft.SDK targets
/// - removing the "runtimes" node.
/// </summary>
public class TemporaryMutateProjectJsonRule : IMigrationRule
{
private static string s_sdkPackageName => "Microsoft.DotNet.Core.Sdk";
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
bool shouldRenameOldProject = PathsAreEqual(migrationSettings.OutputDirectory, migrationSettings.ProjectDirectory);
if (!shouldRenameOldProject && File.Exists(Path.Combine(migrationSettings.OutputDirectory, "project.json")))
{
// TODO: should there be a setting to overwrite anything in output directory?
throw new Exception("Existing project.json found in output directory.");
}
var sourceProjectFile = Path.Combine(migrationSettings.ProjectDirectory, "project.json");
var destinationProjectFile = Path.Combine(migrationSettings.OutputDirectory, "project.json");
if (shouldRenameOldProject)
{
var renamedProjectFile = Path.Combine(migrationSettings.ProjectDirectory, "project.migrated.json");
File.Move(sourceProjectFile, renamedProjectFile);
sourceProjectFile = renamedProjectFile;
}
var json = CreateDestinationProjectFile(sourceProjectFile, destinationProjectFile);
InjectSdkReference(json, s_sdkPackageName, migrationSettings.SdkPackageVersion);
RemoveRuntimesNode(json);
File.WriteAllText(destinationProjectFile, json.ToString());
}
private JObject CreateDestinationProjectFile(string sourceProjectFile, string destinationProjectFile)
{
File.Copy(sourceProjectFile, destinationProjectFile);
return JObject.Parse(File.ReadAllText(destinationProjectFile));
}
private void InjectSdkReference(JObject json, string sdkPackageName, string sdkPackageVersion)
{
JToken dependenciesNode;
if (json.TryGetValue("dependencies", out dependenciesNode))
{
var dependenciesNodeObject = dependenciesNode.Value<JObject>();
dependenciesNodeObject.Add(sdkPackageName, sdkPackageVersion);
}
else
{
var dependenciesNodeObject = new JObject();
dependenciesNodeObject.Add(sdkPackageName, sdkPackageVersion);
json.Add("dependencies", dependenciesNodeObject);
}
}
private void RemoveRuntimesNode(JObject json)
{
json.Remove("runtimes");
}
private bool PathsAreEqual(params string[] paths)
{
var normalizedPaths = paths.Select(path => Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar)).ToList();
for (int i=1; i<normalizedPaths.Count(); ++i)
{
var path1 = normalizedPaths[i - 1];
var path2 = normalizedPaths[i];
if (!string.Equals(path1, path2, StringComparison.Ordinal))
{
return false;
}
}
return true;
}
}
}

View file

@ -0,0 +1,32 @@
{
"version": "1.0.0-featmsbuild-*",
"buildOptions": {
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.DotNet.Compiler.Common": {
"target": "project"
},
"Microsoft.DotNet.Cli.Utils": {
"target": "project"
},
"Microsoft.DotNet.ProjectModel": {
"target": "project"
},
"Microsoft.Win32.Registry": {
"version": "4.0.0",
"exclude": "compile"
},
"MSBuild": "0.1.0-preview-00029-160805",
"Microsoft.Build.Framework": "0.1.0-preview-00029-160805",
"Microsoft.Build.Tasks.Core": "0.1.0-preview-00029-160805",
"Microsoft.Build.Utilities.Core": "0.1.0-preview-00029-160805",
"Microsoft.Build.Targets": "0.1.0-preview-00029-160805",
"Microsoft.Build": "0.1.0-preview-00029-160805",
},
"frameworks": {
"netcoreapp1.0": {
"imports": ["dnxcore50","netstandardapp1.5","portable-net45+win8", "portable-net45+wp80+win8+wpa81+dnxcore50"]
}
}
}

View file

@ -0,0 +1,25 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class AddBoolPropertyTransform : AddPropertyTransform<bool>
{
public AddBoolPropertyTransform(string propertyName)
: base(propertyName, b => b.ToString(), b => b) { }
public AddBoolPropertyTransform(string propertyName, Func<bool, bool> condition)
: base(propertyName, b => b.ToString(), condition) { }
}
}

View file

@ -0,0 +1,124 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class AddItemTransform<T> : ConditionalTransform<T, ProjectItemElement>
{
private ProjectRootElement _itemObjectGenerator = ProjectRootElement.Create();
private string _itemName;
private string _includeValue;
private string _excludeValue;
private Func<T, string> _includeValueFunc;
private Func<T, string> _excludeValueFunc;
private bool _mergeExisting;
private List<ItemMetadataValue<T>> _metadata = new List<ItemMetadataValue<T>>();
public AddItemTransform(
string itemName,
IEnumerable<string> includeValues,
IEnumerable<string> excludeValues,
Func<T, bool> condition,
bool mergeExisting = false)
: this(itemName, string.Join(";", includeValues), string.Join(";", excludeValues), condition, mergeExisting) { }
public AddItemTransform(
string itemName,
Func<T, string> includeValueFunc,
Func<T, string> excludeValueFunc,
Func<T, bool> condition)
: base(condition)
{
_itemName = itemName;
_includeValueFunc = includeValueFunc;
_excludeValueFunc = excludeValueFunc;
}
public AddItemTransform(
string itemName,
string includeValue,
Func<T, string> excludeValueFunc,
Func<T, bool> condition)
: base(condition)
{
_itemName = itemName;
_includeValue = includeValue;
_excludeValueFunc = excludeValueFunc;
}
public AddItemTransform(
string itemName,
Func<T, string> includeValueFunc,
string excludeValue,
Func<T, bool> condition)
: base(condition)
{
_itemName = itemName;
_includeValueFunc = includeValueFunc;
_excludeValue = excludeValue;
}
public AddItemTransform(
string itemName,
string includeValue,
string excludeValue,
Func<T, bool> condition,
bool mergeExisting=false)
: base(condition)
{
_itemName = itemName;
_includeValue = includeValue;
_excludeValue = excludeValue;
_mergeExisting = mergeExisting;
}
public AddItemTransform<T> WithMetadata(string metadataName, string metadataValue)
{
_metadata.Add(new ItemMetadataValue<T>(metadataName, metadataValue));
return this;
}
public AddItemTransform<T> WithMetadata(string metadataName, Func<T, string> metadataValueFunc)
{
_metadata.Add(new ItemMetadataValue<T>(metadataName, metadataValueFunc));
return this;
}
public AddItemTransform<T> WithMetadata(ItemMetadataValue<T> metadata)
{
_metadata.Add(metadata);
return this;
}
public override ProjectItemElement ConditionallyTransform(T source)
{
string includeValue = _includeValue ?? _includeValueFunc(source);
string excludeValue = _excludeValue ?? _excludeValueFunc(source);
var item = _itemObjectGenerator.AddItem(_itemName, includeValue);
item.Exclude = excludeValue;
foreach (var metadata in _metadata)
{
item.AddMetadata(metadata.MetadataName, metadata.GetMetadataValue(source));
}
return item;
}
}
}

View file

@ -0,0 +1,54 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class AddPropertyTransform<T> : ConditionalTransform<T, ProjectPropertyElement>
{
public string PropertyName { get; }
private readonly ProjectRootElement _propertyObjectGenerator = ProjectRootElement.Create();
private readonly string _propertyValue;
private readonly Func<T,string> _propertyValueFunc;
public AddPropertyTransform(string propertyName, string propertyValue, Func<T,bool> condition)
: base(condition)
{
PropertyName = propertyName;
_propertyValue = propertyValue;
}
public AddPropertyTransform(string propertyName, Func<T, string> propertyValueFunc, Func<T,bool> condition)
: base(condition)
{
PropertyName = propertyName;
_propertyValueFunc = propertyValueFunc;
}
public override ProjectPropertyElement ConditionallyTransform(T source)
{
string propertyValue = GetPropertyValue(source);
var property = _propertyObjectGenerator.CreatePropertyElement(PropertyName);
property.Value = propertyValue;
return property;
}
public string GetPropertyValue(T source)
{
return _propertyValue ?? _propertyValueFunc(source);
}
}
}

View file

@ -0,0 +1,25 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class AddStringPropertyTransform : AddPropertyTransform<string>
{
public AddStringPropertyTransform(string propertyName)
: base(propertyName, s => s, s => !string.IsNullOrEmpty(s)) { }
public AddStringPropertyTransform(string propertyName, Func<string, bool> condition)
: base(propertyName, s => s, s => !string.IsNullOrEmpty(s) && condition(s)) { }
}
}

View file

@ -0,0 +1,38 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public abstract class ConditionalTransform<T, U> : ITransform<T, U>
{
private Func<T, bool> _condition;
public ConditionalTransform(Func<T,bool> condition)
{
_condition = condition;
}
public U Transform(T source)
{
if (_condition == null || _condition(source))
{
return ConditionallyTransform(source);
}
return default(U);
}
public abstract U ConditionallyTransform(T source);
}
}

View file

@ -0,0 +1,9 @@
using Microsoft.Build.Construction;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public interface ITransform<T, U>
{
U Transform(T source);
}
}

View file

@ -0,0 +1,26 @@
using System.Collections.Generic;
using Microsoft.Build.Construction;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public interface ITransformApplicator
{
void Execute<T, U>(
T element,
U destinationElement) 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);
}
}

View file

@ -0,0 +1,144 @@
using Microsoft.DotNet.ProjectModel.Files;
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.ProjectJsonMigration
{
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 string _itemName;
private bool _transformMappings;
private List<ItemMetadataValue<IncludeContext>> _metadata = new List<ItemMetadataValue<IncludeContext>>();
private AddItemTransform<IncludeContext>[] _transformSet;
public IncludeContextTransform(
string itemName,
bool transformMappings = true,
Func<IncludeContext, bool> condition = null) : base(condition)
{
_itemName = itemName;
_transformMappings = transformMappings;
CreateTransformSet();
}
public IncludeContextTransform WithMetadata(string metadataName, string metadataValue)
{
_metadata.Add(new ItemMetadataValue<IncludeContext>(metadataName, metadataValue));
return this;
}
public IncludeContextTransform WithMetadata(string metadataName, Func<IncludeContext, string> metadataValueFunc)
{
_metadata.Add(new ItemMetadataValue<IncludeContext>(metadataName, metadataValueFunc));
return this;
}
private void CreateTransformSet()
{
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)
{
includeFilesExcludeFilesTransformation.WithMetadata(metadata);
includeExcludeTransformation.WithMetadata(metadata);
}
_transformSet = new AddItemTransform<IncludeContext>[]
{
includeFilesExcludeFilesTransformation,
includeExcludeTransformation
};
}
private string FormatPatterns(IEnumerable<string> patterns, string projectDirectory)
{
List<string> mutatedPatterns = new List<string>(patterns.Count());
foreach (var pattern in patterns)
{
// Do not use forward slashes
// https://github.com/Microsoft/msbuild/issues/724
var mutatedPattern = pattern.Replace('/', '\\');
// MSBuild cannot copy directories
mutatedPattern = ReplaceDirectoriesWithGlobs(mutatedPattern, projectDirectory);
mutatedPatterns.Add(mutatedPattern);
}
return string.Join(";", mutatedPatterns);
}
private string ReplaceDirectoriesWithGlobs(string pattern, string projectDirectory)
{
if (PatternIsDirectory(pattern, projectDirectory))
{
return $"{pattern.TrimEnd(new char[] { '\\' })}\\**\\*";
}
else
{
return pattern;
}
}
private bool PatternIsDirectory(string pattern, string projectDirectory)
{
// TODO: what about /some/path/**/somedir?
// Should this even be migrated?
var path = pattern;
if (!Path.IsPathRooted(path))
{
path = Path.Combine(projectDirectory, path);
}
return Directory.Exists(path);
}
public override IEnumerable<ProjectItemElement> ConditionallyTransform(IncludeContext source)
{
return _transformSet.Select(t => t.Transform(source));
}
}
}

View file

@ -0,0 +1,170 @@
// 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.Evaluation;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class TransformApplicator : ITransformApplicator
{
private readonly ProjectRootElement _projectElementGenerator = ProjectRootElement.Create();
public void Execute<T, U>(
T element,
U destinationElement) where T : ProjectElement where U : ProjectElementContainer
{
if (element != null)
{
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))
{
var property = destinationElement.ContainingProject.CreatePropertyElement("___TEMP___");
property.CopyFrom(element);
destinationElement.AppendChild(property);
}
else
{
throw new Exception("Unable to add unknown project element to project");
}
}
}
public void Execute<T, U>(
IEnumerable<T> elements,
U destinationElement) where T : ProjectElement where U : ProjectElementContainer
{
foreach (var element in elements)
{
Execute(element, destinationElement);
}
}
public void Execute(
ProjectItemElement item,
ProjectItemGroupElement destinationItemGroup,
bool mergeExisting)
{
if (item == null)
{
return;
}
if (mergeExisting)
{
var existingItems = FindExistingItems(item, destinationItemGroup.ContainingProject);
foreach (var existingItem in existingItems)
{
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);
}
// Item will be null only when it's entire set of includes is merged with existing items
if (item != null)
{
Execute(item, destinationItemGroup);
}
}
else
{
Execute(item, destinationItemGroup);
}
}
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.CommonIncludes(existingItem).Any())
{
throw new InvalidOperationException("Cannot merge items without a common include.");
}
var commonIncludes = item.CommonIncludes(existingItem).ToList();
item.RemoveIncludes(commonIncludes);
existingItem.RemoveIncludes(commonIncludes);
var mergedItem = _projectElementGenerator.AddItem(item.ItemType, string.Join(";", commonIncludes));
mergedItem.AddExcludes(existingItem.Excludes());
mergedItem.AddExcludes(item.Excludes());
mergedItem.AddMetadata(existingItem.Metadata);
mergedItem.AddMetadata(item.Metadata);
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> FindExistingItems(ProjectItemElement item, ProjectRootElement project)
{
return project.ItemsWithoutConditions()
.Where(i => string.Equals(i.ItemType, item.ItemType, StringComparison.Ordinal))
.Where(i => i.CommonIncludes(item).Any());
}
private class MergeResult
{
public ProjectItemElement InputItem { get; set; }
public ProjectItemElement ExistingItem { get; set; }
public ProjectItemElement MergedItem { get; set; }
}
}
}